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ωᵢ
这个积分没法直接算。我们用蒙特卡洛方法:
- 在半球面上随机采样方向 ωᵢ
- 计算每个方向的贡献:fᵣ * Lᵢ * (n·ωᵢ) / p(ωᵢ)
- 取平均
这就是路径追踪的基础。每条路径就是一个采样点,所有路径的平均值就是像素的最终颜色。
我刚开始做路径追踪时,总觉得采样数越多越好。后来发现,采样质量比采样数量更重要。一个好的重要性采样策略,配上低差异序列,128 采样就能达到均匀采样 1024 采样的效果。
下面这张图展示了蒙特卡洛积分在光效提取中的整体流程:
4.5 避坑指南
我曾经踩过一个坑:用蒙特卡洛积分算高动态范围光照时,忘记处理极端值。结果画面出现大量白色噪点,看起来像雪花一样。
后来发现,问题出在采样时偶尔会碰到一个亮度极高的方向(比如直接对着太阳),这个采样点的贡献值比其他点大几个数量级,导致整个估计被拉偏。
解决方案有两个:
- 钳制(Clamping):给贡献值设一个上限,避免单个采样点主导结果
- 多重重要性采样(MIS):同时从多个分布采样,加权合并,降低极端值的影响
另外,还有一个常见问题:采样数不足时的偏差。蒙特卡洛积分是无偏的——理论上采样数趋于无穷时结果收敛。但实际中我们只能用有限采样,这时候结果会有偏差。尤其是光源很小的场景,偏差特别明显。
我的建议:先用低采样数快速预览场景,确定光照布局。最后渲染时再提高采样数。不要一开始就开 4096 采样,等半天发现构图不对,那就白费功夫了。
4.6 小结
蒙特卡洛积分是光效提取的数学基石。它把复杂的积分问题,变成了简单的随机采样和平均问题。核心要点就三个:
- 采样策略:重要性采样 > 均匀采样,低差异序列 > 纯随机数
- 方差控制:采样数决定画质,但采样质量更重要
- 实际应用:路径追踪、光子映射、环境光照都离不开它