深入理解PDF:蒙特卡洛光线追踪中的概率密度函数

上一篇文章中,我们多次提到了PDF(概率密度函数)这个概念,但可能没有深入解释它到底是什么,以及它在光线追踪中为什么如此重要。今天,让我们彻底搞清楚这个蒙特卡洛积分中的核心概念。

一、什么是PDF(概率密度函数)?

1.1 从离散到连续的概率

首先,让我们理解什么是概率密度函数。想象一下:

  • 离散概率 :掷骰子,每个点数出现的概率是1/6,我们可以直接说 P ( 3 ) = 1 / 6 P(3)=1/6 P(3)=1/6
  • 连续概率 :在 [ 0 , 1 ] [0,1] [0,1] 区间内随机取一个数,取到恰好 0.5 0.5 0.5 的概率是 0 0 0(因为有无限多个可能的值)

这就是为什么我们需要PDF!对于连续随机变量,我们不能说取某个特定值的概率,而只能说取某个值附近的概率密度

1.2 PDF的数学定义

概率密度函数 p ( x ) p(x) p(x) 满足:

  1. 非负性 : p ( x ) ≥ 0 p(x) \ge 0 p(x)≥0 对所有 x x x
  2. 归一化 : ∫ − ∞ ∞ p ( x ) d x = 1 \int_{-\infty}^{\infty} p(x) dx = 1 ∫−∞∞p(x)dx=1
  3. 概率计算 : P ( a ≤ X ≤ b ) = ∫ a b p ( x ) d x P(a \le X \le b) = \int_a^b p(x) dx P(a≤X≤b)=∫abp(x)dx

直观理解

  • PDF的值本身不是概率,是"密度"
  • 概率 = 密度 × 区间长度(在一维情况下)
  • PDF越高的区域,随机变量落在那里的可能性越大

1.3 一个简单的例子

cpp 复制代码
// 均匀分布U[0,1]的PDF
float uniformPDF(float x) {
    if (x >= 0 && x <= 1) {
        return 1.0f;  // 在整个区间上密度恒定
    }
    return 0.0f;
}

// 验证归一化:∫₀¹ 1 dx = 1 ✓

// 标准正态分布N(0,1)的PDF
float normalPDF(float x) {
    return (1.0f / std::sqrt(2.0f * M_PI)) * std::exp(-0.5f * x * x);
}

二、光线追踪中的PDF为什么重要?

在蒙特卡洛积分公式中:

F ≈ 1 N ∑ k = 1 N f ( X k ) p ( X k ) F \approx \frac{1}{N} \sum_{k=1}^{N} \frac{f(X_k)}{p(X_k)} F≈N1k=1∑Np(Xk)f(Xk)

分母上的 p ( X k ) p(X_k) p(Xk) 就是PDF!这个公式告诉我们:如果某个样本被抽中的概率很小,它的贡献就要被放大;如果被抽中的概率很大,贡献就要被缩小。

2.1 PDF的作用:偏差校正

假设我们要估计一个函数 f ( x ) f(x) f(x) 的平均值:

cpp 复制代码
// 错误的方式:不考虑采样概率
float badEstimate = 0;
for (int i = 0; i < N; i++) {
    float x = sampleImportant();  // 偏向重要区域采样
    badEstimate += f(x);          // 直接加和会导致过高估计
}
badEstimate /= N;  // 结果会偏大!

// 正确的方式:除以PDF
float goodEstimate = 0;
for (int i = 0; i < N; i++) {
    float x = sampleImportant();
    float pdf = getPDF(x);        // 获取这个样本被抽中的概率密度
    goodEstimate += f(x) / pdf;   // 除以PDF进行校正
}
goodEstimate /= N;  // 无偏估计!

2.2 PDF的理想形式

理论上,最优的PDF应该正比于被积函数:
p ( x ) ∝ f ( x ) p(x) \propto f(x) p(x)∝f(x)

这样, f ( x ) / p ( x ) f(x)/p(x) f(x)/p(x) 变成常数,方差降为零!但现实中我们不知道 f ( x ) f(x) f(x)(这正是我们要计算的),所以我们只能寻找近似。

