Realtime Rendering 4th notes - chapter7

(转载请注明出处,谢谢咯)

"All the variety, all the charm, all the beauty
of life is made up of light and shadow. " — Tolstoy

本章会讨论最重要且流行的阴影算法,以及一些体现了重要原则的不太流行的方法。本章中使用的术语如图(7.1)所示:

(图7.1)光源,遮挡物occluders,接收物receivers,阴影shadow,本影umbra和半影penumbra。

遮挡物(occluders)是将阴影投射到接收者(receivers )上的对象。点源光仅产生完全阴影区域,有时称为硬阴影(hard shadows)。如果使用面积或体积光源,则会产生柔和阴影(soft shadows)。然后,每个阴影可以具有称为本影(umbra)的完全阴影区域和称为半影(penumbra)的部分阴影区域。软阴影的边缘是模糊的。但是,通过使用低通滤波器模糊硬阴影的边缘通常无法正确渲染它们。从图7.2中可以看出,遮挡物越接近接收器,软阴影就越清晰。柔和阴影的本影区域不等同于点源光产生的硬阴影。相反,当光源变大时,柔和阴影的本影区域尺寸减小,并且甚至可能消失,因为光源足够大或接收物距离遮挡物足够远。柔和阴影通常是优选的,因为半影边缘让观察者知道阴影确实是阴影。硬边阴影通常看起来不那么逼真,有时会被误解为实际的几何特征,例如表面的折痕。但是,硬阴影渲染速度比柔和阴影快。

(图7.2)混合了hard shadow和soft shadow的效果。由于遮挡物靠近接收器,因此来自板条箱的阴影是尖锐的。 人的阴影在接触点处是锐利的,随着到遮挡物的距离的增加而变软。 遥远的树枝给出柔和的阴影。

比半影更重要的是有阴影。 没有阴影作为视觉提示,一些场景往往难以令人信服或察觉。 正如Wanger所展示的那样,通常情况下有一个不准确的阴影也比没有要好,因为眼睛对阴影的形状是相当宽容的。 例如,在地板上作为纹理应用的模糊黑色圆圈可以将角色锚定到地面。

在以下部分中,我们将超越这些简单的建模阴影,并提供从场景中的遮挡物实时自动计算阴影的方法。 第一部分处理在平面上投射阴影的特殊情况,第二部分介绍更一般的阴影算法,即将阴影投射到任意表面上。 将覆盖硬阴影和柔和阴影。 总之,提出了一些适用于各种阴影算法的优化技术。

7.1 Planar Shadows(平面阴影)

当对象将阴影投射到平面上时,会发生一个简单的阴影情况。 本节介绍了几种平面阴影算法,每种算法都具有阴影柔和度和真实感的变化。

7.1.1 Projection Shadows(投射阴影)

在该方案中,第二次渲染三维对象以创建阴影。可以导出矩阵,将对象的顶点投影到平面上。考虑图7.3中的情况,其中光源位于l,要投影的顶点为v,投影顶点位于p。我们将导出阴影平面为y = 0的特殊情况下的投影矩阵,然后将该结果推广到任何平面。

(图7.3)

我们首先推导出x坐标的投影。 从图7.3左侧的类似三角形中,我们得到了:

$$
\frac{p_x-l_x}{v_x-l_x}=\frac{l_y}{l_y-v_y}\Longleftrightarrow p_x=\frac{l_yv_x-l_xv_y}{l_y-v_y}\tag{7.1}
$$

以相同的方式获得z坐标:$p_z =(l_yv_z-l_zv_y)=(l_y-v_y)$,而y坐标为0。 现在这些方程可以转换成投影矩阵M:

$$
\boldsymbol{M}=\begin{pmatrix}l_y&-l_x&0&0\\0&0&0&0\\0&-l_z&l_y&0\\0&-1&0&l_y\end{pmatrix}\tag{7.2}
$$

简单可验证$\boldsymbol{M}\boldsymbol{v}=\boldsymbol{p}$,这说明$\boldsymbol{M}$是投影矩阵。

一般情况下,应该投射阴影的平面不是平面y = 0,而是$\pi:\boldsymbol{n}\cdot\boldsymbol{x}+d=0$。这种情况在图7.3的右侧部分中描述。 目标是再次找到一个将v向下投影到p的矩阵。 为此,在l处发出的射线穿过v与平面π相交。 这产生了预计的点p:

$$
\boldsymbol{p}=\boldsymbol{l}-\frac{d+\boldsymbol{n}\cdot\boldsymbol{l}}{\boldsymbol{n}\cdot(\boldsymbol{v}-\boldsymbol{l})}(\boldsymbol{v}-\boldsymbol{l})\tag{7.3}
$$

该等式也可以转换为投影矩阵,如公式7.4所示,满足$\boldsymbol{M}\boldsymbol{v}=\boldsymbol{p}$:

$$
\boldsymbol{M}=
\begin{pmatrix}
\boldsymbol{n}\cdot\boldsymbol{l}+d-l_xn_x&-l_xn_y&-l_xn_z&-l_xd\\
-l_yn_x&\boldsymbol{n}\cdot\boldsymbol{l}+d-l_yn_y&-l_yn_z&-l_yd\\
-l_zn_x&-l_zn_y&\boldsymbol{n}\cdot\boldsymbol{l}+d-l_zn_z&-l_xd\\
-n_x&-n_y&-n_z&\boldsymbol{n}\cdot\boldsymbol{l}
\end{pmatrix}\tag{7.4}
$$

正如预期的那样,如果平面为y = 0,则该矩阵变为公式7.2中的矩阵,即n =(0,1,0)且d = 0。

要渲染阴影,只需将此矩阵应用于在平面π上投射阴影的对象上,并使用深色和无照明渲染此投影对象。在实践中,您必须采取措施避免在接收它们的表面下方渲染投影。一种方法是向我们投影的平面添加一些偏差,以便阴影始终呈现在曲面的前面。

更安全的方法是首先绘制地平面,然后关闭z缓冲区绘制投影三角形,然后像往常一样渲染其余的几何体。 然后,投影的三角形总是绘制在地平面的顶部,因为没有进行深度比较。

如果地平面具有极限,例如,它是矩形,则投射的阴影可能落在其外部,从而打破了幻觉。要解决这个问题,我们可以使用模板缓冲区。 首先,将接收物绘制到屏幕和模板缓冲区。 然后,在关闭z缓冲区的情况下,仅在绘制接收器的位置绘制投影三角形,然后正常渲染场景的其余部分。

另一种阴影算法是将三角形渲染成纹理,然后将其应用于地平面。这种纹理是一种光照贴图,对下面的表面强度进行调节的纹理(见第11.5.1节)。可以看出,将阴影投影渲染到纹理的这种想法也允许弯曲表面上的半影和阴影。 这种技术的一个缺点是纹理可以被放大,单个纹素覆盖多个像素,打破了幻觉。

如果阴影不是逐帧改变,即光和影不相对于彼此移动,则可以重复使用该纹理。可节省计算资源。

所有阴影投射物必须位于灯和地面接收物之间。如果光源低于物体上的最高点,则产生antishadow,因为每个顶点通过光源的点投射。 正确的阴影和反光影如图7.4所示。 如果我们投影一个低于接收平面的对象,也会发生错误,因为它也应该不投射阴影。

(图7.4)左侧,显示正确的阴影,而在右图中,出现一个antishadow,因为光源位于对象的最顶端下方。

可以明确地剔除和修剪阴影三角形以避免这种伪影。接下来介绍的一种更简单的方法是使用现有的GPU管道来执行剪切投影。

7.1.2 Soft Shadows(软阴影)

通过使用各种技术,投影阴影也可以变得柔和。 在这里,我们描述了Heckbert和Herf生成柔和阴影的算法。 该算法的目标是在地平面上生成显示柔和阴影的纹理。 然后我们描述损失精度,但更快的方法。

只要光源有区域,就会出现柔和阴影。近似区域光效果的一种方法是使用放置在其表面上的几个点光源对其进行采样。对于这些点源光中的每一个,渲染图像并将其累积到缓冲器中。这些图像的平均值是具有柔和阴影的图像。 请注意,理论上,任何生成硬阴影的算法都可以与此累积技术一起使用以产生半影。 实际上,由于涉及的执行时间,以交互速率这样做通常是不行的。

Heckbert和Herf使用基于平截头体的方法来产生阴影。 这个想法是将光视为观察者,地平面形成截锥体的远剪切平面。 截锥体足够宽以包围遮挡物。

通过生成一系列地平面纹理来形成柔和阴影纹理。区域光源在其表面上进行采样,每个位置用于对表示地平面的图像进行着色,然后将阴影投射物投影到该图像上。 对所有这些图像求和并求平均以产生地平面阴影纹理。有关示例,请参见图7.5的左侧。

(图7.5)在左边,使用Heckbert和Herf的方法进行渲染,使用256个pass。 在右边,Haines的方法只使用一个pass。以Haines的方法来说,本影过于庞大,这在门口和窗户周围尤为明显。

采样区域光方法的一个问题是它看起来就是来自点源光的几个重叠阴影。此外,对于n个阴影pass,只能生成n + 1个不同的阴影。大量的pass可以给出准确的结果,但成本过高。该方法可以用于测试其他更快算法的质量是有用。

更有效的方法是使用卷积,即过滤。在某些情况下,从单个点生成的硬阴影进行模糊可能就足够了,并且可以产生可以与真实世界内容融合的半透明纹理。见图7.6。然而,在物体与地面接触的地方附近,均匀的模糊可能是不可信的。

