Chapter 11 Global Illumination 全局光照(上)

78 阅读8分钟

目录

Jeremy Birn——“If it looks like computer graphics,it is not good computer graphics.”

杰里米·伯恩——“如果它看起来像是计算机图形学生成的,那它就不是一个好的计算机图形学。”(皮克斯动画公司的光照技术总监)

渲染过程最终计算的是radiance,到目前为止,我们一直在使用反射方程(reflectance equation)来其进行计算:

Lo(p,v)=lΩf(l,v)Li(p,l)(nl)+dl(11.1)L_{o}(\mathbf{p}, \mathbf{v})=\int_{\mathbf{l} \in \Omega} f(\mathbf{l}, \mathbf{v}) L_{i}(\mathbf{p}, \mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} \tag{11.1}

其中Lo(p,v)L_{o}(\mathbf{p}, \mathbf{v})是表面位置p\mathbf{p}在观察方向v\mathbf{v}上的出射radiance;Ω\Omega是表面位置p\mathbf{p}的上半球范围;f(l,v)f(\mathbf{l}, \mathbf{v})是观察方向v\mathbf{v}和当前光线入射方向l\mathbf{l}上的BRDF;Li(p,l)L_{i}(\mathbf{p}, \mathbf{l})是从光线方向l\mathbf{l}到达表面位置p\mathbf{p}的入射radiance;(nl)+(\mathbf{n} \cdot \mathbf{l})^{+}是光线方向l\mathbf{l}和表面法线n\mathbf{n}之间的点积,并将负数结果clamp到0,即将来自表面下方的光线过滤掉。

11.1 渲染方程

反射方程是完整渲染方程的一种特殊情况,它由Kajiya在1986年提出[846]。渲染方程具有各种不同的表达形式,我们将使用以下这个版本:

Lo(p,v)=Le(p,v)+lΩf(l,v)Lo(r(p,l),l)(nl)+dl(11.2)L_{o}(\mathbf{p}, \mathbf{v})=L_{e}(\mathbf{p}, \mathbf{v})+\int_{\mathbf{l} \in \Omega} f(\mathbf{l}, \mathbf{v}) L_{o}(r(\mathbf{p}, \mathbf{l}),-\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} \tag{11.2}

其中多出来的一项为Le(p,v)L_{e}(\mathbf{p}, \mathbf{v}),它表示了从表面位置p\mathbf{p}向观察方向v\mathbf{v}发射的radiance,用于描述自发光表面的出射radiance。被积函数中有一项做了如下替换:

Li(p,l)=Lo(r(p,l),l)(11.3)L_{i}(\mathbf{p}, \mathbf{l})=L_{o}(r(\mathbf{p}, \mathbf{l}),-\mathbf{l}) \tag{11.3}

这一项意味着,从方向l\mathbf{l}进入表面位置p\mathbf{p}的入射radiance,等于另一个表面位置向相反方向l-\mathbf{l}的出射radiance。在这种情况下,这里的“另一个表面位置”由光线投射函数(ray casting function)r(p,l)r(\mathbf{p}, \mathbf{l})所定义的,这个函数会从表面位置p\mathbf{p}向方向l\mathbf{l}上发射一条光线,并返回所击中的第一个表面位置,如图11.1所示。

图11.1

渲染方程的含义很简单。为了对表面位置p\mathbf{p}进行渲染,我们需要知道在观察方向v\mathbf{v}上,离开表面位置p\mathbf{p}的出射radiance LoL_o,它等于该点自身发射的radiance LeL_e,再加上反射出的radiance。有关光源发射和反射率的内容,在前面几章我们已经讨论过了。甚至这里的光线投射操作好像看起来也不是那么陌生,例如:z-buffer实际上就计算了从相机投射到场景中的光线。

这里我们所遇到的唯一的新项是Lo(r(p,l),l)L_{o}(r(\mathbf{p}, \mathbf{l}),-\mathbf{l}),这一项明确指出,入射到某一点上的radiance,一定是从另一点发出的。不幸的是,这是一个递归项,也就是说,如果我们想要计算点r(p,l)r(\mathbf{p}, \mathbf{l})在方向上l-\mathbf{l}上的出射radiance,那么我们首先还要知道来自表面位置r(r(p,l),l)r\left(r(\mathbf{p}, \mathbf{l}), \mathbf{l}^{\prime}\right)的出射radiance,接下来还需要计算来自表面位置r(r(r(p,l),l),l)r\left(r\left(r(\mathbf{p}, \mathbf{l}), \mathbf{l}^{\prime}\right), \mathbf{l}^{\prime \prime}\right)的出射radiance,直到无穷。令人十分惊讶的是,如此复杂的计算量,现实世界居然可以对其进行实时计算。

我们凭借直觉可以知道,光源照亮了一个场景,它所发出的光子(photon)在场景中四处反弹,每次与表面发生碰撞的时候,都会以各种方式被吸收、反射或者折射。渲染方程十分重要,因为它在一个简单的方程中总结了所有可能的光线路径。

渲染方程有一个重要的属性,即它与所发射出的光线呈线性关系。如果我们使光源的强度翻倍,那么最终的着色结果也会加倍变亮。同时,材质对于每种光源的响应也是相互独立的,也就是说,一种光源的存在并不会影响另一种光源与材质之间的相互作用。

在实时渲染中,只使用局部光照模型也是很常见的,我们只需要对可见点的表面数据进行光照计算即可,而这正是GPU最擅长的。传入GPU的各种图元被独立处理和光栅化,然后它们就会被丢弃,我们在点b\mathbf{b}执行光照计算时,无法访问点a\mathbf{a}的光照计算结果。诸如透明、反射和阴影效果,都是全局光照算法的范畴,它们利用了来自其他物体的信息,而不仅仅是被光源所照亮的物体。这些效果大大增强了渲染图像的真实感,并提供了视觉暗示(cues)来帮助观察者理解空间中的位置关系。同时,这些效果模拟起来也十分复杂,可能需要进行预计算或者渲染多个pass来计算一些必须的中间信息。

有一种思考光照问题的方法,即通过光子的传播路径来理解光照。在局部光照模型中,光子从光源出发,传播到表面上(忽略中间的物体),然后到达眼睛。阴影算法考虑了这些中间物体的直接遮挡效果。环境贴图可以捕捉从远处光源到达物体表面的光线,然后将其应用到局部的光泽物体上,这些物体会以镜面反射的方式,将这些光线反射到眼睛中。irradiance贴图还可以捕捉到光源对遥远物体的影响,并在半球范围的方向上进行积分,被这些物体所反射的光线会进行加权求和,从而计算出一个表面的光照效果,最终被眼睛所看到。

图11.2:图中展示了一些路径及其到达眼睛时的等效符号。注意,图中展示了两条从网球开始的连续路径,分别是LSDE和LSDSSE。

以一种更加正式的方式来思考光线传输路径的不同类型和不同组合,有助于理解现有的各种算法。Heckbert [693]提出了一个符号方案,它用于描述由某种技术所模拟的光线路径。光子从光源(LL)到眼睛(EE)的每次相互作用,都可以标记为漫反射(DD)或者镜面反射(SS),还可以通过添加其他表面类型来进一步分类,例如“有光泽的(glossy)”,它代表了有光泽,但是又不像镜子的表面,如图11.2所示。可以使用正则表达式来简单地概括这些算法,从而展示它们所模拟的交互类型。表11.1对基本符号进行了总结。

表11.1:正则表达式符号。

从光源出发的光子可以通过各种路径最终到眼睛。最简单的路径是LELE,光源被眼睛直接看到。一个基本的z-buffer是L(DS)EL(D |S)E,或者写成其等价形式LDELSELDE|LSE。光子离开光源,到达一个漫反射表面或者一个镜面,然后再到达眼睛。请注意,在一个基础的渲染系统中,点光源没有对应的物理表示,它不会被眼睛直接观察到。对于一个具有几何形状的光源而言,将会产生这样一个路径L(DS)?EL(D|S)?E,除了照射到表面之外,从光源发出的光线也可以直接进入眼睛。

如果将环境映射添加到渲染器中,那么这个表达式就不再那么简单了。虽然Heckbert的表示法是从光源出发最终到达眼睛,但是对于渲染而言,从相反方向来构建表达式通常要更加容易。眼睛将首先看到一个镜面或者一个漫反射表面,即(SD)E(S|D)E,如果这个表面是一个镜面,那么它也可以选择反射到一个环境贴图中的镜面,或者是一个漫反射表面上。因此,存在一条额外的可能路径:((SD)?SD)E((S|D)?S|D)E。同时再加上眼睛直接看到光源的路径,那么这个表达式最终会变为:L((SD)?SD)?EL((S|D)?S|D)?E

可以将这个表达式展开:LELSELDELSSELDSELE|LSE|LDE|LSSE|LDSE,它代表了所有可能存在的路径,或者简写为:L(DS)S?EL(D |S) S?E。每一种表示方法在理解关系和限制方面都有各自的优势。这种符号表示法的部分用途是表达算法的效果,并能够以此为基础进行构建,例如:L(SD)L(S |D)是生成环境贴图时所编码的内容,而SESE则代表了随后访问该贴图的过程。

渲染方程本身也可以用简单的表达式L(DS)EL(D|S) * E来进行概括,即来自光源的光子在到达眼睛之前,可以与0到几乎无限数量的漫反射表面或者镜面发生相互作用。

