Render- Animation Instancing
我们经常会通过使用GPU Instancing 来对一些户外的场景(如花草、树木)进行批量优化处理。但是对于游戏中的角色(绑有 SkinnedMeshRenderer 组件的对象),我们无法通过GPU Instancing 来优化,原因是大量的 skinning(蒙皮)计算发生在 CPU 中,然后相关顶点数据流逐个地被提交到 GPU 再进行渲染计算。一般情况下,CPU是无法一口气把所有的角色数据提交到渲染管线。当一个场景中有大量挂有SkinnedMeshRenderer的对象时,将会产生大量的Draw Call(简称DC) 和动画的计算。 原文的作者提供了一种方法来减少CPU环节的开销,这种技术称之为 “ Instancing”,通过对GPU Instancing的特性进行扩充。但是,这个解决方案还处于试验阶段。如果使用中有问题,欢迎猛戳 这里。 目前来说,Animation Instancing 支持的特性包括:Root Motion, Attachment, Animation Events,暂不支持:Transitions(动画过渡),Animation Layer(动画层级)。此外,目前移动平台只支持 OpenGL ES3.0及更新的版本。 接下来,我们来看一下如何使用? Animation Generation 在我们对角色进行 Instancing 之前,我们需要将角色的动画信息制作成纹理,这种纹理我们叫做Animation Texture(动画纹理),是GPU用来进行蒙皮所使用的纹理。 Animation Instancing 的 Generator窗口工具 这个生成器还支持从指定的GameObject对象所挂载的动画组件上收集动画数据以及动画事件。 当我们完成生成动画纹理后,Animation Instancing脚本会在运行时加载动画纹理。需要注意的是,这里的动画数据并不是animation clip动画片段。 Instancing Animation Instancing脚本使用起来很方便,只需要将它挂在我们场景中要处理的对象上。其中Bone Per Vertex 这个参数用来控制每一个顶点缓存对应需要计算的骨骼数。这个参数越小,GPU的性能表现就越好,但是渲染出来的精度就越低。 Animation Instancing脚本 接下来,我们需要修改shader来使得它支持我们的Animation Instancing。其实,你只需要把下面两行代码加到你的shader中就可以。它不会影响你的shader工作,只是增加一个用来蒙皮的顶点着色器。 #include “AnimationInstancingBase.cginc” #pragma vertex vert 性能分析 我们可以通过unity提供的动画案例来进行测试,资源下载地址请见“Animation Instancing 工程源码”。 接下来,我们将它放到iphone6设备机上进行测试,并且看一下profiler视图上的对比数据
一开始,我们在场景中放300个角色,而我们的FPS只有15帧。如果需要达到至少30帧的话,我们需要减少我们的角色数量到150。但是使用了Animation Instancing以后,我们在场景中放了900个角色,可以看到,帧数依然稳定在30帧。 未使用Instancing的效果 正如你所看到的,由于CPU的计算量导致了帧数下降。 使用了Animation Instancing的效果 使用了Animation Instancing以后,我们减少了动画部分的计算量(骨骼与蒙皮),这减轻了CPU大部分的开销,这样我们可以使用比原来多5、6倍的角色。 在刚才的测试场景中,周围场景的渲染大概在80个DC左右,每个角色由3个材质,也就意味着渲染一个角色至少要3个DC。 如果不使用Animation Instancing,我们放置250个角色,那么需要花费1100个左右的DC(3*250 角色 + 他们的阴影)。 当使用了Animation Instancing以后,即使放置800个角色,DC只有50个左右。因为,4800多个DC被合批到了48个batch(3*8 角色 + 3*8 阴影)中发送给GPU。基本上每1个batch包含了100个角色的渲染信息。 FrameDebugger信息对比
这种技术稍微提高了GPU的开销,因为我们把蒙皮的处理放到了GPU环节。如果角色有阴影,我们就会在shadow pass中对角色再次进行蒙皮计算。这是一种权衡行为,由于这种情况,因为减少了CPU的开销而提高了帧率,收益大于损耗。
这种技术会占用一些额外的内存来存储 Animation Texture 动画纹理。这种纹理将蒙皮使用的数据以矩阵形式存储。纹理格式 我们使用 RGBAHalf。设想一下,如果一个角色有N根骨骼,每个骨骼占4个像素(一个矩阵);如果我们生成的动画有M关键帧,那么一个动画将花费N*4*M*2 = 8NM字节空间。如果一个角色有50根骨骼,生成的动画占30帧关键帧,一个动画将占用6000个像素(50*4*30),那么一张1024*1024的纹理可以存储174个动画数据。 重要的事情说三编,工程源码: |
关注CG资源素材