(图7.6)投影。通过从上方渲染阴影投射然后模糊图像并将其渲染在地平面上来生成阴影纹理。(图像在Autodesk的A360查看器中生成,模型来自Autodesk的Inventor示例。)

还有许多其他方法可以提供更好的近似值,但需要额外花费。 例如,Haines以投影的硬阴影开始,然后渲染轮廓边缘的渐变从中心的黑暗到边缘的白色,以创建合理的半影。 请参见图7.5的右侧。 然而,这些半影在物理上是不正确的,因为它们也应该延伸到轮廓边缘内的区域。 Iwanicki借鉴了球面谐波(spherical harmonics)的思想,并用椭圆体近似遮挡角色,以提供柔和的阴影。所有这些方法都有各种近似和缺点,但比平均大量阴影图像要有效得多。


7.2 Shadows on Curved Surfaces (在曲面上投影)

将平面阴影的概念扩展到曲面的一种简单方法是使用生成的阴影图像作为投影纹理。从光的角度考虑阴影。无论光线看到什么都被照亮; 它没有看到的是阴影。假设遮挡物从光的视点呈现为黑色,而不是白色纹理。然后可以将该纹理投影到要接收阴影的表面上。 实际上,接收器上的每个顶点都有一个为其计算的(u,v)纹理坐标,并且纹理应用于它。 这些纹理坐标可以由应用程序显式计算。 这与前一部分中的地面阴影纹理略有不同,其中对象被投影到特定物理平面上。这里,图像是作为光的视图制作的,就像投影仪中的胶片帧一样。

渲染时,投影阴影纹理会修改接收器曲面。 它也可以与其他阴影方法结合使用,有时主要用于帮助感知对象的位置。例如,在游戏中,即使角色处于完全阴影中,主角也可能总是在其正下方给出一个投影。更精细的算法可以提供更好的结果。例如,Eisemann和Décoret假设一个矩形顶灯并创建一堆物体水平切片的阴影图像,然后将其转换为mipmap或类似图像。通过使用其mipmap,每个切片的相应区域与其距接收物的距离成比例,这意味着更远的切片将投射更柔和的阴影。

纹理投影方法存在一些严重的缺点。首先,应用程序必须识别哪些对象是遮挡物以及哪些物体是它们的接收物。 接收器必须由程序保持远离遮光器的光线,否则阴影是向后投射的。此外,遮挡对象不能遮挡自己。接下来的两个部分提供生成正确阴影的算法,而不需要这样干预或限制。

注意,通过使用预构建的投影纹理可以获得各种照明图案。聚光灯只是一个方形投影纹理,其内部有一个圆圈,用于定义光线。威尼斯百叶窗效果可以通过由水平线组成的投影纹理来创建。 这种类型的纹理称为光衰减遮罩(light attenuation mask),cookie纹理或图案图(gobo map)。 通过简单地将两个纹理相乘,预构建的图案可以与创建的投影纹理组合。 这些灯将在第6.9节中进一步讨论。


7.3 Shadow Volumes (阴影体积)

1991年Heidmann提出,基于Crow的shadow volumes的方法可以通过巧妙地使用模板缓冲区将阴影投射到任意对象上。它可以在任何GPU上使用,因为唯一的要求是模板缓冲区。它不是基于图像的(与下面描述的阴影贴图算法不同),因此避免了采样问题,从而在任何地方产生正确的锐利阴影。 这有时可能是一个缺点。 例如,角色的衣服可能会产生褶皱,这些褶皱会产生失真严重的硬阴影。 由于成本不可预测,今天很少使用shadow volumes。 我们在这里给出算法的简要描述,因为它说明了一些重要的原则和基于这些的研究。

首先,想象一个点和一个三角形。 将线从一个点延伸到三角形的顶点到无穷大,产生无限的三面金字塔。 三角形下面的部分,即不包括该点的部分,是截头无限金字塔,上部是金字塔。 如图7.7所示。 现在想象一下这个点实际上是一个点光源。 然后,在截断金字塔的体积内(在三角形下面)的对象的任何部分都是阴影。 该体积称为shadow volumes。

(图7.7)左:来自点光源的线延伸通过三角形的顶点以形成无限金字塔。右:上部是金字塔,下部是无限截断的金字塔,也称为shadow volume。阴影体内的所有几何体都是阴影。

假设我们查看一些场景并跟踪从眼睛通过像素的光线,直到光线击中要在屏幕上显示的对象。当光线正在通往这个物体的路上时,每当它穿过正面的阴影体的面(即面向观察者的面)时递增一个计数器。 因此,每当光线进入阴影时,计数器就会递增。以相同的方式,每当光线穿过截头金字塔的背面时,我们减少相同的计数器。然后光线会从阴影中走出来。 我们继续,递增和递减计数器,直到射线击中要在该像素处显示的对象。 如果计数器大于零,则该像素处于阴影中;否则不在。当有多个投射阴影的三角形时,此原则也适用。见图7.8。

(图7.8)使用两种不同计数方法计算阴影体积交叉的二维侧视图。在z-pass体积计数中,当光线通过阴影体积的前面三角形时计数递增,而在通过背面三角形时离开时递减。因此,在A点,光线进入两个阴影体积为+2,然后留下两个体积,留下净计数为零,因此该点处于光照状态。在z-fail体积计数中,计数从表面开始(这些计数以斜体显示)。对于B点的光线,z-pass方法通过两个正面三角形给出+2计数,而z-fail通过两个背面三角形给出相同的计数。C点显示了如何对z-fail阴影卷进行限制。从点C开始的光线首先击中前面三角形,给出-1。然后它退出两个阴影体(通过它们的端盖,这种方法正常工作所需),计数为+1。计数不为零,因此该点在阴影中。两种方法始终为查看曲面上的所有点提供相同的计数结果。

使用光线执行此操作非常耗时。但是有一个更智能的解决方案:模板缓冲区可以为我们进行计数。首先,清除模板缓冲区。其次,将整个场景绘制到帧缓冲区中,仅使用未接受光照材质的颜色,将这些着色组件放入颜色缓冲区,深度信息放入z缓冲区。第三,关闭z缓冲区更新,写入颜色缓冲区(尽管仍然进行了z缓冲区测试),然后绘制阴影卷的正面三角形。在此过程中,模板操作设置为在绘制三角形的任何位置增加模板缓冲区中的值。第四,使用模板缓冲区完成另一次传递,这次只绘制阴影体积的背面三角形。对于此过程,在绘制三角形时,模板缓冲区中的值会减少。仅当渲染的阴影体面的像素可见时(即,未被任何真实几何体隐藏),才进行递增和递减。此时,模板缓冲区保持每个像素的阴影状态。最后,再次渲染整个场景,这次仅显示受光影响的活动材质的组件,并且仅在模板缓冲区中的值为0时显示。值为0表示光线从阴影射出的数量与进入阴影的次数一样多:即,该位置被光照亮。

(图7.9)Shadow volumes。在左侧,角色投下阴影。在右侧,显示被挤压的三角形。 (来自Microsoft SDK 示例)

这种计数方法是shadow volume背后的基本思想。该算法生成的阴影示例如图7.9所示。有一种有效的方法可以在一次pass中实现该算法。但是,当物体穿透相机的近平面时,会发生计数问题。 称为z-fail的解决方案涉及计算隐藏在可见表面后面而不是前面的交叉点。 图7.8显示了该替代方案的简要概述。

为每个三角形创建四边形会产生大量的成本。每个三角形将创建必须渲染的三个四边形,由一千个三角形组成的球体产生三千个四边形,并且每个四边形可以跨越屏幕。 一种解决方案是仅沿着对象的轮廓边缘绘制那些四边形,例如,我们的球体可能仅具有五十个轮廓边缘,因此仅需要五十个四边形。 几何着色器可用于自动生成此类轮廓边。Culling和clamping技术也可用于降低填充成本。

然而,阴影体积算法仍然有一个可怕的缺点:极端的可变性。想象一下,一个小的三角形在视野中。如果相机和灯光处于完全相同的位置,则阴影量成本最低。形成的四边形不会覆盖任何像素,因为它们是视图的边缘。只有三角形本身很重要。假设观众现在围绕三角形运行,将其保持在视野中。当相机远离光源时,阴影体四边形将变得更加明显并覆盖更多的屏幕像素,从而导致更多的计算发生。如果观察者碰巧移动到三角形的阴影中,阴影体积将完全填满屏幕,与我们的原始视图相比,需要花费相当多的时间来计算。这种可变性使得shadow volume在交互式应用程序中无法使用,因为一致的帧速率很重要。与其他情况一样,对光的观察可能导致算法成本的巨大,不可预测的速率跳跃。

由于这些原因,应用程序大部分都放弃了volume shadow。 然而,考虑到在GPU上访问数据的新方法和不同方式的不断发展,以及研究人员巧妙地重新利用这些功能,volume shadow可能有一天会重新被普遍使用。 例如,Sintorn等人,概述了该算法,这些算法可以提高效率并提出自己的分层加速结构。

下面提出的算法阴影映射具有更加可预测的成本,非常适合GPU,因此构成了许多应用程序中阴影生成的基础。


7.4 Shadow Maps

1978年,Williams提出可以使用基于z缓冲区的常见渲染器在任意对象上快速生成阴影。我们的想法是使用z缓冲区从投射阴影的光源位置渲染场景。 无论光线看到什么都被照亮,其余部分都在阴影中。生成此图像只需要z缓冲。可以关闭照明,纹理和写入颜色缓冲区。

