[toc]

# 全局光照的定义

一次弹射是直接光,多次弹射是间接光,直接光加上所有的间接光的和就是全局光照 其中积分形式的渲染方程定义如下

# 概率密度函数 PDF (Probability Density Function)

最下面的式子是对应概率密度下的期望

# 蒙特卡洛解复杂积分 (数值)

定积分

  1. 如果一个积分表达式很难得到解析式,那我们如何得到积分的值呢?

这就用到了蒙特卡洛方法。

# 黎曼积分

黎曼积分是将函数划分为无数小的矩形 (二维) 来进行累加和求解积分数值

# 蒙特卡洛方法

使用概率的方式采样曲线,在积分域内不断采样 x, 设采样的 y 值为积分域内的高,面积为积分的值,采样无数次之后平均就是积分的值 如果均匀的采样,则每次采样的蒙特卡洛积分的 PDF 是一个常数 蒙特卡洛积分的公式如下: 即每次采样的值除以 PDF 的累加和的平均就是数值解 由上述我们知道,二维下的蒙特卡洛的 PDF 实际上就是 1/b-a 假设采样的结果是每次采样的和 (也可以理解为积分),则最后积分的数值解就是: 上面的式子就可以用一个很直观的方式来理解,即每次采样区间是 b-a/N, 采样值是目标点的函数值,对应的矩形就是采样区间内的数值近似解

# Path Tracing

如何使用蒙特卡洛方法来解决全局光照渲染方程对球面积分的问题? 答:
在球面上选择几个方向,进行蒙特卡洛近似计算即可 最简单的 PDF 采样方法 (均匀): 半球面积是 2Π 蒙特卡洛渲染方程: 直接打到一个光源伪代码:

1
2
3
4
5
6
7
8
shade(p,wo)
Randomly choose N directions wi~pdf
Lo = 0.0
For each wi
Trace a ray r(p,wi)
If ray r hit the light
Lo += (1/N) *L_i * f_r * cosine / pdf(wi)
Return Lo

因为全局光照等于直接光 + 所有间接光的和,如下图,所以当我们计算射线打到物体表面时的光照应该是一个递归表达式 递归伪代码如下:

1
2
3
4
5
6
7
8
9
10
shade(p,wo)
Randomly choose N directions wi~pdf
Lo = 0.0
For each wi
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 hit an object at q
Lo += (1/N) * shade(q,-wi) * f_r * cosine / pdf(wi)
Return Lo

上述代码有一个很明显的问题,就是如果每次射线都随机选取 N 个方向,则当递归次数躲起来的时候,就是指数级的爆炸,而路径追踪是选取 N=1 来防止递归爆炸的问题的

1
2
3
4
5
6
7
8
9
10
shade(p,wo)
Randomly choose 1 directions wi~pdf
Lo = 0.0
For each wi
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 hit an object at q
Lo += (1/N) * shade(q,-wi) * f_r * cosine / pdf(wi)
Return Lo

pathTracing 即从像素打出到物体无穷递归,优化到从一像素点打出很多路径求平均 如上图的红、黑、蓝就是三个路径 pathTracing 的上层函数伪代码如下:

1
2
3
4
5
6
7
8
ray_generation(camPos,pixel)
Uniformly choose N sample positions within the pixel
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函数是N=1的rayTracing 但是上述代码还有一个问题,就是我们无法确定 RayTracing 什么时候会停下来,最坏的方式是限制递归的层数 (即反弹的次数)
那么怎样能使我们即可以让最后的结果期望值为 L0 (正确的 Lumen),
这就引出了下一个方法: 俄罗斯轮盘赌 可以发现,这个公式最后的期望就是 LO 即自己拟定一个固定的概率 P
对于每次发出射线,都使用概率判断是否发射,如果允许,则返回 LO (当前的 LO)/P, 否则返回 0 但最后结果就是可能对的 = = 改善后伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
shade(p,wo)
//新添加
Mannually specify a probability P_RR
Randomly select ksi in a uniform dist. in[0,1]
If(ksi > P_RR) return 0.0;
//新


Randomly choose ONE direction wi~pdf(w)
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 hit an object at q
Rerturn shade(q,-wi) * f_r * cosine /pdf(wi) / P_RR

但是上述代码并不是很高效和有效的,因为返回正确流明完全看运气 = = 改善的方式是: 修改采样用的 PDF 核 如:直接在光源上采样,但是由于现在的积分是在物体半球上的积分,所以需要将蒙特卡洛方程改写成在光源上面积微分的积分 如上图,光源是 luminaire
球面表面的立体角微分和 dA 的转换关系就是 dw 是光源 dA 照到球面的面积对应的立体角 即先把 dA 对准球体,然后计算出立体角即可 这个公式就是这个意思,如此一来,就可以改写成对光源的积分

# 对物体球面积分改写成对光源积分的形式

因为改写成了对光源的积分,所以我们可以将对物体表面的光的贡献拆分
1. 光源对表面的贡献,使用上面的对光源的积分公式 (不需要俄罗斯轮盘赌)
2. 其他物体对表面的贡献,还是使用之前的方式 (需要俄罗斯轮盘赌) 伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
shade(p,wo)
#Contribution from the light source.
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 R 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

在计算光照的时候,需要判断下光源是否被挡住了,如果挡住了,就直接返回 0 就行