跳转至

渲染顺序与带宽预算

帧率下降但不一定是 Draw Call

之前遇到过一个情况:场景里的 Draw Call 不算多,但一开屏幕后处理,帧率就明显下降。后来用 Frame Debugger 看,真正的问题是多张中间 Render Texture 在反复读写,带宽先撑不住了。

所以看渲染性能时,不能只盯着 Draw Call。渲染阶段、分辨率和像素格式都要一起看。

一帧里 CPU 做了什么

渲染管线在 CPU 侧通常可以分成四步:

  1. 收集相机、光源、Renderer 和后处理参数。
  2. 做视锥剔除、层过滤、遮挡剔除和 LOD 选择。
  3. 按队列、材质和距离排序。
  4. 设置 Render Target,提交绘制命令。

很多所谓的“合并 Draw Call”,其实发生在第 2、3 步。如果可见物体本来就很多,单靠合批不会解决问题。

URP 的常见顺序

一个普通的 URP 相机会经历这些阶段:

Shadow Pass
  → Depth Prepass(可选)
  → Opaque
  → Skybox
  → Transparent
  → Post Processing
  → Final Blit

每个阶段都可能创建或读写 Render Texture。比如深度图、法线图、Bloom 的降采样链、TAA 的历史颜色,都要占显存和带宽。

自定义 Pass 的价值不只是“加一个全屏效果”,还要确认它放在哪个阶段、需要读什么、会写什么。放在 Opaque 前面还是后面,成本可能完全不同。

一张 Render Texture 有多贵

先按最简单的公式估算:

单张大小 = 宽 × 高 × 每像素字节数

以 1920×1080、RGBA16F 为例,每像素 8 字节:

1920 × 1080 × 8 = 16,588,800 字节
约等于 15.8 MiB

如果一个 Pass 读一次、写一次,大约是 31.6 MiB。按 60 FPS 算,每秒就是 1.85 GiB 左右的读写量。

如果开启 4× MSAA,这张 RT 的存储会变成约 63.2 MiB,读写带宽继续翻倍。实际 GPU 可能有压缩、Tile Memory 等优化,但量级不会差太多。

所以做后处理时,先看能不能降分辨率。Bloom、景深、屏幕空间反射这类效果,半分辨率往往已经够用。

透明物体和 Overdraw

透明物体通常不写深度,后面的像素仍然要执行片元着色。如果五层特效叠在同一个屏幕上,片元成本就接近五倍。

检查 Overdraw 时,可以临时把不透明物体关掉,只留特效,观察帧时间变化。也可以直接用 RenderDoc 抓一帧,看每个像素被着色了多少次。

减少 Overdraw 的常见办法:

  • 缩小粒子面片,不要用一整张大图做局部效果。
  • 能裁掉的部分在 Shader 里尽早 clip 或丢弃。
  • 透明排序保持稳定,避免无意义的大面积叠加。
  • 低画质档位直接关掉不重要的后期效果。

Render Graph 管什么

Unity 6 的 URP 使用 Render Graph 组织 Pass。每个 Pass 声明自己读写哪些纹理,Render Graph 负责资源生命周期、依赖顺序和部分临时 RT 复用。1

一个 Pass 的基本结构大致如下:

using (var builder = renderGraph.AddRasterRenderPass<PassData>(
           "Copy Color", out var passData))
{
    passData.source = resources.activeColorTexture;

    builder.UseTexture(passData.source);
    builder.SetRenderAttachment(destination, 0);
    builder.SetRenderFunc(static (PassData data, RasterGraphContext ctx) =>
    {
        // 在这里提交绘制或 Blit
    });
}

这里省略了 PassData、destination 的创建和具体绘制命令。不同 URP 版本的 API 有差异,实际写法以项目锁定版本的文档为准。

Render Graph 能减少手动管理 RT 的错误,但不能替你做取舍。比如一个效果该不该半分辨率、该不该每帧执行,仍然要自己决定。

排查顺序

  1. 用 Frame Debugger 看 Pass 顺序和每个 Pass 的绘制对象。
  2. 用 GPU Profiler 看时间花在 ALU、纹理采样、带宽还是同步上。
  3. 检查中间 RT 的分辨率、格式和数量。
  4. 检查透明物体的覆盖面积和排序。
  5. 最后再看 Shader 指令数和变体数量。

一个小习惯:改动渲染效果时,同时记录帧时间和显存占用。只看截图很容易忽略成本,等低端机测试时再回头改,通常要重新调整整个效果链。