上一节讲到了渲染方程,可以获得一个打出来的光线应该是自身发出的光和反射的光的总和。
Monte Carlo Integration(蒙特卡洛积分)
这是一种求解复杂函数(比如没有解析式的时候)定积分的数值方法。
定积分就是求解概率密度函数面积的过程。
证明可以参考蒙特卡洛积分的无偏性:https://zhuanlan.zhihu.com/p/365624460
这里有一个重要的想法就是,不要想它是怎么来的,只要证明它在期望上是无偏的,就可以使用它得到定积分了。
更直观的,当分布是均匀分布的时候。可以认为是取了N个采样点,对采样点\(x_i\)高度为\(f(x_i)\),底是(b-a),面积就是\(f(x_i)*(b-a)\),N个求和:
注意:这里的分布也可以是其他分布,但是要除以对应分布。
综上,蒙特卡洛积分:
因为是数值的方法,用采样去模拟积分的过程。所以采样越多,误差越小。
Path Tracking
在之前的Ray Tracking,主要是Whitted-Style的Ray Tracking),在Whitted-Style Ray Tracking中的定义是:
Whitted-style ray tracing:
- Always perform specular reflections / refractions(执行的都是镜面反光,对glossy这种反光没有定义)
- Stop bouncing at diffuse surfaces(遇到漫反射物体后会停下来,但是这是不对的,因为漫反射出来的肯定也要渲染出来)
从这两个角度上看,Whitted-Style Ray Tracing is Wrong(翻了之前的笔记,当初好像没有具体讲Whitted-Style Ray Tracing是怎么做的,大概介绍了Whitted-Style Ray Tracing的含义是光线会多次弹射)。但是上一节从物理层面推出的渲染方程是正确的,但是问题就是怎么解这个方程?
渲染方程的求解
求解包含两个问题
- 在半球上的积分
- 递归
首先,关于反射方程中的积分,使用第一部分介绍的蒙特卡洛积分解决。这里我们先简单的在半球面上实现遵从均分分布的采样:\(p(w_i)=1/2\pi\),并且认为打到的目标是光源。
我们定义shader(p,wo),p是弹射的位置,wo是弹射点到眼睛的连线。
带入,可得:
进一步,当我们打到的东西是物体的时候,这也是一个相似的过程。物体收到别的光源和物体弹射的光,让这个物体也变成一个光源(这里的光源都是面光源),这个时候这个p就是眼睛,那么这一物体光源q发出的光线就是shader(q,-wi)。wi是p到q的连线,对q来说,应该是-wi:
这里理论上渲染方程已经表达出来了,但是真的这么求解的话,会有一些问题:
- 问题一:假如我们采样很多,那我们的光线打出去之后,会以指数级增长。
- 问题二:递归算法需要定义出口。【目前只有一个出口就是碰到光源,但是有一些终点没打到光源的怎么办?】
针对问题一:
我们一次path tracking只进行一条线(也就是一个采样),但是可以进行多次path tracking【我认为也可进行多次采样,只不过要自己决定策略,不然光线数量就会爆炸】。
针对问题二:
最简单的想法是在一定弹射次数后跳出。但是这样的话,因为弹射次数越多其实我们的结果应该越好,但是不能无限的弹射下去,我们又想让我们的结果接近应有结果。于是,使用了俄罗斯轮盘赌,【也是一个无偏估计】(这里怎么和弹射次数关联起来呢?)
我们以P的概率射出,这部分得到的结果/P为最终结果。(1-P)的概率不射出,这样得到的结果是0。
在这之后,发现效率比较低。是因为有大量的光线弹射出去没有找到光源,得到0的返回值,这种光线就被浪费了。
那么如果可以对光源进行采样,是不是就会解决这种情况?但是蒙特卡洛积分只对我采样的东西积分(也就是积分的东西肯定和积分域是对应的),因此做一个积分域的转换在新的光源积分域上进行积分:
那么,就要找到半球上的w立体角和光源的单位面积之间的关系。这里应该是把光源想象成在一个更大的球上,成为单位立体角的话要除以大R的平方:
最终,式子为:
这样的话,对半球的积分就变成了对光源的积分,这样就是说打出去的光都会到光源上,那么就是光源直接打到p位置上的部分,也就是直接光照。但是和之前不一样的是,这部分不进行轮盘赌,我理解是这部分通过对光源计算可以准确算出来,就不要轮盘赌了。而间接的光【目的不是光源的物体,那这里本身会发光又会反射的怎么办?】要进行轮盘赌来做一个无偏估计。
还需要判断点和光源之间有没有物体遮挡:
最终是振奋人心的渲染效果:
总结
到这里,Path Tracking的基本想法就结束,当然存在一些问题。但是这里仅仅关注最终的Path Tracking部分:
也就是一条path渲染一个眼睛看到的一个点,
shade(p,wo):
-
直接光照计算的是能看到这个点的面光源【这里如果有多个面光源应该要每个单独计算然后加起来】:\(L\_dir=L\_i*f\_r*cos\theta *cos\theta'/ |x'- p|^2 / pdf\_light\) 【渲染方程+蒙特卡洛】
-
当弹射目标不是光源的时候,使用俄罗斯轮盘赌(RR),计算\(L\_indir\):
当生成的随机数大于P_RR时,go die,返回0;
小于P时,随机采样射线并且目标不是光源,然后计算:\(L\_indir=shade(q,-wi)*f\_r*cos\theta / pdf\_hemi/ P\_RR\)【蒙特卡洛积分除一次半球的面积,俄罗斯轮盘除一次P_RR】
return L_dir+L_indir
最终对来源于多个path的shade累加平均或者做别的操作,最终再转化为像素