网上的角色残影实现非常的多,不过大致思想都差不多。
这次作者也不例外,唯一不一样的是作者还实现了节省性能的GPUInstancing的处理,另外支持URP的shader代码也一并给出了。
传统的做法是将角色所有的SkinnedMeshRenderer 拷贝一份出来然后自己把拷贝的mesh通过半透明材质画出来,拷贝mesh这个过程的消耗其实很小不用担心,要担心的是SkinnedMeshRenderer网格可能面数会比较多,残影一下子生成很多个,且每个残影一个DrawCall,场景有很多角色同时出现残影可能会对性能造成瓶颈。
简化的办法就是给定一个简单的减面Mesh,在移动的时候用GpuInstancing画这个Mesh,虽然这样做残影都是一个动作,但Mesh面数少了很多,且由于GpuInstancing画出来所以一个角色残影产生的DrawCall就一个。URP shader代码注释掉了,要用就把注释去掉吧。
using UnityEngine;
using System.Collections.Generic;
using Unity.Collections;
namespace Chess
{
//GPUInstancing所使用的数据
public class InstancingData
{
public List<Matrix4x4> m_Matris = new List<Matrix4x4>();
public List<Vector4> m_Colors = new List<Vector4>();
public List<float> m_RimIntensitys = new List<float>();
public MaterialPropertyBlock m_Mpbs = new MaterialPropertyBlock();
public void Clear()
{
m_Matris.Clear();
m_Colors.Clear();
m_RimIntensitys.Clear();
}
public void Apply()
{
if (m_Colors.Count == 0)
return;
m_Mpbs.Clear();
m_Mpbs.SetVectorArray("_Color", m_Colors);
m_Mpbs.SetFloatArray("_RimIntensity", m_RimIntensitys);
}
}
public class GhostShadow : MonoBehaviour
{
[ReadOnly]public Camera m_Camera;
//持续时间
public float m_Duration = 1f;
public Color m_XrColor = Color.white;
//创建新残影间隔
public float m_Interval = 0.1f;
//边缘颜色强度
[Range(0, 10)]
public float m_Intension = 1;
//mesh自定义mesh
public Mesh m_Mesh;
//默认mesh使用instancing
InstancingData m_instancingData;
//网格数据
SkinnedMeshRenderer[] m_MeshRender;
//X-ray
Shader m_GhostShader;
Material m_Material;
List<GhostItem> m_ItemList = new List<GhostItem>();
List<GhostItem> m_WillRecycleItem = new List<GhostItem>();
void Start()
{
//获取身上所有的Mesh
m_MeshRender = gameObject.GetComponentsInChildren<SkinnedMeshRenderer>();
m_Camera = Camera.main;
if(m_Mesh)
{
m_GhostShader = Shader.Find("Hidden/ghostInstancing");
m_instancingData = new InstancingData();
}
else
{
m_GhostShader = Shader.Find("Hidden/ghost");
}
m_Material = new Material(m_GhostShader);
m_Material.enableInstancing = true;
}
private float lastTime = 0;
private Vector3 lastPos = Vector3.zero;
void Update() {
if (m_ItemList.Count > 0)
{
if (m_Mesh == null)
{
//遍历updata,并画
for (int i = 0; i < m_ItemList.Count; i++)
{
GhostItem item = m_ItemList[i];
Graphics.DrawMesh(item.m_Mesh, item.m_Matri, m_Material, 0, m_Camera, 0, item.m_Block);
item.Update();
}
}
else
{
m_instancingData.Clear();
//遍历updata,并画
for (int i = 0; i < m_ItemList.Count; i++)
{
GhostItem item = m_ItemList[i];
m_instancingData.m_Matris.Add(item.m_Matri);
m_instancingData.m_Colors.Add(item.m_Block.GetColor("_Color"));
m_instancingData.m_RimIntensitys.Add(item.m_Block.GetFloat("_RimIntensity"));
item.Update();
}
m_instancingData.Apply();
//自定义mesh,使用gpu Instancing
Graphics.DrawMeshInstanced(m_Mesh, 0, m_Material, m_instancingData.m_Matris.ToArray(), m_ItemList.Count, m_instancingData.m_Mpbs);
}
}
//删除没用的残影控制器
foreach (var item in m_WillRecycleItem)
{
m_ItemList.Remove(item);
}
if(m_WillRecycleItem.Count != 0)
m_WillRecycleItem.Clear();
//人物有位移才创建残影
if (lastPos == this.transform.position)
return;
lastPos = this.transform.position;
//残影间隔时间
if (Time.time - lastTime < m_Interval)
return;
lastTime = Time.time;
if (m_MeshRender == null)
return;
if(m_Mesh == null)
{
for (int i = 0; i < m_MeshRender.Length; i++)
{
Mesh mesh = new Mesh();
m_MeshRender[i].BakeMesh(mesh);
GhostItem item = new GhostItem();
item.Init(mesh, m_Duration, Time.time + m_Duration, m_Intension, m_XrColor, m_MeshRender[i].localToWorldMatrix, ItemOnEnd);
m_ItemList.Add(item);
}
}
else
{
GhostItem item = new GhostItem();
item.Init(m_Mesh, m_Duration, Time.time + m_Duration, m_Intension, m_XrColor, transform.localToWorldMatrix, ItemOnEnd);
m_ItemList.Add(item);
}
}
//该影子结束的回调
void ItemOnEnd(GhostItem item)
{
m_WillRecycleItem.Add(item);
}
}
}
using System;
using UnityEngine;
namespace Chess
{
public class GhostItem
{
//持续时间
float m_Duration;
//销毁时间
float m_DeleteTime;
Color m_Color;
public Mesh m_Mesh;
public Matrix4x4 m_Matri;
public MaterialPropertyBlock m_Block = new MaterialPropertyBlock();
Action<GhostItem> m_Action;
public void Init(Mesh mesh, float duration, float deleteTime, float intension, Color color, Matrix4x4 matri, Action<GhostItem> action)
{
m_Duration = duration;
m_DeleteTime = deleteTime;
m_Mesh = mesh;
m_Matri = matri;
m_Color = color;
m_Action = action;
m_Block.SetFloat("_RimIntensity", intension);
m_Block.SetColor("_Color", m_Color);
}
public void Clear()
{
m_Block.Clear();
m_Mesh = null;
m_Action = null;
}
public void Update(){
float tempTime = m_DeleteTime - Time.time;
if (tempTime <= 0)
{ if(m_Action != null)
{
m_Action.Invoke(this);
}
}
else
{
float rate = tempTime / m_Duration;//计算生命周期的比例);
m_Color.a *= rate;//设置透明通道
m_Block.SetColor("_Color", m_Color);
}
}
}
}
Shader "Hidden/ghost"
{
/*
SubShader
{
Tags { "Queue"="Transparent" "RenderPopeline" = "UniversalPipeline"}
Blend SrcAlpha OneMinusSrcAlpha
Cull Back
ZWrite Off
ZTest LEqual
Pass
{
HLSLPROGRAM
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
struct Attributes
{
float4 vertex : POSITION;
float4 color : COLOR;
float3 normal : NORMAL;
};
struct Varyings
{
float4 vertex : SV_POSITION;
float4 color : COLOR;
};
CBUFFER_START(UnityPerMaterial)
float4 _Color;
float _RimIntensity;
CBUFFER_END
Varyings vert ( Attributes v )
{
Varyings o = (Varyings)0;
o.vertex = TransformObjectToHClip(v.vertex);
half3 viewDir = GetCameraPositionWS() - TransformObjectToWorld(v.vertex);
viewDir = normalize(viewDir);
float val = 1 - saturate(dot(TransformObjectToWorldNormal(v.normal), viewDir));//计算点乘值
o.color= _Color * val * (1 + _RimIntensity);//计算强度
return o;
}
half4 frag (Varyings i ) : SV_Target
{
return i.color;
}
ENDHLSL
}
}
*/
SubShader
{
Tags { "Queue"="Transparent" }
LOD 100
Blend SrcAlpha OneMinusSrcAlpha
Cull Back
ZWrite Off
ZTest LEqual
Pass
{
Name "Unlit"
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float4 color : COLOR;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 color : COLOR;
};
float4 _Color;
float _RimIntensity;
v2f vert ( appdata v )
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
float3 viewDir = normalize(ObjSpaceViewDir(v.vertex));//计算出顶点到相机的向量
float val = 1 - saturate(dot(v.normal, viewDir));//计算点乘值
o.color = _Color * val * (1 + _RimIntensity);//计算强度
return o;
}
fixed4 frag (v2f i ) : SV_Target
{
return i.color;
}
ENDCG
}
}
}
Shader "Hidden/ghostInstancing"
{
/*
SubShader
{
Tags { "Queue"="Transparent" "RenderPopeline" = "UniversalPipeline"}
Blend SrcAlpha OneMinusSrcAlpha
Cull Back
ZWrite Off
ZTest LEqual
Pass
{
HLSLPROGRAM
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#pragma target 2.0
struct Attributes
{
float4 vertex : POSITION;
float4 color : COLOR;
float3 normal : NORMAL;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float4 vertex : SV_POSITION;
float4 color : COLOR;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
CBUFFER_START(UnityPerMaterial)
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_DEFINE_INSTANCED_PROP(float, _RimIntensity)
UNITY_INSTANCING_BUFFER_END(Props)
CBUFFER_END
Varyings vert ( Attributes v )
{
Varyings o = (Varyings)0;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
//UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
o.vertex = TransformObjectToHClip(v.vertex);
half3 viewDir = GetCameraPositionWS() - TransformObjectToWorld(v.vertex);
viewDir = normalize(viewDir);
float val = 1 - saturate(dot(TransformObjectToWorldNormal(v.normal), viewDir));//计算点乘值
o.color= UNITY_ACCESS_INSTANCED_PROP(Props, _Color) * val * (1 + UNITY_ACCESS_INSTANCED_PROP(Props, _RimIntensity));//计算强度
return o;
}
half4 frag (Varyings i ) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
return i.color;
}
ENDHLSL
}
}
*/
SubShader
{
Tags { "Queue"="Transparent" }
LOD 100
Blend SrcAlpha OneMinusSrcAlpha
Cull Back
ZWrite Off
ZTest LEqual
Pass
{
Name "Unlit"
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_instancing
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float4 color : COLOR;
float3 normal : NORMAL;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 color : COLOR;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_DEFINE_INSTANCED_PROP(float, _RimIntensity)
UNITY_INSTANCING_BUFFER_END(Props)
v2f vert ( appdata v )
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
o.vertex = UnityObjectToClipPos(v.vertex);
float3 viewDir = normalize(ObjSpaceViewDir(v.vertex));//计算出顶点到相机的向量
float val = 1 - saturate(dot(v.normal, viewDir));//计算点乘值
o.color = UNITY_ACCESS_INSTANCED_PROP(Props, _Color) * val * (1 + UNITY_ACCESS_INSTANCED_PROP(Props, _RimIntensity));//计算强度
return o;
}
fixed4 frag (v2f i ) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
return i.color;
}
ENDCG
}
}
}