《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows

写在前面的话:因为英语不好,所以看得慢,所以还不如索性按自己的理解简单粗糙翻译一遍,就当是自己的读书笔记了。不对之处甚多,以后理解深刻了,英语好了再回来修改。相信花在本书上的时间和精力是值得的。

———————————————————————————————

属于生命的一切多样性,一切魅力,一切美好,都是由光和影构成的。 

——Mirumo、托尔斯泰

        阴影对于创建真实的图像很重要, 为用户提供了关于对象位置的视觉提示。 本章主要介绍阴影计算的基本原理,并描述了最重要和最流行的实时算法。 我们还简要讨论了不太流行但体现了重要原则的方法。

        本章中使用的术语如图7.1所示,其中遮光板(occluder)是把阴影投射到接收器(receivers)上的物体。如果使用精准光源(punctual light source),即没有区域,会生成完全阴影区域,有时被称为硬阴影(hard shadows)。 如果使用面积或体积光源,则会产生软阴影(soft shadows)。 每个阴影可以有一个完全阴影的区域,称为本影(umbra)和一个部分阴影的区域,称为半影(penumbra)。软阴影可以通过其模糊的阴影边缘来识别。 然而,需要注意的是,如果只是利用一个低通滤波对硬阴影的边缘进行模糊,通常不能得到正确的渲染结果。如图7.2所示,一个正确的软阴影在接收器上的形状是近似于投射阴影的几何体软阴影的本影区域不等于由精准光源产生的硬阴影。 相反,软阴影的本影区域会随着光源变大而减小,如果光源足够大,且接收器距离遮光板足够远,它甚至可能消失。 软阴影效果更好,因为半阴影边缘让观众知道,这里确实是一个阴影。 硬边阴影通常看起来不那么真实,有时会被误解为当前的几何特征,比如表面的折痕。 然而,硬阴影比软阴影渲染速度快

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第1张图片

图 7.1 阴影的术语:光源(light source)、遮光板( occluder)、接收器(receiver)、阴影(shader)、本影(umbra)、半影(penumbra)

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第2张图片

图 7.2 硬阴影和软阴影的混合。 板条箱的阴影很锐利, 因为遮挡者靠近接收器。人的影子在接触点是锐利的, 随着到遮光板的距离增加而软化。 远处的树枝投下柔和的阴影。

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

         在接下来的部分中,我们将超出这些简单的建模阴影,并介绍从场景中的遮挡器实时自动计算阴影的方法。

7.1 平面阴影(Planar Shadows)

        一个阴影发生的最简单例子就是物体在一个平面上投射阴影。 本节介绍了几种用于平面阴影的算法,每种算法在阴影的柔度和真实感方面都有所不同。

7.1.1 投影阴影(Projection Shadows)

        在这个方案中 ,三维物体被渲染了两次来创建一个阴影。通过矩阵计算,把物体的顶点投射到平面上。考虑图7.3所示的情况,光源的位置为l,被投影的顶点是v,投影到平面的顶点是p。先推出投影矩阵的特殊情况下,即阴影平面是y = 0,然后这个结果将推广到任何平面。        

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第3张图片

图 7.3 左图:光源在I,投射一个阴影到平面y=0上。顶点v被投影到平面上,对应的投影点是p。两个相似三角形可以用来推导出投影矩阵。右图:在平面π:n·x + d = 0 上的投影。

         我们先求出x坐标的投影,从图7.3左图的相似三角形,我们可以得到:

用同样的方式可以获得z坐标:,同事y坐标为0。然后把这些方程转换成投影矩阵M:

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

        在通常情况下,阴影所在平面不是平面y=0,可以用π:n·x+d =0来表示,如图7.3右图所示。 们的目标是找到一个将v投影到p的矩阵。从l发出的射线,经过v,和π平面发生交叉,然后就得到了投影点p:

这个方程也可以转换成一个投影矩阵,如公式7.4所示,满足Mv=p:

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第4张图片

如果平面为y=0,也就是有n=(0,1,0),d=0,公式7.4就变成了公式7.2。

        为了渲染出阴影,简单把这个矩阵应用到物体上,在平面π上投射阴影,然后用黑色并且无光照来渲染这个投影在平面上的阴影。实际操作的时候,需要避免阴影三角形被渲染在接受表面的下方。一种方法是在投影表面上方加一些偏差,这样我们的阴影三角形就永远渲染在表面上方。

         一种更安全的方法是先画出地平面,然后关闭z-buffer,画出阴影三角形,然后像往常一样渲染剩余的几何图形。这些阴影三角形就会永远绘制在地平面上方,因为没有深度比较。

        如果地平面有限制,例如,它是一个矩形,投影阴影可能会落在它之外,打破了视觉错觉。为了解决这个问题,我们可以使用模板缓冲。首先,把接收器绘制到屏幕和模板缓冲中,然后关闭z-buffer,然后绘制只在接收器范围内的阴影三角形,最后正常渲染剩余的场景。

        另外一种阴影算法是把阴影三角形渲染进一张纹理,这个纹理会被应用到地平面上。这个纹理是一种光照贴图(light map)。 正如我们所看到的,这种将阴影投射到纹理上的想法也允许在曲面上产生半阴影和阴影。这种技术的一个缺点是纹理可能被放大,一个纹素可能会覆盖多个像素,打破了视觉错觉。

        如果阴影环境并不是每帧直接发生变化,例如,光源和阴影投射物彼此之间并无移动,这个纹理可以复用。 如果没有发生变化,大多数阴影技术都可以从从一帧到另一帧的中间计算结果的重用中获益。

        所有的阴影发射者必须在光和地平面接收器之间, 如果光源处在物体的最高点下方,会有反阴影(antishadow)发生, 因为每个顶点都是通过光源的点来投影的。 正确的阴影和反阴影如图7.4所示。 如果我们投射一个在接收平面以下的物体,也会发生错误,因为它也不应该投射阴影。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第5张图片

图 7.4 左图展示的是正确的阴影,右图展示的是反阴影, 因为光源在物体的最上面的顶点之下。

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

7.1.2 软阴影(soft shadow)

        使用一些技术也可以使投影阴影变成软阴影。在这描述一种Heckbert和Herf提出的算法,这个算法的目的是在地平面生成一张纹理来展示软阴影。

        当光源有一定面积时,就会出现软阴影。 一种近似区域光效果的方法是通过在其表面放置几个精准光源来采样。 对于这些精准光源,都被渲染到一张纹理中并积累到一个缓冲区中。 这些纹理的平均值就是一个软阴影。注意,理论上,任何生成硬阴影的算法都用这类累加技术来生成半影。实际中,以交互的频率来做这类事情是站不住的,因为执行时间也需要考虑到。

        Heckbert和Herf使用了一种基于平截头体的方法来生成阴影。思想就是把光源当作观察者,地平面当作平截头体的远裁剪平面。 截锥体做得足够宽,以包围遮挡板。

        软阴影纹理是通过生成一系列的地平面纹理而形成的,以区域光源上不同的采样点为当前光源,对物体进行投影阴影到渲染到地平面纹理上。然后把这些纹理加起来进行求平均值,这个平均值就是我们要的阴影纹理,图7.5左图就是一个示例。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第6张图片