z缓冲区中的每个像素现在包含最靠近光源的对象的z深度。 我们将z-buffer的全部内容称为shadow map,有时也称为shadow depth map或shadow buffer。要使用阴影贴图,场景将再次渲染,但这次是针对观察者的。 在渲染每个图元时,将其在每个像素处的位置与阴影贴图进行比较。 如果渲染点距离光源比阴影贴图中的对应值更远,则该点位于阴影中,否则不是。该技术通过使用纹理映射来实现。 见图7.10。 shadow mapping是一种流行的算法,因为它是相对可预测的。 构建阴影贴图的成本与渲染图元的数量大致呈线性关系,访问时间是不变的。在光和物体不移动的场景中,shadow mapping可以只生成一次并重复使用,例如用于计算机辅助设计。

(图7.10)阴影贴图。在左上方,通过将深度存储到视图中的表面来形成阴影图。在右上角,显示的眼睛看着两个位置。 在点va处看到球体,并且发现该点位于阴影图上的纹理像素a处。 存储在那里的深度不小于点va来自光,所以点被照亮。在点vb处击中的矩形(远远超过)比存储在纹理像素b处的深度更远,因此在阴影中。在左下方是从灯光的角度看场景,白色更远。在右下角是使用此阴影贴图渲染的场景。

当生成一个z缓冲区时,光可以仅在特定方向上像相机一样“观察”。对于诸如太阳之类的远距离定向光,光的视图被设置为包含将阴影投射到眼睛看到的观察体积中的所有物体。灯光使用正交投影,其视图需要设置足够宽和高的x和y,以查看这组对象。本地光源需要尽可能类似的调整。如果局部光线远离阴影投射物体,则单个视锥体可能足以包含所有这些。或者,如果灯光是聚光灯,则它具有与之相关的天然平截头体,其平截头体外的所有物体都被认为没有被照亮。

如果局部光源位于场景内并被阴影投射物包围,典型的解决方案是使用六视图立方体,类似于立方体环境映射。这被称为全向阴影贴图(omnidirectional shadow maps)。全方位地图的主要挑战是避免两个独立映射相遇的接缝处的失真。King和Newhall深入分析问题并提供解决方案,Gerasimov提供了一些实现细节。 Forsyth为全向光提供了一种通用的多平截头体分割方案,它还可以根据需要提供更多的阴影贴图分辨率。Crytek根据每个视图投影平截头体的屏幕空间覆盖率为点光源设置六个视图中每个视图的分辨率,所有映射都存储在纹理图集(atlas)中。

并非场景中的所有对象都需要渲染到灯光的视图体积中。首先,只需要渲染可以投射阴影的对象。 例如,如果已知地面只能接收阴影而不能投射阴影,则不必将其渲染到阴影贴图中。

(图7.11)

根据定义,阴影投射物在光的视锥体内。该视锥体可以通过多种方式加强或收紧,使我们能够忽视一些阴影投射物。摄像一组眼睛可见的阴影投射物。这组物体沿着光的视线方向存在一定的最大距离。超出此距离的任何东西都不能在可见接收物上投下阴影。类似地,可见接收物的集合可能小于光的原始x和y视图边界。见图7.11。另外,如果光源位于眼睛的视锥体内,则在这个额外的视锥体之外的任何物体都不能在接收物上投下阴影。仅渲染相关对象不仅可以节省渲染时间,还可以减小光锥体所需的尺寸,从而提高阴影贴图的有效分辨率,从而提高质量。 此外,如果光截头体的近平面尽可能远离光线,且远平面尽可能接近,则可以提高z缓冲区的有效精度(见第4.7.2节)。

阴影贴图的一个缺点是阴影的质量取决于阴影贴图的分辨率(以像素为单位)和z缓冲区的数值精度。由于在深度比较期间对阴影贴图进行采样,因此该算法易受混叠问题的影响,尤其是靠近对象之间的接触点。一个常见的问题是self-shadow aliasing,通常称为“surface acne”或“shadow acne”,三角形错误地遮蔽自身。这个问题有两个来源。一个是处理器精度的数值限制。另一个来源是几何的,因为点样本的值用于表示区域的深度。 也就是说,为光产生的样本几乎从不与屏幕样本处于相同的位置(例如,像素通常在它们的中心处被采样)。当光的存储深度值与观察到的表面深度进行比较时,光的值可能略低于表面的值,从而导致自阴影。这些错误的影响如图7.12所示。

(图7.12)

帮助避免(但不总是能消除)各种阴影图伪像的一种常用方法是引入偏差因子(bias factor)。当检查阴影贴图中找到的距离与被测位置的距离时,从接收物的距离中减去一个小的偏差。见图7.13。这种偏差可能是一个恒定的值,但是当接收物不是大部分面向光时,这样做可能会失败。更有效的方法是使用与接收物与光的角度成比例的偏置。表面越远离光线越倾斜,偏差越大,以避免问题。这种类型的偏差称为斜率偏差。可以通过使用诸如OpenGL的glPolygonOffset之类的命令来应用这两个偏差,以使每个多边形偏离光。请注意,如果表面直接面向光线,则不会向后偏置。因此,使用恒定偏置以及斜率标度偏差以避免可能的精度误差。坡度标度偏差(Slope scale bias)也经常被clamp在某个最大值,因为当表面迎着光接近侧视状态时,正切值可能非常高。

(图7.13)shadow bias。表面渲染为顶灯的阴影贴图,垂直线表示阴影贴图像素中心。封闭深度记录在×位置。我们想知道表面上以点显示的三个样品是否是亮的。每个最接近的阴影贴图深度值用相同的颜色×显示。在左侧,如果没有添加偏差,蓝色和橙色样本将被错误地确定为阴影,因为它们光线距离大于相应的阴影贴图深度。在中间,从每个样品中减去恒定的深度偏差,使每个样品更接近光。蓝色样本仍被视为阴影。在右侧,通过将每个多边形移动远离与其斜率成比例的光来形成阴影贴图。所有样本深度现在都比它们的阴影贴图深度更近,因此所有样本深度都会亮起。

Holbert引入了法线的偏移,它首先将接收者的世界空间位置沿着表面的法线方向稍微移动,与光的方向和几何法线之间的角度的正弦成比例。参考图7.24。这不仅会更改深度,还会更改在阴影贴图上测试样本的x坐标和y坐标。随着光的角度变得越来越浅,这种偏移会增加,希望样品在表面上方足够远以避免自阴影。该方法可视化为将样品移动到接收者上方的“虚拟表面”。此偏移是世界空间距离,因此Pettineo建议按阴影贴图的深度范围进行缩放。 Pesce建议沿摄像机视图方向偏置的想法,这也可以通过调整阴影贴图坐标来实现。其他偏置方法在7.5节中讨论,因为在那里提出的阴影方法也需要测试几个相邻的样本。

太多的偏差会导致一个称为漏光(light leaks)或彼得平移(Peter Panning)的问题,其中物体看起来略微漂浮在下面的表面之上。 出现这种伪影是因为物体接触点下方的区域(例如,脚下的地面)被向前推得太远而因此没有接收到阴影。

避免自阴影问题的一种方法是仅把背面渲染到阴影贴图。这种方案称为第二深度阴影映射(second-depth shadow mapping),适用于许多情况,特别是对于不能手动调整偏差的渲染系统。当对象是双面的,薄的或彼此接触时,会出现问题情况。如果物体是网格两侧可见的模型,例如棕榈叶或纸张,则可能发生自阴影,因为背面和正面位于相同位置。类似地,如果不执行偏置,则在轮廓边缘或薄物体附近可能发生问题,因为在这些区域中背面靠近前面。增加偏差可以帮助避免surface acne,但该方案更容易受到光泄漏的影响,因为在接触点处接收物和遮挡物的背面之间没有分离。见图7.14。选择哪种方案可能取决于具体情况。例如,Sousa等人发现对太阳阴影使用正面室内灯使用背面最适合他们的应用程序。

(图7.14)顶置光源的阴影贴图表面。 在左侧,面向灯光的表面(以红色标记)将被发送到阴影贴图。 表面可能被错误地确定为自身阴影(acne),因此需要偏离光线。在中间,只有背面三角形被渲染到阴影贴图中。向下推动这些遮挡物的偏差可能会让光线泄漏到地平面靠近位置a的地方;向前偏向可以使标记为b的轮廓边界附近的照明位置在阴影中被发觉。在右边,在每个位置处找到的最近的正面和背面三角形之间的中点处形成中间表面 在阴影贴图上。在c点附近可能发生漏光(这也可能发生在第二深度阴影贴图中),因为最近的阴影贴图样本可能位于此位置左侧的中间曲面上,因此该点会更接近光。

请注意,对于阴影贴图,对象必须是“防水的(watertight)”(即固体,封闭的,第16.3.3节),或者必须将前面和后面都渲染到地图,否则对象可能无法完全投射阴影。Woo提出了一种通用的方法,试图在字面上尝试使用正面或背面进行遮蔽。我们的想法是将实体对象渲染到阴影贴图中,并跟踪光线的两个最接近的表面。该过程可以通过深度剥离或其他与透明度相关的技术来执行。两个对象之间的平均深度形成一个中间层,其深度用作阴影贴图,有时称为双阴影贴图(dual shadow map)。如果物体足够厚,则可以最大限度地减少自阴影和漏光伪影。 Bavoil等,讨论了解决潜在工件的方法,以及其他实现细节。主要缺点是与使用两个阴影贴图相关的额外成本。迈尔斯讨论了遮挡和接收者之间的艺术家控制的深度层。