对于全局光照的研究,主要集中在计算光线在这些路径上传播的方法。当将其应用于实时渲染时,我们通常愿意牺牲一些质量或者正确性,来换取更快的计算速度。最常见的两种策略就是简化和预计算。例如:我们可以假设所有反射到眼睛中的光线都是漫反射的,这种简化在某些环境和场景中表现很好。我们还可以离线环境中,对一些物体之间效果的相关信息进行预计算,例如生成记录表面光照水平的纹理,然后在运行过程中,根据这些存储的信息进行一些基本计算,从而获得全局光照效果。本章节将展示如何使用这些策略,来实时实现各种全局光照效果。

11.2 通用全局光照

我们在前面几章中,着重介绍了求解反射方程的各种方法。我们假设入射radiance LiL_i具有一定的分布,并分析了它是如何影响着色计算的。而在本章节中,我们将介绍用于求解完整渲染方程的算法。二者之间的区别在于,前者忽略了radiance的来源,它假设是直接给出的;而后者则明确地说明了这一点:到达某一点的radiance是从其他点发射或者反射而来的。

图11.3:路径追踪可以生成照片级逼真的图像,但是其计算成本较高。上面图像中的每个像素都使用了超过2000条路径(2000spp),每个路径长达64段(深度,即反弹次数)。它花费了两个多小时来进行渲染,但是仍然会表现出一些轻微的噪声。 [149]

能够求解完整渲染方程的算法,可以生成令人惊叹的、照片级逼真的图像,如图11.3所示。然而对于实时应用来说,这些方法的计算成本都太高了,那么为什么我们还要讨论它们呢?第一个原因是:在静态或者部分静态的场景中,这样的算法可以在预处理阶段执行,并将计算结果存储下来,以供稍后在实时渲染期间使用。这在游戏中是一种十分常见的方法,稍后我们将对这类系统的不同方面进行讨论。

第二个原因是:全局光照算法都建立在严格的理论基础上,它们是直接从渲染方程中推导出来的,它们所做的任何近似都是经过仔细分析的。在设计实时解决方案的时候,可以且应该应用类似的推理思路;即使我们走了某些捷径,使用了一些技巧,但是我们也应当知道这么做的后果是什么,什么才应该是正确的方法。随着图形硬件变得越发强大,我们将能够做出更少的妥协和近似,并且能够创建出更加接近正确物理结果的实时渲染图像。

求解渲染方程的两种常用方法是有限元法(finite element)和蒙特卡罗法(Monte Carlo)。其中辐射度算法(radiosity)基于了第一种方法,而不同形式的光线追踪算法(ray tracing)则使用了第二种方法。在这两种不同思路的算法中,光线追踪要更加流行。这主要是因为它可以在同一个算法框架内,对一般的光线传输效果进行有效处理,包括体积散射等效果。而且光线追踪算法也更加容易扩展和并行化。

我们将简要介绍这两种方法,有兴趣的读者还应该参考其他优秀的书籍,它们涵盖了在非实时情况下求解渲染方程的细节[400, 1413]。

11.2.1 辐射度

辐射度算法(Radiosity)[566]是第一种用于模拟漫反射表面之间光线反弹的计算机图形技术,其名字来源于该算法所计算的物理量。在经典的算法形式中,辐射度算法可以计算相互反射以及面光源所产生软阴影。辐射度算法的基本思想相对简单,并且已经有了完整的书籍对这个算法进行介绍[76, 275, 1642]。光线会在环境中发生弹射,当我们打开一盏灯时,房间内的照明会很快达到平衡,在这种稳定状态下,每个表面都可以被看作是一个光源。基本的辐射度算法作出了一种简化的假设,即所有场景中的间接光都来自于漫反射表面。对于具有抛光大理石地板或者墙上有巨大镜面的场景而言,这个假设是不成立的,但是对于现实中的许多建筑而言,这是一个相对合理的近似。辐射度算法可以对无限数量的有效漫反射进行追踪。如果使用本章节开头所介绍的符号表示法,那么可以将它的光线传输路径写为是LDELD*E

辐射度算法假设每个物体表面都由一定数量的面片(patch)组成。对于每个较小的区域(面片),辐射度算法都会计算一个平均辐射度值(radiosity value),因此这些面片的尺寸需要足够小,才能够捕捉所有的照明细节(例如阴影边缘)。这些面片不需要和底层表面的三角形一一匹配,甚至面片的大小尺寸也可以不一样。

从渲染方程出发,我们可以推导出第ii个面片的辐射度为:

Bi=Bie+ρssjFijBj(11.4)B_{i}=B_{i}^{e}+\rho_{\mathrm{ss}} \sum_{j} F_{i j} B_{j} \tag{11.4}

其中BiB_{i}代表了面片ii的辐射度;BieB_{i}^{e}为面片ii的辐射出度(radiant exitance),即面片ii所发出的辐射度;ρss\rho_{\mathrm{ss}}是次表面反照率(详见章节9.3)。只有光源的辐射出度才不为0。FijF_{ij}是面片ii和面片jj之间的形状因子(form factor),这个形状因子的定义为:

Fij=1AiAiAjV(i,j)cosθicosθjπdij2daidaj(11.5)F_{i j}=\frac{1}{A_{i}} \int_{A_{i}} \int_{A_{j}} V(\mathbf{i}, \mathbf{j}) \frac{\cos \theta_{i} \cos \theta_{j}}{\pi d_{i j}^{2}} d a_{i} d a_{j} \tag{11.5}

其中AiA_i是面片ii的面积;V(i,j)V(\mathbf{i}, \mathbf{j})是点ii与点jj之间的可见性函数,如果它们之间没有物体遮挡光线,则该项为1,否则为0。角度值θi\theta_{i}θj\theta_{j}分别是两个面片的法线,与点ii和点jj的之间连线的夹角。最后,dijd_{ij}是点ii和点jj的之间距离。如图11.4所示。

图11.4:两个表面点之间的形状因子。

