16 光线追踪(蒙特卡洛积分与路径追踪)
1 蒙特卡洛积分(Monte Carlo Integration)
首先让我们先搞懂蒙特卡洛路径追踪的这个“蒙特卡洛”的前缀到底指什么。
蒙特卡洛积分的目的: 当一个积分很难通过解析的方式得到答案的时候可以通过蒙特卡洛的方式近似得到积分结果,如下图所示:
显然对于这样一个函数,很难去用一个数学式子去表示,因此无法用一般解析的方法直接求得积分值,而这时候就可以采用蒙特卡洛的思想了。
蒙特卡洛积分的原理及做法: 对函数值进行多次采样求均值作为积分值的近似
该做法十分容易理解,想象一下如果对上图这个函数值进行均匀采样的话,其实就相当于将整个积分面积切成了许许多多个长方形,然后将这些小长方形的面积全部加起来。没错,该做法其实就与黎曼积分的想法几乎一致。但蒙特卡洛积分更加的general,因为它可以指定一个分布来对被积分的值进行采样,定义如下:
因此,蒙特卡洛在此来说就是一个帮助求得困难积分值的方法。
2 蒙特卡洛路径追踪(Monte Carlo Path Tracing)
回顾一下上篇文章中所得到的渲染方程:
要想解出以上方程的解主要有两个难点:
- 积分的计算
- 递归形式
而解决这些难点自然就要利用上节中所提到的蒙特卡洛积分方法了。
在进入具体计算之前,对渲染方程做出一点小修改,即舍弃一下自发光项(因为除了光源其他物体不会发光), 以方便进行计算推导:
从具体例子出发,首先仅仅考虑直接光照:
此时采样的光线碰撞到了另一个物体的Q点,那么该条路径对着色点P的贡献是多少呢?自然是在点Q的直接光照再乘上反射到该方向上的百分比了!显然这是一个类似光线追踪的递归过程,不同在于该方法通过对光线方向的采样从而找出一条条可行的路径,这也正是为什么叫路径追踪的原因,伪代码如下:
至此,我们成功通过蒙特卡洛的方式解出了渲染方程的积分值,也通过考虑直接光照与间接光照解决了递归的问题。但该方法至此有一个非常致命的缺陷:
我们通过每次对光线方向的采样从而解出方程,假设每次采样100条,那么从人眼出发的第一次采样就是100条,在进行第二次反射之后就是10000条,依次类推,反射越多次光线数量便会爆炸增长,计算量会无法负担,那么如何才能使得光线数量不爆炸增长呢?唯有每次只采样一个方向!N=1
每次如果只采样一个方向那么所带来的问题也是显而易见的,积分计算的结果会非常的noisy,虽然蒙特卡洛积分是无偏估计,但样本越少显然偏差越大。但该问题很好解决,如果每次只去寻找一条路径结果不好,那么重复多次寻找到多条路径,将多条路径的结果求得平均即可!如下图所示:
改良之后的Path Tracing伪代码如下:
通过对经过像素的光线重复采样,每次在反射的时候只按分布随机选取一个方向,解决了只对经过像素的光线采样一次,而对反射光线按分布采样多次所导致的光线爆炸问题。
那么现在所有的问题都解决了吗?还没有!因为shade函数的递归没有出口,永远不会停下。 但这里并不没有采用类似光线追踪当中设定反射深度显示的给出递归出口的方法,而是非常精妙的采用了俄罗斯轮盘赌(Russian Roulette)。
至此,我们的路径追踪算法已经完成大半,只差最后一个小问题!现在的路径追踪效率非常的低下,如图所示:
tips:计算直接光照的时候还需要判断光源与着色点之间是否有物体遮挡,该做法也很简单,只需从着色点x向光源采样点x’发出一条检测光线判断是否与光源之外的物体相交即可,如图所示:
path tracing 的效果: