如下为目前和本节课所学的所有渲染方法之间的关系图:

0 引入——辐射度量学概述 Radiometry — Motivation
闫老师学习方法:为什么学它(WHY)、它是什么(WHAT)、它是如何运作的(HOW),学了之后 HOW 其实是最不重要的
- 在实现Blinn-Phong着色模型时,会设置一个数作为”光照强度“,这个数的真实物理意义我们并不清楚(其实是 Irradiance 辐照度)
- Whitted风格光线追踪不是一个物理正确的结果,其中有很多简化;辐射度量学可以帮助我们进行物理准确的光照计算,它同样也是”路径追踪“的基础
- 辐射度量学给出了一系列度量方法和单位描述光照 Measurement system and units for illumination
- 辐射度量学精确的定义了光在空间中的属性:辐射通量 Radiant flux、强度 intensity、辐照度 intensity、辐射度 radiance
1. 辐射度量学相关物理量
1.1 Radiant Energy 辐射能
定义:Radiant energy is the energy of electromagnetic radiation.光源辐射的能量(图形学里很少用此概念,一般所说的能量都是辐射通量,即是在单位时间内讨论的)
符号:Q;单位:焦耳

1.2 Radiant Flux(Power) 辐射通量 (辐射功率)
定义:是单位时间内发射、反射、传输或接收的能量 Radiant flux (power) is the energy emitted, reflected, transmitted or received, per unit time.可 以理解成流过的光功率
符号:Φ;单位:瓦特、流明

Flux可以看作是单位时间内通过传感器的光子的数量 #photons flowing through a sensor in unit time

1.3 Radiant Intensity 辐射强度 I
Why? 光不只是“总共发了多少”,还要知道:它往哪个方向发得多,往哪个方向发得少。
定义:每单位立体角,产生或接收的辐射通量。符号:I;单位:瓦特/sr、lm/sr、candela、cd
立体角(solid angle)是有方向的,所以辐射强度是一个方向有关的属性

1.3.1 弧度和角度
- 用弧度定义的好处是圆放大或缩小不影响角度,因为弧长和半径会同步的放大和缩小
- 角度θ:圆弧的弧长与半径之比\[ \theta = \frac{l}{r} \]
- 圆的总的弧度 (radian) 为 \(2\pi\),对应角度为 \(360^\circ\)

1.3.2 立体角 Solid Angles
Why? 在三维空间里,很多物理量都和“方向范围”有关,但普通角度只能描述二维张角,没法描述空间里一整束方向的大小。所以要定义立体角,来回答这一束方向在空间中占了多大一块。
- 手电筒光束很窄,立体角小
- 灯泡朝四周发光,立体角大
what? 球体上一定区域面积与半径平方的比 ratio of subtended area on sphere to radius squared 立体角 \(\Omega = \frac{A}{r^2}\)
The unit of solid angle is the steradian (sr).立体角的单位是球面度。
球体的总的球面度 (steradian) 为 \(4\pi = \frac{4\pi r^2}{r^2}\),球表面积公式S=4πr2

注:r 的定义是:观察点(立体角的顶点)到被观察那个面积微元的距离
1.3.3 单位立体角 Differential Solid Angles
球面上单位面积与半径平方之比,\(d\omega = \frac{dA}{r^2}\) 球面的单位面积 \(dA\) 比较好求,求出弧长 \(AB\) 和 \(AC\) 即可得到。根据圆的弧长公式 \(l = r\theta\),球面的单位面积 \(dA\) 可由对应的弧长相乘得出。

总的立体角之和 = 球面上无数个单位立体角的加和,即二重积分
\[ \iint \sin\theta \, d\theta d\varphi \] 积分限也比较好理解:\(\theta: 0 \to \pi\),一个半圆弧,\(\phi: 0 \to 2\pi\),用半圆转一整圈得到球面 \[ \begin{aligned} \Omega &= \int_{S^2} d\omega \\ &= \int_0^{2\pi} \int_0^\pi \sin\theta \, d\theta d\phi \\ &= 4\pi \end{aligned} \] 同样可以验证立体角最大是\(4\pi\)
通常把 \(\omega\) 当做方向向量来理解,这样比较好理解 intensity,光源向任意方向辐射的强度

