需要预先学习:


1. 渲染方程


1.1 从局部到全局的数学表达

全局光照与局部光照 把光照分为局部与全局两个层次。渲染方程(rendering equation)把"全局"写成了数学形式:一个表面点向观察方向反射的总亮度,等于它自身发出的光,加上从法线半球所有方向射来的光经 BRDF 反射后的总和:

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

方程中各项:

符号 含义
Lo(p,ωo)L_o(p, \omega_o) 点 pp 朝观察方向 ωo\omega_o 反射的辐射亮度(radiance)
Le(p,ωo)L_e(p, \omega_o) 自发光项,只有光源表面非零
fr(p,ωi,ωo)f_r(p, \omega_i, \omega_o) BRDF(双向反射分布函数),描述入射方向 ωi\omega_i 的光有多少被反射到 ωo\omega_o
Li(p,ωi)L_i(p, \omega_i) 从方向 ωi\omega_i 射入的辐射亮度
n⋅ωi\mathbf{n} \cdot \omega_i Lambert 余弦项(见 着色与渲染管线),倾斜入射时单位面积实际接收的能量减少
Ω\Omega 法线 n\mathbf{n} 所在的半球,积分覆盖所有可能的入射方向

方程说的是:出射光 = 自发光 + 半球上所有入射方向贡献的积分。看起来只是一条方程,但它把反射过程描述为"连续方向的累加",而不是 Blinn-Phong 那种"光源逐项求和"(见 Phong模型与Blinn-Phong模型)——这是它与经验模型最根本的区别。


1.2 方程的递归性:全局的数学根源

渲染方程的"全局"藏在 LiL_i 里:从 ωi\omega_i 射来的光,可能直接来自光源,也可能来自另一个表面点的反射——而那个点的出射光,又由同一条方程描述。所以渲染方程是递归的:把一个点的出射亮度反复展开,得到的是"光在场景中不断反弹"的路径链:

  1. 直接光照:光源 → 该点;
  2. 一次反弹:光源 → 表面 A → 该点;
  3. 两次反弹:光源 → 表面 A → 表面 B → 该点……

这就是路径追踪里"一条路径"的由来(光线追踪 的路径追踪部分)。阴影、间接光照、颜色溢出这些全局效果(全局光照与局部光照)全部隐含在这条递归里:

注:方程中的 LiL_i 天然处理遮挡——如果 ωi\omega_i 方向被别的物体挡住,LiL_i 就取"挡住它的那个表面"的出射光。阴影不是方程的附加项,而是递归本身的结果。


1.3 为什么无法解析求解

渲染方程是第二类 Fredholm 积分方程,且 frf_r 与 LiL_i 都是任意函数。解析解只存在于极端简化的场景(单一均匀光源、无限大平面、朗伯表面)。真实场景只能数值求解,主流方法是蒙特卡洛:把积分当成随机变量的期望来估计。


2. 蒙特卡洛积分


2.1 基本思想

对积分 I=∫f(x) dxI = \int f(x)\, dx,蒙特卡洛(Monte Carlo)估计的做法是:按概率密度函数 p(x)p(x) 随机采样 NN 个点,用样本均值估计积分:

I≈1N∑i=1Nf(xi)p(xi)I \approx \frac{1}{N} \sum_{i=1}^{N} \frac{f(x_i)}{p(x_i)}

其中 p(x)p(x) 是采样点的概率密度函数(PDF,probability density function),满足 ∫p(x) dx=1\int p(x)\, dx = 1。分母上的 p(xi)p(x_i) 修正了"样本在 xix_i 处出现概率不同"造成的偏差。蒙特卡洛有两个关键性质:

  • 无偏:样本数趋于无穷时,估计值收敛到真实积分;
  • 收敛慢:误差按 O(1/N)O(1/\sqrt{N}) 衰减——样本数翻 100 倍,误差只降到原来的 1/101/10。

收敛慢是路径追踪"噪点久久不消"的根本原因:每个像素要成百上千条路径,画面才能干净(全局光照与局部光照)。


2.2 方差与噪声

单次估计 f(xi)p(xi)\frac{f(x_i)}{p(x_i)} 可能偏离真值很远——某条路径恰好特别亮——多次平均后逐渐收敛。方差(variance)描述估计值的波动幅度,直接体现为画面噪点。

关键观察:f(x)p(x)\frac{f(x)}{p(x)} 的方差取决于 pp 与 ff 的匹配程度。若 p(x)∝f(x)p(x) \propto f(x),这个比值恒为常数,方差为零——但 ff 通常无法解析归一化。实用策略是让 pp 尽量贴近 ff 的形状,这正是重要性采样。


3. 重要性采样


3.1 均匀采样为什么不行

对半球均匀采样(p=12πp = \frac{1}{2\pi})实现最简单,但方差巨大。被积函数 f=fr⋅Li⋅cos⁡θf = f_r \cdot L_i \cdot \cos\theta 的形状通常极不均匀:高光 lobe 只覆盖一小块方向,点光源只占天空的一个点。均匀采样时绝大多数样本落在"几乎没贡献"的方向上,偶尔一个样本撞上高光或光源,单样本值极大,画面充满亮点噪点。

