[Ray Tracing: The Rest of Your Life] 笔记

前言

开年第一篇博客~ 整理了三四个小时才整理完orz。
这一部分是光线追踪三部曲的最后一部,主要介绍了蒙特卡洛积分、重要性采样等内容。场景上没有什么大的改变,基本上就是在Cornell Box中渲染的,本篇主要在加速收敛,提升渲染效率上下功夫。
参考链接:https://raytracing.github.io/books/RayTracingTheRestOfYourLife.html

什么是蒙特卡洛积分

蒙特卡洛方法是一类通过随机采样来求解问题的算法的统称,要求解的问题是某随机事件的概率或某随机变量的期望。通过随机抽样的方法,以随机事件出现的频率估计其概率,并将其作为问题的解。

蒙特卡洛的基本做法是通过大量重复试验,通过统计频率,来估计频率,从而得到问题的求解。举个例子,如下图所示,矩形内不规则图案的面积 A s h a p e A_{shape} Ashape可由 A ∗ p A * p Ap得出, p p p是矩形内的点在不规则图案中的概率。
在这里插入图片描述

现在重复往矩形范围内随机投射点,样本点有一定概率会落在不规则图形内,重复n次试验,落在不规则图形内的次数为k,则频率为k/n,若样本数量较大,根据伯努利大数定律,则有:
p = A s h a p e A ≈ k n p = \frac{A_{shape}}{A} \approx \frac{k}{n} p=AAshapenk
由此, A s h a p e A_{shape} Ashape的值可由 k A n \frac{kA}{n} nkA估计得出。

