渲染

Real-time Rendering 第七章 阴影

Spread the love

阴影对于创建真实的图像和为用户提供有关对象位置的视觉提示非常重要。本章主要介绍阴影计算的基本原理,并介绍最重要和最流行的实时算法。我们还简要讨论了不太流行但包含重要原则的方法。我们不在本章花时间讨论所有的选项和方法,因为有两本全面的书深入研究了阴影领域[412,1902]。相反,我们专注于调查自发表以来出现的文章和演示文稿,偏向于经过实战检验的技术。

本章使用的术语如图7.1所示,其中遮挡器是将阴影投射到接收器上的对象。精确光源,就是那些没有大小的光源,只产生完全阴影的区域,有时叫做硬阴影。如果使用面积或体积光源,则会产生柔和的阴影。每个阴影可以有一个完全阴影的区域,称为本影(umbra),和一个部分阴影的区域,称为半影(penumbra)。柔和的阴影可以通过模糊的阴影边缘来识别。但是,需要注意的是,它们通常不能通过使用低通滤波器模糊硬阴影的边缘来正确渲染。如图7.2所示,阴影投射的几何形状离接收器越近,正确的软阴影就越清晰。软阴影的本影区域不等于由点状光源产生的硬阴影。相反,软阴影的本影区域会随着光源的增大而减小,如果光源足够大,接收器距离遮挡器足够远,它甚至可能消失。软阴影通常是可取的,因为半影边缘让观众知道,阴影确实是一个阴影。硬边阴影通常看起来不那么真实,有时会被误解为实际的几何特征,比如表面的折痕。然而,硬阴影比软阴影渲染更快。

比拥有半影更重要的是首先要有阴影。没有一些阴影作为视觉线索,场景往往难以令人信服,更难以理解。正如Wanger[1846]所示,有一个不准确的阴影通常比没有更好,因为眼睛对阴影的形状相当宽容。例如,在地板上应用一个模糊的黑色圆圈作为纹理,可以将字符锚定在地面上。

在接下来的部分中,我们将超越这些简单的建模阴影,并介绍从场景中的遮挡器实时自动计算阴影的方法。第一部分处理投射到平面表面的阴影的特殊情况,第二部分介绍更一般的阴影算法,即,将阴影投射到任意表面。硬阴影和软阴影都将被涉及到。最后,提出了适用于各种阴影算法的优化技术。

7.1 平面阴影 —— Planar Shadows

当物体将阴影投射到平面上时,就会出现一个简单的阴影。本节介绍了几种平面阴影的算法,每种算法在阴影的柔度和真实感方面都有所不同。

7.1.1 投影阴影 —— Projection Shadows

在这个方案中,第二次渲染三维对象以创建阴影。可以导出一个矩阵,将一个对象的顶点投影到一个平面上[162,1759]。考虑图7.3中的情况下,光源位于l的顶点投影是v,和预计的顶点是p。我们将推导出投影矩阵的特殊情况投影平面是y = 0,然后将这个结果会推广到任意平面。

我们从求x坐标的投影开始。由图7.3左半部分的相似三角形得到

z坐标的计算方法相同:pz = (lyvz−lzvy) / (ly−vy), y坐标为零。现在这些方程可以转化为投影矩阵M:

很容易证明Mv = p,这意味着M确实是投影矩阵。

在一般情况下,平面上的阴影应该不是平面y = 0,而是π:n·x + d = 0。这种情况如图7.3的右侧所示。目的是再次找到一个项目的矩阵v p。为此,在l发出射线,通过v,π平面交叉。这就得到了投影点p:

该方程也可以转化为投影矩阵,如式7.4所示,满足Mv = p:

如所料,这个矩阵变为式7.2中的矩阵,如果平面为y = 0,即n = (0,1,0), d = 0。

为了渲染阴影,只是这个矩阵应用于对象应该在平面上阴影π,用暗色以及无光照渲染这个预测对象。在实践中,您必须采取措施,以避免让投影三角形渲染在接收它们的表面之下。一种方法是在投影平面上添加一些偏置,这样阴影三角形总是渲染在几何表面的前面。一个更安全的方法是先画出地面,然后画出带z缓冲区的投影三角形,然后像往常一样渲染其余的几何图形。投影三角形总是在地面上绘制,因为没有深度比较。

如果地面有一个范围,例如,它是一个矩形,投影的阴影可能会落在它的外面,打破这个错觉。为了解决这个问题,我们可以使用stencil buffer。首先,将接收器绘制到屏幕和stencil buffer。然后,关闭z缓冲区,只在绘制接收器的地方绘制投影三角形,然后正常渲染场景的其余部分。

另一种阴影算法是将三角形渲染成纹理,然后应用于地面。这种纹理是一种光照贴图,它调节底层表面的强度(章节11.5.1)。正如我们所看到的,这种将阴影投射到纹理的想法也允许在几何表面上使用半影和阴影。这种技术的一个缺点是纹理可以被放大,用一个texel覆盖多个像素,破坏了图像。

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

当然可以显式地剔除和修剪阴影三角形,以避免此类错误。下面介绍一种更简单的方法,使用现有的GPU管道执行带有裁剪的投影。

7.1.2 软阴影 —— Soft Shadows

投射阴影也可以通过使用各种技术变得柔和。这里,我们描述了Heckbert和Herf[697, 722]提出的一种产生软阴影的算法。该算法的目标是在显示软阴影的地面上生成纹理。然后我们描述不那么精确、速度更快的方法。

当光源有一个区域时,就会出现柔和的阴影。一种近似区域光效果的方法是通过在其表面放置几个点状光来采样。对于这些精确光源中的每一个,图像都被渲染并累积到一个缓冲区中。然后,这些图像的平均值就是带有柔和阴影的图像。注意,理论上,任何生成硬阴影的算法都可以与这种累积技术一起用于生成半影。在实践中,以交互速率这样做通常是站不住脚的,因为这会涉及到执行时间。

Heckbert和Herf使用一种基于视锥的方法来产生阴影。这个想法是把光作为观察者,地面平面形成了截锥体的远截平面。视锥足够宽,可以包住透投影者。

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

采样区域光方法的一个问题是,它往往看起来像它是:从精确光源产生的几个重叠的阴影。同样,对于n个阴影Pass,只能生成n + 1个不同的渲染。大量的传递可以得到准确的结果,但是代价过高。该方法对于获得(字面上)“地面真实”图像以测试其他更快算法的质量是有用的。

更有效的方法是使用卷积,即,滤波器。在某些情况下,对一个单点生成的硬阴影进行模糊就足够了,并且可以生成一个半透明的纹理,可以与真实世界的内容组合在一起。参见图7.6。然而,在物体与地面接触的地方,均匀的模糊效果可能令人难以信服。

还有许多其他方法可以提供更好的近似,但需要额外的成本。例如,Haines[644]从一个投影的硬阴影开始,然后使用从中心的黑暗到边缘的白色渐变来渲染剪影边缘,创建可信的半影。参见图7.5的右侧。然而,这些半影在物理上是不正确的,因为它们也应该延伸到轮廓边缘内的区域。Iwanicki[356,806]利用球面谐波的概念,用椭球体近似遮挡体,从而产生柔和的阴影。所有这些方法都有不同的近似和缺点,但是比平均一组大的阴影图像要有效得多。

7.2 曲面上的阴影——Shadows on Curved Surfaces

从光的角度考虑阴影。凡光所看见的,就被照亮;它看不见的东西在阴影里。遮挡器在灯光的视觉点渲染为黑色,否则为白色。然后可以将这个纹理投射到将要接收阴影的表面上。实际上,接收器上的每个顶点都有一个(u, v)纹理坐标,并将纹理应用于其上。应用程序可以显式地计算这些纹理坐标。这与前一节中的地面阴影纹理稍有不同,在前一节中,对象被投影到特定的物理平面上。在这里,图像是从光的角度拍摄的,就像投影机中的一帧胶片。