如果点光源向三维空间中均匀的辐射出能量,怎么描述强度?
\[ I = \frac{\Phi}{4\pi} \] – \(\Phi\):点光源单位时间内,向三维空间中辐射出的能量 – \(4\pi\):整个三维空间的总立体角 其比值就是单位立体角上的辐射通量。
1.4 Irradiance 辐照度 E
why? 只定义 Intensity 还不够,它只描述:光源往某个方向送出去多少光。但它没有直接回答:表面这一小块地方,真正接收到了多少光?所以需要和 Intensity 对应地,再定义一个“接收端版本”的量,这就是 Irradiance。
表示入射到表面点处单位面积上的能量,当光线不是垂直入射,需要乘cos入射角 – 光线方向与平面法线的夹角,因为斜入射时同样能量会分摊到更大的表面面积上。可见下例
The irradiance is the power per (perpendicular/projected) unit area incident on a surface point.
符号:E,单位:W/m2、lm/m2、lux
Irradiance和intensity是同级不同角度的概念,辐照度是辐射通量/面积,辐射强度是辐射通量/立体角
- 辐射强度的方向和立体角相同
- 辐照度从定义可知本身不具有方向,但光线和单位面积不垂直要投影到垂直的方向上去

有了照度的定义,就可以比较好的理解曾经学过的一些知识了
例一: 单位面积上接收到的能量,跟该平面的法线和光线方向的余弦成正比
公式中的符号Φ是光源的辐射通量,A是平面的面积,其比值就是单位面积接收的辐射照度,再乘以夹角余弦,得到的就是单位投影面积的Irradiance

例二:地球上不同地方为何有冬夏温差之别? 北半球的某一个纬度上,光线与面法线的夹角不同 \[ \cos\alpha > \cos\theta \] 当地球公转到左图位置时,该纬度附近的一片区域收到的能量多,为夏季;公转到右边位置时则能量少,为冬季。

例三:照度衰减。计算着色时,为何离光源越远,目标着色点接收到的能量越少呢?
单个光源下,物体上某个着色点接收到的 irradiance \(\boldsymbol{E = \Phi / S_{球}}\),光源的能量均匀向空间中辐射,着色点的辐照度 (irradiance:某个点接受到的来自所有方向的辐射通量) 只有来自该单个光源方向的辐射通量。辐射通量恒定不变,随着距离越远,辐射球的表面积却不断增大,因此球面的每个点分到的能量就少了

1.5 Radiance 辐亮度 L
Why? 把 Intensity 的“方向视角”和 Irradiance 的“面积视角”合在一起。这个面元朝这个方向,每单位投影面积、每单位立体角里有多少光。
定义: 是每单位立体角,每单位投影面积发射或接收的能量 The radiance (luminance) is the power emitted, reflected, transmitted or received by a surface, per unit solid angle, per projected unit area.
(定义中说的是能量,但因为我们默认是单位时间,因此辐射通量和辐射能量是等价的),单位为nit(尼特) ,1nit=1 cd/m² 符号:L

- 这里写 d^2 是因为它同时对两个变量取微小量:面积,方向
- dAcosθ:也就是把面元沿光线方向投影后的面积
- 在计算单位面积接收的光能时,都要保证表面与光线垂直,前面的辐照度也是如此,否则单位表面实际接收到的能量密度会变小
入射辐亮度incident Radiance —— 从光线接收者的角度理解Radiance
- Incident radiance是到达平面每立体角的irradiance,即所有方向上的radiance积起来就是irradiance,radiance是irradiance在某个单位立体角dw的分量

\[ L(p, \omega) = \frac{d^2\Phi(p, \omega)}{d\omega \, dA \, \cos(\theta)} = \frac{dE(p)}{d\omega \, \cos\theta} \]
出射辐亮度Exiting Radiance —— 从光源的角度理解Radiance
- Exiting radiance是单位面积辐射出的intensity,即整个面上的radiance积起来就是intensity,radiance是intensity在某个单位面积dA上的分量

\[ L(p, \omega) = \frac{d^2\Phi(p, \omega)}{d\omega \, dA \, \cos(\theta)} = \frac{dI(p, \omega)}{dA \, \cos\theta} \]
注意:所有前面的属性,都可以两个角度来理解,光源或接收者
1.6 Irradiance vs. Radiance
这俩在图形学中用得特别频繁,所以务必确保搞清楚他俩的区别和联系
- Irradiance 辐照度: 单位面积dA接收到的通量总和
- Radiance 辐亮度: 单位面积接收到的来自dw方向的辐射通量(单位立体角可以认为是一个方向向量)
- Radiance是Irradiance在某个方向上的分量,如下图,整个半球范围内单位面积接收到的各个方向的radiance积分起来即为接收到的irradiance

