news 2026/10/9 2:56:42

SRP Batcher:让CPU少为绘制“重复准备”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SRP Batcher:让CPU少为绘制“重复准备”

SRP Batcher 主要优化的是:CPU 为连续绘制准备和绑定 Shader 数据的开销。

它通常不会减少 Draw Call 数量,也不会让 GPU 少画几个三角形。

先记住这组对比:

GPU Instancing: 多个实例尽可能放进一次绘制。 SRP Batcher: 绘制次数可以不变, 但让 CPU 更便宜地组织这些绘制。

下面拆开讲。


一、一个 Draw Call,成本不只是“调用 Draw”

假设场景里有三块石头:

石头 A:红色材质 石头 B:绿色材质 石头 C:蓝色材质

它们使用同一个 Shader Variant,只是材质参数不同。

CPU 在提交绘制之前,通常需要准备:

使用哪个 Shader? 使用哪个 Shader Variant? 绑定哪些纹理? 材质颜色、粗糙度等参数是什么? 物体的变换矩阵是什么? 使用哪个网格?

然后才是:

Draw

可以用一个简化公式表示:

[
T_{\text{CPU渲染}}

T_{\text{剔除与排序}}
+
T_{\text{状态和数据准备}}
+
T_{\text{绘制命令组织}}
]

SRP Batcher 重点优化中间的“状态和数据准备”,以及相关的绘制提交路径。

它不是把全部 CPU 渲染成本都消除。


二、传统路径的问题:很多时间花在“准备画”,而不是“画”

用概念流程表示:

石头 A: 整理材质数据 → 设置相关状态 → 绘制 石头 B: 整理材质数据 → 设置相关状态 → 绘制 石头 C: 整理材质数据 → 设置相关状态 → 绘制

底层本来就可能有各种缓存,并非每次都完全从零处理。

但随着物体和材质增多,CPU 仍可能花费大量时间在:

  • 收集、整理 Shader 参数;
  • 更新和绑定常量数据;
  • 处理不同材质的设置;
  • 重复经过较重的状态准备路径。

尤其是:

大量物体使用不同材质,但这些材质实际使用同一个 Shader Variant。

SRP Batcher 正是针对这类情况提供高效路径。


三、核心机制一:让材质数据持久保存在 GPU 缓冲中

考虑材质参数:

CBUFFER_START(UnityPerMaterial) float4 _BaseColor; float _Metallic; float _Smoothness; CBUFFER_END

这些参数属于材质:

石头 A 材质: 红色、金属度 0、光滑度 0.2 石头 B 材质: 绿色、金属度 0、光滑度 0.4

SRP Batcher 的一个关键思路是:

材质数据上传后,在 GPU 侧持久保存;材质未变化时,避免反复重新准备、上传同一份数据。

概念上:

GPU 材质数据区: 材质 A → 一份参数数据 材质 B → 一份参数数据 材质 C → 一份参数数据

绘制不同物体时,使用对应的数据,而不是每次都重新打包同样的材质参数。

注意两点:

  1. 材质参数改变后,相关数据仍然需要更新。
  2. “数据持久保存”不意味着不再绑定资源,或完全没有状态切换。

省掉的是大量重复工作,不是让设置成本变成零。


四、核心机制二:高效处理每个物体自己的数据

同一材质可以被多个物体使用,但每个物体的位置不同:

石头 A:位置 (0, 0, 0) 石头 B:位置 (5, 0, 0) 石头 C:位置 (10, 0, 0)

因此,不能把所有数据都当成材质数据。

常见分类是:

数据类别例子常见常量缓冲约定
每材质数据颜色、金属度、光滑度UnityPerMaterial
每物体数据变换矩阵、部分光照相关数据UnityPerDraw
每帧/相机等数据相机和环境相关参数由管线组织

SRP Batcher 同时提供了更高效的每物体数据组织和绑定路径。

但物体动了,矩阵仍然要更新;物体要画,绘制命令仍然存在。

它不是跳过必要工作,而是:

让这些工作按运行时易于批量处理的数据布局进行。


五、为什么名字叫 Batcher,却不减少 Draw Call?

因为这里的 Batch,不能直接理解成“一个绘制命令”。

例如:

一个 SRP Batch ├── Draw 石头 A ├── Draw 石头 B ├── Draw 石头 C └── Draw 石头 D

里面仍然可以有多个 Draw Call。

它表示的是:

一段可以通过 SRP Batcher 高效连续处理的绘制序列。

概念对比:

普通路径: 较重的准备 → Draw 较重的准备 → Draw 较重的准备 → Draw
SRP Batcher 路径: 复用兼容的准备状态 ├── 较轻量的数据绑定 → Draw ├── 较轻量的数据绑定 → Draw └── 较轻量的数据绑定 → Draw

所以看到:

开启前:1000 Draw Calls 开启后:1000 Draw Calls

不能说明 SRP Batcher 没生效。

要看 CPU 渲染相关耗时,而不只是绘制次数。


六、关键是 Shader Variant,不是“材质必须相同”

这是最容易混淆的地方。

情况一:材质不同,但 Variant 相同

材质 A:红色 材质 B:绿色 材质 C:蓝色 全部使用: 同一个 Shader 的同一个 Variant

这是 SRP Batcher 很适合优化的情况。

材质可以不同,网格也可以不同。


情况二:Shader 文件相同,但 Variant 不同

例如:

材质 A:开启 _NORMALMAP 材质 B:关闭 _NORMALMAP 材质 C:开启 _ALPHATEST_ON