图 7.5  左图是采用的Heckbert和Herf的算法,使用了256个pass,右图采用的是Haines的算法,使用了一个pass。Haines的算法的本影太大了,尤其是在窗户和门处。

        对区域光源进行采样的算法的一个问题就是它看起来像它本来的样子:几个重叠的影子来自精准光源。同时 ,对n个阴影pass,只生成了n+1个明显的阴影。 大量的传递可以得到准确的结果,但是付出的代价太高。

        一个更高效的方法是使用卷积(convolution),即滤波。 在某些情况下,模糊单一点产生的硬阴影就足够了,并且可以产生半透明的纹理,可以与真实世界的内容混合。如图7.6所示。 然而,在物体与地面接触的地方,一个统一的模糊是无法令人信服的。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第7张图片

图 7.6 下落的阴影(Drop Shadow)。 阴影纹理是通过从上往下渲染出物体的阴影,然后模糊纹理,再渲染到地平面上。

        还有一些其他的方法可以获得更好的近似效果,但是需要额外的成本。例如,Haines算法是先算出一个硬阴影,然后渲染轮廓的梯度边缘,从黑暗的中心到白色的边缘,创造似是而非的半阴影。如图7.5右图所示。然而,这些半阴影不是物理正确的, 因为它们也延伸到了轮廓边缘内的区域。 所有这些方法都有不同的近似值和缺点,但是比平均一组大的阴影纹理要有效得多。

7.2 曲面上的阴影(Shadows on Curved Surfaces)

         将平面阴影扩展到曲面的一个简单方法是使用生成的阴影纹理作为投影纹理。从光源方向考虑阴影。 光能看见的,就能被照亮;它看不见的东西则在阴影里。 假设遮光板从光源方向上被渲染到另外一张白色纹理上。 这个纹理可以投射到接收阴影的表面上。在接收器上的每个顶点有一个(u,v)纹理坐标用于计算它,且纹理应用会用到它。 应用程序可以显式地计算这些纹理坐标。 这与前一节中的地面阴影纹理稍有不同,在前一节中,物体被投射到特定的物理平面上。 在这里,纹理是从光的角度拍摄的,就像投影机里的一帧胶片。

        当渲染时,投影阴影纹理会修改接受器表面, 它也可以与其他阴影方法相结合,有时主要用于帮助感知到物体的位置。 例如,在一个平台跳跃的视频游戏中,主角可能总是被直接赋予一个Drop阴影,即使角色处于完全的阴影中。

        纹理投影方法存在一些严重的缺陷。首先,应用程序必须识别哪些对象是遮光板,哪些对象是它们的接收者。 接收器必须由程序维护,使其比遮光板离光更远,否则阴影就会“向后投射(cast backward)”。另外,遮光板并不能给自己阴影。

         注意,可以通过使用预构建投影纹理来获得各种光照模式。 聚光灯是一个简单的方形投射纹理,它的内部有一个圆圈来定义光线。

7.3     阴影体(Shadow Volumes)

         Heidmann在1991年提出了一种基于Crow 's shadow volume的方法,该方法巧妙地利用模板缓冲区将阴影投射到任意物体上。 它可以用于任何GPU,因为唯一的要求是模板缓存。 它不是基于图像的,从而避免了采样问题,从而可以产生正确的尖锐阴影。 这有时可能是一个不利因素。 例如,一个角色的衣服可能会有褶皱,产生稀薄、坚硬的阴影,会有严重的混叠。 由于其不可预测的成本,体积阴影现在很少被使用。我们在这里对算法进行了简要的描述,因为它说明了一些重要的原则和基于这些原则的研究。

        首先,想象一个点和一个三角形。 把线穿过这个点以及该三角形的顶点,并延伸到无限远处,就得到了一个无限的三面金字塔。 三角形下面的部分,即不包括点的部分,是一个截断的无限金字塔,而上面部分只是一个金字塔,见图7.7。现在假设这个点是光源, 然后,在被截断的金字塔体(三角形下方)内的物体的任何部分都处于阴影中。这个体积被称为阴影体。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第8张图片

图 7.7  左图:点光源的光线通过三角形的顶点延伸,形成一个无限的金字塔。 右图:上半部分为金字塔,下半部分为无限截形金字塔,也称阴影体。所有在阴影体内的几何图形都在阴影中。

         假设我们查看某个场景,并通过一个像素跟踪来自眼睛的光线,直到光线击中要在屏幕上显示的对象。 当光线到达这个物体时,当它穿过正对着的阴影体的一个面时(即,面向观众),我们给一个计数器加一。因此,每次射线进入阴影计数器是递增的。 以同样的方式,每次射线穿过截形金字塔的背面时,我们给计数器减一,光线会从阴影中消失。 我们持续对计数器进行递增和递减,直到射线击中的物体需要显示的那个像素。 如果计数器大于0,则该像素处于阴影中;否则就不是。 这个原则也适用于有多个三角形投射阴影的情况。参见图7.8。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第9张图片

图 7.8   使用两种不同的计数方法计算阴影-体积交叉点的二维侧视图。在z-pass体积计数中, 当光线通过阴影体的前面(frontfacing)三角时,计数递增;当光线通过后面(backfacing)三角时,计数递减。因此,在点A,射线进入了两个阴影体,计数为+2,然后离开了两个阴影体,计数清零,所以这个点在光中。在z-fail体计数中, 计数从表面开始(这些计数以斜体显示)。在B点处的射线,z-pass方法给出的计数为+2(穿过了两个前面三角形),z-fail给出了相同的计数(穿过了两个后面三角形)。点C展示了z-fail阴影体积必须要有上限。 射线从点C出发,首先击中前面三角形,给出了- 1。 然后,它退出两个阴影体积(通过它们的结束端,这是此方法正常工作所必需的),净计数为+1。计数不为0,所以这个点在阴影中。 这两种方法对所观察的表面上的所有点都给出相同的计数结果。

         用射线做这件事很费时间。 但还有一个更聪明的解决方案: 模板缓冲可以帮我们计数。 首先,清除模板缓冲区, 其次,整个场景被绘制到帧缓冲中,只使用无光照材质的颜色,以获得颜色缓冲区中的这些着色组件和z-buffer中的深度信息。 第三,关闭z-buffer的更新和写入颜色缓冲区(尽管z缓冲测试仍在进行),然后绘制阴影体积的前面三角形。 在此过程中,增加在模板缓冲中需要绘制三角形的位置的值。 第四步,在另外一次Pass渲染过程中使用模板缓冲区再完成一次,这一次只绘制阴影体积的背面三角形。 在这次Pass中,模板缓冲区中绘制三角形的位置的值将递减。 当计数的加减都完成后,只有阴影体需要渲染的表面的像素是可见的(即不是被任何真实的几何图形所隐藏)。 在这一点上,模板缓冲区保持每个像素的阴影状态。 最后,再次渲染整个场景,这次只使用受光线影响的材质组件,并且只显示模板缓冲区中值为0的地方。 值为0表示射线从阴影中消失的次数与进入阴影体积的次数相同,这个位置被光照亮。

         这种计数方法是阴影体背后的基本思想。 阴影体算法生成的阴影示例如图7.9所示。 有一些有效的方法可以只使用一次Pass就可以实现算法, 然而,当一个物体穿透相机的近平面时,计数问题就会出现。 一种解决方案称为z-fail,计算的是隐藏在可见表面的背面而不是前面。此方案的简要摘要如图7.8所示。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第10张图片