重要性采样(importance sampling)的思想:让采样分布 pp 与被积函数的形状大致一致,样本密集落在贡献大的区域,用更少的样本获得更低的方差。

对应两种常见形状:

被积形状 采样分布 说明
漫反射 lobe 余弦加权采样,p(θ)=cos⁡θπp(\theta) = \frac{\cos\theta}{\pi} 贡献与 cos⁡θ\cos\theta 成正比,直接按余弦分布采样
高光 lobe(GGX) GGX 重要性采样,pp 取法线分布 DD 贡献集中在微表面法线朝向半程向量的区域,必须按其分布采样

3.2 生成样本:Hammersley 序列与逆变换

重要性采样需要一个标准流程:先生成均匀随机数(或低差异序列),再通过逆变换采样(inverse transform sampling)映射到目标分布。这里给出 GLSL 实现——它在 PBR 的 Cook-Torrance 高光项和 IBL 的预计算中都会用到:

const float PI = 3.14159265359;

// 位反转:生成第 i 个 Hammersley 样本(低差异序列,比纯随机更均匀)
float radicalInverse_VdC(uint bits)
{
    bits = (bits << 16u) | (bits >> 16u);
    bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
    bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
    bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
    bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
    return float(bits) * 2.3283064365386963e-10;
}

vec2 hammersley(uint i, uint N)
{
    return vec2(float(i) / float(N), radicalInverse_VdC(i));
}

/*
 *  GGX 重要性采样
 *
 *  参数:
 *      xi    (xi.x, xi.y) 均匀分布的二维随机样本(可用 Hammersley)
 *      N     表面法线(世界空间,已归一化)
 *      roughness   粗糙度
 *
 *  返回:
 *      切线空间映射到世界空间的采样方向(半程向量)
 */
vec3 importanceSampleGGX(vec2 xi, vec3 N, float roughness)
{
    float a = roughness * roughness;

    // 由 GGX 法线分布的累积分布反推极角(逆变换采样)
    float phi = 2.0 * PI * xi.x;
    float cosTheta = sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y));
    float sinTheta = sqrt(1.0 - cosTheta * cosTheta);

    vec3 H;
    H.x = cos(phi) * sinTheta;
    H.y = sin(phi) * sinTheta;
    H.z = cosTheta;

    // 从切线空间(z 轴为法线)转到世界空间
    vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
    vec3 tangent   = normalize(cross(up, N));
    vec3 bitangent = cross(N, tangent);

    return normalize(tangent * H.x + bitangent * H.y + N * H.z);
}

几个要点:

  • Hammersley 序列是一种低差异序列(low-discrepancy sequence):样本在单位正方形内分布更均匀,避免纯随机采样的"扎堆"现象,收敛更快——这种用确定序列替代随机数的做法叫拟蒙特卡洛(quasi-Monte Carlo);
  • 逆变换采样:对 GGX 的 DD 分布求累积分布再反解,得到 cosθ=1−ξy1+(a2−1)ξycos\theta = \sqrt{\frac{1 - \xi_y}{1 + (a^2 - 1)\xi_y}}。粗糙度越小,cos⁡θ\cos\theta 越趋向 1,样本越集中在法线附近——正好对应光滑表面窄而亮的高光;
  • 采样得到的是半程向量 HH,用它再反射出入射方向 ωi\omega_i,因为 GGX 的 lobe 定义在半程向量空间(见 PBR 的微表面模型)。

3.3 采样策略对比

策略 采样分布 方差 适用场景
均匀半球采样 p=12πp = \frac{1}{2\pi} 极大(高光越窄方差越大) 几乎不用,仅作教学演示
余弦加权采样 p∝cos⁡θp \propto \cos\theta 低 漫反射为主的积分(辐照度图,见 IBL)
GGX 重要性采样 p∝DGGXp \propto D_{GGX} 低 高光积分(IBL 预滤波、路径追踪的镜面反弹)
光源采样 pp 集中在光源方向 低 直接光照(路径追踪对点光源/面光源的采样)

注:重要性采样只是降低方差,不改变结果的期望——只要估计量无偏,任何 p>0p > 0 的采样最终都收敛到同一个积分。它的意义在于"同样数量的样本,噪声小得多"。


4. 与实时渲染的联系

渲染方程、蒙特卡洛与重要性采样构成离线渲染(路径追踪)的完整理论,实时渲染则走"绕开积分"的路线:

环节 渲染方程里的角色 实时做法
直接光照 积分中少数几个方向 离散光源求和,积分退化为累加(PBR)
间接漫反射 半球积分 预计算成辐照度图,采样一次(IBL)
间接高光 依赖粗糙度与视角的积分 预滤波 mipmap + BRDF LUT 查表(IBL)

一句话总结:实时渲染里"积分"要么被求和替代,要么被预计算替代;重要性采样则是在必须实时采样时(比如 IBL 的离线预计算、路径追踪)用更少的样本换更低的噪声。这套"预计算换实时性"的思路,与 ShadowMap与PCF、PCSS 的阴影贴图、纹理 的 mipmap 一脉相承。