[图形]路径追踪

Probabilities

XX:随机变量,可以取各种不同的值

p(x)p(x):随机变量的概率

例如:骰子有六个面,可能的取值,也就是XX可以取1、2、3、4、5、6

那么p(1)=p(2)=p(3)=p(4)=p(5)=p(6)=16p(1)=p(2)=p(3)=p(4)=p(5)=p(6)=\frac{1}{6}

很容易可以知道

i=1npi=1\sum\limits_{i=1}^n p_i=1

所有概率叠加起来结果是1

期望:不断取随机变量并求平均(超简单的理解

E[X]=i=1nxipiE[X]=\sum\limits_{i=1}^n x_i p_i

xix_i:所有随机变量能取得值
pip_i:取到某个随机变量的概率
还是拿骰子举例,它可以取1、2、3、4、5、6,它取到每个值的概率是16\frac{1}{6}
所以期望是

\begin{split} E[x]&=\sum\limits_{i=1}^n \frac{i}{6} &=\frac{1+2+3+4+5+6}{6} &=3.5 \end{split}

现在引申到连续型随机变量上,概率会有函数,叫做概率密度函数(Probability Density Function),是一个描述这个随机变量的输出值,在某个确定的取值点附近的可能性的函数

概率密度也满足所有可能的取值的和为1

p(x)dx=1\int p(x)\mathrm{d}x=1

相应的,它的期望也可以很容易知道

E[X]=xp(x)dxE[X]=\int xp(x)\mathrm{d}x

现在,如果随机变量也是一个函数f(x)f(x),如何求期望也没有变化

E[f(x)]=f(x)p(x)dxE[f(x)]=\int f(x)p(x)\mathrm{d}x

Monte Carlo Integration

严格数学推导(完全不懂):蒙特卡洛积分

The Monte Carlo method is a numerical method of solving mathematical problems by random sampling (or by the simulation of random variables).
蒙特卡洛法是一类通过随机采样来求解问题的算法的统称,要求解的问题是某随机事件的概率或某随机变量的期望。通过随机抽样的方法,以随机事件出现的频率估计其概率,并将其作为问题的解。

给任何一个函数,我想算它从a到b的定积分,一般我们会求出它的不定积分,再带入上下限减一下就求出来了。

但是现在这个函数比较复杂,求不出不定积分。而我们只想要最后结果,可以用蒙特卡洛法。

说白了,蒙特卡洛法就是随机n个自变量,带入解析式求出n个解,当n足够大时,把所有结果平均一下,就是最后的解

Fn(x)=1nk=1nf(xk)pdf(xk)F_n(x)=\frac{1}{n}\sum\limits_{k=1}^n\frac{f(x_k)}{pdf(x_k)}

Fn(x)F_n(x):要求解的函数

f(x)f(x):被积函数

pdf(x)pdf(x):x取值的随机分布

不同的随机分布会影响计算速度和结果

(关于为啥它是对的,这篇基本上能解释了https://zhuanlan.zhihu.com/p/260196506

Path Tracing

Whitted-style缺陷

经典光线追踪算法(Whitted-style)缺陷

  • 不是在求解渲染方程,没有使用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\to\omega_o)L_i(p,\omega_i)(n\cdot\omega_i)\mathrm{d}\omega_i

渲染方程看似复杂,其实就是在半球上,不同方向上的积分

根据蒙特卡洛法,在半球不同方向上采样,也就是说随机选个方向(随机变量)。我们还需要对应的被积函数和pdf

很容易得出被积函数f(x)f(x)就是fr(p,ωiωo)Li(p,ωi)(nωi)f_r(p,\omega_i\to\omega_o)L_i(p,\omega_i)(n\cdot\omega_i)
pdf(x)pdf(x)涉及到如何对积分域采样,在这里就是如何对半球采样。最简单的采样法就是均匀采样pdf(ωi)=12πpdf(\omega_i)=\frac{1}{2\pi}

改写渲染方程

Lo(p,ωo)1ni=1Nfr(p,ωiωo)Li(p,ωi)(nωi)pdf(ωi)L_o(p,\omega_o)\approx\frac{1}{n}\sum\limits_{i=1}^N\frac{f_r(p,\omega_i\to\omega_o)L_i(p,\omega_i)(n\cdot\omega_i)}{pdf(\omega_i)}

现在这个算法已经可以算出出射的radiance是多少了

可以写出直接光照伪代码

1
2
3
4
5
6
7
8
shade(p, wo) //任意一个着色点p,出射方向是wo
按照pdf随机往半球各个方向wi生成N个向量
Lo=0
foreach wi
ray r(p, wi) //从p点往wi发射方向连一条光线
if r 射中了光源
Lo += (1 / N) * L_i * f_r * cos / pdf(wi) //L_i是光源辐射出的radiance,f_r是BRDF
return Lo

再考虑间接光,其实间接光就是其他物体反射光源的光,也是radiance

就好像我们在P点算Q点的直接光照

在伪代码里加上间接光

1
2
3
4
5
6
7
8
9
10
shade(p, wo) //任意一个着色点p,出射方向是wo
按照pdf随机往半球各个方向wi生成N个向量
Lo=0
foreach wi
ray r(p, wi) //从p点往wi发射方向连一条光线
if r 射中了光源
Lo += (1 / N) * L_i * f_r * cos / pdf(wi) //L_i是光源辐射出的radiance,f_r是BRDF
else if r 在q点射中了物体
Lo += (1 / N) * shade(q, -wi) * f_r * cos / pdf(wi) //-wi的原因是,我们从p点往外选了一个方向,对于q点来说是负方向
return Lo

全局光照get✔

路径追踪算法

这个算法还有问题吗?

递归生成光线的数量会指数上升,1->100->10000->…基本不可能计算出来

我们可以只生成一条光线(233)

1
2
3
4
5
6
7
8
9
shade(p, wo) //任意一个着色点p,出射方向是wo
按照pdf随机往半球各个方向wi生成1个向量
Lo = 0
ray r(p, wi) //从p点往wi发射方向连一条光线
if r 射中了光源
Lo += L_i * f_r * cos / pdf(wi) //L_i是光源辐射出的radiance,f_r是BRDF
else if r 在q点射中了物体
Lo += shade(q, -wi) * f_r * cos / pdf(wi) //-wi的原因是,我们从p点往外选了一个方向,对于q点来说是负方向
return Lo

只生成一条光线,连结视点和光源的路径的算法,就叫做路径追踪

但是只生成一条光线,噪声很大,我们可以生成多条路径,再把结果叠加求平均

光线生成算法

我们要对所有像素都发射一条光线

1
2
3
4
5
6
7
8
ray_gen(camPos, pixel)
像素内均匀地在N个不同位置取样sample
pixel_radiance = 0
foreach sample in pixel
ray r(camPos, cam_to_sample) //从摄像机位置连一条光线到取样地位置
if r在p点射中了物体
pixel_radiance += 1 / N * shade(p, sample_to_cam) //出射方向是采样点到摄像机
return pixel_radiance

Russian Roulette

现在这个算法还有问题吗?

生成光线路径的shade函数是递归函数,递归需要终止条件,否则会无限制递归下去。现实中的光线会无限制递归,但是计算机不行(爆栈警告)。但是简单地终止递归会造成能量损失,结果不准确。

为了解决这个问题,引入俄罗斯轮盘赌,在一定条件下终止递归

提前定义一个概率p,以这个概率向某个方向发射光线,再把返回地结果除以p。在1-p概率时,返回0

E=p(Lop)+(1p)0=LoE=p * (\frac{Lo}{p}) + (1 - p) * 0 = Lo

我们仍然可以期望,最后结果是Lo

离散型随机变量的期望是概率乘以值,再把所有加起来。

以概率p得到的结果是Lop\frac{Lo}{p}

以概率1p1-p得到的结果是00

加起来结果是正确的,只是有时候有噪音

现在在算法里加入RR

1
2
3
4
5
6
7
8
9
10
11
12
13
shade(p, wo)
选择一个概率P_RR,范围[0,1]
随机一个变量ksi,范围[0,1]
if(ksi > P_RR) return 0

按照pdf随机往半球各个方向wi生成1个向量
Lo = 0
ray r(p, wi)
if r 射中了光源
Lo += L_i * f_r * cos / pdf(wi) / P_RR
else if r 在q点射中了物体
Lo += shade(q, -wi) * f_r * cos / pdf(wi) / P_RR
return Lo

至此,我们推出了正确的路径追踪算法

只不过这个算法效率不是很高,因为想要最终低噪点的图,需要大量采样

因为生成光线的方向是随机的,能不能打中光源看运气

在光源上采样

蒙特卡洛法并没有规定只能用哪种pdf,如果能直接往光源上采样,那所有光线都不会造成浪费

我们想在光源上采样,而光源就是一个2d的图形,可以很容易知道pdfdA=1\int pdf\mathrm{d}A=1pdf=1Apdf=\frac{1}{A}

现在我们采样是在光源面积上采样,但是渲染方程不是定义在光源上,而是定义在立体角上(就那个半球)。蒙特卡洛积分又要求只能在积分域上采样,这样就不能用了

我们得把渲染方程写成在光源上的积分

现在我们需要知道dω\mathrm{d}\omegadA\mathrm{d}A的关系

复习一下立体角定义:球面上的投影面积与半径的平方之比。也可以理解成把任意一个面积投影到单位球的表面上对应的面积,这个投影的面积就是立体角

所以只要把光源的面积投影到着色点单位圆上的面积,就获得了他们的关系

dω=dAcosθxx2\mathrm{d}\omega=\frac{\mathrm{d}Acos \theta^{'}}{\left|\left|x^{'}-x\right|\right|^2}

本质还是面积除以距离平方

重写渲染方程

Lo(p,ωo)=Le(p,ωo)+Ω+fr(p,ωiωo)Li(p,ωi)cosθdωi=Le(p,ωo)+Afr(p,ωiωo)Li(p,ωi)cosθcosθxx2dA\begin{split} L_o(p,\omega_o)&=L_e(p,\omega_o)+\int_{\Omega^+} f_r(p,\omega_i\to\omega_o)L_i(p,\omega_i)cos\theta\mathrm{d}\omega_i\newline &=L_e(p,\omega_o)+\int_{A} f_r(p,\omega_i\to\omega_o)L_i(p,\omega_i)\frac{cos\theta cos\theta^{'}}{\left|\left|x^{'}-x\right|\right|^2}\mathrm{d}A \end{split}

现在应用蒙特卡洛法

被积函数:方程积分号里所有内容

pdf:1A\frac{1}{A}

光源对着色点的贡献可以拆成两部分

  • 直接光的贡献(不需要RR)
  • 其他物体反射的光(需要RR)
1
2
3
4
5
6
7
8
9
10
11
12
13
shade(p, wo)
//直接光对着色点地贡献,pdf_light=1/A
从p
L_dir = L_i * f_r * cos * cos' / | x' - p |^2 / pdf_light

//间接光
L_indir = 0
RR决定是否要发射射线,不发射就返回L_indir
生成方向wi,pdf_hemi = 1 / 2pi
ray r(p, wi)
if r在q打中不是发光物体
L_indir = shade(q, -wi) * f_r * cos / pdf_hemi /P_RR
return L_dir + L_indir

最后最后一个小问题,如果物体和光源之间有遮挡怎么办

从点p到x’发射一条射线,检查下有没有其他物体

如果被挡了就返回0,没挡才计算

参考

光影之美:路径追踪算法初探

蒙特卡洛路径追踪(Monte Carlo Pathing Tracing)(上篇)

蒙特卡洛路径追踪(Monte Carlo Pathing Tracing)(下篇)


[图形]路径追踪
https://ksgfk.github.io/2021/02/28/图形-路径追踪/
作者
ksgfk
发布于
2021年2月28日
许可协议