图 7.9 阴影体。左图,一个角色投射出阴影,右图,是阴影体积的三角形模型。

         为每个三角形创建四边形会产生大量的overdraw。 也就是说,每个三角形将创建三个必须呈现的四边形。一千个三角形构成的球需要三千个四边形, 这些四边形中的每一个都可以跨屏幕。 一种解决方案是沿着物体的轮廓边缘只画那些四边形,例如,我们的球体可能只有50个轮廓边缘,那么我们只需要50个四边形。几何着色器可以用来自动生成这样的轮廓边缘。剔除和钳制技术也可用于降低充填成本。

         然而,阴影体算法仍然有一个可怕的缺点:极端的可变性。 想象视觉中有一个小三角形, 如果相机和光源处于完全相同的位置,阴影体的成本是最小的。 形成的四边形不会覆盖任何像素,因为它们是视图的边缘。 假设观察者现在绕着三角形旋转,让它一直在视野中。 当相机从光源移开时,阴影体积的四边形将变得更加可见,并覆盖更多的屏幕,导致更多的计算发生。 如果观察者恰好移动到三角形的阴影中,阴影体积将完全填满屏幕,与我们最开始的视图相比,需要花费大量的时间。 这种可变性使得阴影体在交互应用程序中不可用,在交互应用程序中,一致的帧率是非常重要。

7.4 阴影贴图(Shadow Maps)

         在1978年,Williams提出了一种通用的基于z缓冲的渲染器,可以用来快速生成任意物体上的阴影。 这个想法是渲染场景,使用z缓冲,并从光源的位置投射阴影。 凡是被光“看见”的都被照亮了,其余的都在阴影里。 当生成此纹理时,只需要了z缓冲。 可以关闭灯光、纹理并将值写入颜色缓冲区。 z缓冲区中的每个像素现在都包含最接近光源的对象的z深度。

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

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第11张图片

图 7.10 阴影贴图。 左上图,阴影贴图是通过将深度值存储到视图中的表面而形成的。右上图,眼睛观看两个位置。可以看到球体上的Va点,这个点也可以在阴影贴图上找到对应纹素a。 储存在那里的深度并不比Va点少,所以这个点被照亮了。 与纹素b中存储的深度相比,击中点Vb的矩形距离光线更远,因此处于阴影中。 左下图是以光的角度渲染一个场景,白色表示较远。右下图是用了这个阴影贴图渲染的场景。

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

        如果局部光源位于场景内部,并被阴影遮挡板包围,典型的解决方案是使用六视图立方体,类似于立方体环境映贴图。这些被称为全向性的阴影贴图(omnidirectional shadow maps)。 全向性的贴图的主要挑战是避免在两个独立贴图相交的接缝处出现瑕疵。 King和Newhall深入分析了问题并提供了解决方案, Gerasimov提供了一些实现细节。 Forsyth提出了一种适用于全向性的光源的通用多截锥体分割方案,该方案在需要的地方提供了更多的阴影贴图分辨率。 Crytek基于每个视图的投影截锥体的屏幕空间覆盖范围,为一个点光源设置六个视图的分辨率,并将所有的贴图存储在一个纹理图集中。

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

        根据定义,阴影的投射物是需在光的视截锥体内。 这个截锥体可以通过几种方式来扩大或收紧,这样我们就可以安全地忽略一些阴影投射物了。 想想那些肉眼可见的阴影接收器。 这组物体在光的视场方向上的最大距离内。 任何超过这个距离的东西都不能在可见的接收器上投下阴影。 同样地,可见接收器的集合可能会比光原来的x和y视图范围更小。如图7.11所示。 另一个例子是,如果光源在眼睛的视锥截体内部,那么这个视锥截体之外的任何物体都不能在接收器上投射阴影。 只渲染相关物体不仅可以节省渲染时间,还可以减小光的截锥体所需的尺寸,从而提高阴影贴图的有效分辨率,从而提高质量。 此外,如果光截锥体的近平面离光越远越好,远平面越近越好。 这样做可以提高z缓冲区的有效精度。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第12张图片

图 7.11 左图光的视图包含了眼睛的截锥体。中间图, 光的远平面剔除了蓝色三角形作为阴影投射者;近平面也做了类似的调整。 右图,光的截锥体的边缘面被用来约束可见的接收器,剔除了绿色的胶囊。

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

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第13张图片

图 7.12  阴影贴图的偏差瑕疵。左图,由于偏差太低,所以发生了自阴影。右图,高偏差导致了鞋子那块没有透射阴影。 阴影贴图的分辨率也太低,给了阴影一个块状的外观。

         一个常见的帮助避免(但不总是消除)各种阴影贴图瑕疵的方法是引入一个偏差因子。 在检查阴影贴图中找到的距离和测试位置的距离时,从接收器的距离中减去一个小偏差。如图7.13所示。 这个偏差可以是一个常数值, 但是在接收器大部分不是面对光的时候,这样做可能会失败。 一个更有效的方法是使用一个偏差,这个偏差与接收器对光的角度成比例。 表面越偏离光源,偏差越大,从而避免了这个问题。 这种类型的偏差称为slop scale bias。注意,如果一个表面直接面对光源, 它完全不受slop scale bias的影响。 因此,为了避免可能出现的精度误差,在slop scale bias的基础上使用了常数偏差slop scale bias也经常钳制在一些最大值,因为从光的角度看,当表面接近侧立时,切线值可能会非常高。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第14张图片

图 7.13 阴影偏差。在头顶光源下,表面被渲染成一个阴影贴图, 用竖线表示阴影贴图的像素中心。 遮挡板的深度记录在×点, 我们想知道表面上的三个采样点是否有光照。 最接近的阴影贴图深度值用相同的颜色×表示。 左图中,如果没有添加任何偏差,蓝色和橙色的样本将被错误地判定为处于阴影中,因为它们离光的距离比对应的阴影贴图深度更远。 中间图,从每个样本中减去一个恒定的深度偏差,使每个样本更靠近光源。 蓝色样本仍然被认为是在阴影中。 右图,阴影贴图是通过将每个多边形沿着光的方向按其斜率比例移动而形成的。所有的样本深度现在都比阴影贴图深度更近了,所以都被点亮了。

         Holbert提出了法向量偏移偏差(normal offset bias),它首先将接收器在世界空间的位置沿表面法向量偏移一点,与光的方向与几何法向量的夹角的正弦成正比。如图7.24所示。 这不仅改变了深度,还改变了在阴影贴图上测试样本的x坐标和y坐标。 当光和表面的角度变得更浅时,这个偏移量就会增加,采样点会变得离表面足够远以避免自阴影。 这种方法可以被想象成把采样点移动到接收器上方的“虚拟表面”。 这个偏移量(offset)是一个世界空间的距离,所以Pettineo建议按阴影贴图的深度范围缩放它。 Pesce提出了一种沿着摄像机视角方向的偏差(bias),这种偏差也可以通过调整阴影贴图的坐标来实现。 过多的偏差会导致光泄漏(light leaks)或彼得平移(Peter Panning), 在这种情况下,物体似乎漂浮在略高于下方表面的地方。 产生这种瑕疵的原因是因为物体接触点以下的区域,被向前推得太远,所以不会有阴影。

         一种避免自阴影问题的方法是只渲染背面到阴影贴图,称为第二深度阴影贴图(second-depth  shadow mapping), 这种方案在很多情况下都能很好地工作,特别是在不允许手工调整偏差的渲染系统中。 当物体是双面的、薄的或相互接触时,就会出现问题。 如果一个物体的网格模型的两边都是可见的,例如,一个棕榈叶或一张纸,自阴影可以发生,因为背面(backface)和正面(frontface)在同一个位置。 同样的,如果不执行偏差,问题可能发生在物体的轮廓边缘或薄物体附近,因为在这些区域,背向距离正面很近。 添加偏置可以帮助避免surface acne,但该方案更容易漏光,因为在接触点上接收器和遮挡板的背面没有分离。如图7.14所示。 选择哪种方案需要根据情况而定。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第15张图片