三、光线追踪中常见PDF的推导

3.1 均匀半球面采样

问题:在半球面上均匀采样方向,PDF是什么?

推导过程

  1. 立体角 :半球面的总面积是 2 π 2\pi 2π 球面度(整个球面是 4 π 4\pi 4π,半球面是它的一半)

  2. 均匀分布要求 :每个方向的概率密度应该相同,即 p ( ω ) = constant p(\omega) = \text{constant} p(ω)=constant

  3. 归一化条件
    ∫ Ω p ( ω ) d ω = 1 \int_{\Omega} p(\omega) d\omega = 1 ∫Ωp(ω)dω=1
    p ( ω ) ⋅ ∫ Ω d ω = 1 p(\omega) \cdot \int_{\Omega} d\omega = 1 p(ω)⋅∫Ωdω=1
    p ( ω ) ⋅ 2 π = 1 p(\omega) \cdot 2\pi = 1 p(ω)⋅2π=1

  4. 结果
    p ( ω ) = 1 2 π p(\omega) = \frac{1}{2\pi} p(ω)=2π1

代码实现

cpp 复制代码
class UniformHemisphereSampler {
public:
    vec3 sample(float& pdf) {
        // 在半球面上均匀采样
        float z = rand() * 2.0f - 1.0f;  // 实际需要投影到半球
        float r = std::sqrt(std::max(0.0f, 1.0f - z*z));
        float phi = 2.0f * M_PI * rand();
        
        vec3 direction(r * std::cos(phi), r * std::sin(phi), z);
        
        pdf = 1.0f / (2.0f * M_PI);  // 恒定PDF
        
        return direction;
    }
};

3.2 余弦加权采样(重要性采样的一种)

问题 :为什么需要余弦加权?因为渲染方程中有个 cos ⁡ θ \cos\theta cosθ 项,我们希望采样方向更多集中在法线方向。

推导过程

我们希望 PDF 正比于 cos ⁡ θ \cos\theta cosθ:
p ( ω ) = C ⋅ cos ⁡ θ p(\omega) = C \cdot \cos\theta p(ω)=C⋅cosθ

归一化
∫ Ω C cos ⁡ θ d ω = 1 \int_{\Omega} C \cos\theta d\omega = 1 ∫ΩCcosθdω=1

在球坐标中, d ω = sin ⁡ θ d θ d ϕ d\omega = \sin\theta d\theta d\phi dω=sinθdθdϕ,且 cos ⁡ θ sin ⁡ θ = 1 2 sin ⁡ 2 θ \cos\theta \sin\theta = \frac{1}{2}\sin 2\theta cosθsinθ=21sin2θ:

C ∫ 0 2 π ∫ 0 π / 2 cos ⁡ θ sin ⁡ θ d θ d ϕ = 1 C \int_0^{2\pi} \int_0^{\pi/2} \cos\theta \sin\theta d\theta d\phi = 1 C∫02π∫0π/2cosθsinθdθdϕ=1
C ⋅ 2 π ∫ 0 π / 2 1 2 sin ⁡ 2 θ d θ = 1 C \cdot 2\pi \int_0^{\pi/2} \frac{1}{2} \sin 2\theta d\theta = 1 C⋅2π∫0π/221sin2θdθ=1
C ⋅ 2 π ⋅ 1 2 = 1 C \cdot 2\pi \cdot \frac{1}{2} = 1 C⋅2π⋅21=1
C = 1 π C = \frac{1}{\pi} C=π1

结果
p ( ω ) = cos ⁡ θ π p(\omega) = \frac{\cos\theta}{\pi} p(ω)=πcosθ

