它是什么
Unity 内置的渲染调试器。它把一帧的渲染过程暂停并拆成一步一步,你可以逐个 Draw Call 往下点,看每一步往屏幕上画了什么。
Window → Analysis → Frame Debugger → 点 Enable点下去之后游戏暂停,Game 视图变成"画到第 N 步为止"的状态。左边是这一帧所有渲染事件的列表,点哪一步,Game 视图就显示到那一步。
它最大的价值不是"看好看",而是回答这三个问题:
① 这一帧到底画了多少东西?顺序是什么? ② 这两个物体为什么没合批? ③ 这个东西为什么没显示 / 显示错了?一、界面怎么读
┌─ 左侧:渲染事件树 ──────────┬─ 右侧:选中事件的详情 ─────────┐ │ │ │ │ ▼ UniversalRenderPipeline │ Shader: Universal Forward │ │ ▼ MainLightShadow │ Pass: ShadowCaster │ │ Draw Mesh (x18) │ │ │ ▼ RenderLoop.Draw │ ┌──────────────────┐ │ │ ▼ RenderOpaques │ │ 这一步的输出预览 │ │ │ Draw Mesh Ground │ └──────────────────┘ │ │ Draw Mesh (x24) Box │ │ │ Draw Dynamic │ Keywords / 纹理 / 属性值 │ │ ▼ RenderTransparents │ │ │ Draw Mesh Glass │ ⚠️ Why can't batch with │ │ ▼ PostProcess │ previous: ... │ │ Draw Procedural │ │ │ ▼ RenderUI │ │ │ Draw Mesh Canvas │ │ │ │ │ │ [◄][ 滑块 ] 42 / 180 │ │ └─────────────────────────────┴────────────────────────────────┘要关注的几处
| 位置 | 看什么 |
|---|---|
底部计数42 / 180 | 这一帧总共 180 个渲染事件。数字大得异常就要查 |
事件名后的(x24) | 这一步合批了 24 个物体。数字越大越好 |
| 事件树的分组 | 阴影 Pass / 不透明 / 半透明 / 后处理 / UI 各占多少 |
| 右侧的 batch 原因 | 为什么没能和上一个合批,最常用的信息 |
| RenderTarget 下拉 | 切换查看 Color / Depth / 各个 RT |
| 通道按钮 RGBA | 单独看某个通道,查法线、深度、Mask 很有用 |
二、最主要的用途:查合批失败
点中一个 Draw Call,右侧底部会直接写明原因:
Why this draw call can't be batched with the previous one: Objects have different materials.这是整个工具最省时间的功能——不用猜。
常见原因与对应处理
| 提示 | 实际原因 | 怎么改 |
|---|---|---|
Objects have different materials | 材质不同(非 SRP Batcher) | 合并材质 / 用图集 / 确认 SRP Batcher 开启 |
Objects are affected by different lights | 受不同光源影响 | 减少实时点光,改用烘焙或 Light Probe |
Objects have different lightmaps | Lightmap 不同张 | 调整 Lightmap 分辨率让它们落到同一张 |
Objects have different reflection probes | 反射探针不同 | 合并探针范围 |
Shadow casting mode is different | 阴影投射设置不一致 | 统一 Renderer 的 Cast Shadows |
Shader has instancing disabled | Shader 没开 Instancing | Shader 里加#pragma multi_compile_instancing |
Objects have different shader keywords | Shader 变体不同 | 统一材质上的 keyword 开关 |
Non-instanced properties set for instanced shader | 用了 SetColor 等直接改材质 | 改用 Instanced 属性或顶点色 |
Instancing is disabled on the material | 材质没勾 Enable Instancing | 勾上 |
The node has different RenderTarget | 中间切了 RT | 检查是否有多余的后处理/RT 切换 |
SRP Batcher 的情况不一样
用 URP/HDRP 时,SRP Batcher 的合批显示方式是:
Draw Mesh (SRP Batch) ← 看到这个说明进 SRP Batcher 了SRP Batcher 不要求材质相同,只要求 Shader 变体相同。所以看到 “different materials” 的提示但你用的是 URP,先确认:
Project Settings → Graphics → 当前 URP Asset → Advanced → ☑ SRP Batcher打断 SRP Batcher 的几个典型写法:
// ❌ 运行时直接改 material,会创建材质实例renderer.material.color=Color.red;// ❌ MaterialPropertyBlock 和 SRP Batcher 冲突renderer.SetPropertyBlock(block);// ✅ 改 sharedMaterial 的属性(但会影响所有用这个材质的物体)// ✅ 更好:把变化数据放顶点色 / UV2 / Instance 属性三、用来查"为什么没显示"
物体在 Scene 视图有,Game 视图没有——这是 Frame Debugger 第二大用途。
排查路径
① 在事件列表里搜这个物体的名字 找不到 → 根本没提交 Draw Call → 剔除问题(视锥 / 距离 / Layer / Occlusion) → 或者 Renderer.enabled = false ② 找到了,但点上去 Game 视图没看到变化 → 画了,但被挡住 / 在屏幕外 / Alpha 为 0 / 尺寸为 0 ③ 找到了,画出来是黑的/紫的 → 紫色 = Shader 编译失败或丢失 → 黑色 = 贴图没赋值,或光照为零 ④ 画出来了,但下一步被覆盖 → 顺序问题,检查 RenderQueue顺序问题怎么看
这是 Frame Debugger 独有的能力:逐步播放。
拖动底部滑块,一步步往后走: 第 30 步:角色画出来了 ✅ 第 31 步:角色不见了 ← 问题就在第 31 步 点开第 31 步 → 发现是一个全屏的半透明 Quad → RenderQueue 设错了,盖住了角色RenderQueue 参考值: 1000 Background 天空盒之前的背景 2000 Geometry 不透明物体(默认) 2450 AlphaTest Alpha 裁剪(植被) 3000 Transparent 半透明 4000 Overlay 最后画的(镜头光晕、全屏效果)四、查 Overdraw 和多余的 Pass
看阴影 Pass 的开销
展开MainLightShadow分支:
▼ MainLightShadow Draw Mesh (x120) ← 120 个物体在投阴影 Draw Mesh (x80)阴影 Pass 等于把场景又画了一遍(只写深度,但顶点处理照跑)。如果这里的物体数接近主 Pass,说明阴影是实打实的双倍成本。
优化方向: · 小物件关掉 Cast Shadows · 缩短 Shadow Distance · 减少 Cascade 数量(每个 Cascade 都是一遍) · 远处物体用烘焙阴影 / 假阴影贴片展开后能看到级联数量:
▼ MainLightShadow ▼ Cascade 0 ← 4 级联 = 场景被画 4 遍 ▼ Cascade 1 ▼ Cascade 2 ▼ Cascade 3中低端机建议 1~2 级联。
看后处理和 RT 切换
▼ PostProcessPass Blit ← 每个 Blit 都是一次全屏读写 Draw Procedural Bloom Blit Draw Procedural Bloom Blit Draw Procedural UberPost Blit移动端每一次全屏 Blit 都要把整帧从主内存读出写回,带宽开销很大。看到一长串 Blit 就要考虑:
· 能不能合并到一个 Pass(Bloom + Tonemap + Vignette 合在 UberPost) · 低端机档位直接关后处理 · Bloom 的 downsample 层数能不能减五、查贴图和参数是否对
选中一个 Draw Call,右侧会列出这次绘制实际用到的所有资源:
Textures _BaseMap character_albedo (1024x1024 ASTC_6x6) _BumpMap character_normal (1024x1024 ASTC_5x5) _MainLightShadowmapTexture (2048x2048) Floats _Smoothness 0.5 _Cutoff 0.5 Vectors _BaseColor (1.0, 1.0, 1.0, 1.0) Keywords _NORMALMAP _MAIN_LIGHT_SHADOWS_CASCADE _ADDITIONAL_LIGHTS能查出来的问题:
· 贴图尺寸比预期大(美术给了 4K,以为是 1K) · 格式不是压缩格式(显示 RGBA32) · 用错了贴图(名字不对) · Keyword 比预期多(说明变体更复杂,影响合批) · 参数值不对(_Cutoff 被改成了 0,导致全透明)Keywords 这一栏对排查变体问题特别有用。两个看起来一样的物体不合批,经常就是 keyword 差一个。
六、连真机调试
编辑器里的数据参考价值有限,真机才是真相。
① Build Settings 里勾上: ☑ Development Build ☑ Autoconnect Profiler ② 打包装到手机,保持同一局域网(或 USB 连接) ③ Frame Debugger 窗口顶部的下拉框 从 "Editor" 切到你的设备 例:AndroidPlayer(SM-G998B@192.168.1.23) ④ 点 Enable⚠️ 注意事项 · Development Build 本身有额外开销,耗时数据偏高 → 用它看「画了什么、合批情况」,不要用它下性能结论 · 真机上 Frame Debugger 可能不稳定,断连重连是常事 · Android 建议用 USB + adb forward 更稳: adb forward tcp:34999 localabstract:Unity-<包名>真机和编辑器会不一样的地方
编辑器 真机 ───────────────────────────────────────────── Shader 变体全量编译 只有打包进去的变体 → 真机可能丢变体变紫 Scene 视图的额外绘制 没有 Editor Only 的 Gizmo 没有 质量档位可能不同 按实际档位走 纹理用的是 Editor 平台设置 用目标平台的压缩格式所以"编辑器正常、真机显示不对"的问题,必须连真机的 Frame Debugger 查。最常见的是 Shader 变体被剥离导致材质异常。
七、常见排查场景
| 现象 | Frame Debugger 怎么查 |
|---|---|
| Draw Call 远超预期 | 看哪个分组事件最多,是阴影、UI 还是半透明 |
| 两个相同物体不合批 | 点第二个,读 “Why can’t batch” |
| 物体不显示 | 搜名字,看有没有 Draw Call |
| 物体显示紫色 | 看 Shader 栏,大概率变体丢失 |
| 物体被莫名覆盖 | 逐步播放,定位到哪一步盖住的 |
| 半透明排序错乱 | 看 RenderTransparents 里的绘制顺序 |
| UI 比预期慢 | 展开 RenderUI,看 Canvas 被拆成几个批次 |
| 不知道后处理花了多少 | 数 PostProcess 分支下的 Blit 数量 |
| 阴影很贵 | 看 MainLightShadow 下的物体数和 Cascade 数 |
| 怀疑贴图太大 | 点任意 Draw Call,看 Textures 栏的实际尺寸 |
八、UI 相关的特殊看法
UGUI 的批次在 Frame Debugger 里很直观:
▼ RenderUI Draw Mesh (x12) Canvas ← 12 个 UI 元素合成一批,好 Draw Mesh (x1) Canvas ← 单独一批,被打断了 Draw Mesh (x8) Canvas Draw Mesh (x1) Canvas出现大量(x1)说明批次被反复打断。常见原因:
· 图片来自不同图集 → 整理图集 · 中间夹了一个 Text(字体是另一张贴图)→ 调整层级顺序 · Mask / RectMask2D → Mask 会强制打断批次 · 元素的 Z 轴不为 0 → 全部归零 · Canvas 里混了不同材质的元素 → 按材质分组排层级优化思路:让同图集的元素在层级上连续 把 Text 集中放在一起 少用 Mask,改用图片本身做形状九、局限与替代工具
Frame Debugger 能看"画了什么",但看不到每一步花了多少时间。
Frame Debugger 能做 不能做 ────────────────────────────────────────────────────── 看渲染事件顺序和数量 ✅ 看每个 Draw Call 的耗时 ❌ 看合批失败原因 ✅ 看 GPU 各阶段占比 ❌ 看每步的贴图和参数 ✅ 看带宽消耗 ❌ 逐步回放定位覆盖问题 ✅ 看 Shader 指令数 ❌需要耗时数据就换工具:
| 需求 | 工具 |
|---|---|
| GPU 各阶段耗时、带宽、tile 统计 | Android GPU Inspector / Snapdragon Profiler |
| iOS 的 GPU 帧分析、Shader 耗时 | Xcode GPU Frame Capture |
| 帧率/温度/功耗长期曲线 | Perfdog |
| CPU 侧耗时分布 | Unity Profiler |
| Shader 编译后的指令数 | Shader Inspector 的 Compile and show code |
实践顺序通常是:
Unity Profiler 定性(CPU bound 还是 GPU bound) ↓ Frame Debugger 看结构(画了什么、合批如何、有无多余 Pass) ↓ AGI / Xcode 看耗时(到底哪一步贵)要点
1. Frame Debugger 回答「画了什么」,不回答「花了多久」 2. 最高频用途是读 "Why can't batch with previous" —— 不用猜原因 3. 底部计数和事件名后的 (xN) 是两个最快的健康指标 4. 逐步拖滑块能定位「被什么盖住了」,这是它独有的能力 5. 展开阴影 Pass 看物体数和 Cascade 数,阴影经常是双倍成本 6. 数 PostProcess 下的 Blit 次数,移动端每次都是全屏带宽 7. 真机行为和编辑器不同,显示异常必须连真机查(尤其变体丢失) 8. 耗时数据要靠 AGI / Xcode / Perfdog,别指望 Frame Debugger