项目中要求某场景中实现数十人奔跑,如果用骨骼动画,那CPU计算蒙皮的开销可太大了。依稀记得 @陈嘉栋 之前实现过一版,不过当时没细看。于是就拿这个思路继续写了。
思路
众所周知的是,因为要在CPU端计算骨骼动画信息,因此蒙皮网格是不能合批的。反过来说,计算骨骼动画实际也就是在计算蒙皮网格各顶点的位置。假如我们把一段动画中各顶点的位置记录下来,放在静态的网格上进行次序的播放,那么应当是等效的。
拿AnimationClip驱动SkinnedMeshRenderer,在每一帧下都Bake一次,把得到的Mesh的顶点都保存在一张纹理上。这样就完成了将动画烘焙成纹理的任务。
当我们要使用纹理的时候,就从纹理中读出某顶点对应像素的颜色值,并转换成空间位置。如何确定顶点和像素的对应关系呢?这就要用到SV_VertexID这个语义了。
实现
这里我做了一个小小的改进,假如动画长度很短,而模型顶点数又多的话,就会烘出3000*30这样的图,委实不美。因此只要在Shader中稍加改进,便可以指定烘出的纹理的尺寸了。
using
这里需要注意两个地方。
首先是需要指定一个采样空间的尺寸,毕竟输出成纹理之后,分量的范围上是[0,1],需要指定采样空间的尺寸
第二个就是需要重新导入一次纹理,sRGB和点过滤器是必须要开的,以保证采样结果和计算结果吻合。
Shader没啥说的,就是将uv变换一次:
Shader
精度修正
当然这样做的话还是有不足之处,就是由于在Unity中即便是RGBA32的图像,精度仍然显得不够。
![v2-a156dee3b36ae4a7bd900f9f714f7858_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=4a5dd878-8f2e-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-a156dee3b36ae4a7bd900f9f714f7858_b.jpg)
为了弥补8位分量精度带来的损失,有必要使用多个分量来进行表达。已知float类型有23位用于有效数字,则我们可以使用两个8位分量或者3个8位分量来进一步修正。一般情况下,两个8位分量已经足够了。
using
因为使用了RGB24的纹理,因此无论是Shader还是C#部分均修改了对纹理的接口。Shader这边使用变体,更方便操作。
Shader
在使用16位精度的情况下,动画的表现已经足够支撑特写镜头时数十人狂奔而过的考量了,而不会出现五官抖动的情况:
![v2-662201cf210172adf3c0c3f5273b4ea9_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=4a5dd878-8f2e-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-662201cf210172adf3c0c3f5273b4ea9_b.jpg)
要更进一步使用24位也不是不可以,但是那样的话以内存和包体换执行效率的性价比就低了。不建议使用。
总结
本文立足于之前文章的思路,并在尺寸定制和精度修正上做出了一定的改进。但是从信息的角度考虑还有不足之处。
该方法最大的缺陷在于每个模型对每个动作都要烘焙一次,因为顶点众多,烘焙出来的纹理相当大,这是因为记录的是计算后的信息。从信息的角度考虑,顶点的信息都来自于骨骼动画的信息,因此如果能烘焙骨骼动画信息到纹理,信息密度必然更大更好。
参考资料
【1】利用GPU实现大规模动画角色的渲染 - 陈嘉栋 - 博客园