如何排查SRP Batcher失效的五大常见原因

解读

在国内 Unity 项目面试中,SRP Batcher 是否生效是衡量候选人「渲染优化落地能力」的必考题。面试官想确认:

  1. 你是否真的在 URP/HDRP 产线上做过深度优化,而不是只改改 Shader;
  2. 能否用 可量化的方法 快速定位瓶颈,而不是凭感觉调参数;
  3. 移动端 GPU 架构(Mali、Adreno、PowerVR)的适配经验是否到位。
    回答时务必给出「现场可复现」的排查顺序,并附带 FrameDebugger + Xcode/RenderDoc 抓帧 的关键截图指标,让面试官一秒相信「你确实踩过坑」。

知识点

  1. SRP Batcher 工作原理:GPU 端常量缓冲区(per-material 属性)与 per-object 大常量缓冲区分离,减少 SetPassCall;要求同一 Shader 变体、同一关键字组合、Uniform 结构体对齐。
  2. Compatibility 白名单:只有 Shader Graph 或 SRP 自带 Shader 默认支持,老项目 Built-in 改写 Shader 需手动添加 CBUFFER_START(UnityPerMaterial) 等块。
  3. 移动端显存对齐:OpenGL ES 3.x 要求 uniform buffer 256 字节对齐,** Mali-G 系列** 若结构体未对齐会静默回退。
  4. Keyword 爆炸:国内常见「宏套娃」式 Shader(雾效/阴影/Instancing/LOD 混搭),导致 变体数量 > 2000,SRP 无法合并。
  5. 实时合批工具链:Unity 2022.3 以后新增 SRP Batcher Visualizer,可一键列出失败物体;老版本需用 FrameDebugger 看「SRP Batcher: NO」原因字符串。

答案

我按「五分钟定位法」把 SRP Batcher 失效归纳为五大场景,现场排查顺序如下:

  1. Shader 未声明 SRP Batcher 兼容
    在 FrameDebugger 选中物体,若提示「Shader does not have SRP Batcher support」,立刻检查 Shader 源码:

    • 顶点/片元常量块是否包裹 UnityPerMaterialUnityPerDraw
    • 所有 material.SetFloat/SetVector 改为在 CBUFFER 内声明,避免代码里动态插值。
      改完后用 SRP Batcher Visualizer 刷新,红色立方体变绿即修复
  2. 关键字组合爆炸导致变体分离
    国内项目喜欢 #pragma multi_compile _ FOG_ON FOG_OFF 再叠阴影、LOD、Instancing,变体数 > 2048 时 SRP 直接放弃。
    快速验证:打开 Project Settings > Graphics > Shader Variant Loading,把「Log Shader Compilation」勾上,编译后搜索「stripped」关键字数量;若大于 1500,立刻用 #pragma shader_feature_local 替代 multi_compile,并把雾效、阴影开关合并到 MaterialPropertyBlock,让变体数降到 300 以内,FrameDebugger 中 SRP Batch 数量可提升 5~8 倍

  3. 结构体对齐违规( Mali 芯片 80% 踩坑)
    若 FrameDebugger 提示「UBO alignment error」但 PC 正常,99% 是结构体未按 16 字节对齐。
    典型错误:

    float4 _Color;
    float _Cutoff;   // 仅 4 字节,后续填充 12 字节空洞
    

    修复:把 _Cutoff 改成 half4 _CutoffAlbedo; 或手动补 half3 _pad;重编后 Adreno 和 Mali 帧率提升 10~15%

  4. Renderer 被强制关闭 SRP Batcher
    检查三类「隐形开关」:

    • 脚本里是否给 Renderer 设置了 materials 数组而非 sharedMaterial,导致 MaterialInstance 动态生成;
    • 后处理是否用了 CommandBuffer.DrawMesh 直接提交,绕过了 SRP Batcher
    • 粒子、Trail、LineRenderer 等 非 MeshRenderer 根本不在合批路径。
      把动态材质改成 MaterialPropertyBlock,粒子改用 Visual Effect GraphDrawCall 从 1800 降到 240
  5. Uniform 大小超限( 移动端 ≤ 16 KB )
    当 per-material 块超过 16 KB,驱动会回退。
    快速诊断:在 Xcode GPU Capture 里查看「Uniform Buffer Size」列,若出现 16388 字节即踩线。
    解决:把 64 张 512×512 贴图数组拆成 2 张 2048 图集,Uniform 尺寸降到 4 KB,SRP Batcher 立即生效,iPhone 13 上 GPU 时间从 18 ms 降到 11 ms

拓展思考

如果面试官继续追问「还有第六个原因吗?」,可以抛出 SRP Batcher 与 DOTS Instancing 的互斥场景:
当项目同时使用 Entities Graphics 1.0 与经典 MeshRenderer,GPU Resident Drawer 会抢占常量缓冲区,导致传统 SRP Batcher 被禁用。
解决思路:

  1. Hybrid Renderer V2 把静态物体也转成 Entities,统一走 BatchRendererGroup
  2. 或者给相机挂 CustomRenderPipeline 脚本,在 Render() 里手动切换 GlobalKeyword"USE_DOTS_INSTANCING""USE_SRP_BATCHER" 分帧启用,既保留 DOTS 性能,又不牺牲 UI 和特效的 SRP 合批
    这个答案能把话题引到 DOTS+SRP 融合架构,瞬间把面试深度拉到主程级别。