图 7.14  在头顶光源下的阴影贴图表面。左图中,表面正对着光源被发送给阴影贴图,用红色标记。 表面可能被错误地认为为阴影(“acne”),所以需要添加偏差远离光源。中间图,只有三角形的背面被渲染到阴影贴图中,偏差将这些遮光板向下推,使得光会泄漏到a点附近的地面上。 向前偏差会引起轮廓边界附近的有光照的点(标记为吧)会考虑进阴影中。右图中,在阴影贴图上每个位置的最近的正面和背面三角形之间的中点形成一个中间面,在点c附件可能会发生漏光( 第二深度阴影贴图也会发生), 因为最近的阴影贴图样本可能在这个位置左边的中间表面上,所以这个点会更靠近于光。

         注意,对于阴影贴图,对象必须是“水密的”(流形和封闭,即固体),或必须同时正面和背面都被渲染到贴图中,否则该物体可能不会完全投射阴影。 Woo提出了一种通用的方法,字面上来说,它试图在仅仅使用正面和背面之间找到一个折衷的方法。 这个想法是把实体物体渲染到阴影贴图上,并跟踪离光最近的两个表面。 这个过程可以通过深度剥离(depth peeling)或其他透明相关技术来完成。 两个对象之间的平均深度形成一个中间层,其深度用作阴影贴图,有时称为对偶阴影贴图(dual shadow map)。 如果物体足够厚,自阴影和光泄漏的影响就会最小化。 

         当观察者移动时,光的视截锥体会随着阴影投射物改变而经常改变大小。 这些变化反过来又导致阴影在帧与帧之间轻微移动。 这是因为光的阴影贴图是从光的不同方向采样的,而这些方向与前一组方向不一致。 对于平行光,解决方案是强制每个后续生成的阴影贴图在世界空间中保持相同的相对纹素光束位置。 也就是说,可以将阴影贴图看作是在整个世界空间添加了一个二维网格框架,每个网格单元表示贴图上的一个像素样本。 移动时,网格单元的不同集合生成阴影贴图。 换句话说,光的视图投射被强制到这个网格上以保持帧与帧之间的一致性。

7.4.1 分辨率增强(Resolution Enhancement)

        和使用纹理类型,理想情况下,我们想要一个阴影贴图纹素覆盖大约一个纹理像素。 如果我们有一个光源和眼睛在同一位置, 阴影贴图完美地与屏幕空间像素一一对应(并且没有可见的阴影,因为光线精确地照亮了眼睛看到的东西)。 一旦光的方向发生改变,每个像素的比例就会改变,就会发生错误。图7.15展示了一个例子。 由于前景中的大量像素与阴影贴图的每个纹素关联,因此阴影是块状的,质量很差。 这种不匹配称为透视走样(perspective aliasing)。如果一个表面和光几乎平行但是朝着观察者,单个阴影贴图纹素可以覆盖多个像素。这个问题称为投影走样(projective aliasing)。如图7.16。 可以通过增加阴影贴图的分辨率来降低块数量,但这是以增加内存和运算为代价的。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第16张图片

图 7.15  左图使用的是标准的阴影贴图,右图使用的是LiSPSM。 每个阴影贴图的纹素的投影都显示出来了。两种阴影贴图有相同的分辨率, 不同之处在于LiSPSM改变了光的矩阵,在靠近观察者的地方提供了更高的采样率。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第17张图片

图 7.16  左图的光源几乎就在头顶。 阴影的边缘有点粗糙,因为和观察视图相比它的分辨率有点低。 右图,光线方向接近地平线,所以每个阴影纹素覆盖了很多水平屏幕区域,因此产生了更多的锯齿状边缘。

         还有另一种方法来创建光的采样模式,使其更接近相机的模式。 这是通过改变场景朝光投射的方式来实现的。 通常我们认为视图是对称的, 视向量在截锥体的中心。 然而,视图方向仅仅定义了一个视图平面, 但不包括采样的像素。 定义截锥体的窗口可以在这个平面上移动、倾斜或旋转, 创建一个四边形,给了视图空间一个不同的映射。 仍然按一定的间隔对四边形进行采样,因为这是线性变换矩阵的性质。 采样率可以通过改变光的视图方向和观察窗口的边界来改变。如图7.17。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第18张图片

图 7.17 给定一个头顶光源, 左图中的地板的采样率与眼睛的速率不匹配,右图中通过改变光的视图方向和投影窗口, 采样率会偏向于在眼睛附近产生更高密度的纹素。

         将光的视图映射到眼睛有22个自由度。 探索这个解空间(solution space)导致了几种不同的算法来解决更好地匹配光线的采样率和眼睛的采样率问题,包括透视阴影贴图(perspective shadow map,PSM),梯形阴影贴图( trapezoidal shadow map, TSM),和光空间透视阴影贴图(light space perspective shadow map,LiSPSM)。如图7.15和7.26所示。

         这些矩阵曲解算法的一个优点是,除了修改光的矩阵外,不需要额外的工作。 每种方法都有自己的优缺点, 因为每种方法都可以帮助匹配某些几何图形和照明情况下的采样率,而使其他情况下的采样率更糟。

         一种光照情况,即当光在摄像机前并指向它时,矩阵曲解算法就会失效。 这种情况被称为dueling 截锥体,或者更通俗的说法是“聚光灯下的鹿,deer in the headlights”。 在眼睛附近需要更多的阴影贴图采样,但是线性曲解只会使情况变得更糟。 这个和其他的问题,如质量的突然变化和在摄像机移动过程中产生的阴影的不稳定的质量,都使得这些方法失宠。

         Blow独立实现了这样一个系统:生成一组固定的阴影贴图(可能是不同分辨率的),覆盖场景的不同区域。 在Blow的方案中,四个阴影贴图被嵌套在观察者周围。 这种情况下,附近的物体就可以得到高分辨率的贴图,而远处物体的分辨率会下降。 Forsyth提出了一个相关的想法,为不同的可视对象集生成不同的阴影贴图。在他的设置中避免了如何处理物体跨越两个阴影贴图边界的转换问题,因为每个对象都有且只有一个与之关联的阴影贴图。Flagship Studios融合了这两种思想,开发出一套系统。 一个阴影贴图用于附近的动态对象,另一个映射用于附近静态对象的网格部分,第三个映射用于整个场景中的静态对象。第一个阴影贴图是每帧都生成,其他两个阴影贴图只需要生成一次,因为光源和几何物体都是静态的。

         在2006年Engel, Lloyd等人,Zhang等人独立地研究了通过平行于视图方向的切片,将视图截体的体积分割成几个部分。如图7.18所示。 随着深度的增加,每个连续体积的深度值大约是先前体积的深度值的两到三倍。对每一个视体, 光源可以形成一个紧密包围它的截锥体,然后生成一个阴影贴图。 通过使用纹理图集或纹理数组,可以将这些不同的阴影贴图视为一个大型纹理对象,从而使缓存访问延迟最小化。图7.19展示了明显的质量提升的对比。 恩格尔将这种算法命名为级联阴影贴图(cascaded shadow maps,CSM),它比张的术语“平行分割阴影贴图(parallel-split shadow maps)”更常用,但两者在文献中都出现过,而且实际上是相同的。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第19张图片

