基于网格烘焙的鬼影拖尾实现
Github源码传送门————>GhostTrail
Bilibili教程传送门————>小祥带你实现鬼影拖尾效果

GhostTrail
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public class GhostTrail : MonoBehaviour { public SkinnedMeshRenderer meshRenderer; public LayerMask meshLayerMask; public float ghostLifeTime; public List<Material> ghostMaterials;
private List<Ghost> ghosts;
private void Start() { ghosts = new List<Ghost>(); }
private void Update() { float time = Time.time; for (int i = ghosts.Count-1; i >= 0; i { Ghost ghost = ghosts[i]; if (time - ghost.createTime > ghostLifeTime) { ghost.RemoveGhost(); ghosts.RemoveAt(i); } else { ghost.UpdateGhost(); } } }
public void CreateGhost() { Ghost ghost = GhostPool.Get();
ghost.createTime = Time.time; ghost.lifeTime = ghostLifeTime; ghost.mesh.Clear(); meshRenderer.BakeMesh(ghost.mesh); ghost.matrix = Matrix4x4.TRS(meshRenderer.transform.position, meshRenderer.transform.rotation, transform.transform.localScale); ghost.layer = meshLayerMask.value; ghost.materials = ghostMaterials;
ghosts.Add(ghost); } }
|
- 鬼影拖尾管理类,负责维护鬼影的生产和销毁
- 创建ghost对象,通过 meshRenderer.BakeMesh 方法烘焙当前SkinnedMeshRenderer的网格数据到Ghost类维护的mesh对象
- 将 TRS矩阵,材质,生命周期 等数据传给ghost对象,Ghost类中去做具体材质更新和渲染
- 在生命周期内的话调用 ghost.UpdateGhost 方法更新鬼影状态,超出生命周期则从维护的鬼影列表移出
Ghost
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class Ghost { public float createTime; public float lifeTime;
public Mesh mesh; public Matrix4x4 matrix; public int layer; public List<Material> materials;
private MaterialPropertyBlock materialPropertyBlock;
public Ghost() { mesh = new Mesh(); materialPropertyBlock = new MaterialPropertyBlock(); }
public void UpdateGhost() { if(materials.Count < mesh.subMeshCount) { Debug.LogError("Check your subMesh materials!"); return; }
float alpha = 1 - Mathf.Clamp01((Time.time - createTime) / lifeTime); materialPropertyBlock.SetFloat("_Alpha", alpha);
for (int subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++) { Graphics.DrawMesh(mesh, matrix, materials[subMeshIndex], layer, null, subMeshIndex, materialPropertyBlock, false, false, false); } }
public void RemoveGhost() { mesh.Clear(); GhostPool.Return(this); } }
|
- 一个Ghost类维护一个mesh用于承接烘焙好的mesh数据,一个MPB用于后续mesh更新材质信息
- 在 UpdateGhost 方法中通过替换鬼影材质,更新材质数据来产生自定义的鬼影效果(hologram全息效果,透明度剔除等等)
- 通过 Graphics.DrawMesh 方法根据传入的数据(mesh信息,TRS矩阵,材质,MPB数据,是否生成或接收阴影等)提交绘制mesh的DrawCall进行绘制
Pool
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Pool<T> { readonly Stack<T> items = new Stack<T>();
readonly Func<T> itemGenerator;
public Pool(Func<T> itemGenerator, int initialCapacity) { this.itemGenerator = itemGenerator;
for (int i = 0; i < initialCapacity; i++) { items.Push(itemGenerator()); } }
[MethodImpl(MethodImplOptions.AggressiveInlining)] public T Get() => items.Count > 0 ? items.Pop() : itemGenerator();
[MethodImpl(MethodImplOptions.AggressiveInlining)] public void Return(T item) => items.Push(item);
public int Count => items.Count; }
|
- 使用对象池来降低频繁生成销毁Mesh导致的内存碎片化以及GC压力,这里提供一个轻量通用的泛型对象池
- 使用Stack作为容器,后进先出提高缓存命中率
- Func itemGenerator 泛型委托用于创建泛型类,Get() 方法在栈中没有对象的时候通过这个委托创建一个新对象,后续回收到池子相当于扩容
完结撒花~
