环境光遮蔽的核心问题
环境光遮蔽(Ambient Occlusion,AO)是实时渲染中近似全局光照最常用的手段之一。它描述了一个着色点 x 从均匀环境光中接收到的可见性积分:当周围几何体遮挡了来自某些方向的光线时,该点会变暗。
现有方法(SSAO、HBAO 等)普遍存在以下问题:
- BIAS 使用衰减函数(obscurance)而非二值可见性,结果偏离物理正确
- COST 每像素采样数量多,60fps 时 budget 极度紧张
- GI 完全忽略近场多次弹射的间接光,导致过度变暗
- SPEC AO 只处理漫反射,无法用于镜面(Specular)遮蔽
GTAO 的三大贡献
GTAO 公式推导
GTAO 基于 Bavoil 等人的 HBAO 框架,但做了关键改造。理解推导需要掌握三个层次:
标准 AO 将反射辐射近似为均匀环境光 Lᵢ 乘以遮蔽积分:
Bavoil (HBAO) 把积分域从半球面转化为一组切线方向
// 外积对 φ 做 Monte Carlo,内积对 θ 积分,仍需 LUT
HBAO 的内积仍使用数值积分或 LUT(Timonen),且参考系以法线为轴。
GTAO 的关键改变:将积分参考系从法线轴改为视角方向
// 内积 â 现在有闭合解析式;外积对 φ 做 Monte Carlo
对每个方向
▸ 交互:解析内积 â 与 θ₁/θ₂ 的关系
实现细节:如何达到 0.5ms
GTAO 的目标是 PS4 1080p 60fps 的 0.5ms 预算。以下是各个优化层级,点击展开详情:
// α ≈ 0.1–0.2,静止时平滑收敛
深度缓冲无法表达几何体的厚度。薄物体(叶片、树枝)在 SSAO 中会产生不真实的大量遮蔽。
核心假设: 一个物体的厚度约等于它在屏幕空间的尺寸。在搜索水平角时,如果下一个深度样本比当前更近(cos 减小),说明可能是薄遮挡体的背面,此时用 EMA 混合而非直接取 max:
else: θ = blend(θₛ₋₁, θₛ) // 可能是薄体背面
ALU 消耗极低(整个 AO 的热点在 texture fetch),GCN rsqrt 指令处理水平线搜索的 sqrt 运算。
近场间接光补偿
标准 AO 假设所有遮挡体都是吸收体(不反射任何光)。但真实世界中,相邻表面会将光线弹射回来。这导致 AO 在墙角、裂缝处过度变暗。
数据驱动拟合
作者在 7 种场景(包含各类遮蔽类型)中,用 7 个反照率值(0.1–0.9)渲染了 AO 值与 3-bounce 路径追踪 GI 的映射关系(见 Fig.6),发现三次多项式拟合效果极好,且系数与反照率呈线性关系:
// 最终着色:Lᵣ = Lᵢ · (ρ/π) · G(A, ρ)
▸ 交互:GI 补偿函数 G(A, ρ) 可视化
GTSO:镜面遮蔽
传统 Split-Integral 方案(UE4 等使用)假设可见性
// 归一化因子 Cᵥ = ∫ fᵣ⟨n,ωᵢ⟩dωᵢ = 恰好与 F 项约掉
计算方式:锥—锥相交
将两个难以精确积分的量都近似为锥体(Cone):
锥角 αᵥ: cos(αᵥ) = √(1−Â)
锥角 αₛ: cos(αₛ) = exp(−2.3 r²)
▸ 交互:GTSO 参数可视化
实时 AO 积分演示
下面模拟 GTAO 在单像素上的水平线搜索过程。调整参数观察地平角、内积 â、以及最终 AO 值的变化:
▸ 单像素水平线搜索(截面视图)
方法横向对比
| 方法 | 内积 | 可见性 | GI 补偿 | 镜面 | 性能 |
|---|---|---|---|---|---|
| SSAO | 球面采样 | 深度比较 | ✗ | ✗ | 快 |
| HBAO | 数值水平线 | 衰减函数 | ✗ | ✗ | 中 |
| HBAO+ | LUT 水平线 | 衰减函数 | ✗ | ✗ | 中 |
| GTAO | 解析水平线 | 二值(物理正确) | ✓ 多项式 | ✓ GTSO LUT | 0.5ms/1080p |
Unity URP 实现要点
Unity 从 URP 14(Unity 2022 LTS)起在 SSAO 中引入了 GTAO 模式。若自行实现需注意:
- ① 水平线搜索步长需在视空间(View Space)执行,使用 Linear Eye Depth
- ② bent normal 需要在采样后存入 G-Buffer(RG10B10A2 足够)供 GTSO 使用
- ③ 时间 AA 的历史重投影可复用 TAA 的 velocity buffer
- ④ GI 补偿系数(Eq.10)只有 6 个浮点数,直接硬编码进 shader 即可
- ⑤ DrawMeshInstancedIndirect 场景中 per-instance 无法轻易获取 bent normal,建议先存入深度/法线 prepass
// 旧方法用连续衰减函数替代 V → "Obscurance"(有偏)