Radiance也能用下面两种方法求得
Radiance = irradiance per solid angle,这个后面会用
Radiance = intensity per projected unit area
intensity 公式为: \( I = \frac{d\Phi}{d\omega} \)
irradiance 为: \( E = \frac{d\Phi}{dA} \)
2 双向反射分布函数 BRDF(Bidirectional Reflectance Distribution Function)
根据Irradiance和Radiance我们可以写出“入射光变成出射光的比例关系,即反射分布函数
2.1 反射
反射的理解:接收到入射光后,自身变成新的光源,向空间中辐射,这样理解有助于利用Radiance和Irradiance解释反射 单位面积 \(dA\) 接收到 \(\omega_i\) 方向来的Radiance,下图中用 \(L(\omega_i)\) 表示,也能理解成辐照度的一个微元 \(dE(\omega_i)\),然后再辐射到四面八方 \(\omega_r\) 中去,这时候辐射出去的又是Radiance(因为反射出去无数多条,power会被均分,而其中特定的一条分到的能量就是一个微元,用 \(dL_r(\omega_r)\) 表示)。

2.2 BRDF
双面反射分布函数 Bidirectional Reflectance Distribution Function(BRDF): 定义了某个入射方向上的微分辐照度,经过表面反射后,在某个出射方向上转化为微分辐亮度的比例

具体来说,描述了入射光线经过某个表面反射后在各个可能的出射方向上能量分布(反射率)。比如已经知道入射方向 ωi 传入任意 ωr 反射方向,能得到值域在[0,1]的浮点数返回值。
- 函数返回值意义:定义某个点在 ωr 方向的反射光线的能量跟 ωi 方向射入的光线能量的比值
- 比如镜面反射,则镜面反射的方向能量接近百分之百,其他反射方向接近0
- 比如漫反射,则所有方向的能量比例相同,均分
- BRDF项定义了物体不同的材质,换句话说,BRDF就是物体的材质在数学中的表达方式。
比如要定义很光滑的金属材质(只发生镜面反射),就把BRDF设置成镜面反射方向100%,其他方向为0; 想定义一个石膏材质(只发生漫反射),则BRDF设置成所有方向都均等的,比如0.001。
3 反射方程 The Reflection Equation

- fr(p,ωi→ωr):BRDF 项,返回值值域 [0,1]
- Li(p,ωi)cosθidωi:相乘的结果其实是 dE(p,ωi)
最后做个积分,把半球上每个入射光经过反射后落在 Lr 方向的贡献全部加起来,就得到最终的结果,即此着色点在所有可能的入射光下,反射给相机看到的光亮度是什么样的。
提醒:入射光不止来自光源,也可能是其他物体反射的光。递归思想,反射出去的光Lr也可被当做其他物体的入射光Li。如果只考虑光线弹射一次,那么Li只能是光源,不止一次那就不一定是光源了。
4 渲染方程Rendering Equation
- 反射方程本身是一个递归的定义方式,反射点接受的irradiance并不只来自光源,还会来自其他表面反射而来的光线(因为光线不止弹射一次),这里我们先不深入考虑这一点,通过添加一个自发光(Emission)做一个通用化
- 虽然入射光是从外向内的,但是我们规定入射方向的表示都是向外的(类似于之前着色计算的方向规定)
\[ L_o(p, \omega_o) = L_e(p, \omega_o) + \int_{\Omega^+} L_i(p, \omega_i) f_r(p, \omega_i, \omega_o) (\boldsymbol{n} \cdot \boldsymbol{\omega}_i) \, d\omega_i \]
\[ \boldsymbol{n} \cdot \boldsymbol{\omega}_i = |\boldsymbol{n}| |\boldsymbol{\omega}_i| \cos\theta_i, \quad |\boldsymbol{n}| = |\boldsymbol{\omega}_i| = 1 \Rightarrow \boldsymbol{n} \cdot \boldsymbol{\omega}_i = \cos\theta_i \] 故渲染方程中的 \(\cos\theta_i\) 可写为 \(\boldsymbol{n} \cdot \boldsymbol{\omega}_i\)。
更深入理解渲染方程
- 如果只有一个光源,则渲染方程写出来是这样

多个点光源

面光源
点光源是把所有点光源的反射贡献加起来,面光源其实就是一堆点光源的集合,直接求积分则可算出整个面光源的能量

