unity3d动态合批设置_Unity3DSkinnedMeshRenderer合批优
化
最近做了性能优化相关的⼯作,其中⼀些是关于战⽃模块的渲染的。主要是对场景中使⽤的基于SkinnedMeshRenderer的⽹格进⾏了⼀些合批优化(降DC),记录如下。项⽬使⽤的Unity版本为5.6.4p1。
游戏中的战⽃模块是这样的:
战⽃逻辑由服务器承担,战⽃瞬间完成,将战⽃过程发给客户端,客户端负责播放;
场景中有敌我双⽅最多12个⽅阵(双⽅各2排3列),每个⽅阵由12个⼩兵组成(3排4列);
战⽃基于回合制,敌我各个⽅阵依次攻击,攻击时会出列,冲到⽬标⽅阵前,攻击完成后返回;
⼩兵使⽤的是带动画的3D模型,使⽤AnimationController控制动作;
每个⽅阵的⼩兵是完全⼀致的(含模型、贴图、动作)
综上,使⽤战场管理器根据后端发来的战⽃数据,改变各个⽅阵的position以及控制AnimationController
来实现战⽃过程的播放。
战⽃模块中的渲染部分,从最初的版本到⽬前的⼀个可接受的版本,总共经历了多次迭代,以下详细记录,本⽂中使⽤的截图⾮项⽬截图(单个模型⾯数⼩于项⽬中使⽤的⾯数,此处使⽤的模型来⾃AssetStore,仅供演⽰使⽤)。本⽂的⼯程可以在github上看到所有源码。
原始版本
每个⽅阵12个⼩兵,实实在在地放了12个SkinnedMeshRenderer(后边简称为SMR),在⽅阵的脚本中保存了所有的AnimationController(后边简称AC),当⽅阵要移动时则移动所有的SMR,播放动画时同时为所有AC设置状态。
此⽅案每⽅阵共计12个SMR和12个AC。
两轮迭代
迭代⼀:共享⽹格
每个⽅阵放1个带SMR的⼩兵模型,每帧Bake⽹格,同时有12个MeshRenderer,其MeshFilter指向的Mesh即SMR每帧Bake出的Mesh。考虑到SMR的AC会控制SMR及⽗节点的运动,保留了所有的AC,仅仅把实际⽤来展⽰的SMR换成了普通的MeshRenderer。
此⽅案每⽅阵共计1个SMR和12(或13)个AC,节省了⼀些⾻骼动画的计算。所有的MeshRenderer使⽤相同的⽹格和材质,但是由于单个模型顶点数的原因⽆法合批绘制。
迭代⼆:GPU Instancing
每个⽅阵1个带SMR的⼩兵模型,每帧需要Bake,并将Bake的Mesh使⽤Graphics.DrawMeshInstanced在指定的位置绘制12份。这⼀操作需要系统⽀持GPU Instancing并且材质(shader)也要⽀持。⼤概是在shader中增加以下的代码。
1#pragma multi_compile_instancing
在顶点着⾊器的输⼊的结构体中增加:
1UNITY_VERTEX_INPUT_INSTANCE_ID
在顶点着⾊器函数中增加:
1UNITY_SETUP_INSTANCE_ID(v);
其中v是顶点着⾊器的输⼊。这⾥是本⽂中使⽤的⽀持Instancing的shader。
最后不要忘了将材质是否⽀持GPU Instancing属性设置为true。
此⽅案每⽅阵共计1个SMR和1个AC,但需要系统和材质⽀持GPU Instancing。12个⼩兵只需⼀个DrawCall即可绘制出来。当整个⽅阵移动时,需要⼿动更新这些Instancing的变换矩阵。
⽬前项⽬中使⽤了结合了迭代⼀和迭代⼆的⽅案,⾸先会根据系统和材质是否⽀持GPU Instancing做出判断,如果⽀持则使⽤迭代⼆的⽅案,否则使⽤迭代⼀的⽅案。
备选⽅案:GPU Instancing的共享⽹格
如果系统⽀持GPU Instancing且使⽤迭代⼆中⽀持GPU Instancing的材质,在使⽤迭代⼀共享⽹格⽅案时也可以合批(按照GPU Instancing处理,可以突破最⼤顶点数对合批的限制)。
与迭代⼆相⽐,这种备选⽅案不需要计算Instance绘制时的变换矩阵,但是却依赖AC来控制各⼩兵中SMR及其⽗级节点的变换。
⽅案汇总
在⼀台⽀持GPU Instancing的机器上对⽐四种⽅案如下:
⽅案A
⽅案B
⽅案C
⽅案D
原始版本
迭代⼀
unity3d animation迭代⼆
备选⽅案
以下依次是⽅案ABCD场景statistics:
⽅案A:
⽅案B:
⽅案C:
⽅案D:
可以看到基本上⽅案A和⽅案B是同⼀个level的,⽅案C和⽅案D是同⼀个level,FPS由于⼀直波动所以截取到的数值只能表现出⼤概的趋势。
下边依次是⽅案ABCD绘制Frame截图(使⽤FrameDebugger截取):
⽅案A:
⽅案B:
⽅案C:
⽅案D:
可以看到,⽅案B中因为超出300顶点这⼀限制所以⽆法合批,但是修改材质之后(⽅案D),⽀持GPU Instancing,不再受此限制的约束。
包含以上四个⽅案的项⽬⼯程都放在github。
⼀些弯路
除了前边的⼀些⽅案之外,还曾经⾛过⼀些弯路。在使⽤基于共享⽹格的迭代⼀⽅案时,发现会因为顶点数量超出限制⽽导致⽹格和材质都相同的多次绘制⽆法合批。就想到使⽤Mesh.Combine来合并⽹格,初始化时合并⼀次并在每帧更新顶点和变换矩阵,或者每帧合并。
使⽤这种⽅案,确实可以合批降低DC,但是⼤⼤增加了CPU的计算负荷,相⽐之下整体性能降低,在移动平台上FPS不升反降。于是抛弃了此⽅案。
其它思路
另外有⼀些思路,做了⼀些简单尝试,但是未落实成具体⽅案:
实时三渲⼆
使⽤另外的相机将⼩兵模型绘制到RenderTexture,然后将此RT作为纹理,在场景中使⽤12个2D的Sprite来显⽰。
⽬前项⽬中使⽤的是正交相机且固定相机位置,此⽅案是可⾏的。但是后期需求变化可能会替换为透视相机且相机允许转动,每个⼩兵呈现出的2D图像不同,这⼀⽅案将失效。
抛开⽆法满⾜未来需求这⼀点,还有其它的⼀些因素需要考虑,这种绘制⽅式可能增加额外的性能开销,如多了⼀个相机和RT,各个Sprite的空间位置关系、绘制层级控制,⼩兵与HUD的绘制层级控制等。
⼏何着⾊器
在shader中增加⼏何着⾊器(Geometry shader),以控制将⼀个模型的顶点(三⾓形)绘制出更多份。由于项⽬最终是部署到Android和iOS 平台,所以很快放弃了这⼀思路。
如果将来会有PC或者主机平台的项⽬,或许可以尝试。将原来的坐标转换(MVP矩阵变换)的⼯作从顶
点着⾊器中移出,放⼊⼏何着⾊器。并且⼏何着⾊器中将输⼊的顶点(三⾓形)绘制多份,最终送达⽚段着⾊器⼀并绘制。由于对于Unity及图形学的研究尚浅,不知这种⽅案是否可以实现,如果真的可以实现,也不了解这种⽅法是否会带来更⼤的性能开销,待后续有时间再研究研究这个。
Animation Instancing
最后⼀个⽅案是Animation Instancing,这种⽅法是在⽹上看到的(链接),由于与项⽬中的需求有些差异,并且时间关系也没有去做更多的探索。这种⽅案可以针对更⾼要求的SMR合批绘制,不限制各个SMR必须有相同的动作。预先⽣成动画保存到纹理中,然后运⾏时读取纹理来播放动画,⼤幅降低了CPU的负载(提升了少量GPU的负荷和内存占⽤),整体的渲染性能⼤⼤提升。但是⽬前⽆法⽀持动画状态切换,不过作者表⽰很快会⽀持这⼀功能。
REFERENCE
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论