1795 字
9 分钟
延迟渲染与前向+——渲染管线的设计取舍

一、前向渲染回顾#

你在 DX11 笔记里画三角形的流程是前向渲染(Forward Rendering)

对场景中每个物体:
对每个光源:
跑一次像素着色器(光照计算)
物体 A(100 个三角形)
→ VS → PS: 光源1 + 光源2 + 光源3 = 300 次 PS
物体 B(50 个三角形)
→ VS → PS: 光源1 + 光源2 + 光源3 = 150 次 PS

问题是:场景有 N 个物体、M 个光源时,PS 执行次数 = N × M。而且很多像素计算最终因为深度测试被覆盖了——白白算了。

你竞拍游戏的 3D 藏馆如果只有两三盏灯,前向渲染完全够用。但如果是开放世界夜市场景有上百盏灯笼,前向渲染就炸了。


二、延迟渲染:先存属性,后算光#

1 核心思路#

把渲染拆成两趟:

Pass 1(GBuffer Pass):渲染所有物体的几何属性到多张纹理
→ 不计算任何光照
→ 输出:GBuffer(一组 Render Target)
Pass 2(Lighting Pass):对屏幕上的每个像素,用 GBuffer 里的属性 + 所有光源,计算最终颜色
→ 这是一个全屏四边形(Fullscreen Quad)+ 一个 Pixel Shader

2 GBuffer 里存什么#

UE 的 GBuffer 布局(简化版):

GBuffer存储内容格式
ABaseColor + AORGBA8
BMetallic + Specular + RoughnessRGBA8
CWorld Normal(世界空间法线)RGBA16F(需要高精度)
D自定义数据(ClearCoat、Subsurface 等)RGBA8
Depth/Stencil深度缓冲D24S8

5 张纹理,每张和屏幕一样大。 GBuffer 的显存占用是 1920×1080 × 5 × 4 bytes ≈ 40MB。不高——但对移动端可能已经是预算瓶颈。

3 Lighting Pass 的优势#

每个像素只需要跑一次像素着色器(不是 N×M 次)。在这一次里,遍历所有光源:

float3 GBuffer_Decode(Texture2D GBufferA, Texture2D GBufferB, ...) {
float3 BaseColor = GBufferA.Sample(Sampler, UV).rgb;
float Metallic = GBufferB.Sample(Sampler, UV).r;
float Roughness = GBufferB.Sample(Sampler, UV).g;
float3 Normal = GBufferC.Sample(Sampler, UV).rgb;
float Depth = SceneDepth.Sample(Sampler, UV).r;
// 从 Depth 重建世界空间位置
float3 WorldPos = ReconstructWorldPos(UV, Depth);
return PBR_DirectLight(Normal, CameraDir, LightDir, ...);
}
// 对屏幕上的每个像素(全屏 Quad):
float3 color = GBuffer_Decode(...);
for (每个光源) {
color += CalcLight(GBufferData, Light);
}

关键收益:光照计算只发生在最终可见的像素上。被深度测试淘汰的像素(被挡住的物体),根本不会进 Lighting Pass。前向渲染可能为一个最终被挡住的物体跑了全套光照计算,都浪费了。


三、延迟渲染的代价#

1 半透明物体不兼容#

GBuffer 一个像素只能存一个面的属性。半透明物体需要混合多个面——不能只靠 GBuffer。UE 的解决:半透明走独立的前向渲染 Pass。

当一个画面同时有不透明和半透明物体时,引擎实际上在跑两套管线。

2 材质多样性受限#

因为 GBuffer 格式是固定的(BaseColor + Metallic + Roughness + Normal),无法为每种特殊材质(皮肤、头发、布料)定制额外的属性存储。Engine 的 Shading Model 枚举本质上就是”你选哪种解码 GBuffer 的方式”——UE 支持 DefaultLit、ClearCoat、Subsurface 等几种,但无法无限扩展。

3 MSAA 不兼容#

延迟渲染无法使用硬件 MSAA(因为 GBuffer 是多张纹理,硬件 MSAA 是按像素为单位工作的,不好对多张纹理同时做 resolve)。UE 统一用 TAA(时间抗锯齿)。

4 带宽#

1920×1080 的 GBuffer 写入(5 张纹理 × 每个像素 16-32 bytes ≈ 40-80MB 每帧)在高端 PC 上问题不大,但在移动端的 Tile-Based GPU 上——把这么多数据写出到显存再读回——是巨大的带宽浪费。这就是移动端普遍不用延迟渲染的原因。


