改进阴影深度映射的常见技术

阴影映射于 1978 年首次引入,是向游戏添加阴影的常用技术。 三十年后,尽管硬件和软件取得了进步,但阴影项目(即闪闪发光的边缘、透视锯齿和其他精度问题)依然存在。

此技术文章概述了一些常见的阴影深度贴图算法和常见项目,并介绍了可用于提高标准阴影映射质量的几种技术(难度范围从基本到中间)。 向游戏添加基本阴影映射通常很简单,但了解阴影项目的细微差别可能很困难。 本技术文章专为已实现阴影的中级图形开发人员编写,但不完全了解特定项目出现的原因,并且不确定如何解决它们。

选择正确的技术来缓解特定项目并不重要。 解决阴影贴图的缺陷时,质量差异可能会令人印象深刻 (图 1) 。 正确实现这些技术可极大地改善标准阴影。 本文中介绍的技术在 DirectX SDK 的示例 CascadedShadowMaps11 中实现。

图 1. 具有严重项目的阴影 (左) ,在实现本文所述的技术后 (右)

具有严重项目的阴影 (左) ,在实现本文所述的技术后 (右)

阴影深度映射评审

阴影深度贴图算法是一种双传递算法。 第一个通道在光线空间中生成深度贴图。 在第二次传递中,此映射用于将每个像素在光空间中的深度与光空间深度映射中的相应深度进行比较。

图 2. 阴影场景的关键部分

阴影场景的关键部分

传递 1

场景如图 2 所示。 在第一次传递 (图 3) 中,几何图形从光线的角度呈现到深度缓冲区中。 更具体地说,顶点着色器将几何图形转换为光线视图空间。

第一次传递的最终结果是一个深度缓冲区,其中包含从光线角度出发的场景深度信息。 现在,这可以在传递 2 中用于确定哪些像素被光遮挡。

图 3. 基本阴影映射的第一次传递

基本阴影映射的第一次传递

传递 2

