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

阴影地图最初于1978年引入,是向游戏添加阴影的常见技术。 30年后,尽管硬件和软件方面有所进步,但阴影项目(即闪闪发光的边缘、透视别名和其他精度问题)仍然存在。

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

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

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

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

阴影深度地图评审

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

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

阴影场景的关键部分

传递 1

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

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

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

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

传递 2

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

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

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

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

阴影映射项目

阴影深度地图算法是最常用的实时阴影算法,但仍会生成多个需要缓解的项目。 接下来将汇总可能发生的项目类型。

透视别名

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

图 6 显示了阴影图和视图 frustum。 在眼睛附近,像素更接近,许多像素映射到相同的阴影纹素。 远平面的像素分散,从而减少透视别名。

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

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

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

图 6. 使用阴影映射查看 frustum

使用阴影映射查看 frustum

远平面中的光像素表示低透视别名,近平面中的深色像素表示高透视别名。

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

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

级联阴影映射(CSM)是处理透视别名的最常用技术。 尽管 CSM 可以与 PSM 和 LSPSM 结合使用,但没有必要。 在配套文章“ 级联阴影映射”中解决了使用 CSM 修复透视别名错误。

投影别名

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

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

高投影别名与低投影别名

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

阴影疮和错误自我阴影

阴影疮(图 8)是错误自我阴影的术语,当阴影贴图将整个纹素的深度量化时发生。 当着色器将实际深度与此值进行比较时,它可能会像隐藏一样自影。

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

图 8. 阴影疮项目

阴影疮项目

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

彼得·潘宁

彼得·潘宁这个词派生自一个儿童书名,他的影子变得分离,谁可以飞。 此项目使缺少阴影的对象似乎与表面上方分离并浮动(图 9)。

图 9. Peter Panning 项目

peter 平移项目

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

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

改进阴影映射的技术

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

斜率刻度深度偏差

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

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

图 10. 与无偏斜深度相比,斜率缩放深度偏差

与无偏斜深度相比,斜率缩放深度偏差

计算紧密投影

紧密拟合光线的投影到视面,会增加阴影地图覆盖率。 图 11 说明了使用任意投影或将投影拟合到场景边界,导致透视别名较高。

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

任意阴影 frustum 和阴影 frustum 适合场景

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

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

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

阴影投影适合查看视线

也可以将 frustum 剪辑到场景 AABB,以获得更紧密的界限。 在所有情况下都不建议这样做,因为这样可以将光相机的投影大小从帧更改为帧。 许多技术(如移动光纹素大小的增量部分中所述)在每帧中光线投影的大小保持不变时提供更好的结果。

计算近平面和远平面

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

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

基于 AABB 的近平面和远平面

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

图 13 中的视图 frustum 被特意选择为小。 在非常大的场景中,显示一个小的视面,其中包含从视图相机延伸出来的柱子。 将场景 AABB 用于近远平面并不最佳。 级联阴影地图技术文章中所述的 CSM 算法必须计算极小的视线的近平面和远平面。

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

基于场景 aabb 的近远平面

基于 Frustum 的近平面和远平面

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

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

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

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

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

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

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

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

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

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

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

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

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

以纹素大小的增量移动光

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

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

  • 视图 frustum 的大小
  • 视图 frustum 的方向
  • 光线的位置
  • 相机的位置

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

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

闪闪发光的阴影边缘

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

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

无闪闪发光边缘的阴影

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

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

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

vWorldUnitsPerTexel 值是通过采用视图 frustum 的绑定计算的,并除以缓冲区大小。

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

将视图的最大大小限制在一定范围后,将松散地拟合到正交投影中。

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

后脸和正面

应使用标准后脸剔除来呈现阴影地图,此过程跳过查看器无法看到的对象光栅化,并加快场景的呈现速度。 另一种常见选项是呈现启用了正面剔除的阴影映射,这意味着将消除面向查看器的对象。 其参数是,它有助于自我阴影,因为构成对象背面的几何图形稍有偏移。 这个想法有两个问题。

  • 任何具有不当正面或后脸几何图形的对象都会导致阴影映射中的项目。 但是,具有不正确的正面或后脸几何图形会导致其他问题,因此可以安全地假定正面和后脸几何图形已正确完成。 为基于子画面的几何图形(如树叶)创建背脸可能不切实际。
  • 彼得·潘宁和阴影缝隙靠近墙壁等物体的底部更有可能发生,因为阴影深度差距太小。

阴影映射 -友好几何图形

创建在阴影地图中效果良好的几何图形,在处理彼得·潘宁和阴影疮等项目时,可以更具灵活性。

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

图 18. 尖锐的边缘导致项目源于低深度差异与偏移

尖锐边缘导致项目源于低深度差异与偏移

狭窄的对象(如墙壁)应该有后退,即使它们永远不会可见。 这将增加深度差距。

确保几何图形面对的方向正确,这一点也很重要;也就是说,对象的外部应背靠背,对象内部应是正面的。 这一点对于启用后脸剔除以及打击深度偏差的影响非常重要。

级联阴影映射

另请参阅 Cascaded 阴影映射和 CascadedShadowMaps11 示例应用。 该示例演示了级联阴影图(CSM)算法,以及可用于有效利用阴影贴图的几种技术。

可以在 DirectX 软件开发工具包(SDK)中找到示例。 或者,可以执行 Web 搜索,在 GitHub 上查找它。

总结

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

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

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

斯塔明格、马克和德里塔基斯,乔治。 透视阴影映射。 计算机图形和交互式技术国际会议, 计算机图形和交互式技术第29届年会。 2002, pp 557–562.

威默,M.,谢尔泽,D.和珀加特霍夫,W. 浅空间透视阴影地图。 关于渲染的欧文信息研讨会。 2004. 2005年6月10日修订。 Technische Universität Wien.