图 7.18  左图,沿着眼睛观察方向的视图截锥体被分成四个部分。 右图,给每个体块创建的包围盒,包围盒决定了方向光下的每个体块渲染的阴影贴图。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第20张图片

图 7.19  左图,场景的可视范围太宽导致一个2048×2048分辨率的单个阴影贴图会显示出透视走样。右图,4个1024x1024的阴影贴图沿视图轴放置可以显著提高质量。

         这种算法实现简单,能够覆盖较大的场景区域,且结果合理,鲁棒性强。 Dueling frusta问题可以通过在接近眼睛的地方采用更高的采样率来解决,并没有其他严重的问题。 由于这些优点,级联阴影贴图在许多应用中得到了应用。

         虽然可以使用透视曲解(perspective warping)将更多的样本压缩到单个阴影贴图的细分区域, 规范是为每个级联使用单独的阴影贴图。 从图7.18和图7.20可以看出,每个贴图所覆盖的区域是不同的。 较小的视体为更近的阴影贴图在需要的地方提供了更多的采样。如何确定贴图分割开来的各个部分的z-buffer的范围—被称为z-partitioning。 一种方法是对数分割(logarithmic partitioning), 对于每个级联贴图,远平面到近平面的距离的比例是相同的。

其中,n和f是整个场景的近平面和远平面,c是贴图的数量,r是最终比例。 例如,如果场景中最近的物体是1米远,最大距离是1000米,我们有三个级联贴图,则

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

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第21张图片

图 7.20 阴影级联可视化。 紫色、绿色、黄色和红色表示的是离我们最近的到最远的级联。

         挑战在于如何设置近平面。 如果近平面设置得离眼睛太远,物体可能会被近平面截去。 Lauritzen等人提出了样本分布阴影贴图(sample distribution shadow maps, SDSM),它使用前一帧的z-depth值来决定使用两种方法中的一种来进行划分。

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

         第二种方法同样也是分析深度缓冲区的值,生成一个称为直方图(histogram),记录z-depth的分布范围。 除了找到紧凑的远近平面外,图中还可能有空隙(完全没有对象)。 任何划分的平面都会被添加到存在物体的地方,从而使级联贴图具有更高的z-depth精度。

        在实际中,第一种方法更为普遍,比较快( 通常每帧在1毫秒范围内),效果也好,如图7.21所示。

《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第22张图片

图 7.21  深度界限(depth bounds)的效果。左图, 没有特殊的处理对近、远平面进行调整。 右图,采用了SDSM来寻找更紧密的界限。 注意每个图像左边缘附近的窗口框, 二楼花坛下面的地方, 还有一楼的窗户, 由于松散的视图边界而导致的欠采样。 虽然渲染这些特定的图像用的是指数阴影贴图(exponential  shadow maps), 但是提高深度精度的想法对于所有的阴影贴图技术都是有用的。

        就像使用单一阴影贴图一样, 由于光线采样在帧与帧之间的移动而产生的闪烁是一个问题,当物体在级联之间移动时可能会变得更糟。 在世界空间中保持稳定采样的方法有很多,每种都有自己的优势。 当一个物体跨越两个阴影贴图之间的边界时,阴影的质量会发生突变。 一种解决方案是让视体略微重叠。 对这些重叠区域,采集的样本会从相邻的阴影贴图中收集并进行混合。 另一种方法是,在这个区域使用抖动(dithering)进行单一采样。

        在实时渲染中, 如果在带有多个灯光的大型场景中,所有的灯光在任何时候都是活跃的,则可能会被计算淹没。 如果在视锥截体内看不见的体积空间,则该空间内的遮挡物体不需要进行计算。 Bittner等人利用遮挡剔除(章节19.7)从眼睛找到所有可见的阴影接收器, 然后从光线的角度将所有潜在的阴影接收器渲染到模板缓冲遮罩中。 这个遮罩编码了从光中可以看到的可见阴影接收器。 为了生成阴影贴图,他们使用遮挡剔除来渲染来自光的物体,并使用遮罩来剔除没有接收器的物体。各种剔除策略同样也适用于光源。 因为辐照度随着距离的平方而衰减, 一种常用的技术是在一定的阈值距离后剔除光源。 例如,第19.5节中的门户剔除技术(portal culling technique)可以发现哪些光影响哪些单元。这是一个活跃的研究领域,因为性能收益是相当可观的。

7.5  百分比渐进滤波(Percentage-Closer Filtering)

         阴影贴图技术 一个简单的扩展是可以提供 伪软(pseudo-soft)阴影 这种方法还可以 帮助改善当单个光样本单元覆盖多个屏幕像素时,会导致阴影看起来是块状的分辨率问题 解决方案类似于纹理放大(第6.2.1节)。 不是从阴影贴图中提取单个样本,而是拾取最近的四个样本。 这项技术并没有在深度之间进行插值,而是对与表面深度进行比较的结果进行插值。 也就是说,表面的深度分别与四个纹素的深度进行比较,然后分别对每个阴影贴图采样来确定该点处于光或阴影中。 这些结果,即0表示阴影,1表示光,然后双线性插值来计算光对表面位置的实际贡献。 这种过滤的结果是一个人为的软阴影。 这些半影会根据阴影贴图的分辨率、摄像机的位置和其他因素而改变。 例如,分辨率越高,软化边缘的范围就越窄。尽管如此,一点点的半影和平滑总比没有好。
         这种从阴影贴图中检索多个样本并混合结果的思想称为百分比渐进过滤(percentage-loser filtering,PCF) 区域光(area lights)会生成柔和的阴影 到达表面某一位置的光量是该位置可见光面积之比。 PCF试图通过反转这一过程来近似精准(或定向)光源的软阴影。 它不是从一个表面位置找到光的可见区域,而是从一组接近原始位置的表面位置中找到精准光源的可见性。如图7.22所示。 “百分比渐进滤波”指的是最终的目标,即找到在光照下可见样本的百分比。 有多少光的百分比被用来处理表面阴影。
