在上一篇文章中有个技术点引发了我的好奇
GPU Instancing 这个技术是Unity自带的功能,但是他不支持SkinnedMeshRenderer,所以要采用特殊方法
他这里采用GPU蒙皮+ECS+Job+Burst 基本上做到了目前优化最优解
找到Unity的一篇文章《Animation Instancing – Instancing for SkinnedMeshRenderer》
https://blogs.unity3d.com/cn/2018/04/16/animation-instancing-instancing-for-skinnedmeshrenderer/
动画实例化– SkinnedMeshRenderer的实例化
作为开发人员,我们始终了解CPU和GPU的性能。随着场景变得越来越大,越来越复杂,保持良好的性能变得越来越具有挑战性,尤其是当我们添加越来越多的角色时。我和我在上海的同事在帮助客户时经常会遇到这个问题,因此我们决定花几个星期的时间致力于提高实例化字符的性能。我们称这种技术为“动画实例化”。
我们经常使用GPU Instancing来实现户外场景,例如草地和树木。但是对于SkinnedMeshRenderer(例如字符),我们不能使用实例化,因为皮肤是在CPU上计算的,并一一提交给GPU。通常,我们无法通过一次提交来绘制所有字符。当场景中有很多SkinnedMeshRenderers时,这将导致大量绘制调用和动画计算。
我们找到了一种方法来降低CPU成本,并通过动画实例化补充Unity中的GPU实例化。您可以在GitHub上获取我们的代码。请注意,这是自定义的实验性解决方案,直到最近我们才与一些企业支持客户共享了该解决方案。现在我们已经准备好征求更多反馈–请在项目评论中直接让我们知道您的想法!
目标
这个实验项目的最初目标是:
- 实例化SkinnedMeshRenderer
- 实施尽可能多的动画功能
- LOD
- 支持手机平台
- 剔除
由于时间限制,并非所有目标都得以实现。支持的动画功能包括:根动画,附件,动画事件(尚不支持的功能:转场,动画层)。另外,请记住,这仅适用于使用OpenGL ES 3.0及更高版本的移动平台。
但是,我们认为该实验成功地证明了这种方法可以产生有趣的结果。让我们深入探讨一些细节。
动画生成
在为角色使用实例化之前,我们需要生成动画。我们将角色的动画生成为纹理。这些纹理称为动画纹理。纹理用于在GPU上蒙皮。
该生成器从附加到有问题的GameObject的Animator组件中收集动画。它还收集动画事件。从Mecanim系统转移到动画实例化非常方便。如果要在角色上附加某些内容,则需要在“附件”设置中指定可以将某些内容附加到的骨骼。
当我们完成生成动画纹理时,Animation Instancing脚本将在运行时加载动画信息。请注意,动画信息不是动画剪辑文件。
实例化
应用“动画实例化”很简单。让我们将Animation Instancing脚本添加到我们生成的游戏对象中。“每个顶点的骨骼”参数控制每个顶点计算的骨骼数量。这里要意识到的重要一点是,减少骨骼可以提高性能,但会降低准确性。
接下来,我们需要修改着色器以支持Instancing。基本上,您需要将这些行添加到着色器中。它不会影响着色,但会为蒙皮添加一个顶点着色器。
1
2
|
#include “AnimationInstancingBase.cginc”
#pragma vertex vert
|
绩效分析
我们使用了Mecanim示例场景中演示场景的稍微修改版本,并在iPhone 6上测试了它的性能。让我们仔细看看原始示例和实例示例的探查器视图。
中央处理器
原始项目产生300个字符,而我们的FPS大约为15。要达到至少30 FPS,我们必须将字符数限制为大约150。在“动画实例化”版本中,我们可以产生900个字符,同时保持30 FPS。
如您所见,CPU上的计算会减慢项目速度。
使用实例化项目,我们在CPU上大大减少了动画计算(骨骼和蒙皮等)。这样,我们可以生成五到六倍的字符!
在测试场景中,绘制环境需要大约80个绘制调用。角色有三种材料。因此,我们有三个绘制调用来渲染角色。
如果不实例化,则生成250个字符需要大约1100次绘制调用(3 * 250个字符及其阴影)。
使用“动画实例化”时,生成800个字符后,绘图调用仅增加到约50个。您可以看到,实例化列中有4800个批处理的绘图调用和48个批处理(3 * 8个字符+ 3 * 8个阴影)。那是因为我们每批提交100个字符。
显卡
这项技术会稍微增加GPU成本,因为我们在GPU上添加了外观。如果角色有阴影,我们必须在阴影通道中再次对角色进行蒙皮。但是,由于它降低了CPU成本,因此提高了总体帧速率。通常,CPU成本是游戏中人群模拟中的最大问题。
记忆
额外的内存用于存储动画纹理。纹理保持皮肤矩阵。我们使用RGBAHalf格式纹理。假设一个角色有N个骨骼,每个骨骼有四个像素(一个矩阵);我们生成一个动画作为M个关键帧。因此,一个动画的成本为N * 4 * M * 2 = 8NM字节。如果一个角色具有50条骨骼,并且我们生成30个关键帧,则一个动画具有50 * 4 * 30 = 6000像素。因此,一个1024 * 1024的纹理最多可以存储174个动画。
结论
我们发现,如果您有很多SkinnedMeshRenderers,则动画实例化可以显着降低CPU成本。适用于僵尸等类似敌人的人群。
我们希望这个实验性项目能够提供一些见识,可以洞察您自己项目的性能挑战,并使您能够构建更精细的场景。当然,未来的工作有很多途径,例如对过渡,动画层等的支持。
请在Github上检查代码,并将您有的任何评论/问题直接发布到项目中!
其他相关:
国内的一份教程:https://www.cnblogs.com/murongxiaopifu/p/7250772.html,写于2017。
但是这个作者在2020年7月,还给他翻译成了英文 https://medium.com/chenjd-xyz/how-to-render-10-000-animated-characters-with-20-draw-calls-in-unity-e30a3036349a