在图 4) (第二次传递中,顶点着色器转换每个顶点两次。 每个顶点将转换为相机的视图空间,并作为位置传递给像素着色器。 每个顶点也由光线的视图投影纹理矩阵转换,并作为纹理坐标传递给像素着色器。 视图投影纹理矩阵是用于在传递 1 中使用一个附加转换呈现场景的相同矩阵。 这是一种转换,用于缩放和转换从视图空间 (-1 到 1 x 和 Y) 到纹理空间 (0 到 1 in X 和 1 到 0 in Y) 。

像素着色器接收内插位置和内插纹理坐标。 执行深度测试所需的所有内容现在都位于此纹理坐标中。 现在可以通过使用 X 和 Y 纹理坐标为第一次传递的深度缓冲区编制索引,并将生成的深度值与 Z 纹理坐标进行比较来执行深度测试。

图 4。 基本阴影映射的第二次传递

基本阴影映射的第二次传递

阴影映射项目

阴影深度贴图算法是最广泛使用的实时阴影算法,但仍会产生多个需要缓解的项目。 接下来汇总了可能发生的项目类型。

透视别名

透视别名是一种常见的项目,如图 5 所示。 当视图空间中的像素与阴影贴图中的纹素的映射不是一对一的比例时,会发生此情况。 这是因为靠近近平面的像素距离更近,需要更高的阴影贴图分辨率。

图 6 显示了阴影贴图和视图视锥。 在眼睛附近,像素距离更近,许多像素映射到相同的阴影纹素。 远平面的像素被分散,从而减少透视锯齿。

图 5。 高透视别名 (左) 与低透视别名 (右)

左)  (高透视别名与右)  (低透视别名

对于左侧的图像,透视别名较高;太多眼距像素映射到相同的阴影贴图纹素。 在右侧图像中,透视锯齿较低,因为眼睛空间像素和阴影贴图纹素之间存在 1:1 映射。

图 6。 使用阴影贴图查看视锥

使用阴影贴图查看视锥

远平面中的浅色像素表示低透视锯齿,近平面中的深色像素表示高透视锯齿。

阴影贴图分辨率也可能过高。 虽然更高的分辨率不太明显,但它可能会导致小物体,如电话线,而不是投射阴影。 此外,由于纹理访问模式,分辨率过高可能会导致严重的性能问题。

透视阴影贴图 (PSM) 和光线空间透视阴影贴图 (LSPSM) 尝试通过倾斜光线的投影矩阵来解决透视锯齿问题,以便将更多纹素放置在眼睛附近需要的位置。 遗憾的是,这两种方法都无法解决透视锯齿问题。 将眼距像素映射到阴影贴图中的纹素所需的转换参数化不能受线性倾斜的约束。 需要对数参数化。 PSM 将太多细节放在眼睛附近,导致远处的阴影质量低,甚至消失。 LSPSM 在提高眼睛附近分辨率和为远处物体留下足够细节之间找到中间点方面做得更好。 在某些场景配置中,这两种技术都退化为正交阴影。 这种退化可以通过为视图视锥的每个面呈现单独的阴影贴图来抵消,尽管这很昂贵。 LogPSM (LogPSM 的对数透视阴影贴图) 还按视图视锥面呈现单独的地图。 此技术使用非线性光栅化在眼睛附近放置更多纹素。 D3D10 和 D3D11 类硬件不支持非线性光栅化。 有关这些技术和算法的详细信息,请参阅参考部分。

级联阴影贴图 (CSM) 是处理透视锯齿的最常用技术。 尽管 CSM 可以与 PSM 和 LSPSM 结合使用,但这是不必要的。 使用 CSM 修复透视别名错误在配套文章 级联阴影映射中进行了介绍。

投影别名

投影别名比透视别名更难显示。 图 7 中突出显示的扩大阴影演示了投影别名错误。 当相机空间中的纹素与光空间中的纹素之间的映射不是一对一的比例时,会发生投影别名;这是因为几何图形相对于光相机的方向。 当几何图形的正切平面与光线平行时,将发生投影别名。

图 7。 高投影别名与低投影别名

高投影别名与低投影别名

用于缓解透视别名错误的技术还可以缓解投影别名。 当表面法线与光正交时,会发生投影锯齿;根据漫射照明公式,这些表面接收的光线应该更少。

阴影痤疮和错误Self-Shadowing

阴影痤疮 (图 8) ,一个与错误的自阴影同义词,当阴影贴图量化整个纹素的深度时发生。 当着色器将实际深度与此值进行比较时,它有可能是自阴影的,因为它是无阴影的。

阴影痤疮的另一个原因是光空间中的纹素非常接近深度贴图中相应纹素的深度,因此精度错误导致深度测试错误失败。 产生这种精度差异的一个原因是深度映射是由固定函数光栅化硬件计算的,而要比较的深度是由着色器计算的。 投影锯齿也会导致阴影疮。

图 8。 阴影痤疮伪像

阴影痤疮伪像

如左图中所示,某些像素未通过深度测试,并创建了斑点伪像和摩尔图案。 为了减少错误的自阴影,应尽可能严格地计算光空间视锥的近平面和远平面的边界。 基于斜率比例的深度偏差和其他类型的偏差是用于缓解阴影疮的其他解决方案。

彼得·潘宁

彼得·潘宁这个词派生自一个儿童读物的角色,他的影子变得分离,谁可以飞。 此项目使具有缺失阴影的对象看起来与图面分离并漂浮在图面上方 (图 9) 。

图 9. Peter Panning 项目

peter 平移项目

在左侧图像中,阴影与对象分离,从而产生浮动效果。

去除表面痤疮的一种方法是增加一些价值在光空间中的像素位置:这称为添加深度偏移量。 当使用的深度偏移量太大时,Peter Panning 会生成 。 在这种情况下,深度偏移会导致深度测试错误通过。 与阴影痤疮一样,当深度缓冲区的精度不足时,Peter Panning 会加重。 计算紧近平面和远平面也有助于避免彼得·潘宁。

改进阴影映射的技术

向游戏添加阴影是一个过程。 第一步是使基本阴影贴图正常工作。 第二个是确保所有基本计算都以最佳方式完成:尽可能紧密地拟合,近/远平面紧密拟合,使用斜率缩放偏差,等等。 启用基本阴影并尽可能好看后,开发人员可以更好地了解需要哪些算法才能使阴影达到足够的保真度。 本部分提供了使基本阴影地图获得最佳效果可能需要的基本提示。

Slope-Scale深度偏差

如前所述,自我阴影可能导致阴影性痤疮。 添加过多的偏见可能会导致彼得·潘宁。 此外,相对于光) 具有陡峭斜率 (的多边形比具有较浅斜率的多边形 (相对于光) 遭受更多的投影锯齿。 因此,每个深度贴图值可能需要不同的偏移量,具体取决于多边形相对于光线的斜率。

Direct3D 10 硬件能够根据多边形相对于视图方向的斜率来偏置多边形。 其效果是将大偏差应用于沿光方向查看的多边形,但不会对直接面对光线的多边形应用任何偏差。 图 10 演示了在针对同一无偏斜率进行测试时,两个相邻像素如何在阴影和无阴影之间交替。

图 10. 斜率缩放深度偏差与无偏斜深度的比较

斜率缩放深度偏差与无偏差深度的比较

计算紧密投影

将光线的投影紧密拟合到视锥上可增加阴影贴图的覆盖范围。 图 11 说明了使用任意投影或将投影拟合到场景边界会导致更高的透视锯齿。

图 11. 任意阴影视锥和阴影视锥适合场景

任意阴影视锥和阴影视锥适合场景

视图是从光线的角度出发的。 梯形表示视图相机的视锥。 在图像上绘制的网格表示阴影贴图。 右侧的图像显示,当同一分辨率阴影贴图更贴合场景时,会创建更多的纹素覆盖。

图 12 说明了正确拟合的锥体。 为了计算投影,构成视锥的八个点将转换为光空间。 接下来,找到 X 和 Y 中的最小值和最大值。 这些值构成正交投影的边界。

图 12. 阴影投影适合查看视锥

适合查看视锥的阴影投影

还可以将锥形剪辑到场景 AABB,以获得更紧密的绑定。 并非在所有情况下都建议这样做,因为这会更改光相机从帧到帧的投影大小。 许多技术,如移动光Texel-Sized增量部分中介绍的技术,当光线的投影大小在每个帧中保持不变时,效果更好。

计算近平面和远平面

近平面和远平面是计算投影矩阵所需的最终部分。 平面越紧密,深度缓冲区中的值就越精确。

深度缓冲区可以是 16 位、24 位或 32 位,其值介于 0 和 1 之间。 通常,深度缓冲区是固定点,与靠近远平面的值相比,靠近近平面的值更紧密地组合在一起。 深度缓冲区可用的精度程度取决于近平面与远平面的比率。 使用尽可能紧密的近/远平面可能允许使用 16 位深度缓冲区。 16 位深度缓冲区可以减少内存的使用,同时提高处理速度。

AABB-Based近平面和远平面

计算近平面和远平面的一种简单而天真的方法是将场景的边界体积转换为光空间。 最小的 Z 坐标值是近平面,最大的 Z 坐标值是远平面。 对于场景和光线的许多配置,此方法就足够了。 但是,最坏的情况可能会导致深度缓冲区的精度严重损失;图 13 显示了这种情况。 此处,近平面到远平面的范围是必要的四倍。

图 13 中的视锥被特意选择为较小。 一个小视锥显示在一个非常大的场景中,该场景中由从视像仪延伸出的柱子组成。 对近平面和远平面使用场景 AABB 不是最佳选择。 级联阴影映射技术文章中所述的 CSM 算法必须计算非常小的视锥的近平面和远平面。

图 13. 基于场景 AABB 的近平面和远平面

基于场景 aabb 的近平面和远平面

Frustum-Based近平面和远平面

计算近平面和远平面的另一种方法是将视锥转换为光空间,并将 Z 中的最小值和最大值分别用作近平面和远平面。 图 14 说明了此方法的两个问题。 首先,计算过于保守,如视锥超出场景几何图形时所示。 其次,附近的平面可能太紧,导致影子脚轮被裁剪。

图 14. 仅基于视锥的近平面和远平面

仅基于视锥的近平面和远平面

光视锥与场景相交以计算近平面和远平面

图 15 显示了计算近平面和远平面的正确方法。 正交光锥的四个平面是使用光空间中视锥的 X 和 Y 坐标的最小值和最大值计算的。 正交视锥的最后两个平面是近平面和远平面。 为了查找这些平面,场景的边界被剪裁在四个已知的光视锥平面上。 新剪裁边界中的最小和最大 Z 值分别表示近平面和远平面。

执行此操作的代码位于 CascadedShadowMaps11 示例中。 构成世界AABB的8个点被转化为光空间。 将点转换为光空间可简化剪裁测试。 光锥的四个已知平面现在可以表示为线条。 光空间中的场景边界体积可以表示为六个四边形。 然后,这 6 个四边形可以转换为 12 个三角形,用于基于三角形的剪裁。 三角形在视锥的已知平面上被剪裁, (这些三角形是 X 和 Y 在光空间) 中的水平线和垂直线。 在 X 和 Y 中找到交集点时,会在该点剪裁 3D 三角形。 所有剪裁的三角形的最小和最大 Z 值是近平面和远平面。 CascadedShadowMaps11 示例演示如何在 ComputeNearAndFar 函数中执行此剪辑。

还有两种技术可用于计算最紧密的近平面和远平面。 CascadedShadowMaps 示例中未显示这些技术。

  • 通过将场景的层次结构或场景中的各个对象与光视锥相交,可以计算出更紧密的近平面和远平面。 这在计算上会更加复杂。 虽然 CascadedShadowMaps11 示例中没有说明,但对于某些磁贴而言,这可能是一种有效的技术。

  • 远平面可以通过采用以下最小值来计算:

    • 光空间中视锥的最大深度。
    • 视锥与场景 AABB 交集的最大深度。

当与级联阴影贴图一起使用时,此方法可能会出现问题,因为这种映射可以在视图视锥之外编制索引。 在这种情况下,阴影贴图可能缺少几何图形。

图 15. 基于光锥的四个计算平面与场景边界几何图形的交集的近平面和远平面

基于光锥的四个计算平面与场景边界几何图形的交集的近平面和远平面

以Texel-Sized增量移动光

阴影映射中的一个常见项目是闪闪发光的边缘效果。 随着相机的移动,沿阴影边缘的像素变亮和变暗。 这在静止图像中是看不到的,但它是非常明显和分心的实时。 图 16 突出显示了此问题,图 17 显示了阴影边缘的外观。

由于每次相机移动时都会重新计算光线投影矩阵,因此会发生闪闪发光的边缘错误。 这会在生成的阴影映射中产生细微的差异。 以下所有因素都可能会影响为绑定场景而创建的矩阵。

  • 视图视锥的大小
  • 视图视锥的方向
  • 灯的位置
  • 相机的位置

每次此矩阵更改时,阴影边缘都可能会更改。

图 16. 闪闪发光的阴影边缘

闪闪发光的阴影边缘

当相机从左到右移动时,阴影边框上的像素会进入和退出阴影。

图 17. 没有闪闪发光边缘的阴影

没有闪闪发光边缘的阴影

当相机从左向右移动时,阴影边缘保持不变。

对于定向光,此问题的解决方案是将构成正交投影边界的 X 和 Y (的最小值/最大值舍入为像素大小增量) 。 这可以通过除法运算、下限运算和乘法来完成。

        vLightCameraOrthographicMin /= vWorldUnitsPerTexel;
        vLightCameraOrthographicMin = XMVectorFloor( vLightCameraOrthographicMin );
        vLightCameraOrthographicMin *= vWorldUnitsPerTexel;
        vLightCameraOrthographicMax /= vWorldUnitsPerTexel;
        vLightCameraOrthographicMax = XMVectorFloor( vLightCameraOrthographicMax );
        vLightCameraOrthographicMax *= vWorldUnitsPerTexel;

vWorldUnitsPerTexel 值是通过取视图视锥的边界除以缓冲区大小来计算的。

        FLOAT fWorldUnitsPerTexel = fCascadeBound /
        (float)m_CopyOfCascadeConfig.m_iBufferSize;
        vWorldUnitsPerTexel = XMVectorSet( fWorldUnitsPerTexel, fWorldUnitsPerTexel,                            0.0f, 0.0f );

限定视图视锥的最大大小会导致正交投影的拟合度越松。

请务必注意,使用此方法时,纹理的宽度和高度要大 1 个像素。 这可以防止阴影坐标在阴影贴图外部进行索引编制。

背面和正面

应使用标准背面剔除来渲染阴影贴图,此过程跳过查看者看不到的对象的光栅化,并加快场景的渲染速度。 另一种常见选项是在启用正面剔除的情况下呈现阴影贴图,这意味着将消除面向查看器的对象。 这样做的理由是,它有助于自阴影,因为组成对象背面的几何图形略有偏移。 这个想法有两个问题。

  • 任何具有不正确的正面或背面几何图形的对象都会导致阴影贴图中的项目。 但是,具有不正确的正面或背面几何图形会导致其他问题,因此可以放心地假设正面和背面几何图形已正确完成。 为基于子画面的几何图形(如叶子)创建背面可能不切实际。
  • 彼得平移和阴影间隙附近的对象(如墙壁)更可能发生,因为阴影深度差异太小。

阴影贴图 - 友好几何图形

创建在阴影贴图中效果良好的几何图形可以在对抗彼得·潘宁和阴影疤痕等项目时获得更大的灵活性。

硬边缘对自阴影有问题。 边缘顶端附近的深度差异非常小。 即使是较小的偏移量也可能导致对象失去其阴影 (图 18) 。

图 18. 尖锐的边缘会导致因偏移量的低深度差异而产生伪像

尖锐的边缘会导致因低深度差异而产生偏移的伪像

较窄的对象(如墙壁)应该有后背,即使它们从不可见。 这将增加深度差距。

确保几何图形所面向的方向正确也很重要;也就是说,对象的外部应朝后,对象的内部应为正面。 对于启用背面剔除的渲染,以及消除深度偏差的影响,这一点很重要。

总结

本文中所述的技术可用于提高标准阴影贴图的质量。 下一步是了解可以很好地与标准阴影贴图配合使用的技术。 建议将 CSM 作为一种高级技术来对抗透视锯齿。 百分比接近筛选或方差阴影映射可用于软化阴影边缘。 有关详细信息,请参阅 级联阴影映射 技术文章。

唐纳利,W.,和劳里森,A. 方差阴影地图。 交互式 3D 图形研讨会、2006 年交互式 3D 图形和游戏研讨会论文集。 2006,第 161-165 页。

恩格尔,沃夫冈F.第4节。 级联阴影映射。 ShaderX5, 高级渲染技术,Wolfgang F. Engel,Ed. 查尔斯河媒体,波士顿,马萨诸塞州。 2006. 第 197-206 页。

斯塔明格、马克和德雷塔基斯,乔治。 透视阴影映射。 计算机图形和交互技术国际会议, 第29届计算机图形和交互技术年会论文集。 2002,第 557-562 页。

Wimmer, M., Scherzer, D., and Purgathofer, W. Light Space Perspective Shadow Maps. 关于渲染的欧文学研讨会。 2004. 修订于2005年6月10日。 Technische Universität Wien.