第-6-章-路径追踪与全局光照
Course: I wrote a Ray Tracer from scratch… in a Year
🎮 第 6 章:路径追踪与全局光照
🏛 适用环境:CPU 路径追踪器 / Vulkan Ray Pipeline / OptiX
🎯 目标:理解路径追踪的数学本质(蒙特卡洛积分)、光能传递原理、间接光与噪点抑制,让渲染结果逼近真实世界。
🧭 引擎定位
这是整个光线追踪系统的 渲染核心模块(Integrator)。
普通 Ray Tracer 计算一次反弹(直接光照);
路径追踪器(Path Tracer)计算多次随机反弹并取平均。
光不止一次反弹 —— 我们要模拟它的旅程。
🏷 关键词速记
Path Tracing|路径追踪
Monte Carlo|蒙特卡洛采样
Global Illumination|全局光照
Indirect Light|间接光
Importance Sampling|重要性采样
Russian Roulette|俄轮终止法
🎮 30 秒玩法摘要
光线追踪画“第一道光”,路径追踪画“所有光”。
它随机模拟光子在世界中的旅程,
最终每个像素的亮度 = 无限反弹的平均结果。
📚 核心原理讲解
🔹 1. 光能传递方程(Rendering Equation)
Kajiya(1986)提出的通用光照模型:
其中:
- [L_o]:点 [p] 向外的出射辐射亮度
- [L_e]:自发光
- [f_r]:BRDF(双向反射分布函数)
- [L_i]:入射光亮度
- [(\omega_i \cdot n)]:入射角余弦项
路径追踪就是用随机采样近似这个积分。
🔹 2. 蒙特卡洛积分原理
对复杂积分进行随机估计:
其中 [p(x_i)] 是采样概率密度。
在路径追踪中,每次随机选择一个方向 [\omega_i],计算该方向的光贡献。
采样次数越多 → 越接近真实平均。
🔹 3. 路径追踪算法逻辑
📦 递归散射伪代码
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():决定下一步反射 / 折射;- 递归调用直到能量消失或达到最大深度。
🔹 4. 俄轮终止法(Russian Roulette)
为避免无限递归 ——
当光能量衰减较小时,以概率终止反弹:
📦 概率终止示例
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;这样既保持能量守恒,又防止性能浪费。
🔹 5. 重要性采样(Importance Sampling)
随机方向过于离散,会导致噪点严重。
解决办法:
让采样方向更集中在法线附近(能量更大处)。
Lambertian 表面重要性采样公式:
\[ \omega = \text{random_cosine_direction()} = \frac{1}{\pi}\cos\theta \]效果:显著减少噪点、提升收敛速度。
🔹 6. 光源采样与间接光
路径追踪自然包含间接光:
当光线从墙壁反弹照亮另一面墙,就是“全局光照”。
同时,我们也可以直接采样光源以减少噪点:
📦 光源采样示例
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 混合得到完整光照。
🔹 7. 噪点与收敛
路径追踪的缺点:随机采样会带来噪点。
常用优化手段:
- 多样本平均 (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 函数)。