1.前言
前面几篇文章介绍了Whitted-style光线追踪,还介绍了基于物理渲染的基础知识,包括辐射度量学、BRDF以及渲染方程,但并没有给出解渲染方程的方法,或者说如何通过该渲染方程计算出屏幕上每一个坐标的像素值。
Whitted-style光线追踪是存在很多不正确的情况,但是基于渲染方程是正确的。本文就介绍通过蒙特卡洛路径追踪来解渲染方程的方法。
2.蒙特卡洛积分
蒙特卡洛积分(Monte Carlo Integration)是为了解决函数的定积分。给任意函数要算出这个函数的定积分,而定积分是一个数值。下面举个例子,如下图所示,如何去求曲线在a、b之间围成的面积。
求曲线在a、b区域内面积也就是求曲线的函数在[a, b]内的定积分。但对于这样一个曲线,很难用一个数学函数去表示,因此无法用一般解析的方法直接求得积分值,可以采用黎曼积分的思路。
也就是对上图这个图形纵向均匀地切割,分成多个宽度相同的条形,那么这些条形就可以近似为长方形,取条形中线的高度作为近似长方形的高度,将这些近似小长方形的面积全部加起来就能得到整个图形近似的面积。
当切割的数量越多也就是采样越多,得到的结果越逼近真实的数值。
而相比起黎曼积分的均匀采样,蒙特卡洛积分可以指定一个随机分布来对被积分的值进行采样,因此更加通用,定义如下。
如上图所示,我们希望求出一个函数f(x)在积分域[a,b]上的积分值,选定一个采样的分布p(x),通过对该分布来进行多次的函数值采样,最后近似的积分值如图中最下方式子所示。
注意公式中的1/N可以理解为不将图形分割,而是每次采样都近似为一个采样对应的函数值的长方形,将多次采样近似的长方形面积累加求均值,如下图是采样次数为4时的。
这里对前面的公式进行一个简单的推导,从概率论的角度理解,求均值的做法其实也是对期望的逼近。
那么对于这样一个服从某一分布的期望的计算套公式直接计算得。
通过以上推导即可明白蒙特卡洛的近似正是对积分值的一个无偏估计,也就是采样次数越多,近似值与积分值越接近。简单说就是样品越多,反差越小。
为了方便,一般都使用均匀采样,因此很容易推出。
因此,蒙特卡洛就是对函数值进行多次采样求均值,从而帮助求得困难积分值的近似的方法。
3.蒙特卡洛路径追踪
3.1 Whitted-style光线追踪的问题
在前面的文章中学过Whitted-style光线追踪主要是不断在弹射光线,那么是怎么弹射光线的?
1.当光线打到表面为光滑的物体上,比如玻璃,那么光会沿着镜面方向反射或者沿着折射方向折射。
2.当光线打到漫反射物体上,光线就停了。
那么,这两个简化是对的吗?
其实,这个不一定是对的,下面举例了几个光线追踪的问题。那么,就有了路径追踪(Path Tracing)来解决很多基于非物体或者不正确的问题。
1.whited-style光线追踪对于反射表面进行镜面反射,难以模拟介于漫反射和镜面反射之间的光泽反射(Glossy Reflection)。
对于有光泽的材料,光线应该反射到哪里?如下图所示。
2.whited-style光线追踪对于漫反射表面直接进行光照计算,并没有对漫反射的光线进行追踪。
whited-style光线追踪把光线打到漫反射物体上,光线就停了,但是实际上还是有光线会反射不同方向上去。如下图所示,直接光照与全局光照的区别,全局光照图上可以看到红色和绿色墙面的颜色反射到了中间的物体表面上。
3.2 解渲染方程
3.2.1 解渲染方程的难点
上面介绍了whited-style光线追踪存在的问题,但是渲染方程是正确的,回顾渲染方程使用了各种辐射度量学、BRDF提供标准来推导出来的,方程如下。
要想解出渲染方程的解主要有两个难点:
1.半球积分的计算。考虑来至四面八方的光照,就要求整个半球(不考虑背面光)的积分。
2.递归形式。反射来的光线有可能是直接光照,也可能是其他物体反射来的间接光照。
对于第一个问题,考虑到如何用一个数值来解一个积分,就需要用到前面所学的蒙特卡洛积分了。
3.2.2 一个简单的蒙特卡洛解决方案
假设我们在以下场景中渲染一个像素(点),仅用于直接光照,场景中为面光源,如下图所示。其中ω0为眼睛的观察方向,ωi则是各个方向弹射到点 p 的光线(这里箭头方向与光线实际方向相反是因为渲染方程中所有方向都以表面向外为正方向)。
先对渲染方程稍微做一下滥用,舍弃自发光项(假设除了光源其他物体不会发光),从而方便进行计算推导如下。由于限定了直接光照,所以四面八方来的光只有光源自身发射的。其物理含义为着色点 p 到摄像机或人眼的Radiance值。
这个方程看起来很复杂,其实就是一个半球在不同方向上的积分,所以就用蒙特卡洛积分来求值。
回想前面所提到的,对于一个困难积分只要选定一个被积分变量的采样分布即可通过蒙特卡洛的方法得到积分结果的近似值。而此时的被积分值为入射光线方向ωi,选定采样分布p(ωi)为在 p 点的半球方向内对ωi进行均匀取样(只考虑从平面上方入射的光线,半球的立体角为2π,p(wi)为1/2π),不难得出积分近似结果如下:
到此时可以算出任何一个着色点出射的Radiance。对这个着色点 p 出射方向为,要计算来至四面八方光线对这个点直接光照的贡献,就可以写出一个算法,如下伪代码所示。
L_i 代表光源亮度(Radiance),f_r 代表BRDF,pdf(wi) 代表均匀采样的值。
3.2.3 全局照明
上述只考虑直接光照显然是不够的,还需要间接光照,即当采样的ωi方向碰撞到了别的物体,如下图所示。
此时采样的光线ωi碰撞到了另一个物体的Q点,如果这条光线在Q点反射后没有碰到光源,这条路径自然没有贡献;如果光线在Q点反射后碰到了光源,那么该条路径对着色点P的贡献是多少呢?
此时可以理解为ωi方向为Q点的观察方向,求Q点到ωi方向的Radiance值,也就是光源在点Q的直接光照再乘上Q点发射到ωi方向的比例。
显然这是一个类似光线追踪的递归过程,不同点是该方法通过对光线方向的采样从而找出一条条可以连接眼睛和光源的的光线路径,这也正是为什么叫路径追踪的原因,伪代码如下:
在Q点反射过来的Radiance,就相当于在Q点反射过来的直接光照,也就是在Q点从-wi方向看过去的直接光照。这里是-wi方向是因为从P点到Q点是wi方向,那么从Q点到P点自然就是-wi。
至此,我们成功通过蒙特卡洛的方式解出(近似)了渲染方程的积分值,也通过考虑直接光照与间接光照解决了递归的问题。到此,是否已经解决了?
3.2.4 路径追踪(Path Tracing)
接上述疑问,还有两个问题。
1.随着反射增加,射线爆炸。
如下图所示,我们通过每次对光线方向的采样从而解出方程,假设有一条光线打到物体上,为了计算直接光照,假如从物体上打出100条光线,那么100条都有可能打到其他物体上,在其他物体上又要算直接光照,那么每条再发射100条光线,就是10000条,依次类推,反射越多次光线数量便会指数级爆炸增长,计算量完全无法接受,那么如何才能使得光线数量不爆炸增长呢?
唯有每次只采样一个方向,也就是N = 1(1的多少次方都是1)。N = 1时就是通常所说的路径追踪(连接一条视点与光源的路径),作为区分N != 1时,一般称为分布式路径追踪。
从现在开始,我们总是假设每个着色点只有一条光线被追踪,伪代码如下。
如果只采样一个方向那么所带来的问题也是显而易见的,积分计算的结果会非常的随机,虽然蒙特卡洛积分是无偏估计,但样本越少显然偏差越大。
但该问题也好解决,如果每次只去寻找一条路径结果不准确,那么就去寻找多条路径。如下图所示,分别有红、蓝、黑三条路径。
从摄像机向每个像素的区域内发射多条路径,追踪每条路径的反射并计算,最后将多条路径的结果求平均即可。伪代码如下。
到此解决了射线爆炸问题,但还没有彻底解决,因为shade函数的递归没有出口,永远不会停下。
因为在现实中光线是经过无数次反射的,如果在这里给光线追踪算法中设定最大反射次数的方式,这样就会导致能量损失,所以这里非常精妙的采用了俄罗斯轮盘赌(Russian Roulette)。
一把弹仓6发的左轮手枪,随机填充两发子弹,按下扳机时,目标有4/6的概率活下来,这就是俄罗斯轮盘赌的概念。
假设一个着色点出射的结果为Lo,期望递归停止后返回的结果还是Lo。那么,首先设定一个概率P(0<P<1),每次光线反射时有P的概率光线会继续递归并设置返回值为Lo/P,有1-P的概率光线停止递归,并返回0。这样巧妙的设定之下光线一定会在某次反射之后停止递归,并且计算的结果依然是无偏的,因为从概率论的角度来看,Radiance的期望Lo不变,证明如下。
shade函数的伪代码变更如下。
至此,路径追踪算法已经完成大半,只差最后一个小问题!
2.现在的路径追踪效率非常的低下
如下图所示,图中顶部为面光源,中间为遮挡物。左图表示每个像素使用更少的采样(Low SPP),右图是每个像素使用更高的采样(High SPP)。可以发现采样点更多,效果越好,越干净(噪点少),所以效率很低,最好在Low SPP情况下也能达到很好的效果。有没有办法呢?
其实是有办法提高效率的,如下图所示就是造成低效率的原因。
在每次计算直接光照的时候,通过均匀采样随机方向打出光线,但只有极少数的光线方向可以碰到光源,尤其当光源越小的时候,这种现象越明显,大量采样的光线都是无效浪费的。
蒙特卡洛方法允许任何采样方法,不仅限于均匀采样。因此我们可以直接对光源进行采样(如果没有别的物体遮挡,光线没有被“浪费”)。
假设光源的面积为A,那么对光源进行的采样分布p(A) = 1/A (因为光源面积p(dA)积分起来为1),但原始的渲染方程:
很明显是对光线方向ωi进行积分的,如果想要对光源进行采样并依然使用蒙特卡洛的方法,那么一定要将其修改为对光源面积 dA 的积分(改变积分域),换言之就是需要找到 dA 与 dω 的关系。如下图所示。
dA 与 dω 的关系如下。
关系式中的 cosθ’ 是为了计算出光源上微分面积元正对半球的面积,之后再按照立体角的定义dω = dA / rr,即除以着色点x与光源采样点 x’ 距离的平方。
于是根据图中二者的关系可将渲染方程改写如下。
这样便成功从对 ωi 的积分转到了对光源面积 A 的积分,然后就可以利用蒙特卡洛的方法对光源进行采样从而计算直接光照的积分值,因为是对光源进行采样所以就不需要俄罗斯轮盘赌了(不涉及弹射)。对于间接光照,依然采用先前的方法进行光线方向的均匀采样。最终伪代码如下,分直接光照和间接光照两部分计算。
计算直接光照的时候还需要判断光源与着色点之间是否有物体遮挡,该做法也很简单,只需从着色点 x 向光源采样点 x’ 发出一条检测光线判断是否与光源之外的物体相交即可,如图所示。
伪代码修改如下。
到这一步,路径追踪才真正完整了,路径追踪几乎可以达到100%真实感,如下图所示真实照片和路径追踪效果的对比,感受一下照片级真实(Photo-Realistic)。
3.2.5 遗留问题
1.路径追踪对点光源很难处理。只能把点光源做成面积很小的面光源。
2.路径追踪实现很困难。需要掌握的知识太多,比如物理量、微积分、概率、几何。