SRP Batcher 可编程渲染线合并器,
在传统的Built-in管线中,游戏卡顿往往不是因为模型面数太高,而是因为DrawCall过多,或者更准确的说是SetPassCall渲染状态切换过多。
传统渲染流程:CPU要绘制一个物体时,它需要向GPU发送指令,SetPassCall:把物体的材质,贴图,Shader参数上传到GPU,然后调用DrawCall告诉GPU把这个物体的网格画出来,
如果场景里有100个物体,
1.如果他们使用的是同一个材质,并且材质上的所有参数都一样,如果开了动态合批或静态合批,就会把这些同参数同材质的物体的mesh合并到一个大的mesh,在静态合批里勾选static勾选框,这样cpu就会把这些材质贴图,mesh的顶点一起发给gpu,执行一次setpasscall,然后调用一次drawcall就可以将这些mesh一次性的画出来,如果是动态合批,就要求这个mesh是不能有位移的,并且不能进行更换材质操作,并且顶点如果超限之后也不能进行将多个mesh合并到一个大的mesh,
2.如果这些物体使用的是同一个材质,不同的材质参数,参数变了,至少会多一次 Draw Call;是否多一次 SetPass 要看 shader pass 有没有变、Unity 能不能复用同一次 pass 设置。
- 同一个 Material 资源 + MaterialPropertyBlock(推荐)
renderer.sharedMaterial = sharedMat; // 同一个 Material
renderer.SetPropertyBlock(mpb); // 每个物体不同参数.
传统 SRP 渲染(无 SRP Batcher)
每画一个物体,CPU 大致要做:
绑定 Shader Pass
→ 上传/绑定 Material 全部属性(纹理、颜色、向量…)
→ 上传 Object 数据(矩阵、Lightmap 等)
→ Draw
换下一个物体(哪怕 shader 相同、只是 _Color 不同)时,往往又要 重新走一遍 Material 绑定流程,CPU 开销大。
SRP Batcher 换了一种完全不同的思路。它发现:在现代图形 API 中,切换材质参数其实很快,真正慢的是切换 Shader 本身(即切换底层的渲染管线状态 PSOs)。
只要物体的 Shader 相同,即使材质球不同,它们底层的渲染内核也是一样的。因此,SRP Batcher 在 GPU 显存里开辟了两块连续的专属缓冲区(Constant Buffers,常量缓冲区):
全局每对象缓冲区(UnityPerDraw CBUFFER): 存放每个物体独特的数据,比如物体的世界坐标(Transform)、矩阵信息等。
全局每材质缓冲区(UnityPerMaterial CBUFFER): 存放材质球的专属属性,比如颜色、贴图的缩放(Tiling/Offset)等。
第一步:数据预载(极快)
CPU 不再像以前那样“画一个物体传一次数据”,而是把场景里所有物体的坐标数据和材质数据,一次性、批量地写进 GPU 的这两个 CBUFFER 显存缓冲区中。
第二步:命令流(Command Loop)
当数据都在显存里对齐后,CPU 只需要发送一个极快的、包含大量 Draw 调用的指令流。GPU 自己通过更改“指针偏移量”,就能直接去缓冲区里抓取第 1 个物体的坐标和材质进行绘制,画完立刻抓取第 2 个,中途不需要 CPU 重新介入发送 SetPass Call。
绘制时流程变成:
① 遇到新 Shader Variant → 绑定一次 Shader Pass(一次「重」切换)
② 画物体 A → 只更新 PerDraw buffer + 指向 Material 的 CBUFFER → Draw
③ 画物体 B(同 Shader Variant,不同 Material)→ 只换 Material CBUFFER 指针 + 更新 PerDraw → Draw
④ 画物体 C(同 Material)→ 只更新 PerDraw → Draw
打断 SRP batch 的条件:换了 Shader Variant(不同 shader、不同 pass、不同 keyword 组合)
不打断的条件:同一 Shader Variant 下,换 Material、换物体参数 —— 只更新 CBUFFER,不必完整重绑 Material
所以:
100 个物体、100 个不同 Material、但同一个 URP Lit Shader → 可能仍是 1 个 SetPass + 100 个 Draw,CPU 侧比传统路径轻很多
100 个物体、10 种 Shader Variant → 大约 10 个 SRP batch(每种 variant 一段)
SRP Batcher 不合并 mesh,Draw Call 数在 Profiler 里可能看起来没少,但 SetPass Calls 和 RenderLoop 耗时 会明显下降。
SRP Batcher 依赖 固定内存布局,Shader 必须满足:
// 所有材质属性 — 必须在一个 CBUFFER 里
CBUFFER_START(UnityPerMaterial)
float4 _BaseColor;
float4 _BaseMap_ST;
// …
CBUFFER_END
// 所有引擎内置 per-object 属性 — 必须在一个 CBUFFER 里
CBUFFER_START(UnityPerDraw)
// unity_ObjectToWorld 等由引擎注入
CBUFFER_END
Pass 之间 CBUFFER 声明 不一致
遍历待渲染物体
│
├─ Shader Variant 变了? ──→ 新 SRP Batch,重新 SetPass
│
├─ Material 变了? ──→ 切换 GPU 上已缓存的 Material CBUFFER(轻量)
│
└─ Object 变了? ──→ 更新 UnityPerDraw 大 Buffer 中对应槽位(轻量)
│
└── Draw Mesh
减少 Shader Variant 数量(少 keyword、少 pass)→ 一个 SRP batch 里能塞更多 draw
同功能尽量共用一个 Shader,用不同 Material 区分参数
自定义 Shader 严格遵循 CBUFFER 规范
不要用 MPB(在 URP/HDRP 下)
用 Frame Debugger / Profiler 看 SRP Batch 和 SetPass Calls,不要只看 Batches 数量
SRP Batcher 通常会比内置管线的传统渲染路径多占一些 GPU 内存,但这是用 显存换 CPU 的权衡,而且和 Static Batching 那种「暴涨式」占内存不是一回事。
SRP Batcher 的核心设计是:把数据长期放在 GPU 上,少做每帧重复上传。
三个内存块
1.Material CBUFFER(UnityPerMaterial)
每个兼容 SRP Batcher 的 Material 在 GPU 上有一份持久常量缓冲
通常 几 KB / Material,和 shader 属性数量有关
2.Per-Object 大 Buffer(UnityPerDraw)
存矩阵、Lightmap、SH 等 per-renderer 数据
随 可见 Renderer 数量 增长,仍是常量缓冲量级
3.纹理 / Mesh
贴图、模型本身
不变(SRP Batcher 不复制 mesh)
实际影响大不大?
对大多数项目:SRP Batcher 带来的显存增量相对纹理、Mesh、Render Target 可以忽略。
真正要留意的场景:
Unique Material 极多(几千种各不相同的材质)→ Material CBUFFER 数量线性增加
同屏 Renderer 极多 → Per-Object 大 Buffer 变大
极低端机、显存非常紧 → 需要实测,但瓶颈更常见的是贴图/分辨率,而不是 SRP Batcher 本身
运行时频繁改 Material 属性 → CBUFFER 要重传,CPU 优势下降,显存占用仍在
一帧里 CPU 和 GPU 各做什么
以「100 艘船、同 URP Lit Shader、100 个不同 Material」为例:
首帧 / Material 第一次出现
CPU:
① Mesh、Texture 已在 VRAM(加载阶段完成)
② 为新 Material 创建并上传 UnityPerMaterial CBUFFER → 留在 GPU
③ 绑定 Shader Variant(一次 SetPass / SRP Batch 开始)
④ 写 UnityPerDraw 里物体 A 的 slot(矩阵等)
⑤ Draw:告诉 GPU 用哪个 VBO、哪张 Texture、哪个 Material CBUFFER、哪个 Draw slot
后续每帧(Material 参数没变)
CPU:
① Mesh / Texture / Material CBUFFER → 不动(已在 VRAM)
② 只更新 UnityPerDraw 大 Buffer 里各物体的 transform 等
③ 对每个物体发 Draw:换 Material CBUFFER 指针 + 换 PerDraw slot + 同一 VBO/Texture 绑定
GPU:
按 Draw 命令从 VRAM 取顶点、贴图、常量 → 跑 Vertex/Fragment Shader → 输出像素
你说的「告诉 GPU 去内存里取东西组装」——对 GPU 侧就是这样;但 CPU 每帧不是重新上传 mesh/贴图/Material,而是 更新 PerDraw + 发绑定与 Draw 命令。
和内置管线对比(帮助记忆)
内置管线(无 SRP Batcher):
每 Draw 可能整套重绑 Material 属性(CPU 重)
Mesh/Texture 同样常驻 GPU,也不是每帧传整块
SRP Batcher:
Material 属性已在 GPU(UnityPerMaterial)
每 Draw 主要换 PerDraw slot + 轻量绑定
Mesh/Texture 机制不变