跳到主要内容
腾讯题库 · Q38

原题 Q38 · 当前收录 37 / 49

学习状态 未开始

Draw Call 合并

工具与管线 draw-call · batching 对应课程:资产与工具链

面试官问

PDF 第 81 页

什么是“Draw Call 合并”?在腾讯项目中,你如何通过材质合并、图集打包减少 Draw Call?(必问题,理清逻辑)

学习进度

0/9 已勾

只保存在这台浏览器;勾满 9 项即视为本题完成。

先把题目说成人话

舞台上有一百棵相同的树,画面未必因为树多而慢;真正可能浪费时间的是指挥每棵树时,都要重新递一张“怎样画”的单子。处理器交给图形处理器的一次绘制指令叫绘制调用(Draw Call);形状、材质或状态不一致时,它们不能轻易共用一次调用。减少调用要先找不能同批的原因,再比较整帧是否受益,而不是把一切焊死。

这是虚构教学故事,不是个人项目经历。

场景:先让问题变得看得见

城市街区里有大量路灯、路牌和建筑小件。目标版本中,真正画像素的一端还有余量,负责反复填写和递交“请画这个物体”指令的一端却很忙。逐步回放一帧后能看到:许多东西看上去几乎相同,却因为贴图、画法或开关有一点差异,被拆成一小份一小份送出。

这个教学场景只讨论通用 Unity 方法,不代表任何腾讯项目的内部方案或数据。现在先看清“为什么不能同车”,再给每一类对象选择办法。

(右脑)画面: 仓库里摆着一百盏一模一样的路灯。工人原本每抱起一盏灯,就单独叫一辆车送往街道;车在门口来回排队,路灯本身却没有变。后来工人把同样的路灯一起装车,只在清单上写清每盏灯要立在哪里、朝向哪边。街道仍是那条街道,仓库门口来回跑的车却少了。

(左脑)对表: 先把每件货对应到渲染器(Renderer)和网格(Mesh),把包装规则对应到材质、着色器变体、渲染步骤与状态;这些键相容时,管线才有批处理机会。随车清单对应实例数据缓冲,保存每个实例的变换与已声明的差异。静态批处理(Static Batching)会生成额外的合并几何数据,仍可逐个渲染器剔除;手工合并网格(CombineMeshes)才把边界并成一个大渲染器。帧调试器证明为什么拆批,性能分析器证明主线程或渲染线程是否真的变快;绘制数、顶点、内存字节和毫秒不能互相代替。

小明:先形成一个完整猜测

小明先打开 SRP Batcher,看到 CPU 端状态设置成本下降,便宣布“Draw Call 已经合并”。可是 Frame Debugger 中绘制事件数量几乎没变。

他的操作仍然有效,只是结论说错了。SRP Batcher 让兼容 Shader Variant 的常量数据组织得更适合连续提交,重点是降低 CPU 切换与设置成本;它不承诺把多个 Renderer 变成一个 Draw Call,也不会减少 GPU 必须处理的顶点。

小红:把猜测放回现实检查

小红先按拆批原因分组,再逐项验证:

  1. 对同 Mesh、同材质的大量路灯启用 GPU Instancing。Frame Debugger 出现实例化绘制,实例 Transform 通过实例数据传入。
  2. 对只能因为纹理不同而拆开的静态小件,制作带安全边距和一致采样规则的图集,并合并材质。绘制数下降,但要重新检查 mip 串色、颜色空间与压缩格式。
  3. 对真正静止且满足平台批处理限制的几何评估 Unity Static Batching。它会增加合并几何的内存与构建存储开销,参与对象不能再独立移动,但仍保留原 GameObject、Renderer 和逐对象剔除。若另行用 CombineMeshes 把整条街合成一个 Renderer,才会让一小部分可见时整块都难以剔除;这不是 Static Batching 本身的性质。

每一步都保留同一相机路径,分别记录 Batches、SetPass Calls、主线程、Render Thread 与 GPU 时间。只有 CPU 提交时间随改动稳定下降,才说明击中了这次瓶颈。