当观察者移动时,随着阴影投射物们的变化,灯光的视图体积通常会改变大小。这种变化反过来导致阴影在帧与帧之间略微移动。发生这种情况是因为灯光的阴影贴图从灯光中采集了一组不同的方向,并且这些方向与前一组不对齐。对于定向光,解决方案是强制生成的每个后续阴影贴图在世界空间中保持相同的相对纹素光束位置。也就是说,您可以将阴影贴图视为在整个世界中施加二维网格化参照系,每个网格单元表示地图上的像素样本。移动时,将为这些相同网格单元生成另一组阴影贴图。 换句话说,光的视图投影被强制到该网格以保持帧间的一致性。

7.4.1 Resolution Enhancement(分辨率增强)

与纹理的使用方式类似,理想情况下我们希望一个阴影贴图纹素覆盖一个图像像素。 如果我们将光源放置在与眼睛相同的位置,则阴影贴图与屏幕空间像素一对一地完美地映射(并且没有可见的阴影,因为光正好照亮了眼睛所看到的)。一旦光的方向改变,这个每像素比率就会改变,这可能会导致伪影。一个例子如图7.15所示。由于前景中的大量像素与阴影贴图的每个纹素相关联,因此阴影是块状的并且定义不明确。 这种不匹配称为透视混叠(perspective aliasing )。如果表面和光接近侧视,但是面向观察者,则单个阴影贴图纹理像素也可以覆盖许多像素。这个问题被称为投射混叠(projective aliasing );见图7.16。 通过增加阴影贴图分辨率可以减少阻塞,但是会增加内存和处理成本。

(图7.15)使用标准阴影贴图创建左侧图像;使用LiSPSM创建右侧图像。显示每个阴影贴图的纹素的投影。 两个阴影贴图具有相同的分辨率,不同之处在于LiSPSM对光的矩阵进行改造以提供更接近观察者的更高采样率。(图片由维也纳科技大学Daniel Scherzer提供。)

还有另一种方法可以创建灯光的采样模式,使其更接近相机的模式。这是通过改变场景投影到光的方式来完成的。通常我们认为视图是对称的,视图矢量位于平截头体的中心。然而,视图方向仅定义视图平面,但不定义哪些像素被采样。定义平截头体的窗口可以在该平面上移动,倾斜或旋转,从而创建四边形,从而提供世界与视图空间的不同映射。四边形仍然以规则的间隔进行采样,因为这是线性变换矩阵的性质并通过GPU实现。可以通过改变光的视图方向和视窗的边界来修改采样率。参见图7.17。

(图7.16)在左边,灯几乎在头顶上。由于与眼睛视图相比分辨率较低,阴影的边缘有点粗糙。在右侧,光线靠近地平线,因此每个阴影纹理像素水平覆盖更多的屏幕区域,因此产生更多的锯齿状边缘。 (Github上由TheRealMJP的Shadows程序生成的图像。)

将光的视图映射到眼睛的位置有22个自由度。对这种解决方案空间的探索导致了几种不同的算法,这些算法试图将光的采样率更好地与眼睛的采样率相匹配。方法包括透视阴影贴图(perspective shadow maps ,PSM),梯形阴影贴图(trapezoidal shadow maps,TSM)和光空间透视阴影贴图(light space perspective shadow maps ,LiSPSM)。有关示例,请参见图7.15和图7.26。此类技术称为透视变形方法(perspective warping)。

这些矩阵变形算法的一个优点是除了修改光的矩阵之外不需要额外的工作。每种方法都有自己的优点和缺点,因为每种方法都可以帮助匹配某些几何情况的采样率和照明情况的采样率,但其他方法的这些速率较差。Lloyd等人分析了PSM,TSM和LiSPSM之间的等价性,用这些方法很好地概述了采样和混叠问题。当光的方向垂直于视图的方向(例如,在头顶)时,这些方案工作得最好,因为透视变换然后可以被移位以使更多的样本更靠近眼睛。

(图7.17)对于顶灯,左侧,地面上的采样与眼睛的速率不匹配。通过改变光的视图方向和右侧的投影窗口,采样率偏向于具有更接近眼睛的更高密度的纹理像素。

当光线位于相机前方并指向它时,矩阵变形技术无法提供帮助。 这种情况被称为dueling frusta,或更通俗地称为“车灯前的鹿”。眼睛附近需要更多的阴影贴图样本,但线性变形只能使情况变得更糟。不稳定的质量等问题,使得这种方法失宠。

在观察者所在位置添加更多样本的想法很好,导致算法为给定视图生成多个阴影贴图。当Carmack在Quakecon 2004的主题演讲中描述时,这个想法首次产生了明显的影响。Blow独立实现了这样一个系统。想法很简单:生成一组固定的阴影贴图(可能在不同的分辨率下),覆盖场景的不同区域。在Blow的方案中,四个阴影贴图嵌套在观察者周围。通过这种方式,可以为附近的物体提供高分辨率的地图,远离这些物体的分辨率会下降。Forsyth提出了一个相关的想法,为不同的可见对象集生成不同的阴影贴图。在他的设置中,避免了处理跨越两个阴影贴图之间边界的对象的过渡的问题,因为每个对象具有与其相关联的一个且仅一个的阴影贴图。Flagship工作室开发了一个融合这两个想法的系统。一个阴影贴图用于附近的动态对象,另一个阴影贴图用于观察者附近的静态对象的grid section,第三个阴影贴图用于整个场景中的静态对象。每帧生成第一个阴影贴图。由于光源和几何形状是静态的,因此其他两个只能生成一次。虽然所有这些特定的系统现在已经相当陈旧,但是针对不同对象和情况的多个映射的想法,一些预计算的和一些动态的,从那时起成为了开发算法的共同主题。

2006年,Engel,Lloyd等人和Zhang等人分别研究了一种相同的基本思想。这个想法是通过平行于视图方向的切片将视锥体的体积分成几个部分。见图7.18。随着深度的增加,每个连续的体积大约是前一个体积的深度范围的两到三倍。 对于每个视图体积,光源可以形成紧密限制它的平截头体,然后生成阴影贴图。通过使用纹理图集或数组,可以将不同的阴影贴图视为一个大的纹理对象,从而最大限度地减少缓存访问延迟。获得的质量改进的比较如图7.19所示。Engel这个算法的名称,级联阴影贴图(cascaded shadow maps,CSM),比Zhang的术语,平行分割阴影贴图(parallel-split shadow maps)更常用,但两者都出现在文献中并且实际上是相同的。

这种类型的算法很容易实现,可以覆盖具有合理结果的大型场景区域,并且是健壮的。dueling frusta问题可以通过以更接近眼睛的更高速率采样来解决,并且没有严重的最坏情况问题。由于这些优势,级联阴影映射在许多应用程序中使用。

(图7.18)在左侧,眼睛的视锥体被分成四个体积。在右侧,为体积创建边界框,这些边框决定了四个阴影贴图中每个阴影贴图为定向光渲染的体积。

7.19

(图7.19)在左侧,场景的宽可视区域导致2048×2048分辨率的单个阴影贴图显示透视锯齿。在右侧,沿视轴放置的四个1024×1024阴影贴图显着提高了质量。在嵌入的红色框中显示围栏前角的缩放。(图片由香港中文大学张帆提供。)

7.20

(图7.20)阴影级联可视化。紫色,绿色,黄色和红色代表最近到最远的级联。(图片由Unity Technologies提供。)

虽然可以使用透视变形将更多样本打包到单个阴影贴图的细分区域,但一般是为每个级联使用单独的阴影贴图。 如图7.18所示,图7.20从观察者的角度显示,每个地图覆盖的区域可以变化。较近的阴影贴图的较小视图体积可在需要它们的地方提供更多样本。确定z-深度范围如何在地图之间分配——称为z分区(z-partitioning)。一种方法是对数分区,其中每个级联映射使远距离与近距离平面距离的比率相同:

$$
r=\sqrt[c]\frac fn\tag{7.5}
$$

其中n和f是整个场景的近和远平面,c是级联图的数量,r是得到的比率。例如,如果场景最近的物体距离1米,最大距离为1000米,我们有三个级联图,则$r =\sqrt[3]{1000}=10$。则近平面和远平面距离最近的视图为1和10,下一个间隔是10到100来保持这个比例,最后一个是100到1000米。初始近深度对此分区有很大影响。如果近深度仅为0.1米,则10000的立方根为21.54,相当高的比例,例如0.1到2.154到46.42到1000。这意味着每个阴影贴图必须覆盖更大的区域,降低了精度。在实践中,这种分区为靠近近平面的区域提供了相当大的分辨率,如果该区域中没有物体则浪费。避免这种不匹配的一种方法是将分区距离设置为对数和等距离分布的加权混合,但如果我们能够确定场景的视图边界,那将更好。

挑战在于设置近平面。如果距离眼睛太远,则该平面可能会夹住物体,这是一种非常糟糕的失真。对于一个镜头,艺术家可以提前精确设置此值,但对于交互式环境,问题更具挑战性。 Lauritzen等人提出样本分布阴影图(sample distribution shadow maps,SDSM),它使用前一帧的z深度值,通过两种方法之一确定更好的分区。

第一种方法是查看z深度的最小值和最大值,并使用它们来设置近平面和远平面。这是使用GPU上的缩减操作(reduce operation)执行的,计算着色器或者一个其他着色器分析一系列更小的缓冲区,使用输出缓冲区返回作为输入,直到剩下1×1缓冲区。通常,将值增加一点以调整场景中对象的移动速度。除非采取纠正措施,否则从屏幕边缘进入的附近物体仍可能导致帧出现问题,但在下一个帧中将很快得到纠正。

