Unity实时运动残影特效

效果图:

实现原理:

复制模型当前时间点的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
  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity Spine 2D残影效果是一种在游戏开发中常用的特效技术,可以用来实现角色或物体移动时留下残影效果。通过在Spine动画中添加额外的骨骼或Sprite Renderer,并结合透明度和延迟淡出效果,可以实现残影特效效果。 首先,在Spine中创建一个新的骨骼或Sprite Renderer,作为残影的载体。这个载体需要复制主角或物体的动画,可以通过复制骨骼及其关键帧并调整帧间延迟来实现。确保在复制动画的过程中,将残影骨骼或Sprite Renderer的透明度逐渐降低,以获得残影淡出效果。 接下来,在Unity中创建一个空物体作为残影对象的父物体,并将其位置与主角或物体保持一致。将残影载体作为父物体的子物体,并将残影载体的位置设置为与父物体的位置一致。 然后,通过控制残影骨骼的播放速度,以及调整透明度和延迟淡出效果,可以实现残影效果。可以尝试使用Animator组件或编写脚本来控制残影骨骼的动画播放速度和透明度变化。 最后,在游戏运行时,每一帧都需要更新残影对象的位置与主角或物体的位置保持一致,这样才能实现残影效果随着主角或物体的移动而产生。 总结来说,Unity Spine 2D残影效果的实现步骤主要包括创建残影载体,复制动画、调整透明度和延迟淡出效果,创建父物体并保持位置一致,控制残影载体的动画播放速度和透明度变化,以及更新残影对象的位置。通过这些步骤,可以实现一个逼真的残影效果,增强游戏的视觉效果
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值