形状因子是一个纯粹的几何项,它描述了离开面片ii的均匀漫反射辐射能量,有多少能够入射到面片jj上[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所示。

图11.5:路径追踪算法所生成的示例路径。图中所展示的三条路径通过了成像平面中的同一个像素,并用于估计它的亮度值。图片底部的地板具有一定的光泽,因此会在一个较小的立体角范围内反射光线。蓝色盒子和红色球体都是漫反射表面,因此光线在交点处,会绕法线周围进行均匀地散射。

对路径进行追踪是一个非常强大的概念。这些路径可以用于渲染光泽材质或者漫反射材质。使用它们,我们还可以生成软阴影、渲染透明物体以及焦散效果。通过对路径追踪进行扩展,我们还可以对体积内的点进行采样,而不仅仅是对物体表面进行采样,这样我们就可以处理雾和次表面散射等效果。

路径追踪的唯一缺点是,想要实现高视觉保真度的画面,所需要的计算复杂度是很高的。对于电影级的图像,我们可能需要对数十亿条路径进行追踪,因为我们计算的只是积分的估计值,而不是真实值。如果使用的路径太少,那么这种近似将会是不精确的,在一些特殊情况下会及其不精确,从而产生大量噪声。此外,即使是两个相邻的点,最终的着色结果也可能会差异很大,这与我们的期望不太相符,我们总是希望相邻点具有相似的光照结果。我们将这样的结果称为具有高方差(high variance),从视觉上看,方差会体现为图像中的噪声(如图11.6所示)。现在已经有了很多方法,可以在不增加额外追踪路径的前提下,消除或者减弱这种噪声所带来的影响。其中一种流行的技术是重要性采样(importance sampling),这个方法的思路是,通过向光线来源的主要方向发射更多的光线,从而大大降低方差。

图11.6:使用蒙特卡罗路径追踪时,由于样本数量不足而产生的噪声。左侧图像为每像素8个路径(8 spp),右侧图像为每像素1024个路径(1024 spp)。 [149]

许多已经出版了的论文和书籍对路径追踪及其相关方法进行了详细讨论。Pharr等人[846]为现代离线光线追踪技术提供了一个很好的介绍。Veach [846]为光线传输算法的现代推理奠定了数学基础。我们将在本章最后的章节11.7中,讨论交互式的光线追踪和路径追踪。

11.3 环境光遮蔽

上一小节中所介绍的通用全局光照算法,它们的计算成本都很高。虽然它们可以产生各种复杂的效果,但是生成一幅图像往往需要好几个小时。我们将首先介绍一些最简单的,但是在视觉上很有说服力的方法,并在本章节逐步探索实时替代方案,逐步构建更加复杂的效果。

一种基本的全局光照效果是环境光遮蔽(ambient occlusion,AO)。这项技术是在21世纪初,由工业光魔的Landis [974]所开发的,当时是用于提高电影《珍珠港》中,由计算机生成的飞机的环境光照质量。尽管这种效应的物理基础进行了相当程度的简化,但是最终的结果看起来却令人惊讶地可信。当光照缺乏方向变化,无法展现物体细节时,这种廉价方法可以提供对于物体形状的视觉暗示。

11.3.1 环境光遮蔽理论

环境光遮蔽的理论背景可以直接从反射方程中推导出来。这里为了简单起见,我们将首先关注Lambertian表面,该表面的出射radiance LoL_o与表面irradiance EE成正比。irradiance是入射radiance的余弦加权积分,一般来说,它取决于表面位置p\mathbf{p}和表面法线n\mathbf{n}。同样,为了简单起见,我们将假设来自所有方向l\mathbf{l}上的入射radiance都是恒定的,即Li(l)=LAL_{i}(\mathbf{l})=L_{A}。基于上述假设,此时计算irradiance的方程如下所示:

E(p,n)=lΩLA(nl)+dl=πLA(11.6)E(\mathbf{p}, \mathbf{n})=\int_{\mathbf{l} \in \Omega} L_{A}(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l}=\pi L_{A} \tag{11.6}

在这里,我们将对半球ΩΩ的所有可能入射方向进行积分。在恒定均匀光照的假设下,irradiance(以及由此产生的出射radiance)与表面位置和表面法线无关,并且在整个物体上都是恒定的。这会生成一个平坦均匀的外观。

方程11.6并没有考虑任何的可见性。着色点半球范围内的某些方向,可能会被自身物体的其他部分或者是场景中的其他物体所遮挡。在这些方向上将会具有不同的入射radiance,而不是恒定的LAL_A。为了简单起见,我们假设来自这些遮挡方向上的入射radiance为零。虽然这个假设忽略了场景中可能会被其他物体反弹,并最终从这些遮挡方向到达点p\mathbf{p}的光线,但是它极大地简化了推理过程。基于上述假设,我们可以得到以下方程,它由Cook和Torrance首次提出[285, 286]:

E(p,n)=LAlΩv(p,l)(nl)+dl(11.7)E(\mathbf{p}, \mathbf{n})=L_{A} \int_{\mathbf{l} \in \Omega} v(\mathbf{p}, \mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} \tag{11.7}

其中v(p,l)v(\mathbf{p}, \mathbf{l})是一个可见性函数,如果从点p\mathbf{p}向方向l\mathbf{l}投射的光线会被物体遮挡,则该函数值为0,反之为1。

将可见性函数进行余弦加权积分,然后再进行归一化,最终的结果被称为环境遮挡系数:

kA(p)=1πlΩv(p,l)(nl)+dl.(11.8)k_{A}(\mathbf{p})=\frac{1}{\pi} \int_{\mathbf{l} \in \Omega} v(\mathbf{p}, \mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l}. \tag{11.8}

这个系数代表了未被遮挡的半球的余弦加权百分比,它的范围位于[0,1][0,1]内,对于完全被遮挡的着色点,它的值为0;对于没有任何遮挡的着色点,它的值为1。值得注意的是,凸面(convex)物体,例如球体或者立方体,不会对自身造成遮挡。因此当场景中不存在其他物体时,凸面物体的环境遮挡值将为1。如果物体表面存在凹陷区域,则这些区域的遮挡值将小于1。

在定义kAk_A之后,考虑遮挡情况的环境irradiance方程为:

E(p,n)=kA(p)πLA(11.9)E(\mathbf{p}, \mathbf{n})=k_{A}(\mathbf{p}) \pi L_{A} \tag{11.9}

注意,在方程11.9中,irradiance会随着表面位置的变化而变化,因为kAk_A确实是由表面位置所决定的,这样所得到的结果会更加真实,如图11.7所示。由于尖锐折痕处的kAk_A值较低,因此这里的表面位置会显得较暗。

图11.7:仅使用恒定环境光照(左)和使用环境光遮蔽(右)渲染的物体。即使光照是恒定均匀的,环境光遮蔽也可以表现出物体的细节。 [149]

比较图11.8中的表面位置p0\mathbf{p}_{0}p1\mathbf{p}_{1},可以发现表面朝向也会对kAk_A有影响,因为可见性函数v(p,l)v(\mathbf{p}, \mathbf{l})在积分的时候会被余弦因子加权。比较图11.8左侧的表面位置p1\mathbf{p}_{1}p2\mathbf{p}_{2},虽然二者具有一个大小相同的未遮挡立体角,但是表面位置p1\mathbf{p}_{1}的大部分未遮挡区域都位于其表面法线附近,该位置上的余弦因子相对较大,从箭头的亮度就可以看出。相比之下,表面位置p2\mathbf{p}_{2}的大部分未遮挡区域都位于其表面法线的一侧,因此该位置的余弦因子相对较小,也就是说,在p2\mathbf{p}_{2}处的kAk_A较低。从这里开始,为简单起见,我们将不再显式说明遮挡系数对表面位置p\mathbf{p}的依赖。

图11.8:环境光照下的物体,图中展示了三个点,分别是 \mathbf{p}_{0} 、 \mathbf{p}_{1} 和 \mathbf{p}_{2} 。在左侧,以相交点(黑色圆点)为端点的射线代表了被遮挡的方向;以箭头为端点的射线代表了未被遮挡的方向,并根据余弦因子的大小进行着色,因此更加靠近表面法线方向的箭头颜色会较浅。在右侧,蓝色箭头代表了平均的未被遮挡方向,或者叫做环境法线(bent normal)。

除了kAk_A,Landis [974]还计算了一个平均的未遮挡方向,它称为环境法线(bent normal),这个方向向量是未遮挡方向的余弦加权平均值:

nbent=lΩlv(l)(nl)+dllΩlv(l)(nl)+dl(11.10)\mathbf{n}_{\mathrm{bent}}=\frac{\int_{\mathbf{l} \in \Omega} \mathbf{l} v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l}}{\left\|\int_{\mathbf{l} \in \Omega} \mathbf{l} v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l}\right\|} \tag{11.10}

其中符号x\|\mathbf{x}\|代表了向量x\mathbf{x}的长度。积分的结果再除以它自身的长度,可以得到归一化的结果,如图11.8右侧所示。这样产生的向量可以在着色期间代替几何法线,从而提供更加准确的结果,同时不需要额外的性能开销(详见章节11.3.7)。

11.3.2 可见性和obscurance

用于计算环境遮挡因子kAk_A(方程11.8)的可见性函数v(l)v(\mathbf{l})需要仔细定义。例如:对于一个物体,比如人物或者车辆,定义这个函数v(l)v(\mathbf{l})是很简单的,我们只需要从表面位置向方向l\mathbf{l}投射光线,然后检查光线是否会与同一物体的任何其他部分相交即可。然而,这种方法并没有考虑到附近其他物体的遮挡情况。通常,为了进行光照,可以假设物体被放置在一个平面上,通过将该平面加入到可见性计算中,可以实现更加真实的遮挡效果。这样做的另一个好处是,物体对地面的遮挡可以模拟接触阴影的效果[974]。

不幸的是,这种可见性函数方法对于封闭的几何体是不起作用的。想象现在有一个场景,它是一个包含各种物品的封闭房间。在这种情况下,所有表面的kAk_A值都会为0,因为来自表面的所有射线都会击中某个物体(墙壁或者物体)。对于这类场景而言,经验方法会更加合适,它会试图重现环境遮挡的外观,但是不一定会对物理可见性进行模拟。其中的一些方法的灵感来自于Miller的可访问性着色(accessibility shading)[1211],该方法对在表面角落和缝隙处如何捕获污垢或者腐蚀进行了建模。

Zhukov等人[1970]引入了obscurance的思想,它通过使用距离映射函数ρ(l)\rho(\mathbf{l})来代替可见性函数v(l)v(\mathbf{l}),从而对环境光遮蔽的计算进行了修改:

kA=1πlΩρ(l)(nl)+dl(11.11)k_{A}=\frac{1}{\pi} \int_{\mathbf{l} \in \Omega} \rho(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} \tag{11.11}

可见性函数v(l)v(\mathbf{l})只有两个有效值,其中1代表没有相交,0表示有相交,而距离映射函数ρ(l)\rho(\mathbf{l})则是一个连续函数,其返回值取决于射线与表面相交之前所传播的距离。当相交距离为0时,函数ρ(l)\rho(\mathbf{l})的值为0;当相交距离大于设定的最大距离dmaxd_{max}时,或者根本没有相交时,函数ρ(l)\rho(\mathbf{l})的值为0。对于相交距离大于dmaxd_{max}的交点,实际上不需要进行任何测试,这样可以大大加快kAk_A的计算速度。图11.9展示了环境光occlusion和环境光obscurance之间的区别。请注意,使用环境光occlusion进行渲染的图像要暗得多,这是因为即使在很远的距离也会对相交情况进行检测,这样会减小kAk_A的值。

图11.9:环境光occlusion和环境光obscurance之间的区别。左侧图像的遮挡是使用无限长的射线进行计算的。右侧图像则使用了有限长度的射线。 [149]

尽管人们试图从物理角度为其辩护,但实际上obscurance在物理上是不正确的。然而,obscurance通常可以给出符合观众期望的合理结果。obscurance方法的一个缺点在于,这个最大相交距离dmaxd_{max}的值需要进行手动调整,才能达到令人满意的效果。这种类型的妥协会在计算机图形学中经常出现,一些技术可能并没有直接的物理基础,但是“在感知上却令人信服”。计算机图形学的目标通常是渲染一张可信的图像,因此这些技术当然是可以使用的。也就是说,基于物理理论的方法具有这样一些优点,它们可以自动进行工作,并且可以通过推理现实世界中的工作原理,来对其进一步改进。

11.3.3 考虑相互反射

尽管环境光遮蔽所产生的结果在视觉上是令人信服的,但是与完整全局光照模拟产生的结果相比,环境光遮蔽的结果要更暗一些,如图11.10中图像的对比。

图11.10:不具有相互反射和具有相互反射的环境光遮蔽之间的区别。左侧图像只使用了可见性信息,右侧图像使用了一次反弹的间接照明。 [149]

环境光遮蔽与完整全局光照之间的一个重要区别是相互反射(interreflection)。方程11.8假设被遮挡方向上的radiance为零,但是实际上相互反射会为这些方向引入一个非零的radiance。如图11.10所示,与右侧模型相比,左侧模型的折痕处和凹陷处会更暗。这种差异可以通过适当增加kAk_A的值来解决。使用obscurance距离映射函数来代替可见性函数(章节11.3.2)也可以缓解这个问题,因为obscurance函数的值通常会大于零。

