您的位置:首页 > 移动开发 > Unity3D

Unity实时运动残影特效

2017-09-13 15:26 441 查看
效果图:





实现原理:

复制模型当前时间点的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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: