← Back to blog

GAMES202 — Real-Time High Quality Rendering Note5: Real-Time Ray Tracing and Industrial Solutions

对应 Lecture 12–14,课程的最后一块。Volumetric / scattering 材质本课不讲 (依赖太多,留给离线渲染课)。

1. Real-Time Ray Tracing(RTRT)

1.1 RTRT 正在发生

实时工业界说 "Ray tracing is the future and ever will be." 2018 年 NVIDIA 发布 GeForce RTX(Turing 架构),打开了 2500 亿美元的市场。RTX 能做的:光追的阴影、 反射与高光、AO、全局光照。

RTX 真正做到的是什么? 10 Giga rays per second == 对实时应用来说 1 sample per pixel。

10 Giga rays per second
10 Giga rays per second

1 SPP path tracing = 1 次光栅化(primary,替代 primary ray)+ 1 条光线(primary visibility,到光源的 shadow ray)+ 1 条光线(secondary bounce)+ 1 条光线 (secondary visibility)。

1 SPP path tracing
1 SPP path tracing

1 SPP 的结果极其噪。关键技术是 denoising。

1 SPP is extremely noisy
1 SPP is extremely noisy

1.2 去噪的目标

在 1 SPP 下:质量(不过度模糊、无 artifact、保留所有细节)和速度(去噪一帧 少于 2 ms)。听起来是 mission impossible。学术界的方法都做不到:sheared filtering 系列(SF、AAF、FSF、MAAF)、其他离线滤波(IPP、BM3D、APR)、深度学习系列 (CNN、autoencoder)。

Denoising goals
Denoising goals

1.3 工业解法:Temporal

三个最重要的想法:Temporal!Temporal!!Temporal!!!

Temporal
Temporal

G-Buffers:渲染过程中免费得到的辅助信息,通常是每像素的深度、法线、世界 坐标、albedo 等。注意它们只有屏幕空间的信息。

The G-buffers
The G-buffers

