光传输

光传输既不简单也不复杂,反而常常被误解。

在一个经典场景中,光在到达眼睛之前会在物体表面弹来弹去。正如前一章节所说,光反射的方向取决于材质类型(是否是漫反射,还是镜面反射,或者其它)。因此,光的路径是由光在到达眼睛的这条路上,和光线不断交互的材质所决定的。

想象从光源射出一束光线,经过漫反射表面之后,经过镜子,再经过漫反射表面,最后到达眼睛。用L表示光,D表示漫反射表面,S表示镜面(镜子可以看作是理想的镜面,其表面粗糙度为0),E表示眼睛,在本例中光的路径就是LDSDE。当然,你还可以假设任何组合作为路径,甚至路径中包含无限的D或者S,但有一点是最基本的,那就是以L开头,以E结尾。最短的路径是LE,这种情况发生在直接看光源时。光线会从光源直接射入眼睛(没有任何弹射)。只要光线发生一次弹射,那么路径不是LSE就是LDE,于是得到了一个直接光照(直接镜面或者直接漫射)。水面反射的太阳光就是直接镜面的一个例子。如果看到湖中反射的大山,那路径很可能是LDSE(假设大山是漫反射表面),这属于间接光照。

光路径 图1:光的路径

研究者Paul Heckbert在1990年发表的书名为《自适应光能传递纹理的双向光线追踪》里介绍了路径表示法。用正则表达式简化描述光的路径并不少见。举例任意漫射表面或镜面的反射组合可以写成:L(D|S)*E。在Regex(正则表达式的缩写)里,(a|b)*符号表示所有字符串的集合只有"a"和"b"元素,以及空字符串:{ɛ, "a", "b", "aa", "ab", "ba", "bb", "aaa", …​}。

基于这一点,可能会想,“这样虽然不错,但是怎么和渲染联系起来呢?”正如一直提到的,在真实世界中,光从光源到眼睛,但真正达到眼睛的只有一少部分光线。因此,模拟光的路径并非从光源到眼睛,一个更有效的方法是从眼睛,往回走到光源。

光线追踪作为代表,通过跟踪从眼睛出发的一条光线(通常称为眼光线主光线或者摄像头光线),检测这条光线在场景中和任何几何体的相交情况。一旦相交(交点为P),接下来需要做两件事:计算从光源(直接光照)开始有多少光达到P点,以及多少从其它表面反射出的光间接地达到P点。

  • 计算直接光照对于P点的作用,可以跟踪一条从P点到光源的线。如果这条线在途中和其它物体相交,则点P就是影子(有时也称为阴影线)。如图2所示。

阴影线 图2:计算直接光照,只需要从点P向光源投射一条阴影线。如果射线在途中被一个物体阻挡,点P就处在阴影中。

  • 间接光照来自于场景中其它物体对着P反射出的光,反射光可以来自光源,也可以来自其它物体之间的弹射。在光线追踪中,间接光照是通过产生新的射线来计算的,称为二次射线(图3)。我们将对此重点解释。

间接光照 图3:计算间接关照,需要从点P处生成二次射线,检测这些射线和其它表面是否有相交。如果相交,需要同时计算交点处的间接以及直接光照,并返回对于P点的光的总量。注意这是一个递归过程:每当一条二次射线接触到表面,就需要计算交点处的直接以及间接光照,这意味着会再次产生更多的二次射线。

如果二次射线和场景中其它物体或者表面相交,那么就有理由相信,光从表面沿着这些射线相交于点P。我们已经知道反射光的量,取决于有多少光达到表面以及视角方向。因此,为了知道沿着这些二次射线有多少光反射到P,我们需要:

  • 计算二次射线和表面间,达到交点P的光的总量。

  • 使用二次射线方向作为视角方向,测量从表面到点P实际反射了多少光。

记住镜面反射是视角依赖的:镜面表面反射多少光,取决于看的角度。漫反射不是视角依赖的:漫反射表面反射多少光和看的角度无关。所以除了漫反射,一个表面反射的光在所有方向上是不相等的。

计算二次射线和表面间,达到交点P的光的总量,和计算到达点P的光没有区别。计算到点P的光,取决于表面属性,通常由着色器完成。关于着色器我们下一章再说。

场景中的其它表面也可能把光反射到P点。显然我们无法知道是哪条光线,因为光可能来自于表面上方的任意方向(光也可以来自于表面下方,比如物体是透明或半透明的,这里不予考虑),不可能对各个方向进行测试,只能测试一小部分方向。原理就和测量一个国家的成年人平均身高是一样的。测试的人越多结果越精确,所以需要一个样本,比如几百或者几千个人,测量他们的身高,得到一个平均值(总数除以样本大小)如此就能得到一个近似值,但仅仅是近似,只能希望它接近真实数字(样本越大,越接近)。渲染用了同样的方法。只能采取一部分方向作为样本,然后得出平均值,作为实际结果。如果之前听说过*Monte Carlo*,具体叫*Monte Carlo ray tracing*,该技术说的就是这些。射出一些光线,得到近似实际光线达到的点。这个方法的缺点就是结果仅仅是一个近似值。优点是,能得到一个问题的解,虽然不太满意(有限时间内无法计算出精确结果)。

计算间接光照是一个递归过程。二次射线从P点出发,不断产生新的交点,新的交点再产生新的二次射线。我们可以记录光从光源出发到表面,再从表面直到点P的次数。光只从物体表面反弹了一次就到了P点称为一次反弹间接光照,反弹过两次的称为两次反弹,三次,四次的以此类推。

