Base-前向渲染与延迟渲染
渲染管线中,光照计算阶段的组织方式决定了渲染器的整体架构。前向渲染(Forward)和延迟渲染(Deferred)是两种最主流的方案,它们各自衍生出一系列变体。
本文梳理它们的核心思路、演进分支、以及在不同场景下的取舍。
1. Forward Rendering(前向渲染)
1.1 基本原理
前向渲染是最直观的渲染方式:遍历每个物体 → 对该物体计算所有影响它的光源 → 得到最终颜色 → 写入 Framebuffer。
伪代码逻辑:
1 | |
每渲染一个物体,就要把所有光源照一遍。场景中 N 个物体 × M 个光源 = O(N × M) 的复杂度。
1.2 优点
- 实现简单直观
- 透明渲染原生支持 — 一个 Pass 完成所有计算,Alpha Blend 直接工作
- MSAA 友好 — 逐样本计算的颜色在光栅化阶段自然抗锯齿
- 带宽低 — 只需要当前物体的数据,不需要读写 G-Buffer
- 硬件兼容性好 — 不需要 MRT 等高级特性
1.3 缺点
- 光源数量受限 — 每物体需要对所有光源逐一计算,性能随光源数线性增长
- 无效计算多 — 即便物体只受极少数光源影响,也可能要遍历全部光源(虽然有 per-object light culling 优化,但效率有限)
- 复杂度 O(N × M) — N(物体数)和 M(光源数)任何一个增长都会带来性能悬崖
1.4 传统 Forward 的优化思路
为了避免 O(N × M) 的失控,传统做法是限制光源数:
- 严格限制每物体影响光源数量(通常 4~8 个)
- 超出部分通过低精度的 Lightmap / SH(球谐光照)作为间接补充
- 依赖引擎的 per-object light culling(如 Unity 在前向渲染中按物体裁减光源列表)
但这些优化只能延迟瓶颈,不能从根本上突破 O(N × M) 的约束。
2. Forward+ Rendering
Forward+(Tiled Forward Rendering)是对传统 Forward 的重要改进,将复杂度从 O(N × M) 降为 O(N + M × TileCount)。
2.1 核心思想
把屏幕划分为多个 Tile(网格块,通常 16×16 或 32×32 像素),在渲染前先用 Compute Shader 计算每个 Tile 内实际影响该区域的光源列表。渲染物体时,Shader 通过屏幕坐标查询当前像素所在 Tile 的光源列表,只计算列表中的光源。
1 | |
2.2 关键改进
- 共享了光源裁减的结果 — 同一 Tile 内的所有像素共享同一个光源列表
- 不再需要 per-object light culling,计算量更稳定
- 光源数量可以大幅提升(数十到上百个),只要在同一 Tile 内的光源数可控
2.3 缺点
- Z 轴浪费 — Tile 是 2D 划分的,不区分深度。一个 Tile 中可能同时包含近处和极远处的光源,它们在深度方向上互不影响,但 2D Tile 无法区分,导致光源列表过重
- 仍需逐物体渲染 — 和传统 Forward 一样,物体数多时仍然有 CPU 提交压力
- 透明物体仍需处理
3. Clustered Forward+(Clustered Forward Rendering)
3.1 核心思想
在 Forward+ 的基础上再加一个维度:将视锥体在 Z 方向(深度方向)也分层,形成 3D 空间中的 Cluster(簇)。每个物体/像素通过屏幕坐标 + 深度值确定自己属于哪个 Cluster,只查询该 Cluster 内的光源。
1 | |
Cluster 划分示意:
1 | |
Z 轴分层通常不是均匀划分的,而是按 log-depth(对数深度) 或指数方式划分,使近处的 Cluster 更密集(因为近处的光源对视差更敏感),远处的 Cluster 更稀疏。
3.2 解决了什么问题
- 消除了 Forward+ 的 Z 轴浪费 — 不再把远处的光源和近处的放在同一个列表里
- 同样场景下,每个像素要计算的光源数更少
- 支持的光源上限进一步上升(可达数百个动态光源)
3.3 代价
- 需要更多的 GPU 内存来存储 3D Cluster 的光源索引
- Compute Shader 预处理逻辑更复杂
- 仍然是逐物体渲染,不减少 DrawCall 数
4. Deferred Rendering(延迟渲染)
4.1 基本原理
延迟渲染的核心思路是把光照计算推迟到所有物体都画完以后,分为两个 Pass:
Pass 1(Geometry Pass / G-Buffer Pass):将场景的几何信息逐像素写入多个 Render Target(MRT)。常见 G-Buffer 包含:
- 法线、Albedo(漫反射颜色)
- 金属度、粗糙度/光泽度
- 深度
- 运动向量、Subsurface Mask 等(按需求扩展)
Pass 2(Lighting Pass / Shading Pass):读取 G-Buffer,对每个像素计算所有影响它的光照。
1 | |
复杂度:O(N × M’) — N 是物体数(仅 Pass 1),M’ 是实际参与计算的光源 × 受影响的像素。物体数和光源数解耦。
4.2 优点
- 光源数不受限 — 因为光照 Pass 是在屏幕空间逐像素做的,渲染 N 个光源的代价 ≈ 每光源一个全屏或局部光照 Pass
- 物体数与光源数解耦 — Pass 1 只关心有多少物体要画,Pass 2 只关心有多少光源要算
- 过度绘制少 — 每个像素只计算一次光照,不会有 Forward 中同一像素被多个半透明物体覆盖时多次计算的问题
4.3 缺点
- G-Buffer 带宽大 — MRT 写 4~6 张 RT,带宽和内存消耗远高于 Forward
- 不支持 MSAA — G-Buffer 存的是逐像素信息,MSAA 的逐样本处理在延迟渲染中非常棘手(虽然可以结合 MSAA 做,但实现复杂且效果差)
- 透明渲染不原生支持 — 透明物体无法在 G-Buffer 中表达(需要额外的 Forward Pass 兜底)
- 硬件要求高 — 需要 MRT(Multiple Render Targets)、较高显存带宽
- 难以处理 Subsurface、Clear Coat 等复杂材质 — G-Buffer 只能存有限的信息,复杂材质需要额外的编码/压缩技巧
5. Deferred 分类与变体
5.1 Classic Deferred(标准延迟渲染 / Deferred Shading)
- 上述标准的两 Pass 架构
- G-Buffer 中存储所有着色需要的信息(法线、Albedo、材质参数)
- Lighting Pass 一次性计算出最终颜色
- 最早出现在《杀戮地带 2》的 Mark 论文中
- 代表:UE4 Deferred、Unity URP/HDRP Deferred
5.2 Deferred Lighting(Light Pre-Pass / 延迟光照)
也称为 Deferred Lighting 或 Light Pre-Pass,将 G-Buffer 拆成了两个阶段:
- Pass 1(Light Pre-Pass):只写深度和法线
- Pass 2(Light Accumulation Pass):用深度+法线计算光照,输出光照缓冲
- Pass 3(Material Pass):读光照缓冲 + 材质属性计算最终颜色
优点:G-Buffer 更小(只需要深度+法线),带宽节省约 50%。材质属性在最后 Pass 才读取,可以支持更多的材质种类,不受固定 G-Buffer 编码限制。
缺点:需要三个 Pass,Light Accumulation Pass 和 Material Pass 之间需要额外的上下文切换。
5.3 Tile-based Deferred(TBDR)
结合 Forward+ 思路的延迟渲染:
- 将屏幕分为 Tile
- 在 Lighting Pass 前,用 Compute Shader 逐 Tile 裁减光源
- Lighting Pass 按 Tile 调度,只处理影响当前 Tile 的光源
适合移动端 GPU(如 PowerVR TBDR),利用 Tile-based 架构的 Local Memory 避免多次带宽消耗。
5.4 Clustered Deferred
类似 Clustered Forward+ 的思路应用于 Deferred:
- 在 Z 方向分层 + 屏幕 Tile,构成 3D Cluster
- 每个 Cluster 有独立的光源列表
- Lighting Pass 逐像素查询所在 Cluster 的光源
5.5 变体对比总览
| 变体 | G-Buffer Passes | 光源 Culling | 带宽 | 透明支持 | MSAA |
|---|---|---|---|---|---|
| Classic Deferred | 1 | 无 | 高 | 需 Forward 兜底 | 差 |
| Deferred Lighting | 2(法线+光照+材质) | 无 | 中 | 需 Forward 兜底 | 差 |
| Tile Deferred | 1 | 2D Tile | 中 | 需 Forward 兜底 | 差 |
| Clustered Deferred | 1 | 3D Cluster | 中 | 需 Forward 兜底 | 差 |
6. 整体对比:Forward vs Deferred
6.1 核心维度
| 维度 | Forward | Forward+ | Clustered Forward+ | Deferred |
|---|---|---|---|---|
| 光照计算时机 | 物体绘制时 | 物体绘制时 | 物体绘制时 | 物体绘制后 |
| 复杂度 | O(N × M) | O(N + M × Tile) | O(N + M × Cluster) | O(N + M × Pixel) |
| 光源上限 | 受限(~4-8/物体) | 中等(~数十) | 高(~数百) | 几乎无限 |
| DrawCall 数 | 高(逐物体) | 高(逐物体) | 高(逐物体) | 低(逐光源 Pass) |
| 物体数上限 | 视 CPU 开销 | 视 CPU 开销 | 视 CPU 开销 | 仅受 G-Buffer 带宽 |
6.2 功能维度
| 维度 | Forward | Forward+ / Cluster Forward+ | Deferred |
|---|---|---|---|
| MSAA | ✅ 原生支持 | ✅ 原生支持 | ❌ 复杂 / 不支持 |
| 透明渲染 | ✅ 原生支持 | ✅ 原生支持 | ❌ 需 Forward 兜底 |
| 复杂材质 | ✅ 灵活 | ✅ 灵活 | ⚠️ 受 G-Buffer 编码限制 |
| 带宽 | 低 | 低 | 高 |
| 半透明 Overdraw | 严重 | 严重 | 极少(不透明部分) |
6.3 场景适用性
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 移动端(GPU 带宽受限) | Forward / Forward+ | 带宽低,MSAA 有用 |
| PC 端多光源场景 | Deferred / Clustered Deferred | 光源数不受限 |
| 主机端(高画质) | Clustered Forward+ 混合 Deferred | 兼顾光源数和 MSAA |
| VR(需要 MSAA + 高帧率) | Clustered Forward+ | MSAA 刚需 |
| 大量半透明物体 | Forward 为主,Deferred 兜底透明 | Forward 支持透明混合 |
| 复杂材质(SSS、Clear Coat) | Forward / Clustered Forward+ | 不受 G-Buffer 编码限制 |
7. 现代引擎的混合策略
很少有引擎只用一种方案。常见的是 混合 Forward + Deferred 的策略:
1 | |
Unity URP 在 Deferred 模式下就是这样的混合管线:不透明物体走 G-Buffer + Lighting Pass,透明物体退回到 Forward。HDRP 更是支持在同一个相机中按 Material 级别切换 Forward / Deferred。
8. 总结
1 | |
Forward 演进路线:传统 Forward(逐物体逐光源)→ Forward+(+2D Tile Culling)→ Clustered Forward+(+Z 方向 Cluster)。每次演进都是增加空间剖分的精度,减少无效光照计算。
Deferred 演进路线:Classic Deferred → Deferred Lighting(分拆法线减少带宽)→ Tile/Clustered Deferred(+空间 Culling)。演进方向同样是减少带宽浪费和提升光源管理效率。
两种方案没有绝对的优劣——选择取决于场景特征和硬件目标。移动端受带宽限制时 Forward+ 往往更好,PC/主机多光源场景 Deferred 更有优势,而 VR 因 MSAA 刚需通常倾向 Clustered Forward+。
参考
- 《Real-Time Rendering》4th Edition, Chapters 19-20
- Forward+: 基于 Tile 的光源裁减方案
- Clustered Deferred and Forward: Ola Olsson 等人的论文
- Unity URP / HDRP 官方文档
- UE4 Deferred Shading 文档