以一种更加精确的方式来追踪相互反射是很昂贵的,因为它需要求解一个递归问题。想要给一个点进行着色,必须首先对其他点进行着色,以此类推。虽然计算kAk_A的值相比于执行一个完整的全局光照计算而言要便宜得多,但是我们还是希望能够以某种形式来包含这部分丢失的光线,从而避免过度暗化。Stewart和Langer [1699]提出了一种廉价、但是却惊人准确的方法来近似相互反射。它基于在漫反射光照下对Lambertian场景的观察,即从一个给定位置能够看见的表面,往往会具有相似的radiance。我们假设遮挡方向的radiance LiL_i,等于当前着色点的出射radiance LoL_o,从而打破了递归,可以得到这样一个解析表达式:

E=πkA1ρss(1kA)Li(11.12)E=\frac{\pi k_{A}}{1-\rho_{\mathrm{ss}}\left(1-k_{A}\right)} L_{i} \tag{11.12}

其中ρss\rho_{\mathrm{ss}}是次表面反照率,或者叫做漫反射率。方程11.12相当于使用一个新的环境遮挡因子kAk_{A}^{\prime}来代替之前的kAk_{A}

kA=kA1ρss(1kA)(11.13)k_{A}^{\prime}=\frac{k_{A}}{1-\rho_{\mathrm{ss}}\left(1-k_{A}\right)} \tag{11.13}

方程11.13倾向于让环境遮挡因子变得更大(更亮),从而使得它在视觉上更加接近一个完整全局光照所产生的结果,它在一定程度上模拟了相互反射效应。这种效应高度依赖于ρss\rho_{\mathrm{ss}}的值,其隐含的假设是:在着色点附近的表面颜色是相同的,这样可以产生有点像类似颜色渗透(color bleeding)的效果。Hoffman和Mitchell [755]使用了这种方法,从而可以实用天光来照亮地形。

Jimenez等人[835]提出了一种不同的解决方案。他们对许多场景执行了完整的离线路径追踪,每个场景都使用均匀的、白色的、无限远的环境贴图来进行照亮,从而获得考虑相互反射的适当遮挡值。在此基础上,他们拟合了一个三次多项式,来对环境遮挡因子kAk_A和次表面反照率ρss\rho_{\mathrm{ss}}映射到遮挡值kAk_{A}^{\prime}的函数ff进行近似,这个新的遮挡值会相互反射的光线照亮。他们的方法同样假设反照率是局部恒定的,并且可以根据给定点的反照率,推导出入射反弹光的颜色。

11.3.4 预计算环境光遮蔽

环境遮挡因子的计算可能会很耗时,通常都是在渲染之前离线计算的。预计算任何与光照相关的信息(包括环境光遮蔽),这个过程通常被称为烘焙(baking)。

预计算环境光遮蔽最常见的方法就是蒙特卡罗方法。发射光线并检查光线与场景的交点,然后对方程11.8进行数值计算,例如:我们在法线n\mathbf{n}的半球方向上均匀随机选择NN个方向l\mathbf{l},然后沿着这些方向发射光线并进行追踪。基于光线的相交结果,我们对可见性函数vv进行计算,这种计算环境光遮蔽的方法,可以表达成如下方程:

kA=1NiNv(li)(nli)+.(11.14)k_{A}=\frac{1}{N} \sum_{i}^{N} v\left(\mathbf{l}_{i}\right)\left(\mathbf{n} \cdot \mathbf{l}_{i}\right)^{+}. \tag{11.14}

在计算环境光obscurance时,可以将投射的光线限制在一个最大距离内,通过最大距离内的相交距离来计算vv的值。

环境光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的烘焙管线。

图11.11:《命运》在间接光照计算中使用基于预计算的环境光遮蔽。该方案同时运行在了两种不同的硬件世代,兼顾了高质量和高性能。

育碧的《刺客信条》[1692]和《孤岛惊魂》[1154]系列,也使用了一种预计算的环境光遮蔽,来增强他们的间接光照解决方案。他们以自上而下的视角来渲染世界,并对产生的深度图进行处理,从而计算大范围的遮挡信息。根据相邻深度样本的分布情况,采用了多种启发式算法来对遮挡值进行估计。通过将世界空间位置投影到纹理空间中,所产生的世界空间AO贴图可以应用于所有物体。他们将这种方法称为世界AO(World AO)。Swoboda [1728]也描述了类似的方法。

11.3.5 环境光遮蔽的动态计算

对于静态场景,可以预先计算环境遮挡因子kAk_A和环境法线nbent \mathbf{n}_{\text {bent }}。但是,对于存在移动或者形状改变物体的场景,必须要通过实时计算这些参数才能获得更好的结果。执行这个操作的方法按照空间可以划分为两类:在物体空间中执行的方法;在屏幕空间中执行的方法。

计算环境光遮蔽的离线方法,通常会从每个表面点向场景中投射大量光线(数十到数百条),并对交点进行检查。这是一个成本很高的操作,而实时渲染中则重点关注如何进行近似,或者如何避免大部分的计算。

Bunnell [210]通过将表面建模为放置在网格顶点处的圆盘元素,从而计算环境遮挡因子kAk_A和环境法线nbent \mathbf{n}_{\text {bent }}。这里选择圆盘的原因是,圆盘之间的遮挡情况可以通过解析计算获得,不需要单独投射光线。简单地将一个圆盘与所有其他圆盘的遮挡因子加起来,会产生双重阴影从而导致表面过暗。也就是说,如果一个圆盘位于另一个圆盘的后面,那么这两个圆盘都将被视为遮挡表面,但是实际上只有最近的圆盘才应当是。Bunnell使用了一种巧妙的两pass方法来避免这个问题:第一个pass正常计算环境光遮蔽效果,包括错误的双重阴影在内。在第二个pass中,会根据第一个pass中的遮挡情况,来减少每个圆盘的贡献值。实际上这只是一个近似值,但在实践中,它能够产生令人信服的结果。

计算每对元素之间的遮挡情况具有O(n2)O(n^2)的复杂度,除非场景构成十分简单,否则这个复杂度对于实时渲染而言太高了。对于远距离的表面,可以使用一些简化的表示,从而降低部分计算开销。Bunnell构建了一个分层的元素树,其中每个节点都是一个圆盘,它代表了其子树圆盘的聚合。在进行圆盘之间的遮挡计算时,对于较远的表面会使用较高层级的的节点。这可以将时间复杂度降低到O(nlogn)O(n \log n),这是一个更加合理的复杂度。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用于代表单个物体或者在逻辑上相连接的物体集合。

图11.12:锥形跟踪通过在场景几何与半径不断增大的球体之间,进行一系列的相交测试来近似遮挡情况。测试球体与圆锥的侧面相接,距离顶点越远,球体的半径就越大。在每一次步进中,锥体的角度都会因为相交遮挡而减小,以考虑场景几何形状的遮挡情况。最终的遮挡因子为裁剪过后的圆锥立体角与原始圆锥立体角之比,这是一个估计值。

Crassin等人[305]在场景的体素表示中描述了一种类似的方法。他们使用稀疏体素八叉树(章节13.10)来存储场景的体素化信息。他们用于计算环境光遮蔽的算法,实际上是一种通用完整全局光照算法的特例(详见章节11.5.7)。

Ren等人[1482]则将遮挡物近似为球体,如图11.13所示,并使用球谐函数来表示表面点被单个球体遮挡的可见性函数,这样一组球体聚合起来的可见性函数,就是单个球体可见性函数的乘积。但不幸的是,计算球谐函数的乘积是一个成本很高的操作。他们的核心思想是:对单个球谐可见性函数的对数进行求和,然后再对结果取指数。这样所产生的结果与可见性函数相乘的结果相同,但是球谐函数的求和操作,其计算成本明显要比乘法小。这篇论文表明,在正确的近似方法下,可以通过执行快速的对数运算和指数运算,从而获得整体加速效果。

这种方法计算出的不仅仅是环境遮挡因子,而是一个完整的球面可见性函数,它使用了球谐函数来进行表示(详见章节10.3.2)。其中,球谐函数的第一个系数(0阶)可以作为环境遮挡因子kAk_A,后面三个系数(1阶)可以用于计算环境法线nbent \mathbf{n}_{\text {bent }}。更高阶的系数可以用于阴影环境贴图或者圆形光源。由于这种方法将几何体近似为包围球,因此无法对来自折痕或者其他小细节的遮挡情况进行建模。

图11.13:这种方法生成的环境光遮蔽效果是模糊的,无法显示遮挡细节。可以使用更简单的几何表示来计算AO,这样仍然可以实现合理的效果。上图将一个犰狳模型(左)近似为一组球体(右)。在这两个例子中,模型在背后墙上投下的遮挡阴影几乎一样。

Sloan等人[1655]在屏幕空间中,对Ren所描述的可见性函数进行了求和。对于每个遮挡物,他们都会考虑一组像素,这组像素距离着色点的距离,小于所规定的世界空间距离。这个操作可以通过渲染一个球体,并在着色器中执行距离测试或者使用模板测试来实现。对于所有受到影响的屏幕区域,会将一个适当的球谐函数值添加到一个离屏缓冲区中。在获得所有遮挡物的可见性之后,会对缓冲区中的值进行求幂运算,最终获得每个屏幕像素上的组合可见性函数。Hill [737]使用了相同的方法,但是他将球谐可见性函数限制到二阶系数。在这种假设下,球谐函数的乘积运算只涉及到少量的标量乘法,甚至可以通过GPU的固定功能混合硬件来完成。这使得我们可以在性能有限的主机硬件上使用这种方法。由于该方法只使用了低阶的球谐函数,因此无法生成具有清晰边界的硬阴影,只能生成无方向的遮挡。

11.3.6 屏幕空间方法