采样方法(Malley's method):

cpp 复制代码
class CosineWeightedSampler {
public:
    vec3 sample(float& pdf) {
        // 在单位圆盘上均匀采样
        float r = std::sqrt(rand());  // 注意:不是均匀分布,需要变换
        float phi = 2.0f * M_PI * rand();
        
        float x = r * std::cos(phi);
        float y = r * std::sin(phi);
        float z = std::sqrt(std::max(0.0f, 1.0f - x*x - y*y));
        
        // 将圆盘上的点投影到半球面,得到余弦加权分布
        vec3 direction(x, y, z);
        
        // PDF = cosθ/π,其中cosθ就是z
        pdf = z / M_PI;
        
        return direction;
    }
};

3.3 BRDF重要性采样

问题:对于特定材质(如Phong、GGX),如何推导对应的PDF?

Phong材质示例:

Phong材质的BRDF:
f r ( ω o , ω i ) = k d ⋅ 1 π + k s ⋅ n + 2 2 π cos ⁡ n α f_r(\omega_o, \omega_i) = k_d \cdot \frac{1}{\pi} + k_s \cdot \frac{n+2}{2\pi} \cos^n\alpha fr(ωo,ωi)=kd⋅π1+ks⋅2πn+2cosnα

其中 α \alpha α 是反射方向和视线方向的夹角。

推导镜面反射部分的PDF

我们希望 PDF ∝ cos ⁡ n α \cos^n\alpha cosnα。经过归一化:
p ( α ) = n + 1 2 π cos ⁡ n α p(\alpha) = \frac{n+1}{2\pi} \cos^n\alpha p(α)=2πn+1cosnα

验证归一化
∫ 0 2 π ∫ 0 π / 2 n + 1 2 π cos ⁡ n α sin ⁡ α d α d ϕ = 1 \int_0^{2\pi} \int_0^{\pi/2} \frac{n+1}{2\pi} \cos^n\alpha \sin\alpha d\alpha d\phi = 1 ∫02π∫0π/22πn+1cosnαsinαdαdϕ=1

cpp 复制代码
class PhongBRDFSampler {
private:
    float n;  // 光泽度参数
public:
    vec3 sample(const vec3& reflectDir, float& pdf) {
        // 在反射方向周围采样,分布由cosⁿα控制
        float alpha = std::acos(std::pow(rand(), 1.0f/(n+1.0f)));
        float phi = 2.0f * M_PI * rand();
        
        // 构建局部坐标系
        vec3 w = reflectDir;
        vec3 u = normalize(cross(vec3(0,1,0), w));
        vec3 v = cross(w, u);
        
        // 从球坐标转换
        float x = std::sin(alpha) * std::cos(phi);
        float y = std::sin(alpha) * std::sin(phi);
        float z = std::cos(alpha);
        
        vec3 direction = x*u + y*v + z*w;
        
        // PDF = (n+1)/(2π) * cosⁿα
        pdf = (n + 1.0f) / (2.0f * M_PI) * std::pow(std::cos(alpha), n);
        
        return direction;
    }
};

3.4 光源采样PDF

问题:当对光源表面直接采样时,PDF应该是什么?

推导过程

假设在光源表面 A A A 上均匀采样,那么面积上的PDF是:
p A ( p o i n t ) = 1 A p_A(point) = \frac{1}{A} pA(point)=A1

但我们需要的是方向上的PDF p ω ( ω ) p_\omega(\omega) pω(ω)。两者之间的关系是:
p ω ( ω ) = p A ( p o i n t ) ⋅ ∣ d A d ω ∣ p_\omega(\omega) = p_A(point) \cdot \left|\frac{dA}{d\omega}\right| pω(ω)=pA(point)⋅ dωdA

几何关系告诉我们:
d A d ω = r 2 cos ⁡ θ ′ \frac{dA}{d\omega} = \frac{r^2}{\cos\theta'} dωdA=cosθ′r2

其中 r r r 是距离, θ ′ \theta' θ′ 是光源表面法线和光线方向的夹角。

所以:
p ω ( ω ) = 1 A ⋅ r 2 cos ⁡ θ ′ p_\omega(\omega) = \frac{1}{A} \cdot \frac{r^2}{\cos\theta'} pω(ω)=A1⋅cosθ′r2

cpp 复制代码
class LightSampler {
public:
    vec3 sampleDirection(const vec3& shadingPoint, 
                        const Light& light, 
                        float& pdf) {
        // 在光源表面采样一个点
        vec3 lightPoint = light.samplePoint();
        
        // 计算方向
        vec3 direction = lightPoint - shadingPoint;
        float distance = length(direction);
        direction = normalize(direction);
        
        // 计算光源面积上的PDF
        float areaPDF = 1.0f / light.area();
        
        // 计算立体角PDF
        float cosThetaPrime = std::max(0.0f, dot(-direction, light.normal()));
        pdf = areaPDF * distance * distance / cosThetaPrime;
        
        return direction;
    }
};

四、PDF在渲染方程中的完整推导

让我们从头推导完整的蒙特卡洛渲染方程,看看PDF如何自然地出现。

4.1 从渲染方程开始

渲染方程:
L o ( p , ω o ) = L e ( p , ω o ) + ∫ Ω f r ( p , ω i , ω o ) L i ( p , ω i ) cos ⁡ θ i d ω i L_o(p, \omega_o) = L_e(p, \omega_o) + \int_{\Omega} f_r(p, \omega_i, \omega_o) L_i(p, \omega_i) \cos\theta_i d\omega_i Lo(p,ωo)=Le(p,ωo)+∫Ωfr(p,ωi,ωo)Li(p,ωi)cosθidωi

4.2 引入采样分布

假设我们按照某个分布 p ( ω ) p(\omega) p(ω) 采样 N N N 个方向 { ω 1 , ω 2 , . . . , ω N } \{\omega_1, \omega_2, ..., \omega_N\} {ω1,ω2,...,ωN}:

蒙特卡洛估计量:
⟨ L o ⟩ = L e + 1 N ∑ k = 1 N f r ( ω k ) L i ( ω k ) cos ⁡ θ k p ( ω k ) \langle L_o \rangle = L_e + \frac{1}{N} \sum_{k=1}^{N} \frac{f_r(\omega_k) L_i(\omega_k) \cos\theta_k}{p(\omega_k)} ⟨Lo⟩=Le+N1k=1∑Np(ωk)fr(ωk)Li(ωk)cosθk

4.3 证明无偏性

取期望:
E [ ⟨ L o ⟩ ] = L e + ∫ [ f r ( ω ) L i ( ω ) cos ⁡ θ p ( ω ) ] p ( ω ) d ω E[\langle L_o \rangle] = L_e + \int \left[ \frac{f_r(\omega) L_i(\omega) \cos\theta}{p(\omega)} \right] p(\omega) d\omega E[⟨Lo⟩]=Le+∫[p(ω)fr(ω)Li(ω)cosθ]p(ω)dω

化简:
E [ ⟨ L o ⟩ ] = L e + ∫ f r ( ω ) L i ( ω ) cos ⁡ θ d ω E[\langle L_o \rangle] = L_e + \int f_r(\omega) L_i(\omega) \cos\theta d\omega E[⟨Lo⟩]=Le+∫fr(ω)Li(ω)cosθdω

正好等于渲染方程!这就是为什么只要 p ( ω ) > 0 p(\omega) > 0 p(ω)>0 对所有可能的 ω \omega ω 成立,蒙特卡洛估计就是无偏的。

4.4 方差分析

方差公式:
V [ ⟨ L o ⟩ ] = 1 N ∫ [ f r ( ω ) L i ( ω ) cos ⁡ θ ] 2 p ( ω ) d ω − 1 N L o 2 V[\langle L_o \rangle] = \frac{1}{N} \int \frac{[f_r(\omega) L_i(\omega) \cos\theta]^2}{p(\omega)} d\omega - \frac{1}{N} L_o^2 V[⟨Lo⟩]=N1∫p(ω)[fr(ω)Li(ω)cosθ]2dω−N1Lo2

重要结论

  • 当 p ( ω ) ∝ f r ( ω ) L i ( ω ) cos ⁡ θ p(\omega) \propto f_r(\omega) L_i(\omega) \cos\theta p(ω)∝fr(ω)Li(ω)cosθ 时,方差最小
  • 这解释了为什么我们需要根据BRDF和光照来设计PDF

五、实践中的PDF处理技巧

5.1 多PDF的混合(多重重要性采样)

cpp 复制代码
class MISPDF {
private:
    std::vector<std::function<float(vec3)>> pdfs;
    std::vector<float> weights;
    
public:
    float evaluate(vec3 direction) {
        float sum = 0.0f;
        for (size_t i = 0; i < pdfs.size(); i++) {
            sum += weights[i] * pdfs[i](direction);
        }
        return sum;
    }
    
    vec3 sample(float& totalPDF) {
        // 随机选择一个PDF
        int idx = choosePDF();
        
        // 从选中的PDF采样
        float componentPDF;
        vec3 direction = samples[idx](componentPDF);
        
        // 计算总PDF(考虑选择概率)
        totalPDF = weights[idx] * componentPDF;
        
        return direction;
    }
};

5.2 PDF的数值稳定性

cpp 复制代码
class SafePDF {
public:
    static float evaluateWithGuard(float pdf, float minPDF = 1e-6f) {
        return std::max(pdf, minPDF);
    }
    
    static float divideWithGuard(float value, float pdf) {
        if (pdf <= 0.0f) {
            return 0.0f;  // 避免除以零
        }
        return value / pdf;
    }
};

5.3 PDF的验证

cpp 复制代码
class PDFValidator {
public:
    static bool validatePDF(std::function<float(vec3)> pdf, 
                           std::function<vec3(float&)> sampler,
                           int numTests = 1000000) {
        // 验证归一化
        float sum = 0.0f;
        for (int i = 0; i < numTests; i++) {
            float samplePDF;
            vec3 direction = sampler(samplePDF);
            
            // 重要:应该用evaluate验证,而不是用采样返回的PDF
            float evaluatedPDF = pdf(direction);
            
            // 用蒙特卡洛估计积分
            sum += 1.0f / evaluatedPDF;
        }
        
        float estimatedIntegral = sum / numTests;
        float error = std::abs(estimatedIntegral - 1.0f);
        
        return error < 0.01f;  // 允许1%的误差
    }
};

六、总结:PDF选择的艺术

在光线追踪中,选择PDF是一门艺术,需要在几个因素间权衡:

PDF类型 优点 缺点 适用场景
均匀采样 简单、鲁棒 方差大、收敛慢 测试、简单场景
余弦加权 匹配几何项 不匹配光照 漫反射表面
BRDF采样 匹配材质特性 忽略光照分布 光泽表面
光源采样 匹配光照分布 忽略材质特性 小光源场景
多重重要性采样 结合多种优点 实现复杂 通用场景

记住黄金法则

  • PDF必须覆盖所有可能的采样方向(支撑集条件)
  • 尽量让PDF的形状接近被积函数
  • 在复杂场景中,多重重要性采样是最安全的选择

理解PDF的本质,你就掌握了蒙特卡洛光线追踪的精髓。它不仅是数学公式,更是我们对物理世界的理解和模拟的智慧结晶。

相关推荐
Felicia-侧听2 小时前
PDF转长图的2个方法
经验分享·pdf·pdf转图片·pdf转长图
We་ct2 小时前
LeetCode 46. 全排列:深度解析+代码拆解
前端·数据结构·算法·leetcode·typescript·深度优先·回溯
逆境不可逃2 小时前
LeetCode 热题 100 之 763.划分字母区间
算法·leetcode·职场和发展
problc2 小时前
前端预览pdf有哪些方案
前端·pdf
缘空如是2 小时前
基础工具包之pdf操作
java·pdf·搜索和水印
MicroTech20252 小时前
微算法科技(NASDAQ:MLGO)量子PBFT改进技术:重构联盟链共识的效率与安全
科技·算法·重构
程序员小明儿2 小时前
量子计算探秘:从零开始的量子编程与算法之旅 · 第二篇
算法·量子计算
kronos.荒2 小时前
LRUCache缓存实现
算法·缓存·哈希算法
weixin_441003642 小时前
全国大学生英语竞赛真题2026大英赛ABCD类历年真题及答案解析PDF
pdf