鬼影拖尾效果实现

基于网格烘焙的鬼影拖尾实现

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() 方法在栈中没有对象的时候通过这个委托创建一个新对象,后续回收到池子相当于扩容

完结撒花~

pid:119396772


鬼影拖尾效果实现
https://baifabaiquan.cn/2025/08/29/GhostTrail/
作者
白发败犬
发布于
2025年8月29日
许可协议