用过Away3D的朋友估计都会发现,在Away3D里面使用超过一定骨骼数量的角色,当场景里面角色的数量稍微多一点,整个场景就会很卡。
对于这个现象,我之前得出的结论是。Stage3D的VC缓存器数量的限制,造成了对需要占用VC的骨骼信息有限制。对于超过了限制数量的骨骼部分,Stage3D会把数据退回CPU计算。
这里存在几个误区:
1、退回CPU的处理不是Stage3D做的,而是away3D本身做的。
原生的Stage3D对于超过能允许数量的骨骼,因为超出了128个vc,不会做其他处理,只会直接报错:
ArgumentError: Error #3615: AGAL 验证失败: 程序大小小于 程序的最小长度。
2、不是部分的退回,是通过一个开关判断是否需要退回,全部退回。
开关是变量usesCPU。一开始给材质赋值的时候,会判断该模型是否需要退回cpu计算。假如不需要,就全部推到GPU计算,即使没有动画信息的时候,顶点着色器也会使用蒙皮计算的一套。假如需要退回cpu计算,那么就不会再使用蒙皮动画的顶点程序,而直接用最基础的顶点程序计算。
在明白了这两点之后,看看Away3D对这个是否超出长度的功能做了什么处理:
1、通过对AnimationSetBase.cancelGPUCompatibility断点,发现了在SkeletonAnimator.testGPUCompatibility方法里面有检查是否需要退回CPU的判断。其判断的条件是:
if (!_useCondensedIndices && (_forceCPU || _jointsPerVertex > 4 || pass.numUsedVertexConstants + _numJoints * 3 > 128))
可以看出:
除了_useCondensedIndices ==false,还需要
1._forceCPU == true
2.一个顶点受到大于4个骨骼的影响。
因为每个va只能存xyzw四个数,按照Away3D的顶点着色器的处理,就只能最多一个顶点受到4根骨骼的影响。
3.已经使用的Vc,加上骨骼占用的VC,要少于128个。
由于Away对于骨骼 Transform推入GPU的计算是三个基向量,也就是占用3个缓存器,所以需要 骨骼数*3
后两个条件,出现了优化的空间:
首先,一般顶点最多受到3根骨骼影响已经很足够了。超过4根的信息可以考虑判断其影响大小,将超出的而且权重小的部分排除掉。
然后,可以考虑一下怎样减少输入的vc数量,把三个基向量看有没有办法变成2个四维向量分别传入位移和旋转信息。由于Away3D使用的md5动画格式本身就没有导出缩放的,所以在没有自己再写解析器的情况下,没有必要处理缩放的信息。
2、对于没有超出允许范围的情况,Away3D会通过代码解析器组成顶点程序,然后每帧推入骨骼的三个基向量给agal计算。
在SkeletonAnimator.setRenderState方法里面,把计算出的所有骨骼的信息(_globalMatrices)传入GPU,_numJoints是骨骼的数量。vertexConstantOffset是VC偏移量。也就是说,在vertexConstantOffset之前是其他顶点程序需要的VC,从vertexConstantOffset开始往后的所有VC都是骨骼信息使用的。由于每根骨骼有三个基向量,所以是_numJoints*3。
所以最终推入GPU的数据是这样的:
stage3DProxy._context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, vertexConstantOffset, _globalMatrices, _numJoints*3);
3、对于返回cpu的情况,顶点程序是最简单的m44 op,va0,vc0,不需要推送骨骼信息进入vc,
而是在SkeletonAnimator.setRenderState方法里面判断了if (_animationSet.usesCPU),
然后在SkeletonAnimator.morphGeometry方法里面在CPU模拟了一次GPU里面逐个顶点分别乘以受到影响的骨骼的基向量再乘以权重最后相加的过程,求出了每个顶点在每一帧实际的位置坐标,然后返回。
如果角色多、顶点多的情况下,这个过程在CPU算明显是超级大的负荷,难怪几个人物就卡死了。
这里又出现了优化点了,宁愿对美术资源进行限制,也不要用CPU来渲染。Away3D这个功能明显是鸡肋,只是为了做得全面,适应各种没有限制的模型,没有项目可行性。
Away3D骨骼优化的多种尝试及结果
之前针对stage3d支持骨骼数量的优化方案,做出过2个可能性的分析:
1、减少vc的推送,把transform拆分成一个四元数和一个三维向量。
2、拆分模型网格,把超出的部分拆分后分别推送。
之后的这段时间,我对这两种方法都做出了尝试。接下来谈一下结论:
第一种方法:
通过向VC推送四元数和三维向量,结合骨骼的bindpose矩阵,是可以算出顶点在动画之后的位置。之前有位朋友评论说在agal里面不能用四元数计算。这个说法不能算错,因为agal的确没有提供直接的四元数计算。但假如对3d图形数学熟悉的朋友,就可以直接把运算的公式在agal里面重现一下,就可以了。
这种做法的优点是cpu计算实在很少,比如在解析动画之后可以直接把动画关键帧保存成四元数和三维向量。然后需要计算动画的时候,直接获取然后推送就行了。但缺点是公式在agal里面运算会比较麻烦,一条点积的公式,在agal里面就需要拆分成好几行。而agal是有限制的,不能超过200行。我尝试的每个点受4根骨骼影响的情况,生成的agal代码已经有192行了。这是比较危险的情况了。假如我们的代码还需要做其他的处理,那么行数可能就不够了。
第二种方法:
我进行的尝试是把各个蒙皮模型的子模型的信息进行提取,获取到该网格实际受到哪些骨骼的影响,然后推送vc的时候,只推受到影响的骨骼。
这样做是从一个侧面的解决这个问题。我们做人物模型的时候就不能整个模型合并完再一起蒙皮,必须单独逐个部分的做,然后每个部分只蒙受到影响的骨骼。
这样做的结果是只要每个部位蒙皮的骨骼不超过一定量,整个人物就可以支持很多很多的骨骼。
不过这样做也是有缺点的。由于是把模型拆分了,所以本来每个人物每一帧只需要获取一次的骨骼信息,就变成要获取多次了。这样就变成加大了cpu的运算量。而且由于模型数量多了,渲染的实际次数也多了。
对于这种情况,我稍微优化了一下提取信息的方法,让他还是同一个角色同一帧只获取一次骨骼信息。
在没优化之前,由于超出骨骼数量会退回cpu运算,一个2000多面的角色带有40多根骨骼,away3d只能同屏运算10个左右就掉帧了。现在同样的面数和骨骼的人物,可以支持同屏80-90个左右而保持在30帧。
对于这个优化结果,我觉得还是不能实际的应用在项目中。或者我还是需要在很多地方找一下优化的可能性。因为在没有优化之前,对于2000多面的角色,但骨骼减少到20多根的情况下,同屏是可以跑100个而保持30帧的。如果是凑合着来做项目的情况下,其实直接让美工减少骨骼数量会是更快的解决方法。不过这样做,游戏的效果就被限制得很厉害了,很多好看的效果和服装就表现不出来了。