Skip to content

光线追踪(蒙特卡洛积分与路径追踪)

上一章我们把光照写成了渲染方程:

出射光 = 自发光 + 半球上所有入射光的反射贡献

这个式子很优雅,但它也带来了一个现实问题:半球方向有无穷多个,间接光还会继续反弹。路径追踪的核心思路是把“全部算完”改成“随机抽样,长期平均”。这就是蒙特卡洛积分进入渲染的地方。

如果只考虑一个表面点的直接漫反射,我们需要在法线半球上积分;如果再考虑一次反弹,积分空间会多出另一个表面点和另一个方向;反弹次数越多,问题的维度越高。

这类积分通常很难解析求解,因为:

  • 场景几何可能很复杂,光线是否被遮挡只能通过追踪判断。
  • 材质函数可能随方向、波长、纹理坐标变化。
  • 间接光是递归的,一个方向上的入射光又来自别处的出射光。
  • 小光源、镜面反射、焦散等现象会让贡献集中在很少的路径上。

蒙特卡洛方法不试图列举所有路径,而是随机选择一些路径,让估计值随着样本数增加逐渐靠近真实结果。

假设我们要计算:

I = ∫ f(x) dx

如果从概率密度函数 p(x) 中随机抽取 N 个样本 x₁, x₂, ..., xₙ,就可以用下面的估计式:

I ≈ (1 / N) Σ f(xₖ) / p(xₖ)

这里最容易忽略的是分母 p(xₖ)。它表示“这个样本被抽到的概率密度”。如果某个区域被更频繁地采样,它在求平均时就必须被相应地除回来,否则结果会被采样策略本身扭曲。

蒙特卡洛估计有三个重要性质:

  • 样本数越多,结果越稳定。 每个像素都可以看成很多随机路径贡献的平均值。
  • 估计可以无偏,但噪声可能很大。 无偏表示长期平均会收敛到正确结果,不表示少量样本就好看。
  • 收敛速度比较慢。 噪声大致按 1 / √N 下降,所以样本数增加 4 倍,噪声通常只减半。

渲染方程中的半球积分可以写成:

Lₒ(x, ωₒ) = Lₑ(x, ωₒ) + ∫Ω fᵣ(x, ωᵢ, ωₒ) Lᵢ(x, ωᵢ) cosθ dωᵢ

用蒙特卡洛方法估计半球积分时,就变成:

Lₒ ≈ Lₑ + (1 / N) Σ
fᵣ(x, ωₖ, ωₒ) Lᵢ(x, ωₖ) cosθₖ / p(ωₖ)

每个随机方向 ωₖ 都对应一条新的光线。光线如果打到光源,就带回直接贡献;如果打到另一个普通表面,就继续递归估计那里的入射光。路径追踪就是把这种随机递归过程做成一条条光路。

蒙特卡洛路径追踪流程

交互辅助:样本、噪声与重要性采样

Section titled “交互辅助:样本、噪声与重要性采样”

下面的小程序把“随机方向采样”和“像素估计收敛”放在一起观察。左侧半球表示从表面点发出的候选方向,右侧柱状图表示一个像素在不同样本下的估计值。

左侧半球显示随机采样方向,右侧条带模拟像素估计结果。样本数越多,平均值越稳定;重要性采样会更多采到贡献大的方向,因此在相同样本数下通常更少噪声。

可以重点观察两件事:

  • 把样本数调大时,右侧估计值会更稳定,但噪声不会线性下降。
  • 切换到重要性采样后,样本会更多集中到高贡献方向;在相同样本数下,估计通常更平稳。

这个示意不是物理精确渲染器,而是用来帮助建立直觉:路径追踪的画面噪声不是“错误”,而是随机积分还没有充分收敛。

路径追踪画面中的颗粒感通常来自方差。某些像素的大部分随机路径贡献很小,偶尔有一条路径打到强光源或高亮反射方向,就会带来很大的值。这种“多数很暗,少数很亮”的分布会产生明显噪声。

常见例子包括:

  • 小面积光源照亮的区域,随机方向很难刚好命中光源。
  • 光泽或镜面材质,能量集中在很窄的反射方向附近。
  • 间接光路径很长,只有少数路径能经过有效的反弹组合。
  • 焦散现象,例如玻璃杯把光聚到桌面上。

提高样本数可以降低噪声,但代价很高。实际渲染器通常会同时使用重要性采样、直接光采样、多重重要性采样、降噪等方法。

重要性采样的目标是让样本分布更接近积分函数的贡献分布。也就是说,哪里可能贡献大,就更常去哪里采样。

对于漫反射表面,贡献里有 cosθ 项,靠近法线方向的入射光通常权重更高。因此漫反射常用余弦加权半球采样,而不是在半球上完全均匀采样。

对于高光材质,样本应该更多靠近镜面反射方向;对于面积光源,样本可以直接在光源表面上抽取。重要性采样不会改变真实积分本身,它只是改变“怎么抽样”。只要估计式中正确除以对应的 p(x),结果仍然可以保持无偏。

一个常见错误是:采样策略变了,但权重没有跟着概率密度一起变。这样画面可能看起来更亮或更暗,却不是物理结果,而是估计器被写偏了。