基于模型空间的方法,其开销与场景的复杂度成正比。然而,我们完全可以从屏幕空间中已有的数据出发,推导出一些有关遮挡的信息,例如深度和法线。这种基于屏幕空间的算法,具有恒定的开销,其复杂度与与场景的细节程度无关,只与渲染时所使用的画面分辨率有关。

在实践中,屏幕空间算法的执行时间,还取决于数据在深度缓冲或者法线缓冲中的分布,因为这种数据分散效应,在进行遮挡计算的时候,会降低GPU缓存的命中率,从而延长算法的执行时间。

图11.14:Crytek的环境光遮蔽方法,被应在了图中的三个表面点(黄色圆圈)上。这里为了清晰起见,使用了二维形式来展示该算法的流程,相机位于图像内容的正上方(未显示在图中)。在这个例子中,有10个样本分布在了围绕表面点的圆盘上(实际上它们是分布在一个球体内部)。未通过深度测试的样本点使用红色进行表示,即该样本所对应的深度,超过了z-buffer中对应位置的深度;通过的样本则使用绿色进行表示。环境遮挡因子 k_A 的值是通过测试的样本数与总样本数的加权比值。为了简单起见,这里我们先忽略了可变的样本权重,假设所有的样本都具有相同的权重。对于左边的像素点,总共10个样本,其中有6个通过,因此 k_A=0.6 。对于中间的像素点,只有3个样本通过了测试。还有一个样本虽然在物体外部,但是没有通过深度测试,如图中红色箭头所示,最终计算出的 k_A=0.3 。对于右边的像素点,只有1个样本通过了测试,因此 k_A=0.1 。

Crytek [1227]开发了一种动态的屏幕空间环境光遮蔽(screen-space ambient occlusion,SSAO)算法,并用在了《孤岛危机》中。他们使用z-buffer作为唯一的输入,在一个全屏pass中计算来环境光遮蔽效果。每个像素都有一个环境遮挡因子kAk_A,它会在该像素周围的球形范围内采样一组点,并将样本与z-buffer进行深度测试,然后来估计kAk_AkAk_A的值与z-buffer中位于像素点深度前面的测试样本有关,通过的样本数量越少,kAk_A的值就越低,如图11.14所示。与obscurance因子相类似[1970],这些样本的权重会随着到像素距离的增大而减小,即距离像素越远,该样本的权重就越小。需要注意的是,由于这些样本并没有被余弦因子(nl)+(\mathbf{n} \cdot \mathbf{l})^{+}加权,因此所产生的环境光遮蔽效果是不正确的。该方法会将球形范围内的所有样本都考虑在内,而不是只考虑表面上半球范围内的样本。这种简化意味着会对表面以下的样本进行计数,但是实际上我们是不应当对它们进行计数的。这样做会导致表面变暗(因为环境遮挡因子kAk_A变大了),同时边缘会比周围环境更亮。尽管如此,最终产生结果在视觉上令人十分满意,如图11.15所示。

图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,并将其定义为:

vA=xXρ(d(x))o(x)dx(11.15)v_{A}=\int_{\mathbf{x} \in X} \rho(d(\mathbf{x})) o(\mathbf{x}) d \mathbf{x} \tag{11.15}

其中XX是围绕该像素点的一个三维球形邻域;ρρ是距离映射函数,与方程11.11所描述的相类似;dd是距离函数;o(x)o(\mathbf{x})是占用函数(occupancy function),如果x\mathbf{x}未被占用,则o(x)o(\mathbf{x})等于0,否则等于1。他们注意到,ρ(d)ρ(d)函数对于最终视觉质量的影响很小,因此可以使用常数函数。在这个假设下,体积obscurance是对占用函数在像素点邻域上的积分。Crytek的方法是在三维邻域内进行随机采样从而计算积分,而Loos和Sloan则通过对像素的屏幕空间邻域随机采样,在xyxy维度上进行积分,对zz轴的积分过程则是解析的。如果该点的球面邻域中不包含任何几何图形,则积分值等于射线与球体XX相交的长度。如果该点的球面邻域中存在几何图形,则会使用深度缓冲来作为占用函数的近似值,并且仅会在每个线段的未占用部分上进行积分,如图11.16左侧所示。该方法最终生成的结果,其质量与Crytek的方法相当,但是使用的样本数量较少,因为在其中一个维度上(zz轴)的积分是精确的。如果可以使用表面法线的话,还可以对这个方法进一步扩展,从而获得更好的结果。在这个考虑法线的版本中,线积分会被限制在由像素点法线所定义的平面上。

图11.16:体积obscurance(左)使用了线积分,来计算像素点周围的未占用体积的积分。体积环境光遮蔽(右)同样也使用了线积分,不同的是,它计算了与着色点相切球体的占用率,这对反射方程中的余弦项进行了模拟。在这两种情况下,积分的结果都是球体的未占用体积(绿色实线)与球体总体积(未占用体积与占用体积之和,其中占用体积使用红色虚线进行表示)的比值。对于这两幅图像,相机都是从上往下观察的,其中绿色点代表了从深度缓冲中读取的样本,黄色点代表了正在计算遮挡情况的样本。

Szirmay-Kalos等人[1733]提出了另一种使用法线信息的屏幕空间方法,它被称为体积环境光遮蔽(volumetric ambient occlusion)。方程11.6描述了在法线半球上进行的积分,这个积分还包含了余弦项。他们提出,这种类型的积分,可以将被积函数中的余弦项移除,并使用余弦分布来限制积分范围,从而对余弦因子进行近似。这样做可以将积分转换到一个球面上,而不是在一个半球上;这个球体的半径为半球的一半,并且会沿着法线移动一个球体半径的距离,最终这个球体会与半球内接,被半球完全包裹。其中未被占用部分的体积,其计算方法与Loos和Sloan的方法一样,都是通过在像素邻域上进行随机采样,并在zz轴上对占用函数进行解析积分,如图11.16右侧所示。

Bavoil等人[119]提出了一种不同的方法,用于解决估计局部可见性的问题,他们从Max [1145]的视界映射(horizon mapping)中获得了灵感。他们的方法被称为基于视界的环境光遮蔽(horizon-based ambient occlusion,HBAO),它假设z-buffer中的数据表示了一个连续的高度场。通过确定视界角(horizon angle),可以对像素点的可见性进行估计,这里的视界角,指的是切面上方被邻域遮挡的最大角度。也就是说,对于某个点上的给定方向,我们会记录最高的可见物体所对应的角度。如果我们忽略积分中的余弦项,那么环境遮挡因子可以被计算为视界上未被遮挡部分的积分,或者是1减去视界下被遮挡部分的积分:

kA=112πϕ=ππα=t(ϕ)h(ϕ)W(ω)cos(θ)dθdϕ(11.16)k_{A}=1-\frac{1}{2 \pi} \int_{\phi=-\pi}^{\pi} \int_{\alpha=t(\phi)}^{h(\phi)} W(\omega) \cos (\theta) d \theta d \phi \tag{11.16}

其中h(ϕ)h(\phi)是切平面的视界角;t(ϕ)t(\phi)是切平面与观察向量的切角(tangent angle);W(ω)W(\omega)是衰减函数,如图11.17所示。积分前面的1/2π{1}/{2 \pi}是归一化系数,它将积分的结果归一化到[0,1][0,1]之间。

图11.17:HBAO(左)通过找到切平面上方的视界角 h ,并对视界角之间的未遮挡角度进行积分,从而计算环境遮挡因子。切平面和观察向量之间的角度记为 t 。GTAO使用了相同的视界角度 h_1 和 h_2 ,同时还使用了法线和观察向量之间的角度 \gamma ,并将余弦项添加到计算中。在上述两幅图中,相机都是从上往下观察场景的,图中显示的是场景横截面,其中视界角是角度 \phi 的函数, \phi 是一个相对于观察方向的角度。图中绿色的点代表了从深度缓冲中读取的样本。黄色点代表了正在进行遮挡计算的样本。

对于定义视界的角度ϕ\phi,我们利用角度的线性衰减,可以解析地计算内部的积分:

kA=112πϕ=ππ(sin(h(ϕ))sin(t(ϕ)))W(ϕ)dϕ(11.17)k_{A}=1-\frac{1}{2 \pi} \int_{\phi=-\pi}^{\pi}(\sin (h(\phi))-\sin (t(\phi))) W(\phi) d \phi \tag{11.17}

这个剩余的积分,是通过对一些方向进行采样,来找到视界角度,从而进行数值计算的。

Jimenez等人[835]也使用了这种基于视界的方法,他们称之为真实环境光遮蔽(ground-truth ambient occlusion ,GTAO)。他们的目标是实现ground-truth的结果,并能够与光线跟踪的结果相匹配,该方法所使用的唯一信息,就是由z-buffer构建的高度场。HBAO在计算遮挡的时候并不包括余弦项,并且它还增加了一个特殊的衰减(没有出现在方程11.8中),因此它的结果最多只能与光线追踪相接近,但是始终还是不一样的。GTAO引入了缺失的余弦因子,去除了这个特殊的衰减函数,并在绕观察向量的参考系中给出了遮挡积分,该方法的遮挡因子定义如下:

kA=1π0πh1(ϕ)h2(ϕ)cos(θγ)+sin(θ)dθdϕ(11.18)k_{A}=\frac{1}{\pi} \int_{0}^{\pi} \int_{h_{1}(\phi)}^{h_{2}(\phi)} \cos (\theta-\gamma)^{+}|\sin (\theta)| d \theta d \phi \tag{11.18}

