Unity角色残影特效支持gpuInstancing

这篇博客介绍了在Unity中实现角色残影的技巧,包括传统方法和GPU实例化(GPUInstancing)的优化方案。通过GPU实例化,可以减少DrawCall并提高性能,尤其在场景中存在多个角色残影时。同时,提供了支持URP(Unity Render Pipeline)的自定义shader代码,以实现残影效果并调整边缘颜色强度。
摘要由CSDN通过智能技术生成

 

网上的角色残影实现非常的多,不过大致思想都差不多。

    这次作者也不例外,唯一不一样的是作者还实现了节省性能的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
		}
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IChessChess

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值