路径追踪实现中通常会维护一个累积权重,常叫做路径吞吐量。它表示光线经过多次反弹后,当前路径还剩下多少贡献能力。

当路径在表面点采样一个新方向时,可以用类似下面的形式更新:

T_new = T_old × fᵣ(ωᵢ, ωₒ) cosθ / p(ωᵢ)

其中:

  • T_old 是此前路径累积下来的权重。
  • fᵣ 描述材质如何把入射方向的光反射到出射方向。
  • cosθ 来自表面投影关系。
  • p(ωᵢ) 是这次方向采样的概率密度。

如果路径后来打到光源,就把光源辐射乘上当前吞吐量累加到像素:

L += T × Lₑ

这就是为什么路径越长,贡献通常越小,但仍然可能带来颜色渗透、柔和间接光和更真实的空间氛围。

  1. 从相机生成主光线。
    对每个像素发出一条或多条随机扰动后的相机光线。

  2. 寻找最近交点。
    用加速结构找到光线首先击中的三角形、球体或其他几何体。

  3. 累加自发光。
    如果交点是光源,或者材质带有自发光,就把它按当前路径吞吐量加入结果。

  4. 选择新的反弹方向。
    根据材质分布、半球分布或光源采样策略随机生成下一条光线。

  5. 更新路径吞吐量。
    用 BRDF、余弦项和采样概率密度更新路径权重。

  6. 继续追踪或终止路径。
    达到最大反弹次数,路径离开场景,或者通过俄罗斯轮盘终止时停止。

概念上,它可以写成:

let radiance = 0;
let throughput = 1;
let ray = cameraRay(pixel);
for (let bounce = 0; bounce < maxBounces; bounce++) {
const hit = trace(ray);
if (!hit) break;
radiance += throughput * emittedLight(hit);
const sample = sampleMaterialDirection(hit);
throughput *= sample.brdf * sample.cosine / sample.pdf;
ray = spawnRay(hit.position, sample.direction);
}

真实渲染器会处理更多细节,例如法线偏移、纹理、介质、光源采样、多重重要性采样和数值稳定性。但上面这段流程已经抓住了路径追踪的主干。

如果只按材质随机反弹,路径要“碰巧”打到小光源可能很困难。比如一个房间里只有一盏小灯,漫反射表面随机向半球发射方向,大部分方向都不会命中灯。

下一事件估计的做法是:在每次命中表面时,额外直接从光源上采样一个点,检查这个点是否可见。如果没有被遮挡,就把这条到光源的直接贡献加入当前像素。

这样一来,直接光不必完全依赖随机反弹碰运气。许多现代路径追踪器会同时使用:

  • 材质采样。 根据 BRDF 选择下一条反弹方向,擅长处理高光和间接路径。
  • 光源采样。 直接朝光源抽样,擅长减少小光源造成的噪声。
  • 多重重要性采样。 把多种采样策略按权重合并,避免某一种策略在特定场景里表现很差。

路径不能无限追踪下去。最简单的方法是设置最大反弹次数,例如超过 8 次就停止。但如果理论上无限次反弹仍有贡献,硬性截断会引入一定偏差。

俄罗斯轮盘是一种概率终止方法:路径在若干次反弹后,以某个概率继续,以另一个概率停止。如果路径幸运地继续,就把吞吐量除以继续概率作为补偿。

如果继续概率是 q:
继续时 T = T / q
停止时路径贡献为 0

长期平均后,这种方法不会因为随机终止而系统性变暗。它的作用不是让单条路径更准确,而是在控制计算量的同时保持估计器的期望值正确。

路径追踪把很多光照现象统一到随机路径上,因此一些画面特征可以从路径角度理解:

  • 颜色渗透。 光线从红色墙面反弹到白色物体上,会把红色能量带过去。
  • 柔和阴影。 面积光源上的不同采样点会产生不同遮挡边界,平均后形成柔和过渡。
  • 高光噪声。 光泽材质只有少数方向贡献很大,低样本时容易出现亮点。
  • 萤火点。 极少数路径带回异常高贡献,形成孤立的亮像素。
  • 降噪痕迹。 后处理降噪可以减少颗粒,但过强时会抹掉纹理、细节或间接光变化。

理解这些现象后,调试渲染器时就不只是“画面不好看”,而是能判断问题更可能来自采样、材质、光源、终止策略还是后处理。

本章把渲染方程落到了路径追踪实现上:

  1. 蒙特卡洛积分用随机样本估计复杂积分,样本数越多结果越稳定。
  2. 路径追踪把半球积分和递归间接光转换成一条条随机光路。
  3. 采样概率密度必须进入权重计算,否则估计结果会产生偏差。
  4. 重要性采样通过更聪明地分配样本来降低方差。
  5. 路径吞吐量记录多次反弹后路径剩余的贡献权重。
  6. 直接光采样、下一事件估计和俄罗斯轮盘是路径追踪中非常基础的效率工具。

到这里,第五部分已经从“光线如何求交”推进到了“随机光路如何形成全局光照”。下一章开始,我们会转向材质与外观:同样一束光打到塑料、金属、玻璃和皮肤上,为什么会呈现完全不同的视觉结果。