间接光
这个负号非常关键,因为它在切换“上一跳看到的方向”和“当前点的出射方向”。
在点p处,你采样了一个方向\(w_i\);沿着\(w_i\)发光线,打到了点q;也就是说,从p到q的方向是\(w_i\)。那么站在点q看,回到p的方向就是反过来的:\(-w_i\)。而shade(q, wo)的含义是:求点q沿方向\(w_o\)的出射光。现在我们正需要的,就是:点q朝着返回p的方向,发出了多少光。所以必须写成:shade(q, \(-w_i\))

将渲染方程简写成积分方程
- 把积分号内的已知量归到一项之中

再进一步简化成几个记号表示,出射方向的强度 = 自发光强度 + K· 入射光强度;K直接理解成一个反射操作符

写成算子的形式方便级数展开,展开所用公式如下,对应的把x换成K,1换成单位矩阵I

则可以用一种无限逼近的方式得到最终的Lr,项数(反射次数)越多,则越接近真实值

最终就可以描述光线追踪了
注意自发光的物体本身其实就是光源,所以弹射0次相当于直接看向光源


渲染方程被拆解成以光的弹射次数为区分的很多项。用渲染方程来理解光栅化,光栅化能做的只有自发光与直接光照(弹射1次);全局光照是经过了多次反射后的结果,即把表示间接光照的项也加起来
所以,为什么要用光线追踪呢?因为它可以很容易的计算出间接光照的部分

光线经过多次弹射后结果会越来越亮,逐渐收敛(第四次弹射时上方灯亮了,是因为这里玻璃时双层的,光线需要两次弹射进入两次弹射射出才能到达相机)
至此,已经了解光线的传播方式,那我们需要正确的实现光线传播方式就需要解渲染 方程,路径追踪就是一种解渲染方程的方式
5. 蒙特卡洛积分 Monte Carlo Integration
5.1 为什么学习蒙特 Why
蒙特卡洛积分可以用来计算复杂定积分的值 we want to solve an integral, but it can be too difficult to solve analytically,特别适用于得不到解析解的定积分,可以通过这个方式不得到解析式直接得到结果
5.2 什么是蒙特卡洛积分 What
- 蒙特卡洛方法,其实是一种思想,把这种思想运用在求解定积分上,就是蒙特卡洛积分。
- 再把蒙特卡洛积分,这种求解积分的方法,运用在渲染方程的求解上,就是蒙特卡洛路径追踪
- 高数课程中,求定积分用的是黎曼积分,即划分成无数的面积微元求和,但是如果函数图形非常复杂则不好用
举一个例子:如果在定义域 \((a,b)\) 内随意取一点 \(x\),得到对应的 \(f(x)\) 的值,则我们可以用一个高为 \(f(x)\)、宽为 \(b-a\) 的长方形面积来估计曲线下围出的面积;如果多次在 \((a,b)\) 间采样,随后把长方形面积平均起来,就能得到一个相对准确的结果。
蒙特卡洛积分是一种通过随机采样然后平均函数值来估计整个函数积分的方法 estimate the integral of a function by averaging random samples of the function’s value

5.3 如何进行蒙特卡洛积分 How
定义一个函数 ,使用蒙特卡洛积分用一定概率密度去采样,估计其在定义域内的积分

一种特殊情况是概率密度函数 PDF 为常数 \(C\),即在函数定义域内均匀采样,这样得到的蒙特卡洛积分的形式很符合直觉:即一开始说的多个长方形的面积(长 \((b – a)\),高 \(f(X_i)\))的和平均(除以 \(N\))结果。


- 对于不同采样频率,更为通用的形式即为
\[ \int f(x) \, dx = \frac{1}{N} \sum_{i=1}^{N} \frac{f(X_i)}{p(X_i)}, \quad X_i \sim p(x) \]- \(X_i\) 是从概率密度函数
- \(p(x)\) 中采样得到的随机样本
- \(N\) 是采样总数
- \(p(X_i)\) 是样本 \(X_i\) 对应的概率密度值
- 蒙特卡洛积分采样的样本越大(N越大),越准确
6. 路径追踪 Path Tracing
6.1 Whitted风格光线追踪的问题 Motivation: Whitted-Style Ray Tracing
Whitted风格光线追踪会在光线打到光滑表面会进行镜面反射,打到漫反射表面会停止
Whitted风格光线追踪无法做出Glossy的材质(不是完全的镜面反射)

Whitted风格光线追踪不考虑漫反射的效果,但实际上光线也会在漫反射表面弹射,如右图中有Color Bleeding效果,而左图没有

