Base-前向渲染与延迟渲染
渲染管线中,光照计算阶段的组织方式决定了渲染器的整体架构。前向渲染(Forward)和延迟渲染(Deferred)是两种最主流的方案,它们各自衍生出一系列变体。
下文从光源裁减、G-Buffer、带宽和透明渲染等方面比较两类方案及其常见变体。
1. Forward Rendering(前向渲染)
1. 1 基本原理
前向渲染是最直观的渲染方式:遍历每个物体 → 对该物体计算所有影响它的光源 → 得到最终颜色 → 写入 Framebuffer。
伪代码逻辑:
1 | |
朴素实现可能逐物体检查所有光源,但实际着色成本还取决于像素覆盖、光源列表与材质。N×M 可用于说明一种粗筛关系,不是整帧渲染复杂度的完整公式。
1. 2 优点
- 实现简单直观
- 透明渲染原生支持 — 一个 Pass 完成所有计算,Alpha Blend 直接工作
- MSAA 较易接入 — 覆盖与深度可逐样本处理,着色频率则由实现决定,不一定每个样本都运行一次完整 Shader
- 带宽低 — 只需要当前物体的数据,不需要读写 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 在前向渲染中按物体裁减光源列表)
有效的空间索引和光源裁剪可以改变实际检查量,不能把所有前向实现锁定在同一个最坏情况公式。
2. Forward+ Rendering
Forward+(Tiled Forward Rendering)共享区域光源列表,减少逐像素无关光源计算;列表构建、像素数量与每区光源数共同决定成本,不能只用物体数和 Tile 数给出通用复杂度。
2. 1 核心思想
把屏幕划分为多个 Tile(网格块,通常 16×16 或 32×32 像素),在渲染前先用 Compute Shader 计算每个 Tile 内实际影响该区域的光源列表。渲染物体时,Shader 通过屏幕坐标查询当前像素所在 Tile 的光源列表,只计算列表中的光源。
1 | |
2. 2 关键改进
- 共享了光源裁减的结果 — 同一 Tile 内的所有像素共享同一个光源列表
- 可减少对逐物体光源列表的依赖,也能与其他裁剪组合使用
- 光源数量可以大幅提升(数十到上百个),只要在同一 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 | |
成本应拆成几何/材质写入与可见表面的光照求值。实际光照量仍与光源覆盖、列表和采样有关,不应把不同单位的数量直接相乘作为整帧复杂度。
4. 2 优点
- 更便于组织多光源 — 可利用光源体积、Tile 或 Cluster 减少无关计算,仍受带宽、着色与列表容量约束
- 物体数与光源数解耦 — Pass 1 只关心有多少物体要画,Pass 2 只关心有多少光源要算
- 避免对被遮挡不透明表面重复做完整光照 — 但材质写入仍可能 Overdraw,多光源也可能重复访问同一像素;透明通常仍走独立路径
4. 3 缺点
- G-Buffer 带宽大 — MRT 写 4~6 张 RT,带宽和内存消耗远高于 Forward
- MSAA 更复杂 — 可以实现,但需要处理多样本 GBuffer、边缘着色和存储,具体管线可能选择不提供
- 透明渲染不原生支持 — 透明物体无法在 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 一次性计算出最终颜色
- 延迟着色的发展早于这些游戏工程分享,不将某个项目报告视为唯一首次提出
- 代表: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 更小(只需要深度+法线),实际带宽变化取决于附件格式、额外材质 Pass 与缓存,不能固定写成节省 50%。材质属性在最后 Pass 才读取,可以支持更多的材质种类,不受固定 G-Buffer 编码限制。
缺点:需要三个 Pass,Light Accumulation Pass 和 Material Pass 之间需要额外的上下文切换。
5. 3 Tiled Deferred Shading(分块延迟着色)
结合 Forward+ 思路的延迟渲染:
- 将屏幕分为 Tile
- 在 Lighting Pass 前,用 Compute Shader 逐 Tile 裁减光源
- Lighting Pass 按 Tile 调度,只处理影响当前 Tile 的光源
这是渲染算法的分块光照组织,与硬件 TBDR 不是同一个概念。在 IMR GPU 上也可使用;只有具体附件访问与后端允许时,才能进一步利用片上存储减少传输。
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. 参考
- 《Real-Time Rendering》4th Edition, Chapters 19-20
- Forward+: 基于 Tile 的光源裁减方案
- Clustered Deferred and Forward: Ola Olsson 等人的论文
- Unity URP / HDRP 官方文档
- UE4 Deferred Shading 文档
9. 范围与成本模型
本文以 2024 年 9 月 15 日 Forward+/Clustered 笔记作为整理时间。Tile 深度上下界能改善光源裁剪,但只用不透明深度构建列表时,还要确保透明表面的所需光源不会被错误排除。
实际选择需测几何、材质、光照与带宽,不把“前向简单、延迟多光源”变成绝对规则。表格中的高低为典型趋势,无法代替具体附件布局、分辨率和平台测量。
总结
1 | |
Forward 从逐物体管理光源,演进到二维 Tile Culling,再加入 Z 方向的 Cluster;Deferred 也从经典 G-Buffer 方案发展出 Deferred Lighting 和 Tile/Clustered Deferred。两条路线都在减少无效光照计算和不必要的带宽消耗。
具体选择仍取决于场景与硬件。移动设备对带宽更敏感,通常更适合 Forward+;PC 和主机上的多光源场景常采用 Deferred;VR 需要兼顾 MSAA 时,则更倾向 Clustered Forward+。现代渲染器也经常混用多种路径,而不要求整帧服从单一架构。