当渲染时,投影阴影纹理修改接收器表面。它还可以与其他阴影方法结合使用,有时主要用于帮助感知对象的位置。例如,在一款跨平台的视频游戏中,即使主角处于完全阴影中,主角也可能会投影一个阴影[1343]。更精细的算法可以得到更好的结果。例如,Eisemann和D’ecoret[411]假设有一个矩形的顶灯,并创建一个对象水平切片的阴影图像堆栈,然后将其转换为mipmap或类似的图像。通过使用它的mipmap,每个切片对应的面积与它到接收器的距离成正比,这意味着距离越远的切片会投射出更柔和的阴影。

纹理投影方法存在一些严重的缺陷。首先,应用程序必须识别哪些对象是遮挡器,哪些对象是它们的接收器。接收器必须由程序维护,使其比遮挡器离光更远,否则阴影就会“向后投射”。此外,遮挡物体也不能遮蔽自己。接下来的两部分介绍的算法可以生成正确的阴影,而不需要这种干预或限制。

注意,可以通过使用预构建的投射纹理获得各种各样的照明模式。聚光灯是一个简单的方形投射纹理,它的内部有一个圆圈来定义光线。百叶帘效果可以通过由水平线组成的投射纹理创建。这种类型的纹理称为light attenuation mask、cookie纹理或gobo贴图。通过简单地将两个纹理相乘,可以将预构建的模式与动态创建的投影纹理组合在一起。这些光将在第6.9节中进一步讨论。

7.3 阴影体——Shadow Volumes

Heidmann在1991年[701]提出了一种基于Crow ‘s shadow volume[311]的方法,该方法可以巧妙地使用模板缓冲区将阴影投射到任意物体上。它可以在任何GPU上使用,因为唯一的要求是模板缓冲区。它不是基于图像的(不像下面描述的阴影映射算法),因此避免了采样问题,从而产生正确的尖锐阴影。这有时是一个缺点。例如,一个角色的衣服可能会有褶皱,导致薄而硬的阴影严重混叠。阴影体在今天很少被使用,因为它们的成本是不可预测的[1599]。我们在这里对算法进行了简要的描述,并在此基础上阐述了一些重要的原理和研究。

首先,想象一个点和一个三角形。将直线从一点经过三角形的顶点延伸到无穷远,就得到一个无穷远的三边金字塔。三角形下的部分,即,不包括点的部分,是一个截短的无限金字塔,而上面的部分只是一个金字塔。如图7.7所示。现在假设这个点是一个点光源。然后,位于截形金字塔体(三角形下方)内的对象的任何部分都处于阴影中。这个体积称为阴影体。

假设我们看了一些场景,然后沿着一条光线从眼睛穿过一个像素,直到光线击中要显示在屏幕上的对象。当光线到达这个物体时,每次它穿过阴影体的正面时,我们都会增加一个计数器(面向观众)。因此,每次光线进入阴影时计数器都会增加。以同样的方式,每次射线穿过截形金字塔的背面时,我们递减相同的计数器。然后光线从阴影中消失。我们继续,递增和递减计数器,直到射线击中要在该像素处显示的对象。如果计数器大于零,则该像素处于阴影中;否则就不是。当有多个三角形投射阴影时,这个原则也适用。参见图7.8。

用光线做这件事很费时。但是有一个更聪明的解决方案[701]:模板缓冲区可以为我们做计数。首先,清除模板缓冲区。其次,将整个场景绘制到framebuffer中,只使用Unlit材质的颜色,以便将这些阴影组件绘制到颜色缓冲区中,并将深度信息绘制到z-buffer中。第三,关闭z缓冲区的更新和对颜色缓冲区的写入(尽管仍然进行了z缓冲区测试),然后绘制阴影体的正面三角形。在此过程中,模板操作被设置为在绘制三角形的任何位置递增模板缓冲区中的值。第四步,使用模板缓冲区完成另一次Pass,这次只绘制阴影体的背面三角形。对于此Pass,当绘制三角形时,模板缓冲区中的值递减。只有当渲染的阴影体表面像素可见时,才会进行递增和递减,(而不是被任何真实的几何图形所隐藏)。此时,模板缓冲区保存每个像素的阴影状态。最后,再次渲染整个场景,这次只使用受光影响的活跃材质组件,并且只显示模板缓冲区中的值为0的地方。值为0表示光线离开阴影的次数与进入阴影体积的次数相同,即光线离开阴影的次数为0,这个位置被灯光照亮。

这种计数方法是阴影体的基本思想。阴影体积算法生成的阴影示例如图7.9所示。有很多有效的方法可以一次实现算法[1514]。然而,当一个物体穿透相机的近平面时,计数问题就会出现。这种情况称为z-fail,包括计算隐藏在可见表面后面的交叉口,而不是前面的交叉口[45,775]。图7.8显示了对这个备选方案的简要总结。

为每个三角形创建四边形会产生大量的OverDraw。也就是说,每个三角形将创建三个必须渲染的四边形。一个由一千个三角形组成的球体可以生成三千个四边形,而这些四边形中的每一个都可以跨越屏幕。一种解决方案是沿着物体的轮廓边缘只画那些四边形,例如,我们的球体可能只有50条轮廓边缘,所以只需要50条四边形。几何着色器可以用来自动生成这样的轮廓边缘[1702]。剔除和夹紧技术也可用于降低充填成本[1061]。

然而,阴影体积算法仍然有一个可怕的缺点:极端的可变性。想象一个单独的小三角形。如果相机和光处于完全相同的位置,阴影体积成本是最小的。形成的四边形将不会覆盖任何像素,因为它们是边缘对视图。只有三角形本身重要。假设观察者现在绕着三角形旋转,保持它在视野中。当相机远离光源时,阴影体积四边形将变得更加可见,并覆盖更多的屏幕,导致更多的计算发生。如果观察者恰好移动到三角形的阴影中,阴影体积将完全填满屏幕,与我们最初的视图相比,将花费相当多的时间来评估。这种可变性使得阴影体在交互应用程序中不可用,而在交互应用程序中,一致的帧速率非常重要。像其他场景一样,朝光的方向看会导致算法成本出现巨大的、不可预测的跳跃。

由于这些原因,阴影体在很大程度上被应用程序抛弃了。然而,考虑到访问GPU上数据的新方法和不同方式的不断发展,以及研究人员对这些功能的巧妙重新利用,阴影体可能有一天会重新被广泛使用。例如,Sintorn等人[1648]综述了提高效率的阴影体积算法,并提出了自己的分层加速结构。

下一个算法,阴影映射,有一个更可预测的成本,并很适合GPU,因此形成了阴影生成的基础在许多应用程序。

7.4 阴影图——Shadow Maps

1978年,Williams[1888]提出了一种通用的基于z缓冲的渲染器,可以用来快速生成任意物体上的阴影。这个想法是渲染场景,使用z缓冲,从光源的位置,也就是投下阴影。无论光“看见”的是什么,其余的都在阴影中。生成此映像时,只需要z缓冲。可以关闭灯光、纹理和写入颜色缓冲区的值。

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

当产生一个z缓冲器时,光只能“看”一个特定的方向,就像照相机一样。对于像太阳这样的远距离定向光,光的视野被设置为包含所有投射阴影到眼睛所看到的可视范围内的物体。光使用正投影,它的视图需要在x和y中足够宽和足够高来查看这组对象。局部光源需要尽可能地进行类似的调整。如果局部光线离投射阴影的物体足够远,一个视图截锥体就足以包含所有这些。另外,如果局部光是聚光灯,那么它有一个与之相关联的自然截锥体,其截锥体之外的所有物体都被认为没有被照亮。