《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第23张图片
图 7.22  左图中,来自区域光源的棕色线展示了半影的形成。 对于接收器上的一个点P,接收器收到的光量,可以通过计算区域光源所在表面上点,找出其中有多少没有被任何遮挡器给挡住。 右图中,点光源不会投射出半影。 PCF通过反转这一过程来近似一个区域光的效果:给定一个位置,它在阴影贴图上的一个合适的区域上采样,以获得被照亮的样本的百分比。 红色椭圆表示阴影贴图上采样的区域。 理想情况下,这个圆盘的宽度与接收器和遮挡板之间的距离成正比。
 
         在PCF中,位置是在表面位置附近,深度大致相同,但在阴影贴图上的不同纹素位置。 检查每个 位置的可见性, 混合 这些 布尔值 结果,有光照的或无光照,得到一个软阴影。 注意,这个过程是非物理的: 这个过程不是直接对光源进行采样,而是对表面本身进行采样。 到遮挡板的距离不影响结果,所以阴影有相似大小的半影。 尽管如此,这种方法在许多情况下提供了一个合理的近似值。
         一旦确定了采样区域的宽度,则如何避免走样的采样方式变得很重要。 如何在阴影 贴图位置附近进行 采样和过滤有很多变量, 包括要采样的区域有多宽,要使用多少个样本,采样模式,以及如何对结果进行加权。 在API能力较差的情况下,可以采用类似于双线性插值的特殊纹理采样模式来加速采样过程,访问最近的四个位置。 不是混合结果,而是将四个样本中的每一个与给定的值进行比较,然后返回通过测试的比率。 然而,在规则的网格模式中执行最近邻居采样可能会产生明显的走样。
         DirectX 10为PCF引入了单指令双线性滤波(single-instruction bilinear filtering)支持,结果更加平滑。与最近邻居采样相比,有了相当大的视觉进步,但是对规则的网格模式采样还是有问题。 最小化网格模式的一个解决方案是使用预先计算的 泊松分布模式(Poisson distribution pattern)进行采样 ,如图7.23所示。 这种分布将样本分散开来,使得它们既不在彼此附近,也没有固定的模式。 这种走样可以通过绕其中心随机旋转样本分布来避免,从而将其转化为噪声。
         自阴影问题和漏光问题,在使用PCF情况下可能会变得更糟。在 假设一个样本在阴影贴图上的距离不超过一个纹素的情况下, 坡度比例尺的偏差会根据它与光线的角度将表面推离光线。通过在表面位置附近使用较大范围的采样,一些测试样本可能会被真实的表面所阻挡。
         Burley提出了 偏差圆锥体(bias cone) ,在 偏差圆锥体 中,每个样本朝光偏移的距离与它与原始样本的距离成正比。 Burley建议 斜率为2.0,并保持一个小的恒定偏差 。如 图7.24。
《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第24张图片
图 7.24 额外的阴影偏差方法。 对于PCF,在原始样本位置(五个点的中心)周围取几个样本。 所有这些样本都应该有光照。 左图中,形成了一个偏差圆锥体,样本被移到原本位置的上面, 通过增加圆锥体的陡度,把右边的样品拉到足够近的地方,让它们被照亮,这样做的风险是,其他地方的采样点(没有显示出来)的漏光会增加,而这些样本确实是被遮蔽了的。 中间图中,所有的样本都调整到位于接收平面上。 这对凸面效果很好,但在凹处可能适得其反,如左侧所示。 在右图中,法向量偏移偏差使样本点沿着表面的法向移动,与法向量和光之间夹角的正弦成正比。 对于中心样本,这可以被认为是移动到原始表面之上的一个假想表面。这种偏差不仅影响深度,还会改变用于测试阴影贴图的纹理坐标。
 
         PCF的一个问题是,由于采样区域的宽度保持不变,阴影会呈现出均匀的柔和,所有的阴影都具有相同的半阴影宽度。在某些情况下,这是可以接受的,但在遮挡板和接收器之间有地面接触的地方,这是不正确的。如图7.25。
《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第25张图片
图 7.25 百分比渐进滤波和百分比渐进软阴影。左图是有一点PCF滤波的硬阴影。中间图,等宽软阴影。 右图,物体与地面接触的地方,具有适当硬度的变宽软阴影。
 

7.6 百分比渐进软阴影(Percentage-Closer Soft Shadow)

         2005年,Fernando发表了一篇颇具影响力的研究报告,名为 百分比渐进软阴影(percentage-closer soft shadows,PCSS) 它试图通过在阴影贴图上搜索附近区域来找到所有可能的遮挡板。 利用 这些遮挡板到位置的平均距离来确定采样区域的宽度:
其中,dr是接收器到光源的距离,do是遮挡板的平均距离。 换句话说,当平均遮挡板离接收器越远,离光越近时,采样的表面积宽度就越大。 在 图7.22中,考虑移动遮挡器带来的影响。
         如果没有发现遮挡物,则位置是完全亮的,不需要进一步处理。 类似地,如果位置完全闭塞,处理可以结束。 此外则需要对 感兴趣的区域进行采样,计算光的近似贡献。 为了节省处理花销,采样宽度可以用来改变采样的数量。 其他的技术也可以实现,例如,使用低采样率来处理不太重要的远距离软阴影。
         这种方法的一个缺点是,它需要在阴影贴图上一个较大的区域进行采样以找到这些遮挡板。 使用旋转的泊松盘模式可以帮助隐藏采样不足带来的瑕疵。 Jimenez指出,泊松采样在运动状态下可能是不稳定的,并发现使用 介于抖动函数和随机函数之间形成的螺旋模式在帧与帧之间的结果更好
         Sikachev等人详细讨论了使用SM 5.0中的特性来更快地实现PCSS,该特性由AMD引入,通常以其名称来称呼,即接触硬化阴影(contact hardening shadows,CHS)。 这个方案还解决了基本PCSS的另一个问题:半影的大小受到阴影贴图分辨率的影响。如图7.25所示。 通过首先生成阴影贴图的mipmaps,然后选择最接近用户定义的世界空间内核大小的mip level,可以最小化这个问题。 一个 8×8的 采样 区域,找到平均块深度,只需要16个  GatherRed() 纹理调用。 一旦找到半影估计,高分辨率的mip level用于阴影的锐利区域,而低分辨率的mip level用于阴影的柔软区域。
         CHS已经被用于大量的电子游戏中,并且还在继续研究。 例如,Buades等人提出了 可分离软阴影贴图(separable soft shadow mapping,SSSM) ,该方法将对网格采样的PCSS过程分解为可分离的部分,并尽可能在像素之间重用元素。 对于每像素需要多个样本的加速算法,有一个概念已经被证明对加速逐像素的多采样的算法很有用,那就 是分层的最小/最大阴影贴图 阴影贴图的深度通常不能取平均值, 每个mipmap level的最小值和最大值可能很有用 也就是说,可以形成两个mipmap,一个保存每个区域中最大的z- depth(有时称为HiZ),另一个保存最小的z- depth。 给定纹素的位置、深度和要采样的区域,可以使用mipmaps快速确定完全光照和完全阴影的条件。 例如,如果纹素的z-depth大于为mipmap相应区域存储的最大z-depth,那么纹素必然处于阴影中,不需要进一步的采样。 这种类型的阴影贴图使得确定光可见性更有效。
         像PCF这样的方法是通过采样 接收器 附近的位置来工作的。 PCSS的工作原理是找到附近遮挡板的平均深度。 这些算法不直接考虑光源的面积,而是对表面 附近 进行采样,并受阴影贴图分辨率的影响。 PCSS背后的一个主要假设是,平均块对半影大小的估计是合理的。 当两个遮挡器,比如一盏路灯和一座远山,在一个像素处遮挡了相同表面的部分时,这种假设就被打破了,并可能导致瑕疵。 理想情况下,我们想要确定在 一个单一的接收器位置 有多少面积的光源是可见的。 一些研究人员已经探索了使用GPU的 反向投影(backprojection) 。这个想法是把每个接收器的位置当作一个视点,把区域光源当作视图平面的一部分,并把遮挡器投射到这个平面上。
 