用蒙特卡洛方法来计算函数积分,这里给出一般的定义,设 X 1 , X 2 , . . . , X n X_1, X_2, ..., X_n X1,X2,...,Xn是独立同分布的一组样本,它们的概率密度函数为 p d f ( x ) pdf(x) pdf(x),则函数的积分可以表示为:
F ( X ) = 1 n ∑ k = 1 n f ( X k ) p d f ( X k ) F(X) = \frac{1}{n}\sum_{k=1}^{n}\frac{f(X_k)}{pdf(X_{k})} F(X)=n1k=1npdf(Xk)f(Xk)
这就是蒙特卡洛积分的一般等式,其中除以概率密度函数的做法,可以理解为是对样本的统计处理。我们可以证明蒙特卡洛法的积分估计量的正确性:
E ( F ( X ) ) = E [ 1 n ∑ k = 1 n f ( X k ) p d f ( X k ) = 1 n ∑ k = 1 n ∫ f ( x ) p d f ( x ) ⋅ p d f ( x ) d x = 1 n ∑ k = 1 n ∫ f ( x ) d x = ∫ f ( x ) d x E(F(X)) = E[\frac{1}{n}\sum_{k=1}^{n}\frac{f(X_k)}{pdf(X_k)}\\ = \frac{1}{n}\sum_{k=1}^{n}\int \frac{f(x)}{pdf(x)} \cdot pdf(x)dx\\ = \frac{1}{n}\sum_{k=1}^{n}\int f(x)dx\\ = \int f(x)dx E(F(X))=E[n1k=1npdf(Xk)f(Xk)=n1k=1npdf(x)f(x)pdf(x)dx=n1k=1nf(x)dx=f(x)dx
蒙特卡洛法的积分估计值得数学期望等于被积函数的积分真值,证明 F ( X ) F(X) F(X)是无偏估计量。

什么是重要性采样

采样函数 p d f ( x ) pdf(x) pdf(x)的变化趋势与 f ( x ) f(x) f(x)越相近,蒙特卡洛积分的收敛速度就会越快。因此,我们可以把采样点集中在被积函数数值较高的部分,以及来得到更准确更有效率的采样估算。这种非均匀的采样方式被称为重要性采样。

正文记录

接下来,我将顺序罗列正文中我认为重要的部分。

一个简单的蒙特卡洛程序

在做采样时,我们一般可以采用两种随机算法:Monte Carlo以及Las Vegas。

Las Vegas算法可以保证得到正确的结果,但是时间可能会很长,不能保证什么时候可以获得结果。一段采用Las Vegas随机的代码片段如下所示,在一个单位球内随机选取一个点:

inline vec3 random_in_unit_sphere() {
    while (true) {
        auto p = vec3::random(-1,1);
        if (p.length_squared() < 1)
            return p;
    }
}

用Monte Carlo算法可能会得到正确的结果,但是也可能出错,出错时我们将该样本点剔除就好,时间上不需要做过多假设。一个Monte Carlo算法的例子,估计 π \pi π的值:

int main() {
    int N = 100000;
    int inside_circle = 0;
    for (int i = 0; i < N; i++) {
        auto x = random_double(-1,1);
        auto y = random_double(-1,1);
        if (x*x + y*y < 1)
            inside_circle++;
    }
    std::cout << std::fixed << std::setprecision(12);
    std::cout << "Estimate of Pi = " << (4.0 * inside_circle) / N << '\n';
}

Monte Carlo算法一个糟糕的点是,随着采样进行,每一个样本对结果的帮助都小于上一个样本。我们可以通过一种称为分层样本(Jittering)的方式来改进采样。分层法在采样时会在细分的一个个小网格里采样,进行了一部分约束,示意图如下:
在这里插入图片描述

用分层的方式估计 π \pi π值与不用分层方式进行对比,代码和结果如下:

int main() {
    int inside_circle = 0;
    int inside_circle_stratified = 0;
    int sqrt_N = 1000;
    for (int i = 0; i < sqrt_N; i++) {
        for (int j = 0; j < sqrt_N; j++) {
            auto x = random_double(-1,1);
            auto y = random_double(-1,1);
            if (x*x + y*y < 1)
                inside_circle++;
            x = 2*((i + random_double()) / sqrt_N) - 1;
            y = 2*((j + random_double()) / sqrt_N) - 1;
            if (x*x + y*y < 1)
                inside_circle_stratified++;
        }
    }

    std::cout << std::fixed << std::setprecision(12);
    std::cout
        << "普通采样估计的Pi值 = "
        << (4.0 * inside_circle) / (sqrt_N*sqrt_N) << '\n'
        << "分层采样估计的Pi值 = "
        << (4.0 * inside_circle_stratified) / (sqrt_N*sqrt_N) << '\n';
}

结果:

普通采样估计的Pi值 = 3.143392000000

分层采样估计的Pi值 = 3.141532000000

分层采样可以以更好的渐进率收敛。不幸的是,这种优势随着问题维度的增大而减弱(例如,用3D球体体积与正方体体积之比来估计 π \pi π的值,两种方法的差距会变小)。这就是所谓的维度诅咒。

以下是采用分层采样与不进行分层采样渲染Cornell Box场景的对比:

在这里插入图片描述

在这里插入图片描述

上图为不进行分层采样,下图为进行分层采样,这里的分层采样只在光线从相机第一次向场景发射时使用,后续的散射与分层采样无关。教程中说分层采样在高频信息(物体边缘)上的精度比不进行分层采样更高。(虽然我觉得两张图结果差不多…)

如何生成随机变量

这里补充一下概率论中有关累积分布函数(Cumulative Distribution Function, CDF)和概率密度函数(Probability Distribution Function, PDF)的知识。

X X X是一个随机变量, x x x是任意实数,函数 c d f ( x ) = P { X ≤ x } , − ∞ < x < + ∞ cdf(x) = P\left\{ X \leq x\right\}, -\infty < x < +\infty cdf(x)=P{Xx},<x<+称为 X X X累计分布函数

如果对于随机变量 X X X的累积分布函数 p d f ( x ) pdf(x) pdf(x),存在非负函数 p d f ( x ) pdf(x) pdf(x),使对于任意实数 x x x,有:
c d f ( x ) = ∫ − ∞ x p d f ( t ) d t cdf(x) = \int_{-\infty}^{x}pdf(t)dt cdf(x)=xpdf(t)dt
则称 X X X为连续型随机变量,其中函数 p d f ( x ) pdf(x) pdf(x)称为 X X X的概率密度函数,简称概率密度

那么,如何生成一个随机变量呢?

X X X是一个随机变量,它的概率密度函数为 p d f ( x ) pdf(x) pdf(x),它的累积密度函数可以表示为:
c d f ( x ) = ∫ − ∞ x p d f ( t ) d t cdf(x) = \int_{-\infty}^{x}pdf(t)dt cdf(x)=xpdf(t)dt
计算符合该概率分布的随机数方法如下所示:

  • 对于概率密度函数 p d f ( x ) pdf(x) pdf(x),计算它的累积分布函数 c d f ( x ) cdf(x) cdf(x),如上面的等式所示;
  • 计算 c d f ( x ) cdf(x) cdf(x)的反函数 c d f − 1 ( x ) cdf^{-1}(x) cdf1(x)
  • 对于一个范围在 [ 0 , 1 ] [0, 1] [0,1]之间均匀分布的随机数 ξ \xi ξ,则 X = c d f − 1 ( ξ ) X = cdf^{-1}(\xi) X=cdf1(ξ),就是符合该概率分布的随机数。

举个例子,区间 [ 0 , 1 ] [0, 1] [0,1]之间的概率密度函数 p d f ( x ) = ( n + 1 ) x n , x ∈ [ 0 , 1 ] pdf(x) = (n + 1)x^n, x\in [0, 1] pdf(x)=(n+1)xn,x[0,1],计算其累积分布函数:
c d f ( x ) = ∫ 0 x ( n + 1 ) t n d t = x n + 1 cdf(x) = \int_{0}^{x}(n + 1)t^ndt = x^{n+1} cdf(x)=0x(n+1)tndt=xn+1
其反函数为
c d f − 1 ( x ) = x n + 1 cdf^{-1}(x) = \sqrt[n+1]x cdf1(x)=n+1x
则符合该概率分布的随机数为
X = ξ n + 1 X = \sqrt[n+1]{\xi} X=n+1ξ

重要性采样

正如前言中提到的,利用蒙特卡洛法进行积分的计算公式如下:
F ( X ) = 1 n ∑ k = 1 n f ( X k ) p d f ( X k ) F(X) = \frac{1}{n}\sum_{k=1}^{n}\frac{f(X_k)}{pdf(X_{k})} F(X)=n1k=1npdf(Xk)f(Xk)
当进行采样的分布律 p d f pdf pdf与被采样函数越接近时,这个采样便会越快收敛。我们可以把采样点集中在被积函数数值较高的部分,以及来得到更准确更有效率的采样估算。这种非均匀的采样方式被称为重要性采样。

举个例子,我们对积分 ∫ 0 2 x 2 d x \int_0^2x^2dx 02x2dx进行估计,采用均匀采样和重要性采样两种方式来进行蒙特卡洛积分。这个积分的值为 8 3 \frac{8}{3} 38,换成小数为2.666666666666…

均匀采样

此时,我们令 p d f ( x ) = 1 2 , x ∈ [ 0 , 2 ] pdf(x) = \frac{1}{2}, x \in [0, 2] pdf(x)=21,x[0,2],计算其累积分布函数:
c d f ( x ) = ∫ 0 x p d f ( t ) d t = ∫ 0 x 1 2 d t = x 2 cdf(x) = \int_0^xpdf(t)dt = \int_0^x\frac{1}{2}dt = \frac{x}{2} cdf(x)=0xpdf(t)dt=0x21dt=2x
其反函数为
c d f − 1 ( x ) = 2 x cdf^{-1}(x) = 2x cdf1(x)=2x
因此符合该概率分布的随机数为
X = 2 ξ , ξ ∈ [ 0 , 1 ] X = 2\xi, \xi \in [0, 1] X=2ξ,ξ[0,1]
这里的计算代码如下:

double f(double d) {
    return 2.0 * d;
}

double pdf(double x) {
    return 0.5;
}

int main() {
    int N = 1000000;
    auto sum = 0.0;
    for (int i = 0; i < N; i++) {
        auto x = f(random_double());
        sum += x*x / pdf(x);
    }
    std::cout << std::fixed << std::setprecision(12);
    std::cout << "I = " << sum / N << '\n';
}

运算结果:

I = 2.666942705288

重要性采样

我们让进行采样的分布律 p d f pdf pdf与被采样函数接近,设 p d f ( x ) = c x 2 , x ∈ [ 0 , 2 ] pdf(x) = cx^2, x \in [0, 2] pdf(x)=cx2,x[0,2],于是有
∫ 0 2 c x 2 d x = 1 c ∫ 0 2 x 2 d x = c ∗ 8 3 = 1 c = 3 8 \int_0^2cx^2dx = 1\\ c\int_0^2x^2dx = c *\frac{8}{3} = 1\\ c = \frac{3}{8} 02cx2dx=1c02x2dx=c38=1c=83
此时我们得到一个与被采样函数接近的分布函数 p d f ( x ) = 3 8 x 2 , x ∈ [ 0 , 2 ] pdf(x) = \frac{3}{8}x^2, x \in [0, 2] pdf(x)=83x2,x[0,2],计算其累积分布函数
c d f ( x ) = ∫ 0 x 3 8 t 2 d t = x 3 8 cdf(x) = \int_0^x\frac{3}{8}t^2dt = \frac{x^3}{8} cdf(x)=0x83t2dt=8x3
其反函数为
c d f − 1 ( x ) = 2 x 3 cdf^{-1}(x) = 2\sqrt[3]x cdf1(x)=23x
因此符合该概率分布的随机数为
X = 2 ξ 3 , ξ ∈ [ 0 , 1 ] X = 2\sqrt[3]\xi, \xi \in [0, 1] X=23ξ ,ξ[0,1]
这里的计算代码如下:

double f(double d) {
    return 2.0 * pow(d, 1.0/3.0);
}

double pdf(double x) {
    return (3.0/8.0) * x*x;
}

int main() {
    int N = 1;
    auto sum = 0.0;
    for (int i = 0; i < N; i++) {
        auto x = f(random_double());
        sum += x*x / pdf(x);
    }
    std::cout << std::fixed << std::setprecision(12);
    std::cout << "I = " << sum / N << '\n';
}

运算结果:

I = 2.666666666667

在结果中,我们可以明显感受到重要性采样的威力。(何止更快,只需要迭代1次就能得到正确结果)

生成半球面上的随机方向

这一小节是生成半球面上的随机方向。

首先是生成相对于Z轴的随机方向,为了简单起见,假设z是平面的法线。

首先我们考虑半球上的均匀随机方向生成,设此时随机方向的概率密度函数为 p d f ( w ) = c pdf(w) = c pdf(w)=c,其中 w w w是立体角,极坐标与立体角的对应关系有
∫ Ω 2 p ( w ) d w = ∫ Ω 2 s i n ( θ ) d ϕ d θ \int_{\Omega^2}p(w)dw = \int_{\Omega^2}sin(\theta)d\phi d\theta Ω2p(w)dw=Ω2sin(θ)dϕdθ
于是有
∫ Ω 2 p ( w ) d w = c ∫ 0 π 2 s i n ( θ ) ∫ 0 2 π d ϕ d θ = 2 π c = 1 \int_{\Omega^2}p(w)dw = c\int_0^{\frac{\pi}{2}}sin(\theta) \int_0^{2\pi}d\phi d\theta = 2\pi c = 1 Ω2p(w)dw=c02πsin(θ)02πdϕdθ=2πc=1
c = 1 / 2 π c = 1 / 2\pi c=1/2π,那么
p d f ( θ , ϕ ) = s i n θ / 2 π pdf(\theta, \phi) = sin\theta / 2\pi pdf(θ,ϕ)=sinθ/2π
可以计算出
p d f ( θ ) = ∫ 0 2 π p ( θ , ϕ ) d ϕ = s i n θ pdf(\theta) = \int_0^{2\pi}p(\theta, \phi)d\phi = sin\theta pdf(θ)=02πp(θ,ϕ)dϕ=sinθ
再根据条件概率的公式,可得
p d f ( ϕ ∣ θ ) = p d f ( θ , ϕ ) p d f ( θ ) = 1 2 π pdf(\phi|\theta) = \frac{pdf(\theta, \phi)}{pdf(\theta)} = \frac{1}{2\pi} pdf(ϕθ)=pdf(θ)pdf(θ,ϕ)=2π1
分别计算累积分布函数
c d f ( θ ) = ∫ 0 θ s i n t d t = 1 − c o s θ c d f ( ϕ ∣ θ ) = ∫ 0 ϕ 1 2 π d t = ϕ 2 π cdf(\theta) = \int_0^\theta sintdt = 1 - cos\theta \\ cdf(\phi | \theta) = \int_0^\phi \frac{1}{2\pi}dt = \frac{\phi}{2\pi} cdf(θ)=0θsintdt=1cosθcdf(ϕθ)=0ϕ2π1dt=2πϕ
根据之前介绍的随机变量生成,设 ξ 1 , ξ 2 \xi _1, \xi _2 ξ1,ξ2 [ 0 , 1 ] [0, 1] [0,1]之间均匀分布的随机数,可以用 1 − ξ 1 1 - \xi _1 1ξ1替换 ξ 1 \xi _1 ξ1,我们能够求出:
θ = c o s − 1 ξ 1 , ϕ = 2 π ξ 2 \theta = cos^{-1}\xi_1, \phi = 2\pi \xi_2 θ=cos1ξ1,ϕ=2πξ2
代入极坐标,可得:
x = s i n θ c o s ϕ = c o s ( 2 π ξ 2 ) 1 − ξ 1 2 y = s i n θ s i n ϕ = s i n ( 2 π ξ 2 ) 1 − ξ 1 2 z = c o s θ = ξ 1 x = sin\theta cos\phi = cos(2\pi \xi_2)\sqrt{1 - \xi_1^2}\\ y = sin\theta sin\phi = sin(2\pi \xi_2)\sqrt{1 - \xi_1^2}\\ z = cos\theta = \xi_1 x=sinθcosϕ=cos(2πξ2)1ξ12 y=sinθsinϕ=sin(2πξ2)1ξ12 z=cosθ=ξ1
除此之外,还有一种用cos加权的随机生成方法,此时采用的是Lambertian光照模型。这时 p d f ( w ) pdf(w) pdf(w) c o s θ cos\theta cosθ正相关,计算可得 p d f ( θ , ϕ ) = 1 π c o s θ s i n θ pdf(\theta, \phi) = \frac{1}{\pi}cos\theta sin\theta pdf(θ,ϕ)=π1cosθsinθ,用相似的方法,可以计算出
θ = c o s − 1 ξ 1 , ϕ = 2 π ξ 2 \theta = cos^{-1}\sqrt\xi_1, \phi = 2\pi \xi_2 θ=cos1ξ 1,ϕ=2πξ2
代入极坐标,可得:
x = s i n θ c o s ϕ = c o s ( 2 π ξ 2 ) 1 − ξ 1 y = s i n θ s i n ϕ = s i n ( 2 π ξ 2 ) 1 − ξ 1 z = c o s θ = ξ 1 x = sin\theta cos\phi = cos(2\pi \xi_2)\sqrt{1 - \xi_1}\\ y = sin\theta sin\phi = sin(2\pi \xi_2)\sqrt{1 - \xi_1}\\ z = cos\theta = \sqrt{\xi_1} x=sinθcosϕ=cos(2πξ2)1ξ1 y=sinθsinϕ=sin(2πξ2)1ξ1 z=cosθ=ξ1

正交基

这一部分主要是让上一章随机生成的方法支持生成任意表面的法向量。

相对坐标系由一个原点 O O O和三个基方向 u 、 v 、 w u、v、w uvw来定义的。

给定一个表面法线向量 n n n,如何构建这个相对坐标系?

我们不需要原点,因此只需要求两外两个互相垂直的向量。

  1. 选择一个辅助向量 a a a,这个向量不能与 n n n垂直,通常可以用y轴正方向,如果 n n n接近与y轴平行,则用将 a a a设置为x轴正方向,即:
if (fabs(n.x()) > 0.9)
    a = vec3(0, 1, 0);
else
    a = vec3(1, 0, 0);
  1. 得出第二个轴向量 s s s s s s的方向可由 n n n a a a叉乘得出。最后一个轴向量 t t t,可由 n n n s s s叉乘得出,即:
vec3 s = unit_vector(cross(n, a));
vec3 t = cross(n, s);

一旦我们得到了正交基 s s s t t t n n n,我们在获取一个相对z轴随机的向量 ( x , y , z ) (x, y, z) (x,y,z)之后,便可以获得一个相对于 n n n轴的随机向量:
R a n d o m v e c t o r = x s + y t + z n Randomvector = x\textbf s + y\textbf t + z\textbf n Randomvector=xs+yt+zn
可以单独构造一个表示正交基的类,这个类的核心代码如下:

#ifndef ONB_H
#define ONB_H

#include "rtweekend.h"

class onb {
  public:
    onb() {}

    vec3 operator[](int i) const { return axis[i]; }
    vec3& operator[](int i) { return axis[i]; }

    vec3 u() const { return axis[0]; }
    vec3 v() const { return axis[1]; }
    vec3 w() const { return axis[2]; }

    vec3 local(double a, double b, double c) const {
        return a*u() + b*v() + c*w();
    }

    vec3 local(const vec3& a) const {
        return a.x()*u() + a.y()*v() + a.z()*w();
    }

    void build_from_w(const vec3& w) {
        vec3 unit_w = unit_vector(w);
        vec3 a = (fabs(unit_w.x()) > 0.9) ? vec3(0,1,0) : vec3(1,0,0);
        vec3 v = unit_vector(cross(unit_w, a));
        vec3 u = cross(unit_w, v);
        axis[0] = u;
        axis[1] = v;
        axis[2] = unit_w;
    }

  public:
    vec3 axis[3];
};


#endif

生成指向光源的随机方向

我们可以让所有的散射光线都努力指向光源的方向,在指向光源方向途中,如果折射的光线射入物体表面之下,或者方向和光源平面平行,则丢弃。

设光源表面的面积为 A A A,我们假设光线打到光源上的概率是均匀的,则打在平面上的每一点的概率密度为 1 / A 1 / A 1/A

在这里插入图片描述

对于光线打向的这一小块区域 d A dA dA,采样这块区域的概率为 p q ( q ) ⋅ d A p_q(q)\cdot dA pq(q)dA,其中 p q ( q ) = 1 / A p_q(q) = 1/A pq(q)=1/A。而在光线的发射球面上,采样球表面小片区域 d w dw dw的概率为 p ( w ) ⋅ d w p(w)\cdot dw p(w)dw d w dw dw d A dA dA有如下的几何关系:
d w = d A ⋅ c o s ( θ ) d i s t a n c e 2 ( p , q ) dw = \frac{dA\cdot cos(\theta)}{distance^2(p, q)} dw=distance2(p,q)dAcos(θ)
由于在采样中 d w dw dw d A dA dA的概率必须相等,因此有
p ( w ) ⋅ d w = p q ( q ) ⋅ d A p ( w ) ⋅ d A ⋅ c o s ( θ ) d i s t a n c e 2 ( p , q ) = p q ( q ) ⋅ d A p(w)\cdot dw = p_q(q)\cdot dA\\ p(w)\cdot \frac{dA\cdot cos(\theta)}{distance^2(p,q)} = p_q(q)\cdot dA\\ p(w)dw=pq(q)dAp(w)distance2(p,q)dAcos(θ)=pq(q)dA
其中 p q ( q ) p_q(q) pq(q)的值为 1 A \frac{1}{A} A1,因此我们有
p ( w ) = d i s t a n c e 2 ( p , q ) c o s ( θ ) ⋅ A p(w) = \frac{distance^2(p,q)}{cos(\theta)\cdot A} p(w)=cos(θ)Adistance2(p,q)
如何生成这样的射线

随机在光源平面上取一个点,与当前表面的点构成射线,如果射线在表面点和光源之间没有别的遮挡,那么就可以成功发出。这条射线的 p d f pdf pdf已由上式推导计算出。

直接生成指向光源的射线,其渲染结果如下:

在这里插入图片描述

这样做的效果是,牺牲了折射的次数,好处是噪声比较少。

构建射向球面的随机方向

这一部分构建从一个点到球面的射线的均匀随机采样。

参考生成半球面上的随机方向,我们要进行均匀采样,设 p d f ( w ) = C pdf(w) = C pdf(w)=C因此有
∫ Ω 2 p d f ( w ) d w = C ∫ 0 θ m a x s i n ( θ ) ∫ 0 2 π d ϕ d θ = 2 π C ( 1 − c o s θ m a x ) = 1 \int_{\Omega^2}pdf(w)dw = C\int_0^{\theta_{max}}sin(\theta)\int_0^{2\pi}d\phi d\theta = 2\pi C(1 - cos\theta_{max}) = 1 Ω2pdf(w)dw=C0θmaxsin(θ)02πdϕdθ=2πC(1cosθmax)=1
其中 w w w是立体角, θ , ϕ \theta, \phi θ,ϕ为极坐标, θ m a x \theta_{max} θmax为光线能够打到球面上时, θ \theta θ的最大值,如下图所示:

在这里插入图片描述

接上面的公式,因此有
p d f ( w ) = C = 1 2 π ⋅ ( 1 − c o s θ m a x ) p d f ( θ , ϕ ) = C ⋅ s i n θ pdf(w) = C = \frac{1}{2\pi \cdot (1 - cos\theta _{max})}\\ pdf(\theta, \phi) = C\cdot sin\theta pdf(w)=C=2π(1cosθmax)1pdf(θ,ϕ)=Csinθ
那么
p d f ( θ ) = ∫ 0 2 π p d f ( θ , ϕ ) d ϕ = 2 π C ⋅ s i n θ p d f ( ϕ ∣ θ ) = p d f ( θ , ϕ ) p d f ( θ ) = 1 2 π pdf(\theta) = \int_0^{2\pi}pdf(\theta, \phi)d\phi = 2\pi C\cdot sin\theta\\ pdf(\phi |\theta) = \frac{pdf(\theta, \phi)}{pdf(\theta)} = \frac{1}{2\pi} pdf(θ)=02πpdf(θ,ϕ)dϕ=2πCsinθpdf(ϕθ)=pdf(θ)pdf(θ,ϕ)=2π1
分别计算累积分布函数
c d f ( θ ) = ∫ 0 θ 2 π C ⋅ s i n t d t = 2 π C ( 1 − c o s θ ) c d f ( ϕ ∣ θ ) = ∫ 0 ϕ 1 2 π d t = ϕ 2 π cdf(\theta) = \int_0^\theta 2\pi C\cdot sintdt = 2\pi C(1 - cos\theta)\\ cdf(\phi |\theta) = \int_0^\phi \frac{1}{2\pi}dt = \frac{\phi}{2\pi}\\ cdf(θ)=0θ2πCsintdt=2πC(1cosθ)cdf(ϕθ)=0ϕ2π1dt=2πϕ
ξ 1 , ξ 2 \xi_1, \xi_2 ξ1,ξ2 [ 0 , 1 ] [0, 1] [0,1]之间均匀分布的随机数,我们有
θ = c o s − 1 ( 1 − ξ 1 2 π C ) = c o s − 1 ( 1 + ξ 1 ⋅ ( c o s θ m a x − 1 ) ) ϕ = 2 π ξ 2 \theta = cos^{-1}(1 - \frac{\xi_1}{2\pi C}) = cos^{-1}(1 + \xi_1 \cdot(cos\theta_{max} - 1))\\ \phi = 2\pi \xi_2 θ=cos1(12πCξ1)=cos1(1+ξ1(cosθmax1))ϕ=2πξ2
因此我们有下面的式子:
z = c o s θ = 1 + ξ 1 ⋅ ( c o s θ m a x − 1 ) x = c o s ϕ ⋅ s i n θ = c o s ( 2 π ⋅ ξ 2 ) ⋅ 1 − z 2 y = s i n ϕ ⋅ s i n θ = s i n ( 2 π ⋅ ξ 2 ) ⋅ 1 − z 2 z = cos\theta = 1 + \xi_1 \cdot (cos\theta _{max} - 1)\\ x = cos\phi\cdot sin\theta = cos(2\pi \cdot \xi_2) \cdot \sqrt{1 - z^2}\\ y = sin\phi\cdot sin\theta = sin(2\pi \cdot \xi_2) \cdot \sqrt{1 - z^2} z=cosθ=1+ξ1(cosθmax1)x=cosϕsinθ=cos(2πξ2)1z2 y=sinϕsinθ=sin(2πξ2)1z2
对玻璃球采用重要性采样,渲染结果如下:
在这里插入图片描述

密度混合

我们可以创建任何PDF的线性混合,以生成随机变量。PDF的任何加权平均值也是PDF。只要权重为正并且加起来为1,我们就得到了一个新的PDF分布。

例如,在生成随机散射光线时,我们提到了两种方式,一种是让散射光线随机射向光源,另一种是让散射光线根据表面法线随机散射(Lambertian光照模型)。我们也可以将两种方式结合,让光线既有可能随机射向光源,又可能按照Lambertian光照模型进行散射。这种思路的伪代码如下:

if (random_double() < 0.5)
    pick direction according to pSurface
else
    pick direction according to pLight

实现这种密度混合的代码如下:

class mixture_pdf : public pdf {
  public:
    mixture_pdf(shared_ptr<pdf> p0, shared_ptr<pdf> p1) {
        p[0] = p0;
        p[1] = p1;
    }

    double value(const vec3& direction) const override {
        return 0.5 * p[0]->value(direction) + 0.5 *p[1]->value(direction);
    }

    vec3 generate() const override {
        if (random_double() < 0.5)
            return p[0]->generate();
        else
            return p[1]->generate();
    }

  private:
    shared_ptr<pdf> p[2];
};

最后,展示一下最终的渲染结果:

在这里插入图片描述

ps:文中没有讲到的有利于采样的内容:低差异序列。

完整代码

链接:https://pan.baidu.com/s/1n3EcaPQoZcG6dyRyNWuhUA?pwd=itd6
提取码:itd6
–来自百度网盘超级会员V6的分享

参考

https://raytracing.github.io/books/RayTracingTheRestOfYourLife.html

https://zhuanlan.zhihu.com/p/146144853

https://dreamerchen.com/post/ImportanceSampling.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/289127.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

踩坑记录-安装nuxt3报错:Error: Failed to download template from registry: fetch failed;

报错复现 安装nuxt3报错&#xff1a;Error: Failed to download template from registry: fetch failednpx nuxi init nuxt-demo 初始化nuxt 项目 报错 Error: Failed to download template from registry: fetch faile 解决方法 配置hosts Mac电脑&#xff1a;/etc/hostswin电…

java StringBuilder对比String的优点和15个经典案例

文章目录 区别&#xff1a;1. 字符串拼接2. 构建动态查询语句3. 格式化输出4. 字符串反转5. 插入字符串6. 删除子串7. 字符串替换8. 构建复杂JSON或XML结构9. 处理用户输入的大量数据10. 动态生成HTML页面11. 处理字符串格式转换12. 实现字符串缓冲区13. 拼接大量字符串数组元素…

LLVM(简介)

历史 LLVM(low level virtual machine)起源于伊利诺伊大学的一个编译器实验项目&#xff0c;目前已经发展成一个集编译器和工具链为一体的商业开源项目&#xff0c;因此其英文名称的含义被扩大&#xff0c;不再仅仅是字面意思。其创始人为 Chris Lattner。LLVM项目遵循的开源许…

java数据结构与算法刷题-----LeetCode746. 使用最小花费爬楼梯

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 很多人觉得动态规划很难&#xff0c;但它就是固定套路而已。其实动态规划只…

嵌入式系统(二)单片机基础 | 单片机特点 内部结构 最小系统 电源 晶振 复位

上一篇文章我们介绍了嵌入式系统 嵌入式系统&#xff08;Embedded System&#xff09;是一种特定用途的计算机系统&#xff0c;它通常嵌入在更大的产品或系统中&#xff0c;用于控制、监测或执行特定的任务。这些系统通常由硬件和软件组成&#xff0c;旨在满足特定的需求&…

SQL窗口函数大小详解

窗口大小 OVER 子句中的 frame_clause 选项用于指定一个滑动的窗口。窗口总是位于分区范围之内&#xff0c;是分区的一个子集。指定了窗口之后&#xff0c;分析函数不再基于分区进行计算&#xff0c;而是基于窗口内的数据进行计算。 指定窗口大小的语法如下&#xff1a; ROWS…

了解并使用django-rest-framework-jwt

一 JWT认证 在用户注册或登录后&#xff0c;我们想记录用户的登录状态&#xff0c;或者为用户创建身份认证的凭证。我们不再使用Session认证机制&#xff0c;而使用Json Web Token&#xff08;本质就是token&#xff09;认证机制。 Json web token (JWT), 是为了在网络应用环…

2023年12月GESP C++七级编程题转Python真题解析

七、2023年12月GESP C(Python)七级编程题 2023年12月GESP Python最高六级&#xff0c;但C与Python同级编程题相同。本篇引用2023年12月GESPC七级编程题&#xff0c;用Python实现。 【七级编程题1】 【试题名称】&#xff1a;商品交易 时间限制&#xff1a;1.0 s 内存限制&…

CMake入门教程【核心篇】引用子模块.cmake文件(include)

&#x1f608;「CSDN主页」&#xff1a;传送门 &#x1f608;「Bilibil首页」&#xff1a;传送门 &#x1f608;「本文的内容」&#xff1a;CMake入门教程 &#x1f608;「动动你的小手」&#xff1a;点赞&#x1f44d;收藏⭐️评论&#x1f4dd; 文章目录 include子模块举个例…

四、HTML 属性

属性是 HTML 元素提供的附加信息。 一、HTML 属性 HTML 元素可以设置属性属性可以在元素中添加附加信息属性一般描述于开始标签属性总是以名称/值对的形式出现&#xff0c;比如&#xff1a;name"value"。 二、 属性实例 HTML 链接由 <a> 标签定义。链接的地…

HC-05蓝牙模块--------手机与STM32通信(代码编写)(上位机配置)保姆级教程

⏩ 大家好哇&#xff01;我是小光&#xff0c;嵌入式爱好者&#xff0c;一个想要成为系统架构师的大三学生。 ⏩因为之前无论是电赛还是做项目&#xff0c;都用到了蓝牙模块&#xff0c;如&#xff1a;手机和stm32的通信&#xff0c;电赛中的双车通信&#xff0c;还是遥感小车的…

教育机构培训系统小程序功能清单

制作一款适合自己的教育机构培训系统小程序&#xff0c;可以为学员提供更便捷的学习体验&#xff0c;同时提高机构的教学效率。今天将详细介绍如何使用乔拓云平台制作教育机构培训系统小程序。 在浏览器搜索乔拓云&#xff0c;登录到后台&#xff0c;选择教育系统并点击进入。在…

使用MQTT.JS创建一个网页版的MQTT客户端

一、MQTT.JS介绍 MQTT.js 是一个开源的 MQTT 协议的客户端库&#xff0c;使用 JavaScript 编写&#xff0c;主要用于 Node.js 和 浏览器环境中。是JavaScript 环境下的 MQTT 客户端库。可以用于微信小程序、支付宝小程序等定制浏览器环境。 我们可以直接在HTML文件中进行调用…

静态代理、JDK动态代理、CGLIB动态代理以及JDK和CGLIB动态代理的区别

代理 什么是代理&#xff1f;两个设计原则三要素 静态代理静态代理的实现定义接口-定义行为静态代理 -> 目标角色静态代理-> 代理角色静态代理测试 特点 JDK动态代理newProxyInstance获取代理对象通过代理对象实现目标对象的功能特点 Java动态代理类中的invoke是怎么调用…

P8 RV1126推流 —— 摄像头和咪头模块初始化

前言 从本章开始我们将要学习嵌入式音视频的学习了 &#xff0c;使用的瑞芯微的开发板 &#x1f3ac; 个人主页&#xff1a;ChenPi &#x1f43b;推荐专栏1: 《C_ChenPi的博客-CSDN博客》✨✨✨ &#x1f525; 推荐专栏2: 《Linux C应用编程&#xff08;概念类&#xff09;_C…

对偶问题的基本性质

写于&#xff1a;2024年1月3日晚 修改于&#xff1a; 原规划与对偶规划 原规划对偶规划 max ⁡ z C T X s.t. { A X ≤ b , 其中 X ( m ∗ 1 ) X ≥ 0 \begin{aligned} & \max \mathrm{z}\mathbf{C}^T \mathbf{X} \\ & \text { s.t. }\left\{\begin{array}{l}\mat…

RFID在物流、供应链管理、工业自动化等领域的广泛应用

随着物联网技术的不断发展&#xff0c;RFID&#xff08;无线射频识别&#xff09;技术作为一种自动识别和跟踪技术&#xff0c;在物流、供应链管理、工业自动化等领域得到了广泛应用。本文将介绍RFID解决方案及其应用场景。 一、RFID技术概述 RFID是一种通过无线电波通信&…

Apache的配置与应用

目录 1、Apache简介 2、Apache连接保持 3、Apache的访问控制 3.1、客户机地址限制 3.2、用户授权限制 &#xff08;1&#xff09;创建用户认证数据文件 &#xff08;2&#xff09;添加用户授权配置 &#xff08;3&#xff09;验证用户访问授权 4、Apache日志分割 4…

ALSA学习(5)——ASoC架构中的Machine

参考博客&#xff1a;https://blog.csdn.net/DroidPhone/article/details/7231605 &#xff08;以下内容皆为原博客转载&#xff09; 文章目录 一、注册Platform Device二、注册Platform Driver三、初始化入口soc_probe() 一、注册Platform Device ASoC把声卡注册为Platform …

FreeRTOS——计数型信号量知识总结及实战

1计数型信号量概念 1&#xff09;计数型信号量相当于队列长度大于1 的队列&#xff0c;因此计数型信号量能够容纳多个资源 2&#xff09;适用场景&#xff1a; 事件计数&#xff1a; 当每次事件发生后&#xff0c;在事件处理函数中释放计数型信号量&#xff08;计数值1&#x…