【性能优化需要关注的参数——Batches】
一、定义:什么是 Batches?
Batches,中文常译为批处理或绘制调用批次。在 Unity 的 Profiler 中,它指的是引擎在一帧内向图形显卡(GPU)发起的绘制调用(Draw Call)的次数。
可以把它理解为:CPU 命令 GPU “画一个东西” 的指令次数。
- 1 个 Batch = 1 条 Draw Call 指令。
- Batches 数越高,代表 CPU 需要向 GPU 发送指令的次数越多,CPU 的负担就越重,越容易成为性能瓶颈。
- 优化的核心目标就是:尽可能地减少 Batches 的数量。
为了更直观地理解其工作原理和优化逻辑,我们可以参考下面的流程图:
如图所示,CPU 在准备渲染时,会判断多个对象是否满足合批条件。如果满足,则会将多个对象打包,通过一次 Draw Call 处理,极大减少了 CPU 与 GPU 之间的通信开销。反之,每个对象都会产生一次 Draw Call,造成大量的性能浪费。
二、为什么 Batches 如此重要?(CPU-GPU 协作瓶颈)
要理解批处理为何重要,需要先了解 CPU 和 GPU 是如何协作渲染一个对象的:
- CPU 准备工作:CPU 需要为 GPU 准备好渲染所需的所有数据。包括:
- 使用哪个材质(Material)和着色器(Shader)
- 使用哪个纹理(Texture)
- 模型的网格(Mesh)数据
- 物体的位置、旋转、缩放(Transform)信息
- CPU 发起调用:CPU 准备好所有数据后,向 GPU 发送一条指令:“嘿,GPU,把这些数据画出来!” 这就是一个 Draw Call。
- GPU 执行渲染:GPU 收到指令和数据后,开始真正进行顶点变换、光栅化、像素着色等复杂的计算工作,将最终像素输出到屏幕上。
关键点在于:CPU 准备数据和发送指令的工作非常耗时! 如果一帧内有成千上万个 Draw Call,CPU 就会忙得不可开交,导致帧率下降和卡顿。而 GPU 通常非常强大,处理大量像素的速度远快于 CPU 准备指令的速度。
因此,优化的核心思路是:让 CPU 尽可能少地工作(减少 Batches),让 GPU 每次工作能处理尽可能多的东西。
三、Unity如何减少Batches?
Unity为所有渲染对象(模型、UI、粒子等)提供了多种批处理技术,其根本目的都是让一次Draw Call能处理更多对象。
1. 静态批处理 (Static Batching) - 适用于场景静态物件
- 工作原理:对于在编辑器标记为
Static
(静态)且使用相同材质的物体(如场景中的建筑、岩石、树木),Unity会在运行前将它们的数据合并成一个大的网格,这样这些物体就可以用一个Draw Call(一个Batch) 渲染出来。 - 优点:极致高效,运行时几乎无开销。
- 缺点:会显著增加内存占用和构建时间(因为要存储和加载合并后的大网格),且物体不能移动、旋转或缩放,否则会破坏合批。
2. 动态批处理 (Dynamic Batching) - 适用于小型动态物件
- 工作原理:在运行时,Unity会自动尝试将小型、使用相同材质的动态网格在当前帧合并,然后用一个Draw Call渲染。
- 限制非常严格(这也是为什么它经常“不工作”的原因):
- 网格顶点属性数量必须很少(通常少于900个)。
- 物体的缩放必须一致(不能一个有缩放,一个没有)。
- 使用的Shader不能包含多Pass等复杂操作。
- 使用Lightmap的物体无法动态合批。
- 注意:这是Unity的默认行为,但因其限制极多,对复杂项目效果非常有限,不应作为主要的优化依赖。
3. GPU实例化 (GPU Instancing) - 适用于大量相同物体
- 工作原理:这是为3D模型优化的利器!对于大量完全相同的物体(如草地、树木、子弹、士兵),它可以将模型网格和材质数据一次发送给GPU,然后通过传递不同的位置、颜色等少量属性数据来批量绘制。一次Draw Call可以绘制几十上百个物体。
- 如何使用:
- 确保材质球开启了
Enable GPU Instancing
选项。 - 在脚本中使用
Graphics.DrawMeshInstanced
API或通过Component间接使用。
- 确保材质球开启了
- 优点:性能极高,是处理大量重复物体的首选方案。
4. SRP Batcher - 适用于使用SRP的高端项目
- 工作原理:这是一种更高端的批处理技术,它不合并网格,而是通过优化Constant Buffer的提交方式来大幅减少Draw Call的准备工作。它需要配合可编程渲染管线(SRP,如URP/HDRP) 使用。
- 核心条件:物体必须使用同一个Shader变种(Shader Variant)。
- 优点:对大量使用相同Shader但网格、材质不同的动态物体有极佳的优化效果,且不增加内存。
四、Beyond UI: 模型与环境的批处理实践
对于3D模型/环境艺术家和程序员:
-
材质合并 (Material Merging):
- 核心原则:尽可能让多个模型使用相同的材质和相同的纹理。这是合批的最基本前提。
- 实践:将多个模型的纹理拼接到一张大图(图集化,Texture Atlasing)上,然后让它们引用这张大图和同一个材质球。
-
使用LOD(Level of Detail):
- 虽然LOD本身不直接减少Batches,但它通过减少远处模型的顶点数,降低了GPU负担,并且因为LOD模型通常也遵循材质共享原则,间接支持了合批。
-
遮挡剔除 (Occlusion Culling):
- 剔除被挡住的物体,使它们根本不被渲染,从而连Draw Call都省去了,是从根源上减少Batches的终极手段。
在Profiler中诊断非UI的Batches问题:
- 你依然会看到
Batch Breaking Reason
,其原因与UI类似且更多:Different Material
/Different Texture
:最常见的原因。解决方案就是合并材质和纹理。Different Shadow Caster
:投射阴影的设置不同。Different Lightmaps
:使用了不同的光照贴图。Different Reflection Probes
:使用了不同的反射探针。Different Motion Vectors
:运动矢量不同(例如一个静态,一个动态)。
总结:全局性的Batches优化思维
- Batches是渲染的通用货币:无论渲染什么,UI、模型、粒子,其底层都是通过Draw Call(Batch)来完成的。优化Batches就是优化渲染效率。
- 目标是减少通话次数:让CPU(项目经理)给GPU(画家)下指令时,尽量是“把这些一堆东西按这个方案画出来”,而不是“画这个A;好,再画那个B;好,再画那个C……”。
- 优化链:
- 首先,尝试通过设计(减少材质数量、使用图集)满足最基本的合批条件。
- 然后,根据场景选择最合适的批处理技术:静态批处理处理静物,GPU Instancing处理重复动态物体,SRP Batcher处理现代项目。
- 最后,使用Profiler定位无法合批的原因,并针对性解决。