第-6-章-路径追踪与全局光照

Course: I wrote a Ray Tracer from scratch… in a Year


🏛 适用环境:CPU 路径追踪器 / Vulkan Ray Pipeline / OptiX

🎯 目标:理解路径追踪的数学本质(蒙特卡洛积分)、光能传递原理、间接光与噪点抑制,让渲染结果逼近真实世界。


这是整个光线追踪系统的 渲染核心模块(Integrator)

普通 Ray Tracer 计算一次反弹(直接光照);

路径追踪器(Path Tracer)计算多次随机反弹并取平均。

光不止一次反弹 —— 我们要模拟它的旅程。


Path Tracing|路径追踪

Monte Carlo|蒙特卡洛采样

Global Illumination|全局光照

Indirect Light|间接光

Importance Sampling|重要性采样

Russian Roulette|俄轮终止法


光线追踪画“第一道光”,路径追踪画“所有光”。

它随机模拟光子在世界中的旅程,

最终每个像素的亮度 = 无限反弹的平均结果。


Kajiya(1986)提出的通用光照模型:

Lo(p,ωo)=Le(p,ωo)+Ωfr(p,ωi,ωo)Li(p,ωi)(ωin)dωi L_o(p, \omega_o) = L_e(p, \omega_o) + \int_{\Omega} f_r(p, \omega_i, \omega_o) L_i(p, \omega_i) (\omega_i \cdot n) d\omega_i

其中:

  • [L_o]:点 [p] 向外的出射辐射亮度
  • [L_e]:自发光
  • [f_r]:BRDF(双向反射分布函数)
  • [L_i]:入射光亮度
  • [(\omega_i \cdot n)]:入射角余弦项

路径追踪就是用随机采样近似这个积分。


对复杂积分进行随机估计:

f(x)dx1Ni=1Nf(xi)p(xi) \int f(x) dx \approx \frac{1}{N}\sum_{i=1}^{N}\frac{f(x_i)}{p(x_i)}

其中 [p(x_i)] 是采样概率密度。

在路径追踪中,每次随机选择一个方向 [\omega_i],计算该方向的光贡献。

采样次数越多 → 越接近真实平均。


📦 递归散射伪代码

Color trace(const Ray& ray, int depth) {
    if (depth >= MAX_DEPTH) return Color(0,0,0);

    HitRecord rec;
    if (!world.hit(ray, 0.001, inf, rec))
        return backgroundColor(ray);

    Ray scattered;
    Vec3 attenuation;
    Color emitted = rec.mat->emitted();

    if (!rec.mat->scatter(ray, rec, attenuation, scattered))
        return emitted;

    return emitted + attenuation * trace(scattered, depth + 1);
}
  • emitted():若物体发光(如灯),直接加上光能;
  • scatter():决定下一步反射 / 折射;
  • 递归调用直到能量消失或达到最大深度。

为避免无限递归 ——

当光能量衰减较小时,以概率终止反弹:

📦 概率终止示例

float p = max(attenuation.r, max(attenuation.g, attenuation.b));
if (randomFloat() > p)
    return Color(0,0,0);
else
    return attenuation * trace(scattered, depth + 1) / p;

这样既保持能量守恒,又防止性能浪费。


随机方向过于离散,会导致噪点严重。

解决办法:

让采样方向更集中在法线附近(能量更大处)。

Lambertian 表面重要性采样公式:

\[ \omega = \text{random_cosine_direction()} = \frac{1}{\pi}\cos\theta \]

效果:显著减少噪点、提升收敛速度。


路径追踪自然包含间接光

当光线从墙壁反弹照亮另一面墙,就是“全局光照”。

同时,我们也可以直接采样光源以减少噪点:

📦 光源采样示例

Color directLight(HitRecord& rec) {
    Vec3 lightDir = normalize(light.pos - rec.p);
    float intensity = dot(rec.normal, lightDir);
    if (intensity <= 0) return Color(0,0,0);
    if (isInShadow(rec.p, lightDir)) return Color(0,0,0);
    return light.color * intensity;
}

将 directLight + indirectLight 混合得到完整光照。


路径追踪的缺点:随机采样会带来噪点。

常用优化手段:

  • 多样本平均 (Samples per Pixel ↑)
  • Importance Sampling
  • Temporal Accumulation (多帧平均)
  • Denoising(如 OpenImageDenoise、NVIDIA OptiX AI)

1️⃣ 启用多次反弹

  • trace() 中将 MAX_DEPTH 设为 5~10,观察间接光。

2️⃣ 加入自发光物体

  • 在场景中加入发光球体 emitted(),测试光照扩散。

3️⃣ 调整采样数

  • 10 spp → 100 spp → 1000 spp,观察噪点减少。

4️⃣ 开启俄轮终止

  • 验证性能提升与能量一致性。

  • 每次反弹可存储路径能量,后期用于 光线可视化
  • 为快速测试收敛度,可先渲染低分辨率帧。
  • 若使用 Vulkan,可将每条路径映射为 GPU Thread。
  • 支持动态采样:近景高采样、远景低采样。
  • 可加入 Tone Mapping (ACES) 与 Gamma Correction 提升视觉表现。

“光路千转返千回,采样积分近真辉;

随机反射藏间接,俄轮截断保平衡。”


Q1:路径追踪与普通光线追踪的主要区别?

🅰️ 前者随机模拟多次反弹,后者只计算直接光。

Q2:俄轮终止的作用是什么?

🅰️ 以概率提前终止递归,防止能量耗尽的光线浪费计算。

Q3:为何重要性采样能减少噪点?

🅰️ 因为它集中采样在贡献较大的方向,提高采样效率。

Q4:光能积分公式中的 [f_r] 表示什么?

🅰️ 表示表面反射特性(BRDF 函数)。