7.7 经过滤波的阴影贴图(Filtered Shadow Maps)

         一种允许对生成的阴影贴图进行过滤的算法是Donnelly和 Lauritzen的  方差阴影贴图(variance shadow map ,VSM) 该算法在一张贴图中存储深度,在另一张贴图中存储深度的平方, 在生成贴图时可以使用MSAA或其他反走样方案。 这些贴图可以 被模糊(blurred) ,mipmapped,置于求和区域表中,或任何其他方法。 将这些贴图视为可过滤纹理是一个巨大的优势,因为从它们中检索数据时,整个采样数组和滤波技术都可以发挥作用。
         首先,对于VSM,在接收器的位置对 深度贴图进行 采样(仅一次),以返回靠近光遮挡器的平均深度。 当这个平均深度M 1,称为 first  moment ,比阴影接收器上的深度t大时,接收器认为完全在光照中。当平均深度要比接收器的深度小时,则应用下列方程:
其中,Pmax是处于 光照下样本的 最大百分比, σ的平方是方差,t是接收器的深度,M1是阴影贴图中平均期望深度。深度平方阴影贴图的样本M2,称为 second moment ,用来计算方差:
Pmax是接收器的可见百分比的上限。 实际的光照百分比P不能大于这个值。 这个上界来自于 切比雪夫不等式 的单边变量。 该方程试图利用概率论来估计在表面位置上遮挡物的分布有多少超出了表面离光的距离。 Donnelly和Lauritzen证明了 平面遮挡器和平面接收器是固定深度, P= Pmax ,因此 方程7.7可以作为许多真实阴影情况的一个很好的近似。
         Myers解释了为什么这个方法有效。 面积的方差在阴影边缘处增大。 深度差异越大,方差越大。 (t − M 1 ) 2 是可见百分比的一个重要决定因素。 如果这个值略高于0, 这意味着平均遮挡深度比接收器更接近于光,而Pmax接近于1(全光照)。 这会发生在半影完全被照亮的边缘处。 进入半影,平均遮挡深度更接近光,所以这一项变大,Pmax减小。 与此同时,方差本身在半影内也在变化,从边缘 近于零 变化到最大的方差,即遮挡体的深度不同,但面积相等。 这 些项相互平衡,在半影上形成线性变化的阴影。如图7.26给出了几种算法的比较。
图 7.26  左上是标准阴影贴图。右上是透视阴影贴图,越靠近观察者阴影贴图的纹素密度越大。左下是百分比渐进软阴影,随着遮挡物离接收器之间距离的增加,阴影变得越软。右下是软阴影宽度不变情况下的方差阴影贴图,每个像素都有单个方差贴图采样。
 
         方差阴影映射的一个重要特点是,它可以优雅地处理由几何引起的表面偏差问题。
         总体而言,由于GPU可以有效地优化纹理,VSM在处理时间上有了显著的改善。 PCF需要更多的样本,因此需要更多的时间,来避免生成软阴影时的噪音,而VSM可以只使用一个高质量的样本来确定整个区域的效果,并生成一个平滑的半影。 这种能力意味着 在算法的限制范围内, 阴影可以不增加成本的同时进行任意柔化。
         与PCF一样,滤波核的宽度决定了半影的宽度。 通过找到接收器和最近的遮挡物之间的距离,可以改变内核宽度,从而产生令人信服的软阴影。 Lauritzen详细介绍了如何使用求和区域表来生成更好的阴影。图7.27就是一个例子。
《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第26张图片
图 7.27 方差阴影贴图,从左到右距离光源的距离依次增大。
 
《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第27张图片
图 7.28 左图采用了方差阴影贴图,右图,一个三角形(没显示出来)给茶壶投射了一个阴影,在地面上引起了很明显的错误。
 
        级联阴影贴图技术也可以应用于滤波后的贴图,以提高精度。级联ESM相对于标准级联贴图的一个优点是可以为所有级联设置一个单一的偏差因子。Chen和Tatarchuk详细讨论了级联过程中遇到的各种漏光问题和其他问题, 并提出了一些解决方案。
 
        经过滤波的阴影贴图可以看作是PCF的一种廉价形式,它只需要很少的样本。像PCF,这些阴影都有一个恒定的宽度。这些滤波方法都可以与PCSS联合使用来生成可变宽度的半影
 

7.8 体阴影技术(Volumetric Shadow Techniques)

         透明物体会衰减并改变光的颜色。 对于某些透明对象集,可以使用与第5.5节中讨论的类似的技术来模拟这种效果。 例如,在某些情况下,可以生成第二种类型的阴影贴图。 将透明对象渲染到它上,并存储最近的深度和颜色或alpha覆盖范围。 如果接收器没有被不透明的阴影贴图阻挡,那么会测试透明深度贴图,如果被遮挡,则根据需要检索颜色或覆盖范围。 这个想法会联想到7.2节中的光投射,存储的深度需要避免在透明物体和光之间投射到接收器上。 这种技术不能应用于透明对象本身。
         自阴影对于头发和云彩等物体的真实感渲染是至关重要的,因为这些物体要么很小,要么是半透明的。 单一深度的阴影贴图在这种情况下是行不通的。 Lokovic和Veach首先提出了 深度阴影贴图(deep shadow maps) 的概念,在这个概念中, 每个阴影贴图的纹素都存储了光线随深度下降的函数 这个函数通常由一系列不同深度的样本来近似,每个样本都有一个不透明度值。 在贴图中给定两个样本,把给定定位置深度包括起来,然后用这两个样本找阴影效果。 面临的挑战是 GPU 如何高效地生成和评估这些函数。
         Kim和Neumann首先提出了一种基于GPU的方法,他们称之为 不透明度阴影贴图(opacity shadow maps)  贴图 只存储了用一组 固定的深度数组生成的 不透明度。 然而,由于深度切片都是平行且均匀的,因此需要大量的切片来隐藏由于线性插值造成的切片之间的不透明伪影。 Yuksel和Keyser通过创建更接近模型形状的不透明贴图来提高效率和质量。 这样做可以减少所需的层数,而每一层的评估对最终的图像变得更为重要。
         为了避免依赖于固定切片的一些设置,提出了更多的自适应技术。 Salvi等人提出了 自适应体阴影贴图(adaptive volumetricshadow maps) ,每个阴影图的纹素都存储了不透明度和层深度。 像素着色操作在光栅化时对数据流(表面不透明度)进行有损压缩。 这避免了需要无限量的内存来收集所有样本,并以一个集合来处理它们。 该技术类似于深度阴影贴图,但在是在像素着色器中动态完成压缩步骤。 将函数表示形式限制在一个较小的、固定数量的存储不透明度/深度对( opacity/depth pairs )上,可以使GPU上的压缩和检索更高效。 这个代价比简单的混合要高,因为需要读取、更新和写回曲线,并且它取决于用来表示曲线的点的数量。 在这种情况下,这项技术还需要支持UAV和ROV功能(3.8节结尾处)的最新硬件 。参见图7.29中的示例。
