光照对我们示例着色模型的影响非常简单;它为着色提供了一个主导方向。当然,现实世界中的照明可能非常复杂。可以有多个光源,每个光源都有自己的大小、形状、颜色和强度;间接照明甚至增加了更多的变化。正如我们将在第9章中看到的,基于物理的、写实的着色模型需要考虑所有这些参数。
相比之下,风格化的着色模型可能会以多种不同的方式使用照明,具体取决于应用程序的需求和视觉风格。一些高度风格化的模型可能根本没有照明的概念,或者(如我们的 Gooch 着色示例)可能仅使用它来提供一些简单的方向性。
照明复杂性的下一步是使着色模型以二元的方式对光的存在或不存在做出反应。用这种模型着色的表面在被照亮时将具有一种外观,而在不受光影响时具有不同的外观。这意味着区分这两种情况的一些标准:与光源的距离、阴影(将在第7章中讨论)、表面是否背对光源(即表面法线\textbf{n} 与光向量\textbf{l} 之间的夹角大于 90^{\circ} ),或这些因素的某种组合。
从光的二元存在或不存在到光强度的连续尺度,这是一小步。这可以表示为不存在和完全存在之间的简单插值,意味着强度的有界范围,可能是0到1,或者表示为以其他方式影响着色的无界数量。后者的一个常见选项是将着色模型分解为有光照和无光照的部分,光照强度k_\textrm{light} 线性缩放光照部分:
这很容易扩展到RGB颜色\textbf{c}_\textrm{light} :
并且,对于对光源:
无光照部分f_\textrm{unlit}(\textbf{n},\textbf{v}) 对应于将光视为二元着色模型的“不受光照影响时的外观”。它可以有多种形式,具体取决于所需的视觉风格和应用程序的需要。例如,f_\textrm{unlit}() = (0,0,0) 将导致任何不受光源影响的表面变成纯黑色。或者,无光照的部分可以为无光照物体表达某种形式的风格化外观,类似于 Gooch模型的冷色表面远离光线。通常,着色模型的这部分表示某种形式的照明,这些照明不是直接来自明确放置的光源,例如来自天空的光或从周围物体反射的光。这些其他形式的照明将在第10章和第11章中讨论。
我们之前提到过,如果光的方向\textbf{l} 与表面法线\textbf{n} 的夹角超过90度,则光源不会影响表面点,实际上来自表面下方。这可以被认为是光的方向(相对于表面)与其对着色的影响之间更一般关系的一种特殊情况。尽管基于物理,但这种关系可以从简单的几何原理推导出来,并且对于许多类型的非基于物理的风格化着色模型也很有用。
光在表面上的效果可以可视化为一组光线,照射到表面的光线密度与用于表面着色目的的光强度相对应。参见图5.4,它显示了一个被照亮的表面的横截面。沿该横截面撞击表面的光线之间的间距与\textbf{l} 和\textbf{n} 之间的夹角的余弦成反比。因此,入射到表面的光线的总密度与\textbf{l} 和\textbf{n} 之间夹角的余弦成正比,正如我们之前看到的,它等于这两个单位长度向量之间的点积。在这里我们看到为什么定义与光行进方向相反的光向量\textbf{l} 很方便;否则我们将不得不在执行点积之前取反它。
图5.4. 上排的附图显示了表面上的光的横截面图。在左侧,光线直接照射在表面上,在中心它们以一定角度照射表面,在右侧,我们看到使用矢量点积来计算角度余弦。下图显示了与整个表面相关的横截面平面(包括光和视图矢量)。
更准确地说,光线密度(以及光线对着色的贡献)与点积为正时成正比。负值对应于来自表面后面的光线,没有任何影响。所以,在将光的着色乘以光照点积之前,我们需要先将点积夹钳(clamp)为0。使用1.2节中介绍的x^+ 表示法,这意味着将负值夹钳为零,因此有:
支持多个光源的着色模型通常会使用公式5.5中的一种结构,它更通用,或者公式 5.6,这是基于物理的模型所必需的。它对于风格化模型也很有用,因为它有助于确保照明的整体一致性,特别是对于背离灯光或有阴影的表面。然而,有些模型并不适合这种结构;此类模型将使用公式5.5中的结构。
函数f_\textrm{lit}() 最简单的可能选择是使它成为一个恒定的颜色:
这会形成下面的着色模型:
该模型的光照部分对应于Lambertian着色模型,以Johann Heinrich Lambert[967]命名,他在1760年就发表了它(令人惊叹)。该模型适用于理想的漫反射表面,即完美无光泽的表面。我们在此对Lambert模型进行稍微简化的解释,第9章将对其进行更严格的介绍。Lambertian模型可以单独用于简单的着色,它是许多着色模型中的关键构建块。
我们可以从方程5.3-5.6中看到,光源通过两个参数与着色模型交互:指向光的向量\textbf{l} 和光的颜色\textbf{c}_\textrm{light} 。有各种不同类型的光源,主要区别在于这两个参数在场景中的变化方式。
我们接下来将讨论几种流行的光源类型,它们有一个共同点:在给定的表面位置,每个光源仅从一个方向\textbf{l} 照亮表面。换句话说,从着色表面位置看到的光源是一个无限小的点。对于真实世界的灯光来说,严格来说并非如此,但大多数光源相对于它们与被照明表面的距离而言都很小,因此这是一个合理的近似值。 在第7.1.2和第10.1节中,我们将讨论从一系列方向照亮表面位置的光源,即“区域光”。
平行光是最简单的光源模型。\textbf{l} 和\textbf{c}_\textrm{light} 在场景中都是恒定的,除了\textbf{c}_\textrm{light} 可能会因阴影而减弱。平行光没有位置。当然,实际的光源在空间中确实有特定的位置。平行光是抽象的,当到光的距离相对于场景尺寸很大时,可以得到很好的效果。例如,可以将20英尺外的探照灯照亮一个小型桌面西洋镜,这可以表示为平行光。另一个例子是被太阳照亮的场景,几乎任何场景都能用到,除非所讨论的场景是诸如太阳系内行星之类的场景。
平行光的概念可以稍微扩展,允许在光方向\textbf{l} 保持不变的情况下改变\textbf{c}_\textrm{light} 的值。出于性能或创造性的原因,这通常用于将灯光效果绑定到场景的特定部分。例如,一个区域可以用两个嵌套的(一个在另一个内)盒形体来定义,其中外盒外的\textbf{c}_\textrm{light} 等于(0,0,0)(纯黑色),内盒内部等于某个常数值,两个盒子之间的区域中在其极值之间平滑地插值。
与平行光不同,精确光源不是按时完成约会的光,而是具有位置的光。与现实世界的光源不同,这种光也没有尺寸,没有形状或大小。我们使用来自拉丁文punctus的术语“punctual”(意思是“点”),表示由源自单个局部位置的所有照明源组成的类。我们使用术语“点光源”来表示一种特定类型的发射体,它向所有方向均等地发光。因此,点光源和聚光灯是两种不同形式的精确光源。光方向向量\textbf{l} 根据当前着色表面点\textbf{p}_0 相对于点光源的位置\textbf{p} _\textrm{light} 的位置而变化:
此等式是向量归一化的一个示例:将向量除以其长度以生成指向同一方向的单位长度向量。这是另一种常见的着色操作,和我们在上一节中看到的着色操作一样,它是大多数着色语言中的内置函数。但是,有时需要此操作的中间结果,这需要使用更基本的操作在多个步骤中明确地执行规范化。将此应用于精确光源方向计算,我们得到以下结果:
由于两个向量的点积等于两个向量的长度与其夹角余弦的乘积,0°的余弦为1.0,因此向量与其自身的点积是其长度的平方。因此,要找到任何向量的长度,我们只需将其与自身求点积并取结果的平方根。
我们需要的中间值是r ,即精确光源与当前着色点之间的距离。除了用于对光向量进行归一化之外,还需要使用r 的值来计算距离函数中光强\textbf{c}_\textrm{light} 的衰减(变暗)。这将在下一节中进一步讨论。
向各个方向均匀发光的精确光源称为点光源或泛光灯。对于点光源,\textbf{c}_\textrm{light} 随着距离r 的函数变化而变化,其唯一的变化来源是上述提到的距离衰减。与图5.4中使用余弦因子演示类似的几何推理类似,图5.5显示了为什么会发生这种衰减。在给定的表面上,来自点光源的光线之间的间距与从表面到光源的距离成正比。与图5.4中的余弦因子不同,这种间距增加发生在表面的两个维度上,因此光线密度(以及光强\textbf{c}_\textrm{light} )与平方反比距离1/r^2 成正比。这使我们能够使用单个光属性\textbf{c}_{\textrm{light}_0} 指定\textbf{c}_\textrm{light} 的空间变化,\textbf{c}_{\textrm{light}_0} 定义为\textbf{c}_\textrm{light} 在固定参考距离r_0 处的值:
图5.5. 来自点光源的光线之间的间距与距离r 成比例地增加。由于间距增加发生在二维中,光线的密度(以及光强度)与1/r^2 成比例地减小。
公式 5.11 通常称为反平方光衰减。尽管从技术上讲,这是是点光源的正确距离衰减,但存在一些问题使该方程不太适合实际着色使用。
第一个问题发生在相对较小的距离处。随着r 的值趋于0,\textbf{c}_\textrm{light} 的值将无界增加。当r 达到0时,我们将有一个被零除的奇点。为了解决这个问题,一个常见的修改是在分母[861]上添加一个小值\epsilon :
使用的\epsilon 的确切值取决于应用;例如,Unreal游戏引擎使用\epsilon = 1\ \textrm{cm} [861]。
在CryEngine[1591]和Frostbite[960]游戏引擎中,使用了另一种修改,将r 限制为最小值r_\textrm{min} :
与前一种方法中使用的有点任意的\epsilon 值不同,r_\textrm{min} 的值具有物理解释:发射光的物理对象的半径。小于r_\textrm{min} 的r值对应于穿透物理光源内部的着色表面,这是不可能的。
相比之下,平方反比衰减的第二个问题发生在相对较大的距离处。问题不在于视觉效果,而在于性能。尽管光强度随着距离不断降低,但它永远不会变为0。为了有效渲染,希望光在某个有限距离处达到0强度(第20章)。有许多不同的方法可以修改平方反比方程来实现这一点。理想情况下,修改应该引入尽可能少的变化。为了避免在光的影响边界处出现突然截断,最好还是使改进函数的导数和值在相同距离处达到0。一种解决方案是将平方反比方程乘以具有所需属性的窗口函数。虚幻引擎[861]和Frostbite[960]游戏引擎都使用了一个这样的函数[860]:
+2表示在平方之前将值(如果为负)夹钳为0。图5.6显示了一个示例,平方反比方程曲线、公式5.14中的窗口函数以及将两者相乘的结果。
图5.6. 该图显示了平方反比曲线(使用\epsilon 方法避免奇点,\epsilon 值为1)、公式5.14中描述的窗口函数(r_\textrm{max} 设置为3)和窗口曲线。
应用程序的要求将影响所用方法的选择。例如,当距离衰减函数在相对较低的空间频率(例如,在光照贴图或每个顶点中)采样时,在r_\textrm{max} 处使导数等于0特别重要。CryEngine不使用光照贴图或顶点光照,因此它采用了更简单的调整,在0.8r_\textrm{max} 和r_\textrm{max}之间切换到线性衰减 [1591]。
对于某些应用程序,匹配反平方曲线不是优先事项,完全可以使用一些其他的函数。这有效地将方程5.11-5.14推广到以下公式:
其中f_\textrm{dist}(r) 是距离的某个函数。此类函数称为距离衰减函数。在某些情况下,非平方反比衰减函数的使用是由性能限制驱动的。例如,游戏《正当防卫2》需要计算成本极低的灯光。这决定了一个易于计算的衰减函数,同时也足够平滑以避免每个顶点的光照伪影[1379]:
在其他情况下,衰减函数的选择可能是出于创造性考虑。例如,用于现实游戏和风格化游戏的虚幻引擎有两种光衰减模式:平方反比模式,如公式5.12中所述,以及指数衰减模式,可以进行调整以创建各种衰减曲线[1802]。游戏《古墓丽影(2013)》的开发人员使用样条编辑工具来创作衰减曲线[953],从而可以更好地控制曲线形状。
与点光源不同,几乎所有真实世界光源的照明都因方向和距离而异。这种变化可以表示为方向衰减函数f_\textrm{dir}(\textbf{l}) ,它结合距离衰减函数来定义光强度的整体空间变化:
f_\textrm{dir}(\textbf{l})的不同选择可以产生不同的光照效果。一种重要的效果是聚光灯,它以圆锥体形式投射光线。聚光灯的方向衰减函数具有围绕聚光灯方向向量\textbf{s} 的旋转对称性,因此可以表示为\textbf{s} 与到表面反向光向量-\textbf{l} 之间的角度\theta_s 的函数。需要反转光向量,因为我们将表面上的\textbf{l} 定义为指向光,而这里我们需要指向远离光的向量。
大多数聚光灯函数使用由\theta_s 的余弦组成的表达式,它(正如我们之前看到的)是着色中角度最常见的形式。聚光灯通常有一个本影角\theta_u ,它限制了光线,使得所有\theta_s ≥ \theta_u 的f_\textrm{dir}(\textbf{l})= 0 。这个角度可以用于以类似于前面看到的最大衰减距离r_\textrm{max} 的方式进行剔除。聚光灯的半影角 \theta_p 也很常见,它定义了一个内锥体,其光线处于其最大强度。参见图5.7。
图5.7. 聚光灯:\theta_s 是从光的定义方向\textbf{s} 到矢量-\textbf{l} (即到表面的方向)的角度;\theta_p 表示半影;\theta_u 表示光定义的本影角。
聚光灯使用了各种方向衰减函数,但它们往往大致相似。例如,函数f_{\textrm{dir}_\textrm{F}}(\textbf{l}) 用于Frostbite游戏引擎[960],函数f_{\textrm{dir}_\textrm{T}}(\textbf{l}) 用于three.js浏览器图形库[218]:
复习一下,x^{\mp} 是我们在第1.2节中介绍的将x 限制在0和1之间的符号。smoothstep函数是三次多项式,通常用于着色中的平滑插值。它是大多数着色语言的内置函数。
图5.8显示了我们迄今为止讨论过的一些光类型。
图5.8. 某些类型的灯光。从左到右:平行光、无衰减的点光源和平滑过渡的聚光灯。请注意,由于灯光和表面之间的角度变化,点光源向边缘变暗。
精确光源的\textbf{c}_\textrm{light} 值可以通过许多其他方式改变。f_\textrm{dir}(\textbf{l}) 函数不限于上面讨论的简单聚光灯衰减函数;它可以代表任何类型的方向变化,包括从现实世界的光源测量的复杂表格模式。照明工程学会(IES)已为此类测量定义了标准文件格式。许多照明制造商都提供IES配置文件,并已在游戏《杀戮地带:暗影坠落[379,380] 以及Unreal[861]和Frostbite[960]游戏引擎等中使用。Lagarde对解析和使用此文件格式相关的问题进行了很好的总结[961]。
游戏《古墓丽影(2013)》[953]有一种准精确光源,它对沿x、y和z世界轴的距离应用独立的衰减函数。在《古墓丽影》中,曲线也可用于随时间改变光强度,例如,产生闪烁的手电筒。
在第6.9节中,我们将讨论如何通过使用纹理来改变光强度和颜色。
平行光和精确光的主要特征在于光方向\textbf{l} 的计算方式。可以使用其他方法来定义不同类型的灯光,以此计算灯光方向。例如,除了前面提到的光类型,《古墓丽影》还有胶囊灯,它使用线段而不是点作为光源[953]。对于每个着色像素,到线段上最近点的方向作为光的方向\textbf{l}。
只要着色器具有用于评估着色方程的\textbf{l} 和\textbf{c}_\textrm{light} 值,就可以使用任何方法来计算这些值。
到目前为止讨论的光类型是抽象的。在现实中,光源是有大小和形状的,它们从多个方向照亮表面点。在渲染中,这种光被称为区域光,它们在实时应用中的使用正在稳步增加。区域光渲染技术分为两类:模拟由部分遮挡的区域光导致阴影边缘的柔化(第7.1.2节)和模拟区域光在表面的着色效果(第10.1节)。第二类照明对于光滑、镜面般的表面最为明显,在这种情况下,可以从反射中清楚地辨别出光的形状和大小。平行光和精确光源不太可能被废弃,尽管它们不再像过去那样无处不在。已经开发出计算光面积的近似值的技术,其实施成本相对较低,因此得到了更广泛的使用。与过去相比,GPU性能的提高还允许采用更精细的技术。