第二种方法还是分析深度缓冲区的值,制作一个称为直方图(histogram)的图形,记录沿该范围的z深度分布。除了找到紧密的近和远平面之外,图形中可能还有其中根本没有任何物体的间隙。通常添加到此类区域的任何分区平面都可以捕捉到实际存在的对象,从而为级联映射集提供更多z深度精度。

在实践中,第一种方法是通用的,是快速的(通常每帧在每1毫秒范围内),并给出了良好的结果,因此它已被多种应用所采用。 见图7.21。

(图7.21)深度边界的影响。在左侧,没有使用特殊处理来调整近处和远处平面。右侧,SDSM用于查找更严格的边界。请注意每个图像左边缘附近的窗口框架,二楼花箱下方的区域以及一楼的窗口,由于松散的视图边界而导致的欠采样会导致失真。指数阴影贴图用于渲染这些特定图像,但提高深度精度的想法对于所有阴影贴图技术都很有用。(图片由Ready at Dawn Studios提供,版权归Sony Interactive Entertainment所有。)

由于这种方法很受欢迎,在提高效率和质量方面都付出了相当大的努力。如果阴影贴图的平截头体内没有任何变化,则不需要重新计算该阴影贴图。对于每个灯光,阴影投射物的列表可以通过查找哪些对象对灯光可见而预先计算,这些对象可以在接收物上投射阴影。由于很难感知阴影是否正确,因此可以对级联和其他算法采用一些捷径。一种技术是使用低LOD级别的模型作为代理实际投射阴影。另一种方法是去除微小的遮挡物。对于更远的阴影贴图可能比一帧一次的更新频率更低,因为理论上这些阴影不那么重要。大型的移动物体可能由于这种方式产生失真,因此需要谨慎使用。Day提出了一帧一帧“滚动”远处阴影贴图的想法,其思想是每个静态阴影贴图的大部分是可重复使用的帧,并且只有条纹可能会改变,因此需要渲染。诸如DOOM(2016)之类的游戏维护着大量的阴影贴图,只重新生成物体移动的图集。可以将更远的级联地图设置为完全忽略动态对象,因为这样的阴影可能对场景贡献很少。在某些环境中,可以使用高分辨率静态阴影贴图代替这些更远的级联,这可以显着减少工作量。稀疏纹理系统(第19.10.1节)可用于单个静态阴影贴图非常庞大的世界。级联阴影贴图可以与烘焙贴图纹理或其他更适合特定情况的阴影技术相结合。Valient的演示值得注意,因为它描述了针对各种视频游戏的不同影子系统定制和技术。第11.5.1节详细讨论了预计算的光影算法。

创建几个单独的阴影贴图意味着为每个阴影贴图运行一组几何图形。在一次通过中将遮挡物渲染到一组阴影贴图的想法已经建立了许多提高效率的方法。几何着色器可用于复制对象数据并将其发送到多个视图。实例几何着色器允许将对象输出到最多32个深度纹理。多视口扩展可以执行诸如将对象渲染到特定纹理阵列切片的操作。第21.3.1节在它们用于虚拟现实的背景下更详细地讨论了这些。视口共享技术的一个可能的缺点是生成的所有阴影贴图的遮挡物必须沿着管线发送,而不是发现与每个阴影贴图相关的集合。

你自己目前正处于世界各地数十亿光源的阴影中。只有少数几个光到达你。在实时渲染中,如果所有灯始终处于活动状态,则具有多个灯的大型场景可能会被计算淹没。如果一个空间体积在视锥体内但对眼睛不可见,则不需要计算遮挡该接收体积的物体。Bittner 等人从眼睛位置使用遮挡剔除(第19.7节)找到所有可见的阴影接收物,然后从光的角度将所有潜在的阴影接收物渲染到模板缓冲遮罩中。该遮罩编码从光中看到哪些可见的阴影接收器。为了生成阴影贴图,它们使用遮挡剔除从光源渲染对象,并使用遮罩来剔除没有接收物的对象。各种剔除策略也适用于灯光。由于辐照度随着距离的平方而下降,因此常见的技术是在一定的阈值距离之后剔除光源。例如,第19.5节中的门户剔除技术可以找到哪些灯影响哪些像素。这是一个活跃的研究领域,因为性能优势可能相当大。


7.5 Percentage-Closer Filtering(百分比近似过滤)

阴影贴图技术的一个简单扩展可以提供伪软阴影。此方法还可以帮助改善分辨率问题,当单个光样本单元覆盖许多屏幕像素时,这些问题会导致阴影看起来像块状。解决方案类似于纹理放大(第6.2.1节)。不是从阴影图中取出单个样本,而是检索四个最近的样本。该技术不在深度本身之间进行插值,而是在与表面深度进行比较的结果之间进行插值。也就是说,将表面的深度与四个纹素深度分别进行比较,然后针对每个阴影图样本确定该点在光或阴影中。然后对这些结果,即阴影为0,对光为1,然后进行双线性插值,以计算光实际对表面位置的贡献程度。这种过滤导致人为柔和的阴影。根据阴影贴图的分辨率,摄像机位置和其他因素,这些半影会发生变化。例如,较高的分辨率使边缘的柔和变窄。尽管如此,一点点半影和平滑总比没有好。

从阴影贴图中检索多个样本并混合结果的这种想法称为百分比近似过滤(percentage-closer filtering,PCF)。区域光产生柔和阴影。到达表面上的位置的光量是从该位置可见的光的面积的比例的函数。PCF试图通过反转过程来近似点源光(或平行光)的软阴影。不是从表面位置找到光的可见区域,而是从原始位置附近的一组表面位置找到准点光的可见性。见图7.22。名称“百分比 - 近距离过滤”指的是最终目标,即找到对光可见的样本的百分比。这个百分比用于遮蔽表面的光量。

(图7.22)在左侧,来自区域光源的棕色线条显示半影形成的位置。对于接收物上的单个点p,可以通过测试区域光表面上的一组点并找出未被任何遮挡物阻挡的点来计算所接收的照明量。在右边,点光源不会产生半影。PCF通过反转过程来近似区域光的影响:在给定位置,它在阴影图上的可比较区域上进行采样,以得出照亮多少样本的百分比。红色椭圆显示阴影贴图上采样的区域。理想情况下,该盘的宽度与接收物和遮挡物之间的距离成比例。

在PCF中,位置靠近表面位置,大约相同的深度,但在阴影贴图上的不同纹理元素位置处生成。 检查每个位置的可见性,然后将这些得到的布尔值(点亮或不亮)混合以获得柔和阴影。 请注意,此过程是非物理过程:此过程不依赖于直接对光源进行采样,而是依赖于在曲面本身上进行采样的想法。 到遮挡物的距离不会影响结果,因此阴影具有相似大小的半影。 尽管如此,该方法在许多情况下提供了合理的近似。

一旦确定了要采样的区域的宽度,重要的是以避免混叠伪像的方式进行采样。如何对附近的阴影贴图位置进行采样和过滤有很多变化。变量包括要采样的区域的宽度,要使用的样本数量,采样模式以及如何对结果进行加权。利用功能较少的API,可以通过类似于双线性插值的特殊纹理采样模式来加速采样过程,该模式访问四个相邻位置。不是混合结果,而是将四个样本中的每一个与给定值进行比较,并返回通过测试的比率。但是,以常规网格图案执行最近邻居采样会产生明显的伪像。使用联合双边滤波可以模糊结果,但保留物体边缘可以提高质量,同时避免阴影泄漏到其他表面上。 有关此过滤技术的更多信息,请参见第12.1.1节。

DirectX 10为PCF引入了单指令双线性过滤(single-instruction bilinear filtering)支持,从而提供更平滑的结果。与最近邻采样相比,这提供了相当大的视觉改善,但是来自常规采样的伪像仍然是个问题。最小化网格模式的一种解决方案是使用预先计算的泊松分布模式对区域进行采样,如图7.23所示。该分布将样本扩散出来,使得它们既不相邻也不是规则模式。众所周知,对于每个像素使用相同的采样位置,不管分布如何,都可以产生图案。通过围绕其中心随机旋转样本分布可以避免这种伪影,这会使混叠变成噪声。Castaño发现泊松采样产生的噪声因其平滑,风格化的内容而特别明显。他提出了一种基于双线性采样的高效加权采样方案。

PCF可能会导致自我遮蔽,漏光,acne和Peter Panning变得更糟。假设样本在阴影贴图上不超过一个纹素,斜率刻度偏差完全基于其与光的角度将表面推离光线。通过从表面上的单个位置在更宽的区域中取样,一些测试样品可能被真实表面阻挡。

已经发明并使用了一些不同的附加偏差因子,并且成功地降低了自阴影的风险。Burley描述了偏置锥,其中每个样本朝向与原始样本的距离成比例的光移动。 Burley建议斜率为2.0,并伴有小的恒定偏差。 见图7.24。

(图7.24)额外的阴影偏差方法。对于PCF,在原始样品位置(五个点的中心)周围采集了几个样品。所有这些样品都应该点亮。在左图中,形成偏置锥,样品向上移动。可以增加锥体的陡度以将样品拉到右侧足够近以便点亮,存在增加其他样品(未示出)中真正被遮蔽的光泄漏的风险。在中图中,所有样本都被调整为位于接收器的平面上。这适用于凸面,但在凹面处可能适得其反,如左侧所示。在右图中,正常的偏移使样本沿着表面的法线方向移动,与正常和光线之间的角度的正弦成比例。对于中心样本,可以认为这是移动到原始表面上方的假想表面。这种偏差不仅会影响深度,还会改变用于测试阴影贴图的纹理坐标。

