渲染过程最终计算的是radiance,到目前为止,我们一直在使用反射方程(reflectance equation)来其进行计算:
L o ( p , v ) = ∫ l ∈ Ω f ( l , v ) L i ( p , l ) ( n ⋅ l ) + d l (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∈Ωf(l,v)Li(p,l)(n⋅l)+dl(11.1)
其中 L o ( p , v ) L_{o}(\mathbf{p}, \mathbf{v}) Lo(p,v)是表面位置 p \mathbf{p} p在观察方向 v \mathbf{v} v上的出射radiance; Ω \Omega Ω是表面位置 p \mathbf{p} p的上半球范围; f ( l , v ) f(\mathbf{l}, \mathbf{v}) f(l,v)是观察方向 v \mathbf{v} v和当前光线入射方向 l \mathbf{l} l上的BRDF; L i ( p , l ) L_{i}(\mathbf{p}, \mathbf{l}) Li(p,l)是从光线方向 l \mathbf{l} l到达表面位置 p \mathbf{p} p的入射radiance; ( n ⋅ l ) + (\mathbf{n} \cdot \mathbf{l})^{+} (n⋅l)+是光线方向 l \mathbf{l} l和表面法线 n \mathbf{n} n之间的点积,并将负数结果clamp到0,即将来自表面下方的光线过滤掉。
渲染方程
&esmp;反射方程是完整渲染方程的一种特殊情况,它由Kajiya在1986年提出。渲染方程具有各种不同的表达形式,我们将使用以下这个版本:
L o ( p , v ) = L e ( p , v ) + ∫ l ∈ Ω f ( l , v ) L o ( r ( p , l ) , − l ) ( n ⋅ l ) + d l (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} Lo(p,v)=Le(p,v)+∫l∈Ωf(l,v)Lo(r(p,l),−l)(n⋅l)+dl(11.2)
其中多出来的一项为 L e ( p , v ) L_{e}(\mathbf{p}, \mathbf{v}) Le(p,v),它表示了从表面位置 p \mathbf{p} p向观察方向 v \mathbf{v} v发射的radiance,用于描述自发光表面的出射radiance。被积函数中有一项做了如下替换:
L i ( p , l ) = L o ( r ( p , l ) , − l ) (11.3) L_{i}(\mathbf{p}, \mathbf{l})=L_{o}(r(\mathbf{p}, \mathbf{l}),-\mathbf{l}) \tag{11.3} Li(p,l)=Lo(r(p,l),−l)(11.3)
这一项意味着,从方向 l \mathbf{l} l进入表面位置 p \mathbf{p} p的入射radiance,等于另一个表面位置向相反方向 − l -\mathbf{l} −l的出射radiance。在这种情况下,这里的“另一个表面位置”由光线投射函数(ray casting function) r ( p , l ) r(\mathbf{p}, \mathbf{l}) r(p,l)所定义的,这个函数会从表面位置 p \mathbf{p} p向方向 l \mathbf{l} l上发射一条光线,并返回所击中的第一个表面位置,如图11.1所示。
图11.1:图中展示了表面着色点 p \mathbf{p} p,光线方向 l \mathbf{l} l,光线投射函数 r ( p , l ) r(\mathbf{p}, \mathbf{l}) r(p,l),着色点 p \mathbf{p} p的入射radiance L i ( p , l ) L_{i}(\mathbf{p}, \mathbf{l}) Li(p,l),以及表面点 r ( p , l ) r(\mathbf{p}, \mathbf{l}) r(p,l)的出射radiance L o ( r ( p , l ) , − l ) L_{o}(r(\mathbf{p}, \mathbf{l}),-\mathbf{l}) Lo(r(p,l),−l)。
渲染方程的含义很简单。为了对表面位置 p \mathbf{p} p进行渲染,我们需要知道在观察方向 v \mathbf{v} v上,离开表面位置 p \mathbf{p} p的出射radiance L o L_o Lo,它等于该点自身发射的radiance L e L_e Le,再加上反射出的radiance。有关光源发射和反射率的内容,在前面几章我们已经讨论过了。甚至这里的光线投射操作好像看起来也不是那么陌生,例如:z-buffer实际上就计算了从相机投射到场景中的光线。
这里我们所遇到的唯一的新项是 L o ( r ( p , l ) , − l ) L_{o}(r(\mathbf{p}, \mathbf{l}),-\mathbf{l}) Lo(r(p,l),−l),这一项明确指出,入射到某一点上的radiance,一定是从另一点发出的。不幸的是,这是一个递归项,也就是说,如果我们想要计算点 r ( p , l ) r(\mathbf{p}, \mathbf{l}) r(p,l)在方向上 − l -\mathbf{l} −l上的出射radiance,那么我们首先还要知道来自表面位置 r ( r ( p , l ) , l ′ ) r\left(r(\mathbf{p}, \mathbf{l}), \mathbf{l}^{\prime}\right) r(r(p,l),l′)的出射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) r(r(r(p,l),l′),l′′)的出射radiance,直到无穷。令人十分惊讶的是,如此复杂的计算量,现实世界居然可以对其进行实时计算。
我们凭借直觉可以知道,光源照亮了一个场景,它所发出的光子(photon)在场景中四处反弹,每次与表面发生碰撞的时候,都会以各种方式被吸收、反射或者折射。渲染方程十分重要,因为它在一个简单的方程中总结了所有可能的光线路径。
渲染方程有一个重要的属性,即它与所发射出的光线呈线性关系。如果我们使光源的强度翻倍,那么最终的着色结果也会加倍变亮。同时,材质对于每种光源的响应也是相互独立的,也就是说,一种光源的存在并不会影响另一种光源与材质之间的相互作用。
在实时渲染中,只使用局部光照模型也是很常见的,我们只需要对可见点的表面数据进行光照计算即可,而这正是GPU最擅长的。传入GPU的各种图元被独立处理和光栅化,然后它们就会被丢弃,我们在点 b \mathbf{b} b执行光照计算时,无法访问点 a \mathbf{a} a的光照计算结果。诸如透明、反射和阴影效果,都是全局光照算法的范畴,它们利用了来自其他物体的信息,而不仅仅是被光源所照亮的物体。这些效果大大增强了渲染图像的真实感,并提供了视觉暗示(cues)来帮助观察者理解空间中的位置关系。同时,这些效果模拟起来也十分复杂,可能需要进行预计算或者渲染多个pass来计算一些必须的中间信息。
有一种思考光照问题的方法,即通过光子的传播路径来理解光照。在局部光照模型中,光子从光源出发,传播到表面上(忽略中间的物体),然后到达眼睛。阴影算法考虑了这些中间物体的直接遮挡效果。环境贴图可以捕捉从远处光源到达物体表面的光线,然后将其应用到局部的光泽物体上,这些物体会以镜面反射的方式,将这些光线反射到眼睛中。irradiance贴图还可以捕捉到光源对遥远物体的影响,并在半球范围的方向上进行积分,被这些物体所反射的光线会进行加权求和,从而计算出一个表面的光照效果,最终被眼睛所看到。
图11.2:图中展示了一些路径及其到达眼睛时的等效符号。注意,图中展示了两条从网球开始的连续路径,分别是LSDE和LSDSSE。
以一种更加正式的方式来思考光线传输路径的不同类型和不同组合,有助于理解现有的各种算法。Heckbert [693]提出了一个符号方案,它用于描述由某种技术所模拟的光线路径。光子从光源( L L L)到眼睛( E E E)的每次相互作用,都可以标记为漫反射( D D D)或者镜面反射( S S S),还可以通过添加其他表面类型来进一步分类,例如“有光泽的(glossy)”,它代表了有光泽,但是又不像镜子的表面,如图11.2所示。可以使用正则表达式来简单地概括这些算法,从而展示它们所模拟的交互类型。表11.1对基本符号进行了总结。
表11.1:正则表达式符号。
从光源出发的光子可以通过各种路径最终到眼睛。最简单的路径是 L E LE LE,光源被眼睛直接看到。一个基本的z-buffer是 L ( D ∣ S ) E L(D |S)E L(D∣S)E,或者写成其等价形式 L D E ∣ L S E LDE|LSE LDE∣LSE。光子离开光源,到达一个漫反射表面或者一个镜面,然后再到达眼睛。请注意,在一个基础的渲染系统中,点光源没有对应的物理表示,它不会被眼睛直接观察到。对于一个具有几何形状的光源而言,将会产生这样一个路径 L ( D ∣ S ) ? E L(D|S)?E L(D∣S)?E,除了照射到表面之外,从光源发出的光线也可以直接进入眼睛。
如果将环境映射添加到渲染器中,那么这个表达式就不再那么简单了。虽然Heckbert的表示法是从光源出发最终到达眼睛,但是对于渲染而言,从相反方向来构建表达式通常要更加容易。眼睛将首先看到一个镜面或者一个漫反射表面,即 ( S ∣ D ) E (S|D)E (S∣D)E,如果这个表面是一个镜面,那么它也可以选择反射到一个环境贴图中的镜面,或者是一个漫反射表面上。因此,存在一条额外的可能路径: ( ( S ∣ D ) ? S ∣ D ) E ((S|D)?S|D)E ((S∣D)?S∣D)E。同时再加上眼睛直接看到光源的路径,那么这个表达式最终会变为: L ( ( S ∣ D ) ? S ∣ D ) ? E L((S|D)?S|D)?E L((S∣D)?S∣D)?E。
可以将这个表达式展开: L E ∣ L S E ∣ L D E ∣ L S S E ∣ L D S E LE|LSE|LDE|LSSE|LDSE LE∣LSE∣LDE∣LSSE∣LDSE,它代表了所有可能存在的路径,或者简写为: L ( D ∣ S ) S ? E L(D |S) S?E L(D∣S)S?E。每一种表示方法在理解关系和限制方面都有各自的优势。这种符号表示法的部分用途是表达算法的效果,并能够以此为基础进行构建,例如: L ( S ∣ D ) L(S |D) L(S∣D)是生成环境贴图时所编码的内容,而 S E SE SE则代表了随后访问该贴图的过程。
渲染方程本身也可以用简单的表达式 L ( D ∣ S ) ∗ E L(D|S) * E L(D∣S)∗E来进行概括,即来自光源的光子在到达眼睛之前,可以与0到几乎无限数量的漫反射表面或者镜面发生相互作用。
对于全局光照的研究,主要集中在计算光线在这些路径上传播的方法。当将其应用于实时渲染时,我们通常愿意牺牲一些质量或者正确性,来换取更快的计算速度。最常见的两种策略就是简化和预计算。例如:我们可以假设所有反射到眼睛中的光线都是漫反射的,这种简化在某些环境和场景中表现很好。我们还可以离线环境中,对一些物体之间效果的相关信息进行预计算,例如生成记录表面光照水平的纹理,然后在运行过程中,根据这些存储的信息进行一些基本计算,从而获得全局光照效果。本章节将展示如何使用这些策略,来实时实现各种全局光照效果。
通用全局光照
我们在前面几章中,着重介绍了求解反射方程的各种方法。我们假设入射radiance L i L_i Li具有一定的分布,并分析了它是如何影响着色计算的。而在本章节中,我们将介绍用于求解完整渲染方程的算法。二者之间的区别在于,前者忽略了radiance的来源,它假设是直接给出的;而后者则明确地说明了这一点:到达某一点的radiance是从其他点发射或者反射而来的。
图11.3:路径追踪可以生成照片级逼真的图像,但是其计算成本较高。上面图像中的每个像素都使用了超过2000条路径(2000spp),每个路径长达64段(深度,即反弹次数)。它花费了两个多小时来进行渲染,但是仍然会表现出一些轻微的噪声。
能够求解完整渲染方程的算法,可以生成令人惊叹的、照片级逼真的图像,如图11.3所示。然而对于实时应用来说,这些方法的计算成本都太高了,那么为什么我们还要讨论它们呢?第一个原因是:在静态或者部分静态的场景中,这样的算法可以在预处理阶段执行,并将计算结果存储下来,以供稍后在实时渲染期间使用。这在游戏中是一种十分常见的方法,稍后我们将对这类系统的不同方面进行讨论。
第二个原因是:全局光照算法都建立在严格的理论基础上,它们是直接从渲染方程中推导出来的,它们所做的任何近似都是经过仔细分析的。在设计实时解决方案的时候,可以且应该应用类似的推理思路;即使我们走了某些捷径,使用了一些技巧,但是我们也应当知道这么做的后果是什么,什么才应该是正确的方法。随着图形硬件变得越发强大,我们将能够做出更少的妥协和近似,并且能够创建出更加接近正确物理结果的实时渲染图像。
求解渲染方程的两种常用方法是有限元法(finite element)和蒙特卡罗法(Monte Carlo)。其中辐射度算法(radiosity)基于了第一种方法,而不同形式的光线追踪算法(ray tracing)则使用了第二种方法。在这两种不同思路的算法中,光线追踪要更加流行。这主要是因为它可以在同一个算法框架内,对一般的光线传输效果进行有效处理,包括体积散射等效果。而且光线追踪算法也更加容易扩展和并行化。
我们将简要介绍这两种方法,有兴趣的读者还应该参考其他优秀的书籍,它们涵盖了在非实时情况下求解渲染方程的细节。
辐射度
辐射度算法(Radiosity)是第一种用于模拟漫反射表面之间光线反弹的计算机图形技术,其名字来源于该算法所计算的物理量。在经典的算法形式中,辐射度算法可以计算相互反射以及面光源所产生软阴影。辐射度算法的基本思想相对简单,并且已经有了完整的书籍对这个算法进行介绍。光线会在环境中发生弹射,当我们打开一盏灯时,房间内的照明会很快达到平衡,在这种稳定状态下,每个表面都可以被看作是一个光源。基本的辐射度算法作出了一种简化的假设,即所有场景中的间接光都来自于漫反射表面。对于具有抛光大理石地板或者墙上有巨大镜面的场景而言,这个假设是不成立的,但是对于现实中的许多建筑而言,这是一个相对合理的近似。辐射度算法可以对无限数量的有效漫反射进行追踪。如果使用本章节开头所介绍的符号表示法,那么可以将它的光线传输路径写为是 L D ∗ E LD*E LD∗E。
辐射度算法假设每个物体表面都由一定数量的面片(patch)组成。对于每个较小的区域(面片),辐射度算法都会计算一个平均辐射度值(radiosity value),因此这些面片的尺寸需要足够小,才能够捕捉所有的照明细节(例如阴影边缘)。这些面片不需要和底层表面的三角形一一匹配,甚至面片的大小尺寸也可以不一样。
B i = B i e + ρ s s ∑ j F i j B j (11.4) B_{i}=B_{i}^{e}+\rho_{\mathrm{ss}} \sum_{j} F_{i j} B_{j} \tag{11.4} Bi=Bie+ρssj∑FijBj(11.4)
其中 B i B_{i} Bi代表了面片 i i i的辐射度; B i e B_{i}^{e} Bie为面片 i i i的辐射出度(radiant exitance),即面片 i i i所发出的辐射度; ρ s s \rho_{\mathrm{ss}} ρss是次表面反照率。只有光源的辐射出度才不为0。 F i j F_{ij} Fij是面片 i i i和面片 j j j之间的形状因子(form factor),这个形状因子的定义为:
F i j = 1 A i ∫ A i ∫ A j V ( i , j ) cos θ i cos θ j π d i j 2 d a i d a j (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} Fij=Ai1∫Ai∫AjV(i,j)πdij2cosθicosθjdaidaj(11.5)
其中 A i A_i Ai是面片 i i i的面积; V ( i , j ) V(\mathbf{i}, \mathbf{j}) V(i,j)是点 i i i与点 j j j之间的可见性函数,如果它们之间没有物体遮挡光线,则该项为1,否则为0。角度值 θ i \theta_{i} θi和 θ j \theta_{j} θj分别是两个面片的法线,与点 i i i和点 j j j的之间连线的夹角。最后, d i j d_{ij} dij是点 i i i和点 j j j的之间距离。如图11.4所示。
图11.4:两个表面点之间的形状因子。
形状因子是一个纯粹的几何项,它描述了离开面片 i i i的均匀漫反射辐射能量,有多少能够入射到面片 j j j上。两个面片的面积、距离、相对朝向、以及它们之间存在的任何表面,都会对它们的形状因子产生影响。想象现在有一个面片,假设它代表了一个计算机显示器;房间里的其他每个面片,都会直接接收到这个显示器所发出的部分光线。位于显示器背面或者看不见显示器的表面,它们无法接收到显示器发射出来的光线,对于这些表面而言,这个比例因子为零。这些比例因子的和为1。辐射度算法的一个重要部分,就是准确确定场景中面片之间的形状因子。
在计算出形状因子之后,所有面片的方程(方程11.4)会被组合为一个的线性系统(一个由线性方程组成的数学模型)。然后对这个线性系统进行求解,从而得到每个面片的辐射度值。随着面片数量的不断增加,会导致计算复杂度变得很高,化简(reduce)这样一个矩阵的成本是相当大的,因此求解这个线性系统的成本也是相当大的。
由于这个算法的扩展性很差,并且存在一些其他的限制,因此经典的辐射度算法很少用于作为光照解决方案。然而,在现代实时全局光照系统中,预先计算形状因子,并在运行过程中使用它们来执行某种形式的光线传播,这个思想仍然很流行。
光线追踪
光线投射(ray casting)是指从某个表面位置上发射一根光线,从而确定在特定方向上存在哪些物体的过程。光线追踪(ray tracing)使用光线来确定不同场景元素之间的光线传输。在其最基本的形式中,光线会从相机所在的位置出发,穿过像素网格进入到场景中。对于每条光线,都找到第一个与光线相交的物体。然后,通过从交点向各个光源发射光线,并查找交点与光源之间是否存在遮挡物体,来检查这个交点是否位于阴影中。不透明的物体会遮挡光线,透明的物体会减弱光线。还可以在交点处发射其他光线,如果表面具有光泽,则可以在反射方向上生成光线。这条光线会获取第一个相交物体的颜色,然后再对相交点进行阴影测试。也可以在透明固体的折射方向上生成光线,然后再进行递归计算。光线追踪的基本机制非常简单,以致于最基础的光线追踪渲染器的代码,甚至可以写在一张名片的背面。
经典的光线追踪算法只能提供有限的效果:尖锐的反射和折射,以及硬阴影。然而,光线追踪的基本思想可以用于求解完整的渲染方程。Kajiya认识到,可以利用光线的发射机制以及评估它们所携带的光线能量,从而计算方程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)。
环境光遮蔽
上一小节中所介绍的通用全局光照算法,它们的计算成本都很高。虽然它们可以产生各种复杂的效果,但是生成一幅图像往往需要好几个小时。我们将首先介绍一些最简单的,但是在视觉上很有说服力的方法,并在本章节逐步探索实时替代方案,逐步构建更加复杂的效果。
&esmp; 一种基本的全局光照效果是环境光遮蔽(ambient occlusion,AO)。这项技术是在21世纪初,由工业光魔的Landis 所开发的,当时是用于提高电影《珍珠港》中,由计算机生成的飞机的环境光照质量。尽管这种效应的物理基础进行了相当程度的简化,但是最终的结果看起来却令人惊讶地可信。当光照缺乏方向变化,无法展现物体细节时,这种廉价方法可以提供对于物体形状的视觉暗示。
环境光遮蔽理论
环境光遮蔽的理论背景可以直接从反射方程中推导出来。这里为了简单起见,我们将首先关注Lambertian表面,该表面的出射radiance L o L_o Lo与表面irradiance E E E成正比。irradiance是入射radiance的余弦加权积分,一般来说,它取决于表面位置 p \mathbf{p} p和表面法线 n \mathbf{n} n。同样,为了简单起见,我们将假设来自所有方向 l \mathbf{l} l上的入射radiance都是恒定的,即 L i ( l ) = L A L_{i}(\mathbf{l})=L_{A} Li(l)=LA。基于上述假设,此时计算irradiance的方程如下所示:
E ( p , n ) = ∫ l ∈ Ω L A ( n ⋅ l ) + d l = π L A (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} E(p,n)=∫l∈ΩLA(n⋅l)+dl=πLA(11.6)
在这里,我们将对半球 Ω Ω Ω的所有可能入射方向进行积分。在恒定均匀光照的假设下,irradiance(以及由此产生的出射radiance)与表面位置和表面法线无关,并且在整个物体上都是恒定的。这会生成一个平坦均匀的外观。
方程11.6并没有考虑任何的可见性。着色点半球范围内的某些方向,可能会被自身物体的其他部分或者是场景中的其他物体所遮挡。在这些方向上将会具有不同的入射radiance,而不是恒定的 L A L_A LA。为了简单起见,我们假设来自这些遮挡方向上的入射radiance为零。虽然这个假设忽略了场景中可能会被其他物体反弹,并最终从这些遮挡方向到达点 p \mathbf{p} p的光线,但是它极大地简化了推理过程。基于上述假设,我们可以得到以下方程,它由Cook和Torrance首次提出:
E ( p , n ) = L A ∫ l ∈ Ω v ( p , l ) ( n ⋅ l ) + d l (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} E(p,n)=LA∫l∈Ωv(p,l)(n⋅l)+dl(11.7)
其中 v ( p , l ) v(\mathbf{p}, \mathbf{l}) v(p,l)是一个可见性函数,如果从点 p \mathbf{p} p向方向 l \mathbf{l} l投射的光线会被物体遮挡,则该函数值为0,反之为1。
将可见性函数进行余弦加权积分,然后再进行归一化,最终的结果被称为环境遮挡系数:
k A ( p ) = 1 π ∫ l ∈ Ω v ( p , l ) ( n ⋅ l ) + d l . (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} kA(p)=π1∫l∈Ωv(p,l)(n⋅l)+dl.(11.8)
这个系数代表了未被遮挡的半球的余弦加权百分比,它的范围位于 [ 0 , 1 ] [0,1] [0,1]内,对于完全被遮挡的着色点,它的值为0;对于没有任何遮挡的着色点,它的值为1。值得注意的是,凸面(convex)物体,例如球体或者立方体,不会对自身造成遮挡。因此当场景中不存在其他物体时,凸面物体的环境遮挡值将为1。如果物体表面存在凹陷区域,则这些区域的遮挡值将小于1。
在定义 k A k_A kA之后,考虑遮挡情况的环境irradiance方程为:
E ( p , n ) = k A ( p ) π L A (11.9) E(\mathbf{p}, \mathbf{n})=k_{A}(\mathbf{p}) \pi L_{A} \tag{11.9} E(p,n)=kA(p)πLA(11.9)
注意,在方程11.9中,irradiance会随着表面位置的变化而变化,因为 k A k_A kA确实是由表面位置所决定的,这样所得到的结果会更加真实,如图11.7所示。由于尖锐折痕处的 k A k_A kA值较低,因此这里的表面位置会显得较暗。
图11.7:仅使用恒定环境光照(左)和使用环境光遮蔽(右)渲染的物体。即使光照是恒定均匀的,环境光遮蔽也可以表现出物体的细节
比较图11.8中的表面位置 p 0 \mathbf{p}_{0} p0和 p 1 \mathbf{p}_{1} p1,可以发现表面朝向也会对 k A k_A kA有影响,因为可见性函数 v ( p , l ) v(\mathbf{p}, \mathbf{l}) v(p,l)在积分的时候会被余弦因子加权。比较图11.8左侧的表面位置 p 1 \mathbf{p}_{1} p1和 p 2 \mathbf{p}_{2} p2,虽然二者具有一个大小相同的未遮挡立体角,但是表面位置 p 1 \mathbf{p}_{1} p1的大部分未遮挡区域都位于其表面法线附近,该位置上的余弦因子相对较大,从箭头的亮度就可以看出。相比之下,表面位置 p 2 \mathbf{p}_{2} p2的大部分未遮挡区域都位于其表面法线的一侧,因此该位置的余弦因子相对较小,也就是说,在 p 2 \mathbf{p}_{2} p2处的 k A k_A kA较低。从这里开始,为简单起见,我们将不再显式说明遮挡系数对表面位置 p \mathbf{p} p的依赖。
图11.8:环境光照下的物体,图中展示了三个点,分别是 p 0 \mathbf{p}_{0} p0、 p 1 \mathbf{p}_{1} p1和 p 2 \mathbf{p}_{2} p2。在左侧,以相交点(黑色圆点)为端点的射线代表了被遮挡的方向;以箭头为端点的射线代表了未被遮挡的方向,并根据余弦因子的大小进行着色,因此更加靠近表面法线方向的箭头颜色会较浅。在右侧,蓝色箭头代表了平均的未被遮挡方向,或者叫做环境法线(bent normal)。
除了 k A k_A kA,Landis 还计算了一个平均的未遮挡方向,它称为环境法线(bent normal),这个方向向量是未遮挡方向的余弦加权平均值:
n b e n t = ∫ l ∈ Ω l v ( l ) ( n ⋅ l ) + d l ∥ ∫ l ∈ Ω l v ( l ) ( n ⋅ l ) + d l ∥ (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} nbent= ∫l∈Ωlv(l)(n⋅l)+dl ∫l∈Ωlv(l)(n⋅l)+dl(11.10)
其中符号 ∥ x ∥ \|\mathbf{x}\| ∥x∥代表了向量 x \mathbf{x} x的长度。积分的结果再除以它自身的长度,可以得到归一化的结果,如图11.8右侧所示。这样产生的向量可以在着色期间代替几何法线,从而提供更加准确的结果,同时不需要额外的性能开销。
可见性和 obscurance
用于计算环境遮挡因子 k A k_A kA(方程11.8)的可见性函数 v ( l ) v(\mathbf{l}) v(l)需要仔细定义。例如:对于一个物体,比如人物或者车辆,定义这个函数 v ( l ) v(\mathbf{l}) v(l)是很简单的,我们只需要从表面位置向方向 l \mathbf{l} l投射光线,然后检查光线是否会与同一物体的任何其他部分相交即可。然而,这种方法并没有考虑到附近其他物体的遮挡情况。通常,为了进行光照,可以假设物体被放置在一个平面上,通过将该平面加入到可见性计算中,可以实现更加真实的遮挡效果。这样做的另一个好处是,物体对地面的遮挡可以模拟接触阴影的效果。
不幸的是,这种可见性函数方法对于封闭的几何体是不起作用的。想象现在有一个场景,它是一个包含各种物品的封闭房间。在这种情况下,所有表面的 k A k_A kA值都会为0,因为来自表面的所有射线都会击中某个物体(墙壁或者物体)。对于这类场景而言,经验方法会更加合适,它会试图重现环境遮挡的外观,但是不一定会对物理可见性进行模拟。其中的一些方法的灵感来自于Miller的可访问性着色(accessibility shading),该方法对在表面角落和缝隙处如何捕获污垢或者腐蚀进行了建模。
Zhukov等人引入了obscurance的思想,它通过使用距离映射函数 ρ ( l ) \rho(\mathbf{l}) ρ(l)来代替可见性函数 v ( l ) v(\mathbf{l}) v(l),从而对环境光遮蔽的计算进行了修改:
k A = 1 π ∫ l ∈ Ω ρ ( l ) ( n ⋅ l ) + d l (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} kA=π1∫l∈Ωρ(l)(n⋅l)+dl(11.11)
可见性函数 v ( l ) v(\mathbf{l}) v(l)只有两个有效值,其中1代表没有相交,0表示有相交,而距离映射函数 ρ ( l ) \rho(\mathbf{l}) ρ(l)则是一个连续函数,其返回值取决于射线与表面相交之前所传播的距离。当相交距离为0时,函数 ρ ( l ) \rho(\mathbf{l}) ρ(l)的值为0;当相交距离大于设定的最大距离 d m a x d_{max} dmax时,或者根本没有相交时,函数 ρ ( l ) \rho(\mathbf{l}) ρ(l)的值为1。对于相交距离大于 d m a x d_{max} dmax的交点,实际上不需要进行任何测试,这样可以大大加快 k A k_A kA的计算速度。图11.9展示了环境光occlusion和环境光obscurance之间的区别。请注意,使用环境光occlusion进行渲染的图像要暗得多,这是因为即使在很远的距离也会对相交情况进行检测,这样会减小 k A k_A kA的值。
图11.9:环境光occlusion和环境光obscurance之间的区别。左侧图像的遮挡是使用无限长的射线进行计算的。右侧图像则使用了有限长度的射线。
尽管人们试图从物理角度为其辩护,但实际上obscurance在物理上是不正确的。然而,obscurance通常可以给出符合观众期望的合理结果。obscurance方法的一个缺点在于,这个最大相交距离 d m a x d_{max} dmax的值需要进行手动调整,才能达到令人满意的效果。这种类型的妥协会在计算机图形学中经常出现,一些技术可能并没有直接的物理基础,但是“在感知上却令人信服”。计算机图形学的目标通常是渲染一张可信的图像,因此这些技术当然是可以使用的。也就是说,基于物理理论的方法具有这样一些优点,它们可以自动进行工作,并且可以通过推理现实世界中的工作原理,来对其进一步改进。
考虑相互反射
尽管环境光遮蔽所产生的结果在视觉上是令人信服的,但是与完整全局光照模拟产生的结果相比,环境光遮蔽的结果要更暗一些,如图11.10中图像的对比。
图11.10:不具有相互反射和具有相互反射的环境光遮蔽之间的区别。左侧图像只使用了可见性信息,右侧图像使用了一次反弹的间接照明。
环境光遮蔽与完整全局光照之间的一个重要区别是相互反射(interreflection)。方程11.8假设被遮挡方向上的radiance为零,但是实际上相互反射会为这些方向引入一个非零的radiance。如图11.10所示,与右侧模型相比,左侧模型的折痕处和凹陷处会更暗。这种差异可以通过适当增加 k A k_A kA的值来解决。使用obscurance距离映射函数来代替可见性函数也可以缓解这个问题,因为obscurance函数的值通常会大于零。
以一种更加精确的方式来追踪相互反射是很昂贵的,因为它需要求解一个递归问题。想要给一个点进行着色,必须首先对其他点进行着色,以此类推。虽然计算 k A k_A kA的值相比于执行一个完整的全局光照计算而言要便宜得多,但是我们还是希望能够以某种形式来包含这部分丢失的光线,从而避免过度暗化。Stewart和Langer提出了一种廉价、但是却惊人准确的方法来近似相互反射。它基于在漫反射光照下对Lambertian场景的观察,即从一个给定位置能够看见的表面,往往会具有相似的radiance。我们假设遮挡方向的radiance L i L_i Li,等于当前着色点的出射radiance L o L_o Lo,从而打破了递归,可以得到这样一个解析表达式:
E = π k A 1 − ρ s s ( 1 − k A ) L i (11.12) E=\frac{\pi k_{A}}{1-\rho_{\mathrm{ss}}\left(1-k_{A}\right)} L_{i} \tag{11.12} E=1−ρss(1−kA)πkALi(11.12)
其中 ρ s s \rho_{\mathrm{ss}} ρss是次表面反照率,或者叫做漫反射率。方程11.12相当于使用一个新的环境遮挡因子 k A ′ k_{A}^{\prime} kA′来代替之前的 k A k_{A} kA:
k A ′ = k A 1 − ρ s s ( 1 − k A ) (11.13) k_{A}^{\prime}=\frac{k_{A}}{1-\rho_{\mathrm{ss}}\left(1-k_{A}\right)} \tag{11.13} kA′=1−ρss(1−kA)kA(11.13)
方程11.13倾向于让环境遮挡因子变得更大(更亮),从而使得它在视觉上更加接近一个完整全局光照所产生的结果,它在一定程度上模拟了相互反射效应。这种效应高度依赖于 ρ s s \rho_{\mathrm{ss}} ρss的值,其隐含的假设是:在着色点附近的表面颜色是相同的,这样可以产生有点像类似颜色渗透(color bleeding)的效果。Hoffman和Mitchell使用了这种方法,从而可以实用天光来照亮地形。
Jimenez等人提出了一种不同的解决方案。他们对许多场景执行了完整的离线路径追踪,每个场景都使用均匀的、白色的、无限远的环境贴图来进行照亮,从而获得考虑相互反射的适当遮挡值。在此基础上,他们拟合了一个三次多项式,来对环境遮挡因子 k A k_A kA和次表面反照率 ρ s s \rho_{\mathrm{ss}} ρss映射到遮挡值 k A ′ k_{A}^{\prime} kA′的函数 f f f进行近似,这个新的遮挡值会被相互反射的光线照亮。他们的方法同样假设反照率是局部恒定的,并且可以根据给定点的反照率,推导出入射反弹光的颜色。
预计算环境光遮蔽
环境遮挡因子的计算可能会很耗时,通常都是在渲染之前离线计算的。预计算任何与光照相关的信息(包括环境光遮蔽),这个过程通常被称为烘焙(baking)。
预计算环境光遮蔽最常见的方法就是蒙特卡罗方法。发射光线并检查光线与场景的交点,然后对方程11.8进行数值计算,例如:我们在法线 n \mathbf{n} n的半球方向上均匀随机选择 N N N个方向 l \mathbf{l} l,然后沿着这些方向发射光线并进行追踪。基于光线的相交结果,我们对可见性函数 v v v进行计算,这种计算环境光遮蔽的方法,可以表达成如下方程:
k A = 1 N ∑ i N v ( l i ) ( n ⋅ l i ) + . (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} kA=N1i∑Nv(li)(n⋅li)+.(11.14)
在计算环境光obscurance时,可以将投射的光线限制在一个最大距离内,通过最大距离内的相交距离来计算 v v v的值。
环境光occlusion或者环境光obscurance的环境遮挡因子,其计算过程都包含一个余弦加权因子。虽然我们可以像方程11.14那样将其直接纳入计算,但更加有效的方法是通过重要性采样。现在我们对光线发射的分布进行调整,将其修改为按余弦加权进行发射光线,而不是先在半球范围内均匀投射光线,然后再对结果进行余弦加权。换句话说,光线会更有可能被投射到接近表面法线的方向上,因为来自这些方向的结果具有更大的贡献值,它们是更加重要的样本。这种抽样方案被称为Malley方法。
环境光遮蔽的预计算可以在CPU或者GPU上进行。在这两种计算环境下,都有一些针对复杂几何图形的光线投射加速库。其中最受欢迎的两个分别是:用于CPU的Embree 和用于GPU的OptiX 。在过去,来自GPU管线的生成结果,例如深度图或者遮挡查询等,也会被用于计算环境光遮蔽,但是随着更加通用的光线投射解决方案在GPU上的日益普及,它们在今天不太常用了。大多数商业建模和渲染软件都会提供预计算环境光遮蔽的选项。
遮挡数据对于物体上的每个顶点都是唯一的。它们通常会存储在纹理、体积或者网格顶点中。而无论存储的信号类型如何,不同存储方法都具有类似的特点和问题。同样的方法还可以用于存储环境光遮蔽、定向遮蔽或者预计算光照等。
预计算数据也可以用来模拟物体之间的环境光遮蔽效果。Kontkanen和Laine将物体对其周围的环境光遮蔽效应存储在一张立方体贴图中,并将其称为环境光遮蔽场(ambient occlusion field)。他们使用了一个二次多项式的倒数,来模拟环境光遮蔽随物体之间距离的变化情况。这个多项式的系数存储在一张立方体贴图中,以模拟遮挡的方向性变化。在运行过程中,利用遮挡物的距离和相对位置来获取合适的系数,从而对遮挡值进行重建。
Malmer等人将环境遮挡因子和环境法线(可选)存储一个三维网格中,从而对结果进行了改进,他们将这个三维网格称为环境光遮蔽体(ambient occlusion volume)。这种方法的计算成本较低,因为可以从纹理中直接读取出环境遮挡因子,不用实时计算。与Kontkanen和Laine的方法相比,这种方法需要存储的标量值要更少一些,两种方法的纹理分辨率都比较低,总体的存储需求是类似的。Hill 和Reed描述了Malmer等人的方法在商业游戏引擎中的实现,他们对算法的各个实现方面以及一些有用的优化方法进行了讨论。这两种方法都适用于刚体物体,而且可以扩展到具有少量运动部件的铰接物体上,其中每个运动部件都会被视为一个单独的物体。
无论我们选择哪种方法来存储环境光遮蔽值,我们都需要明确,我们正在处理的是一个连续信号。当我们从空间中的特定点发射光线的时候,我们进行的是采样(sample);当我们在着色之前对这些数值插值的时候,我们进行的是重建(reconstruct)。信号处理领域中的所有相关工具都可以用来提高这个采样-重建过程的质量。Kavan等人提出了一种方法,他们将其称之为最小二乘烘焙(least-squares baking)。在这个方法中,遮挡信号会在网格上进行均匀采样,然后推导出顶点对应的值,从而可以在最小二乘法中,将插值和采样顶点之间的总差异最小化。他们还专门讨论了在顶点存储数据的方法,同样的推理方法也可以用于导出存储在纹理或者体积中的值。
《命运》是一款广受好评的游戏,它使用了预计算的环境光遮蔽作为基础的间接光照解决方案(如图11.11所示)。这款游戏是在两代主机硬件之间的过渡时期发行的,因此它需要一个解决方案,来平衡新平台上预期的高质量画面与老平台上性能和内存使用限制。这个游戏的其中一个特点是,一天中的光照效果会随着时间动态变化,因此任何预计算的解决方案都必须正确地考虑到这一点。开发者选择使用环境光遮蔽,是因为它在有着较低开销的同时,可以提供可信的外观表现。同时,由于环境光遮蔽将可见性计算与光照渲染过程相解耦,因此可以在一天中的任何时间,使用相同的预计算数据。Sloan等人完整介绍了这个系统,包括基于GPU的烘焙管线。
图11.11:《命运》在间接光照计算中使用基于预计算的环境光遮蔽。该方案同时运行在了两种不同的硬件世代,兼顾了高质量和高性能。
育碧的《刺客信条》和《孤岛惊魂》系列,也使用了一种预计算的环境光遮蔽,来增强他们的间接光照解决方案。他们以自上而下的视角来渲染世界,并对产生的深度图进行处理,从而计算大范围的遮挡信息。根据相邻深度样本的分布情况,采用了多种启发式算法来对遮挡值进行估计。通过将世界空间位置投影到纹理空间中,所产生的世界空间AO贴图可以应用于所有物体。他们将这种方法称为世界AO(World AO)。Swoboda 也描述了类似的方法。
环境光遮蔽的动态计算
对于静态场景,可以预先计算环境遮挡因子 k A k_A kA和环境法线 n bent \mathbf{n}_{\text {bent }} nbent 。但是,对于存在移动或者形状改变物体的场景,必须要通过实时计算这些参数才能获得更好的结果。执行这个操作的方法按照空间可以划分为两类:在物体空间中执行的方法;在屏幕空间中执行的方法。
计算环境光遮蔽的离线方法,通常会从每个表面点向场景中投射大量光线(数十到数百条),并对交点进行检查。这是一个成本很高的操作,而实时渲染中则重点关注如何进行近似,或者如何避免大部分的计算。
Bunnell 通过将表面建模为放置在网格顶点处的圆盘元素,从而计算环境遮挡因子 k A k_A kA和环境法线 n bent \mathbf{n}_{\text {bent }} nbent 。这里选择圆盘的原因是,圆盘之间的遮挡情况可以通过解析计算获得,不需要单独投射光线。简单地将一个圆盘与所有其他圆盘的遮挡因子加起来,会产生双重阴影从而导致表面过暗。也就是说,如果一个圆盘位于另一个圆盘的后面,那么这两个圆盘都将被视为遮挡表面,但是实际上只有最近的圆盘才应当是。Bunnell使用了一种巧妙的两pass方法来避免这个问题:第一个pass正常计算环境光遮蔽效果,包括错误的双重阴影在内。在第二个pass中,会根据第一个pass中的遮挡情况,来减少每个圆盘的贡献值。实际上这只是一个近似值,但在实践中,它能够产生令人信服的结果。
计算每对元素之间的遮挡情况具有 O ( n 2 ) O(n^2) O(n2)的复杂度,除非场景构成十分简单,否则这个复杂度对于实时渲染而言太高了。对于远距离的表面,可以使用一些简化的表示,从而降低部分计算开销。Bunnell构建了一个分层的元素树,其中每个节点都是一个圆盘,它代表了其子树圆盘的聚合。在进行圆盘之间的遮挡计算时,对于较远的表面会使用较高层级的的节点。这可以将时间复杂度降低到 O ( n log n ) O(n \log n) O(nlogn),这是一个更加合理的复杂度。Bunnell的技术很高效,并且能够产生高质量的结果,该技术被应用在了加勒比海盗电影(Pirates of the Caribbean)的最终渲染中。
Hoberock对Bunnell的算法进行了几项修改,使用更高的计算成本进一步提高了质量。他还提出了一种距离衰减因子,其结果与Zhukov等人所提出的obscurance因子相类似。
Evans 描述了一种基于符号距离场(signed distance field,SDF)的动态环境光遮蔽近似方法。在这种表示方法中,物体会被嵌入到一个三维网格中。网格中的每个位置都会存储到最近物体表面的距离。对于在物体内部的点,这个值为负;对于在物体外部的点,这个值为正。Evans在体积纹理中创建并存储场景的SDF。为了估计物体上某个表面位置的遮挡情况,他使用了一种启发式方法,该方法会沿着表面法线进行点采样,这些点会距离表面越来越远。Quilez指出,当SDF以解析方式进行表示,而不是存储在三维纹理中的时候,也可以使用相同的方法进行处理。虽然这种方法是非物理的,但是生成的结果在视觉上令人满意。
Wright 进一步扩展了使用符号距离场来进行环境光遮蔽的方法。Wright并没有使用启发式方法来生成遮挡值,而是进行了锥形追踪(cone tracing)。这个圆锥的顶点位于着色点,并对编码在距离场中的场景表示进行相交测试。锥形追踪会沿轴执行一组步进操作,在每一次步进之后都会使用一个更大半径的球,来与SDF进行相交测试。如果此时距离最近的遮挡物距离(从SDF中采样的值)小于球体的半径,那么圆锥的这部分就会被遮挡(如图11.12所示)。如果仅仅追踪一个锥形区域,那么结果将是很不精确的,并且无法包含余弦项,出于这个原因,Wright追踪了一组覆盖整个半球的圆锥,从而来估计环境光遮蔽。为了提高视觉保真度(visual fidelity),他的解决方案不仅使用了场景的全局SDF,还使用局部的SDF,这个局部SDF用于代表单个物体或者在逻辑上相连接的物体集合。
图11.12:锥形跟踪通过在场景几何与半径不断增大的球体之间,进行一系列的相交测试来近似遮挡情况。测试球体与圆锥的侧面相接,距离顶点越远,球体的半径就越大。在每一次步进中,锥体的角度都会因为相交遮挡而减小,以考虑场景几何形状的遮挡情况。最终的遮挡因子为裁剪过后的圆锥立体角与原始圆锥立体角之比,这是一个估计值。
Crassin等人在场景的体素表示中描述了一种类似的方法。他们使用稀疏体素八叉树来存储场景的体素化信息。他们用于计算环境光遮蔽的算法,实际上是一种通用完整全局光照算法的特例。
Ren等人则将遮挡物近似为球体,如图11.13所示,并使用球谐函数来表示表面点被单个球体遮挡的可见性函数,这样一组球体聚合起来的可见性函数,就是单个球体可见性函数的乘积。但不幸的是,计算球谐函数的乘积是一个成本很高的操作。他们的核心思想是:对单个球谐可见性函数的对数进行求和,然后再对结果取指数。这样所产生的结果与可见性函数相乘的结果相同,但是球谐函数的求和操作,其计算成本明显要比乘法小。这篇论文表明,在正确的近似方法下,可以通过执行快速的对数运算和指数运算,从而获得整体加速效果。
这种方法计算出的不仅仅是环境遮挡因子,而是一个完整的球面可见性函数,它使用了球谐函数来进行表示。其中,球谐函数的第一个系数(0阶)可以作为环境遮挡因子 k A k_A kA,后面三个系数(1阶)可以用于计算环境法线 n bent \mathbf{n}_{\text {bent }} nbent 。更高阶的系数可以用于阴影环境贴图或者圆形光源。由于这种方法将几何体近似为包围球,因此无法对来自折痕或者其他小细节的遮挡情况进行建模。
图11.13:这种方法生成的环境光遮蔽效果是模糊的,无法显示遮挡细节。可以使用更简单的几何表示来计算AO,这样仍然可以实现合理的效果。上图将一个犰狳模型(左)近似为一组球体(右)。在这两个例子中,模型在背后墙上投下的遮挡阴影几乎一样。
Sloan等人在屏幕空间中,对Ren所描述的可见性函数进行了求和。对于每个遮挡物,他们都会考虑一组像素,这组像素距离着色点的距离,小于所规定的世界空间距离。这个操作可以通过渲染一个球体,并在着色器中执行距离测试或者使用模板测试来实现。对于所有受到影响的屏幕区域,会将一个适当的球谐函数值添加到一个离屏缓冲区中。在获得所有遮挡物的可见性之后,会对缓冲区中的值进行求幂运算,最终获得每个屏幕像素上的组合可见性函数。Hill 使用了相同的方法,但是他将球谐可见性函数限制到二阶系数。在这种假设下,球谐函数的乘积运算只涉及到少量的标量乘法,甚至可以通过GPU的固定功能混合硬件来完成。这使得我们可以在性能有限的主机硬件上使用这种方法。由于该方法只使用了低阶的球谐函数,因此无法生成具有清晰边界的硬阴影,只能生成无方向的遮挡。
屏幕空间方法
基于模型空间的方法,其开销与场景的复杂度成正比。然而,我们完全可以从屏幕空间中已有的数据出发,推导出一些有关遮挡的信息,例如深度和法线。这种基于屏幕空间的算法,具有恒定的开销,其复杂度与与场景的细节程度无关,只与渲染时所使用的画面分辨率有关。
图11.14:Crytek的环境光遮蔽方法,被应在了图中的三个表面点(黄色圆圈)上。这里为了清晰起见,使用了二维形式来展示该算法的流程,相机位于图像内容的正上方(未显示在图中)。在这个例子中,有10个样本分布在了围绕表面点的圆盘上(实际上它们是分布在一个球体内部)。未通过深度测试的样本点使用红色进行表示,即该样本所对应的深度,超过了z-buffer中对应位置的深度;通过的样本则使用绿色进行表示。环境遮挡因子 k A k_A kA的值是通过测试的样本数与总样本数的加权比值。为了简单起见,这里我们先忽略了可变的样本权重,假设所有的样本都具有相同的权重。对于左边的像素点,总共10个样本,其中有6个通过,因此 k A = 0.6 k_A=0.6 kA=0.6。对于中间的像素点,只有3个样本通过了测试。还有一个样本虽然在物体外部,但是没有通过深度测试,如图中红色箭头所示,最终计算出的 k A = 0.3 k_A=0.3 kA=0.3。对于右边的像素点,只有1个样本通过了测试,因此 k A = 0.1 k_A=0.1 kA=0.1。
Crytek开发了一种动态的屏幕空间环境光遮蔽(screen-space ambient occlusion,SSAO)算法,并用在了《孤岛危机》中。他们使用z-buffer作为唯一的输入,在一个全屏pass中计算来环境光遮蔽效果。每个像素都有一个环境遮挡因子 k A k_A kA,它会在该像素周围的球形范围内采样一组点,并将样本与z-buffer进行深度测试,然后来估计 k A k_A kA。 k A k_A kA的值与z-buffer中位于像素点深度前面的测试样本有关,通过的样本数量越少, k A k_A kA的值就越低,如图11.14所示。与obscurance因子相类似,这些样本的权重会随着到像素距离的增大而减小,即距离像素越远,该样本的权重就越小。需要注意的是,由于这些样本并没有被余弦因子 ( n ⋅ l ) + (\mathbf{n} \cdot \mathbf{l})^{+} (n⋅l)+加权,因此所产生的环境光遮蔽效果是不正确的。该方法会将球形范围内的所有样本都考虑在内,而不是只考虑表面上半球范围内的样本。这种简化意味着会对表面以下的样本进行计数,但是实际上我们是不应当对它们进行计数的。这样做会导致表面变暗(因为环境遮挡因子 k A k_A kA变大了),同时边缘会比周围环境更亮。尽管如此,最终产生结果在视觉上令人十分满意,如图11.15所示。
图11.15:左上角:展示了屏幕空间环境光遮蔽的效果。右上角:展示了没有环境光遮蔽的反照率(漫反射颜色)。左下角:将上述两者进行了合并。右下角:最终的渲染图像,添加了高光着色和阴影效果。
Shanmugam和Arikan同时提出了一种类似的方法。在他们的论文中,他们描述了两种方法,其中一种可以从附近的小细节中生成良好的环境光遮蔽效果;另一中可以从较大的物体中生成较为粗略的环境光遮蔽效果。将二者的结果结合起来,就可以生成最终的环境遮挡因子。其中,他们的精细尺度环境光遮蔽方法使用了一个全屏pass,在这个pass中,不仅访问了z-buffer,还访问了可见像素表面的法线缓冲。对于每个着色像素,会从z-buffer中对附近的像素进行采样,被采样的像素分布在球体内部,会根据其法线信息来计算着色像素的遮挡项。这种方法并没有将双重阴影考虑在内,因此结果会显得有点暗。他们的粗略遮挡方法,与Ren等人的物体空间方法相类似(我们在上文中讨论过),它同样将遮挡几何体近似为球体。然而不同的是,Shanmugam和Arikan的方法是在屏幕空间进行遮挡计算的,并使用了与屏幕对齐的广告牌,来覆盖每个遮挡球体的“效果区域”。与Ren等人的方法不同,这里的粗略遮挡方法也没有考虑双重阴影。
由于这两种方法极其简洁,因此很快引起了工业界和学术界的注意,并催生了大量的后续工作。许多方法,例如Filion等人在游戏《星际争霸II》中所使用的方法,以及McGuire等人所使用的可扩展环境光obscurance方法,都使用了这种特别启发式方法(ad hoc heuristics)来生成遮挡因子。这类方法具有良好的性能表现,并暴露出了一些参数,可以通过手动调整参数来达到预期的艺术效果。
其他的一些方法旨在提供更有原则和理论基础的遮挡计算方法。Loos和Sloan注意到,Crytek的方法可以被解释为蒙特卡洛积分。他们将计算出来的值称为体积obscurance,并将其定义为:
v A = ∫ x ∈ X ρ ( d ( x ) ) o ( x ) d x (11.15) v_{A}=\int_{\mathbf{x} \in X} \rho(d(\mathbf{x})) o(\mathbf{x}) d \mathbf{x} \tag{11.15} vA=∫x∈Xρ(d(x))o(x)dx(11.15)
其中 X X X是围绕该像素点的一个三维球形邻域; ρ ρ ρ是距离映射函数,与方程11.11所描述的相类似; d d d是距离函数; o ( x ) o(\mathbf{x}) o(x)是占用函数(occupancy function),如果 x \mathbf{x} x未被占用,则 o ( x ) o(\mathbf{x}) o(x)等于0,否则等于1。他们注意到, ρ ( d ) ρ(d) ρ(d)函数对于最终视觉质量的影响很小,因此可以使用常数函数。在这个假设下,体积obscurance是对占用函数在像素点邻域上的积分。Crytek的方法是在三维邻域内进行随机采样从而计算积分,而Loos和Sloan则通过对像素的屏幕空间邻域随机采样,在 x y xy xy维度上进行积分,对 z z z轴的积分过程则是解析的。如果该点的球面邻域中不包含任何几何图形,则积分值等于射线与球体 X X X相交的长度。如果该点的球面邻域中存在几何图形,则会使用深度缓冲来作为占用函数的近似值,并且仅会在每个线段的未占用部分上进行积分,如图11.16左侧所示。该方法最终生成的结果,其质量与Crytek的方法相当,但是使用的样本数量较少,因为在其中一个维度上( z z z轴)的积分是精确的。如果可以使用表面法线的话,还可以对这个方法进一步扩展,从而获得更好的结果。在这个考虑法线的版本中,线积分会被限制在由像素点法线所定义的平面上。
图11.16:体积obscurance(左)使用了线积分,来计算像素点周围的未占用体积的积分。体积环境光遮蔽(右)同样也使用了线积分,不同的是,它计算了与着色点相切球体的占用率,这对反射方程中的余弦项进行了模拟。在这两种情况下,积分的结果都是球体的未占用体积(绿色实线)与球体总体积(未占用体积与占用体积之和,其中占用体积使用红色虚线进行表示)的比值。对于这两幅图像,相机都是从上往下观察的,其中绿色点代表了从深度缓冲中读取的样本,黄色点代表了正在计算遮挡情况的样本。
Szirmay-Kalos等人提出了另一种使用法线信息的屏幕空间方法,它被称为体积环境光遮蔽(volumetric ambient occlusion)。方程11.6描述了在法线半球上进行的积分,这个积分还包含了余弦项。他们提出,这种类型的积分,可以将被积函数中的余弦项移除,并使用余弦分布来限制积分范围,从而对余弦因子进行近似。这样做可以将积分转换到一个球面上,而不是在一个半球上;这个球体的半径为半球的一半,并且会沿着法线移动一个球体半径的距离,最终这个球体会与半球内接,被半球完全包裹。其中未被占用部分的体积,其计算方法与Loos和Sloan的方法一样,都是通过在像素邻域上进行随机采样,并在 z z z轴上对占用函数进行解析积分,如图11.16右侧所示。
Bavoil等人提出了一种不同的方法,用于解决估计局部可见性的问题,他们从Max的视界映射(horizon mapping)中获得了灵感。他们的方法被称为基于视界的环境光遮蔽(horizon-based ambient occlusion,HBAO),它假设z-buffer中的数据表示了一个连续的高度场。通过确定视界角(horizon angle),可以对像素点的可见性进行估计,这里的视界角,指的是切面上方被邻域遮挡的最大角度。也就是说,对于某个点上的给定方向,我们会记录最高的可见物体所对应的角度。如果我们忽略积分中的余弦项,那么环境遮挡因子可以被计算为视界上未被遮挡部分的积分,或者是1减去视界下被遮挡部分的积分:
k A = 1 − 1 2 π ∫ ϕ = − π π ∫ α = 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} kA=1−2π1∫ϕ=−ππ∫α=t(ϕ)h(ϕ)W(ω)cos(θ)dθdϕ(11.16)
其中 h ( ϕ ) h(\phi) h(ϕ)是切平面的视界角; t ( ϕ ) t(\phi) t(ϕ)是切平面与观察向量的切角(tangent angle); W ( ω ) W(\omega) W(ω)是衰减函数,如图11.17所示。积分前面的 1 / 2 π {1}/{2 \pi} 1/2π是归一化系数,它将积分的结果归一化到 [ 0 , 1 ] [0,1] [0,1]之间。
图11.17:HBAO(左)通过找到切平面上方的视界角 h h h,并对视界角之间的未遮挡角度进行积分,从而计算环境遮挡因子。切平面和观察向量之间的角度记为 t t t。GTAO使用了相同的视界角度 h 1 h_1 h1和 h 2 h_2 h2,同时还使用了法线和观察向量之间的角度 γ \gamma γ,并将余弦项添加到计算中。在上述两幅图中,相机都是从上往下观察场景的,图中显示的是场景横截面,其中视界角是角度 ϕ \phi ϕ的函数, ϕ \phi ϕ是一个相对于观察方向的角度。图中绿色的点代表了从深度缓冲中读取的样本。黄色点代表了正在进行遮挡计算的样本。
对于定义视界的角度 ϕ \phi ϕ,我们利用角度的线性衰减,可以解析地计算内部的积分:
k A = 1 − 1 2 π ∫ ϕ = − π π ( 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} kA=1−2π1∫ϕ=−ππ(sin(h(ϕ))−sin(t(ϕ)))W(ϕ)dϕ(11.17)
这个剩余的积分,是通过对一些方向进行采样,来找到视界角度,从而进行数值计算的。
Jimenez等人也使用了这种基于视界的方法,他们称之为真实环境光遮蔽(ground-truth ambient occlusion ,GTAO)。他们的目标是实现ground-truth的结果,并能够与光线跟踪的结果相匹配,该方法所使用的唯一信息,就是由z-buffer构建的高度场。HBAO在计算遮挡的时候并不包括余弦项,并且它还增加了一个特殊的衰减(没有出现在方程11.8中),因此它的结果最多只能与光线追踪相接近,但是始终还是不一样的。GTAO引入了缺失的余弦因子,去除了这个特殊的衰减函数,并在绕观察向量的参考系中给出了遮挡积分,该方法的遮挡因子定义如下:
k A = 1 π ∫ 0 π ∫ h 1 ( ϕ ) h 2 ( ϕ ) 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} kA=π1∫0π∫h1(ϕ)h2(ϕ)cos(θ−γ)+∣sin(θ)∣dθdϕ(11.18)
其中 h 1 ( ϕ ) h_1(\phi) h1(ϕ)和 h 2 ( ϕ ) h_2(\phi) h2(ϕ)为给定 ϕ \phi ϕ的左右视界角; γ γ γ为表面法线与观察方向之间的夹角。这里积分的归一化项为 1 / π {1}/{\pi} 1/π,这与HBAO中的不同,因为GTAO包含了余弦项,这使得开放半球的积分结果为 π \pi π,如果方程中不包含余弦项,则开放半球的积分结果为 2 π 2\pi 2π。在给定高度场假设的情况下,方程11.18与与方程11.8完全匹配,如图11.17所示。这里的内部积分仍然可以进行解析求解,因此只需要对外部积分进行数值计算即可,这个积分过程与HBAO中的积分过程基本相同,都是对给定像素周围的多个方向上进行采样。
在这些基于视界的方法中,成本最高的操作就是沿着屏幕空间的线段对深度缓冲进行采样,从而确定视界角度。Timonen提出了一种方法,专门用于提高这一步的性能表现。他指出,用于估计给定方向上视界角度的样本,可以在屏幕空间中沿直线排列的像素之间进行大量重用。他将遮挡计算分为两步,首先,他会在整个z-buffer中执行线段追踪。在追踪的每一步中,他都会根据所规定的最大影响距离,在沿着线段移动的时候更新视界角度,并将这个信息写入一个缓冲区中。在视界映射(horizon mapping)中,每个屏幕空间方向上都会创建一个这样的缓冲区。这些缓冲区的大小不需要与原始的深度缓冲区相同,而是取决于线段之间的间距,以及沿着线段的步长,在选择这些参数的时候有一定的灵活性。不同的设置会对最终的质量产生影响。
第二步是根据存储在缓冲区中的视界角度信息来计算遮挡因子。Timonen使用HBAO(方程11.17)所定义的遮挡因子,但是也可以使用其他遮挡估计方法,例如GTAO中的遮挡因子(方程11.18)。
深度缓冲并不是一个完美的场景表示,因为在一个给定的方向上,只有最近的物体会被记录下来,我们实际上并不知道它背后发生了什么。有许多技术使用了启发式方法,来尝试推断可见物体的厚度信息,这些近似值在许多情况下都表现良好,人眼对于稍微不准确的结果是很宽容的。虽然有一些方法使用了多层深度来缓解这个问题,但是由于将其集成到渲染引擎中太过复杂,并且这类方法的运行时成本很高,因此它们从未流行过。
屏幕空间中的方法依赖于对z-buffer进行反复采样,从而在给定点周围构建一些简化的几何模型。实验表明,想要获得较高的视觉质量,可能需要多达几百个样本,这个级别的样本数量太多了,想要用于交互式渲染,每个像素最多只能采样10-20个样本,甚至更少。Jimenez等人报告提到,为了适应60 FPS的性能预算,他们只能在每个像素上使用1个样本!为了弥合理论和实践之间的差距,屏幕空间方法通常会采用某种形式的空间抖动。在最常见的形式中,每个屏幕像素都会使用略有不同的随机样本集合,然后进行旋转或者径向移动。并在AO计算的主要阶段之后,执行一次全屏的滤波pass。联合双边滤波(章节12)可以避免在表面的不连续处进行过滤,从而保持尖锐的边缘。它可以利用可用的深度信息或者法线信息来对过滤进行限制,即它只会对属于同一表面的样本进行过滤。还有一些方法使用了随机变化的采样模式,以及经过实验选择的滤波核;另一些方法则使用了固定大小的屏幕空间采样模式(例如 4 × 4 4 × 4 4×4像素),以及一个限制在该邻域上的滤波核。
环境光遮蔽的计算也可以在时域上进行超采样。通常会在每一帧中应用不同的采样模式,并对计算出来的遮挡因子进行指数平均从而实现这个目的。使用上一帧的z-buffer、相机变换和动态物体的运动信息,来将上一帧的数据重新投影到当前视图中,然后再将其与当前帧的结果进行混合。还会使用一些基于深度、法线、速度的启发式方法,来检测上一帧数据的可靠性,对于不可靠的数据需要丢弃(例如:由于一些新物体进入了视野中,因此上一帧中的数据与当前帧存在差异)。章节5在更一般的情况下,介绍了时域超采样和时域抗锯齿技术。时域过滤的成本较小,并且很容易实现,虽然它并不总是完全可靠的,但是在实践中出现的大多数问题都不太明显。这主要是因为环境光遮蔽不会直接单独显示在画面上,它只是光照计算的输入之一。在将这种环境光遮蔽效果与法线贴图、反照率纹理以及直接光照相结合之后,任何微小的瑕疵都会被掩盖掉,人眼一般很难观察到这些瑕疵。
使用环境光遮蔽进行着色
虽然我们是在恒定、遥远光照环境中推导出的环境光遮蔽值,但是我们也可以将其应用于更复杂的光照场景中。再次回顾一些反射方程:
L o ( v ) = ∫ l ∈ Ω f ( l , v ) L i ( l ) v ( l ) ( n ⋅ l ) + d l . (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} Lo(v)=∫l∈Ωf(l,v)Li(l)v(l)(n⋅l)+dl.(11.19)
如上文中所介绍的,方程11.19中包含了可见性函数 v ( l ) v(\mathbf{l}) v(l)。
假如我们现在正在处理一个漫反射表面,我们可以使用Lambertian BRDF来代替方程11.19中的 f ( l , v ) f(\mathbf{l}, \mathbf{v}) f(l,v),这个BRDF等于次表面反照率 ρ s s \rho_{\mathrm{ss}} ρss除以 π \pi π,将其带入方程11.19,可得:
L o = ∫ l ∈ Ω ρ s s π L i ( l ) v ( l ) ( n ⋅ l ) + d l = ρ s s π ∫ l ∈ Ω L i ( l ) v ( l ) ( n ⋅ l ) + d l . (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} Lo=∫l∈ΩπρssLi(l)v(l)(n⋅l)+dl=πρss∫l∈ΩLi(l)v(l)(n⋅l)+dl.(11.20)
我们对方程11.20进行一些化简整理,可得:
L o = ρ s s π ∫ l ∈ Ω L i ( l ) v ( l ) ( n ⋅ l ) + d l = ρ s s π ∫ l ∈ Ω L i ( l ) v ( l ) ( n ⋅ l ) + d l ∫ l ∈ Ω v ( l ) ( n ⋅ l ) + d l ∫ l ∈ Ω v ( l ) ( n ⋅ l ) + d l = ρ s s π ∫ l ∈ Ω L i ( l ) v ( l ) ( n ⋅ l ) + ∫ l ∈ Ω v ( l ) ( n ⋅ l ) + d l d l ∫ l ∈ Ω v ( l ) ( n ⋅ l ) + d l . (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} Lo=πρss∫l∈ΩLi(l)v(l)(n⋅l)+dl=πρss∫l∈Ωv(l)(n⋅l)+dl∫l∈ΩLi(l)v(l)(n⋅l)+dl∫l∈Ωv(l)(n⋅l)+dl=πρss∫l∈ΩLi(l)∫l∈Ωv(l)(n⋅l)+dlv(l)(n⋅l)+dl∫l∈Ωv(l)(n⋅l)+dl.(11.21)
如果我们使用方程11.8中所定义的环境光遮蔽,则方程11.21可以简化为:
L o = k A ρ s s ∫ l ∈ Ω L i ( l ) K ( n , l ) d l (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} Lo=kAρss∫l∈ΩLi(l)K(n,l)dl(11.22)
其中:
K ( n , l ) = v ( l ) ( n ⋅ l ) + ∫ l ∈ Ω v ( l ) ( n ⋅ l ) + d l (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} K(n,l)=∫l∈Ωv(l)(n⋅l)+dlv(l)(n⋅l)+(11.23)
上述形式为我们提供了一个全新的视角来看待这个过程。方程11.22中的积分,可以认为是对入射radiance L i L_i Li应用了一个方向性的滤波核 K K K。滤波器 K K K以一种复杂的方式在空间和方向上同时变化,但它具有两个重要的属性。首先,由于对点积进行了clamp操作,因此它最多只能覆盖点 p \mathbf{p} p法线周围的半球范围。其次,由于分母中包含归一化因子,因此它在整个半球上的积分等于1。
为了进行着色,我们需要计算两个函数乘积的积分,即入射radiance L i L_i Li和滤波器函数 K K K乘积的积分。在某些情况下,我们可以使用一种简化的方式来描述这个滤波器,并以很低的成本来计算这个二重积分,例如当 L i L_i Li和 K K K都使用球谐函数来进行表示的时候(章节10)。降低这个方程复杂度的另一种方法是,使用一个具有类似特性,但是更简单的滤波器来对其近似。最常见的选择就是归一化的余弦核函数 H H H:
H ( n , l ) = ( n ⋅ l ) + ∫ l ∈ Ω ( n ⋅ l ) + d l (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} H(n,l)=∫l∈Ω(n⋅l)+dl(n⋅l)+(11.24)
在没有入射光线被阻挡的时候,这种近似是十分准确的。它还涵盖了与原本滤波器相同的角度范围。虽然它完全忽略了可见性函数,但是方程11.22中仍然包含了环境光遮蔽 k A k_A kA,因此在被着色的表面上会有一些与可见性相关的暗化。
选择了这个近似滤波核,那么方程11.22就变成了:
L o = k A ρ s s ∫ l ∈ Ω L i ( l ) ( n ⋅ l ) + ∫ l ∈ Ω ( n ⋅ l ) + d l d l = k A π ρ s s E . (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} Lo=kAρss∫l∈ΩLi(l)∫l∈Ω(n⋅l)+dl(n⋅l)+dl=πkAρssE.(11.25)
这意味着,在最简单的形式中,可以通过计算irradiance,并将其乘上环境光遮蔽值来完成环境光遮蔽的效果着色。这里的irradiance可以来自任何来源,例如:它可以从irradiance环境贴图(章节10)中进行采样。这种方法的准确性,取决于这个近似滤波器有多大能力能够表现正确滤波器。对于在球面上平滑变化的光照,这种近似方式能够给出合理的结果。如果 L i L_i Li在所有可能的方向上都是恒定的,就好像场景是由全白的环境贴图所照亮的那样,在这种情况下,它是完全准确的。
这个方程还让我们了解到,为什么环境光遮蔽对于精确光源或者很小的面光源而言是一个很差的可见性近似,因为这些光源在表面上只占据了很小的一个立体角(对于精确光源而言是无穷小的),而可见性函数对光照积分会产生重要影响。它几乎是以二进制的方式来控制光源的贡献,也就是说,它要么完全启用,要么完全禁用。忽略可见性(正如我们在方程11.25中所做的那样)是一个影响很大的近似操作,这样做通常不会产生符合预期的结果。在这种近似情况下,所产生的阴影缺乏清晰度,并且没有任何预期的方向性,也就是说,它们看起来并不像是由特定光源产生的。对这种光源的可见性进行建模,环境光遮蔽并不是一个好的选择,应当使用一些其他的方法,例如阴影贴图等。然而,值得注意的是,有时候我们会使用较小的局部光源来模拟间接光照的效果,在这种情况下,使用环境光遮蔽值来调整它们的贡献是合理的。
到目前为止,我们都是假设在Lambertian表面上进行着色的。在处理更加复杂的、非常数的BRDF时,这一项无法从积分中提出来(就像我们在方程11.20中所做的那样)。对于镜面材质而言, K K K不仅取决于可见性和法线,还取决于观察方向。对于一个典型的微表面BRDF而言,其波瓣会在整个区域上发生显著改变;使用单一的、预先确定的形状来对其近似会显得过于粗糙,无法产生可信的结果。这也就是为什么在漫反射BRDF中,使用环境光遮蔽进行着色最有意义的原因。我们会在接下来的若干小节中讨论一些其他方法,它们更加适合复杂的材质模型。
使用环境法线(详见方程11.10)可以更加精确地近似滤波器 K K K。虽然滤波器中仍然没有包含可见性项,但是其最大值与未被遮挡的平均方向相匹配,这使得它在总体上可以更好地逼近方程11.23。当几何法线和环境法线不匹配的时候,使用环境法线将会给出更加准确的结果。Landis 不仅将它用在环境贴图的着色中,还用在了一些直接光照的着色中,来代替常规的阴影技术。
对于环境贴图的着色,Pharr提出了一种替代方案,该方法使用GPU的纹理过滤硬件来动态执行滤波操作。滤波器 K K K的形状是动态确定的,其滤波中心位于环境法线的方向上,其大小取决于 k A k_A kA的值,这样可以更加精确地与方程11.23中的原始滤波器相匹配。