Unity实时运动残影特效

来源:互联网 发布:c语言证明哥德巴赫猜想 编辑:程序博客网 时间:2024/05/03 15:58

效果图:


实现原理:

复制模型当前时间点的Mesh,然后将Mesh全部绘制出来,并修改Shader的Alpha通道实现淡出的效果

方法一:

由于骨骼蒙皮动画的Mesh是随着动画改变的,要捕捉人物变化的形态需要实时获取人物Mesh并渲染出来。

1.获取当前Game Object节点下的所有Mesh:

带有蒙皮的需要通过SkinnedMeshRenderer组件,调用SkinnedMeshRenderer.BakeMesh(Mesh)获取当前动画的实时Mesh。
没有动画的通过MeshFilter获取Mesh

2.创建GameObject将Mesh渲染出来

代码:
using System.Collections;using System.Collections.Generic;using UnityEngine;public class ShadowTail : MonoBehaviour{    private MeshFilter[] meshFilter = null;    private SkinnedMeshRenderer[] skinMeshRender = null;    private Animator animt = null;    [Range(1f, 10f)]    public float createSpeed = 5f;//创建残影的时间间隔    public float fadeOutSpeed = 1.5f;//淡出的速率    private float previousTime = 0;//用于纪录上次创建残影的时间点    private void Awake()    {        animt = GetComponent<Animator>();        meshFilter = GetComponentsInChildren<MeshFilter>();        skinMeshRender = GetComponentsInChildren<SkinnedMeshRenderer>();    }void Update()    {        if (animt.GetFloat("speed") > 0.1 && (animt.GetBool("b_walk") || animt.GetBool("b_walkback")))//疾跑时显示残影        {            if (Time.time - previousTime >= createSpeed * Time.deltaTime)            {                //每隔时段创建一个幻影                GameObject shadowObj = new GameObject("Shadow");                foreach (MeshFilter tempMF in meshFilter)                {                    GameObject obj = new GameObject("Mesh");                    obj.transform.SetPositionAndRotation(tempMF.transform.position, tempMF.transform.rotation);                    MeshFilter mf = obj.AddComponent<MeshFilter>();                    MeshRenderer mr = obj.AddComponent<MeshRenderer>();                    //禁止投射阴影和接收阴影                    mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;                    mr.receiveShadows = false;                    mf.mesh = tempMF.mesh;                    mr.material.shader = Shader.Find("Shader Forge/XRay");                    obj.transform.SetParent(shadowObj.transform);                    StartCoroutine(FadeOut(mr));                }                foreach (SkinnedMeshRenderer tempMR in skinMeshRender)                {                    GameObject obj = new GameObject("SkinMesh");                    obj.transform.SetPositionAndRotation(tempMR.transform.position, tempMR.transform.rotation);                    MeshFilter mf = obj.AddComponent<MeshFilter>();                    MeshRenderer mr = obj.AddComponent<MeshRenderer>();                    mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;                    mr.receiveShadows = false;                    tempMR.BakeMesh(mf.mesh);                    mr.material.shader = Shader.Find("Shader Forge/XRay");//残影Shader                    obj.transform.SetParent(shadowObj.transform);                    StartCoroutine(FadeOut(mr));                }                previousTime = Time.time;            }        }    }    IEnumerator FadeOut(MeshRenderer mr)//淡出效果    {        var mat = mr.material;        float alpha = 0;        while (mat.GetFloat("_Alpha") > 0)        {            alpha = mat.GetFloat("_Alpha") - fadeOutSpeed * Time.deltaTime;            mat.SetFloat("_Alpha", alpha > 0 ? alpha : 0);            yield return 0;        }        Destroy(mr.transform.parent.gameObject);    }}

方法二:

方法一的代码过于复杂,实时复制Mesh本就很消耗性能,又加上GameObject的不停创建销毁,效率很低。

1.获取当前Game Object节点下的所有Mesh放入List

2.通过Graphics.DrawMesh()将所有Mesh绘制出来

代码:
using System.Collections;using System.Collections.Generic;using UnityEngine;public class ShadowTail : MonoBehaviour{    private MeshFilter[] meshFilter = null;    private SkinnedMeshRenderer[] skinMeshRender = null;    private Animator animt = null;    [Range(1f, 10f)]    public float createSpeed = 5f;//创建残影的时间间隔    public float fadeOutSpeed = 1.5f;//淡出的速率    private float previousTime = 0;//用于纪录上次创建残影的时间点    private void Awake()    {        animt = GetComponent<Animator>();        meshFilter = GetComponentsInChildren<MeshFilter>();        skinMeshRender = GetComponentsInChildren<SkinnedMeshRenderer>();    }    private struct MeshInfo    {        public Mesh _mesh;        public Matrix4x4 _matrix;//用于确定对应mesh绘制的位置        public Material _material;    };    private List<MeshInfo> meshList = new List<MeshInfo>();    private void Update()    {        for (int i = 0; i < meshList.Count; i++)        {            Graphics.DrawMesh(meshList[i]._mesh, meshList[i]._matrix, meshList[i]._material, gameObject.layer);            float alpha = 0;            if (meshList[i]._material.GetFloat("_Alpha") > 0)            {                alpha = meshList[i]._material.GetFloat("_Alpha") - fadeOutSpeed * Time.deltaTime;                meshList[i]._material.SetFloat("_Alpha", alpha > 0 ? alpha : 0);            }            else            {                meshList.Remove(meshList[i]);                            }        }        if (animt.GetFloat("speed") > 0.1 && (animt.GetBool("b_walk") || animt.GetBool("b_walkback")))        {            if (Time.time - previousTime >= createSpeed * Time.deltaTime)            {                foreach (var mf in meshFilter)                {                    MeshInfo info = new MeshInfo();                    info._mesh = mf.mesh;                    info._matrix = mf.transform.localToWorldMatrix;                    info._material = new Material(Shader.Find("Shader Forge/XRay"));                    meshList.Add(info);                }                foreach (var mr in skinMeshRender)                {                    Mesh m = new Mesh();                    MeshInfo info = new MeshInfo();                    mr.BakeMesh(m);                    info._mesh = m;                    info._matrix = mr.transform.localToWorldMatrix;                    info._material = new Material(Shader.Find("Shader Forge/XRay"));                    meshList.Add(info);                }                previousTime = Time.time;            }        }    }}
残影Shader的实现:
我是偷懒用Shader Forge拖的shader

Shader比较简单,主要算法只有法线与观察向量的点乘,然后用1减去点乘结果。最后叠加一个颜色就得到了边缘发光的效果。
Shader代码:
Shader "Shader Forge/XRay" {    Properties {        [HideInInspector]_node_3777 ("node_3777", Float ) = 1        _RayPower ("RayPower", Range(1, 10)) = 1.196581        _RayColor ("RayColor", Color) = (0,0,0,1)        _LightPower ("LightPower", Range(1, 10)) = 1.8        _Alpha ("Alpha", Range(0, 1)) = 0.6324787        [HideInInspector]_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5    }    SubShader {        Tags {            "IgnoreProjector"="True"            "Queue"="Transparent"            "RenderType"="Transparent"        }        Pass {            Name "FORWARD"            Tags {                "LightMode"="ForwardBase"            }            Blend SrcAlpha OneMinusSrcAlpha            ZWrite Off                        CGPROGRAM            #pragma vertex vert            #pragma fragment frag            #define UNITY_PASS_FORWARDBASE            #include "UnityCG.cginc"            #pragma multi_compile_fwdbase            #pragma only_renderers d3d9 d3d11 glcore gles             #pragma target 3.0            uniform float _node_3777;            uniform float _RayPower;            uniform float4 _RayColor;            uniform float _LightPower;            uniform float _Alpha;            struct VertexInput {                float4 vertex : POSITION;                float3 normal : NORMAL;            };            struct VertexOutput {                float4 pos : SV_POSITION;                float4 posWorld : TEXCOORD0;                float3 normalDir : TEXCOORD1;            };            VertexOutput vert (VertexInput v) {                VertexOutput o = (VertexOutput)0;                o.normalDir = UnityObjectToWorldNormal(v.normal);                o.posWorld = mul(unity_ObjectToWorld, v.vertex);                o.pos = UnityObjectToClipPos( v.vertex );                return o;            }            float4 frag(VertexOutput i) : COLOR {                i.normalDir = normalize(i.normalDir);                float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);                float3 normalDirection = i.normalDir;////// Lighting:////// Emissive:                float node_543 = pow((_node_3777-saturate(dot(i.normalDir,viewDirection))),_RayPower);                float3 emissive = ((node_543*_RayColor.rgb)*_LightPower);                float3 finalColor = emissive;                return fixed4(finalColor,(node_543*_Alpha));            }            ENDCG        }    }    FallBack "Unlit/Texture"    CustomEditor "ShaderForgeMaterialInspector"}

残影有透明效果,所以是不需要抛阴影和接收阴影的,Shader Forge默认FallBack到Diffuse,Diffuse对阴影进行了处理。所以需要把FallBack删掉或改为没有处理阴影的Shader