在Unity游戏开发中,渲染性能往往是关键的性能瓶颈之一。当场景中存在大量游戏对象时,CPU需要频繁地向图形API(如OpenGL或Direct3D)发出绘制调用(Draw Call),这个过程非常消耗CPU资源。本文将详细介绍Unity中的多种合批技术,帮助你优化项目渲染性能。
1. 理解Draw Call与批处理基础
1.1 什么是Draw Call?
在Unity中,每个绘制调用都是CPU向GPU发出的渲染指令,通知GPU使用特定的顶点数据、材质和纹理进行绘制。每次绘制调用前,CPU都需要进行大量准备工作:设置材质状态、绑定纹理、传递着色器参数等。
如果场景中有大量使用不同材质的对象,就会产生大量的绘制调用,给CPU带来沉重负担,可能导致游戏帧率下降。
1.2 批处理的基本原理
批处理的核心思想是将多个使用相同材质的游戏对象的渲染操作合并为一个绘制调用。
批处理的主要优势在于保留了对象的单独剔除能力,同时减少了状态变化——这正是绘制调用中最耗时的部分。
1.3 批处理的前提条件
要实现批处理,有一个基本条件必须满足:只有共享相同材质的游戏对象才能被一起批处理。这意味着优化批处理的第一步就是在尽可能多的不同游戏对象之间共享材质。
核心合批技术
| 合批方式 | 核心原理 | 最佳使用场景 | 关键前提条件 | 主要代价 |
|---|---|---|---|---|
| 静态合批 | 运行前将静态物体网格合并成大模型 | 建筑、地形等完全不动的物体 | 1. 标记Static 2. 相同材质实例 | 内存占用显著增加 |
| 动态合批 | 运行时每帧合并小型动态物体顶点 | 金币、子弹等顶点数少的小型动态物体 | 1. 顶点属性≤900 2. 相同材质实例 3. 单Pass着色器 | CPU计算开销 |
| GPU实例化 | GPU一次性绘制同一网格的多个副本 | 草地、树木、同型号NPC等大量相似物体 | 1. 支持Instancing的着色器 2. 相同网格和材质 | 配置相对复杂 |
2. 动态合批
2.1 动态合批的工作原理
动态批处理是Unity自动进行的优化过程,无需开发者手动干预。对于满足条件的移动对象,Unity会在CPU上将它们的顶点转换到世界空间,并将多个对象的顶点数据组合在一起,一次性发送给GPU。
2.2 动态合批的使用
-
Project Settings > Player > Other Settings > Dynamic Batching开启动态合批。
-
需要使用相关的材质。
-
支持不同Mesh网格之间的合批。
2.3 动态合批的优势
能够自动处理小型动态物体的合批,无需开发者手动干预。
2.4 动态合批的限制条件
合批过程本身会消耗CPU资源。此外,它的限制条件非常严格:
- 顶点数量限制:批处理仅适用于总共不超过900个顶点属性且不超过300个顶点的网格。具体限制取决于着色器使用的顶点属性:如果着色器使用顶点位置、法线和单个UV,可批处理最多300个顶点;如果使用顶点位置、法线、UV0、UV1和切线,则只能批处理180个顶点。
- 缩放限制:如果游戏对象在变换中包含镜像(如一个对象缩放为+1,另一个为-1),则不会进行批处理。需要注意的是,根据实际测试,不同Unity版本对此处理可能不同,某些版本允许不同非统一缩放的对象进行批处理,只要负缩放轴向个数为偶数即可。
- 材质要求:即使对象基本相同,使用不同的材质实例也会阻止批处理。阴影投射渲染是个例外。
- 光照贴图限制:使用光照贴图的对象带有额外的渲染参数(光照贴图索引和偏移/缩放),通常需要指向完全相同的光照贴图位置才能批处理。
- 着色器限制:多通道着色器(multi-pass shader)会中断批处理。几乎所有Unity内置着色器在前向渲染中都支持多个光照,这会为额外光照执行额外通道,这些额外的每像素光照绘制调用不会进行批处理。
- 渲染路径限制:旧版延迟(光照pre-pass)渲染路径会禁用动态批处理,因为它需要绘制对象两次。
2.5 动态合批的适用场景
动态批处理最适合场景中的小道具,如可拾取的金币、子弹等小型、顶点数少且移动的对象。对于这类对象,确保它们使用相同的材质和缩放,并控制顶点数量在限制范围内。
需要注意的是,在游戏主机或使用Metal等现代图形API的平台,绘制调用开销通常较低,动态批处理可能不再具有明显优势。
3. 静态合批详解
3.1 静态合批的工作原理
静态合批就像是让场景中所有不动的物体提前拍一张“大合影”,之后渲染时只需要把这张合影拿出来用一次就行了。在构建项目或者场景加载时,Unity会把所有标记为"Static"且材质相同的物体,它们的网格顶点数据变换到世界空间下,然后合并成一个或几个大的网格。在后续的渲染中,这些原本独立的对象将被隐藏,转而由合并后的大网格一次性绘制出来。与动态批处理不同,静态批处理在运行时不需要进行顶点变换,因此效率更高,但会占用更多内存。
3.2 静态合批的使用
使用静态批处理很简单:
Project Settings > Player > Other Settings > Static Batching开启静态合批。

-
在Unity编辑器中,选中那些确定不会移动、旋转或缩放的物体(比如房子、石头、路灯),在检查器(Inspector)中勾选"Static"复选框。