如图,左右两张图片都是PathTracing方法渲染得到的结果,区别是:
左边: 仅计算直接光照,光线弹射0次,即击中物体直接连光源做阴影判断和着色计算。
右边: 计算全局光照(直接+间接),光线弹射1次以上。比如天花板,摄像机射线击中天花板,天花板弹射一次到地面/左右墙体,再直接连到光源,则被照亮了,正向过程就是,光打到地面,地面反射部分比例的光到天花板,天花板反射到摄像机,从而可以看见。
还有一个很大的区别是: 右图中长方体的左边明显染上了左墙的红色,正方体的右边明显染上右墙的绿色。这种现象叫做Color Bleeding
到此我们可以得出,Whitted-Style光追是“错”的,渲染方程是对的
6.2 求解渲染渲染方程
渲染方程很对,那么现在的问题就是怎么解
\[ L_o(p, \omega_o) = L_e(p, \omega_o) + \int_{\Omega^+} L_i(p, \omega_i) f_r(p, \omega_i, \omega_o) (\boldsymbol{n} \cdot \boldsymbol{\omega}_i) \, d\omega_i \]
这个方程涉及到:
- 半球上的积分求解问题
- 递归计算问题:对于入射的Radiance \(L_i(p,\omega_i)\) – 如果是直接光照,不用递归,\(L_i\) 就是光源射出的Radiance – 如果是其他物体反射过来的间接光照,则该 \(L_i\) 其实是上一个物体的 \(L_r\),为了求这个 \(L_r\),开始递归
6.2.1 仅考虑直接光照
(1)理论部分
假设有一个有面光源、着色点、其他物体挡住光的的场景,首先我们先考虑直接光照Suppose we want to render one pixel (point) in the following scene for direct illumination only

- 注意:入射光本该是从光源指向着色点,但为计算方便,认为是从着色点指向光源
- 算曲线积分的时候采样x轴,而此时采样的是入射方向ωi,即对其球面上的不同方向进行采样
- 可以均匀采样,也可以是规定某些地方采样多一些,某些地方采样少一些

想要用蒙特卡洛方法解渲染方程,那就必须知道对应的函数表达式 \(f(x)\) 和每次采样对应的概率即 PDF 是多少。
- 渲染方程中的 \(f(x)\) 为被积函数:\(L_i(p,\omega_i) f_r(p,\omega_i,\omega_o) (\boldsymbol{n} \cdot \omega_i)\)
- PDF 要看我们如何设置采样方法,比如均匀采样,采样到每个方向的概率相等,则 \(p(\omega_i) = 1/(2\pi)\)(整个球的球面度是 \(4\pi\),半球则是 \(2\pi\))
然后就可以带入蒙特卡洛积分方程求解了:
\[ L_o(p, \omega_o) = \int_{\Omega^+} L_i(p, \omega_i) f_r(p, \omega_i, \omega_o) (\boldsymbol{n} \cdot \omega_i) \, d\omega_i \approx \frac{1}{N} \sum_{i=1}^{N} \frac{L_i(p, \omega_i) f_r(p, \omega_i, \omega_o) (\boldsymbol{n} \cdot \omega_i)}{p(\omega_i)} \]
到此为止,可以算出多个光源/面光源作用下的某个着色点的最终射向摄像机的Radiance,即最终着色结果
(2) 算法部分
对p点计算着色的算法
- 按照某种自己定的 PDF,采样 N 个不同入射方向;初始化出射到相机方向的能量为 \(L_O = 0.0\);
- 对每个入射方向 \(\omega_i\) 的确定方式:从 p 点向该方向发出射线 r,判断如果击中了光源,则计算此方向贡献的 \(L_{O_i}\),并且累加到之前的 \(L_O\) 上
shade(p,wo) //着色点p,沿wo方向到达观测点
Randomly choose N directions wi~pdf //以某一pdf采样N个方向的光线
Lo=0.0 //初始化
For each wi //对于任何一个被选中的方向都从p点向这个方向打出一条光线
Trace a ray r(p,wi)
If ray r hit the light //如果打出的光线打到了光源,则利用渲染方程着色
Lo += (1 / N) * L_i * f_r cosine / pdf(wi)
Return Lo

补充说明:完整的步骤是
- 选一个像素
- 从相机穿过这个像素发出一条主光线
- 拿这条光线去和场景求交
- 找到最近交点 p
- 只有当真的命中某个表面点 p 时,才调用 shade(p, wo)
6.2.2 全局光照(直接光照+间接光照)
除了要计算直接光照以外,还要计算间接光照

