UPL6-2——动态批处理

动态批处理

动态批处理(Dynamic Batching)是一种由 Unity 自动完成的 CPU 端优化技术
它会在运行时把多个使用相同材质的动态物体的网格数据合并到一次 DrawCall 中
从而减少 DrawCall 的调用次数,降低 CPU 提交渲染命令的开销

它的 3 个重要优势:

  1. 批处理在运行时生成
  2. 批处理中包含的对象在不同帧之间可能有所不同,取决于哪些网格在主摄像机视图中当前可见
  3. 场景中运动的对象也可以批处理

它的主要原理是:
Unity 在渲染时,原本需要将每个物体的网格、材质数据通过 CPU 发送给 GPU,即一次 DrawCall
假设 3 个相同材质的 3D 对象(网格可不同,材质需相同),渲染他们需要 3 次 DrawCall
但是如果在开启动态批处理时,并且这些对象满足动态批处理的限制条件
那么动态批处理会将这 3 个对象合并为一个更大的网格缓冲区,并和相同的材质一起,通过一次 DrawCall 提交给 GPU,从而达到减少 DrawCall 的目的

注意:这种合并每帧都需要重新计算批次(即将满足条件的对象进行批处理),所以叫动态

说人话:动态批处理是一种 Unity 在运行时,由 CPU 自动将满足限制条件的多个动态物体网格合并成一个大网格,一次性提交给 GPU,从而减少 DrawCall 的技术

注意:

  1. 动态批处理是内置渲染管线中的重要优化手段,SRP(URP / HDRP)管线中主要采用 SRP Batcher(我们之后会讲解)
  2. Unity 中创建了一个对象,但是 DrawCall 并不是增长了 1 次,而是 n 次,是因为阴影、深度等贴图以及多 Pass、透明、子网格、多材质都会增加 DrawCall 的次数

如何开启动态批处理

  • 内置渲染管线:Player Settings ——> Other Settings ——> Rendering ——> Dynamic Batching

    image

  • URP 管线:URP 管线,主要采用的是之后要讲解的 SRP Batcher 优化方案

    在较新版本中(Unity 2023 之后)URP 管线中已不再怎么使用动态批处理,如果想要使用,需要切换为内置渲染管线

假设场景上有 5 个使用相同材质的对象

image

  • 未开启动态批处理时的 Batch 数

    image

  • 开启动态批处理时的 Batch 数

    image

可以看到,开启动态批处理后,Batch 数有明显的下降

动态批处理的限制

  1. 必须满足的硬性条件

    • 顶点 ≤ 225 或 300(Unity 2021 之前为 300)
    • 顶点 × 顶点属性 ≤ 900

    即,每个网格最多 225 或 300 个顶点。并且着色器使用的顶点属性不能超过 900
    其中,顶点属性是在 Shader 中会用来进行渲染计算的数据,常用属性有 顶点的 位置、法线、切线、多套 UV 纹理、顶点颜色、骨骼权重、骨骼索引等等

    对于简单 Shader 来说,一般至少需要 位置、法线、UV 3 个属性,则:

    • 若顶点最多 225 个,则 顶点 × 顶点属性 为 225 × 3 = 675
    • 若顶点最多 300 个,则 顶点 × 顶点属性 为 300 × 3 = 900

    此时 顶点数量 满足条件,顶点 × 顶点属性 也满足条件,就会进行动态批处理

    若对于一些复杂Shader,假设使用 5 个 属性,那么网格的顶点上限 就是 900 ÷ 5 = 180 个顶点
    只要超出 225 顶点(Unity 2021 之前为 300)或 顶点 × 顶点属性超过 900 任意一个限制,该网格将不会被动态批处理

  2. 相同材质实例

    不同对象之间,必须使用同一个 Material 对象(同一内存引用)
    并且材质属性、Shader 关键字、渲染状态必须完全一致

  3. 不能是 SkinnedMeshRenderer(蒙皮网格渲染器)

    一般骨骼动画模型会使用蒙皮网格渲染器,因为每帧 CPU 都要做蒙皮变形,数据不适合直接合并
    只有 MeshRenderer​(网格渲染器)和 ParticleSystem(粒子系统)能进行动态批处理

  4. 对象不能再变换中包含镜像

    即 一个正比例缩放的游戏对象 和 一个负比例缩放的游戏对象 不能放在一起批处理

  5. 相同光照状态

    如果使用了光照烘焙中的光照贴图,那必须保持使用的是同一个光照贴图
    并且 光照探针、反射探针、雾效开关 等必须一致,否则渲染状态切换会打断批处理。

  6. 同一渲染队列

    渲染队列(Render Queue)必须一致,比如都在 不透明(Geometry)队列,否则会被排序拆开

  7. 材质不能开启 GPU Instancing

    开启了 Enable GPU Instancing 会让 Unity 用 Instancing 路径,而不是动态批处理(两者互斥)

  8. Shader 最好单 Pass

    否则每个 Pass 都有单独的 DrawCall

等等

适合使用动态批处理的情况

在使用内置渲染管线的情况下,大量使用相同材质的小型动态网格,并且满足批处理条件,且顶点数低于 225(300) / 900 属性限制

关键点:物体是动态的,位置、旋转、缩放会发生改变,不能用静态批处理时,首选动态批处理

使用场景:

  1. 游戏中的小道具、小物件

    比如 地图上的金币、宝石、道具箱子、掉落物

  2. 简单的动态环境元素

    比如 旋转的风扇叶片等

  3. 低顶点数的动态建筑元素

    小路灯、小栅栏、路标等(材质相同、顶点少)

  4. 粒子系统的网格粒子(Mesh Particle)

    多个相同网格的小粒子,如果没用 GPU Instancing,也能走动态批处理

等等

不太适合使用动态批处理的情况

  1. 网格顶点数大(超 225 或超 900 属性限制)
  2. 多 Pass Shader(描边、ForwardAdd 多光源、阴影等)
  3. 材质多样化(不同材质实例会切断批处理)
  4. Skinned Mesh(角色、骨骼动画模型)
  5. URP / HDRP 管线,新版本,优先用 SRP Batcher + GPU Instancing