我们经常利用GPU Instancing 去实现室外场景,比如草地和树木。但是对于SkinnedMeshRenderer,例如角色,我们是使用不了instancing技术的。因为角色的蒙皮计算通常是在CPU计算的,然后一个一个的提交给GPU渲染。通常来说,我们没办法通过一次提交来画出多个角色。那么当场景中有大量角色时,就会有相应的大量Drawcall和动画计算存在。

 

Animation Instancing是一种大大减少CPU消耗并且对GPU Instancing的一种补充。你可以在GitHub上获取代码。下载地址:

https://github.com/Unity-Technologies/Animation-Instancing

 

特别说明:这是一个实验性质的解决方案,在此之前仅分享给个别企业级服务的客户。但是现在我们希望获得更多的反馈以便改进此方案,如果你有任何问题请在GitHub或者后台留言给我们。

 

目标

 

我们对这个实验项目最初的目标是:

  • Instancing SkinnedMeshRenderer

  • 尽可能实现动画特性

  •  LOD

  • 支持移动平台

  • Culling

 

由于时间关系,不是所有的目标我们都已经实现的。例如动画特性已经支持的有:Root Motion,Attachment,Animation Events,还不支持的有:Transitions, Animation Layer。同时,对于移动平台的支持需要平台支持OpenGL ES 3.0及以上。

 

通过我们的测试,这个实验性的解决方案已经可以获得很有趣的结果。让我们来深入了解一下细节。

 

动画处理

 

在我们instancing角色之前,我们需要预处理角色的动画。我们把角色动画存储为了texture,以便于在GPU上蒙皮。这些texture我们称其为Animation Texture。

 

 

这个处理器会收集角色的动画以及动画事件。从Mecanim系统转化到Animation instancing是十分方便的。如果你想挂接某些东西,例如:挂接武器到你的角色上,你需要在Attachment settings中设置要挂接的骨骼节点。

 

当我们处理完成Animation Texture,名字为Animation Instancing的脚本在运行时会自动读取对应的动画信息。请注意这里的动画信息并不是指animation clip文件。

 

Instancing

 

启用Animation Instancing是十分简单的。我们只需添加Animation Instancing脚本到我们已经处理过的Game Object。Bone Per Vertex选项控制着每顶点受影响的骨骼数目。更少的数目带来更高的性能但是更低的动画精确度。

 

之后,我们需要修改角色所用的shader来支持instancing。基本上,你所需要做的就是把下面二句话加到你的shader中。它不会影响你的shader的着色,只是添加了一个顶点着色器来蒙皮。

   #include    “AnimationInstancingBase.cginc”

   #pragma vertex vert

 

性能分析

 

我们使用了Mecanim Example Scenes中的一个场景来做测试,这个测试结果是在iPhone 6上的,在pc上你可以获得更好的测试结果。让我们来看一下测试结果对比。

 

1

CPU

原始的测试场景我们生成了300个角色后FPS大概在15帧左右。由此我们可以粗略的推断要达到30帧大概要生成150个角色。在Animation Instancing版本我们生成了900个角色后,可以稳定在30帧。

 

通过下图我们可以发现,瓶颈主要在CPU端。而通过Animation Instancing,我们降低了在CPU上的动画计算与蒙皮。所以,我们相对于原始版本可以生成5、6倍的角色。

 

下面让我们来看一下Drawcall。在这个测试场景中,环境的Drawcall大概在80左右。由于我们的测试角色有3个材质,所以我们需要3个Drawcall去渲染一个角色。

 

原始版本中,我们生成了250个角色,这产生了1100个左右的Drawcall(3*250个角色+阴影)。

 

在Animation Instancing版本中,我们生成了800个角色,但是Drawcall的增加量只有50左右。同时,在instancing这一行中,你可以看到有Batched drawcall4800个(3*8角色+3*8阴影)。这是因为我们把100个角色作为一个批次来提交的。

 

 

2

GPU

这个技术会提升一点GPU的消耗,因为我们把蒙皮放在了GPU上。如果角色还有阴影的话,我们在shadow pass中会再次计算蒙皮。然后,总体来看它提升了整体的帧率,因为我们大大降低了CPU的消耗。通常在有大量角色存在的场景中,CPU的消耗是主要的瓶颈。

 

3

内存

我们需要的额外的内存占用是Animation Textures。这些纹理保存了蒙皮矩阵。我们使用了RGBAHalf格式的纹理(对于不支持此纹理的平台我们还在找寻解决方法)。让我们假设一个角色有N个骨骼且每骨骼保存4个像素作为一个矩阵。我们预处理一个动画为M帧。所以一个动画会花费N * 4 * M * 2 = 8NM bytes。如果一个角色有50个骨骼且我们生成30帧的动画的话,一个动画会消耗50 * 4 * 30 = 6000 像素。所以一张1024x1024的纹理可以存储174个动画。

 

结论

 

通过验证我们发现如果你的场景中有大量的SkinnedMeshRenderers,Animation Instancing可以有效的降低CPU的消耗。它十分适合那些有大量角色存在的游戏中,比如僵尸、战争模拟等类型游戏。

 

我们系统此实验性的方案可以抛砖引玉,使开发者可以实现更为宏大的游戏场景。当然,还有工作需要我们来完善,比如支持transitions, animation layer等。

 


本文信息摘录自unity官网、Unity官方微信公众号及官方商店