光线反弹 图4:计算间接光照是一个递归过程。每当二次射线碰到一个表面,会在交点产生新的光线用于计算间接光照。

物体表面的反弹次数可以是无限的(想象摄像机在一个盒子内部,光线可能会在四壁不停的反弹)。为了避免这种情况,通常会在几次(通常是1,2,3次)反弹后停止产生新的二次射线。注意,这样一限制反弹次数,可能会让P点看上去比实际来的暗(因为实际反弹到P点的光,可能因为反弹次数太多而被忽略掉)。如果设置二次反弹,那么之后的(三次、四次等)的反射光都会被忽略。幸运的是,光每次从表面反弹后,都会有一点点能量损耗。也就是说,当反弹次数增加,反弹光的间接光照就减弱。因此,有一点需要考虑的是,计算多次反弹得到的结果不一定有很大的意义。

计算间接光照时,我们假定每次和表面相交会产生32条光线(假设每条光线都会和表面相交),那么第一次反弹后,会产生32条光线。之后每条光线又会产生另外32条光线,一共是1024条。反弹三次后,总共会有32768条光线!由于光线以指数方式增加,如果使用光线追踪计算间接光照,消耗会增加的非常快。这也是光线追踪常常被诟病的一地啊。

经过这么一长串的解释,光不管是直接还是间接地碰到点P的计算原理还是比较简单的,尤其是使用了光线追踪方法。目前唯一的缺陷是计算反弹时需要设置一个上限,确保不会无限执行。在图形学中,这个算法叫单向路径追踪(它属于路径追踪,是光传输算法中一块比较大的课题)。它是基于光线追踪最简单、最基本的光传输模型之一。称其为单向是因为,只通过从眼睛到光源这一个方向。而“路径追踪”则顾名思义:跟踪光线在场景中穿梭的路径。

图5:当计算直接光照,只需要投射一条阴影线。如果阴影线在“通往光源的路上”和其它物体相交,该点就是阴影。

经典光线追踪生成图片的过程,就是追踪从眼睛射入场景的光线,经过反复的镜面反射和方向传递直到光源来模拟阴影的过程。(Paul S. Heckbert - 《自适应光能传递纹理的双向光线追踪》于1990年)

该方法最初是Appel在1986年提出的,后来由Whitted开发完成(用于着色显示的改进光照模型 - 1979)

最初的算法,Appel和Whitted只考虑了镜子表面和透明物体。这是因为对于这种材质计算二次射线(间接光照)要比漫反射表面要求小。计算镜子的间接光照,只需要向场景中投射一条反射光线,计算透明物体,只需要一条反射光线和一条折射光线。而当漫反射表面时,为了近似计算点P的间接光照,需要投射大量光线,这些光线一般分布于以入射点所在法线的半球范围内。如此开销要比只计算反射和折射大的多,所以第一个版本用到的概念在今天的标准看来是非常慢的 —— 但把算法扩展到间接漫反射却很直观。

相比其它技术,光线追踪不太适合计算全局光照。虽然光线追踪看上去是最合适用来模拟真实世界中散发的光线,但太复杂。以单向路径追踪为例,一些光线路径计算起来非常复杂。让我们来举个例子。

焦散 图6:点P上所有的光线均来自玻璃球,为了计算间接光照,会从P点产生二次射线,却只有其中一部分会碰到球。我们无法得到所有从球转移到光照点P上的光线,因此使用反向追踪来计算点P的间接光照,会非常不精确。

正如上图情况所示,光线从顶部向下投射,经过一个(透明)玻璃球产生折射,光线被聚焦于底部平面的一点上。这种现象称为焦散。注意,实际上从光源没有直接到P的光(P是球阴影的一点),全是来自玻璃球的间接光。这么看来,模拟光线从光源到眼睛看上去更自然。

当计算有多少光线间接地到达点P,如果假设P所在的表面是漫反射的,那么向随机方向生成几束光线,并检测场景内有多少表面把光转向了点P。我们发现,这么做根本无法统计出实际上有多少光来自于球体底部表面。或许生成出所有从点P朝向球体的光线可以解决这个问题,但因为我们预先不知道光是如何从光源达到每个点的(预先不知道光来自于球体上方,就无法假设这些光是折射光),所以这么做也没用。我们能做的,就是对其余所有表面在随机方向上生成光线,这也是单向路径跟踪所做的事情。其中可能会有碰到球体的光线,一路跟回光源(我们不能确保任何一条光线会碰到球体,因为方向是随机的),可能10条里面有1条,或者20条里面有一条,甚至100条里面有一条,因此是没法计算出点P的间接光照的。

是否百分之5或者10就足够了?是也不是。这个很难从技术角度解释,间接光照在点P的近似值在于相似度。比如当测量一个国家的成年人平均身高时,不会去测量所有人的高度,而是在小一部分人口中采样,然后测量样本的平均高度,再假设这个值足够接近于真实值。这个技术背后的理论并不简单(需要用数学证明,而不是凭经验),但概念非常容易懂。同理间接光照的近似值。我们选择随机方向,测量从这些方向来的光,取平均结果,再假设这个结果就是点P的实际间接光照的近似值。这个方法称为Monte Carlo积分。这是个非常重要的渲染方法,在之后的“图形学的数学和物理”会有更详细的解释。