-
确保这些对象共享相同材质。
此外,也可以通过代码使用UnityEngine.StaticBatchingUtility实现静态批处理:
// 将所有要合并的静态物体放入统一一个root
StaticBatchingUtility.Combine(root);
这种方法允许整体移动合并后的对象,并且可以处理代码动态加载的场景或物体。
3.3 静态合批的优势
它能极其高效地减少静态物体的Draw Call,并且运行时几乎没有额外的性能开销。
3.4 内存开销考虑
静态批处理的主要缺点是内存开销。如果多个游戏对象在静态批处理前共享相同的几何体,Unity会在编辑器或运行时为每个对象创建该几何体的副本。
例如,在茂密森林关卡中,如果将树标记为静态,可能会产生严重的内存影响。在这种情况下,需要权衡渲染性能与内存占用,决定是否使用静态批处理。
3.5 平台限制
静态批处理在大多数平台上的限制是64k顶点和64k索引(OpenGLES上为48k索引,macOS上为32k索引)。
4. GPU实例化(GPU Instancing)
当你需要渲染大量结构相同但位置、颜色等略有差异的物体(比如一片草地、一群同型号的敌人)时,GPU实例化是最佳选择。GPU Instancing也是Unity提供的一种优化方案,其本质是使用一个DrawCall渲染多个相同材质的网格对象,从而减少CPU和GPU的开销.比较适合场景中大量重复的物体如树木和草地等。
4.1 GPU实例化的工作原理
与上述两种在CPU端合并网格的方式不同,GPU实例化一次将主模型的网格数据发送给GPU,同时额外提供一个包含所有实例不同信息(如位置、颜色等)的数据缓冲区。GPU通过一次Draw Call,就能绘制出成千上万个变体,效率极高。
4.2 GPU实例化的使用
- 需要使用支持GPU Instancing的着色器。许多Unity内置着色器(如Standard Shader)自带此选项,在材质的Inspector窗口中勾选“Enable GPU Instancing ”即可。

4.3 GPU实例化的优势
处理大量相同物体的渲染时,性能远超动态合批,且不会像静态合批那样增加内存负担。
4.4 使用限制
- 会合并使用相同材质和Mesh的对象。
- 材质需要支持GPU Instancing。
- Tranform信息需要有所不同。
- 粒子对像不能合批。
- 使用MaterialPropertyBlocks的游戏不能合批。
- Shader必须是使用compatible。
- 动态合批与GPU实例化是互斥的。
5. 其他批处理技术与优化建议
5.1 手动合并网格
对于复杂的静态对象,如带有大量抽屉的橱柜,手动在3D建模应用程序中合并网格,或使用Unity的Mesh.CombineMeshes方法,可以显著减少绘制调用。
手动合并网格的优势在于完全消除多个对象间的绘制调用,但缺点是合并后的对象将失去个体剔除能力,可能导致GPU处理更多不可见顶点。
5.2 纹理图集
如果多个对象使用相同材质但不同纹理,可以考虑将这些纹理组合成单个大纹理——称为纹理图集(Texture Atlas)。一旦纹理位于同一图集中,就可以使用单一材质,使批处理成为可能。
纹理图集是优化2D游戏和UI性能的常用技术,也适用于3D场景中多个相似对象的情况。
5.3 材质属性管理
在脚本中修改材质属性时需特别注意:修改Renderer.material会创建该材质的副本,应改用Renderer.sharedMaterial来保持材质共享。
5.4 渲染器特定批处理
Unity对不同类型渲染器的批处理支持各不相同:
- 目前,仅对网格渲染器、轨迹渲染器、线渲染器、粒子系统和精灵渲染器进行批处理。
- 蒙皮网格、布料和其他类型的渲染组件不会进行批处理。
- 渲染器仅与其他相同类型的渲染器一起批处理。
5.5 阴影投射的批处理
阴影投射物即使使用不同材质,通常也可以在渲染时接受批处理。Unity中的阴影投射物即使具有不同材质也可以使用动态批处理,只要阴影pass所需材质中的值相同即可。
6. 实战建议与策略
6.1 选择合适的批处理方式
根据项目需求选择合适的批处理策略:
- 静态物体:优先使用静态批处理,但需注意内存消耗。
- 小型动态物体:使用动态批处理,确保满足顶点数、缩放和材质要求。
- 复杂静态场景:考虑手动合并网格或使用
StaticBatchingUtility。 - 包含动画的物体:其中不动部分可以标记为Static。
6.2 性能权衡
在性能优化时需要做出权衡:
- 静态批处理:以内存消耗换取渲染性能。
- 动态批处理:以CPU计算换取绘制调用减少。
- 手动合并网格:以灵活性和剔除精度换取极致性能。
6.3 检测与分析
使用Unity的Stats窗口查看批处理效果:
-
帧调试器(Frame Debugger):这是最直接的工具。通过
Window > Analysis > Frame Debugger打开,启用后,你可以清晰地看到每一帧的所有Draw Call。成功合批的物体会被标记为 “Batched: Static” 或 “Batched: Dynamic”。
-
统计窗口(Statistics):在Game视图下,点击"Stats"按钮打开。关注"Batches"项,它表示实际提交的Draw Call数量。通过与Draw Call总数对比,可以直观感受到合批带来的优化。
-
Draw Calls:总绘制调用次数。
-
Saved by batching:通过批处理节省的绘制调用数。

通过这些数据可以评估优化效果,并识别哪些对象破坏了批处理。
结论
Unity的批处理技术是优化渲染性能的重要手段。通过合理使用动态批处理、静态批处理、GPU实例化以及其他手动合并网格等技术,可以显著减少绘制调用,降低CPU负担,提升项目整体性能。

7336

被折叠的 条评论
为什么被折叠?