Schüler,Isidoro和Tuft提出了基于观察的技术,即应使用接收物本身的斜率来调整其余样本的深度。在这三者中,Tuft的配方最容易应用于级联阴影贴图。Dou等人进一步完善和扩展这一概念,考虑z深度如何以非线性方式变化。 这些方法假设附近的样本位置在由三角形形成的同一平面上。被称为接收器平面深度偏差或其他类似术语,在许多情况下,该技术可以非常精确,因为该假想平面上的位置确实在表面上,或者如果模型是凸的则在其前面。如图7.24所示,凹陷附近的样本可能会隐藏。 已经使用常数,斜率,接收物平面,视图偏置和正常偏移偏置的组合来解决自阴影的问题,尽管仍然需要对每个环境进行手动调整。

PCF的一个问题是因为采样区域的宽度保持不变,阴影将显得均匀柔和,所有阴影都具有相同的半影宽度。 在某些情况下这可能是可以接受的,但在遮挡物和接收物之间存在接地的情况下看起来是不正确的。见图7.25。


7.6 Percentage-Closer Soft Shadows(百分比近似软阴影)

(图7.25)百分比近似过滤和百分比近似软阴影。在左侧,硬阴影与一点PCF过滤。在中间,恒定宽度柔和的阴影。在右侧,可变宽度的柔和阴影具有适当的硬度,物体与地面接触。(图片由NVIDIA Corporation提供。)

2005年,Fernando发布了一种名为百分比隐形软阴影(PCSS)的有影响力的方法。 它通过搜索阴影贴图上的附近区域来尝试找到所有可能的遮挡物。这些遮挡物与该位置的平均距离用于确定样本区域宽度:

$$
w_{sample}=w_{light}\frac{d_r-d_o}{d_r}\tag{7.6}
$$

其中$d_r$是接收物与光的距离,并进行平均封堵器距离。换句话说,随着平均遮挡物越来越远离接收物并且更靠近光线,样本的表面区域的宽度增大。检查图7.22并考虑移动遮挡物的效果,看看它是如何发生的。图7.2,7.25和7.26显示了示例。

如果找不到遮挡物,则该位置完全点亮,无需进一步处理。类似地,如果位置完全被遮挡,则处理可以结束。否则,继续对感兴趣的区域进行采样并计算光的近似贡献。为了节省处理成本,样本区域的宽度可用于改变采样的数量。 可以实现其他技术,例如,使用较低的采样率来获得不太重要的远距离软阴影。

这种方法的一个缺点是需要对阴影贴图的大小区域进行采样以找到遮挡物。 使用旋转的泊松盘模式可以帮助隐藏欠采样伪像。 Jimenez指出泊松采样在运动中可能是不稳定的,并且发现通过在抖动和随机之间使用函数形成的螺旋图案给出了帧到帧的更好结果。

Sikachev等人详细讨论了使用AMD推出的SM 5.0中的功能更快地实现PCSS,并且通常用它们的名称来称呼,接触硬化阴影(Contact Hardening Shadows,CHS)。 这个新版本还解决了基本PCSS的另一个问题:半影的大小受阴影贴图分辨率的影响。见图7.25。 首先生成阴影贴图的mipmap,然后选择最接近用户定义的世界空间内核大小的mip级别,可以最大限度地减少此问题。采样8×8区域以找到平均阻塞深度,仅需要16个GatherRed()纹理调用。一旦发现半影,较高分辨率的mip级别用于阴影的锐利区域,而较低分辨率的mip级别用于较软区域。

CHS已被用于大量视频游戏,并且研究仍在继续。 例如,Buades等人。 目前可分离的软阴影映射(SSSM),其中对网格进行采样的PCSS过程被分成可分离的部分,并且元素尽可能从像素到像素被重用。

已经证明有助于加速每像素需要多个样本的算法的一个概念是分层最小/最大阴影图。虽然阴影贴图深度通常无法平均,但每个mipmap级别的最小值和最大值都很有用。 也就是说,可以形成两个mipmap,一个保存每个区域中最大的zdepth(有时称为HiZ),一个最小。给定纹素位置,深度和要采样的区域,mipmap可用于快速确定完全亮起和完全阴影的条件。例如,如果纹素的z深度大于为mipmap的相应区域存储的最大z深度,则纹理元素必须处于阴影中——不需要进一步的样本。 这种类型的阴影贴图使得确定光可见度的任务更加有效。

诸如PCF的方法通过对附近的接收物位置进行采样来工作。PCSS的工作原理是找到附近遮挡物的平均深度。这些算法不直接考虑光源的面积,而是对附近的表面进行采样,并受阴影贴图的分辨率影响。PCSS背后的主要假设是平均阻滞剂是对半影大小的合理估计。当两个遮挡物(例如路灯和远处的山)在一个像素处部分遮挡同一表面时,这种假设被破坏并且可能导致伪影。理想情况下,我们希望确定从单个接收器位置可以看到多少区域光源。一些研究人员已经使用GPU探索了反投影。这个想法是将每个接收物的位置视为视点,将区域光源视为视图平面的一部分,并将遮挡物投射到该平面上。Schwarz和Stamminger以及Guennebaud等人总结以前的工作并提供自己的改进。 Bavoil等采用不同的方法,使用深度剥离来创建多层阴影贴图。反投影算法可以提供出色的结果,但每像素的高成本(到目前为止)意味着它们在交互式应用程序中尚未被采用。


7.7 Filtered Shadow Maps

允许过滤生成的阴影贴图的一种算法是Donnelly和Lauritzen的方差阴影图(variance shadow map,VSM)。该算法将深度存储在一个贴图中,并将深度的平方存储在另一个贴图中。 生成映射时可以使用MSAA或其他抗锯齿方案。 这些贴图可以模糊,mipmap,放在SAT或使用任何其他方法。可以将这些贴图视为可过滤纹理是一个巨大的优势,因为从它们检索数据时可以使用所有采样和过滤技术。

我们将在这里深入描述VSM,以了解这个过程是如何工作的;此外,这种类型的测试也被用于此类算法中的所有方法。有兴趣了解该领域的读者应该访问相关的参考文献,我们也推荐Eisemann等人的书。

首先,对于VSM,深度图在接收物的位置被采样(仅一次)以返回最近的光遮挡物的平均深度。 当称为first moment的平均深度$M_1$,大于阴影接收物t上的深度时,接收物被认为是完全在光下。 当平均深度小于接收物的深度时,使用以下等式:

$$
p_{max}(t)=\frac{\sigma^2}{\sigma^2+(t-M_1)^2}\tag{7.7}
$$

其中$p_{max}$是光照样本的最大百分比,$σ^2$是方差,t是接收器深度,$M_1$是阴影图中的平均预期深度。 深度平方阴影贴图的样本$M_2$(称为second moment)用于计算方差:

$$
\sigma^2=M_2-M_1^2\tag{7.8}
$$

值$p_{max}$是接收物的可见性百分比的上限。实际照明百分比p不能大于该值。 这个上限来自切比雪夫不等式(Chebyshev’s inequality)的变体。该方程试图使用概率理论估计表面位置上遮挡物的分布有多少超出表面与光的距离。Donnelly和Lauritzen表明,对于固定深度的平面遮挡物和平面接收物,$p=p_{max}$,因此公式7.7可以用作许多真实阴影情况的良好近似。

Myers说明了为什么这种方法有效。在阴影边缘处,区域上的方差增加。深度差异越大,方差越大。然后,$(t-M_1)^2$项是可见性百分比的重要决定因素。如果该值略高于零,则这意味着平均遮挡物深度比接收物稍微接近光,并且$p_{max}$接近1(完全点亮)。这将发生在半影的完全照亮的边缘。移动到半影中,平均遮挡物深度更接近光线,因此该项变得更大并且$p_{max}$下降。 同时,方差本身在半影内发生变化,从几乎为零的边缘变为最大的方差,其中遮挡物的深度不同并且平均分享该区域。 这些项相互抵消,在半影上产生线性变化的阴影。有关与其他算法的比较,请参见图7.26。

图7.26。在左上角,标准阴影贴图。 右上角,透视阴影映射,增加观察者附近的阴影贴图纹理密度。 左下角,百分比近似软阴影,当遮挡物与接收物的距离增加时软化阴影。右下角,方差阴影映射具有恒定的软阴影宽度,每个像素用单个方差图样本着色。 (图片由Nico Hempe,Yvonne Jung和Johannes Behr提供。)

方差阴影映射的一个重要特征是它可以以优雅的方式处理由于几何形状引起的表面偏差问题。 Lauritzen推导出如何使用曲面的斜率来修改second moment的值。数值稳定性的偏差和其他问题可能是方差映射的问题。例如,公式7.8从一个近似值中减去一个大值。这种类型的计算导致底层数值表示的精度缺乏问题被放大。使用浮点纹理有助于避免此问题。

整体上讲,由于有效利用了GPU的优化纹理功能,VSM花费的时间成本也换来了质量的显着提高。PCF需要更多的样本,更多的时间来避免产生更柔和的阴影时的噪点,但VSM只需要一个高质量的样本即可使用,以确定整个区域的效果并产生平滑的半影。这种能力意味着在算法的限制范围内,阴影可以在没有额外成本的情况下任意地变软。

与PCF一样,过滤内核的宽度决定了半影的宽度。通过找到接收物和最接近的遮挡物之间的距离,可以改变内核宽度,从而给出令人信服的柔和阴影。Mipmapped样本是半影的覆盖率差估计,宽度缓慢增加,产生四四方方的伪影。Lauritzen详述了如何使用SAT来提供更好的阴影。一个例子如图7.27所示。