如果局部光源位于场景中,并且被阴影投射器包围,典型的解决方案是使用六视图立方体,类似于环境立方贴图[865]。这些被称为全向阴影映射(omnidirectional shadow maps)。全向映射的主要挑战是避免沿着两个单独map相遇的接缝处出现瑕疵。King和Newhall[895]深入分析问题并提供解决方案,Gerasimov[525]提供了一些实现细节。Forsyth[484,486]提出了一种适用于全向光源的通用多截锥分区方案,在需要的地方还提供了更多的阴影映射分辨率。Crytek[1590,1678,1679]根据每个视图的投影截锥体的屏幕空间覆盖率,将所有地图存储在一个纹理图集中,为一个点光源设置六个视图的每个视图的分辨率。

并不是场景中的所有对象都需要渲染到光线的视图中。首先,只需要渲染能够投射阴影的对象。例如,如果已知地面只能接收阴影而不能投射阴影,那么它就不需要渲染到阴影映射中。

根据定义,阴影投射器是在光的视野范围内的投射器。这个截锥体可以通过几种方式来增强或收紧,让我们可以安全地忽略一些阴影投射器[896,1812]。考虑那些肉眼可见的阴影接收器。这组物体在光的视场方向上的最大距离内。任何超过这个距离的物体都不能在可见光接收器上投射阴影。类似地,可视接收器的集合很可能比光原来的x和y视图边界要小。参见图7.11。另一个例子是,如果光源在眼睛的视锥内,那么在这个额外的视锥外的任何物体都不能在接收器上投射阴影。只渲染相关对象不仅可以节省渲染时间,还可以减小光锥的尺寸,从而提高阴影贴图的有效分辨率,从而提高画质。此外,如果光锥的近平面离光越远越好,如果远平面离光越近越好。这样做可以提高z缓冲区的有效精度[1792] (第4.7.2节)。

阴影映射的一个缺点是阴影的质量取决于阴影映射的分辨率(以像素为单位)和z-buffer的数值精度。由于阴影映射是在深度比较过程中采样的,算法容易产生混叠问题,尤其是在接近物体接触点时。一个常见的问题是自阴影混叠,通常称为“surface acne”或“shadow acne”,指一个三角形被错误地认为阴影本身。这个问题有两个来源。一个是处理器精度的数值限制。另一个来源是几何的,因为一个点样本的值被用来表示一个区域的深度。也就是说,为光照生成的样本几乎从不与屏幕样本位于相同的位置(例如,像素通常在其中心采样)。当光的存储深度值与被观察表面的深度相比较时,光的值可能略低于表面的值,从而导致自阴影。这些错误的影响如图7.12所示。

帮助避免(但不总是消除)各种阴影映射瑕疵一种常见方法是引入偏差因子。当检测阴影图中发现的距离与被测位置的距离时,从接收机的距离中减去一个小偏差。参见图7.13。这种偏差可以是一个常数值[1022],但是当接收器不是主要面对光线时,这样做可能会失败。一个更有效的方法是使用一个偏置(bias ),它与接收器对光的角度成正比。表面离光越远,偏置就越大,从而避免了这个问题。这种类型的偏差称为坡度比例尺偏差(slope scale bias)。通过使用OpenGL 的 glPolygonOffset等命令将每个多边形从光线处移开,可以应用这两种偏差。请注意,如果一个表面直接面向光,它根本不会因为比例尺偏差而向后倾斜。因此,为了避免可能出现的精度误差,在比例尺偏差的基础上,采用了恒偏置。比例尺偏差也常常在某一最大值处被裁剪,因为从光的角度看,当表面几乎是侧面时,切线值可能会非常高。

Holbert[759,760]引入了法向偏移偏置(normal offset bias),它首先将接收器的世界空间位置沿表面法向移动了一点,与光的方向与几何法向夹角的正弦成正比。参见250页的图7.24。这不仅改变了深度,还改变了在阴影地图上测试样本的x和y坐标。当光的角度变得比表面更浅时,这个偏移量就会增加,希望样品离表面足够远以避免自阴影。这种方法可以被可视化为将样本移动到接收器上方的“虚拟表面”。这个偏移量是一个世界空间距离,因此Pettineo[1403]建议按阴影图的深度范围缩放它。Pesce[1391]提出了沿相机视图方向偏置的想法,这也可以通过调整阴影图坐标来实现。其他的偏置方法将在7.5节中讨论,因为这里提出的阴影法也需要测试几个相邻的样本。

过多的偏置会导致漏光或Peter Panning的问题,在这种情况下,物体似乎会稍稍浮在下表面的上方。产生这种瑕疵的原因是物体接触点以下的区域,例如,一英尺下的地面,被向前推得太远,因此不会受到阴影。

避免自阴影问题的一种方法是只渲染阴影映射的背面。这种方法被称为二次深度阴影映射(second-depth shadow mapping)[1845],在许多情况下都能很好地工作,特别是在不允许手工调整偏向的渲染系统中。当对象是双面的、薄的或彼此接触时,就会出现问题。如果一个对象是一个模型,其中网格的两边都是可见的,例如,一个棕榈叶或一张纸,自阴影可能会发生,因为背面和正面在同一个位置。类似地,如果不执行偏置,可能会在剪影边缘或薄物体附近出现问题,因为在这些区域,背向距离正面很近。添加偏置可以帮助避免surface acne,但该方案更容易漏光,因为在接触点,接收器和闭塞器的背面之间没有分离。参见图7.14。选择哪种方案取决于具体情况。例如,Sousa等人[1679]发现,将正面用于太阳阴影,将背面用于室内灯光,这两种方法的应用效果最好。

注意,对于阴影映射,对象必须是“水密的”(流形和封闭的,即、固体 16.3.3 节),或必须同时渲染map的正面和背面,否则对象可能不会完全投射阴影。Woo[1900]提出了一种通用的方法,试图在仅仅使用正面或背面进行阴影处理之间找到一种折衷的方法。这个想法是将实体物体渲染到阴影贴图中,并跟踪最接近光线的两个表面。这个过程可以通过深度剥离或其他透明相关技术来完成。两个物体之间的平均深度形成一个中间层,这个层的深度被用作阴影地图,有时也被称为dual shadow map[1865]。如果物体足够厚,自阴影和光泄漏的影响就会最小化。Bavoil等人[116]讨论了处理潜在瑕疵的方法,以及其他实现细节。主要缺点是使用两个阴影映射会带来额外的成本。Myers[1253]讨论了遮挡和接收器之间由艺术家控制的深度层。

当观察者移动时,光的视量经常会随着阴影投射的设置而改变。这些变化反过来又导致阴影在帧与帧之间轻微移动。这是因为光线的阴影地图是抽样一组不同方向的光,而这些方向与前面不一致集。对于定向照明,解决办法是迫使每一个成功的影子映射生成维持相同的texel相对位置位置在世界空间[927,1227,1792,1810]。也就是说,您可以将阴影映射看作是向整个世界施加一个二维网格化的参考框架,每个网格单元表示映射上的一个像素样本。当您移动时,将为这些相同网格单元格的不同集合生成阴影映射。换句话说,光的视图投射被强制到这个网格上,以保持帧与帧之间的一致性。

7.4.1 分辨率增强——Resolution Enhancement

与纹理的使用类似,理想情况下我们希望一个阴影贴图纹理覆盖一个图像像素。如果我们有一个光源位于与眼睛相同的位置,阴影映射就会完美地与屏幕空间像素一一对应(并且没有可见的阴影,因为光线恰好照亮了眼睛看到的东西)。一旦光的方向发生变化,这种每像素比就会发生变化,这可能会导致瑕疵。图7.15显示了一个示例。阴影是块状的,表现很差,因为前景中的大量像素与阴影地图的每个texel相关联。这种不匹配称为透视混叠。单一的阴影贴图纹理也可以覆盖许多像素,如果一个表面几乎是对着光的,但是面对着观察者。这个问题被称为投影混叠[1792];参见图7.16。可以通过增加阴影映射的分辨率来降低块度,但是需要额外的内存和处理。

