Flash骨骼动画渲染优化

这里的优化思路或许也不仅限于flash。优化说到底就是个发现问题 -解决问题的过程,首先使用性能分析工具找到瓶颈,然后再对症下药。

第一步:

在原有的设计中,我们的每根骨骼都是一个Object3D,会挂接在渲染树上,参与绘制流程。这样的方便之处在于我们可以很方便地把特效或者武器等物件挂接在某根骨骼上,只需要找到对应的骨骼调用addChild()就可以了。然而,这带来的比较大的性能开销:
1. 每根骨骼都要参与可见性判断、裁剪等过程。
2. 每根骨骼都要参与矩阵级联运算。

优化思路也很简单:
1. 把骨骼从渲染树上拆解出来,单独成为一套更新体系,省去了一系列可见性判断。
2. 原本每根骨骼的矩阵是相对于父骨骼的,现在会在解析模型的阶段将所有骨骼的矩阵预处理成相对于根骨骼的,避免了每次渲染时的矩阵级联运算。据说相对于根骨骼的矩阵在做插值时会遇到问题,但项目开发到现在,依旧没有出现肉眼能观察到的问题,所以这样是可行的。
3. 至于特效和武器的挂接问题,需要另外写机制实现,但总体效率会好过优化前。

第二步:

做完第一步的优化后,发现骨骼矩阵插值运算这块变成了性能大头。我知道有些优化方法是降低骨骼动画的更新帧率、或者是直接关闭某些部分骨骼的更新(比如披风)。然而,我们并不希望优化会带来游戏品质的下降(至少不要下降太多:)),因此只能另想办法了。

我们所使用的办法是缓存运算结果。比如我们设定骨骼动画的更新频率为每秒60帧,那么我们就会按照每秒60的频率对骨骼动画进行采样,并把计算结果保存在内存中。那么,每次需要更新骨骼动画时,我们只需要抽取对应时间片的数据,并将其提交给显卡即可。

在这个大前提下,还有一些优化的细节:
1. 并不是一开始就采样出所有的数据,而是先去缓存里取,发现没有,那么运算插值,并把结果写入缓存。第二次就可以从缓存中取数据了。这样会更平滑,不会因为采样而导致卡顿。
2. 所有同种类的骨骼动画使用同一份缓存。

进行完这一步的优化后,骨骼运算的开销降低到了一个令人发指的地步,因为开销只是一次数组取操作。这是一个典型的空间换时间优化,在这个基础上降低骨骼动画的更新频率也只是减少一些内存开销,已经没什么意义了。

第三步:

把骨骼矩阵上传至显卡也会是一个渲染瓶颈。正常情况下,一个矩阵是4×4个浮点数。然而,矩阵中的齐次项在运算时可有可无,是可以不用提交的,因此我们只需要提交4×3个浮点数。对于一个模型60根骨骼来说,还是会有不少提升。

第四步:

这是一个Flash平台特定的优化。对于骨骼动画,把蒙皮的工作交给GPU而不是CPU已经是一种基本做法了,说不上是优化。然而在Flash中我们能够使用的VC(顶点常量寄存器)非常少,只有128个。即使我们使用4×3矩阵,一个批次最多也只能上传42根骨骼。考虑到VC可能会有其他用途,实际能上传的骨骼还要进一步减少。那么对于一个有60根骨骼的动画来说,我们只能分成两批上传。比如,第一次渲染人物的上半身,然后渲染人物的下半身。

在随后的版本中,Flash新增了一个Standard Profile。在此Profile下,agal的版本提升到了2.0,同时各种寄存器数量也加倍了。对于VC来说,提升到了250个。因此,原本需要两个drawcall才能完成的骨骼动画绘制现在只需要一个drawcall就能完成, 性能就翻倍了。

当然,为了兼容旧的显卡和机器,我们需要判断当前是否能请求Standard Profile。具体如何实现网上有很多做法,在这里就不介绍了。我们要做的就是根据一个标志位判断当前是否是standard profile,这可以通过Context3D.driverInfo获取。

发表评论

电子邮件地址不会被公开。 必填项已用*标注