由浅到浅入门批量渲染(四)

作者:zt枸杞忧天 腾讯游戏学院 2021-01-06
上回(由浅到浅入门批量渲染(三))简单总结了一下实例化渲染,这次我们说说优化骨骼蒙皮动画。

前言

试想一下,当我们在游戏场景中放置大量(成百上千)带有骨骼蒙皮动画的单位时,会发现帧数已经开始下降,这是为什么呢?

经过多年研究,我发现,造成这种情况的根本原因是:放的太多了。

然而,在开发某些类型的游戏(如策略或即时战略等)时,通常又需要尽可能的多放些小兵或者怪物,来烘托战场气氛。

需要呈现大量的角色,又需要保证性能,是一件挺麻烦的事情;如果你也在尝试解决这个问题,并且暂时还没有找到合适的方法,那接下来要讲的内容可能会帮到你;因为我们将进入这一系列(从浅到浅)的下半部分:总结目前较为成熟的针对骨骼蒙皮动画的优化方案。

骨骼蒙皮动画的开销

“欲练神功,必先自宫”是笑傲江湖中非常有名的一句台词,它也是神功《葵花宝典》和《辟邪剑法》武功秘籍上的第一句话。以前听到它时只是感觉这就是邪魔外道武功的一个符号罢了;但现在想来,当时还是年轻了,没看懂。它其实是一条学习任何技能、知识的诀窍,也是一把打开成功之门的钥匙。


葵花宝典上的斑斑血迹记录了多少悲伤故事

其实,这句话想传达的真正意思是:做事情,要打好基础。

所以,要优化骨骼蒙皮动画,就要先简单了解下它的性能瓶颈所在。

骨骼蒙皮动画的流程

可以简粗(简单粗暴)的将骨骼蒙皮动画的工作流程分为以下几个阶段:

播放动画阶段

动画控制器会根据关键帧信息等,调整骨骼的空间属性(旋转、缩放、平移)。

计算骨骼矩阵阶段

从根骨骼开始,根据层级关系,逐一计算出每一根骨骼的转换矩阵。

这个矩阵连接的是这根骨骼的本地坐标系和角色坐标系(通常会是角色脚下);也就是说通过它可以在某一帧动画结束后,将(某一根)骨骼坐标系下的坐标或向量,转换到角色坐标系下。

蒙皮阶段

更新网格上每个顶点的属性。

由于动画改变的是骨骼而不是顶点的空间属性;而且网格中的顶点是相对于网格坐标系下的,并非在角色坐标系下。所以在这一阶段,我们首先要依据创建骨骼蒙皮动画时,被记录下来的顶点和骨骼的关系,找到对应的骨骼(Unity中通过Mesh.boneWeights获取,一个顶点最多可受到四根骨骼影响)。

其次,通过网格坐标系到骨骼本地坐标系的转换矩阵(Unity中通过Mesh.bindposes获取),来建立从网格坐标系到骨骼坐标系的桥梁;结合上阶段得到的骨骼坐标系到角色坐标系的转换矩阵,实现将动画对骨骼的影响最终作用到顶点上,并将其更新到角色坐标系下。

顶点的属性被动画控制器“间接”更新

渲染阶段

当顶点变换到角色坐标系下后,就可以进行渲染了。这里与一次普通的渲染没什么太大差别,唯一需要注意的是,Unity不会对蒙皮网格渲染器进行合批,所以每一个骨骼蒙皮动画实例都至少需要一次DrawCall。

骨骼蒙皮动画的开销

可以将骨骼蒙皮动画的主要开销,也简粗的分成以下几个部分:

  • 更新动画的开销
  • 计算骨骼矩阵的开销
  • 蒙皮开销
  • 渲染开销

这里我创建了一个简单的场景,来简单测试下这些开销。

使用Unity 2018.4.14f1版本创建一个测试场景,场景中包含500个使用相同模型的骨骼蒙皮动画角色,并循环播放空闲、移动、攻击动画;测试机是华为P20(Geekbench5得分约为1400),通过Profiler查看运行耗时。

简单的步兵模型

场景运行后

我们把与骨骼蒙皮动画有关的主线程运算开销整理出来,看一下在骨骼蒙皮动画工作时哪些计算耗时最多。

与骨骼蒙皮动画有关的主线程耗时占比

可以发现,动画更新的耗时占比最高,其次是蒙皮网格的更新(计算矩阵、蒙皮等),最后是渲染。

需要指出的是,我使用的Unity(版本2018.4.14f1)将动画更新和蒙皮放到了工作线程中;所以像蒙皮这种“逐顶点、理论上应该开销很大的操作”带来的耗时增加,并没有体现在主线程中;而且我在打包时也没有勾选多线程渲染(Multi-threadedRenderer),所以渲染指令的调用也都发生在主线程。

动画更新被放在工作线程中执行

蒙皮被放在工作线程中执行

渲染指令在主线程中调用

常见优化方式

Unity下可以通过以下两种方式快速优化骨骼蒙皮动画:

  • 在导入模型时进行的优化
  • 在打包设置中开启GPU蒙皮

这两者的优化效果怎么样呢。

导入模型时的优化


勾选模型导入设置进行优化

在相同的测试环境下,再次进行测试后可以发现,这种方法确实可以产生一定效果。

其原因我认为主要有以下两点:

不再为骨骼创建不必要的游戏对象

对导入模型进行优化后,Unity将不会为骨骼创建实际的游戏对象了(我们也可以暴露出一些骨骼作为挂点)。


这些消失的游戏对象一定程度上也减少了CPU的性能开销

计算矩阵被移到工作线程

除此之外,Unity还会将计算骨骼矩阵的操作放到工作线程中,来减少主线程耗时。

计算矩阵在工作线程中进行

主线程耗时不再包含计算矩阵

GPU蒙皮

开启GPU蒙皮,Unity会通过ComputeShader的方式,使用GPU进行蒙皮。

勾选设置开启GPU蒙皮

GPU上有大量的ALU(算数逻辑单元),可以并行大量的数值计算,效率较高,应该很适合这种针对顶点属性的数值计算。

但是实际情况是:在移动设备上使用GPU蒙皮反而会使主线程的耗时增加。

开启GPU蒙皮后主线程耗时增加

通过Profiler可以发现,CPU把更多的时间放在了执行ComputeShader上,由于骨骼动画的实例很多(500个),所以这个调用时间本身成为了性能热点。

开启GPU后,蒙皮网格更新中增加了GPU蒙皮

执行GPU蒙皮耗时较高

所以,以目前的情况来看,这种在移动设备上使用GPU蒙皮的方式,似乎不适合处理大量骨骼蒙皮动画实例(也许是我使用的方式存在问题)。

写在最后

我们简单总结了骨骼蒙皮动画的工作方式,分析了主要的性能开销及耗时,以及比较了Unity中常用的两种优化方式。

但在面对数以千计的角色表现需求上时,无论使用何种Unity自带的优化,都显得有些力不从心;所以下次的更新,将介绍两种目前较为有效、成熟的“奇技淫巧”,来(一定程度上)解决这个问题。

相关阅读:

由浅到浅入门批量渲染(三)
由浅到浅入门批量渲染(二)
由浅到浅入门批量渲染(一)


来源:腾讯游戏学院
原文:https://gameinstitute.qq.com/community/detail/133615
最新评论
暂无评论
参与评论

商务合作 查看更多

编辑推荐 查看更多