还有另一种方法来创建光的采样模式,使其更接近相机的模式。这是通过改变场景向光投射的方式来实现的。通常我们认为视图是对称的,视图向量在截锥体的中心。然而,视图方向仅仅定义了一个视图平面,而没有定义采样的像素。定义截锥体的窗口可以在这个平面上移动、倾斜或旋转,创建一个四边形,以提供不同的世界映射来查看空间。四边形仍然是定期采样,因为这是一个线性变换矩阵的性质和它的GPU使用。采样率可以通过改变光的视图方向和视图窗口的边界来修改。参见图7.17。

将光的视角映射到眼睛的视角有22个自由度[896]。对这个解空间的探索导致了几种不同的算法,它们试图更好地匹配光线的采样率和眼睛的采样率。方法包括透视阴影图(PSM)[1691]、梯形阴影图(TSM)[1132]、光空间透视阴影图(LiSPSM)[1893, 1895]。有关示例,请参见254页上的图7.15和图7.26。此类中的技术称为透视图翘曲方法。

这些矩阵扭曲算法(matrix-warping algorithms)的一个优点是,除了修改光的矩阵外,不需要额外的工作。每种方法都有自己的优缺点[484],因为每种方法都可以帮助匹配某些几何形状和光照情况下的采样率,而对其他情况则会使采样率恶化。Lloyd等人[1062,1063]分析了PSM、TSM和LiSPSM之间的等价关系,对这些方法的采样和混叠问题给出了很好的概述。当光的方向垂直于视图的方向(例如头顶)时,这些方案的效果最好,因为这样就可以改变透视图的变换,使更多的样本更靠近眼睛。

矩阵翘曲技术无法帮助的一种照明情况是,当一束光在摄像机前面并指向它时。这种情况被称为dueling frusta,或者更通俗的说法是“鹿在车头灯。(deer in the headlights.)”眼睛附近需要更多的阴影地图样本,但线性扭曲只会让情况变得更糟[1555]。这和其他问题,如质量的突然变化[430]和相机运动时产生的阴影的“紧张”、不稳定的质量[484,1227],使这些方法失宠。

在观察者所在位置添加更多采样的想法是一个很好的想法,这导致了为给定视图生成多个阴影映射的算法。当卡马克在2004年Quakecon的主题演讲中描述这个想法时,这个想法第一次产生了明显的影响。Blow独立实现了这样一个系统[174]。这个想法很简单:生成一组固定的阴影地图(可能是不同分辨率的),覆盖场景的不同区域。在Blow的方案中,四个阴影映射嵌套在查看器周围。这样,附近的物体就可以得到高分辨率的地图,而远处的物体的分辨率就会下降。Forsyth[483,486]提出了一个相关的想法,为不同的可视对象集生成不同的阴影映射。在他的方法中避免了如何处理跨越两个阴影映射之间边界的对象的转换问题,因为每个对象都有且只有一个与之关联的阴影映射。Flagship Studios开发了一个融合了这两种理念的系统。一个阴影映射用于附近的动态对象,另一个映射用于观察者附近静态对象的网格部分,第三个映射用于场景中的静态对象。每个帧生成第一个阴影映射。另外两个只需要生成一次,因为光源和几何图形都是静态的。虽然所有这些特定的系统现在都非常古老,但是针对不同对象和情况的多映射的思想,有些是预先计算的,有些是动态的,是自那以后开发的算法中的一个共同主题。

2006年Engel [430], Lloyd等[1062,1063],Zhang等[1962,1963]独立研究了相同的基本思想。这个想法是通过平行于视图方向将视图截锥体分割成几个部分。参见图7.18。随着深度的增加,每一体积的深度范围都是前一体积的两到三倍[430,1962]。对于每个视图体,光源可以形成一个紧密约束它的截锥体,然后生成一个阴影映射。通过使用纹理地图集或数组,可以将不同的阴影映射视为一个大型纹理对象,从而最小化缓存访问延迟。得到的质量改进比较如图7.19所示。恩格尔将这种算法命名为级联阴影映射( cascaded shadow maps ,CSM),它比Zhang提出的parallel-split shadow maps更常用,但两者都出现在文献中,而且实际上是相同的[1964]。

该算法实现简单,能覆盖较大的场景区域,结果合理,鲁棒性强。对dueling frusta的问题可以通过在离眼睛更近的地方以更高的速率采样来解决,而且不存在严重的最坏情况问题。由于这些优点,CSM在许多应用程序中都得到了应用。

虽然可以使用perspective warping将更多的样本打包到单个阴影映射的细分区域中[1783],但通常为每个级联使用单独的阴影映射。如图7.18所示,图7.20从查看者的角度显示,每个映射所覆盖的区域可能不同。较小的视图体积为更近的阴影地图提供了更多的样本。确定z深度的范围如何在地图之间分割—一项称为z分区的任务—可以非常简单,也可以非常复杂[412,991,1791]。其中一种方法是对数分割[1062],对于每个叶栅图,远平面距离与近平面距离的比值是相同的:

其中n和f为整个场景的远近平面,c为地图的数量,r为最终的比例。例如,如果场景中最近的物体在1米之外,最大距离是1000米,我们有三个级联地图,那么最近视图的远近平面距离是1和10,下一个间隔是10到100来保持这个比例,最后一个间隔是100到1000米。初始近深度对这种划分有很大影响。如果近深度只有0.1米,那么10000的立方根为21.54,这是一个相当高的比例,例如0.1比2.154比46.42比1000。这将意味着生成的每个阴影映射必须覆盖更大的区域,从而降低其精度。在实践中,这样的划分为接近平面的区域提供了相当高的分辨率,如果该区域没有对象,则会浪费这种分辨率。避免这种不匹配的一种方法是将分区距离设置为对数分布和等距分布的加权混合[1962,1963],但如果我们能够确定场景的紧密视图边界,那就更好了。

挑战在于如何设置近平面。如果设置得离眼睛太远,物体可能会被这个平面裁切,这是一个非常糟糕的瑕疵。对于过场动画,艺术家可以提前精确地设置这个值[1590],但是对于交互式环境,这个问题更具挑战性。Lauritzen等人[991,1403]提出了样本分布阴影图(sample distribution shadow maps ,SDSM),该图使用前一帧的z-depth值,通过两种方法中的一种来确定更好的分区。

第一种方法是通过z深度查找最小值和最大值,并使用这些值设置近平面和远平面。这是使用GPU上所谓的reduce操作来执行的,在这个操作中,一系列越来越小的缓冲区被计算或其他着色器分析,输出缓冲区作为输入返回,直到剩下一个1×1的缓冲区。通常情况下,这些值会根据场景中物体的移动速度进行调整。除非采取纠正措施,否则从屏幕边缘进入的附近物体仍可能对帧造成问题,不过在下一帧中很快就会纠正。

第二种方法还分析深度缓冲区的值,生成一个称为直方图的图,记录z-depth沿范围的分布。除了找到紧密的近平面和远平面,图中可能还有一些空白,其中根本没有对象。通常添加到这样一个区域的任何分区平面都可以被捕捉到对象实际存在的位置,从而为级联映射集提供更高的z-depth精度。

在实践中,第一种方法是通用的,快速的(通常在每帧1 ms范围内),并且给出了很好的结果,因此在几个应用中被采用[1405,1811]。参见图7.21。