间接光照要递归:
如果考虑间接光照,即从 \(p\) 点连线没有直接打到光源,而是先打到了 \(q\) 点,\(q\) 点可以将光反射到 \(p\) 点,这里可以递归地想这个过程相当于在 \(p\) 点位置观测 \(q\) 点得到的反射来的 radiance。
shade(p,wo) //着色点p,沿wo方向到达观测点
Randomly choose N directions wi~pdf //以某一pdf采样N个方向的光线
Lo=0.0 //初始化
For each wi //对于任何一个被选中的方向都从p点向这个方向打出一条光线
Trace a ray r(p,wi)
If ray r hit the light //如果打出的光线打到了光源,则利用渲染方程着色
Lo += (1 / N) * L_i * f_r cosine / pdf(wi)
Else If ray r git an object at q //如果打出的光线打到了q点,计算p点看q点的着色再作为光源着色p点
Lo += (1 / N) * shade(q, -wi) * f_r * cosine / pdf(wi)
Return Lo //返回着色结果
没完,目前还是存在一些问题!
处理光线数量爆炸问题
随着弹射次数的增多,打出的光线数量会指数级上升,\(\text{Rays} = N^{\text{弹射次数}}\),\(N\) 是对入射方向的采样次数。

那么N等于多少的时候才不会数量爆炸呢?
解决方法:只有当 \(N=1\) 时光线数量不会爆炸,将只采样一次的光线追踪被称之为路径追踪,每次弹射都只有一个方向一条线,最终形成一条连接视点和光源的路径。
上面采样N次的被称之为分布式光线追踪(Distributed Ray Tracing)
shade(p,wo) //着色点p,沿wo方向到达观测点
Randomly choose ONE directions wi~pdf(w) //*这里只采样一个方向,同时也不用for循环了*
Trace a ray r(p,wi)
If ray r hit the light //如果打出的光线打到了光源,则利用渲染方程着色,因为只有一个方向,所以1/N等于1
Return L_i * f_r cosine / pdf(wi)
Else If ray r git an object at q //如果打出的光线打到了q点,计算p点看q点的着色再作为光源着色p点
Return shade(q, -wi) * f_r * cosine / pdf(wi)
但是这种算法,会产生非常多的噪点。因为有些着色点计算时,随机采样的某个入射光方向wi并没有击中光源,甚至也没有击中其他物体。那就啥也没击中,Li为0,算着色可不就是算了个寂寞么。
采样数N = 1而引发的噪点问题
对于每个像素发射N条光线(采样)做以下的算法
将每个射出去的采样接收到能量的点做蒙特卡洛积分得到平均值,只要用足够多的path,就能够确保有更大的几率击中有效光源或物体,计算出接近正确的着色

