Whitted-style(递归式)光线追踪原理及实现细节
摘要
本篇文章主要分两个部分,第一部分会从为什么需要从光线追踪入手,一步步介绍Whitted-style光线追踪的原理,第二部分会具体介绍一些光线追踪的细节,包括光线的表示,光线与物体的求交,以及反射折射方向的计算。
Whitted-Style光线追踪
在进行原理讲解前,我们首先考虑一下为什么会需要光线追踪?因为Blinn-phong这种局部模型无法进行全局效果处理
举个例子:
如上图中房屋顶部的所接收到的光可不仅仅是Blinn-phong模型考虑的直接光源,还有可能是来自窗外的光源照射到地板上,再发生反射照射到了房屋顶部,而这部分光是局部光照模型没有考虑到的,而光线追踪正式为了解决这个问题所提出的一种考虑全局效果的光照模型。这是再说间接光照无法实现。
其次,软阴影也无法实现。
光线追踪的效果非常好,但是实时性比较差,开销高昂,所以一般是用于离线渲染。
1.1光线追踪原理
光线追踪听名字就知道,讨论的核心是光线,因此我们先对光线进行一些假设。
这些假设在物理学上不一定成立。
1.光线一定沿直线传播
2.光线之间无法碰撞
3.光线路径可逆,即从A发出到B的光线,一定也可以从B发出到A光线(中途可发生反射和折射)
根据第三条假设:光路可逆。
所有进入到人眼的光,都可以从人眼发出光按照原路反方向返回,那么利用这种模拟从人眼发射光线的方法就可以还原所有的光路,这就是光线追踪的核心思想,从光源出发难以模拟那就反着从摄像机发射光线。
第一步 RayCasting
从人眼或摄像机向近投影平面上的每一个像素点发射一条光线,判断与场景物体的交点,示意图如下:
当然一条光线自然可能会与不止一个物体相交,但是考虑到遮挡关系,只去找最近的交点。接着连接该节点和光源,只需要判断这条连线之间是否有物体存在就知道该交点是否在阴影之中(怎么样,是不是比shadow mapping那一套简单多了)
接着,自然可以利用Blinn-Phong模型对这个点进行局部光照模型计算,得到该像素的颜色,那么遍历所有近投影平面上的像素就能得到一张完整的图像。但如果光线追踪仅仅是在第一步Ray Casting就停止的话,那么他的效果和局部光照模型是一样的,因此我们需要第二部,真正的考虑全局效果。
第二步Recursive Ray Tracing
考虑第一步中所作的Ray Casting,该条光线第一个与圆球物体相交,假设该圆球是一个玻璃球,那么变回发生镜面反射,如图:
当然除了镜面反射之外,自然也会存在折射,同时反射与折射出去的光线会可能与场景中的物体再次碰撞,发生第二次折射与反射:
(为了图示清晰,图中仅以两次折射或反射的部分光线为例)从图中可以见到,不仅仅是与圆球相交的哪一点可以贡献光到眼睛,折射与反射之后在于物体相交的点也可以贡献光(光路可逆原理)。简言之,除了直接从光源照射到圆球交点再沿着eye rays(从眼睛发射的第一条光线)到眼睛中,也可能存在这样一种场景,有光照射到其他物体,再沿着eye rays的反射或者折射的光线防线传回人眼。
因此每一个交点的颜色贡献来自这样几种类型:直接光照,反射方向间接光,折射方向间接光(如果有折射的话)
下一步将这些所有交点与光源连接,称这些线为shadow rays(因为可以用来检测阴影),计算这些所有店的局部光照模型的结果,将其按照光线能量权重累加(该做法与递归过程等价,读者可以看看伪代码思考一下),最终得到近投影平面上该像素点的颜色!而这就是一个考虑全局效果的光照模型了,因为不仅仅考虑了直接光源的贡献,还考虑了各种反射和折射光线的贡献。
以上就是光线追踪的转增个过程了,还有额外绩点要注意的tips:
1.整体过程是依噶递归的过程,因此一定要有一定的递归终止条件,比如说允许的最大反射或折射次数为10.
2.光线在每次反射和折射之后都有能量损失,由系数来决定,因此越往后的折射和反射光的贡献能量越小,这也是为什么在上文中剃刀根据光线能量权重求和。
e.g.反射系数为0.7,那么第一次反射折损30%,第二次反射折损1-(70%*70%),依次类推。
3.如果反射或者折射光线没有碰撞到物体,一般直接返回一个背景色。
4.有一些关于光线的表述,以及如何求交点的实现细节在1.2中进行讨论。
参考的伪代码如下:
如果读者是第一次接触光线追踪的话,理解起来会有很多的难度,但是只要自己去亲自实现一遍,就能很快的理解其中的原理,因此我们在这里推荐一个我看过的很不错的教程:https://raytracing.github.io/books/RayTracingInOneWeekend.html。
手把手的教你用C++实现一个光线追踪器出来,并且还有很多细节方面原理知识的讲解,写的通俗易懂。
1.2光线的表示方法
我们可以将每一条光线想象成一条射线,那么每一条光线都会由起点及方向这两个属性所固定,如下图:
除了起点o,以及方向d之外,还额外定义了一个参数t来表示光线行进的长度。
1.3光线与物体求交的方法
光线与隐式曲面求交的方法:
首先介绍如何计算光线与隐式曲面的交点的方法,以一个球体为例,两者表示方程如下:
光线的表示方法在商界已经介绍过,对于一个球体来说,其表面上所有点p,到圆心c的距离是固定为R的,也就得到了上述的球的隐式曲面方程、
那么对于一个光线会在什么时候与球相交呢?
当然是在一个点即满足光线方程,有满足球体方程的时候,所以可以计算如下,把p=o+td带入球体方程,利用一元二次方程的解法即可得到参数t值:
同样的根据b²-4ac的正负关系,即可判断光线与球是一个交点还是两个交点又或是没有交点。
虽然这里只举了对一个球的隐式曲面交点的计算,对于所有其他隐式曲面过程都是类似的,只要将光线方程代入求解t即可,如下图所示:
光线与显式曲面求交的方法:
当然,真正在图形学中大量运用的其实是显式曲面,更具体来说,就是许许多多个三角形,因此如何判断一条光线与显式曲面的交点,其实就是计算光线与三角形面的交点。对于任意一个平面,可以用如下图中的式子来表示:
用点法式来表示平面。
图中对于平面方程的讲解已经很清楚。那么到这里其实已经成功把对显示曲面的求交又转化为了类似隐式曲面求交的方法,对于任意一个三角形来说,他一定处于一个平面智商,只需要求出光线与平面的交点,在判断该交点是否在三角形内,就可以得到光线是否与三角形面相交的结果了。
首先给出如何计算光线与平面交点的过程:
得到参数t之后,自然可以计算出交点,并且再去计算重心坐标就能判断该交点是否在三角形内了,但是这种方法略显繁琐,能不能一步就得到结果呢?当然可以!
直接将点的形式用重心坐标的形式来表示,随后用克莱姆法则求解线性方程组即可!
(推导过程省略,其实就是使用了线性代数知识里面的克莱姆法则)。
1.4反射与折射
反射方向的计算相对容易,如下图所示,一直l,n想要求出反射方向r。
计算如下:r=2n(l*n)-l,几何含义便是入射光线l在法向上投影的两倍再减去入射光线l方向,即可得到r。
1.42 折射方向的计算
折射方向的推导其实是由斯奈尔定理(Snell’s Law)得到的:
其中n,nt分别代表反射平面两边的反射率,如下图所示左半部分为n,右半部分为nt。
根据sin²+cos²=1以及(1)式,可以推出cos如下:
但是,这仅仅是求得了反射角度,更希望得到的是向量形式的方向t,因此根据上图,可以得到如下关系(图中皆为单位向量):
其中b是未知的,但是可以根据入射光线d推出
如此所有变量都是已知的,计算得到折射方向t如下:
至此就已经成功得到了折射方向,但是有没有什么问题呢?这就要说到菲涅尔反射了。
1.43菲尼尔反射(Fresnel Reflection)
注意在计算折射方向时,用到了
显而易见,
一定要确保根号里面的是正数,那么有没有可能会是负数呢?我们说这是有可能的,如果从一个折射率大的空间折射入一个折射率小的空间,折射角度会增大,且会有n/nt 》1,只要入射角度足够大,1-cos²就接近1,那么此时根号里面的数便会小于0,而这也就意味着,才是没有折射项,光线全部反射。
大家不妨在家实验一下,如果你垂直观察玻璃,你很容易看清玻璃外的东西(折射而来),如果你视线与玻璃近乎平行,此时你看到的大部分会是你自己(反射得到的),且实现越与玻璃平行,即与法线夹角越大,你的人像越加清晰,这种现象,就可以用菲涅尔反射来进行解释。
简单来说,便是物体的反射率其实与你的观察角度有关,对于绝缘物体来说观察角度与法线夹角越大,反射的程度就越大,如下图:
(其中光线极化什么的那两条线不用太去管,只要知道对于绝缘体角度越大反射越多即可),而导体则与绝缘体不同,他的反射率与夹角呈如下关系。
想想金属确实反射率一直很大,所以很有光泽。符合上图的规律。
那么对于任意一个物体该去怎么计算出它的精确的反射率呢?计算公式如下:
当然这里考虑了两个极化,然后再求平均,我们不需要知道为什么这样算,只要知道,物体的反射率和入射角度,和入射空间的折射率,和物体的折射率有关就可以了。然后套公式算就能得出正确的反射率了!那么对于精确的算法来说,可以看到计算量是非常大的,因此就有大佬提出了个简单的算法,近似得到结果,但计算量大大减小,如下:
Note:whitted-style光线追踪该如何考虑漫反射?
在blinn-phong模型中曾提到过,漫反射是光线照射到粗糙物体表面从而发生向周围均匀反射光线的一种现象,反射的光线可以说是无数的!!!
那么对于这种反射,在光线追踪该怎么处理呢?借鉴RayTracingInOneWeekend里的做法,对于漫反射表面每次进行反射的时候,随机的选取物体表面向外半圆内的一个方向作为该次反射的方向,对齐再像镜面反射及折射一样进行递归的光线追踪计算。
但对每一个像素不仅仅只发出一条感知光线,利用多条光线RayTracing的结果求均值,最终作为该像素的颜色值。
比如说我每个像素sample 1000条光线,如果撞到漫反射表面那就是1000条随机方向的RayTracing结果的均值,这样便能较为准确的模拟了漫反射表面的特性了。(对一个像素进行多次sample,其实也就把抗锯齿给做了)。
(tips:该方法其实更多算是path tracing,经典的whited-style光线追踪遇到漫反射表面会直接利用blinn-phong模型计算颜色值返回,而不再递归下去)。