与单个阴影映射一样,由于光线样本在帧与帧之间移动而产生的闪烁瑕疵也是一个问题,当对象在级联之间移动时,情况可能更糟。在世界空间中稳定采样点的方法多种多样,各有其优势[41,865,1381,1403,1678,1679,1810]。当一个物体跨越两个阴影映射之间的边界时,阴影的质量会发生突然的变化。一种解决方案是让视图体稍微重叠。在这些重叠区域采集的样本收集了相邻阴影图的结果,并进行了混合[1791]。另一种方法是,可以使用抖动在该区域内提取单个样本[1381]。

由于它的普及,人们在提高效率和质量方面投入了大量的努力[1791,1964]。如果阴影映射的截锥内没有任何更改,则不需要重新计算该阴影映射。对于每一盏灯,阴影投射器的列表都可以预先计算出来,方法是找出哪些物体对光线是可见的,其中哪些物体可以在接收器上投射阴影[1405]。由于很难判断阴影是否正确,可以采用一些适用于级联和其他算法的快捷方式。一种技术是使用低层次的细节模型作为代理来实际投射阴影[652,1812]。另一种方法是从考虑中去除微小的遮挡物[1381,1811]。根据这样的阴影不那么重要的理论,距离越远的阴影地图更新的频率可能比一帧更新的频率要低。这种想法冒着由大型移动对象引起的瑕疵的风险,因此需要谨慎使用[865、1389、1391、1678、1679]。Day[329]提出了从一帧到另一帧“滚动”远处地图的想法,即每个静态阴影地图的大部分是可重复使用的帧到帧,只有边缘可能会改变,因此需要渲染。像《毁灭战士》(2016)这样的游戏维护了一个大型的阴影地图地图集,只生成那些移动了物体的地图集[294]。更远的级联映射可以被设置为完全忽略动态对象,因为这样的阴影可能对场景贡献很小。对于某些环境,可以使用高分辨率的静态阴影映射来代替这些更远的级联,这可以显著减少工作负载[415,1590]。稀疏纹理系统(sparse texture system)(章节19.10.1)可用于单个静态阴影地图可以是巨大的世界[241,625,1253]。级联阴影映射可以与烘焙光照贴图纹理或其他更适合特定情况的阴影技术相结合[652]。Valient的报告[1811]值得注意的是,它描述了针对多种视频游戏的不同影子系统定制和技术。第11.5.1节详细讨论了预先计算的光影算法。

创建几个独立的阴影映射意味着为每个映射运行一组几何图形。许多提高效率的方法都是建立在一次渲染一组阴影映射的遮挡器的思想上的。几何着色器可用于复制对象数据并将其发送到多个视图[41]。实例几何着色器允许对象输出多达32个深度纹理[1456]。多视图端口扩展可以执行一些操作,比如将一个对象渲染给一个特定的纹理数组切片[41,154,530]。第21.3.1节在它们用于虚拟现实的上下文中更详细地讨论了这些。视图共享技术的一个可能的缺点是,生成的所有阴影映射的遮挡器必须通过管道发送,而与此相对的是与每个阴影映射相关的集[1791,1810]。

你自己现在正处于世界上数十亿光源的阴影中。光只能从其中的几个到达。在实时渲染中,如果所有的灯光都处于活动状态,那么带有多个灯光的大场景就会被计算淹没。如果一个空间体位于视图截锥体内,但肉眼不可见,则不需要评估遮挡该接收体体块的对象[625,1137]。Bittner等[152]使用遮挡剔除(章节19.7)从眼睛中找到所有可见的阴影接收器,然后从光线的角度将所有潜在的阴影接收器渲染到stencil buffer mask中。这个遮罩编码了从光线中可以看到的可见阴影接收器。为了生成阴影贴图,他们使用遮挡剔除来渲染光线中的物体,并使用遮罩剔除没有接收器的物体。不同的淘汰策略也适用于灯光。由于辐照度随距离的平方成反比,一般的方法是在一定的阈值距离后剔除光源。例如,第19.5节中的门户筛选技术可以发现哪些光影响哪些单元格。这是一个活跃的研究领域,因为性能的收益可以是可观的[1330,1604]。

7.5 Percentage-Closer Filtering(PCF)

阴影映射技术的一个简单扩展可以提供伪软阴影。这种方法还可以帮助改善分辨率问题,当单个光样本单元覆盖多个屏幕像素时,会导致阴影看起来是块状的。解决方案类似于纹理放大(第6.2.1节)。不再从阴影映射中提取单个样本,而是检索最近的四个样本。这项技术并不在深度之间进行插值,而是将它们与表面深度进行比较的结果进行插值。也就是说,将表面的深度分别与四个texel深度进行比较,然后确定每个shadow-map样本的点处于光或阴影中。这些结果,即,阴影为0,光为1,然后用bilinearly插值来计算光对表面位置的实际贡献。这种过滤会产生一个人为的柔和阴影。这些半影会根据阴影地图的分辨率、相机位置和其他因素而变化。例如,分辨率越高,边缘的软化范围就越窄。不过,有一点半影和平滑效果总比没有好。

这种从阴影图中检索多个样本并混合结果的想法称为percentage-closer filtering(PCF)[1475]。区域灯产生柔和的阴影。到达表面某一位置的光量是该位置可见光面积的比例的函数。PCF试图通过反转这个过程来近似一个精确(或定向)光的软阴影。它不是从一个表面位置找到光的可见区域,而是从靠近原始位置的一组表面位置找到准时光的可见性。参见图7.22。“percentage-closer filtering”这个名称指的是最终的目标,即找到在光线下可见的样本的百分比。这个百分比是用来给表面渲染的。

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

一旦确定了要采样的区域的宽度,就必须以避免混叠瑕疵的方式进行采样。对于如何采样和过滤附近的阴影地图位置,有许多不同的方法。变量包括要采样的区域有多大、要使用多少个样本、采样模式以及如何对结果进行加权。在api能力较差的情况下,可以通过一种特殊的纹理采样模式来加速采样过程,这种模式类似于双线性插值,访问四个相邻的位置。不是混合结果,而是将四个样本中的每一个与给定的值进行比较,并返回通过测试的比率[175]。然而,在常规网格模式中执行最近邻居抽样可以创建明显的瑕疵。使用一个模糊结果但尊重物体边缘的联合双边滤波器可以提高质量,同时避免阴影泄漏到其他表面[1343]。有关此过滤技术的更多信息,请参见第12.1.1节。

DirectX 10引入了对PCF的单指令双线性滤波支持,得到了更平滑的结果[53,412,1709,1790]。与最近邻抽样相比,这提供了相当大的视觉改进,但是常规抽样的瑕疵仍然是一个问题。最小化网格模式的一个解决方案是使用预先计算的泊松分布模式对一个区域进行采样,如图7.23所示。这种分布将样本分散开来,使它们既不靠近彼此,也不按一定的规律分布。众所周知,对于每个像素使用相同的采样位置,无论其分布如何,都可能导致模式[288]。这种瑕疵可以通过随机旋转样本分布到其中心来避免,从而将混叠转化为噪声。Casta˜no[235]发现泊松采样产生的噪音特别明显的光滑、风格化的内容。提出了一种基于双线性抽样的高效高斯加权抽样方案。

自阴影问题和光线泄漏, acne 和Peter Panning,可以变得更糟的PCF。比例尺偏差将表面推离光,完全基于它与光的角度,假设一个样本距离阴影地图不超过一个texel。通过在更大范围内从一个表面的单一位置取样,一些测试样本可能会被真实的表面所阻挡。

一些不同的附加偏移因素已经被发明出来,并在一定程度上成功地用于降低自阴影的风险。Burley[212]描述了偏移锥,在偏移锥中,每个样本向光的移动与它与原始样本的距离成正比。Burley建议斜率为2.0,并保持一个小的恒定偏差。参见图7.24。

