目录
Jeremy Birn——“If it looks like computer graphics,it is not good computer graphics.”
杰里米·伯恩——“如果它看起来像是计算机图形学生成的,那它就不是一个好的计算机图形学。”(皮克斯动画公司的光照技术总监)
渲染过程最终计算的是radiance,到目前为止,我们一直在使用反射方程(reflectance equation)来其进行计算:
其中是表面位置在观察方向上的出射radiance;是表面位置的上半球范围;是观察方向和当前光线入射方向上的BRDF;是从光线方向到达表面位置的入射radiance;是光线方向和表面法线之间的点积,并将负数结果clamp到0,即将来自表面下方的光线过滤掉。
11.1 渲染方程
反射方程是完整渲染方程的一种特殊情况,它由Kajiya在1986年提出[846]。渲染方程具有各种不同的表达形式,我们将使用以下这个版本:
其中多出来的一项为,它表示了从表面位置向观察方向发射的radiance,用于描述自发光表面的出射radiance。被积函数中有一项做了如下替换:
这一项意味着,从方向进入表面位置的入射radiance,等于另一个表面位置向相反方向的出射radiance。在这种情况下,这里的“另一个表面位置”由光线投射函数(ray casting function)所定义的,这个函数会从表面位置向方向上发射一条光线,并返回所击中的第一个表面位置,如图11.1所示。
渲染方程的含义很简单。为了对表面位置进行渲染,我们需要知道在观察方向上,离开表面位置的出射radiance ,它等于该点自身发射的radiance ,再加上反射出的radiance。有关光源发射和反射率的内容,在前面几章我们已经讨论过了。甚至这里的光线投射操作好像看起来也不是那么陌生,例如:z-buffer实际上就计算了从相机投射到场景中的光线。
这里我们所遇到的唯一的新项是,这一项明确指出,入射到某一点上的radiance,一定是从另一点发出的。不幸的是,这是一个递归项,也就是说,如果我们想要计算点在方向上上的出射radiance,那么我们首先还要知道来自表面位置的出射radiance,接下来还需要计算来自表面位置的出射radiance,直到无穷。令人十分惊讶的是,如此复杂的计算量,现实世界居然可以对其进行实时计算。
我们凭借直觉可以知道,光源照亮了一个场景,它所发出的光子(photon)在场景中四处反弹,每次与表面发生碰撞的时候,都会以各种方式被吸收、反射或者折射。渲染方程十分重要,因为它在一个简单的方程中总结了所有可能的光线路径。
渲染方程有一个重要的属性,即它与所发射出的光线呈线性关系。如果我们使光源的强度翻倍,那么最终的着色结果也会加倍变亮。同时,材质对于每种光源的响应也是相互独立的,也就是说,一种光源的存在并不会影响另一种光源与材质之间的相互作用。
在实时渲染中,只使用局部光照模型也是很常见的,我们只需要对可见点的表面数据进行光照计算即可,而这正是GPU最擅长的。传入GPU的各种图元被独立处理和光栅化,然后它们就会被丢弃,我们在点执行光照计算时,无法访问点的光照计算结果。诸如透明、反射和阴影效果,都是全局光照算法的范畴,它们利用了来自其他物体的信息,而不仅仅是被光源所照亮的物体。这些效果大大增强了渲染图像的真实感,并提供了视觉暗示(cues)来帮助观察者理解空间中的位置关系。同时,这些效果模拟起来也十分复杂,可能需要进行预计算或者渲染多个pass来计算一些必须的中间信息。
有一种思考光照问题的方法,即通过光子的传播路径来理解光照。在局部光照模型中,光子从光源出发,传播到表面上(忽略中间的物体),然后到达眼睛。阴影算法考虑了这些中间物体的直接遮挡效果。环境贴图可以捕捉从远处光源到达物体表面的光线,然后将其应用到局部的光泽物体上,这些物体会以镜面反射的方式,将这些光线反射到眼睛中。irradiance贴图还可以捕捉到光源对遥远物体的影响,并在半球范围的方向上进行积分,被这些物体所反射的光线会进行加权求和,从而计算出一个表面的光照效果,最终被眼睛所看到。
以一种更加正式的方式来思考光线传输路径的不同类型和不同组合,有助于理解现有的各种算法。Heckbert [693]提出了一个符号方案,它用于描述由某种技术所模拟的光线路径。光子从光源()到眼睛()的每次相互作用,都可以标记为漫反射()或者镜面反射(),还可以通过添加其他表面类型来进一步分类,例如“有光泽的(glossy)”,它代表了有光泽,但是又不像镜子的表面,如图11.2所示。可以使用正则表达式来简单地概括这些算法,从而展示它们所模拟的交互类型。表11.1对基本符号进行了总结。
从光源出发的光子可以通过各种路径最终到眼睛。最简单的路径是,光源被眼睛直接看到。一个基本的z-buffer是,或者写成其等价形式。光子离开光源,到达一个漫反射表面或者一个镜面,然后再到达眼睛。请注意,在一个基础的渲染系统中,点光源没有对应的物理表示,它不会被眼睛直接观察到。对于一个具有几何形状的光源而言,将会产生这样一个路径,除了照射到表面之外,从光源发出的光线也可以直接进入眼睛。
如果将环境映射添加到渲染器中,那么这个表达式就不再那么简单了。虽然Heckbert的表示法是从光源出发最终到达眼睛,但是对于渲染而言,从相反方向来构建表达式通常要更加容易。眼睛将首先看到一个镜面或者一个漫反射表面,即,如果这个表面是一个镜面,那么它也可以选择反射到一个环境贴图中的镜面,或者是一个漫反射表面上。因此,存在一条额外的可能路径:。同时再加上眼睛直接看到光源的路径,那么这个表达式最终会变为:。
可以将这个表达式展开:,它代表了所有可能存在的路径,或者简写为:。每一种表示方法在理解关系和限制方面都有各自的优势。这种符号表示法的部分用途是表达算法的效果,并能够以此为基础进行构建,例如:是生成环境贴图时所编码的内容,而则代表了随后访问该贴图的过程。
渲染方程本身也可以用简单的表达式来进行概括,即来自光源的光子在到达眼睛之前,可以与0到几乎无限数量的漫反射表面或者镜面发生相互作用。
对于全局光照的研究,主要集中在计算光线在这些路径上传播的方法。当将其应用于实时渲染时,我们通常愿意牺牲一些质量或者正确性,来换取更快的计算速度。最常见的两种策略就是简化和预计算。例如:我们可以假设所有反射到眼睛中的光线都是漫反射的,这种简化在某些环境和场景中表现很好。我们还可以离线环境中,对一些物体之间效果的相关信息进行预计算,例如生成记录表面光照水平的纹理,然后在运行过程中,根据这些存储的信息进行一些基本计算,从而获得全局光照效果。本章节将展示如何使用这些策略,来实时实现各种全局光照效果。
11.2 通用全局光照
我们在前面几章中,着重介绍了求解反射方程的各种方法。我们假设入射radiance 具有一定的分布,并分析了它是如何影响着色计算的。而在本章节中,我们将介绍用于求解完整渲染方程的算法。二者之间的区别在于,前者忽略了radiance的来源,它假设是直接给出的;而后者则明确地说明了这一点:到达某一点的radiance是从其他点发射或者反射而来的。
能够求解完整渲染方程的算法,可以生成令人惊叹的、照片级逼真的图像,如图11.3所示。然而对于实时应用来说,这些方法的计算成本都太高了,那么为什么我们还要讨论它们呢?第一个原因是:在静态或者部分静态的场景中,这样的算法可以在预处理阶段执行,并将计算结果存储下来,以供稍后在实时渲染期间使用。这在游戏中是一种十分常见的方法,稍后我们将对这类系统的不同方面进行讨论。
第二个原因是:全局光照算法都建立在严格的理论基础上,它们是直接从渲染方程中推导出来的,它们所做的任何近似都是经过仔细分析的。在设计实时解决方案的时候,可以且应该应用类似的推理思路;即使我们走了某些捷径,使用了一些技巧,但是我们也应当知道这么做的后果是什么,什么才应该是正确的方法。随着图形硬件变得越发强大,我们将能够做出更少的妥协和近似,并且能够创建出更加接近正确物理结果的实时渲染图像。
求解渲染方程的两种常用方法是有限元法(finite element)和蒙特卡罗法(Monte Carlo)。其中辐射度算法(radiosity)基于了第一种方法,而不同形式的光线追踪算法(ray tracing)则使用了第二种方法。在这两种不同思路的算法中,光线追踪要更加流行。这主要是因为它可以在同一个算法框架内,对一般的光线传输效果进行有效处理,包括体积散射等效果。而且光线追踪算法也更加容易扩展和并行化。
我们将简要介绍这两种方法,有兴趣的读者还应该参考其他优秀的书籍,它们涵盖了在非实时情况下求解渲染方程的细节[400, 1413]。
11.2.1 辐射度
辐射度算法(Radiosity)[566]是第一种用于模拟漫反射表面之间光线反弹的计算机图形技术,其名字来源于该算法所计算的物理量。在经典的算法形式中,辐射度算法可以计算相互反射以及面光源所产生软阴影。辐射度算法的基本思想相对简单,并且已经有了完整的书籍对这个算法进行介绍[76, 275, 1642]。光线会在环境中发生弹射,当我们打开一盏灯时,房间内的照明会很快达到平衡,在这种稳定状态下,每个表面都可以被看作是一个光源。基本的辐射度算法作出了一种简化的假设,即所有场景中的间接光都来自于漫反射表面。对于具有抛光大理石地板或者墙上有巨大镜面的场景而言,这个假设是不成立的,但是对于现实中的许多建筑而言,这是一个相对合理的近似。辐射度算法可以对无限数量的有效漫反射进行追踪。如果使用本章节开头所介绍的符号表示法,那么可以将它的光线传输路径写为是。
辐射度算法假设每个物体表面都由一定数量的面片(patch)组成。对于每个较小的区域(面片),辐射度算法都会计算一个平均辐射度值(radiosity value),因此这些面片的尺寸需要足够小,才能够捕捉所有的照明细节(例如阴影边缘)。这些面片不需要和底层表面的三角形一一匹配,甚至面片的大小尺寸也可以不一样。
从渲染方程出发,我们可以推导出第个面片的辐射度为:
其中代表了面片的辐射度;为面片的辐射出度(radiant exitance),即面片所发出的辐射度;是次表面反照率(详见章节9.3)。只有光源的辐射出度才不为0。是面片和面片之间的形状因子(form factor),这个形状因子的定义为:
其中是面片的面积;是点与点之间的可见性函数,如果它们之间没有物体遮挡光线,则该项为1,否则为0。角度值和分别是两个面片的法线,与点和点的之间连线的夹角。最后,是点和点的之间距离。如图11.4所示。
形状因子是一个纯粹的几何项,它描述了离开面片的均匀漫反射辐射能量,有多少能够入射到面片上[399]。两个面片的面积、距离、相对朝向、以及它们之间存在的任何表面,都会对它们的形状因子产生影响。想象现在有一个面片,假设它代表了一个计算机显示器;房间里的其他每个面片,都会直接接收到这个显示器所发出的部分光线。位于显示器背面或者看不见显示器的表面,它们无法接收到显示器发射出来的光线,对于这些表面而言,这个比例因子为零。这些比例因子的和为1。辐射度算法的一个重要部分,就是准确确定场景中面片之间的形状因子。
在计算出形状因子之后,所有面片的方程(方程11.4)会被组合为一个的线性系统(一个由线性方程组成的数学模型)。然后对这个线性系统进行求解,从而得到每个面片的辐射度值。随着面片数量的不断增加,会导致计算复杂度变得很高,求解这样一个矩阵的成本是相当大的。
由于这个算法的扩展性很差,并且存在一些其他的限制,因此经典的辐射度算法很少用于作为光照解决方案。然而,在现代实时全局光照系统中,预先计算形状因子,并在运行过程中使用它们来执行某种形式的光线传播,这个思想仍然很流行。我们将在章节11.5.3中来讨论这些方法。
11.2.2 光线追踪
光线投射(ray casting)是指从某个表面位置上发射一根光线,从而确定在特定方向上存在哪些物体的过程。光线追踪(ray tracing)使用光线来确定不同场景元素之间的光线传输。在其最基本的形式中,光线会从相机所在的位置出发,穿过像素网格进入到场景中。对于每条光线,都找到第一个与光线相交的物体。然后,通过从交点向各个光源发射光线,并查找交点与光源之间是否存在遮挡物体,来检查这个交点是否位于阴影中。不透明的物体会遮挡光线,透明的物体会减弱光线。还可以在交点处发射其他光线,如果表面具有光泽,则可以在反射方向上生成光线。这条光线会获取第一个相交物体的颜色,然后再对相交点进行阴影测试。也可以在透明固体的折射方向上生成光线,然后再进行递归计算。光线追踪的基本机制非常简单,以致于最基础的光线追踪渲染器的代码,甚至可以写在一张名片的背面[696]。
经典的光线追踪算法只能提供有限的效果:尖锐的反射和折射,以及硬阴影。然而,光线追踪的基本思想可以用于求解完整的渲染方程。Kajiya认识到[846],可以利用光线的发射机制以及评估它们所携带的光线能量,从而计算方程11.2中的积分项。方程11.2是递归的,这意味着对于每条光线,我们需要在不同的位置重新计算积分。幸运的是,我们已经有了处理这个问题的坚实数学基础。在曼哈顿计划(Manhattan Project)期间,为物理实验而开发的蒙特卡罗(Monte Carlo)方法就是专门为处理这类问题而设计的。我们并不是直接按照积分规则来计算每个着色点上的积分值,而是通过在积分域上采样一定数量的随机点,从而获得被积函数的具体数值。然后我们使用这些被积函数值来计算积分的估计值,采样点越多,最终的精度就越高。这种方法最重要的性质是,我们只需要对被积函数上的点进行求值计算,给定足够的时间,我们就可以以任意的精度来计算积分。在渲染领域中,这正是光线追踪所能提供的,当我们投射光线的时候,就相当于对方程11.2中的被积函数进行了点采样。尽管在交点处我们还需要递归计算另一个积分,但是我们不需要计算它的最终精确值,我们可以再次对这个积分进行点采样。当光线在场景中反弹的时候,就形象的建立了一条路径(path),我们沿每条光线路径,对被积函数进行一次计算,这个过程被称为路径追踪(path tracing),如图11.5所示。
对路径进行追踪是一个非常强大的概念。这些路径可以用于渲染光泽材质或者漫反射材质。使用它们,我们还可以生成软阴影、渲染透明物体以及焦散效果。通过对路径追踪进行扩展,我们还可以对体积内的点进行采样,而不仅仅是对物体表面进行采样,这样我们就可以处理雾和次表面散射等效果。
路径追踪的唯一缺点是,想要实现高视觉保真度的画面,所需要的计算复杂度是很高的。对于电影级的图像,我们可能需要对数十亿条路径进行追踪,因为我们计算的只是积分的估计值,而不是真实值。如果使用的路径太少,那么这种近似将会是不精确的,在一些特殊情况下会及其不精确,从而产生大量噪声。此外,即使是两个相邻的点,最终的着色结果也可能会差异很大,这与我们的期望不太相符,我们总是希望相邻点具有相似的光照结果。我们将这样的结果称为具有高方差(high variance),从视觉上看,方差会体现为图像中的噪声(如图11.6所示)。现在已经有了很多方法,可以在不增加额外追踪路径的前提下,消除或者减弱这种噪声所带来的影响。其中一种流行的技术是重要性采样(importance sampling),这个方法的思路是,通过向光线来源的主要方向发射更多的光线,从而大大降低方差。
许多已经出版了的论文和书籍对路径追踪及其相关方法进行了详细讨论。Pharr等人[846]为现代离线光线追踪技术提供了一个很好的介绍。Veach [846]为光线传输算法的现代推理奠定了数学基础。我们将在本章最后的章节11.7中,讨论交互式的光线追踪和路径追踪。
11.3 环境光遮蔽
上一小节中所介绍的通用全局光照算法,它们的计算成本都很高。虽然它们可以产生各种复杂的效果,但是生成一幅图像往往需要好几个小时。我们将首先介绍一些最简单的,但是在视觉上很有说服力的方法,并在本章节逐步探索实时替代方案,逐步构建更加复杂的效果。
一种基本的全局光照效果是环境光遮蔽(ambient occlusion,AO)。这项技术是在21世纪初,由工业光魔的Landis [974]所开发的,当时是用于提高电影《珍珠港》中,由计算机生成的飞机的环境光照质量。尽管这种效应的物理基础进行了相当程度的简化,但是最终的结果看起来却令人惊讶地可信。当光照缺乏方向变化,无法展现物体细节时,这种廉价方法可以提供对于物体形状的视觉暗示。
11.3.1 环境光遮蔽理论
环境光遮蔽的理论背景可以直接从反射方程中推导出来。这里为了简单起见,我们将首先关注Lambertian表面,该表面的出射radiance 与表面irradiance 成正比。irradiance是入射radiance的余弦加权积分,一般来说,它取决于表面位置和表面法线。同样,为了简单起见,我们将假设来自所有方向上的入射radiance都是恒定的,即。基于上述假设,此时计算irradiance的方程如下所示:
在这里,我们将对半球的所有可能入射方向进行积分。在恒定均匀光照的假设下,irradiance(以及由此产生的出射radiance)与表面位置和表面法线无关,并且在整个物体上都是恒定的。这会生成一个平坦均匀的外观。
方程11.6并没有考虑任何的可见性。着色点半球范围内的某些方向,可能会被自身物体的其他部分或者是场景中的其他物体所遮挡。在这些方向上将会具有不同的入射radiance,而不是恒定的。为了简单起见,我们假设来自这些遮挡方向上的入射radiance为零。虽然这个假设忽略了场景中可能会被其他物体反弹,并最终从这些遮挡方向到达点的光线,但是它极大地简化了推理过程。基于上述假设,我们可以得到以下方程,它由Cook和Torrance首次提出[285, 286]:
其中是一个可见性函数,如果从点向方向投射的光线会被物体遮挡,则该函数值为0,反之为1。
将可见性函数进行余弦加权积分,然后再进行归一化,最终的结果被称为环境遮挡系数:
这个系数代表了未被遮挡的半球的余弦加权百分比,它的范围位于内,对于完全被遮挡的着色点,它的值为0;对于没有任何遮挡的着色点,它的值为1。值得注意的是,凸面(convex)物体,例如球体或者立方体,不会对自身造成遮挡。因此当场景中不存在其他物体时,凸面物体的环境遮挡值将为1。如果物体表面存在凹陷区域,则这些区域的遮挡值将小于1。
在定义之后,考虑遮挡情况的环境irradiance方程为:
注意,在方程11.9中,irradiance会随着表面位置的变化而变化,因为确实是由表面位置所决定的,这样所得到的结果会更加真实,如图11.7所示。由于尖锐折痕处的值较低,因此这里的表面位置会显得较暗。
比较图11.8中的表面位置和,可以发现表面朝向也会对有影响,因为可见性函数在积分的时候会被余弦因子加权。比较图11.8左侧的表面位置和,虽然二者具有一个大小相同的未遮挡立体角,但是表面位置的大部分未遮挡区域都位于其表面法线附近,该位置上的余弦因子相对较大,从箭头的亮度就可以看出。相比之下,表面位置的大部分未遮挡区域都位于其表面法线的一侧,因此该位置的余弦因子相对较小,也就是说,在处的较低。从这里开始,为简单起见,我们将不再显式说明遮挡系数对表面位置的依赖。
除了,Landis [974]还计算了一个平均的未遮挡方向,它称为环境法线(bent normal),这个方向向量是未遮挡方向的余弦加权平均值:
其中符号代表了向量的长度。积分的结果再除以它自身的长度,可以得到归一化的结果,如图11.8右侧所示。这样产生的向量可以在着色期间代替几何法线,从而提供更加准确的结果,同时不需要额外的性能开销(详见章节11.3.7)。
11.3.2 可见性和obscurance
用于计算环境遮挡因子(方程11.8)的可见性函数需要仔细定义。例如:对于一个物体,比如人物或者车辆,定义这个函数是很简单的,我们只需要从表面位置向方向投射光线,然后检查光线是否会与同一物体的任何其他部分相交即可。然而,这种方法并没有考虑到附近其他物体的遮挡情况。通常,为了进行光照,可以假设物体被放置在一个平面上,通过将该平面加入到可见性计算中,可以实现更加真实的遮挡效果。这样做的另一个好处是,物体对地面的遮挡可以模拟接触阴影的效果[974]。
不幸的是,这种可见性函数方法对于封闭的几何体是不起作用的。想象现在有一个场景,它是一个包含各种物品的封闭房间。在这种情况下,所有表面的值都会为0,因为来自表面的所有射线都会击中某个物体(墙壁或者物体)。对于这类场景而言,经验方法会更加合适,它会试图重现环境遮挡的外观,但是不一定会对物理可见性进行模拟。其中的一些方法的灵感来自于Miller的可访问性着色(accessibility shading)[1211],该方法对在表面角落和缝隙处如何捕获污垢或者腐蚀进行了建模。
Zhukov等人[1970]引入了obscurance的思想,它通过使用距离映射函数来代替可见性函数,从而对环境光遮蔽的计算进行了修改:
可见性函数只有两个有效值,其中1代表没有相交,0表示有相交,而距离映射函数则是一个连续函数,其返回值取决于射线与表面相交之前所传播的距离。当相交距离为0时,函数的值为0;当相交距离大于设定的最大距离时,或者根本没有相交时,函数的值为0。对于相交距离大于的交点,实际上不需要进行任何测试,这样可以大大加快的计算速度。图11.9展示了环境光occlusion和环境光obscurance之间的区别。请注意,使用环境光occlusion进行渲染的图像要暗得多,这是因为即使在很远的距离也会对相交情况进行检测,这样会减小的值。
尽管人们试图从物理角度为其辩护,但实际上obscurance在物理上是不正确的。然而,obscurance通常可以给出符合观众期望的合理结果。obscurance方法的一个缺点在于,这个最大相交距离的值需要进行手动调整,才能达到令人满意的效果。这种类型的妥协会在计算机图形学中经常出现,一些技术可能并没有直接的物理基础,但是“在感知上却令人信服”。计算机图形学的目标通常是渲染一张可信的图像,因此这些技术当然是可以使用的。也就是说,基于物理理论的方法具有这样一些优点,它们可以自动进行工作,并且可以通过推理现实世界中的工作原理,来对其进一步改进。
11.3.3 考虑相互反射
尽管环境光遮蔽所产生的结果在视觉上是令人信服的,但是与完整全局光照模拟产生的结果相比,环境光遮蔽的结果要更暗一些,如图11.10中图像的对比。
环境光遮蔽与完整全局光照之间的一个重要区别是相互反射(interreflection)。方程11.8假设被遮挡方向上的radiance为零,但是实际上相互反射会为这些方向引入一个非零的radiance。如图11.10所示,与右侧模型相比,左侧模型的折痕处和凹陷处会更暗。这种差异可以通过适当增加的值来解决。使用obscurance距离映射函数来代替可见性函数(章节11.3.2)也可以缓解这个问题,因为obscurance函数的值通常会大于零。
以一种更加精确的方式来追踪相互反射是很昂贵的,因为它需要求解一个递归问题。想要给一个点进行着色,必须首先对其他点进行着色,以此类推。虽然计算的值相比于执行一个完整的全局光照计算而言要便宜得多,但是我们还是希望能够以某种形式来包含这部分丢失的光线,从而避免过度暗化。Stewart和Langer [1699]提出了一种廉价、但是却惊人准确的方法来近似相互反射。它基于在漫反射光照下对Lambertian场景的观察,即从一个给定位置能够看见的表面,往往会具有相似的radiance。我们假设遮挡方向的radiance ,等于当前着色点的出射radiance ,从而打破了递归,可以得到这样一个解析表达式:
其中是次表面反照率,或者叫做漫反射率。方程11.12相当于使用一个新的环境遮挡因子来代替之前的:
方程11.13倾向于让环境遮挡因子变得更大(更亮),从而使得它在视觉上更加接近一个完整全局光照所产生的结果,它在一定程度上模拟了相互反射效应。这种效应高度依赖于的值,其隐含的假设是:在着色点附近的表面颜色是相同的,这样可以产生有点像类似颜色渗透(color bleeding)的效果。Hoffman和Mitchell [755]使用了这种方法,从而可以实用天光来照亮地形。
Jimenez等人[835]提出了一种不同的解决方案。他们对许多场景执行了完整的离线路径追踪,每个场景都使用均匀的、白色的、无限远的环境贴图来进行照亮,从而获得考虑相互反射的适当遮挡值。在此基础上,他们拟合了一个三次多项式,来对环境遮挡因子和次表面反照率映射到遮挡值的函数进行近似,这个新的遮挡值会相互反射的光线照亮。他们的方法同样假设反照率是局部恒定的,并且可以根据给定点的反照率,推导出入射反弹光的颜色。
11.3.4 预计算环境光遮蔽
环境遮挡因子的计算可能会很耗时,通常都是在渲染之前离线计算的。预计算任何与光照相关的信息(包括环境光遮蔽),这个过程通常被称为烘焙(baking)。
预计算环境光遮蔽最常见的方法就是蒙特卡罗方法。发射光线并检查光线与场景的交点,然后对方程11.8进行数值计算,例如:我们在法线的半球方向上均匀随机选择个方向,然后沿着这些方向发射光线并进行追踪。基于光线的相交结果,我们对可见性函数进行计算,这种计算环境光遮蔽的方法,可以表达成如下方程:
在计算环境光obscurance时,可以将投射的光线限制在一个最大距离内,通过最大距离内的相交距离来计算的值。
环境光occlusion或者环境光obscurance的环境遮挡因子,其计算过程都包含一个余弦加权因子。虽然我们可以像方程11.14那样将其直接纳入计算,但更加有效的方法是通过重要性采样。现在我们对光线发射的分布进行调整,将其修改为按余弦加权进行发射光线,而不是先在半球范围内均匀投射光线,然后再对结果进行余弦加权。换句话说,光线会更有可能被投射到接近表面法线的方向上,因为来自这些方向的结果具有更大的贡献值,它们是更加重要的样本。这种抽样方案被称为Malley方法。
环境光遮蔽的预计算可以在CPU或者GPU上进行。在这两种计算环境下,都有一些针对复杂几何图形的光线投射加速库。其中最受欢迎的两个分别是:用于CPU的Embree [1829]和用于GPU的OptiX [951]。在过去,来自GPU管线的生成结果,例如深度图[1412]或者遮挡查询[493]等,也会被用于计算环境光遮蔽,但是随着更加通用的光线投射解决方案在GPU上的日益普及,它们在今天不太常用了。大多数商业建模和渲染软件都会提供预计算环境光遮蔽的选项。
遮挡数据对于物体上的每个顶点都是唯一的。它们通常会存储在纹理、体积或者网格顶点中。而无论存储的信号类型如何,不同存储方法都具有类似的特点和问题。同样的方法还可以用于存储环境光遮蔽、定向遮蔽或者预计算光照等,详见章节11.5.4。
预计算数据也可以用来模拟物体之间的环境光遮蔽效果。Kontkanen和Laine [924, 925]将物体对其周围的环境光遮蔽效应存储在一张立方体贴图中,并将其称为环境光遮蔽场(ambient occlusion field)。他们使用了一个二次多项式的倒数,来模拟环境光遮蔽随物体之间距离的变化情况。这个多项式的系数存储在一张立方体贴图中,以模拟遮挡的方向性变化。在运行过程中,利用遮挡物的距离和相对位置来获取合适的系数,从而对遮挡值进行重建。
Malmer等人[1111]将环境遮挡因子和环境法线(可选)存储一个三维网格中,从而对结果进行了改进,他们将这个三维网格称为环境光遮蔽体(ambient occlusion volume)。这种方法的计算成本较低,因为可以从纹理中直接读取出环境遮挡因子,不用实时计算。与Kontkanen和Laine的方法相比,这种方法需要存储的标量值要更少一些,两种方法的纹理分辨率都比较低,总体的存储需求是类似的。Hill [737]和Reed [1469]描述了Malmer等人的方法在商业游戏引擎中的实现,他们对算法的各个实现方面以及一些有用的优化方法进行了讨论。这两种方法都适用于刚体物体,而且可以扩展到具有少量运动部件的铰接物体上,其中每个运动部件都会被视为一个单独的物体。
无论我们选择哪种方法来存储环境光遮蔽值,我们都需要明确,我们正在处理的是一个连续信号。当我们从空间中的特定点发射光线的时候,我们进行的是采样(sample);当我们在着色之前对这些数值插值的时候,我们进行的是重建(reconstruct)。信号处理领域中的所有相关工具都可以用来提高这个采样-重建过程的质量。Kavan等人[875]提出了一种方法,他们将其称之为最小二乘烘焙(least-squares baking)。在这个方法中,遮挡信号会在网格上进行均匀采样,然后推导出顶点对应的值,从而可以在最小二乘法中,将插值和采样顶点之间的总差异最小化。他们还专门讨论了在顶点存储数据的方法,同样的推理方法也可以用于导出存储在纹理或者体积中的值。
《命运》是一款广受好评的游戏,它使用了预计算的环境光遮蔽作为基础的间接光照解决方案(如图11.11所示)。这款游戏是在两代主机硬件之间的过渡时期发行的,因此它需要一个解决方案,来平衡新平台上预期的高质量画面与老平台上性能和内存使用限制。这个游戏的其中一个特点是,一天中的光照效果会随着时间动态变化,因此任何预计算的解决方案都必须正确地考虑到这一点。开发者选择使用环境光遮蔽,是因为它在有着较低开销的同时,可以提供可信的外观表现。同时,由于环境光遮蔽将可见性计算与光照渲染过程相解耦,因此可以在一天中的任何时间,使用相同的预计算数据。Sloan等人[1658]完整介绍了这个系统,包括基于GPU的烘焙管线。
育碧的《刺客信条》[1692]和《孤岛惊魂》[1154]系列,也使用了一种预计算的环境光遮蔽,来增强他们的间接光照解决方案。他们以自上而下的视角来渲染世界,并对产生的深度图进行处理,从而计算大范围的遮挡信息。根据相邻深度样本的分布情况,采用了多种启发式算法来对遮挡值进行估计。通过将世界空间位置投影到纹理空间中,所产生的世界空间AO贴图可以应用于所有物体。他们将这种方法称为世界AO(World AO)。Swoboda [1728]也描述了类似的方法。
11.3.5 环境光遮蔽的动态计算
对于静态场景,可以预先计算环境遮挡因子和环境法线。但是,对于存在移动或者形状改变物体的场景,必须要通过实时计算这些参数才能获得更好的结果。执行这个操作的方法按照空间可以划分为两类:在物体空间中执行的方法;在屏幕空间中执行的方法。
计算环境光遮蔽的离线方法,通常会从每个表面点向场景中投射大量光线(数十到数百条),并对交点进行检查。这是一个成本很高的操作,而实时渲染中则重点关注如何进行近似,或者如何避免大部分的计算。
Bunnell [210]通过将表面建模为放置在网格顶点处的圆盘元素,从而计算环境遮挡因子和环境法线。这里选择圆盘的原因是,圆盘之间的遮挡情况可以通过解析计算获得,不需要单独投射光线。简单地将一个圆盘与所有其他圆盘的遮挡因子加起来,会产生双重阴影从而导致表面过暗。也就是说,如果一个圆盘位于另一个圆盘的后面,那么这两个圆盘都将被视为遮挡表面,但是实际上只有最近的圆盘才应当是。Bunnell使用了一种巧妙的两pass方法来避免这个问题:第一个pass正常计算环境光遮蔽效果,包括错误的双重阴影在内。在第二个pass中,会根据第一个pass中的遮挡情况,来减少每个圆盘的贡献值。实际上这只是一个近似值,但在实践中,它能够产生令人信服的结果。
计算每对元素之间的遮挡情况具有的复杂度,除非场景构成十分简单,否则这个复杂度对于实时渲染而言太高了。对于远距离的表面,可以使用一些简化的表示,从而降低部分计算开销。Bunnell构建了一个分层的元素树,其中每个节点都是一个圆盘,它代表了其子树圆盘的聚合。在进行圆盘之间的遮挡计算时,对于较远的表面会使用较高层级的的节点。这可以将时间复杂度降低到,这是一个更加合理的复杂度。Bunnell的技术很高效,并且能够产生高质量的结果,该技术被应用在了加勒比海盗电影(Pirates of the Caribbean)的最终渲染中[265]。
Hoberock [751]对Bunnell的算法进行了几项修改,使用更高的计算成本进一步提高了质量。他还提出了一种距离衰减因子,其结果与Zhukov等人[1970]所提出的obscurance因子相类似。
Evans [444]描述了一种基于符号距离场(signed distance field,SDF)的动态环境光遮蔽近似方法。在这种表示方法中,物体会被嵌入到一个三维网格中。网格中的每个位置都会存储到最近物体表面的距离。对于在物体内部的点,这个值为负;对于在物体外部的点,这个值为正。Evans在体积纹理中创建并存储场景的SDF。为了估计物体上某个表面位置的遮挡情况,他使用了一种启发式方法,该方法会沿着表面法线进行点采样,这些点会距离表面越来越远。Quilez指出[1450],当SDF以解析方式进行表示(章节17.3),而不是存储在三维纹理中的时候,也可以使用相同的方法进行处理。虽然这种方法是非物理的,但是生成的结果在视觉上令人满意。
Wright [1910]进一步扩展了使用符号距离场来进行环境光遮蔽的方法。Wright并没有使用启发式方法来生成遮挡值,而是进行了锥形追踪(cone tracing)。这个圆锥的顶点位于着色点,并对编码在距离场中的场景表示进行相交测试。锥形追踪会沿轴执行一组步进操作,在每一次步进之后都会使用一个更大半径的球,来与SDF进行相交测试。如果此时距离最近的遮挡物距离(从SDF中采样的值)小于球体的半径,那么圆锥的这部分就会被遮挡(如图11.12所示)。如果仅仅追踪一个锥形区域,那么结果将是很不精确的,并且无法包含余弦项,出于这个原因,Wright追踪了一组覆盖整个半球的圆锥,从而来估计环境光遮蔽。为了提高视觉保真度(visual fidelity),他的解决方案不仅使用了场景的全局SDF,还使用局部的SDF,这个局部SDF用于代表单个物体或者在逻辑上相连接的物体集合。
Crassin等人[305]在场景的体素表示中描述了一种类似的方法。他们使用稀疏体素八叉树(章节13.10)来存储场景的体素化信息。他们用于计算环境光遮蔽的算法,实际上是一种通用完整全局光照算法的特例(详见章节11.5.7)。
Ren等人[1482]则将遮挡物近似为球体,如图11.13所示,并使用球谐函数来表示表面点被单个球体遮挡的可见性函数,这样一组球体聚合起来的可见性函数,就是单个球体可见性函数的乘积。但不幸的是,计算球谐函数的乘积是一个成本很高的操作。他们的核心思想是:对单个球谐可见性函数的对数进行求和,然后再对结果取指数。这样所产生的结果与可见性函数相乘的结果相同,但是球谐函数的求和操作,其计算成本明显要比乘法小。这篇论文表明,在正确的近似方法下,可以通过执行快速的对数运算和指数运算,从而获得整体加速效果。
这种方法计算出的不仅仅是环境遮挡因子,而是一个完整的球面可见性函数,它使用了球谐函数来进行表示(详见章节10.3.2)。其中,球谐函数的第一个系数(0阶)可以作为环境遮挡因子,后面三个系数(1阶)可以用于计算环境法线。更高阶的系数可以用于阴影环境贴图或者圆形光源。由于这种方法将几何体近似为包围球,因此无法对来自折痕或者其他小细节的遮挡情况进行建模。
Sloan等人[1655]在屏幕空间中,对Ren所描述的可见性函数进行了求和。对于每个遮挡物,他们都会考虑一组像素,这组像素距离着色点的距离,小于所规定的世界空间距离。这个操作可以通过渲染一个球体,并在着色器中执行距离测试或者使用模板测试来实现。对于所有受到影响的屏幕区域,会将一个适当的球谐函数值添加到一个离屏缓冲区中。在获得所有遮挡物的可见性之后,会对缓冲区中的值进行求幂运算,最终获得每个屏幕像素上的组合可见性函数。Hill [737]使用了相同的方法,但是他将球谐可见性函数限制到二阶系数。在这种假设下,球谐函数的乘积运算只涉及到少量的标量乘法,甚至可以通过GPU的固定功能混合硬件来完成。这使得我们可以在性能有限的主机硬件上使用这种方法。由于该方法只使用了低阶的球谐函数,因此无法生成具有清晰边界的硬阴影,只能生成无方向的遮挡。
11.3.6 屏幕空间方法
基于模型空间的方法,其开销与场景的复杂度成正比。然而,我们完全可以从屏幕空间中已有的数据出发,推导出一些有关遮挡的信息,例如深度和法线。这种基于屏幕空间的算法,具有恒定的开销,其复杂度与与场景的细节程度无关,只与渲染时所使用的画面分辨率有关。
在实践中,屏幕空间算法的执行时间,还取决于数据在深度缓冲或者法线缓冲中的分布,因为这种数据分散效应,在进行遮挡计算的时候,会降低GPU缓存的命中率,从而延长算法的执行时间。
Crytek [1227]开发了一种动态的屏幕空间环境光遮蔽(screen-space ambient occlusion,SSAO)算法,并用在了《孤岛危机》中。他们使用z-buffer作为唯一的输入,在一个全屏pass中计算来环境光遮蔽效果。每个像素都有一个环境遮挡因子,它会在该像素周围的球形范围内采样一组点,并将样本与z-buffer进行深度测试,然后来估计。的值与z-buffer中位于像素点深度前面的测试样本有关,通过的样本数量越少,的值就越低,如图11.14所示。与obscurance因子相类似[1970],这些样本的权重会随着到像素距离的增大而减小,即距离像素越远,该样本的权重就越小。需要注意的是,由于这些样本并没有被余弦因子加权,因此所产生的环境光遮蔽效果是不正确的。该方法会将球形范围内的所有样本都考虑在内,而不是只考虑表面上半球范围内的样本。这种简化意味着会对表面以下的样本进行计数,但是实际上我们是不应当对它们进行计数的。这样做会导致表面变暗(因为环境遮挡因子变大了),同时边缘会比周围环境更亮。尽管如此,最终产生结果在视觉上令人十分满意,如图11.15所示。
Shanmugam和Arikan [1970]同时提出了一种类似的方法。在他们的论文中,他们描述了两种方法,其中一种可以从附近的小细节中生成良好的环境光遮蔽效果;另一中可以从较大的物体中生成较为粗略的环境光遮蔽效果。将二者的结果结合起来,就可以生成最终的环境遮挡因子。其中,他们的精细尺度环境光遮蔽方法使用了一个全屏pass,在这个pass中,不仅访问了z-buffer,还访问了可见像素表面的法线缓冲。对于每个着色像素,会从z-buffer中对附近的像素进行采样,被采样的像素分布在球体内部,会根据其法线信息来计算着色像素的遮挡项。这种方法并没有将双重阴影考虑在内,因此结果会显得有点暗。他们的粗略遮挡方法,与Ren等人的物体空间方法相类似(我们在上文中讨论过),它同样将遮挡几何体近似为球体。然而不同的是,Shanmugam和Arikan的方法是在屏幕空间进行遮挡计算的,并使用了与屏幕对齐的广告牌,来覆盖每个遮挡球体的“效果区域”。与Ren等人[1482]的方法不同,这里的粗略遮挡方法也没有考虑双重阴影。
由于这两种方法极其简洁,因此很快引起了工业界和学术界的注意,并催生了大量的后续工作。许多方法,例如Filion等人[471]在游戏《星际争霸II》中所使用的方法,以及McGuire等人[471]所使用的可扩展环境光obscurance方法,都使用了这种特别启发式方法(ad hoc heuristics)来生成遮挡因子。这类方法具有良好的性能表现,并暴露出了一些参数,可以通过手动调整参数来达到预期的艺术效果。
其他的一些方法旨在提供更有原则和理论基础的遮挡计算方法。Loos和Sloan [1072]注意到,Crytek的方法可以被解释为蒙特卡洛积分。他们将计算出来的值称为体积obscurance,并将其定义为:
其中是围绕该像素点的一个三维球形邻域;是距离映射函数,与方程11.11所描述的相类似;是距离函数;是占用函数(occupancy function),如果未被占用,则等于0,否则等于1。他们注意到,函数对于最终视觉质量的影响很小,因此可以使用常数函数。在这个假设下,体积obscurance是对占用函数在像素点邻域上的积分。Crytek的方法是在三维邻域内进行随机采样从而计算积分,而Loos和Sloan则通过对像素的屏幕空间邻域随机采样,在维度上进行积分,对轴的积分过程则是解析的。如果该点的球面邻域中不包含任何几何图形,则积分值等于射线与球体相交的长度。如果该点的球面邻域中存在几何图形,则会使用深度缓冲来作为占用函数的近似值,并且仅会在每个线段的未占用部分上进行积分,如图11.16左侧所示。该方法最终生成的结果,其质量与Crytek的方法相当,但是使用的样本数量较少,因为在其中一个维度上(轴)的积分是精确的。如果可以使用表面法线的话,还可以对这个方法进一步扩展,从而获得更好的结果。在这个考虑法线的版本中,线积分会被限制在由像素点法线所定义的平面上。
Szirmay-Kalos等人[1733]提出了另一种使用法线信息的屏幕空间方法,它被称为体积环境光遮蔽(volumetric ambient occlusion)。方程11.6描述了在法线半球上进行的积分,这个积分还包含了余弦项。他们提出,这种类型的积分,可以将被积函数中的余弦项移除,并使用余弦分布来限制积分范围,从而对余弦因子进行近似。这样做可以将积分转换到一个球面上,而不是在一个半球上;这个球体的半径为半球的一半,并且会沿着法线移动一个球体半径的距离,最终这个球体会与半球内接,被半球完全包裹。其中未被占用部分的体积,其计算方法与Loos和Sloan的方法一样,都是通过在像素邻域上进行随机采样,并在轴上对占用函数进行解析积分,如图11.16右侧所示。
Bavoil等人[119]提出了一种不同的方法,用于解决估计局部可见性的问题,他们从Max [1145]的视界映射(horizon mapping)中获得了灵感。他们的方法被称为基于视界的环境光遮蔽(horizon-based ambient occlusion,HBAO),它假设z-buffer中的数据表示了一个连续的高度场。通过确定视界角(horizon angle),可以对像素点的可见性进行估计,这里的视界角,指的是切面上方被邻域遮挡的最大角度。也就是说,对于某个点上的给定方向,我们会记录最高的可见物体所对应的角度。如果我们忽略积分中的余弦项,那么环境遮挡因子可以被计算为视界上未被遮挡部分的积分,或者是1减去视界下被遮挡部分的积分:
其中是切平面的视界角;是切平面与观察向量的切角(tangent angle);是衰减函数,如图11.17所示。积分前面的是归一化系数,它将积分的结果归一化到之间。
对于定义视界的角度,我们利用角度的线性衰减,可以解析地计算内部的积分:
这个剩余的积分,是通过对一些方向进行采样,来找到视界角度,从而进行数值计算的。
Jimenez等人[835]也使用了这种基于视界的方法,他们称之为真实环境光遮蔽(ground-truth ambient occlusion ,GTAO)。他们的目标是实现ground-truth的结果,并能够与光线跟踪的结果相匹配,该方法所使用的唯一信息,就是由z-buffer构建的高度场。HBAO在计算遮挡的时候并不包括余弦项,并且它还增加了一个特殊的衰减(没有出现在方程11.8中),因此它的结果最多只能与光线追踪相接近,但是始终还是不一样的。GTAO引入了缺失的余弦因子,去除了这个特殊的衰减函数,并在绕观察向量的参考系中给出了遮挡积分,该方法的遮挡因子定义如下:
其中和为给定的左右视界角;为表面法线与观察方向之间的夹角。这里积分的归一化项为,这与HBAO中的不同,因为GTAO包含了余弦项,这使得开放半球的积分结果为,如果方程中不包含余弦项,则开放半球的积分结果为。在给定高度场假设的情况下,方程11.18与与方程11.8完全匹配,如图11.17所示。这里的内部积分仍然可以进行解析求解,因此只需要对外部积分进行数值计算即可,这个积分过程与HBAO中的积分过程基本相同,都是对给定像素周围的多个方向上进行采样。
在这些基于视界的方法中,成本最高的操作就是沿着屏幕空间的线段对深度缓冲进行采样,从而确定视界角度。Timonen [1771]提出了一种方法,专门用于提高这一步的性能表现。他指出,用于估计给定方向上视界角度的样本,可以在屏幕空间中沿直线排列的像素之间进行大量重用。他将遮挡计算分为两步,首先,他会在整个z-buffer中执行线段追踪。在追踪的每一步中,他都会根据所规定的最大影响距离,在沿着线段移动的时候更新视界角度,并将这个信息写入一个缓冲区中。在视界映射(horizon mapping)中,每个屏幕空间方向上都会创建一个这样的缓冲区。这些缓冲区的大小不需要与原始的深度缓冲区相同,而是取决于线段之间的间距,以及沿着线段的步长,在选择这些参数的时候有一定的灵活性。不同的设置会对最终的质量产生影响。
第二步是根据存储在缓冲区中的视界角度信息来计算遮挡因子。Timonen使用HBAO(方程11.17)所定义的遮挡因子,但是也可以使用其他遮挡估计方法,例如GTAO中的遮挡因子(方程11.18)。
深度缓冲并不是一个完美的场景表示,因为在一个给定的方向上,只有最近的物体会被记录下来,我们实际上并不知道它背后发生了什么。有许多技术使用了启发式方法,来尝试推断可见物体的厚度信息,这些近似值在许多情况下都表现良好,人眼对于稍微不准确的结果是很宽容的。虽然有一些方法使用了多层深度来缓解这个问题,但是由于将其集成到渲染引擎中太过复杂,并且这类方法的运行时成本很高,因此它们从未流行过。
屏幕空间中的方法依赖于对z-buffer进行反复采样,从而在给定点周围构建一些简化的几何模型。实验表明,想要获得较高的视觉质量,可能需要多达几百个样本,这个级别的样本数量太多了,想要用于交互式渲染,每个像素最多只能采样10-20个样本,甚至更少。Jimenez等人[835]报告提到,为了适应60 FPS的性能预算,他们只能在每个像素上使用1个样本!为了弥合理论和实践之间的差距,屏幕空间方法通常会采用某种形式的空间抖动。在最常见的形式中,每个屏幕像素都会使用略有不同的随机样本集合,然后进行旋转或者径向移动。并在AO计算的主要阶段之后,执行一次全屏的滤波pass。联合双边滤波(章节12.1.1)可以避免在表面的不连续处进行过滤,从而保持尖锐的边缘。它可以利用可用的深度信息或者法线信息来对过滤进行限制,即它只会对属于同一表面的样本进行过滤。还有一些方法使用了随机变化的采样模式,以及经过实验选择的滤波核;另一些方法则使用了固定大小的屏幕空间采样模式(例如像素),以及一个限制在该邻域上的滤波核。
环境光遮蔽的计算也可以在时域上进行超采样[835, 1660, 1916]。通常会在每一帧中应用不同的采样模式,并对计算出来的遮挡因子进行指数平均从而实现这个目的。使用上一帧的z-buffer、相机变换和动态物体的运动信息,来将上一帧的数据重新投影到当前视图中,然后再将其与当前帧的结果进行混合。还会使用一些基于深度、法线、速度的启发式方法,来检测上一帧数据的可靠性,对于不可靠的数据需要丢弃(例如:由于一些新物体进入了视野中,因此上一帧中的数据与当前帧存在差异)。章节5.4.2在更一般的情况下,介绍了时域超采样和时域抗锯齿技术。时域过滤的成本较小,并且很容易实现,虽然它并不总是完全可靠的,但是在实践中出现的大多数问题都不太明显。这主要是因为环境光遮蔽不会直接单独显示在画面上,它只是光照计算的输入之一。在将这种环境光遮蔽效果与法线贴图、反照率纹理以及直接光照相结合之后,任何微小的瑕疵都会被掩盖掉,人眼一般很难观察到这些瑕疵。
11.3.7 使用环境光遮蔽进行着色
虽然我们是在恒定、遥远光照环境中推导出的环境光遮蔽值,但是我们也可以将其应用于更复杂的光照场景中。再次回顾一些反射方程:
如章节11.3.1中所介绍的,方程11.19中包含了可见性函数。
假如我们现在正在处理一个漫反射表面,我们可以使用Lambertian BRDF来代替方程11.19中的,这个BRDF等于次表面反照率除以,将其带入方程11.19,可得:
我们对方程11.20进行一些化简整理,可得:
如果我们使用方程11.8中所定义的环境光遮蔽,则方程11.21可以简化为:
其中:
上述形式为我们提供了一个全新的视角来看待这个过程。方程11.22中的积分,可以认为是对入射radiance 应用了一个方向性的滤波核。滤波器以一种复杂的方式在空间和方向上同时变化,但它具有两个重要的属性。首先,由于对点积进行了clamp操作,因此它最多只能覆盖点法线周围的半球范围。其次,由于分母中包含归一化因子,因此它在整个半球上的积分等于1。
为了进行着色,我们需要计算两个函数乘积的积分,即入射radiance 和滤波器函数乘积的积分。在某些情况下,我们可以使用一种简化的方式来描述这个滤波器,并以很低的成本来计算这个二重积分,例如当和都使用球谐函数来进行表示的时候(章节10.3.2)。降低这个方程复杂度的另一种方法是,使用一个具有类似特性,但是更简单的滤波器来对其近似。最常见的选择就是归一化的余弦核函数:
在没有入射光线被阻挡的时候,这种近似是十分准确的。它还涵盖了与原本滤波器相同的角度范围。虽然它完全忽略了可见性函数,但是方程11.22中仍然包含了环境光遮蔽,因此在被着色的表面上会有一些与可见性相关的暗化。
选择了这个近似滤波核,那么方程11.22就变成了:
这意味着,在最简单的形式中,可以通过计算irradiance,并将其乘上环境光遮蔽值来完成环境光遮蔽的效果着色。这里的irradiance可以来自任何来源,例如:它可以从irradiance环境贴图(章节10.6)中进行采样。这种方法的准确性,取决于这个近似滤波器有多大能力能够表现正确滤波器。对于在球面上平滑变化的光照,这种近似方式能够给出合理的结果。如果在所有可能的方向上都是恒定的,就好像场景是由全白的环境贴图所照亮的那样,在这种情况下,它是完全准确的。
这个方程还让我们了解到,为什么环境光遮蔽对于精确光源或者很小的面光源而言是一个很差的可见性近似,因为这些光源在表面上只占据了很小的一个立体角(对于精确光源而言是无穷小的),而可见性函数对光照积分会产生重要影响。它几乎是以二进制的方式来控制光源的贡献,也就是说,它要么完全启用,要么完全禁用。忽略可见性(正如我们在方程11.25中所做的那样)是一个影响很大的近似操作,这样做通常不会产生符合预期的结果。在这种近似情况下,所产生的阴影缺乏清晰度,并且没有任何预期的方向性,也就是说,它们看起来并不像是由特定光源产生的。对这种光源的可见性进行建模,环境光遮蔽并不是一个好的选择,应当使用一些其他的方法,例如阴影贴图等。然而,值得注意的是,有时候我们会使用较小的局部光源来模拟间接光照的效果,在这种情况下,使用环境光遮蔽值来调整它们的贡献是合理的。
到目前为止,我们都是假设在Lambertian表面上进行着色的。在处理更加复杂的、非常数的BRDF时,这一项无法从积分中提出来(就像我们在方程11.20中所做的那样)。对于镜面材质而言,不仅取决于可见性和法线,还取决于观察方向。对于一个典型的微表面BRDF而言,其波瓣会在整个区域上发生显著改变;使用单一的、预先确定的形状来对其近似会显得过于粗糙,无法产生可信的结果。这也就是为什么在漫反射BRDF中,使用环境光遮蔽进行着色最有意义的原因。我们会在接下来的若干小节中讨论一些其他方法,它们更加适合复杂的材质模型。
使用环境法线(详见方程11.10)可以更加精确地近似滤波器。虽然滤波器中仍然没有包含可见性项,但是其最大值与未被遮挡的平均方向相匹配,这使得它在总体上可以更好地逼近方程11.23。当几何法线和环境法线不匹配的时候,使用环境法线将会给出更加准确的结果。Landis [974]不仅将它用在环境贴图的着色中,还用在了一些直接光照的着色中,来代替常规的阴影技术。
对于环境贴图的着色,Pharr [1412]提出了一种替代方案,该方法使用GPU的纹理过滤硬件来动态执行滤波操作。滤波器的形状是动态确定的,其滤波中心位于环境法线的方向上,其大小取决于的值,这样可以更加精确地与方程11.23中的原始滤波器相匹配。
11.4 定向遮蔽
尽管单独使用环境光遮蔽可以极大地提高图像的视觉质量,但它毕竟是一个大大简化了的模型。在处理大面积光源的时候,它所给出的可见性近似很差,更不用说较小的光源或者精确光源了。它也无法正确处理光滑的BRDF或者更加复杂的光照环境。想象现在有一个表面,它被远处的圆形顶灯所照亮,这个圆形顶灯的颜色从红色渐变为绿色。这个圆形顶灯可能会用来代表来自天空的光线,又或者是来自某个遥远的星球的光线,如图11.18所示。即使环境光遮蔽会让点和点的光照变暗,但是它们仍然会被红色和绿色的天空所照亮。使用环境法线可以缓解这种效果,但是这样做也不是完美的。我们之前所提出的简单模型不够灵活,无法处理这种特殊情况,其中一种解决方案是,使用一些更具表现力的方式来描述可见性。
我们将专注于编码整个球面可见性或者半球可见性的方法,即描述哪些方向会阻挡入射radiance的方法。这些信息可以用来为精确光源产生阴影,但这并不是它的主要目的。针对这些特定类型光源的专用方法(详见第7章),能够生成质量更好的阴影,因为它们只需要对光源的某个位置或者某个方向进行可见性编码即可。
这里我们所要描述的解决方案,主要是用于为大面积光源或者环境照明提供遮挡效果,这些方法可以生成柔和的阴影,并且由近似可见性所引起的瑕疵也不是很明显。此外,这些方法还可以在常规阴影技术无法运行的时候,提供一些遮挡效果,例如凹凸贴图细节所产生的自阴影,以及超大场景的阴影,导致阴影贴图没有足够的分辨率。
11.4.1 预计算定向遮蔽
Max [1145]引入了视界映射(horizon mapping)的概念来描述高度场表面的自遮挡现象。在视界映射中,对于表面上的每个点,会根据一组方位角方向来确定视界角度,例如8个方向:北、东北、东、东南、以此类推。
我们可以不存储在给定方位上的视界角,而是将未遮挡的三维方向集合作为一个整体,将其建模为椭圆[705, 866]或者圆形[1306, 1307]孔径,其中后一种技术被称为环境光圈照明(ambient aperture lighting,如图11.19所示)。这些技术对存储的要求比视界映射低,但是当未遮挡的方向不像椭圆或者圆的时候,可能会产生错误的阴影效果。例如在一个平面上,以规则间隔突出的山峰,应该设置一个星形的未遮挡方向,这与上述椭圆方案和圆形方案不匹配。
遮挡技术有许多变种。Wang等人[1838]使用了球形符号距离函数(spherical signed distance function,SSDF)来表示可见性。它将一个到被遮挡区域边界的符号距离编码到球体上。章节10.3节中所讨论的任何球面基底或者半球基底,都可以用来对可见性进行编码[582, 632, 805, 1267]。就像环境光遮蔽一样,这些定向可见性信息可以存储在纹理、网格顶点或者体积中[1969]。
11.4.2 定向遮蔽的动态计算
许多用于生成环境光遮蔽的方法,同样也可以用于生成定向的可见性信息。Ren等人[1482]提出的球谐函数指数方法,以及Sloan等人[1655]提出的屏幕空间变体,以球谐向量的形式来生成可见性。如果使用多个SH频带,这些方法本身就可以提供方向信息,同时使用更多的频带可以更加精确的对可见性进行编码。
锥形追踪方法,例如Crassin等人[305]和Wright [1910]所提出的方法,它们为每个追踪区域都提供了一个遮挡值。出于质量原因,即使是对环境光遮蔽进行估计,也是会进行多次锥形追踪,这些可用的信息本身就已经具有方向性了。如果还需要某些特定方向的可见性,我们还可以在该方向上执行较少次数的锥形追踪。
Iwanicki [806]也使用了锥形追踪,但他将其限制在了一个方向上。该方法将动态角色近似为一组球体,追踪的结果用于生成投射到静态几何体上的软阴影,这与Ren等人[1482]和Sloan等人[1655]的方法相类似。在这个解决方案中,静态几何物体的照明使用AHD进行编码存储(详见章节10.3.3)。环境光遮蔽和定向遮蔽这两部分的可见性可以独立进行处理,其中在对于定向可见性而言,会对指定方向进行一次锥形追踪,并与球体进行相交,从而计算其衰减因子。
许多屏幕空间中的方法也可以进行扩展,从而提供定向的遮挡信息。Klehm等人[904]使用z-buffer数据来计算屏幕空间中的环境圆锥(screen-space bent cone),这些圆锥实际上就是圆形孔径,这与Oat和Sander[1307]离线预计算的圆锥非常相似(章节11.4.1)。当对像素的邻域进行采样的时候,它会将未遮挡的方向相加,最终的结果向量,其长度可以用来估计可见性锥(visibility cone)的顶角大小,其方向则定义了可见锥的轴。Jimenez等人[835]使用视界角度来估计圆锥的轴方向,并使用环境遮挡因子来推导出圆锥的顶角大小。
11.4.3 使用定向遮蔽进行着色
由于编码定向遮蔽的方式实在太多,因此我们无法提供一个标准通用的着色方案,具体所使用的解决方案将取决于我们想要达到的特定效果。
让我们再次回顾反射方程,在这个版本的方程中,我们将入射radiance拆分为远处的照明及其可见性:
我们能够做的最简单的操作,就是使用可见形信号来遮挡精确光源。由于大多数编码可见性的方法都很简单,其结果的质量往往也不太令人满意,但是这样的方法可以让我们在一个基本的例子中进行推理。这种方法同样也可以用于传统阴影方法由于分辨率不足而失效的情况,在这种情况下,生成的结果精度没有那么重要,总比没有任何形式的遮挡要好得多。这种情况包括:面积非常大的地形模型,使用凹凸贴图表示的表面微小细节等。
根据章节9.4中的讨论,当处理精确光源的时候,方程11.26会变为:
其中是一个纯白的Lambertian表面正对光源时所反射出的颜色,是指向光源的颜色。
我们可以把上面的方程解释为,首先计算材质对未遮挡光源的响应结果,再将结果乘以可见性函数的值。如果光线方向位于视界以下(当使用视界映射时)、或者位于可见锥之外(当使用环境光圈照明时)、或者位于SSDF的负区域,那么可见性函数的值为零,因此不需要考虑来自光源的任何贡献。值得一提的是,尽管可见性函数被定义为一个二进制函数,但是许多表示方式都可以返回整个范围内的值,即,而不仅仅是非0即1,位于范围内的非整数值代表部分遮挡的情况。
至少在大多数情况下是这样。在某些情况下,我们希望可见性函数取0和1以外的值,但是仍然位于范围内。例如:当对由半透明材质所引起的遮挡进行编码时,我们可能会希望使用小数遮挡值。
由于振铃效应,球谐函数或者H-basis甚至可能会重建出负值,这些行为是我们不想要的,它只是编码方式的固有属性。
我们可以对面光源进行类似的推理。在这种情况下,位于光源所对应的立体角内,等于光源发出的radiance;位于光源所对应的立体角外,为零。我们将其记作,并假设它在光源立体角上是恒定的。此时我们可以将对整个球体的积分,转换为对光源立体角的积分,即:
如果我们假设方程中的BRDF也是常数,例如Lambertian表面,那么它也可以从积分中提出来,即:
为了确定被遮挡的光照,我们需要计算可见性函数乘上余弦项,在光源所对应的立体角上的积分。在某些情况下,这个积分可以通过解析计算出来。Lambert [967]推导了一个方程,用于计算一个球面多边形上的余弦积分。如果我们的面光源是多边形的,并且我们可以根据可见性表示来对其进行剪裁的话,那么我们只需要使用这个Lambert方程就可以得到精确的结果,如图11.20所示。例如:当我们选择视界角作为可见性表示的时候,就可以这么做。然而,如果出于某种原因,我们选择了其他的编码方式,例如环境圆锥(bent cone),此时再对光源进行裁剪将会产生圆形片段,因此我们将无法再使用Lambert方程。如果我们的面光源是非多边形的话,上述原则同样适用。
还有另外一种可能的假设方法,即假设余弦项的值在整个积分域中是个常数。如果面光源的尺寸很小的话,那么这种近似是相当精确的。简单起见,我们可以使用面光源中心方向所对应的余弦值。这时,我们只需要计算可见性函数在光源立体角上的积分即可。下一步的操作,还是取决于我们所选择的可见性表示方法和面光源类型。如果我们使用球形光源,并且使用环境圆锥来表示可见性的话,那么积分的值就是可见性圆锥与光源圆锥相交部分所对应的立体角。这部分是可以解析计算的,Oat和Sander [1307]推导出了一种计算方法,虽然精确求解的方程相当复杂,但是好在他们还提供了一个近似解,这个近似解在实践中十分有效。如果使用球谐函数来编码可见性的话,那么这个积分同样也可以解析计算。
对于环境光照而言,我们无法限制积分的范围,因为光照是来自四面八方的。我们需要找到一种方法来计算方程11.26中的完整积分。为了简单起见,让我们首先考虑Lambertian BRDF:
这个方程中的积分叫做三重乘积积分(triple product integral)。如果其中的单个函数可以使用特定的方式来进行表示的话(例如球谐函数或者小波),那么它是可以通过解析计算出来的。但不幸的是,这对于通常的实时应用程序而言太昂贵了,尽管这样的解决方案已经被证明,可以在简单的环境设置中以交互式帧率来运行[1270]。
不过,我们的这个例子稍微简单一些,因为其中一个函数是余弦函数。我们可以将方程11.30改写为:
或者:
其中:
与和一样,和都是球面函数。我们没有尝试直接去计算这个三重乘积积分,而是首先将余弦项乘以(方程11.31)或者(方程11.32),这样做使得被积函数变成两个函数的乘积。虽然这看起来只是一个数学技巧,但是它可以极大地简化计算。如果被积函数中的乘积因子,使用了标准正交基(例如球谐函数)来进行表示,那么这个二重乘积积分可以很简单的计算出来,积分的结果就是它们系数向量的点积(章节10.3.2)。
但是我们仍然需要计算或者,由于它们都包含了余弦项,因此要比完全一般的情况稍微简单一些。如果我们使用球谐函数来表示这些函数,那么余弦函数将会被投影到球带谐波(zonal harmonics,ZH)上。球带谐波是球谐函数的一个子集,其中每个频带只有一个系数是非零的(详见章节10.3.2)。这个投影的系数有一个很简单的解析公方程[1656]。SH和ZH的乘积计算效率,要比SH和SH的乘积高得多。
如果我们决定先将余弦项乘以(方程11.32),那么我们可以在离线环境中对其进行预计算,同时只需要存储可见性即可。正如Sloan等人[1651]所描述的那样(章节11.5.3),这是一种形式的预计算radiance传输(precomputed radiance transfer)。然而,在这种形式下,我们无法对法线进行任何精细的修改,因为由法线控制的余弦项已经和可见性函数融合在一起了。如果我们想要模拟精细尺度的法线细节,则可以先用乘以余弦项(方程11.31)。由于我们事先并不知道法线的具体方向,因此可以预先计算出不同法线所对应的乘积[805],或者是在运行过程中动态执行乘法操作[809]。离线预计算和余弦项的乘积意味着,我们对光照的任何修改都会受到限制,并且允许光照在空间上发生变化会消耗大量的内存。另一方面,在运行时计算这个乘积的开销也很高。Iwanicki和Sloan [809]描述了如何降低这一操作的成本,在他们的例子中,这个乘积可以在更低的粒度(顶点)上进行计算。乘积的结果与余弦项进行卷积,再投影到一个更简单的表示方法(AHD)上,然后再使用逐像素的法线进行插值和重建。这种方法允许他们在60 FPS的游戏中,使用乘以余弦项的策略。
Klehm等人[904]提出了一种使用环境贴图表示光照,并使用锥形编码可见性的解决方案。他们使用了不同大小的滤波核来对环境贴图进行过滤,这些滤波核代表了不同锥形开口的可见性与光照乘积的积分。他们按照锥形开口角度大小的增加,将结果存储在纹理的mipmap中。这样做是合理的,因为较大锥形开口的预过滤结果在球体上会平滑变化,因此不需要使用较高分辨率来进行存储。在预过滤的过程中,他们假设可见性锥的方向与法线是对齐的,这是一个近似假设,但是在实践中可以给出较为可信的结果。他们还分析了这种近似是如何对最终质量产生影响的。
如果我们需要处理光泽BRDF和环境光照,那么情况就要更加复杂了。此时我们无法再将BRDF从积分中提取出来,因为它并不是一个常数。为了解决这个问题,Green等人[582]建议用一组球面高斯函数(spherical Gaussian)来对BRDF本身进行近似。这些球面高斯函数都是径向对称的,它们可以使用三个参数来进行表示(十分紧凑):方向(或者平均值),标准差和振幅。这个近似BRDF可以定义为球面高斯函数的和:
其中是一个球面高斯波瓣,它指向方向,锐度为(详见章节10.3.2);是第个波瓣的振幅。对于各向同性的BRDF而言,其波瓣的形状仅仅取决于法线方向和观察方向之间的夹角。我们可以将一组近似值存储在一维查找表中,并在运行时进行插值重建。
有了这个BRDF近似,我们可以将方程11.26改写成:
Green等人还假设可见性函数在每个球面高斯的范围内都是恒定的,这使得他们可以将可见性项从积分中提取出来。他们在波瓣的中心方向上计算了可见性函数,最终的方程形式如下:
剩余的积分代表了入射光线与球面高斯进行卷积,这个球面高斯是给定方向和给定标准差的。这个卷积的结果可以进行预先计算,并存储在一个环境贴图中,其中较大的所对应的卷积结果存储在较低的mipmap层级中。这里的可见性可以使用较低阶的球谐函数进行编码,或者是任何其他的表示方法,因为这里只需要进行点计算即可。
Wang等人[1838]以类似的方式来对BRDF进行近似,不同的是他们以一种更加精确的方式来处理可见性。他们的表示方法允许在可见性函数的范围内,计算单个球面高斯函数的积分。他们使用这个积分值来引入一个新的球面高斯函数,它具有相同的方向和标准差,但是振幅不同,他们会在实际的光照计算中使用这个新的球面高斯函数。
对于某些应用程序而言,这种方法可能会过于昂贵。因为它需要从预过滤的环境贴图中进行多次采样,而纹理采样往往会成为渲染过程中的瓶颈。Jimenez等人[835]和El Garawany [414]给出了更简单的近似方法,为了计算遮挡因子,他们使用一个圆锥来表示整个BRDF波瓣,忽略了BRDF波瓣对观察角度的依赖,只考虑材质粗糙度等参数,如图11.21所示。它们将可见性近似为一个圆锥体,并计算可见性圆锥与BRDF圆锥相交部分的立体角,就像环境光圈照明所做的那样。这个计算出来的标量结果会用于对光照的衰减,虽然这是一个重大的简化,但是最终的结果看起来是可信的。