相关代码如下,计算过程跟光线追踪很相似,注意调用了上面的 `shade()` 函数做递归:
- 注意1:原本可能就是相机朝着像素中心发射一条射线,但现在对 1×1 的像素点进行采样,采样 N 个位置,相机朝着这些位置发射多条光线。
- 注意2:`pixel_radiance` 的计算中,只是把每个 path 结果加起来取平均,并不是套用蒙特卡洛公式。蒙特卡洛积分是在 `shade` 函数里面用的
ray_generation(camPos,pixel) //向哪一个像素打出很多光线
Uniformly choose N sample positions within the pixel //再像素内均匀的取N个不同的位置
pixel_radiance 0.0
For each sample in the pixel
Shoot a ray r(camPos, cam_to_sample) //任意一个采样都从视点到样本的位置连一条光线
If ray r hit the scene at p
pixel_radiance += 1 / N * shade(p, sample_to_cam) //连的光线打在了物体上,则着色
Return pixel radiance
无限递归问题
- shade()函数递归没有停止条件,会无限递归下去,所以设置终止递归条件
- 但是自然界中光线就是弹射无数次,我们限定弹射次数必然损失能量。不想无限递归又不想损失能量?
解决办法: 俄罗斯轮盘赌 RussianRoulette(RR),手动设置一个概率 \(P\)(\(0 < P < 1\))
RR 就像每一层递归都掷一次骰子:一次继续的概率是 \(P\),两次连续继续的概率是 \(P^2\),继续 \(n\) 次的概率是 \(P^n\);因为 \(0 < P < 1\) 所以路径几乎一定会在某一层终止,从而避免无限递归,而继续时再除以 \(P\) 做补偿,就能保证整体结果仍然无偏。
- 以 \(P\) 概率继续发射一条光线,返回 \(L_O\) 除以 \(P\) 之后的着色结果:\(L_O / P\)
- 加入 RR 后,这条路径只有 \(P_{RR}\) 的概率会继续并被采到, 按蒙特卡洛估计,权重要除真正采到这条路径的总概率,所以要再除以 \(P_{RR}\) 才能保持结果无偏
- 无偏”的意思是: 长期平均下来,你算出来的结果等于真实值。 也就是:一次估计可能偏高也可能偏低,但如果重复很多次取平均,平均值会趋近真正答案
- 以 \(1-P\) 的概率不发射光线,返回 \(0\) – 最终计算离散型随机变量的期望 \(E = P \times (L_O / P) + (1 – P) \times 0\)
无偏的数学证明:
\[ Y = \begin{cases} X/P, & \text{以概率 } P \\ 0, & \text{以概率 } 1-P \end{cases} \] \[ E[Y] = \sum_{y} y \, \mathrm{Pr}(Y=y) = (X/P) \cdot P + 0 \cdot (1-P) = X \]
shade(p,wo) //着色点p,沿wo方向到达观测点
Manually specify a probability P_RR
Randomly select ksi in a uniform dist. in [0, 1]
If (ksi > P_RR)return 0.0; //[0,1]取一个数ksi,ksi大于规定概率则不能生存
Randomly choose ONE directions wi~pdf(w) //这里只采样一个方向,同时也不用for循环了
Trace a ray r(p,wi)
If ray r hit the light //如果打出的光线打到了光源,则利用渲染方程着色
Return L_i * f_r cosine / pdf(wi) / P_RR
Else If ray r git an object at q //如果打出的光线打到了q点,计算p点看q点的着色再作为光源着色p点
Return shade(q, -wi) * f_r * cosine / pdf(wi) / P_RR
6.3 对光线采样 Sampling the Light
我们已经得到了一个正确的Path Tracing的方法,但是效率并不高 Now we already have a correct version of path tracing! But it’s not really efficient.

光源较小的时候,如果使用之前的方法对着色点半球范围内的光线均匀采样,由于能打到光源的很少,则很多光线就会被浪费掉 there will be 1 ray hitting the light. So a lot of rays are “wasted” if we uniformly sample the hemisphere at the shading point.

蒙特卡洛积分允许我们用任何方式采样,所以我们可以只对光源采样,这样光线就不会浪费。 假设我们在光源上采样,则 \(pdf = 1/A\),但是渲染方程的积分是定义在立体角上的,而蒙特卡洛积分要求积分和采样在一个域上,现在是在光源的面积采样,在着色点半球积分,并不在一个域上
- 之前蒙特卡洛解渲染方程,采样的是半球上的立体角,并且渲染方程也是对 \(\omega_i\) 进行积分的。
- 我们想变成对光源表面进行采样、对光源表面的 \(dA\) 进行积分怎么办?——积分换元
要做换元,就必须找到 \(dA\) 和 \(d\omega_i\) 的关系,相应的修改被积函数,并且更换积分限 - 回忆单位立体角的定义:\(d\omega = dA / r^2\)
- \(dA\) 是在光源上的一个小的表面,\(d\omega\) 是这个小的表面对应到单位球上的立体角。
- \(dA \cos\theta’\) 可以得到 \(dA\) 垂直于连线方向的投影面积,再除以两点距离的平方即可得到 \(d\omega\)(立体角定义)