Sch¨uler[1585]、Isidoro[804]和Tuft[1790]提出了基于观察的技术,即接收器本身的斜率应该用来调整其余样品的深度。其中Tuft的公式[1790]最容易应用于级联阴影映射。Dou等人[373]进一步细化和扩展了这一概念,解释了z-depth如何以非线性的方式变化。这些方法假定附近的样本位置在三角形构成的同一平面上。这种技术被称为接收平面深度偏差或其他类似术语,在许多情况下可以非常精确,因为这个假想平面上的位置确实在表面上,或者如果模型是凸的,则在它的前面。如图7.24所示,孔附近的样本可以被隐藏。使用常量、比例尺、接收平面、视图偏置和正常偏移偏置的组合来解决自阴影问题,但是仍然需要对每个环境进行手工调整[235、1391、1403]。PCF的一个问题是,由于采样区域的宽度保持不变,阴影将呈现均匀的柔和,所有的半影宽度相同。在某些情况下,这可能是可以接受的,但在遮挡器和接收器之间有地面接触的情况下,这似乎是不正确的。参见图7.25。

7.6 Percentage-Closer Soft Shadows(PCSS)

2005年Fernando[212,467,1252]发表了一项颇具影响力的方法,叫做Percentage-Closer Soft Shadows(PCSS)。它试图通过在Shadow Map上搜索附近的区域来找到所有可能的遮挡器。用这些遮挡器与位置的平均距离来确定样本区域宽度:

其中dr为接收机与光的距离,做平均遮挡距离。换句话说,随着平均遮挡器离接收器越来越远,离光越来越近,到样品的表面积的宽度就会增加。检查图7.22,并考虑移动遮挡器的效果,看看这是如何发生的。图7.2(第224页)、7.25和7.26给出了示例。

如果没有发现遮挡器,则位置是完全亮的,不需要进一步处理。类似地,如果位置完全闭塞,处理就可以结束。否则,将对感兴趣的区域进行采样,并计算光的近似贡献。为了节省加工成本,可以使用取样区域的宽度来改变取样的数量。其他的技术也可以实现,例如,使用较低的采样率来处理较不重要的远距离软阴影。

这种方法的一个缺点是,它需要采样一个公平大小的阴影地图的面积,以找到遮挡器。使用旋转的泊松盘模式可以帮助隐藏欠采样瑕疵[865,1590]。Jimenez[832]指出,泊松采样在运动状态下可能是不稳定的,并发现使用介于抖动和随机之间的函数形成的螺旋模式可以在帧与帧之间得到更好的结果。

Sikachev等人[1641]详细讨论了使用SM 5.0中的特性更快地实现PCSS,该特性由AMD引入,通常以其名称命名为contact hardening shadows (CHS)。这个新版本还解决了基本PCSS的另一个问题:半影的大小受阴影贴图分辨率的影响。参见图7.25。通过首先生成阴影映射的mipmap,然后选择最接近用户定义的世界空间内核大小的mip级别,可以最小化这个问题。采样一个8×8的区域来找到平均块深度,只需要16个gather()纹理调用。一旦找到半影估计,高分辨率的mip级别用于阴影的锐区,而低分辨率的mip级别用于较软的区域。

CHS已经在大量的电子游戏中得到了应用[1351,1590,1641,1678,1679],研究还在继续。例如,Buades等人[206]提出了可分离软阴影映射(SSSM),其中采样网格的PCSS过程被分割成可分离的部分,并且尽可能地在像素之间重用元素。

对于每个像素需要多个样本的加速算法,一个已被证明有用的概念是分层的最小/最大阴影映射。虽然阴影映射深度通常不能取平均值,但是每个mipmap级别上的最小值和最大值是有用的。也就是说,可以形成两个mipmap,一个保存每个区域中发现的最大zdepth(有时称为HiZ),另一个保存最小的zdepth。给定texel位置、深度和要采样的区域,mipmaps可用于快速确定完全光照和完全阴影条件。例如,如果texel的z-depth大于为mipmap相应区域存储的最大z-depth,则texel必须处于阴影中—不需要进一步的样本。这种类型的阴影地图使得确定光可见性的任务更加有效[357,415,610,680,1064,1811]。

像PCF这样的方法是通过采样附近的接收器位置来工作的。PCSS的工作原理是找到附近闭塞器的平均深度。这些算法不直接考虑光源的面积,而是对附近的表面进行采样,并受阴影贴图分辨率的影响。PCSS背后的一个主要假设是,平均阻滞剂是对半影大小的合理估计。当两个遮挡器,比如一盏路灯和一座远山,在一个像素处部分遮挡了相同的表面时,这种假设就被打破了,可能会产生瑕疵。理想情况下,我们想要确定从单个接收器位置可以看到多少面积的光源。一些研究人员已经探索了使用GPU的反投影。其思想是将每个接收器的位置作为一个视点,区域光源作为视场平面的一部分,并将遮挡器投射到这个平面上。Schwarz和Stamminger[1593]以及Guennebaud等[617]总结了之前的工作并提出了自己的改进。Bavoil等[116]采用了一种不同的方法,使用深度剥离来创建多层阴影地图。反投影算法可以得到很好的结果,但是每像素的高成本(到目前为止)意味着它们还没有被应用到交互式应用中。

7.7 Filtered Shadow Maps

Donnelly和Lauritzen’s variance shadow map (VSM)是一种允许对生成的阴影映射进行过滤的算法[368]。该算法在一张Map中存储深度,在另一张Map中存储深度的平方。在生成映射时可以使用MSAA或其他抗混叠方案。这些地图可以被模糊,mipmapped,放在求和面积表[988],或任何其他方法。将这些映射视为可过滤纹理的能力是一个巨大的优势,因为当从它们检索数据时,可以使用整个采样和过滤技术数组。

我们将在这里更深入地描述VSM,以了解这个过程是如何工作的;同样,这类算法中的所有方法都使用了相同类型的测试。有兴趣进一步了解这一领域的读者应该访问相关的参考文献,我们也推荐Eisemann等人的书[412],这给了主题更多的空间。

首先,对于VSM,在接收端位置采样深度图(仅采样一次),以返回最近的光遮挡器的平均深度。当这个平均深度M1,称为第一个力矩,大于阴影接收器t上的深度时,接收器就完全处于光中。当平均深度小于接收机深度时,采用下式:

pmax是最大比例的样品在光,σ2方差,t是接收器深度,M1是影子的平均预期深度图。depthsquared shadow map的样本M2,称为second moment,用来计算方差:

值pmax是接收方可见性百分比的上限。实际照度百分比p不能大于这个值。这个上界来自于Chebyshev’s不等式的单边变量。该方程试图利用概率论估计遮挡器在地表位置的分布有多少超出了地表离光的距离。Donnelly和Lauritzen证明,对于固定深度的平面遮挡器和平面接收机,p = pmax,因此方程7.7可以很好地逼近许多真实阴影情况。

Myers[1251]对这种方法的工作原理建立了一种直觉。面积的方差在阴影边缘处增大。深度差越大,方差越大。(t – M1)2项是能见度百分比的一个重要决定因素。如果这个值略高于零,这意味着平均遮挡深度比接收器更接近于光,而pmax则接近于1(完全照明)。这将发生在完全照亮的半影边缘。进入半影,平均遮挡深度更接近于光,所以这一项变大,pmax下降。与此同时,方差本身在半影内也在变化,从近于零的边缘变化到最大的方差,此时遮挡器的深度不同,面积也相同。这些项相互平衡,在半影上形成线性变化的阴影。与其他算法的比较见图7.26。

方差阴影映射的一个显著特点是,它能以优雅的方式处理几何引起的曲面偏置问题。Lauritzen[988]给出了一个关于如何使用曲面的斜率来修改第二个弯矩值的推导。偏置等问题的数值稳定性可以是一个问题的方差映射。例如,公式7.8从另一个类似的值中减去一个大的值。这种类型的计算往往放大了底层数值表示的准确性不足。使用浮点纹理有助于避免这个问题。