Back projection:当前帧 i 的像素 x,在上一帧 i−1 的哪里?

  1. 如果 G-buffer 里有世界坐标 \(s\),直接取;否则 \(s = M^{-1} V^{-1} P^{-1} E^{-1} x\) (\(E\) 是 viewport 变换,仍需要 z 值)
  2. 物体的运动 \(T\) 是已知的(\(s' \xrightarrow{T} s\)),所以 \(s' = T^{-1} s\)
  3. 把上一帧的世界坐标投影到上一帧的屏幕:\(x' = P' V' M' s'\)
Back projection
Back projection

Temporal accumulation / denoising。记 \(\tilde{C}\) 为未滤波的,\(\bar{C}\) 为 滤波后的:

\[\bar{C}^{(i)} = Filter[\tilde{C}^{(i)}], \qquad C^{(i)} = \alpha \bar{C}^{(i)} + (1 - \alpha) C^{(i - 1)}\]

\(\alpha = 0.1 \sim 0.2\),即这一帧 80% 到 90% 的贡献来自之前的帧。

Temporal accumulation 1 SPP RTGI 1 SPP RTGI with denoising

1.4 Temporal 的失败情况

时序信息不总是可用:

Failure: switching scenes Failure: walking backwards Failure: disocclusion

忽略时序失败会怎样? 当然不正确,artifact 是 lagging(拖影、ghosting)。

Lagging
Lagging

调整:

Adjustments to temporal failure
Adjustments to temporal failure

更多的时序失败发生在 shading 上:栅栏场景里光源在背后移动,几何没动但阴影动了, 阴影的 motion vector 是什么? 移动的椅子,glossy 地板上反射像的 motion vector 是什么?几何的 motion vector 对 shading 不适用,会出现 detached / lagging 的阴影和 反射。

Motion vector of shadows Motion vector of reflections

说明:Temporal accumulation 的灵感来自 TAA,两者非常相似,时序复用本质上是提高 采样率。进一步缓解时序失败的研究:Eurographics 论文 "Temporally Reliable Motion Vectors for Real-time Ray Tracing"(专门为阴影、glossy 反射、遮挡设计 motion vector)。

2. 空间滤波的实现

2.1 Gaussian filter

要(低通)滤波一张图去掉高频噪声。输入噪声图 \(\tilde{C}\) 和滤波核 \(K\)(可以逐像素 不同),输出 \(\bar{C}\)。以像素 i 为中心的高斯滤波,邻域内的每个像素 j 按距离贡献:

For each pixel i
    sum_of_weights = sum_of_weighted_values = 0.0
    For each pixel j around i
        Calculate the weight w_ij = G(|i - j|, sigma)
        sum_of_weighted_values += w_ij * C_input[j]
        sum_of_weights += w_ij
    C_output[i] = sum_of_weighted_values / sum_of_weights

注意:记录权重之和做归一化;检查权重和是否为零(对其他核);颜色可以多通道。

Gaussian filtering
Gaussian filtering

2.2 Bilateral filtering

高斯滤波的问题:把边界也模糊了,而边界是我们想保留的高频。观察:边界 ↔ 颜色 剧烈变化。思路:如果 j 的颜色和 i 差太多,就让 j 贡献得少,给核加一个控制项:

\[w(i, j, k, l) = \exp\left(-\frac{(i - k)^2 + (j - l)^2}{2\sigma_d^2} - \frac{\|I(i, j) - I(k, l)\|^2}{2\sigma_r^2}\right)\]

效果不错,但对噪声很大的图(噪点本身就是颜色剧变)不够。

Bilateral filtering
Bilateral filtering

2.3 Joint bilateral filtering

高斯用 1 个度量(距离),双边用 2 个(位置距离和颜色距离)。能用更多的"特征" 来引导滤波吗? 可以,这就是 cross / joint bilateral filtering,特别适合给 path tracing 的结果去噪。

Joint bilateral filtering
Joint bilateral filtering

渲染里的独特优势:有大量免费的特征,即 G-buffers(法线、深度、位置、object ID 等,主要是几何的)。更好的是,G-buffer 是没有噪声的,因为它们和多次弹射无关。

Joint bilateral with G-buffers
Joint bilateral with G-buffers

说明:度量本身不需要归一化,滤波过程会归一化;高斯不是唯一选择,任何随"距离" 递减的函数都行(指数、clamped cosine 等)。

Notes on joint bilateral filtering
Notes on joint bilateral filtering

例子:考虑深度、法线、颜色。A 和 B 之间的边界靠深度区分,B 和 C 之间靠法线, D 和 E 之间靠颜色。

Joint bilateral example
Joint bilateral example

2.4 实现大滤波器

每个像素要遍历 N×N 邻域:小滤波器(7×7)没问题,大滤波器(64×64)代价太大。两种 方案:

方案 1:分离的 pass。 2D 高斯拆成一个水平 pass(1×N)和一个垂直 pass(N×1), 查询次数从 \(N^2\) 降到 \(N + N\)。为什么可以?因为 2D 高斯核是可分离的: \(G_{2D}(x, y) = G_{1D}(x) \cdot G_{1D}(y)\),而滤波是卷积, \(\iint F(x_0, y_0) G_{2D}(x_0 - x, y_0 - y) \, dx \, dy = \int \left(\int F(x_0, y_0) G_{1D}(x_0 - x) \, dx\right) G_{1D}(y_0 - y) \, dy\)。 所以分离 pass 要求核可分离,理论上双边滤波不能分离实现(实践中也有人硬这么做)。

Separate passes Separable kernel

方案 2:逐渐增大的尺寸。 用递增的尺寸滤波多次,具体是 à-trous wavelet: 多个 pass,每个都是 5×5 的滤波,但样本间隔按 \(2^i\) 增长(64×64 的滤波只需 5 个 pass 各 25 次查询)。

À-trous wavelet
À-trous wavelet

更深的理解:为什么要增大尺寸?更大的滤波器 == 去掉更低的频率。为什么跳过样本 是安全的?采样 == 重复频谱,第一个 pass 已经去掉了高频,第二个 pass 采样稀疏 造成的频谱重复不会和剩余的低频重叠,所以不会走样。

Frequency view of à-trous
Frequency view of à-trous

以上滤波方法也可以用于 PCSS、SSR 等的去噪。

2.5 Outlier removal 与 temporal clamping

滤波不是万能的:有时滤波后仍然噪甚至一块一块的,主要是极亮的像素(outliers、 fireflies)造成的(一个亮点被摊到整个滤波范围)。思路:在滤波前去掉 outlier。

Outlier detection:对每个像素看它的 7×7 邻域,算均值和方差,值在 \([\mu - k\sigma, \mu + k\sigma]\) 之外的就是 outlier。Outlier removal:把范围外 的值 clamp 到这个范围,注意不是扔掉(置零)。

Outlier detection and clamping
Outlier detection and clamping

Temporal clamping:直接用上一帧的颜色会 ghosting,因为 \(C^{(i - 1)}\) 可能和 \(\bar{C}^{(i)}\) 差很远。在时序复用里,把 \(C^{(i - 1)}\) clamp 到 \(\bar{C}^{(i)}\) 的附近:

\[C^{(i)} = \alpha \bar{C}^{(i)} + (1 - \alpha) \, clamp(C^{(i - 1)}, \mu - k\sigma, \mu + k\sigma)\]

\(\mu, \sigma\) 来自当前帧的邻域。这是噪声和拖影之间的权衡;注意是把上一帧 clamp 向当前帧,不是反过来。

Temporal clamping
Temporal clamping

3. RTRT 专用的滤波方法

3.1 SVGF

Spatiotemporal Variance-Guided Filtering(Schied et al. 2017):和基本的 时空去噪方案非常相似,但加了方差分析和一些技巧。

SVGF
SVGF

三个因子引导 joint bilateral filtering:

深度:

\[w_z = \exp\left(-\frac{|z(p) - z(q)|}{\sigma_z |\nabla z(p) \cdot (p - q)| + \epsilon}\right)\]

理解:A 和 B 在同一平面上、颜色相近,应该互相贡献,但它们的深度差很大(平面斜着)! 所以用相对于切平面的深度差(深度梯度沿 p 到 q 方向的投影作为分母)。

SVGF depth
SVGF depth

法线:

\[w_n = \max(0, n(p) \cdot n(q))^{\sigma_n}\]

回忆,不一定要是高斯。注意有 normal map 时用宏观法线。

SVGF normal
SVGF normal

亮度(灰度):

\[w_l = \exp\left(-\frac{|l_i(p) - l_i(q)|}{\sigma_l \sqrt{g_{3 \times 3}(Var(l_i(p)))} + \epsilon}\right)\]

噪声大的地方颜色差不可信,所以用方差归一化:方差在 7×7 邻域内空间计算,也用 motion vector 在时间上平均,使用前再做一次 3×3 的空间滤波。

SVGF luminance
SVGF luminance

结果很干净,但失败情况是 ghosting(比如光源移动导致阴影移动时,时序累积不知道 阴影动了)。

SVGF failure cases
SVGF failure cases

3.2 RAE

Recurrent AutoEncoder(Chaitanya et al. 2017):"Interactive Reconstruction of Monte Carlo Image Sequences using a Recurrent Denoising AutoEncoder"。一个做去噪 (noisy → clean)的后处理网络,借助 G-buffers,网络自动做时序累积。

架构:AutoEncoder(或 U-Net)结构,skip / residual connections 让训练更快更好; recurrent block 累积(并逐渐遗忘)之前帧的信息。

RAE RAE architecture

对比:

质量 Artifact 性能 可解释性 发表
SVGF 干净 Ghosting 快 有 HPG
RAE(刚发明时) 过度模糊 Ghosting 慢 无 SIGGRAPH
SVGF vs RAE
SVGF vs RAE

4. 工业实践(仍从科学角度看)

4.1 Temporal Anti-Aliasing(TAA)

为什么走样?光栅化时每像素采样不够,终极方案是更多采样。TAA 把采样分布 / 复用到 多帧上(每帧在像素内用不同的抖动偏移),和 RTRT 里的时序复用几乎完全一样,包括 motion vector 和 clamping。

Temporal anti-aliasing
Temporal anti-aliasing

关于反走样的说明:

MSAA vs SSAA SMAA

4.2 Temporal super resolution:DLSS

Super resolution / super sampling:字面意思是提高分辨率。来源 1(DLSS 1.0): 凭空猜;来源 2(DLSS 2.0):来自时序信息。DLSS 2.0 的关键思想是又一个 TAA 类的应用:时序复用采样来提高分辨率。

Temporal super resolution
Temporal super resolution

主要问题:时序失败时 clamping 不再是选项,因为每个更小的像素都需要一个清晰的值 (clamp 得到的是模糊的值)。所以关键是比 clamping 更聪明地使用时序信息,这就是 网络在做的事。

DLSS 2.0
DLSS 2.0

实际问题:如果 DLSS 自己每帧跑 30 ms 就已经死了,网络推理的性能优化是保密的。 对应的方案:AMD 的 FidelityFX Super Resolution,Facebook 的 Neural Supersampling for Real-time Rendering(Xiao et al.)。

4.3 Deferred / tiled / clustered shading

Deferred shading 最初是为了节省 shading 时间。光栅化的流程是三角形 → fragment → 深度测试 → shade → 像素,每个 fragment 都要 shade,复杂度 \(O(\#fragment \times \#light)\)。 关键观察:大部分 fragment 在最终图像里看不到(深度测试 / 遮挡)。能只 shade 可见的 fragment 吗?

修改光栅化过程:把场景光栅化两次。 Pass 1 不 shade,只更新深度缓冲;pass 2 一样但只有深度相等的 fragment 通过(所以只 shade 可见的)。隐含假设是光栅化场景比 shade 所有看不见的 fragment 快得多(通常成立)。复杂度 → \(O(\#vis.frag \times \#light)\)。 问题:难做反走样(几何信息在 G-buffer 里已经离散了),但几乎被 TAA 完全解决。

Deferred shading Deferred shading: two passes

Tiled shading:把屏幕分成 32×32 的 tile 分别 shade。关键观察:不是所有光源都 能照到某个 tile,主要因为距离平方衰减(给光源设影响范围)。复杂度 → \(O(\#vis.frag \times avg\ \#light\ per\ tile)\)。

Tiled shading
Tiled shading

Clustered shading:每个 tile 再按深度分段,本质上是把视锥分成 3D 网格。关键 观察:每个 tile 的深度范围可能很大,很多光源被判定可能照到这个 tile,但实际只照到 很小的深度范围。复杂度 → \(O(\#vis.frag \times avg\ \#light\ per\ cluster)\)。

Clustered shading
Clustered shading

4.4 Level of Detail(LoD)

LoD 非常重要,回忆纹理的 MIPMAP:选对细节层级可以节省计算。RTR 工业界常把多层 细节叫 "cascaded",例子:cascaded shadow maps(近处高分辨率、远处低分辨率 的 shadow map)、cascaded LPV。

Level of detail
Level of detail

关键挑战:不同层级之间的过渡,通常在边界附近需要重叠和混合。另一个例子: geometric LoD,预先生成一组不同三角形数的简化模型,按到相机的距离选(或模型 的一部分,使得没有三角形比一个像素大)。Popping artifact?交给 TAA!这就是 UE5 的 Nanite(当然 Nanite 远不止这些:不同地方不同层级的裂缝怎么办、动态加载调度 怎么最好地利用缓存和带宽、用三角形还是 geometry texture 表示几何、裁剪和剔除……)。

Geometric LoD
Geometric LoD

4.5 全局光照的方案

从本课可以看到:没有一个 GI 方案对所有情况都完美,除了 RTRT,但当前一代完全用 RTRT 仍太贵,所以工业界倾向于混合方案。比如:用 SSR 做粗略的 GI 近似;SSR 失败时(屏幕外、被遮挡)切换到更复杂的光线追踪,硬件或软件的。

GI solutions
GI solutions

把这些混合起来就是 UE5 的 Lumen。

Lumen
Lumen

4.6 结语

什么是有趣的? 任何需要思考的东西,所以放弃思考 == 自杀。实现比理论不重要吗? 从不,但工程技能必须在工程中获得。

课程四块:real-time shadows / environment lighting、real-time global illumination、 real-time shading / materials、real-time ray tracing。没覆盖的话题还有很多:给 SDF 贴纹理、透明材质与 order-independent transparency、粒子渲染、后处理(景深、运动 模糊)、随机种子与蓝噪声、foveated rendering、基于 probe 的 GI、ReSTIR、Neural Radiance Caching、many-light 理论与 light cuts、participating media 与 SSSSS、 头发外观……

Uncovered topics
Uncovered topics
X / Twitter Facebook LinkedIn 微博

You may also like

Comments