其中h1(ϕ)h_1(\phi)h2(ϕ)h_2(\phi)为给定ϕ\phi的左右视界角;γγ为表面法线与观察方向之间的夹角。这里积分的归一化项为1/π{1}/{\pi},这与HBAO中的不同,因为GTAO包含了余弦项,这使得开放半球的积分结果为π\pi,如果方程中不包含余弦项,则开放半球的积分结果为2π2\pi。在给定高度场假设的情况下,方程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)可以避免在表面的不连续处进行过滤,从而保持尖锐的边缘。它可以利用可用的深度信息或者法线信息来对过滤进行限制,即它只会对属于同一表面的样本进行过滤。还有一些方法使用了随机变化的采样模式,以及经过实验选择的滤波核;另一些方法则使用了固定大小的屏幕空间采样模式(例如4×44 × 4像素),以及一个限制在该邻域上的滤波核。

环境光遮蔽的计算也可以在时域上进行超采样[835, 1660, 1916]。通常会在每一帧中应用不同的采样模式,并对计算出来的遮挡因子进行指数平均从而实现这个目的。使用上一帧的z-buffer、相机变换和动态物体的运动信息,来将上一帧的数据重新投影到当前视图中,然后再将其与当前帧的结果进行混合。还会使用一些基于深度、法线、速度的启发式方法,来检测上一帧数据的可靠性,对于不可靠的数据需要丢弃(例如:由于一些新物体进入了视野中,因此上一帧中的数据与当前帧存在差异)。章节5.4.2在更一般的情况下,介绍了时域超采样和时域抗锯齿技术。时域过滤的成本较小,并且很容易实现,虽然它并不总是完全可靠的,但是在实践中出现的大多数问题都不太明显。这主要是因为环境光遮蔽不会直接单独显示在画面上,它只是光照计算的输入之一。在将这种环境光遮蔽效果与法线贴图、反照率纹理以及直接光照相结合之后,任何微小的瑕疵都会被掩盖掉,人眼一般很难观察到这些瑕疵。

11.3.7 使用环境光遮蔽进行着色

虽然我们是在恒定、遥远光照环境中推导出的环境光遮蔽值,但是我们也可以将其应用于更复杂的光照场景中。再次回顾一些反射方程:

Lo(v)=lΩf(l,v)Li(l)v(l)(nl)+dl.(11.19)L_{o}(\mathbf{v})=\int_{\mathbf{l} \in \Omega} f(\mathbf{l}, \mathbf{v}) L_{i}(\mathbf{l}) v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l}. \tag{11.19}

如章节11.3.1中所介绍的,方程11.19中包含了可见性函数v(l)v(\mathbf{l})

假如我们现在正在处理一个漫反射表面,我们可以使用Lambertian BRDF来代替方程11.19中的f(l,v)f(\mathbf{l}, \mathbf{v}),这个BRDF等于次表面反照率ρss\rho_{\mathrm{ss}}除以π\pi,将其带入方程11.19,可得:

Lo=lΩρssπLi(l)v(l)(nl)+dl=ρssπlΩLi(l)v(l)(nl)+dl.(11.20)L_{o}=\int_{\mathbf{l} \in \Omega} \frac{\rho_{\mathrm{ss}}}{\pi} L_{i}(\mathbf{l}) v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l}=\frac{\rho_{\mathrm{ss}}}{\pi} \int_{\mathbf{l} \in \Omega} L_{i}(\mathbf{l}) v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l}. \tag{11.20}

我们对方程11.20进行一些化简整理,可得:

Lo=ρssπlΩLi(l)v(l)(nl)+dl=ρssπlΩLi(l)v(l)(nl)+dllΩv(l)(nl)+dllΩv(l)(nl)+dl=ρssπlΩLi(l)v(l)(nl)+lΩv(l)(nl)+dldllΩv(l)(nl)+dl.(11.21)\begin{aligned} L_{o} & =\frac{\rho_{\mathrm{ss}}}{\pi} \int_{\mathbf{l} \in \Omega} L_{i}(\mathbf{l}) v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} \\ & =\frac{\rho_{\mathrm{ss}}}{\pi} \frac{\int_{\mathbf{l} \in \Omega} L_{i}(\mathbf{l}) v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l}}{\int_{\mathbf{l} \in \Omega} v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l}} \int_{\mathbf{l} \in \Omega} v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} \\ & =\frac{\rho_{\mathrm{ss}}}{\pi} \int_{\mathbf{l} \in \Omega} L_{i}(\mathbf{l}) \frac{v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+}}{\int_{\mathbf{l} \in \Omega} v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l}} d \mathbf{l} \int_{\mathbf{l} \in \Omega} v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} .\end{aligned} \tag{11.21}

如果我们使用方程11.8中所定义的环境光遮蔽,则方程11.21可以简化为:

Lo=kAρsslΩLi(l)K(n,l)dl(11.22)L_{o}=k_{A} \rho_{\mathrm{ss}} \int_{\mathbf{l} \in \Omega} L_{i}(\mathbf{l}) K(\mathbf{n}, \mathbf{l}) d \mathbf{l} \tag{11.22}

其中:

K(n,l)=v(l)(nl)+lΩv(l)(nl)+dl(11.23)K(\mathbf{n}, \mathbf{l})=\frac{v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+}}{\int_{\mathbf{l} \in \Omega} v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l}} \tag{11.23}

上述形式为我们提供了一个全新的视角来看待这个过程。方程11.22中的积分,可以认为是对入射radiance LiL_i应用了一个方向性的滤波核KK。滤波器KK以一种复杂的方式在空间和方向上同时变化,但它具有两个重要的属性。首先,由于对点积进行了clamp操作,因此它最多只能覆盖点p\mathbf{p}法线周围的半球范围。其次,由于分母中包含归一化因子,因此它在整个半球上的积分等于1。

为了进行着色,我们需要计算两个函数乘积的积分,即入射radiance LiL_i和滤波器函数KK乘积的积分。在某些情况下,我们可以使用一种简化的方式来描述这个滤波器,并以很低的成本来计算这个二重积分,例如当LiL_iKK都使用球谐函数来进行表示的时候(章节10.3.2)。降低这个方程复杂度的另一种方法是,使用一个具有类似特性,但是更简单的滤波器来对其近似。最常见的选择就是归一化的余弦核函数HH

H(n,l)=(nl)+lΩ(nl)+dl(11.24)H(\mathbf{n}, \mathbf{l})=\frac{(\mathbf{n} \cdot \mathbf{l})^{+}}{\int_{\mathbf{l} \in \Omega}(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l}} \tag{11.24}

在没有入射光线被阻挡的时候,这种近似是十分准确的。它还涵盖了与原本滤波器相同的角度范围。虽然它完全忽略了可见性函数,但是方程11.22中仍然包含了环境光遮蔽kAk_A,因此在被着色的表面上会有一些与可见性相关的暗化。

选择了这个近似滤波核,那么方程11.22就变成了:

Lo=kAρsslΩLi(l)(nl)+lΩ(nl)+dldl=kAπρssE.(11.25)L_{o}=k_{A} \rho_{\mathrm{ss}} \int_{\mathbf{l} \in \Omega} L_{i}(\mathbf{l}) \frac{(\mathbf{n} \cdot \mathbf{l})^{+}}{\int_{\mathbf{l} \in \Omega}(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l}} d \mathbf{l}=\frac{k_{A}}{\pi} \rho_{\mathrm{ss}} E. \tag{11.25}

这意味着,在最简单的形式中,可以通过计算irradiance,并将其乘上环境光遮蔽值来完成环境光遮蔽的效果着色。这里的irradiance可以来自任何来源,例如:它可以从irradiance环境贴图(章节10.6)中进行采样。这种方法的准确性,取决于这个近似滤波器有多大能力能够表现正确滤波器。对于在球面上平滑变化的光照,这种近似方式能够给出合理的结果。如果LiL_i在所有可能的方向上都是恒定的,就好像场景是由全白的环境贴图所照亮的那样,在这种情况下,它是完全准确的。

这个方程还让我们了解到,为什么环境光遮蔽对于精确光源或者很小的面光源而言是一个很差的可见性近似,因为这些光源在表面上只占据了很小的一个立体角(对于精确光源而言是无穷小的),而可见性函数对光照积分会产生重要影响。它几乎是以二进制的方式来控制光源的贡献,也就是说,它要么完全启用,要么完全禁用。忽略可见性(正如我们在方程11.25中所做的那样)是一个影响很大的近似操作,这样做通常不会产生符合预期的结果。在这种近似情况下,所产生的阴影缺乏清晰度,并且没有任何预期的方向性,也就是说,它们看起来并不像是由特定光源产生的。对这种光源的可见性进行建模,环境光遮蔽并不是一个好的选择,应当使用一些其他的方法,例如阴影贴图等。然而,值得注意的是,有时候我们会使用较小的局部光源来模拟间接光照的效果,在这种情况下,使用环境光遮蔽值来调整它们的贡献是合理的。

到目前为止,我们都是假设在Lambertian表面上进行着色的。在处理更加复杂的、非常数的BRDF时,这一项无法从积分中提出来(就像我们在方程11.20中所做的那样)。对于镜面材质而言,KK不仅取决于可见性和法线,还取决于观察方向。对于一个典型的微表面BRDF而言,其波瓣会在整个区域上发生显著改变;使用单一的、预先确定的形状来对其近似会显得过于粗糙,无法产生可信的结果。这也就是为什么在漫反射BRDF中,使用环境光遮蔽进行着色最有意义的原因。我们会在接下来的若干小节中讨论一些其他方法,它们更加适合复杂的材质模型。

使用环境法线(详见方程11.10)可以更加精确地近似滤波器KK。虽然滤波器中仍然没有包含可见性项,但是其最大值与未被遮挡的平均方向相匹配,这使得它在总体上可以更好地逼近方程11.23。当几何法线和环境法线不匹配的时候,使用环境法线将会给出更加准确的结果。Landis [974]不仅将它用在环境贴图的着色中,还用在了一些直接光照的着色中,来代替常规的阴影技术。