(图7.27)方差阴影映射,其中到光源的距离从左到右增加。(来自NVIDIA SDK 10样本的图片,由NVIDIA Corporation提供。)

当两个或多个遮挡物覆盖接收物,并且一个遮挡物靠近接收物时,沿着半影区域的一个地方差异阴影映射分解。概率论中的切比雪夫不等式将产生与正确光百分比无关的最大光值。最接近的遮挡物,通过仅部分隐藏光,抛出等式的近似值。这导致轻微光泄漏(也叫出血),其中完全被遮挡的区域仍然接收光。见图7.28。通过在较小区域上采集更多样本,可以解决此问题,将方差阴影映射转换为PCF形式。与PCF一样,速度和性能会受到影响,但对于阴影深度复杂度较低的场景,方差映射效果很好。Lauritzen提供了一种艺术家控制的方法来改善问题,即将低百分比视为完全阴影并将剩余的百分比范围重新映射到0%到100%。这种方法可以减少轻度溢出,但总体上会缩小半影。虽然光线溢出是一个严重的限制,但VSM有利于从地形产生阴影,因为这种阴影很少涉及多个遮挡物。

由于能够使用过滤技术快速生成平滑阴影,引起了很多人对过滤阴影映射的极大兴趣;主要的挑战是解决各种出血问题。Annen等人提出了卷积阴影贴图(convolution shadow map)。扩展了Soler和Sillion的平面接收物算法背后的思想,其思想是在傅立叶展开中对阴影深度进行编码。与方差阴影映射一样,可以过滤此类映射。该方法收敛于正确的结果,因此减少了漏光问题。

(图7.28)左侧,应用于茶壶的方差阴影贴图。 右侧,一个三角形(未显示)在茶壶上投下阴影,在地面阴影中造成令人反感的瑕疵。(图片由Marco Salvi提供。)

卷积阴影映射的一个缺点是需要计算和访问几个项,这大大增加了执行和存储成本。Salvi和Annen等人同时并独立地提出了使用基于指数函数的单个术语的想法。 称为指数阴影贴图(exponential shadow map,ESM)或指数方差阴影贴图(exponential variance shadow map,EVSM),此方法将深度的指数与其second moment一起保存到两个缓冲区中。指数函数更接近于阴影贴图执行的阶梯函数(无论是否在光照下),因此这可以显着减少出血伪影。 它避免了卷积阴影映射具有的另一个问题,称为ringing,其中微小的光泄漏可能发生在刚刚超过原始封堵器深度的特定深度处。

存储指数值的限制是second moment可能变得非常大,以至使用浮点数会超出范围。为了提高精度,并允许指数函数更陡峭地下降,可以生成z深度,使得它们是线性的。

由于其与VSM相比具有更高的质量,并且与卷积图相比具有更低的存储性和更好的性能,因此指数阴影贴图方法引起了三种滤波方法的兴趣。Pettineo还注意到其他一些改进,例如使用MSAA改进结果和获得有限透明度的能力,并描述了如何使用计算着色器提高过滤性能。

最近,Peters和Klein引入了时刻阴影贴图(moment shadow mapping)。 它提供更好的质量,但代价是使用四个或更多moment,增加了存储成本。通过使用16位整数来存储moments,可以降低这种成本。Pettineo实现并将这种新方法与ESM进行比较,提供了探索许多变体的代码库。

级联阴影贴图技术可应用于过滤后的贴图以提高精度。级联ESM优于标准级联映射的优点是可以为所有级联设置单个偏置因子。Chen和Tatarchuk详细介绍了级联ESM遇到的各种漏光问题和其他工件,并提出了一些解决方案。

过滤后的贴图可以被认为是一种廉价的PCF形式,需要很少的样本。与PCF一样,这种阴影具有恒定的宽度。这些过滤后的方法都可以与PCSS结合使用,以提供可变宽度的半影。moment shadow mapping的扩展还包括提供光散射和透明效果的能力。


7.8 Volumetric Shadow Techniques(体积阴影技术)

透明物体会衰减并改变光的颜色。对于某些透明对象集,可以使用与第5.5节中讨论的类似的技术来模拟这些效果。例如,在某些情况下,可以生成第二类阴影图。透明对象将呈现给它,并存储最接近的深度和颜色或alpha覆盖范围。如果接收器未被不透明阴影贴图阻挡,则测试透明度深度图,如果被遮挡,则根据需要检索颜色或覆盖范围。 这个想法让人联想到7.2节中的阴影和光线投影,存储的深度避免了投影到透明物体和光线之间的接收物上。 这些技术不能应用于透明对象本身。

自阴影对于物体(如头发和云)的真实渲染至关重要,它们都是小的或半透明的。单深度阴影贴图不适用于这些情况。Lokovic和Veach首先提出了深阴影贴图(deep shadow maps)的概念,其中每个阴影贴图纹素都存储了光线如何随深度下降的函数。该函数通常由不同深度处的一系列样本近似,每个样本具有不透明度值。地图中包含给定位置深度的两个样本用于查找阴影效果。GPU上的挑战在于有效地生成和计算这些功能。这些算法使用类似的方法,并遇到一些与顺序无关的透明度算法(第5.5节)中发现的类似挑战,例如忠实代表每个函数所需的数据的紧凑存储。

Kim和Neumann是第一个提出基于GPU的方法的人,他们称之为不透明阴影贴图(opacity shadow maps)。 仅存储固定深度集合处生成的不透明物体。Nguyen和Donnelly给出了这种方法的更新版本,产生了如图17.2所示的图像。然而,深度切片都是平行且均匀的,因此需要许多切片来隐藏由于线性插值导致的切片不透明度伪像。 Yuksel和Keyser通过创建更接近模型形状的不透明度贴图来提高效率和质量。这样做可以减少所需的层数,因为每层的计算对最终图像更重要。

(图17.2)

为了避免不得不依赖于固定切片设置,提出了更多自适应技术。 Salvi等引入了自适应体积阴影贴图(adaptive volumetric shadow maps),其中每个阴影贴图纹素都存储不透明度和层深度。像素着色器操作用于在光栅化时有损压缩数据流(表面不透明度)。这避免了需要无限量的内存来收集所有样本并在集合中处理它们的问题。该技术类似于深阴影贴图,但压缩步骤在像素着色器中即时完成。将函数表示限制为小的,固定数量的存储的不透明度/深度,使得GPU上的压缩和检索更有效。成本高于简单混合,因为需要读取,更新和回写曲线,并且它取决于用于表示曲线的点数。在这种情况下,该技术还需要支持UAV和ROV功能的最新硬件(3.8节结束)。有关示例,请参见图7.29。

(图7.29)使用自适应体积阴影贴图进行头发和烟雾渲染。

自适应体积阴影映射方法用于游戏GRID2中的逼真烟雾渲染,平均成本低于2毫秒/帧。 Fürst等人。 描述并提供用于实现视频游戏的深阴影贴图的代码。它们使用链接列表来存储深度和alpha,并使用指数阴影贴图在点亮和阴影区域之间提供软转换。

阴影算法的探索仍在继续,各种算法和技术的综合变得越来越普遍。 例如,Selgrad等人。研究使用链表存储多个透明样本,并使用带有分散写入的计算着色器来构建地图。他们的工作使用深阴影图概念,以及过滤后的地图和其他元素,这为提供高质量的柔和阴影提供了更通用的解决方案。


7.9 Irregular Z -Buffer Shadows(不规则的Z缓冲阴影)

由于一些原因,很多阴影贴图方法都很受欢迎。它们的成本是可预测的,并且增加的场景大小时可以很好地扩展,最坏情况与图元数量成线性关系。它们很好地映射到GPU上,因为它们依靠光栅化来定期对灯光的世界观进行采样。 然而,由于这种离散采样,出现问题是因为眼睛看到的位置不与光看到的位置一一对应。 当光对表面的采样频率低于眼睛时,会出现各种混叠问题。 即使采样率相当,也存在偏差问题,因为表面采样的位置与眼睛看到的位置略有不同。

阴影体积提供精确的分析解决方案,因为灯光与曲面的交互会产生一组三角形定义任何给定位置是亮起还是阴影。在GPU上实现算法时,不可预测的成本是一个严重的缺点。近年来探索的改进令人着迷,但尚未在商业应用中拥有“存在证据”。

另一种分析阴影测试方法可能具有长期潜力:射线追踪。 在第11.2.2节中详细描述,基本思想很简单,特别是对于阴影。从接收物位置向光线射出光线。 如果发现任何阻挡光线的物体,则接收物处于阴影中。大多数快速光线跟踪物的代码专用于生成和使用分层数据结构,以最小化每条光线所需的对象测试数量。每帧为动态场景构建和更新这些结构是一个已有数十年历史的主题和持续研究的领域。

另一种方法是使用GPU的光栅化硬件来查看场景,但是不仅仅是z深度,也存储关于光的每个网格单元中的遮挡物的边缘信息。例如,想象在每个阴影贴图纹素处存储与网格单元重叠的三角形列表。这样的列表可以通过保守光栅化生成,其中如果三角形的任何部分与像素重叠,则三角形生成片段,而不仅仅是像素的中心(第23.1.2节)。这种方案的一个问题是通常需要限制每个纹素的数据量,这反过来可能导致确定每个接收器位置的状态的不准确性。鉴于GPU的现代链表原则,每个像素可以存储更多数据。但是,除了物理内存限制之外,在每个texel的列表中存储可变数量的数据的问题是GPU处理可能变得非常低效,因为单个warp可能有一些需要检索和处理许多项的片段线程,其余的线程都空闲,没有工作要做。设计一个着色器以避免由于动态“if”语句和循环导致的线程分歧对性能至关重要。