《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第28张图片
图 7.29 利用自适应体阴影贴图渲染的烟雾和毛发。
 
         在游戏GRID2中,使用了自适应的体阴影贴图方法来实现真实的烟雾渲染,平均成本低于2 ms/帧。 Furst等人描述并提供了他们实现一个视频游戏的深度阴影贴图的代码。 他们使用链表来存储深度和阿尔法值,并使用指数阴影贴图来提供光照和阴影区域之间的软过渡。
 

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

         阴影贴图方法的流行有几种因素, 它们的成本是可预测的,能够很好地适应场景大小的增加,最坏的情况也与图元的数量成线性关系。  他们很好地映射到GPU上,因为他们依赖光栅化来定期采样在沿着光的视图下的世界。 然而,由于这种离散的采样,会出现问题,因为眼睛看到的位置与光看到的位置不是一一对应的。 当光线对表面的采样频率低于人眼时,就会出现各种各样的问题。 即使采样率是可比较的,也存在偏差问题,因为表面采样的位置与肉眼看到的位置稍有不同。
         阴影体提供了一个精确的分析方案,因为光线与表面的相互作用产生了一组三角形,这些三角形定义了任何给定的位置是有光照的还是处在阴影中。 该算法在GPU上实现时不可预测的代价是一个严重的缺点。 近年来所探索的改进是诱人的,但还没有“存在证据”证明它被用于商业应用。
         另一种分析阴影测试的方法在长期内很有潜力: 射线追踪(ray tracing) 在第11.2.2节中有详细描述,基本思想非常简单,特别是对于阴影。 光线从接收器位置射向光源。 如果发现任何物体阻挡了射线,接收器就处于阴影中。 快速射线跟踪器的代码主要用于生成和使用分层数据结构,以最小化每条射线所需的对象测试数量。 为动态场景的每一帧构建和更新这些结构是一个有几十年历史的主题,也是一个持续的研究领域。
         另一种方法是 使用GPU的栅格化硬件 来查看场景,除了 z-depth值,还需要在每个光线的网格单元中存储关于遮光板边缘的额外信息。 例如,想象在每个阴影贴图的纹素中存储一个三角形列表,这些三角形覆盖的网格单元有 重叠 这样的列表可以通过保守的栅格化生成,在这种情况下,如果三角形的任何部分与像素重叠,而不仅仅是像素的中心,那么三角形就会生成一个片元。 这种方案的一个问题是,每个纹素的数据量通常需要限制,这反过来会导致在确定每个接收器位置的状态时出现不准确。 考虑到GPU的 现代链表原则(modern linked-list principles) ,每像素存储更多的数据当然是可能的。 然而,除了物理内存限制之外,在每个纹素的列表中存储可变数量的数据是一个问题,GPU处理可能变得极其低效,虽然一个warp有少许片元线程(fragment threads),这些线程需要检索和处理很多项目,然而其他剩余的线程是空闲的,处于等待状态。 构建一个着色器来避免由于动态“if”语句和循环而导致的线程分散,这对性能至关重要。
         选择存储三角形或其他数据到阴影贴图中,并用以测试接收器表面位置,也是一种解决这类问题的方法。 Johnson等人和Aila和Laine首次提出的保存接收器位置的概念,称为不规则z缓冲(irregular z-buffer,IZB) 这个名称有点误导人因为缓冲区本身对于阴影贴图来说是一个正常的、规则的形状。 相反,缓冲区里的内容是不规则的,因为每个阴影贴图纹素将会有一个或多个接收器位置存储在其中,或者可能根本没有。如图7.30所示。
《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第29张图片
图 7.30 不规则z缓冲。左上图中,沿着眼睛的视图在像素中心生成一组点。 两个三角形形成一个立方体的面。右上图中,这些点的来自沿着光线的视图。左下图中,使用了阴影贴图网格。 对于每个纹素会生成一组都在网格单元内的点。 右下图中,通过保守的栅格化对红色三角形进行阴影测试。 每个接触到的纹素上,以淡红色显示,它的列表中的所有点都会被测试三角形的光的可见度。
 
         利用Sintorn等人和Wyman等人提出的方法,一个多Pass算法创建IZB,并测试其内容的从光可见性。 首先,从眼睛视图渲染场景,找出从眼睛看到的表面的z-深度。 这些点被转换到光的视图中,并从光的截锥体形成紧密的边界。 然后这些点被放置在光的IZB中,每个点都被放置在相应的纹素上的一个列表中。 注意,有些列表可能是空的,这些表面虽然可以被光看到但没有被眼睛看到。遮光板经过 保守的光栅化到光的IZB,以 确定是否有任何点被隐藏并因此在阴影中。 保守的栅格化确保了,即使一个三角形没有覆盖光的纹素的中心,它也会被测试在一些可能重叠的点上。
         可见性测试发生在像素着色器中。 测试本身可以被可视化为射线跟踪的一种形式。 光线是从图像点的位置出发到光源的位置。 如果一个点在三角形内并且比三角形的平面更远,它就被隐藏了。 一旦所有的遮挡器被光栅化,光可见的结果被用来对表面着色。 这种测试也称为截锥体追踪(frustum tracing), 因为三角形可以被认为是一个视截锥体,检查它体内包含的点。
         仔细的编码是使这种方法与GPU良好工作的关键。 怀曼等人注意到他们的最终版本比最初的原型快两个数量级。 这种性能提升的部分原因是直接的算法改进,比如剔除表面法线背光的图像点(因此总是没有光照), 避免为空纹素生成片元 其他的性能提升来自于改进GPU的数据结构,以及通过在每个纹素中使用短但长度相似的点列表来最小化线程发散。 图7.30展示了使用长列表但低分辨率的阴影贴图。 理想情况是每个 图像点一个列表。 较高的分辨率可以令列表变短,但也增加了由遮光板生成的用于评估的片元的数量。
         从图7.30左下图中可以看出,由于透视效果的影响,地面平面上可见点的密度左侧明显高于右侧。 使用级联阴影贴图可以通过将更多的光贴图分辨率聚焦在眼睛附近来帮助降低这些区域的列表大小。
         这种方法避免了其他方法的采样和偏差问题,并提供了完美的锐利阴影。 出于审美和感觉的原因,通常需要软阴影,但是遮挡板附加可能会有偏差问题,比如Peter Panning。 Story和Wyman研究了混合阴影技术。 其核心思想是利用遮光板距离来混合IZB和PCSS阴影,遮光板距离较近时用硬阴影,距离较远时用软阴影。如图7.31所示, 对附近的物体来说阴影质量很重要 所以IZB的成本可以通过在一个选定的子集上使用这种技术来降低 该解决方案已成功应用于视频游戏中。本章从这样一个图开始,图7.2。
     《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows_第30张图片
图 7.31  左图,PCF为所有物体提供了均匀的软阴影。 中间图,PCSS通过与遮挡器的距离来柔化阴影,但是树枝的阴影覆盖了板条箱的左侧边缘,产生了伪影。右图中, IZB的锐利阴影与PCSS的柔和阴影混合后得到的结果效果很好。
 
 
 
 

你可能感兴趣的:(读书笔记)