找到 \(dA\) 和 \(d\omega\) 之间的关系后,我们就可以对渲染方程进行改写,此时是在光源进行采样,对光源进行积分,再次利用蒙特卡罗积分进行计算
最终的渲染方程——效率高、对光源采样
\[ \begin{aligned} L_o(x, \omega_o) &= \int_{\Omega^+} L_i(x, \omega_i) f_r(x, \omega_i, \omega_o) \cos\theta \, \mathrm{d}\omega_i \\ &= \int_{A} L_i(x, \omega_i) f_r(x, \omega_i, \omega_o) \frac{\cos\theta \cos\theta’}{\|x’ – x\|^2} \, \mathrm{d}A \end{aligned} \]
注意积分限:从半球变成了光源的表面积!
- 蒙特卡洛公式所需要的变量:
f(x) :依然是被积函数那一大堆
p(x) :由于对光源表面均匀采样,所以每个点的概率均等,都是1 / A
原来是在着色点上方半球的立体角域 \(\Omega\) 内,对方向 \(\omega\) 做均匀采样,因此 pdf 是按立体角定义的: \[ p(\omega) = \frac{1}{2\pi} \] 因为半球的立体角大小是 \(2\pi\)。 现在改为在面积为 \(A\) 的面光源表面上均匀采样一个点,因此 pdf 是按面积定义的: \[ p(x) = \frac{1}{A} \]
最终对于每个点的radiance计算,都是来自两部分
- 光源(直接光照,不需要俄罗斯轮盘赌RR)。对光源进行均匀采样,求积分
- 其他物体反射过来的光(间接光源,依然用RR)。(N = 1按概率决定是否发射、射到哪里随缘、多个path求平均)
shade(p, wo)
# Contribution from the light source.
L_dir = 0.0
Uniformly sample the light at x' (pdf light = 1 / A)
#没有多次递归的话不需要设计概率让其最终停止
L_dir = L_i * f_r * cosθ * cosθ / |x' - p|^2 / pdf_light
# Contribution from other reflectors.
L_indir = 0.0
Test Russian Roulette with probability P_RR
Uniformly sample the hemisphere toward wi (pdf_hemi 1 / 2pi)
Trace a ray r(p, wi)
If ray r hit a non-emitting object at q 优化部分,不再是只要碰撞到物体就一视同仁的处理,不再把大量半球样本浪费在找光源上
L_indir = shade(q, -wi) * f_r * cos θ / pdf_hemi / P_RR
Return L_dir + L_indir
路径追踪不好做点光源的计算,建议把点光源做成有很小面积的面光源
一个小问题,我们还需要考虑光源与物体之间是否有遮挡,在光源和物体间连线,判定中间有没有物体遮挡 One final thing: how do we know if the sample on the light is not blocked or not?
# Contribution from the light source.
L_dir = 0.0
Uniformly sample the light at x' (pdf light = 1 / A)
Shoot a ray from p to x'
If the ray is not blocked in the middle
L_dir = ...
其他 Some Side Notes
- 学路径追踪你会收获很大,因为路径追踪是几乎100%正确的算法(左:照片,右:渲染)And so learning it will be rewarding also because …
- 路径追踪真的很难 Path tracing (PT) is indeed difficult
- 把它当作本科CS里最具挑战性的任务 Consider it the most challenging in undergrad CS
- 为什么:涉及了物理、概率论、计算、代码能力 Why: physics, probability, calculus, coding
- 通过学习路径追踪你会对之前的知识理解更深 Learning PT will help you understand deeper in these
- 路径追踪还算”入门吗“ Is it still “Introductory”?
- 要进阶一点点,但是是”现代的“ Not really, but it’s “modern” 🙂

- 早期的光线追踪说的就是Whitted风格光线追踪 Ray tracing == Whitted-style ray tracing
- 怎么生成一张图呢,要么光栅化Rasterazation要么Ray tracing 路径追踪(指的不是Whitted-style ray tracing)
- 现在的光线追踪是所有光线传播方法的大集合(闫老师个人理解) The general solution of light transport, including
还有其他很多比这个更难的方法,学术上研究的- 单向/双向路径追踪 (Unidirectional & bidirectional) path tracing
- 光子映射 Photon mapping
- Metropolis light transport
- VCM / UPBP…
我们没有涉及/不会涉及的内容 Things we haven’t covered / won’t cover
- 怎样让方向均匀分布在半球上,怎样采样一个函数 Uniformly sampling the hemisphere, And in general, how to sample any function?
- 蒙特卡洛积分可以用任意的pdf,选择什么样的pdf是最好的(重要性采样理论) Monte Carlo integration allows arbitrary pdfs, What’s the best choice? (importance sampling)
- 随机数有质量的好坏(低差异序列) Do random numbers matter?-Yes! (low discrepancy sequences)
- 采样光源和采样半球两者可以结合起来 I can sample the hemisphere and the light -Can I combine them? Yes! (multiple imp. sampling)
- 为什么一个像素所有路径的radiance平均起来就是这个像素的radiance?The radiance of a pixel is the average of radiance on all paths passing through it, Why? (pixel reconstruction filter)
- radiance和像素的颜色不是线性对应的,需要经过伽马矫正 Is the radiance of a pixel the color of a pixel? No. (gamma correction, curves, color space)
Reference
https://blog.csdn.net/Motarookie/article/details/122353183
https://zhuanlan.zhihu.com/p/473902856