我:用检查结果修正模型

我把方案按对象特征分开:重复 Mesh 用实例化;纹理差异是主要障碍且资产适合共用采样规则时才上图集;兼容且不会移动的对象才进入 Static Batching,并继续保留逐对象剔除;只有能接受一个合并 Renderer 的材质约束与更粗剔除时,才单独评估手工 CombineMeshes。频繁移动或需要独立材质的对象保持分开。

材质合并也不是把所有参数抹成一样。采用 GPU Instancing 时,已声明为实例属性的颜色、缩放等少量差异可以经 MaterialPropertyBlock 或实例 Buffer 传入;但 MaterialPropertyBlock 会让对应 Renderer 失去 SRP Batcher 兼容,不能把同一做法无条件套给两条路径。会改变 Pass、渲染状态、Shader Variant、光照贴图或 Render Queue 的差异仍可能拆批。Static Batching 后重点检查额外合并几何内存、存储、不可独立移动和平台限制;CombineMeshes 后才额外检查合并 Renderer 的剔除粒度。两者都要复测加载时间和 GPU 顶点工作,避免 CPU 省了一点,其他系统却付出更大代价。

(右脑)画面修正: 小明先让工人少填几张重复表格,每辆车出门更利落了,出门的车数却几乎没变。小红这才逐一检查货物:完全相同的路灯可以共车,只差表面图案的箱子有时可以共用一张包装纸,永远不动的街边设施则可以提前整理。她也试过把许多东西焊成一个大件,结果镜头只看见一角时,整件货都得被送来。最后留下的不是一种万能装车法,而是几种各自适合不同货物的办法。

(左脑)严格对表: 先用目标构建确认瓶颈在 CPU 提交;再逐个 Draw 记录拆批键:Pass、Variant、材质/纹理、渲染状态、lightmap 与队列。相同 Mesh/材质的重复物体测试 Instancing,仅纹理不同且采样约束一致的资产测试图集;兼容且静止的对象测试 Static Batching,并核对 GameObject/Renderer 与逐对象剔除仍在,同时记录合并几何的内存/存储、移动约束和平台限制;手工 CombineMeshes 作为另一候选,单独验合并 Renderer 的剔除粒度。各方案都以同一相机路径比较 Batches、SetPass、Render Thread、GPU 时间、顶点量、内存和可见性,只有整帧与画面验收同时通过才保留。

老师解惑

  • Batch/Draw 与 SetPass 不是同一个数:前者是绘制提交,后者更接近材质与 Shader Pass 状态切换。
  • SRP Batcher:主要减少兼容绘制之间的 CPU 状态设置成本,不等于自动减少 Draw Call。
  • GPU Instancing:复用 Mesh 与材质、批量提交实例数据;它降低提交成本,不会消灭每个可见实例的顶点和像素工作。
  • 图集与材质合并:能消除纹理或材质造成的拆批,但会引入边距、mip、压缩、更新与资产管理成本。

工具分工

  • Unity Profiler:判断瓶颈是否真的在主线程或 Render Thread,并比较合批前后 CPU/GPU 时间。
  • Frame Debugger:查看绘制为何被材质、Pass、关键字或渲染状态拆开。
  • Rendering Debugger 与 Stats:辅助查看 SRP Batcher 兼容性、Batches 和 SetPass;不能代替时间测量。
  • instancing-batcher Lab:观察提交次数、实例数据、顶点量和剔除粒度之间的区别。

记忆钩子

先找拆批原因,再给重复物体实例化、给合适资产做图集;SRP Batcher 省状态设置,不保证减少 Draw Call。

这是教学场景,不是目标项目的真实捕获或个人经历。

下一步:验证这条因果

同一批可见实例在普通提交与 GPU Instancing 下,CPU 命令如何变化,而顶点工作为何不自动减少。

教学边界:它不模拟真实 Unity 驱动、Shader/Pass、剔除、LOD 或目标设备时序,也不替代图集的真实画质与内存验收。

进入教学 Lab:「Instancing:少发命令,树并没有少画」