4、蒙特卡洛积分:随机采样与期望估计

蒙特卡洛积分,说白了就是用随机数来算积分。你可能会觉得奇怪——积分不是高等数学里那种精确计算的东西吗?怎么还能用随机数来搞?

嗯,这里有个关键点:很多光效相关的积分,比如渲染方程里的半球积分,根本没有解析解。你没法像算 x² 的积分那样直接套公式。这时候,蒙特卡洛方法就派上用场了。

我个人习惯把蒙特卡洛积分理解成「用随机采样来逼近真实值」。你采样越多,结果越准。就这么简单。

4.1 从期望说起

先看一个生活中的例子。你想知道一个班级的平均身高,但没时间量所有人。怎么办?随机抽 10 个人,量他们的身高,算个平均值。这个平均值,就是真实平均身高的一个估计。

蒙特卡洛积分也是这个道理。我们要算一个积分:

I = ∫ f(x) dx

如果 f(x) 太复杂,没法直接算,我们就随机采样一些 x,计算 f(x),然后取平均。数学上可以证明,这个平均值会收敛到真实的积分值。

公式长这样:

I ≈ (1/N) * Σ f(xᵢ) / p(xᵢ)

其中 p(x) 是采样概率密度函数。为什么要除以 p(x)?因为如果某些区域采样更密集,就要降低它们的权重,否则结果会偏。

核心思想:蒙特卡洛积分把积分问题转化成了期望估计问题。采样点越多,估计越准。误差以 O(1/√N) 的速度下降。

4.2 重要性采样:别浪费你的采样

我在项目中遇到过一个问题:用均匀采样算光照,噪点特别多。尤其是光源很小的时候,大部分采样都落在暗处,只有少数几个采样能打到光源。结果就是画面忽明忽暗,闪烁得厉害。

这时候就需要重要性采样。说白了就是:在贡献大的地方多采样,贡献小的地方少采样。

举个例子。你要估算一个房间里灯泡的总亮度。如果你随机站在房间里各个位置去感受亮度,大部分位置都很暗,只有灯泡附近很亮。均匀采样的话,你可能要采几千个点才能得到一个稳定的估计。

但如果你聪明一点,专门在灯泡附近多采几个点,其他地方少采几个点,那效率就高多了。

数学上,重要性采样就是选择一个与 f(x) 形状相似的 p(x):

I ≈ (1/N) * Σ f(xᵢ) / p(xᵢ)

如果 p(x) 选得好,方差会大幅降低。选得不好……嗯,可能会更糟。

采样策略 方差 适用场景
均匀采样 高 函数变化平缓时可用
重要性采样 低(p选得好时) 函数有尖峰或集中区域
分层采样 中 需要保证覆盖均匀性
自适应采样 动态降低 复杂场景,自动调整

我的经验:重要性采样是光效渲染中最重要的优化手段之一。我曾经把一个需要 10000 采样的场景,用重要性采样降到 512 采样,画质几乎没区别。关键是 p(x) 要选对——通常用 BRDF 或光源分布作为参考。

4.3 随机数生成与低差异序列

蒙特卡洛积分需要随机数。但计算机生成的随机数其实是伪随机数——用算法模拟出来的。这本身没问题,但伪随机数可能会有聚集现象,导致采样不均匀。

你想想看,如果采样点都挤在一起,那估计出来的积分肯定不准。为了解决这个问题,人们发明了低差异序列。

低差异序列的特点是:采样点分布得更均匀,不会出现明显的聚集或空洞。常用的有 Halton 序列、Sobol 序列、Hammersley 序列等。

// 生成二维 Halton 序列示例
float halton(int index, int base) {
    float result = 0.0;
    float f = 1.0 / base;
    int i = index;
    while (i > 0) {
        result += f * (i % base);
        i /= base;
        f /= base;
    }
    return result;
}

// 使用
float u = halton(sampleIndex, 2);  // base 2
float v = halton(sampleIndex, 3);  // base 3

用低差异序列代替纯随机数,收敛速度可以从 O(1/√N) 提升到接近 O(1/N)。这在实践中意味着:同样的采样数,噪点少得多。

注意:低差异序列在高维空间(比如 5 维以上)的效果会下降。我曾经在路径追踪里用 5 维 Sobol 序列,效果还不错,但到了 8 维以上就明显不如分层采样了。高维场景建议用多重重要性采样。

4.4 蒙特卡洛在光效提取中的应用

光效提取的核心是渲染方程:

Lₒ(p, ωₒ) = Lₑ(p, ωₒ) + ∫_Ω fᵣ(p, ωᵢ, ωₒ) Lᵢ(p, ωᵢ) (n·ωᵢ) dωᵢ

这个积分没法直接算。我们用蒙特卡洛方法:

  1. 在半球面上随机采样方向 ωᵢ
  2. 计算每个方向的贡献:fᵣ * Lᵢ * (n·ωᵢ) / p(ωᵢ)
  3. 取平均

这就是路径追踪的基础。每条路径就是一个采样点,所有路径的平均值就是像素的最终颜色。

我刚开始做路径追踪时,总觉得采样数越多越好。后来发现,采样质量比采样数量更重要。一个好的重要性采样策略,配上低差异序列,128 采样就能达到均匀采样 1024 采样的效果。

下面这张图展示了蒙特卡洛积分在光效提取中的整体流程:

蒙特卡洛积分在光效提取中的应用流程 渲染方程 采样策略选择 均匀采样 重要性采样 低差异序列 路径追踪 / 光线投射 像素颜色(期望估计) 采样数 ↑ → 方差 ↓ → 画质 ↑ 但渲染时间也 ↑

4.5 避坑指南

我曾经踩过一个坑:用蒙特卡洛积分算高动态范围光照时,忘记处理极端值。结果画面出现大量白色噪点,看起来像雪花一样。

后来发现,问题出在采样时偶尔会碰到一个亮度极高的方向(比如直接对着太阳),这个采样点的贡献值比其他点大几个数量级,导致整个估计被拉偏。

解决方案有两个:

  • 钳制(Clamping):给贡献值设一个上限,避免单个采样点主导结果
  • 多重重要性采样(MIS):同时从多个分布采样,加权合并,降低极端值的影响

另外,还有一个常见问题:采样数不足时的偏差。蒙特卡洛积分是无偏的——理论上采样数趋于无穷时结果收敛。但实际中我们只能用有限采样,这时候结果会有偏差。尤其是光源很小的场景,偏差特别明显。

我的建议:先用低采样数快速预览场景,确定光照布局。最后渲染时再提高采样数。不要一开始就开 4096 采样,等半天发现构图不对,那就白费功夫了。

4.6 小结

蒙特卡洛积分是光效提取的数学基石。它把复杂的积分问题,变成了简单的随机采样和平均问题。核心要点就三个:

  • 采样策略:重要性采样 > 均匀采样,低差异序列 > 纯随机数
  • 方差控制:采样数决定画质,但采样质量更重要
  • 实际应用:路径追踪、光子映射、环境光照都离不开它