总体而言,由于GPU的优化纹理功能得到了有效的利用,VSM在处理时间上有了显著的提高。虽然PCF需要更多的样本,因此需要更多的时间,以避免产生更柔和的阴影时的噪音,但VSM只需要一个高质量的样本就可以确定整个区域的效果,并产生一个平滑的半影。这种能力意味着阴影可以在算法的限制范围内任意地变软,而不需要额外的成本。与PCF一样,滤波核的宽度决定了半影的宽度。通过找到接收器和最近的遮挡器之间的距离,可以改变内核的宽度,从而产生令人信服的柔和阴影。Mipmapped samples是一个较差的估计覆盖率的半影与一个缓慢增长的宽度,创建四四方方的瑕疵。Lauritzen[988]详细介绍了如何使用和域表来提供更好的阴影。图7.27显示了一个示例。

当两个或多个遮挡器覆盖一个接收机,且一个遮挡器靠近接收机时,沿半影区域的方差阴影映射将被分解。概率论中的Chebyshev不等式将产生一个与正确的光率无关的最大光值。最近的遮挡器,通过只部分地遮挡光,使方程的近似值不成立。这导致轻度漏光(又称光渗漏),完全闭塞的区域仍能接收到光。参见图7.28。通过在更小的区域上取更多的样本,可以解决这个问题,将方差阴影映射转化为PCF的一种形式。与PCF一样,速度和性能需要权衡,但对于阴影深度复杂度较低的场景,方差映射可以很好地工作。Lauritzen[988]提出了一种艺术家控制的方法来改善这一问题,即将低百分比视为完全阴影,并将其余百分比范围重新映射为0%到100%。这种方法使浅漏光变暗,以整体缩小半影为代价。虽然轻度漏光是一个严重的限制,但是VSM很好地从地形生成阴影,因为这种阴影很少涉及多个遮挡器[1227]。

能够使用滤波技术快速生成平滑阴影的前景引起了人们对滤波阴影映射的极大兴趣;主要的挑战是解决各种漏光问题。Annen等人介绍了卷积阴影图(convolution shadow map)。扩展了Soler和Sillion的平面接收机算法[1673]背后的思想,该思想是用傅里叶展开来编码阴影深度。与方差阴影映射一样,可以对此类映射进行过滤。该方法收敛到正确答案,从而减少了光泄漏问题。

Salvi[1529, 1530]和Annen等人同时独立地提出了基于指数函数使用单个项的想法。这种方法称为指数阴影映射(ESM)或指数方差阴影映射(EVSM),它将深度的指数及其二阶矩保存到两个缓冲区中。指数函数更接近于阴影映射执行的阶跃函数(即是否在光照下)因此,这可以显著减少漏光问题。它避免了卷积阴影映射所带来的另一个问题,称为ringing,在这个问题中,光的微小泄漏可能发生在特定的深度,刚好超过原始遮挡器的深度。

存储指数值的一个限制是,二阶矩值可能变得非常大,因此使用浮点数会超出范围。为了提高精度,并允许指数函数下降得更厉害,可以生成z-depth,使它们是线性的[117,258]。

指数阴影映射方法由于其相对于VSM的质量得到了提高,并且与卷积图相比存储空间更小,性能更好,因此在三种滤波方法中,指数阴影映射方法引起了最多的兴趣。Pettineo[1405]指出了其他一些改进,比如使用MSAA来改进结果并获得一些有限的透明度,并描述了如何使用计算着色器改进过滤性能。

最近,Peters和Klein[1398]引入了矩影映射。它提供了更好的质量,但代价是使用了四个或更多的时间,增加了存储成本。使用16位整数存储矩可以降低这一成本。Pettineo[1404]实现了这种新方法,并将其与ESM进行了比较,提供了一个探索许多变体的代码库。

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

过滤后的映射可以看作是PCF的一种廉价形式,这种形式只需要很少的样本。像PCF一样,这样的阴影有一个恒定的宽度。这些滤波方法都可以与PCSS结合使用,以提供可变宽度的半影[57,1620,1943]。矩阴影映射的扩展还包括提供光散射和透明效果的能力[1399]。

7.8 体阴影技术——Volumetric Shadow Techniques

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

自阴影对于真实渲染物体非常重要,比如头发和云彩,这些物体要么很小要么半透明。单深度阴影贴图在这种情况下是行不通的。Lokovic和Veach[1066]首先提出了深度阴影映射的概念,其中每个阴影映射texel都存储一个函数,该函数表示光线如何随深度下降。这个函数通常由一系列不同深度的样本逼近,每个样本都有一个不透明度值。在Map中,将给定位置的深度括起来的两个示例用于查找阴影的效果。GPU面临的挑战是如何高效地生成和评估这些函数。这些算法使用类似的方法,并且遇到了一些与顺序无关的透明算法类似的挑战(第5.5节),比如压缩数据的存储,以忠实地表示每个函数。

Kim和Neumann[894]首先提出了一种基于gpu的方法,他们称之为不透明度阴影映射。只存储不透明度的映射在一组固定的深度上生成。Nguyen和Donnelly[1274]给出了这种方法的更新版本,生成了如图17.2所示的719页的图像。然而,由于深度切片都是平行且均匀的,因此需要许多切片来隐藏由于线性插值而产生的切片之间的不透明度瑕疵。Yuksel和Keyser[1953]通过创建更接近模型形状的不透明映射来提高效率和质量。这样做可以减少所需的层数,因为对每一层的评估对最终图像更重要。

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

在游戏GRID2中采用自适应体阴影映射方法进行逼真的烟雾渲染,平均渲染成本低于2 ms/帧[886]。F¨urst等人[509]为他们实现视频游戏的深阴影映射提供了代码。他们使用链表来存储深度和阿尔法,并使用指数阴影映射来提供光照和阴影区域之间的软转换。

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

7.9 不规则Z-Buffer阴影——Irregular Z-Buffer Shadows

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

阴影体提供了一个精确的解析解,因为光与表面的相互作用产生了一系列三角形,这些三角形定义了任何给定的位置是亮的还是处于阴影中。该算法在GPU上实现时不可预测的开销是一个严重的缺点。近年来[1648]探索的改进是诱人的,但还没有“存在证据”表明它被应用于商业应用。

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

另一种方法是使用GPU的栅格化硬件来查看场景,但是不仅仅是z-depth,更多的信息存储在光线的每个网格单元中关于遮挡器边缘的信息[1003,1607]。例如,假设在每个阴影映射texel中存储一个重叠网格单元格的三角形列表。这种列表可以通过保守的栅格化生成,其中,如果三角形的任何部分与像素重叠,而不仅仅是像素的中心重叠,则三角形生成片段(第23.1.2节)。这种方案的一个问题是,通常需要限制每个texel的数据量,从而导致在确定每个接收器位置的状态时出现错误。考虑到gpu的现代链表原则[1943],每像素存储更多的数据当然是可能的。然而,除了物理内存的限制,存储一个变量的数据量的问题列表中每特塞尔绵羊是GPU处理可以变得非常低效,作为一个单一的变形可以有几个片段线程需要检索和处理许多项目,而其他的线程空闲,没有工作要做。构造着色器以避免由于动态“if”语句和循环而导致线程发散,这对性能至关重要。

在阴影映射中存储三角形或其他数据并根据它们测试接收器位置的另一种方法是翻转问题,存储接收器位置,然后根据每个位置测试三角形。Johnson等人[839]和Aila and Laine[14]首次探索了保存接收器位置的概念,并将其称为不规则z缓冲(IZB)。这个名称有点误导人,因为缓冲区本身具有用于阴影映射的正常、规则形状。相反,缓冲区的内容是不规则的,因为每个shadowmap texel将有一个或多个接收器位置存储在其中,或者可能根本没有。参见图7.30。