虽然都来自同一个 Shader,但关键词组合不同,可能对应不同的 Shader Variant。

这会使连续绘制需要切换程序或相关状态,影响批处理连续性。

因此:

相同 Shader 文件 ≠ 相同 Shader Variant

不过,也不能认为“Variant 相同就一定进入同一个 SRP Batch”。实际分组还受 Pass、渲染顺序及其他条件影响。

优化方向是减少不必要的 Variant 和状态切换,而不是为了凑批次破坏正确的渲染顺序。


七、Shader 怎样才算兼容?

典型要求包括:

1. 材质标量和向量等常量放入UnityPerMaterial

CBUFFER_START(UnityPerMaterial) float4 _BaseColor; float4 _BaseMap_ST; float _Smoothness; CBUFFER_END

纹理和采样器不是这样塞进常量缓冲:

TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);

2. 引擎要求的每物体常量遵循UnityPerDraw布局

使用 URP 提供的 Shader 库时,相关声明通常由库处理,不要随意重复定义。

3. 多个 Pass 保持兼容的数据布局

例如,不能因为不同 Pass 或关键词,随意改变同一材质常量缓冲的布局。

最稳妥的方式是共享相应声明,并参考当前 URP/HDRP 版本的官方 Shader。

4. 注意MaterialPropertyBlock

在常见的 Unity SRP Batcher 路径中,使用MaterialPropertyBlock会使对应 Renderer 无法走 SRP Batcher 兼容路径。

因此:

“用 MPB 避免创建材质实例”与“维持 SRP Batcher”之间,可能需要权衡。

具体应以项目 Unity 版本和实际渲染路径验证。


八、它与 GPU Instancing 的区别

对比SRP Batcher传统 GPU Instancing
主要目的降低 CPU 状态和数据准备成本合并多个实例的绘制
是否通常减少 Draw Call不一定,通常不是核心收益是
网格是否必须相同不要求同一实例化绘制通常要求相同网格
材质是否必须相同不要求,但需满足兼容条件通常共享材质和绘制状态
差异数据如何处理材质和每物体数据路径实例数据
典型场景很多不同物体和材质,共用少量 Variant大量重复树木、石头、草等

同一个物体不能想当然地同时吃到两者全部收益。

Unity 会根据具体渲染路径和兼容条件选择处理方式;传统 Renderer 路径与较新的 GPU 驱动渲染路径也不能简单混为一谈。


九、什么时候效果明显?什么时候几乎没用?

更容易获益

物体数量多 材质数量多 Shader Variant 相对少 CPU 渲染准备成为瓶颈

例如:

一个城市有大量不同建筑和材质,但材质都基于少数几种 Shader Variant。

提升可能不明显

Draw Call 本来就少 大量时间消耗在其他 CPU 系统 GPU 已经成为主要瓶颈 大量绘制不兼容 SRP Batcher Variant 和状态频繁切换

例如,满屏透明粒子导致 GPU Overdraw 极高:

CPU 提交更快了 但 GPU 仍然要反复计算和混合大量像素

帧率可能变化不大。

SRP Batcher 不直接减少阴影采样、片元计算、透明叠加或纹理带宽。


十、怎么确认它真的有用?

建议验证三个问题:

1. Shader 是否兼容?

检查 Shader Inspector 中的 SRP Batcher 兼容信息。具体界面随版本不同。

2. 实际绘制是否走了对应路径?

使用 Frame Debugger 查看:

  • SRP Batch 分组;
  • 使用的 Shader 和 Variant;
  • 绘制之间为什么发生分组变化。

不要只看项目里“已开启”这个选项。

3. CPU 时间是否下降?

在相同场景、相同设置下对比开关:

  • 主线程相关渲染耗时;
  • 渲染线程耗时;
  • GPU 耗时;
  • 最终帧时间。

最好在目标设备的 Player 中测试,并排除 Shader 首次使用、资源加载等干扰。


最后一句话

SRP Batcher 不是“让 GPU 少画”,而是“让 CPU 少为每次绘制重复准备”。

最典型的结果是:

物体数量不变 Draw Call 数量基本不变 画面不变 GPU 工作量大致不变 但 CPU 渲染提交更便宜了。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 2:56:29

ntko控件实战:浏览器内Word编辑、盖章与留痕开发指南

简介:这份资源围绕NTKO Office文档控件的使用展开,面向需要在Web应用中实现在线文档编辑与处理的开发者,尤其适合企业级文档管理系统的搭建者。内容涵盖控件接口参考、JavaScript编程指南、技术白皮书及多版本函数功能列表,帮助读…

作者头像 李华
网站建设 2026/10/9 2:53:46

更适合科研学术党体质的神仙论文写作 SKILL 来了!

各位同仁好,我是七哥。一个在高校里从事人工智能 相关领域研究,钻研用大模型AI实操的学术人。可以和七哥交流学术写作或Gemini、GPT、Claude 等大模型 学术实操相关问题,多多交流,相互成就,共同进步。 科研过程中,很少有人能一路顺风。很多令人头疼的情况几乎每位研究…

作者头像 李华
网站建设 2026/10/9 2:53:11

进程同步与互斥:竞态条件、锁与信号量实战

1. 先从一道“修不好”的Bug说起:竞态的真实模样做服务端开发这些年,我最怕的不是业务逻辑复杂,而是遇到那种“偶尔出错、一上线就出问题、本地怎么都复现不了”的并发Bug。这类Bug十有八九都出在进程同步与互斥没有做好。说得直白一点&#…

作者头像 李华