四、前向+(Forward+):取长补短#

1 为什么要有 Forward+#

移动端没法接受 GBuffer 的带宽。能不能保留前向渲染的简洁性,同时解决”每个物体遍历所有光源”的问题?

Forward+ 的做法:在渲染前,先把屏幕切成 Tile(如 16×16 像素的格子),对每个 Tile 预计算”哪些光源影响这一 Tile”:

CPU/Compute Shader: 对每个 Tile:
→ 用 Tile 的深度范围构建一个子视锥体
→ 检查哪些光源的包围球和这个子视锥体相交
→ 输出:每个 Tile 的光源列表(Light Grid)
前向渲染:
→ 每个像素执行时,只遍历自己 Tile 的光源列表
→ 而不是全局光源列表

和前向渲染的对比

  • 传统 Forward:每个像素 × 全局光源列表(100 个光源全跑一遍)
  • Forward+:每个像素 × 本 Tile 光源列表(通常 5-10 个光源)

和延迟渲染的对比

  • Deferred:GBuffer(大带宽)+ 一个全屏 Lighting Pass
  • Forward+:无 GBuffer(低带宽)+ 每个像素只跑自己 Tile 的光

Forward+ 是移动端多光源场景的标配方案(因为避免了 GBuffer 的大带宽写入)。

2 UE 的 Forward Shading#

UE 支持 Forward Rendering 模式(在 Project Settings 中切换 Forward Shading)。在这个模式下:

  • 不存储完整 GBuffer
  • 支持 MSAA(移动端 VR 的硬需求)
  • 牺牲多光源支持,换取更低的显存带宽

五、Tile-Based GPU:硬件层面也分 Tile#

移动端 GPU 本身就是 Tile-Based 架构:

桌面端 Immediate Mode GPU:
→ 整个帧存在 VRAM,逐 Primitive 渲染
→ GBuffer 的 40-80MB 读写问题不大
移动端 Tile-Based GPU:
→ 屏幕被硬件分成 Tile(如 32×32 像素)
→ 每个 Tile 的数据在片上 SRAM 里处理,结束后才写回 VRAM
→ 片上 SRAM 非常快(TBDR 架构),但非常小(128-256KB)
→ 如果 GBuffer 是 5 张 × 32×32 × 4 bytes = 20KB per Tile,勉强能装下
→ 如果 GBuffer 更复杂(7-8 张),一个 Tile 就溢出了 → 必须 spill 到 VRAM → 性能暴跌

这就是为什么引擎要有两套管线:桌面端用 Deferred(接受 GBuffer 带宽),移动端用 Forward+(尊重 TBDR 架构)。不只是一套 Shader 代码换平台编译——是整套渲染管线的设计思路都不同。


六、三套管线的对比总结#

ForwardDeferred(UE 默认)Forward+(TBDR)
光照复杂度O(N×M)O(P×M)(P=像素数)O(P×M_tile)
半透明天然支持单独 Forward Pass天然支持
MSAA支持不支持(用 TAA)支持
显存带宽高(GBuffer 读写)中等
材质多样性任意 Shader受 GBuffer 格式限制任意 Shader
适用场景简单场景、VR复杂场景多光源(桌面端)移动端、VR
UE 中怎么选Project Settings → Forward Shading默认Project Settings → Forward Shading(移动平台)

七、总结#

概念一句话
前向渲染每个物体 × 每个光源——直接但光照多了就炸
GBuffer屏幕大小的多张纹理——存 BaseColor+Normal+Roughness+Metallic
延迟渲染先存属性、再算光——光照 O(像素×光源) 但牺牲了半透明和 MSAA
Forward+用 Tile 预筛选光源——保留前向的优点,接近延迟的光照效率
TBDR移动端硬件就是 Tile 架构——GBuffer 带宽是大忌
UE 的选择桌面端默认 Deferred,移动端/VR 推荐 Forward

选哪套管线不是宗教问题——是数学问题。GBuffer 的带宽省了光照计算的重复、MSAA 的兼容性被 GBuffer 吃掉、移动端的 TBDR 又反过来限制 GBuffer 的规模。理解了这些 trade-off,你才知道什么时候该切 Forward Shading。

延迟渲染与前向+——渲染管线的设计取舍
https://www.m4doka.xyz/posts/cg/cg-7-deferred-rendering/
作者
m4doka
发布于
2026-03-01
许可协议
CC BY-NC-SA 4.0