动态合批与静态合批其本质是对将多次绘制请求,在允许的条件下进行合并处理,减少cpu对gpu绘制请求的次数,达到提高性能的目的。
两者是否开启都可以在Project Settings -> Player -> Other Settings 下的 Static Batching 和 Dynamic Batching
1.静态合批是将静态(不移动)GameObjects组合成大网格,然后进行绘制。静态合批使用比较简单,PlayerSettings中开启static batching,然后对需要静态合批物体的Static打钩即可,unity会自动合并被标记为static的对象,前提它们共享相同的材质,并且不移动,被标记为static的物体不能在游戏中移动,旋转或缩放。但是静态批处理需要额外的内存来存储合并的几何体。注意如果多个GameObject在静态批处理之前共享相同的几何体,则会在编辑器或运行时为每个GameObject创建几何体的副本,这会增大内存的开销。例如,在密集的森林级别将树标记为静态可能会产生严重的内存影响。此时就必须去权衡利弊,为了更少的内存占用,可能需要避免某些GameObjects的静态批处理,尽管这必须要牺牲一定的渲染性能。
静态合批在大多数平台上的限制是64k顶点和64k索引(OpenGLES上是48k索引,macOS上是32k索引)。
2.动态合批是将一些足够小的网格,在CPU上转换它们的顶点,将许多相似的顶点组合在一起,并一次性绘制它们。
无论静态还是动态合批都要求使用相同的材质,动态合批有以下限制:
-
动态合批处理动态的GameObjects的每个顶点都有一定的开销,因此动态合批处理仅应用于包含不超过900个顶点和不超过300个顶点的网格。
- 如果shader中使用Vertex Position, Normal和single UV,可以批量处理最多300个顶点,而如果shader中使用Vertex Position, Normal, UV0, UV1和Tangent,则只能使用180个顶点。
- 注意:将来可能会更改属性计数限制。
-
如果GameObjects在Transform上包含镜像,则不会对其进行动态合批处理(例如,scale 为1的GameObject A和scale为-1的GameObject B无法一起动态合批处理)。
- 为了验证此说法,笔者亲自做了测试,对于两个同样的物体:
a.一个物体保持scale为(1,1,1),改变另一个物体的scale:
1).当三个轴向的缩放值为负数(简称负缩放)的个数为偶数时可以合批处理,即scale为(1,1,1)和scale为(-1,-2,3)、(-1,2,-3)、(2,-3,-4),此时三个轴向的负缩放的个数为0个、2个、2个和2个,均为偶数个,可以合批处理,注意正负号,缩放数值可以随便更改;
2).当三个轴向的负缩放的个数为奇数时可以不能合批处理,即scale为(-1,1,1)、(1,-2,3)、(1,2,-3)和(-2,-3,-4),此时三个轴向的负缩放的个数为1个、1个、1个和3个,均为奇数个,不可以合批处理。
b.同时改变两个物体的缩放,仍然符合上述的三个轴向的负缩放的个数为奇数个时不合批,偶数个时合批:
1).A物体(1,2,3),B物体(-1,2,3)、(1,-2,3)、(1,2,-3),奇数个负缩放不合批
2).A物体(-1,2,3),B物体(-1,2,3),奇数个负缩放不合批,其他组合类似
3).A物体(-1,-2,3),B物体(1,2,3)、(-1,2,-3),偶数个负缩放,其他组合类似
总结为:对于两个相同的物体,当两个物体三个轴向的负缩放的个数为偶数个时(0个,2个),可以合批,当两个物体中任意一个物体或者两个物体同时三个轴向的负缩放的个数为奇数个时,不合批。此前有很多文章说对于不同缩放的物体,无论是否为负缩放,均不会合批处理,笔者使用的unity版本是unity2019.1.7f1,可能是因为unity在某个版本已经修复了该问题,有知道的朋友,请告知一声,谢谢。
- 为了验证此说法,笔者亲自做了测试,对于两个同样的物体:
-
使用不同的Material实例会导致GameObjects不能一起批处理,即使它们基本相同。阴影渲染(shadow caster)是一个例外,下文会解释为什么。
-
带有光照贴图的GameObjects有额外的渲染器参数:保存光照贴图的索引和偏移/缩放。一般来说,动态光照贴图的GameObjects应指向完全相同的光照贴图位置才能被动态合批处理。
-
使用多个pass的shader不会被动态合批处理。
- 几乎所有Unity Shaders在forward rendering(前向渲染)中都支持多个灯光 ,为了他们有效地进行额外的传递。额外的pre-pixel lights的绘制调用不会被动态合批处理,这个在移动平台上比较少遇到。
- The Legacy Deferred (light pre-pass) rendering path 中禁用动态批处理,因为它必须绘制两次GameObjects。
github 上Unity官方总结了25种不能被合批处理的情况, Unity-Technologies/BatchBreakingCause
动态合批处理的工作是在cpu上将所有GameObject顶点转换到世界空间,因此,如果该工作小于执行绘制调用,则这是一个优势。绘制调用的资源需求取决于多方面的因素,主要是使用的图形API。例如,在 consoles or modern APIs,比如Apple Metal,绘制调用开销通常要低得多,此时动态合批处理就不再是优势了,所以动态合批处理并不是万能的啊。
动态合批处理(Particle Systems, Line Renderers, Trail Renderers)
对于Unity动态生成的几何体的组件,动态合批处理与网格相比有不同的工作方式。
- 对于每个兼容的渲染器类型,Unity将所有可混合内容构建为1个大型顶点缓冲区(Vertex Buffer)。
- 渲染器为批处理设置材质的状态。
- Unity将顶点缓冲区(Vertex Buffer)绑定到图形设备。
- 对于动态合批处理中的每个渲染器,Unity将偏移更新到顶点缓冲区(Vertex Buffer),然后提交新的绘制调用。
在评估图形设备调用的成本时,渲染组件的最慢部分是材质状态的设置。相比之下,将不同的偏移绘制调用提交到共享顶点缓冲区的速度非常快。
注意:
1.不同材质的阴影会动态合批,只要绘制阴影的 pass是相同的,因为阴影跟其他贴图等数据无关
2.目前,只有 Mesh Renderers, Trail Renderers, Line Renderers, Particle Systems和Sprite Renderers支持合批处理,而skinned Meshes,Cloth和其他类型的渲染组件不支持合批处理。
3.渲染器仅与其他相同类型的渲染器进行合批处理。
4.对于半透明的GameObject,按照从前到后的顺序绘制,Unity首先按这个顺序对GameObjects进行排序,然后尝试对它们进行批处理,但由于必须严格满足顺序,这通常意味着对于半透明的材质更少使用合批处理。
5.手动的合并GameObject是代替合批处理的好办法,比如使用Mesh.CombineMeshes,或者直接在建模时将多个网格合并成单个网格。