对于环境贴图的着色,Pharr [1412]提出了一种替代方案,该方法使用GPU的纹理过滤硬件来动态执行滤波操作。滤波器KK的形状是动态确定的,其滤波中心位于环境法线的方向上,其大小取决于kAk_A的值,这样可以更加精确地与方程11.23中的原始滤波器相匹配。

11.4 定向遮蔽

尽管单独使用环境光遮蔽可以极大地提高图像的视觉质量,但它毕竟是一个大大简化了的模型。在处理大面积光源的时候,它所给出的可见性近似很差,更不用说较小的光源或者精确光源了。它也无法正确处理光滑的BRDF或者更加复杂的光照环境。想象现在有一个表面,它被远处的圆形顶灯所照亮,这个圆形顶灯的颜色从红色渐变为绿色。这个圆形顶灯可能会用来代表来自天空的光线,又或者是来自某个遥远的星球的光线,如图11.18所示。即使环境光遮蔽会让点a\mathbf{a}和点b\mathbf{b}的光照变暗,但是它们仍然会被红色和绿色的天空所照亮。使用环境法线可以缓解这种效果,但是这样做也不是完美的。我们之前所提出的简单模型不够灵活,无法处理这种特殊情况,其中一种解决方案是,使用一些更具表现力的方式来描述可见性。

图11.18:图中展示了在复杂的光照条件下,点 \mathbf{a} 和点 \mathbf{b}  irradiance的近似颜色。环境光遮蔽无法模拟任何方向性,因此这两个点上的颜色是相同的。使用环境法线可以有效地将余弦波瓣移向天空的未遮挡部分,但是由于积分范围没有受到任何限制,因此所提供的结果还不够准确。定向遮蔽方法能够正确地消除来自天空中被遮挡部分的光线。

我们将专注于编码整个球面可见性或者半球可见性的方法,即描述哪些方向会阻挡入射radiance的方法。这些信息可以用来为精确光源产生阴影,但这并不是它的主要目的。针对这些特定类型光源的专用方法(详见第7章),能够生成质量更好的阴影,因为它们只需要对光源的某个位置或者某个方向进行可见性编码即可。

这里我们所要描述的解决方案,主要是用于为大面积光源或者环境照明提供遮挡效果,这些方法可以生成柔和的阴影,并且由近似可见性所引起的瑕疵也不是很明显。此外,这些方法还可以在常规阴影技术无法运行的时候,提供一些遮挡效果,例如凹凸贴图细节所产生的自阴影,以及超大场景的阴影,导致阴影贴图没有足够的分辨率。

11.4.1 预计算定向遮蔽

Max [1145]引入了视界映射(horizon mapping)的概念来描述高度场表面的自遮挡现象。在视界映射中,对于表面上的每个点,会根据一组方位角方向来确定视界角度,例如8个方向:北、东北、东、东南、以此类推。

我们可以不存储在给定方位上的视界角,而是将未遮挡的三维方向集合作为一个整体,将其建模为椭圆[705, 866]或者圆形[1306, 1307]孔径,其中后一种技术被称为环境光圈照明(ambient aperture lighting,如图11.19所示)。这些技术对存储的要求比视界映射低,但是当未遮挡的方向不像椭圆或者圆的时候,可能会产生错误的阴影效果。例如在一个平面上,以规则间隔突出的山峰,应该设置一个星形的未遮挡方向,这与上述椭圆方案和圆形方案不匹配。

图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拆分为远处的照明LiL_i及其可见性vv

Lo(v)=lΩf(l,v)Li(l)v(l)(nl)+dl(11.26)L_{o}(\mathbf{v})=\int_{\mathbf{l} \in \Omega} f(\mathbf{l}, \mathbf{v}) L_{i}(\mathbf{l}) v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} \tag{11.26}

我们能够做的最简单的操作,就是使用可见形信号来遮挡精确光源。由于大多数编码可见性的方法都很简单,其结果的质量往往也不太令人满意,但是这样的方法可以让我们在一个基本的例子中进行推理。这种方法同样也可以用于传统阴影方法由于分辨率不足而失效的情况,在这种情况下,生成的结果精度没有那么重要,总比没有任何形式的遮挡要好得多。这种情况包括:面积非常大的地形模型,使用凹凸贴图表示的表面微小细节等。

根据章节9.4中的讨论,当处理精确光源的时候,方程11.26会变为:

Lo(v)=πf(lc,v)clight v(lc)(nlc)+(11.27)L_{o}(\mathbf{v})=\pi f\left(\mathbf{l}_{c}, \mathbf{v}\right) \mathbf{c}_{\text {light }} v\left(\mathbf{l}_{c}\right)\left(\mathbf{n} \cdot \mathbf{l}_{c}\right)^{+} \tag{11.27}

其中clight \mathbf{c}_{\text {light }}是一个纯白的Lambertian表面正对光源时所反射出的颜色,lc\mathbf{l}_{c}是指向光源的颜色。

我们可以把上面的方程解释为,首先计算材质对未遮挡光源的响应结果,再将结果乘以可见性函数的值。如果光线方向位于视界以下(当使用视界映射时)、或者位于可见锥之外(当使用环境光圈照明时)、或者位于SSDF的负区域,那么可见性函数的值为零,因此不需要考虑来自光源的任何贡献。值得一提的是,尽管可见性函数被定义为一个二进制函数,但是许多表示方式都可以返回整个范围内的值,即[0,1][0,1],而不仅仅是非0即1,位于范围内的非整数值代表部分遮挡的情况。

至少在大多数情况下是这样。在某些情况下,我们希望可见性函数取0和1以外的值,但是仍然位于[0,1][0,1]范围内。例如:当对由半透明材质所引起的遮挡进行编码时,我们可能会希望使用小数遮挡值。

由于振铃效应,球谐函数或者H-basis甚至可能会重建出负值,这些行为是我们不想要的,它只是编码方式的固有属性。

我们可以对面光源进行类似的推理。在这种情况下,位于光源所对应的立体角内,LiL_i等于光源发出的radiance;位于光源所对应的立体角外,LiL_i为零。我们将其记作LlL_l,并假设它在光源立体角上是恒定的。此时我们可以将对整个球体Ω\Omega的积分,转换为对光源立体角Ωl\Omega_l的积分,即:

Lo(v)=LllΩlv(l)f(l,v)(nl)+dl(11.28)L_{o}(\mathbf{v})=L_{l} \int_{\mathbf{l} \in \Omega_{l}} v(\mathbf{l}) f(\mathbf{l}, \mathbf{v})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} \tag{11.28}

如果我们假设方程中的BRDF也是常数,例如Lambertian表面,那么它也可以从积分中提出来,即:

Lo(v)=ρssπLllΩlv(l)(nl)+dl(11.29)L_{o}(\mathbf{v})=\frac{\rho_{\mathrm{ss}}}{\pi} L_{l} \int_{\mathbf{l} \in \Omega_{l}} v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} \tag{11.29}

为了确定被遮挡的光照,我们需要计算可见性函数乘上余弦项,在光源所对应的立体角上的积分。在某些情况下,这个积分可以通过解析计算出来。Lambert [967]推导了一个方程,用于计算一个球面多边形上的余弦积分。如果我们的面光源是多边形的,并且我们可以根据可见性表示来对其进行剪裁的话,那么我们只需要使用这个Lambert方程就可以得到精确的结果,如图11.20所示。例如:当我们选择视界角作为可见性表示的时候,就可以这么做。然而,如果出于某种原因,我们选择了其他的编码方式,例如环境圆锥(bent cone),此时再对光源进行裁剪将会产生圆形片段,因此我们将无法再使用Lambert方程。如果我们的面光源是非多边形的话,上述原则同样适用。

图11.20:将一个黄色的多边形光源,投影到着色点的单位半球上,形成了一个球面多边形。如果使用视界映射来描述可见性的话,则可以对这个球面多边形进行裁剪,裁剪的结果使用红色进行表示。红色多边形的余弦加权积分可以使用Lambert方程进行解析计算。

还有另外一种可能的假设方法,即假设余弦项的值在整个积分域中是个常数。如果面光源的尺寸很小的话,那么这种近似是相当精确的。简单起见,我们可以使用面光源中心方向所对应的余弦值。这时,我们只需要计算可见性函数在光源立体角上的积分即可。下一步的操作,还是取决于我们所选择的可见性表示方法和面光源类型。如果我们使用球形光源,并且使用环境圆锥来表示可见性的话,那么积分的值就是可见性圆锥与光源圆锥相交部分所对应的立体角。这部分是可以解析计算的,Oat和Sander [1307]推导出了一种计算方法,虽然精确求解的方程相当复杂,但是好在他们还提供了一个近似解,这个近似解在实践中十分有效。如果使用球谐函数来编码可见性的话,那么这个积分同样也可以解析计算。

对于环境光照而言,我们无法限制积分的范围,因为光照是来自四面八方的。我们需要找到一种方法来计算方程11.26中的完整积分。为了简单起见,让我们首先考虑Lambertian BRDF:

Lo(v)=ρssπlΩLi(l)v(l)(nl)+dl(11.30)L_{o}(\mathbf{v})=\frac{\rho_{\mathrm{ss}}}{\pi} \int_{\mathbf{l} \in \Omega} L_{i}(\mathbf{l}) v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} \tag{11.30}

