概述
要在屏幕上绘制一个物体,引擎必须发送一个绘制命令(Draw Call)给图形的API,比如OpenGL或者Direct3D的接口。由于每次Draw Call,CPU要准备大量的数据,往往会引起较大的CPU的性能消耗。因此我们需要想办法减少Draw Call的数量。
一般情况下,Unity有两种比较方便使用的减少Draw Call的方法:动态批处理和静态批处理。
动态批处理:适用于面数比较小的物体,Unity会动态的将这些小物体的网格合并在一起,然后一次性绘制
静态批处理:将静态的物体的网格合并成一个大网格,然后一次性绘制
相比手动的去合并Mesh,Unity的合批有一个最大的优点就是仍然可以进行单个对象的剔除,当然也有缺点,比如静态批处理会增加内存的消耗并且需要额外的存储,动态批处理由于需要动态的合并Mesh,需要额外的CPU消耗。
我们可以通过Unity的设置开关批处理
批处理的材质设置(一些注意点)
只有用相同材质球的物体才能够被合批处理,所以我们想要更好的批处理效果,就需要尽可能的让不同的物体使用相同的材质球。
如果我们的材质球之间的区别仅仅是需要使用不同的贴图,那么我们就可以将这些贴图合并到一张大贴图中来实现使用同一个材质球的需求。我们一般用的UI图集就是这么处理的,所以我们的UI有那么多的绘制对象却只需要较少Draw Call。
如果我们需要在代码里面修改材质,要注意不能用Renderer.material接口,这个会导致复制一份材质实例出来,要用Renderer.sharedMaterial,这样材质球才是被共用的。
阴影的绘制会比较特殊,即使材质不一样,也基本上能合批,只要在阴影的Pass中用到的材质中的参数值是一样的,比如物体用了不同的贴图,但由于阴影Pass中不需要使用贴图,所以它们的阴影还是可以合批的
(我觉得Unity的文档有点坑,讲的一点都不清楚,很可能写文档的人自己都没搞明白,阴影的合批依然的分静态合批和动态合批来讨论,这样子说个大概有啥用)
未设置静态合批时,会尝试阴影动态合批,但仍然有诸如顶点数量的限制,如下:
将这些物体全部设置为静态后,有部分使用了不同ShadowPass的物体依然无法合批
动态合批(Dynamic batching)
Unity可以对满足条件的相同材质的移动物体进行动态的合批,并且这个合批只需要开启即可,不需要我们做额外的工作。
当然,看起来这么好的事情肯定会有很多限制
动态合批的限制
顶点数量限制
由于要动态的将物体进行合并为一个Mesh,所以合并的物体的顶点数量肯定不能很多,否则合并的性能消耗太大,反而得不偿失
顶点数量不超过300
顶点包含的属性数量不超过900
举个例子:
如果我们的Shader使用到了顶点位置、法线和一套UV,那这里会有三份属性,900/3 = 300,因此顶点数量不能超过300
如果我们的Shader使用到了顶点位置,法线,UV0,UV1,切线,总共五份属性,900/5 = 180,因此顶点数量不能超过180
Scale不允许有负值
其他条件满足后,Scale只要是正值,是多少都可以合批,但只要有任意的负值,这个物体就不再能被合并
所以为了动态合批,我们应该尽量避免设置负的scale
Position和Rotation不会有影响
我们可以简单做个测试
- 创建4个Cube
- 对他们分别做下调整
左侧两个Y轴是-1
右侧两个scale都是正值,只是x轴和y轴进行放大
我们来看合批的结果
我们看到虽然两个scale负值都是-1,但两个都无法合批
而另外两个虽然scale值不同,但是都是正值,所以也可以合批
必须是同一个材质实例
如果是不同的材质实例,即使他们所有的参数都一模一样,也会导致无法动态合批,所以在修改材质时不能用Renderer.material,而是要用Renderer.sharedMaterial
但阴影的合批会比较特殊,主要看用到的参数是否一致