选择在阴影贴图中存储三角形或其他数据还是测试接收物对于他们的位置需翻转问题,存储接收器位置,然后针对每个三角形测试。这种保存接收者位置的概念,首先由Johnson等人研究。Aila和Laine称之为不规则的z缓冲区(IZB)。该名称有点误导,因为缓冲区本身具有法线,原始阴影贴图的形状。相反,缓冲区的内容是不规则的,因为每个阴影贴图纹素将在其中存储一个或多个接收物的位置,或者可能根本不存在。 见图7.30。

(图7.30)不规则的z缓冲区。在左上角,来自眼睛的视图在像素中心处生成一组点。示出了形成立方体面的两个三角形。在右上角,这些点是从灯光的视图中显示的。在左下角,加上了阴影贴图网格。对于每个纹素,生成其网格单元内所有点的列表。在右下角,通过保守地光栅化对红色三角形执行阴影测试。接触到的每个纹理元素,以浅红色显示,其列表中的所有点都针对三角形进行测试以获得光的可见性。(底层光栅图像由Timo Aila和Samuli Laine提供。)

使用Sintorn等人和Wyman等人提出的方法,多次通过算法创建IZB并测试其内容以获得来自光的可见性。首先,从眼睛渲染场景,以找到从眼睛看到的表面的z深度。这些点被转换为灯光的场景视图,并且由光线的平截头体形成了紧密的边界。然后将这些点存储在光的IZB中,每个点放置在其相应纹理元素的列表中。请注意,某些列表可能是空的,即灯光可以看到但没有眼睛看到的表面。将遮挡物保守光栅化到灯的IZB以确定是否隐藏任何点,因此在阴影中。保守光栅化确保即使三角形不覆盖浅纹理像素的中心,也会对它可能重叠的点进行测试。

可见性测试发生在像素着色器中。测试本身可以看作是光线跟踪的一种形式。光线是从图像点到光的位置生成的。 如果一个点在三角形内并且比三角形的平面更远,则它是隐藏的。 一旦所有遮挡物被光栅化,光能见度结果用于遮蔽表面。该测试也称为视锥体跟踪,因为三角形可以被认为是定义视锥体,检查包含在其体积中的点。

仔细编码对于使这种方法与GPU良好协作至关重要。Wyman等人注意到他们的最终版本比最初的原型快两个数量级。这种性能提升一部分来源于直接的算法改进,例如剔除表面法线背离光线的图像点(因此总是不亮)并避免为空纹理像素生成碎片。其他性能提升来自于改进GPU的数据结构,以及通过在每个纹理元素中使用短的,相似长度的点列表来最小化线程差异。图7.30显示了一个低分辨率阴影贴图,其中包含用于说明目的的长列表。理想的是每个列表一个图像点。分辨率越高,列表越短,但也会增加遮挡物生成的需要计算的碎片数量。

从图7.30的左下图可以看出,由于透视效应,地面上的可见点密度在左侧比右侧高得多。 使用级联阴影贴图可以使更多的光照贴图分辨率更接近眼睛,从而有助于降低这些区域中的列表尺寸。

这种方法避免了其他方法的采样和偏差问题,并提供了完美的阴影。出于美学和感知的原因,通常需要柔和的阴影,但是附近的遮挡物可能存在偏差问题,例如Peter Panning。Story和Wyman探索混合阴影技术。核心思想是使用遮挡距离来混合IZB和PCSS阴影,当遮挡物靠近时使用硬阴影结果,当更远时使用柔和阴影。见图7.31。阴影质量通常对附近的对象最重要,因此通过仅在选定的子集上使用此技术可以降低IZB成本。 该解决方案已成功用于video game。本章从这样的图像开始,如图7.2所示。


7.10 Other Applications(其他应用)

(图7.31)在左侧,PCF为所有对象提供均匀柔化的阴影。在中间,PCSS通过距离遮挡物的距离来软化阴影,但是与箱子左角重叠的树枝阴影会产生伪影。在右侧,来自IZB的锐利阴影与来自PCSS的柔和混合产生了改善的结果。(图片由育碧提供)

将阴影贴图视为定义一定体积的空间,将光与暗分离,也可以帮助确定要遮挡物体的哪些部分。Gollent描述了CD Projekt的地形阴影系统如何计算每个区域仍然被遮挡的最大高度,然后可以用来不仅遮蔽地形,还遮挡场景中的树木和其他元素。为了找到每个高度,为太阳渲染可见区域的阴影贴图。然后检查每个地形高度场位置以获得来自太阳的可见性。如果在阴影中,通过将世界高度增加固定步长直到太阳进入视野然后执行二分搜索来估计太阳首次可见的高度。换句话说,我们沿着垂直线行进并迭代以缩小与阴影贴图的表面相交的位置,该表面将光与暗分开。插入相邻高度以在任何位置找到该遮挡高度。图7.32中可以看到用于地形高度场软阴影的这种技术示例。

值得一提的最后一种方法是渲染屏幕空间阴影(screen-space shadows)。由于分辨率有限,阴影贴图通常无法在小特征上产生准确的遮挡。在渲染人脸时,尤其严重,因为我们特别容易注意到。例如,发光的鼻孔(如果没有打算)看起来很不和谐。虽然使用更高分辨率的阴影贴图或仅针对感兴趣区域的单独阴影贴图可以提供帮助,但另一种可能性是利用已存在的数据。在大多数现代渲染引擎中,在渲染过程中可以使用来自相机视角的深度缓冲,来自较早的预处理。存储在其中的数据可以视为高度场。通过对该深度缓冲器进行迭代采样,我们可以执行射线行进过程(第6.8.1节)并检查朝向光的方向是否未被遮挡。虽然价格昂贵,因为它涉及重复采样深度缓冲,但这样做可以为切割场景中的特写镜头提供高质量的结果,其中花费额外几毫秒通常是合理的。该方法由Sousa等人提出。并且今天在许多游戏引擎中使用。

(图7.32)地形点亮了第一次看到太阳的高度,为每个高度位置计算。请注意阴影边缘的树木是如何正确遮蔽的。

☆总结一下这一章,阴影贴图是迄今为止用于投射到任意表面形状上的阴影的最常用算法。级联阴影贴图可在大面积(例如室外场景)中投射阴影时提高采样质量。通过SDSM找到近平面的良好最大距离可以进一步提高精度。百分比近似滤波(PCF)为阴影提供了一些柔和度,百分比近似软阴影(PCSS)及其变体提供了接触处的硬化,IZS可以提供精确的硬阴影。过滤后的阴影贴图可提供快速的软阴影计算,并且当遮挡物远离接收物时也能正常工作,就像地形一样。最后,屏幕空间技术可以用于额外的精度,但成本显着。

在本章中,我们将重点放在当前应用程序中使用的关键概念和技术上。每个都有自己的优势,选择哪个取决于世界大小,组成(静态内容与动画),材质类型(不透明,透明,头发或烟雾),灯光的数量和类型(静态或动态;本地或远程;点光,聚光灯还是区域光),以及诸如底层纹理是否可以隐藏失真。GPU功能不断发展和完善,因此我们期望在未来几年内继续看到能够很好地映射的新硬件算法。例如,第19.10.1节中描述的稀疏纹理技术已应用于阴影贴图存储以提高分辨率。在一种创造性的方法中,Sintorn,Kämpe和其他人探索了将光的二维阴影图转换为三维体素集(参见13.10节)的想法。使用体素的一个优点是它可以被分类为亮的还是在阴影中的,因此需要最少的存储空间。高度压缩的稀疏体素八叉树表示存储大量灯光和静态遮挡物的阴影。 Scandolo等将他们的压缩技术与使用双阴影贴图的基于区间的方案相结合,提供更高的压缩率。 Kasyan使用体素锥体跟踪(cone-traced shadows,第13.10节)从区域光源生成柔和阴影。有关示例,请参见图7.33。更多锥形跟踪阴影显示在第585页的图13.33中。

Further Reading and Resources

我们在本章中的重点是基本原理以及影子算法需要什么样的质量和可预测的质量和性能才能对交互式渲染有用。 我们已经避免对这个渲染领域的研究进行详尽的分类,因为有两个文本可以解决这个问题。 Eisemann等人的“实时阴影”一书。 直接关注交互式渲染技术,讨论各种算法及其优势和成本。SIGGRAPH 2012课程提供了本书的摘录,同时还添加了对新作品的参考。 他们的SIGGRAPH 2013课程的演示文稿可在网站www.realtimeshadows.com上找到。Woo和Poulin的书“Shadow Algorithms Data Miner”概述了用于交互式和批量渲染的各种阴影算法。 这两本书都提供了该领域数百篇研究论文的参考。

Tuft的文章对常用的阴影贴图技术和所涉及的问题进行了很好的概述。 Bjørge提供了一系列适用于移动设备的流行阴影算法,以及比较各种算法的图像。Lilley的演示文稿对实际阴影算法进行了全面而广泛的概述,重点是GIS系统的地形渲染。Pettineo和Castaño的博客文章对于他们的实用技巧和解决方案以及演示代码库特别有价值。见Scherzer等 有关专门针对硬阴影的工作的简短摘要。 Hasenfratz等人对软阴影算法的调查已过时,但涵盖了广泛的早期工作。

(chapter 7 end.)