利用Sintorn等人[1645]和Wyman等人[1930,1932]提出的方法,一个多通道算法创建IZB,并测试其内容在光照下的可见性。首先,从眼睛渲染场景,找到从眼睛看到的表面的z深度。这些点被转换成光的场景视图,并且从这个集合中形成了光的截锥体的紧密边界。然后这些点被放置在光的IZB中,每个点都被放置在相应的texel的列表中。注意,有些列表可能是空的,即光可以看到但肉眼看不到表面的空间。遮挡器被保守地光栅化到光的IZB,以确定是否隐藏了任何点,从而隐藏在阴影中。保守的栅格化确保,即使一个三角形没有覆盖轻texel的中心,它也会被测试它可能重叠的点。

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

谨慎的编码对于使这种方法在GPU上运行良好是至关重要的。Wyman等人[1930,1932]注意到他们的最终版本比最初的原型快两个数量级。这种性能提高的部分原因是直接的算法改进,比如剔除表面法线朝向背光的图像点(因此总是不亮的),避免为空的文本生成碎片。其他性能提升来自于改进GPU的数据结构,以及通过在每个texel中使用短而相似长度的点列表来最小化线程分歧。图7.30显示了一个低分辨率的阴影映射,其中有很长的列表,用于说明。理想情况是每个列表有一个图像点。较高的分辨率提供了更短的列表,但也增加了由闭塞器生成的用于评估的片段的数量。

从图7.30左下角的图像中可以看出,由于透视效果的影响,地面上可见点的密度在左侧明显高于右侧。使用级联阴影贴图可以将更多的光贴图分辨率聚焦在眼睛附近,从而帮助降低这些区域的列表大小。

这种方法避免了其他方法的采样和偏置问题,并提供了完美的锐利阴影。出于审美和感知的原因,通常需要柔和的阴影,但是对于附近的遮挡器,比如Peter Panning,可能会有偏置问题。Story和Wyman[1711,1712]探索了混合阴影技术。其核心思想是利用遮挡距离来混合IZB和PCSS阴影,利用遮挡距离较近时的硬阴影效果和距离较远时的软阴影效果。参见图7.31。

7.10 其他应用——Other Applications

将阴影映射视为定义一个空间体,将光与暗分开,也可以帮助确定要阴影的对象的哪些部分。Gollent[555]描述了CD Projekt的地形阴影系统如何为每个区域计算仍然被遮挡的最大高度,然后该系统不仅可以用于阴影地形,还可以用于阴影场景中的树木和其他元素。为了找到每个高度,为太阳绘制了可见区域的阴影图。然后检查每个地形的高度场位置,以确定其在太阳下的可见度。如果在阴影中,太阳第一次可见的高度是通过将世界的高度增加一个固定的步长来估计的,直到太阳进入视野,然后执行二分查找。换句话说,我们沿着一条垂直线前进,并迭代缩小它与阴影映射的表面相交的位置,阴影映射的表面将明暗分开。相邻的高度被插值,以找到这个闭塞的高度在任何位置。在图7.32中可以看到这种技术用于地形高度场的软阴影。在第14章中,我们将会看到更多的光线在光明和黑暗中穿行。

最后一个值得一提的方法是渲染屏幕空间阴影。阴影地图由于分辨率有限,往往无法对小特征产生准确的遮挡。这在绘制人脸时尤其成问题,因为我们特别容易注意到人脸上的任何视觉制品。例如,使发光的鼻孔(如果不是故意的)看起来不协调。虽然使用高分辨率的阴影地图或单独针对感兴趣区域的阴影地图可以提供帮助,但另一种可能性是利用现有的数据。在大多数现代渲染引擎中,来自早期prepass的相机视角的深度缓冲在渲染过程中是可用的。存储在其中的数据可以作为一个高度图处理。通过对这个深度缓冲区进行迭代采样,我们可以执行一个ray-marching process(第6.8.1节),并检查朝向光的方向是否没有被遮挡。虽然代价高昂,因为它涉及重复采样深度缓冲区,这样做可以为过场动画中的特写提供高质量的结果,在过场动画中,花费额外的毫秒通常是合理的。该方法是由Sousa at al.[1678]提出的,并在今天的许多游戏引擎中广泛使用[384,1802]。

总结一下这一章,某种形式的阴影映射是目前为止用于投射到任意表面形状上的阴影最常用的算法。级联阴影贴图可以提高大面积阴影的采样质量,比如户外场景。通过SDSM为近平面寻找一个较好的最大距离,可以进一步提高精度。接近百分比的滤波(PCF)使阴影变得柔和,接近百分比的软阴影(PCSS)及其变体使接触硬化,不规则的z缓冲可以提供精确的硬阴影。滤过的阴影图提供了快速的软阴影计算,当遮挡器远离接收器时,如地形时,计算效果特别好。最后,屏幕空间技术可以用于额外的精度,尽管代价很明显。

在本章中,我们重点介绍了当前应用程序中使用的关键概念和技术。每一种都有自己的优势,选择取决于世界的大小、组成(静态内容相对于动画)、材料类型(不透明、透明、头发或烟雾)以及灯光的数量和类型(静态或动态;本地或遥远;点、点或区域),以及诸如底层纹理隐藏任何瑕疵的程度等因素。GPU的功能不断发展和改进,所以我们希望在未来几年继续看到新的算法能够很好地映射到硬件上。例如,第19.10.1节中描述的稀疏纹理技术被应用到阴影地图存储中,以提高分辨率[241,625,1253]。Sintorn, K¨ampe和其他人[850,1647]采用了一种创造性的方法,探索了将二维阴影图转换为三维体素(小盒子;见13.10节)。使用体素的一个优点是,它可以被分为亮体素和阴影体素,因此需要的存储空间很小。一个高度压缩的稀疏体素八叉树表示为大量的光和静态遮挡器存储阴影。Scandolo等人[1546]将他们的压缩技术与使用双阴影映射的基于区间的方案相结合,给出了更高的压缩率。Kasyan[865]使用体素锥跟踪(第13.10节)从区域灯生成柔和的阴影。有关示例,请参见图7.33。更多的锥形跟踪阴影显示在585页的图13.33中。

扩展阅读——Further Reading and Resources

我们在这一章的重点是基本原则和阴影算法需要什么样的质量-可预测的质量和性能-是有用的交互式渲染。我们避免了对这一领域的研究进行详尽的分类,因为有两篇文章讨论了这个主题。Eisemann等人[412]的著作《实时阴影》(Real-Time Shadows)直接关注交互渲染技术,讨论了各种算法及其优点和成本。SIGGRAPH 2012课程提供了本书的摘录,同时也增加了对新作品的参考[413]。他们的SIGGRAPH 2013课程的演示文稿可以在他们的网站www.siggraph.realtimeshadows.com上找到。Woo和Poulin的书《Shadow Algorithms Data Miner》[1902]概述了用于交互式和批处理呈现的各种阴影算法。这两本书都提供了该领域数百篇研究论文的参考资料。

Tuft的两篇文章[1791,1792]很好地概述了常用的阴影映射技术及其涉及的问题。Bjørge[154]提出了一系列受欢迎的阴影算法适合移动设备,以及比较各种图像算法。Lilley的报告[1046]对实际的阴影算法进行了坚实而广泛的概述,重点是GIS系统的地形绘制。博客文章由Pettineo(1403、1404)和Casta˜没有[235])特别有价值的实用技巧和解决方案,以及演示代码库。请参阅Scherzer等人[1558]对专门针对硬阴影的工作的简短总结。Hasenfratz等人[675]对软阴影算法的研究已经过时,但在一定程度上涵盖了早期的大量工作。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.