这个方程中的积分叫做三重乘积积分(triple product integral)。如果其中的单个函数可以使用特定的方式来进行表示的话(例如球谐函数或者小波),那么它是可以通过解析计算出来的。但不幸的是,这对于通常的实时应用程序而言太昂贵了,尽管这样的解决方案已经被证明,可以在简单的环境设置中以交互式帧率来运行[1270]。

不过,我们的这个例子稍微简单一些,因为其中一个函数是余弦函数。我们可以将方程11.30改写为:

Lo(v)=ρssπlΩLi(l)v(l)dl(11.31)L_{o}(\mathbf{v})=\frac{\rho_{\mathrm{ss}}}{\pi} \int_{\mathbf{l} \in \Omega} \overline{L_{i}}(\mathbf{l}) v(\mathbf{l}) d \mathbf{l} \tag{11.31}

或者:

Lo(v)=ρssπlΩLi(l)vˉ(l)dl(11.32)L_{o}(\mathbf{v})=\frac{\rho_{\mathrm{ss}}}{\pi} \int_{\mathbf{l} \in \Omega} L_{i}(\mathbf{l}) \bar{v}(\mathbf{l}) d \mathbf{l} \tag{11.32}

其中:

Li(l)=Li(l)(nl)+,v(l)=v(l)(nl)+.\begin{aligned} \overline{L_{i}}(\mathbf{l}) & =L_{i}(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+}, \\ \overline{v}(\mathbf{l}) & =v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} .\end{aligned}

Li(l)L_{i}(\mathbf{l})v(l)v(\mathbf{l})一样,Li(l)\overline{L_{i}}(\mathbf{l})v(l)\overline{v}(\mathbf{l})都是球面函数。我们没有尝试直接去计算这个三重乘积积分,而是首先将余弦项乘以LiL_i(方程11.31)或者viv_i(方程11.32),这样做使得被积函数变成两个函数的乘积。虽然这看起来只是一个数学技巧,但是它可以极大地简化计算。如果被积函数中的乘积因子,使用了标准正交基(例如球谐函数)来进行表示,那么这个二重乘积积分可以很简单的计算出来,积分的结果就是它们系数向量的点积(章节10.3.2)。

但是我们仍然需要计算Li(l)\overline{L_{i}}(\mathbf{l})或者v(l)\overline{v}(\mathbf{l}),由于它们都包含了余弦项,因此要比完全一般的情况稍微简单一些。如果我们使用球谐函数来表示这些函数,那么余弦函数将会被投影到球带谐波(zonal harmonics,ZH)上。球带谐波是球谐函数的一个子集,其中每个频带只有一个系数是非零的(详见章节10.3.2)。这个投影的系数有一个很简单的解析公方程[1656]。SH和ZH的乘积计算效率,要比SH和SH的乘积高得多。

如果我们决定先将余弦项乘以vv(方程11.32),那么我们可以在离线环境中对其进行预计算,同时只需要存储可见性即可。正如Sloan等人[1651]所描述的那样(章节11.5.3),这是一种形式的预计算radiance传输(precomputed radiance transfer)。然而,在这种形式下,我们无法对法线进行任何精细的修改,因为由法线控制的余弦项已经和可见性函数融合在一起了。如果我们想要模拟精细尺度的法线细节,则可以先用LiL_i乘以余弦项(方程11.31)。由于我们事先并不知道法线的具体方向,因此可以预先计算出不同法线所对应的乘积[805],或者是在运行过程中动态执行乘法操作[809]。离线预计算LiL_i和余弦项的乘积意味着,我们对光照的任何修改都会受到限制,并且允许光照在空间上发生变化会消耗大量的内存。另一方面,在运行时计算这个乘积的开销也很高。Iwanicki和Sloan [809]描述了如何降低这一操作的成本,在他们的例子中,这个乘积可以在更低的粒度(顶点)上进行计算。乘积的结果与余弦项进行卷积,再投影到一个更简单的表示方法(AHD)上,然后再使用逐像素的法线进行插值和重建。这种方法允许他们在60 FPS的游戏中,使用LiL_i乘以余弦项的策略。

Klehm等人[904]提出了一种使用环境贴图表示光照,并使用锥形编码可见性的解决方案。他们使用了不同大小的滤波核来对环境贴图进行过滤,这些滤波核代表了不同锥形开口的可见性与光照乘积的积分。他们按照锥形开口角度大小的增加,将结果存储在纹理的mipmap中。这样做是合理的,因为较大锥形开口的预过滤结果在球体上会平滑变化,因此不需要使用较高分辨率来进行存储。在预过滤的过程中,他们假设可见性锥的方向与法线是对齐的,这是一个近似假设,但是在实践中可以给出较为可信的结果。他们还分析了这种近似是如何对最终质量产生影响的。

如果我们需要处理光泽BRDF和环境光照,那么情况就要更加复杂了。此时我们无法再将BRDF从积分中提取出来,因为它并不是一个常数。为了解决这个问题,Green等人[582]建议用一组球面高斯函数(spherical Gaussian)来对BRDF本身进行近似。这些球面高斯函数都是径向对称的,它们可以使用三个参数来进行表示(十分紧凑):方向(或者平均值)d\mathbf{d},标准差μμ和振幅ww。这个近似BRDF可以定义为球面高斯函数的和:

f(l,v)kwk(v)G(dk(v),μk(v),l)(11.33)f(\mathbf{l}, \mathbf{v}) \approx \sum_{k} w_{k}(\mathbf{v}) G\left(\mathbf{d}_{k}(\mathbf{v}), \mu_{k}(\mathbf{v}), \mathbf{l}\right) \tag{11.33}

其中G(d,μ,l)G(\mathbf{d}, \mu, \mathbf{l})是一个球面高斯波瓣,它指向方向d\mathbf{d},锐度为μ\mu(详见章节10.3.2);wkw_{k}是第kk个波瓣的振幅。对于各向同性的BRDF而言,其波瓣的形状仅仅取决于法线方向和观察方向之间的夹角。我们可以将一组近似值存储在一维查找表中,并在运行时进行插值重建。

有了这个BRDF近似,我们可以将方程11.26改写成:

Lo(v)lΩkwk(v)G(dk(v),μk(v),l)Li(l)v(l)(nl)+dl=kwk(v)lΩG(dk(v),μk(v),l)Li(l)v(l)(nl)+dl.(11.34)\begin{aligned} L_{o}(\mathbf{v}) & \approx \int_{\mathbf{l} \in \Omega} \sum_{k} w_{k}(\mathbf{v}) G\left(\mathbf{d}_{k}(\mathbf{v}), \mu_{k}(\mathbf{v}), \mathbf{l}\right) L_{i}(\mathbf{l}) v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} \\ & =\sum_{k} w_{k}(\mathbf{v}) \int_{\mathbf{l} \in \Omega} G\left(\mathbf{d}_{k}(\mathbf{v}), \mu_{k}(\mathbf{v}), \mathbf{l}\right) L_{i}(\mathbf{l}) v(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} .\end{aligned} \tag{11.34}

Green等人还假设可见性函数在每个球面高斯的范围内都是恒定的,这使得他们可以将可见性项从积分中提取出来。他们在波瓣的中心方向上计算了可见性函数,最终的方程形式如下:

Lo(v)kwk(v)vk(dk(v))lΩG(dk(v),μk(v),l)Li(l)(nl)+dl(11.35)L_{o}(\mathbf{v}) \approx \sum_{k} w_{k}(\mathbf{v}) v_{k}\left(\mathbf{d}_{k}(\mathbf{v})\right) \int_{\mathbf{l} \in \Omega} G\left(\mathbf{d}_{k}(\mathbf{v}), \mu_{k}(\mathbf{v}), \mathbf{l}\right) L_{i}(\mathbf{l})(\mathbf{n} \cdot \mathbf{l})^{+} d \mathbf{l} \tag{11.35}

剩余的积分代表了入射光线与球面高斯进行卷积,这个球面高斯是给定方向和给定标准差的。这个卷积的结果可以进行预先计算,并存储在一个环境贴图中,其中较大的μ\mu所对应的卷积结果存储在较低的mipmap层级中。这里的可见性可以使用较低阶的球谐函数进行编码,或者是任何其他的表示方法,因为这里只需要进行点计算即可。

Wang等人[1838]以类似的方式来对BRDF进行近似,不同的是他们以一种更加精确的方式来处理可见性。他们的表示方法允许在可见性函数的范围内,计算单个球面高斯函数的积分。他们使用这个积分值来引入一个新的球面高斯函数,它具有相同的方向和标准差,但是振幅不同,他们会在实际的光照计算中使用这个新的球面高斯函数。

对于某些应用程序而言,这种方法可能会过于昂贵。因为它需要从预过滤的环境贴图中进行多次采样,而纹理采样往往会成为渲染过程中的瓶颈。Jimenez等人[835]和El Garawany [414]给出了更简单的近似方法,为了计算遮挡因子,他们使用一个圆锥来表示整个BRDF波瓣,忽略了BRDF波瓣对观察角度的依赖,只考虑材质粗糙度等参数,如图11.21所示。它们将可见性近似为一个圆锥体,并计算可见性圆锥与BRDF圆锥相交部分的立体角,就像环境光圈照明所做的那样。这个计算出来的标量结果会用于对光照的衰减,虽然这是一个重大的简化,但是最终的结果看起来是可信的。

图11.21:为了计算遮挡信息,光泽材料的镜面波瓣可以表示为一个圆锥。如果将可见性近似为另一个圆锥,那么遮挡因子可以通过这两个圆锥相交部分的立体角计算得来,这个方法与环境光圈照明相同(详见 图11.19 )。这张图片展示了使用一个圆锥来表示BRDF波瓣的一般原理,但这仅仅是为了进行说明。在实践中,为了产生更加合理的遮挡效果,这个圆锥需要更宽。