作为开发人员,我们总是关注性能,包括CPU和GPU。随着场景变得越来越大越来越复杂,保持良好的性能变得越来越有挑战性,尤其是当我们添加越来越多的角色时。我和我在上海的同事在帮助客户时经常遇到这个问题,所以我们决定花几周时间致力于一个旨在提高角色实例化性能的项目。我们把产生的技术称为动画实例化。
我们经常用来实现户外场景GPU实例化,如草和树。要不是SkinnedMeshRenderer(比如人物),我们不能用实例化,因为蒙皮是在CPU上计算的,一个一个提交给GPU。一般来说,我们不能通过一次提交就画出所有的角色。当场景中有大量SkinnedMeshRenderers时,这会导致大量绘制调用和动画计算。
我们已经找到了一种方法来降低CPU成本,并用动画实例化来补充Unity中的GPU实例化。你可以在GitHub上获取我们的代码。请注意,这是定制的实验性解决方案,直到最近,我们才与少数企业支持客户分享它。现在,我们准备好接受更多反馈,请让我们知道您的想法直接在项目注释中!
目标
我们这个实验项目的最初目标是:
实例化SkinnedMeshRenderer
实现尽可能多的动画特性
《牛津小词典》
支持移动平台
选择
由于时间限制,我们的目标并没有全部实现。支持的动画功能有:根运动、附件、动画事件(尚不支持的功能:过渡、动画层)。此外,请记住,这仅适用于使用OpenGL ES 3.0和更新版本的移动平台。
然而,我们认为实验成功地证明了这种方法可以产生有趣的结果。让我们深入了解一些细节。
动画生成
在为角色使用实例化之前,我们需要生成动画。我们把一个角色的动画制作成纹理。这些纹理被称为动画纹理。纹理用于GPU上的蒙皮。
发展
这个生成器从附属于游戏对象的Animator组件中收集动画。它还收集动画事件。从Mecanim系统转移到动画实例化很方便。如果要在角色上附加某些东西,需要在“附件设置”中指定可以附加某些东西的骨骼。
当我们完成生成动画纹理时,动画实例脚本将在运行时加载动画信息。请注意,动画信息不是动画剪辑文件。
举例说明
应用动画实例很简单。让我们将动画实例脚本添加到我们生成的游戏对象中。每顶点骨骼参数控制每顶点计算的骨骼数量。这里需要注意的重要一点是,拥有更少的骨骼可以提高性能,但会降低准确性。
发展
接下来,我们需要修改着色器以支持实例化。基本上,你需要的是将这些线添加到你的着色器中。它不会影响您的着色,但会向蒙皮添加顶点着色器。
#include “AnimationInstancingBase.cginc”
#pragma vertex vert
技术性能分析
我们使用了稍微修改过的版本的演示场景机械动画场景示例并在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个字符。
发展
发展
国家政治保卫局。参见OGPU
这项技术增加了一点GPU成本,因为我们在GPU上放置了皮肤。如果角色有阴影,我们必须在阴影通道中再次为角色蒙皮。但是,它提高了整体帧速率,因为它降低了CPU成本。通常CPU成本是游戏中人群模拟的最大问题。
记忆
额外的内存用于存储动画纹理。纹理保持皮肤矩阵。我们使用RGBAHalf格式纹理。我们假设一个角色有N块骨头,每块骨头四个像素(一个矩阵);我们生成一个M个关键帧的动画。所以一个动画花费N * 4 * M * 2 = 8NM字节。如果一个角色有50块骨骼,我们生成30个关键帧,那么一个动画就有50 * 4 * 30 = 6000个像素。所以一个1024*1024的纹理最多可以存储174个动画。
结论
我们发现,如果你有很多SkinnedMeshRenderers,动画实例化可以显著降低CPU成本。它适合类似的敌人,如僵尸等人群。
我们希望这个实验项目提供一些洞察力,可以照亮你自己的项目的性能挑战,并让你有能力建立更复杂的场景。当然,未来的工作有很多途径,比如支持过渡、动画层等等。