级联阴影映射
级联阴影贴图 (CSM) 是解决阴影的最普遍错误之一的最佳方法:透视锯齿。 此技术文章假设读者熟悉阴影映射,本文介绍 CSM 的主题。 具体而言,它:
- 解释 CSM 的复杂性;
- 提供有关 CSM 算法的可能变体的详细信息;
- 介绍了两种最常见的筛选技术: (PCF) 的百分比更接近筛选,以及使用方差阴影贴图 (VSM) 筛选;
- 识别并解决与向 CSM 添加筛选相关的一些常见缺陷;和
- 演示如何将 CSM 映射到 Direct3D 10 到 Direct3D 11 硬件。
本文中使用的代码可在 CascadedShadowMaps11 和 VarianceShadows11 示例中的 DirectX 软件开发工具包 (SDK) 中找到。 在实现 技术文章改进阴影深度映射的常见技术后,本文将证明最有用。
级联阴影映射和透视别名
阴影映射中的透视别名是最难解决的问题之一。 技术文章改进阴影深度映射的常见技术介绍了透视别名,并确定了缓解问题的一些方法。 在实践中,CSM 往往是最佳解决方案,并且通常用于现代游戏。
CSM 的基本概念很容易理解。 相机视锥的不同区域需要具有不同分辨率的阴影贴图。 与距离较远的对象相比,最靠近眼睛的对象需要更高的分辨率。 事实上,当眼睛非常接近几何图形时,距离眼睛最近的像素可能需要如此大的分辨率,以至于即使 4096 × 4096 阴影贴图也不够。
CSM 的基本理念是将视锥分区为多个 fusta。 为每个子阴影呈现阴影贴图;然后,像素着色器从与所需分辨率最匹配的地图中采样 (图 2) 。
图 1. 阴影地图覆盖范围
在图 1 中,质量 (从左到右) 从高到低显示。 以红色) 表示具有视锥 (倒锥的阴影贴图的网格系列显示了不同分辨率阴影贴图对像素覆盖率的影响。 当光线空间中的像素与阴影贴图中的纹素 (比例为 1:1 时,阴影的质量最高,) 白色像素。 当太多像素映射到同一阴影纹素时,透视锯齿以大型块状纹理贴图的形式发生 (左图像) 。 当阴影贴图太大时,它将处于采样状态。 在这种情况下,将跳过纹素,引入闪闪发光的项目,并影响性能。
图 2. CSM 阴影质量
图 2 显示了图 1 中每个阴影贴图中质量最高的部分的切口。 位于顶点 (像素最接近) 的阴影贴图距离眼睛最近。 从技术上讲,这些映射大小相同,白色和灰色用于演示级联阴影贴图的成功。 白色是理想的,因为它显示良好的覆盖率 - 眼睛空间像素和阴影贴图纹素的比率为 1:1。
CSM 要求每个帧执行以下步骤。
将视锥分区为 subfrusta。
计算每个子rustum 的正交投影。
为每个子阴影呈现阴影贴图。
渲染场景。
绑定阴影映射并呈现。
顶点着色器执行以下操作:
- 计算每个光子锥 (的纹理坐标,除非在像素着色器) 中计算所需的纹理坐标。
- 转换和点亮顶点等。
像素着色器执行以下操作:
- 确定正确的阴影贴图。
- 如有必要,转换纹理坐标。
- 对级联采样。
- 照亮像素。
对 Frustum 进行分区
将视锥分区是创建子 ffrusta 的行为。 拆分视锥的一种方法是在 Z 方向计算从 0% 到 100% 的间隔。 然后,每个间隔表示一个近平面和一个远平面,表示 Z 轴的百分比。
图 3. 查看任意分区的视锥
在实践中,重新计算每个帧的视锥拆分会导致阴影边缘闪烁。 一般接受的做法是为每个方案使用一组静态的级联间隔。 在此方案中,沿 Z 轴的间隔用于描述在对视锥进行分区时发生的子rustum。 确定给定场景的正确大小间隔取决于多个因素。
场景几何图形的方向
就场景几何图形而言,相机方向会影响级联间隔选择。 例如,非常接近地面的相机(如足球游戏中的地面摄像头)的级联间隔静态集与天空中的相机不同。
图 4 显示了一些不同的相机及其各自的分区。 当场景的 Z 范围非常大时,需要更多的拆分平面。 例如,当眼睛非常接近地面平面,但远处的对象仍然可见时,可能需要多个级联。 划分视锥,使更多的分裂靠近眼睛 (其中透视锯齿正在改变最快) 也很有用。 当大部分几何图形被凝聚到一小部分 ((如头顶视图或飞行模拟器) 视图视锥)时,需要的级联更少。
图 4。 不同的配置需要不同的视锥拆分
(左) 当几何图形在 Z 中具有较高的动态范围时,需要大量级联。 (中心) 当几何图形的 Z 动态范围较低时,多个视锥几乎没有好处。 (右) 当动态范围为中等时,只需要三个分区。
光线和相机的方向
每个级联的投影矩阵都紧贴在其相应的子锥周围。 在视像仪和光线方向正交的配置中,级联可以紧密贴合,几乎没有重叠。 当光线和视像仪进入平行对齐 (图 5) 时,重叠会变大。 当光线和视像仪几乎平行时,它被称为“决斗 frusta”,对于大多数阴影算法来说,这是一个非常困难的场景。 限制光线和相机以使这种情况不发生的情况并不少见。 但是,在此方案中,CSM 的性能比许多其他算法要好得多。
图 5。 随着光方向与相机方向平行,级联重叠增加
许多 CSM 实现使用固定大小的 frusta。 当视锥按固定大小间隔拆分时,像素着色器可以使用 Z 深度为级联数组编制索引。
计算View-Frustum绑定
选择锥体间隔后,将使用以下两种方法之一创建子 ffrusta:拟合场景和拟合级联。
适应场景
可以使用同一近平面创建所有 frusta。 这会强制级联重叠。 CascadedShadowMaps11 示例调用此技术适合场景。
适合级联
或者,可以使用实际分区间隔作为近平面和远平面来创建 frusta。 这会导致更紧密的拟合,但在决斗时会退化到适合场景。 CascadedShadowMaps11 示例调用此技术适合级联。
图 6 中显示了这两种方法。 适合级联减少分辨率浪费。 拟合到级联的问题在于,正交投影会根据视图视锥的方向增长和收缩。 拟合场景技术通过视锥的最大大小填充正交投影,从而删除在视图相机移动时出现的项目。 改善阴影深度映射的常见技术 可解决在“以纹素大小为增量移动光线”部分中光线移动时出现的项目。
图 6。 适合场景与适合级联
呈现阴影贴图
CascadedShadowMaps11 示例将阴影贴图呈现到一个大型缓冲区中。 这是因为纹理数组上的 PCF 是 Direct3D 10.1 功能。 对于每个级联,都会创建一个视区,该视区涵盖与该级联对应的深度缓冲区部分。 绑定 null 像素着色器,因为只需要深度。 最后,为每个级联设置正确的视区和阴影矩阵,因为深度贴图一次呈现一个到main阴影缓冲区。
呈现场景
包含阴影的缓冲区现在绑定到像素着色器。 有两种方法用于选择 CascadedShadowMaps11 示例中实现的级联。 这两种方法通过着色器代码进行说明。
Interval-Based级联选择
图 7。 基于间隔的级联选择
在基于间隔的选择 (图 7) 中,顶点着色器计算顶点在世界空间中的位置。
Output.vDepth = mul( Input.vPosition, m_mWorldView ).z;
像素着色器接收内插深度。
fCurrentPixelDepth = Input.vDepth;
基于间隔的级联选择使用矢量比较和点积来确定正确的 cacade。 CASCADE_COUNT_FLAG指定级联数。 m_fCascadeFrustumsEyeSpaceDepths_data约束视图视锥分区。 比较后,fComparison 包含一个值 1,其中当前像素大于屏障,当当前级联较小时,值为 0。 点积将这些值求和成数组索引。
float4 vCurrentPixelDepth = Input.vDepth;
float4 fComparison = ( vCurrentPixelDepth > m_fCascadeFrustumsEyeSpaceDepths_data[0]);
float fIndex = dot(
float4( CASCADE_COUNT_FLAG > 0,
CASCADE_COUNT_FLAG > 1,
CASCADE_COUNT_FLAG > 2,
CASCADE_COUNT_FLAG > 3)
, fComparison );
fIndex = min( fIndex, CASCADE_COUNT_FLAG );
iCurrentCascadeIndex = (int)fIndex;
选择级联后,必须将纹理坐标转换为正确的级联。
vShadowTexCoord = mul( InterpolatedPosition, m_mShadow[iCascadeIndex] );
然后,此纹理坐标用于使用 X 坐标和 Y 坐标对纹理进行采样。 Z 坐标用于执行最终深度比较。
Map-Based级联选择
基于地图的选择 (图 8) 测试级联的四边,以查找覆盖特定像素的最紧密的地图。 顶点着色器计算每个级联的视图空间位置,而不是计算世界空间中的位置。 像素着色器循环访问级联,以便缩放和移动纹理坐标,以便为当前级联编制索引。 然后,根据纹理边界测试纹理坐标。 当纹理坐标的 X 和 Y 值落在级联内时,它们用于采样纹理。 Z 坐标用于执行最终深度比较。
图 8。 基于地图的级联选择
Interval-Based选择与Map-Based选择
基于间隔的选择比基于地图的选择要快一点,因为级联选择可以直接完成。 基于地图的选择必须将纹理坐标与级联边界相交。
当阴影贴图无法完全对齐时,基于地图的选择更高效地使用级联, (请参阅图 8) 。
在级联之间混合
(本文后面部分介绍的 VSM) 和筛选技术(如 PCF)可用于低分辨率 CSM 来生成柔和阴影。 遗憾的是,这会导致图 9 级联层之间的可见接缝 () ,因为分辨率不匹配。 解决方案是在阴影贴图之间创建一个波段,其中为两个级联执行阴影测试。 然后,着色器根据像素在混合带中的位置在两个值之间线性内插。 CascadedShadowMaps11 和 VarianceShadows11 示例提供了可用于增加和减少此模糊带的 GUI 滑块。 着色器执行动态分支,以便绝大多数像素仅从当前级联读取。
图 9. 级联接缝
(左) 在级联重叠的地方可以看到可见接缝。 (右) 当级联混合在一起时,不会发生接缝。
筛选阴影映射
PCF
筛选普通阴影贴图不会产生柔和模糊的阴影。 筛选硬件模糊深度值,然后将这些模糊值与光空间纹素进行比较。 通过/未通过测试生成的硬边缘仍然存在。 模糊阴影贴图只会错误地移动硬边缘。 PCF 支持对阴影贴图进行筛选。 PCF 的一般思路是,根据通过深度测试的子样本数超过子样本总数,计算阴影中像素的百分比。
Direct3D 10 和 Direct3D 11 硬件可以执行 PCF。 PCF 采样器的输入由纹理坐标和比较深度值组成。 为简单起见,PCF 使用四点击筛选器进行说明。 纹理采样器读取纹理四次,类似于标准筛选器。 但是,返回的结果是通过深度测试的像素的百分比。 图 10 显示了通过四个深度测试之一的像素在阴影中的 25%。 返回的实际值是基于纹理读取的子纹素坐标的线性内插,以生成平滑渐变。 如果没有这种线性内插,四点击 PCF 将只能返回五个值:{ 0.0, 0.25, 0.5, 0.75, 1.0 }。
图 10. PCF 筛选图像,覆盖了 25% 的选定像素
也可以在没有硬件支持的情况下执行 PCF,或者将 PCF 扩展到更大的内核。 某些技术甚至使用加权内核进行采样。 为此,请为 N × N 网格创建内核 (,例如高斯) 。 权重必须加起来最多 1。 然后对纹理进行 N2 次采样。 每个样本都按内核中的相应权重进行缩放。 CascadedShadowMaps11 示例使用此方法。
深度偏差
使用大型 PCF 内核时,深度偏差变得更加重要。 仅将像素的光空间深度与它在深度映射图中映射到的像素进行比较才有效。 深度贴图纹素的邻居引用不同的位置。 此深度可能类似,但可能会因场景而大不相同。 图 11 突出显示了发生的项目。 将单个深度与阴影映射中的三个相邻纹素进行比较。 其中一个深度测试错误地失败,因为它的深度与计算的当前几何图形的光空间深度不相关。 此问题的建议解决方案是使用更大的偏移量。 但是,偏移量过大可能会导致彼得·潘宁。 计算紧近平面和远平面有助于减少使用偏移的影响。
图 11. 错误的自阴影
将光空间深度中的像素与阴影映射中不相关的纹素进行比较,导致错误的自阴影。 光空间中的深度与深度映射中的阴影纹素 2 相关。 纹素 1 大于光空间深度,而 2 等于,3 小于。 纹素 2 和 3 通过深度测试,而纹素 1 失败。
使用 DDX 和 DDY 计算大型 PCF 的Per-Texel深度偏差
使用 ddx 和 ddy 计算大型 PCF 的每纹素深度偏差是一种计算相邻阴影贴图纹素的正确深度偏差(假设表面为平面)的技术。
此方法使用导数信息将比较深度拟合到平面。 由于此方法在计算上很复杂,因此仅当 GPU 有备用的计算周期时,才应使用它。 使用非常大的内核时,这可能是唯一一种在不导致 Peter 平移的情况下删除自阴影项目的方法。
图 12 突出显示了此问题。 光空间中的深度对于正在比较的一个纹素是已知的。 与深度映射中相邻纹素对应的光空间深度未知。
图 12. 场景和深度贴图
呈现的场景显示在左侧,右侧显示带有示例纹素块的深度图。 眼空间纹素映射到块中心标记为 D 的像素。 此比较是准确的。 与相邻 D 未知的像素关联的眼部空间的正确深度。 仅当假定像素与 D 相关的三角形时,才能将相邻纹素映射回眼睛空间。
深度已知为与光空间位置相关的纹素。 深度映射中相邻纹素的深度未知。
概括而言,此方法使用 ddx 和 ddy HLSL 运算来查找光空间位置的导数。 这是非平凡的,因为导数运算返回相对于屏幕空间的光空间深度的渐变。 若要将其转换为相对于光空间的光空间深度的渐变,必须计算转换矩阵。
使用着色器代码的说明
该算法的其余部分的详细信息作为执行此操作的着色器代码的说明提供。 可以在 CascadedShadowMaps11 示例中找到此代码。 图 13 显示了光空间纹理坐标如何映射到深度贴图,以及如何使用 X 和 Y 中的导数来创建转换矩阵。
图 13. 屏幕空间到光空间矩阵
X 和 Y 中光空间位置的导数用于创建此矩阵。
第一步是计算光-视图-空间位置的导数。
float3 vShadowTexDDX = ddx (vShadowMapTextureCoordViewSpace);
float3 vShadowTexDDY = ddy (vShadowMapTextureCoordViewSpace);
Direct3D 11 类 GPU 通过并行运行 2 × 2 个象限像素并减去 X 中 ddx 的相邻像素和 ddy 的相邻的纹理坐标来计算这些导数。 这两个导数构成 2 × 2 矩阵的行。 在其当前形式中,此矩阵可用于将屏幕空间相邻像素转换为光空间斜率。 但是,需要此矩阵的反数。 需要将相邻光空间像素转换为屏幕空间斜率的矩阵。
float2x2 matScreentoShadow = float2x2( vShadowTexDDX.xy, vShadowTexDDY.xy );
float fInvDeterminant = 1.0f / fDeterminant;
float2x2 matShadowToScreen = float2x2 (
matScreentoShadow._22 * fInvDeterminant,
matScreentoShadow._12 * -fInvDeterminant,
matScreentoShadow._21 * -fInvDeterminant,
matScreentoShadow._11 * fInvDeterminant );
图 14. 光空间到屏幕空间
然后,此矩阵用于转换当前纹素的上方和右侧的两个纹素。 这些邻居表示为与当前纹素的偏移量。
float2 vRightShadowTexelLocation = float2( m_fTexelSize, 0.0f );
float2 vUpShadowTexelLocation = float2( 0.0f, m_fTexelSize );
float2 vRightTexelDepthRatio = mul( vRightShadowTexelLocation,
matShadowToScreen );
float2 vUpTexelDepthRatio = mul( vUpShadowTexelLocation,
matShadowToScreen );
矩阵创建的比率最终乘以深度导数,以计算相邻像素的深度偏移量。
float fUpTexelDepthDelta =
vUpTexelDepthRatio.x * vShadowTexDDX.z
+ vUpTexelDepthRatio.y * vShadowTexDDY.z;
float fRightTexelDepthDelta =
vRightTexelDepthRatio.x * vShadowTexDDX.z
+ vRightTexelDepthRatio.y * vShadowTexDDY.z;
现在可以在 PCF 循环中使用这些权重来向位置添加偏移量。
for( int x = m_iPCFBlurForLoopStart; x < m_iPCFBlurForLoopEnd; ++x )
{
for( int y = m_iPCFBlurForLoopStart; y < m_iPCFBlurForLoopEnd; ++y )
{
if ( USE_DERIVATIVES_FOR_DEPTH_OFFSET_FLAG )
{
depthcompare += fRightTexelDepthDelta * ( (float) x ) +
fUpTexelDepthDelta * ( (float) y );
}
// Compare the transformed pixel depth to the depth read
// from the map.
fPercentLit += g_txShadow.SampleCmpLevelZero( g_samShadow,
float2(
vShadowTexCoord.x + ( ( (float) x ) * m_fNativeTexelSizeInX ) ,
vShadowTexCoord.y + ( ( (float) y ) * m_fTexelSize )
),
depthcompare
);
}
}
PCF 和 CSM
PCF 不适用于 Direct3D 10 中的纹理数组。 若要使用 PCF,所有级联都存储在一个大型纹理图集中。
Derivative-Based偏移量
为 CSM 添加基于导数的偏移量会带来一些挑战。 这是由于发散流控制中的导数计算造成的。 出现此问题的原因是 GPU 的基本运行方式。 Direct3D11 GPU 在 2 × 2 个象限像素上运行。 为了执行导数,GPU 通常会从相邻像素的同一变量副本中减去当前像素的变量副本。 这种情况发生的方式因 GPU 而异。 纹理坐标由基于地图或基于间隔的级联选择确定。 像素象限中的某些像素选择不同于其余像素的级联。 这会导致阴影贴图之间可见接缝,因为基于导数的偏移现在完全错误。 解决方法是在光视图空间纹理坐标上执行导数。 这些坐标对于每个级联都是相同的。
PCF 内核的填充
如果未填充影子缓冲区,则 PCF 内核在级联分区外部编制索引。 解决方法是将级联的外边缘填充 PCF 内核大小的一半。 这必须在选择级联的着色器中实现,在投影矩阵中实现,该矩阵必须呈现足以保留边框的级联。
方差阴影映射
(的 VSM 请参阅 Donnelly 和 Lauritzen 提供的 方差阴影贴图 ,以获取) 启用直接阴影贴图筛选的详细信息。 使用 VSM 时,可以使用纹理筛选硬件的所有电源。 可以使用三线性和各向异性 (图 15) 筛选。 此外,VSM 可以直接通过卷积模糊。 VSM 确实有一些缺点:深度数据的两个通道必须存储 (深度和深度平方) 。 当阴影重叠时,光出血很常见。 但是,它们的分辨率较低,并且可与 CSM 结合使用。
图 15. 各向异性筛选
算法详细信息
VSM 的工作原理是将深度和深度平方成双通道阴影映射。 然后,可以像正常纹理一样模糊和筛选此双通道阴影贴图。 然后,该算法在像素着色器中使用 Chebychev 的不相等值来估计通过深度测试的像素面积的分数。
像素着色器提取深度和深度平方值。
float fAvgZ = mapDepth.x; // Filtered z
float fAvgZ2 = mapDepth.y; // Filtered z-squared
执行深度比较。
if ( fDepth <= fAvgZ )
{
fPercentLit = 1;
}
如果深度比较失败,则估计点燃像素的百分比。 方差计算为平方平均值减去平均值平方。
float variance = ( fAvgZ2 ) − ( fAvgZ * fAvgZ );
variance = min( 1.0f, max( 0.0f, variance + 0.00001f ) );
fPercentLit 值是使用 Chebychev 的不平等来估计的。
float mean = fAvgZ;
float d = fDepth - mean;
float fPercentLit = variance / ( variance + d*d );
轻微出血
VSM 的最大缺点是轻微出血 (图 16) 。 当多个阴影脚轮沿边缘相互遮挡时,会发生轻出血。 VSM 根据深度差异对阴影边缘进行着色。 当阴影相互重叠时,应隐藏的区域中心存在深度差异。 这是使用 VSM 算法时遇到的问题。
图 16. VSM 轻度出血
问题的部分解决方案是将 fPercentLit 提升到电源。 这有抑制模糊的效果,这可能会导致深度差异很小的伪像。 有时存在一个神奇的价值,可以缓解问题。
fPercentLit = pow( p_max, MAGIC_NUMBER );
将照明百分比提高到电源的替代方法是避免阴影重叠的配置。 即使是高度优化的阴影配置,在光线、相机和几何图形方面也存在一些限制。 通过使用更高分辨率的纹理,也减轻了轻微出血。
分层方差阴影映射 (LVSM) 解决问题,代价是将视锥分解成与光垂直的层。 使用 CSM 时,所需的映射数会相当大。
此外,安德鲁·劳里森(Andrew Lauritzen)是 VSM 论文的合著者,是LVSM论文的作者,他讨论了在 Beyond3D 论坛中将指数阴影贴图 (ESM) 与 VSM 相结合来抵消光混合。
将 VSM 与 CSM 配合使用
示例 VarianceShadow11 结合了 VSM 和 CSM。 组合相当简单。 该示例遵循与 CascadedShadowMaps11 示例相同的步骤。 由于不使用 PCF,因此阴影在双传递可分离卷积中会模糊。 不使用 PCF 还使示例能够使用纹理数组而不是纹理图集。 纹理阵列上的 PCF 是 Direct3D 10.1 功能。
使用 CSM 的渐变
将渐变与 CSM 配合使用可以沿两个级联之间的边界生成接缝,如图 17 所示。 示例指令使用像素之间的导数来计算筛选器所需的 mipmap 级别等信息。 这尤其会导致 mipmap 选择或各向异性筛选出现问题。 当象限中的像素在着色器中采用不同的分支时,GPU 硬件计算的导数无效。 这会导致沿阴影贴图出现锯齿状接缝。
图 17. 由于各向异性筛选和发散流控制,级联边界上的接缝
通过计算光视空间中位置的导数解决了这个问题:光视图空间坐标不特定于所选级联。 计算的导数可以按投影纹理矩阵的比例部分缩放到正确的 mipmap 级别。
float3 vShadowTexCoordDDX = ddx( vShadowMapTextureCoordViewSpace );
vShadowTexCoordDDX *= m_vCascadeScale[iCascade].xyz;
float3 vShadowTexCoordDDY = ddy( vShadowMapTextureCoordViewSpace );
vShadowTexCoordDDY *= m_vCascadeScale[iCascade].xyz;
mapDepth += g_txShadow.SampleGrad( g_samShadow, vShadowTexCoord.xyz,
vShadowTexCoordDDX, vShadowTexCoordDDY );
VSM 与 PCF 标准阴影的比较
VSM 和 PCF 都尝试近似通过深度测试的像素区域的分数。 VSM 适用于筛选硬件,并且可以使用可分离内核进行模糊处理。 与完整内核相比,可分离卷积内核的实现成本要低得多。 此外,VSM 将一个光空间深度与光空间深度映射中的一个值进行比较。 这意味着 VSM 与 PCF 的偏移量问题不同。 从技术上讲,VSM 对更大区域的深度采样,以及执行统计分析。 这不如 PCF 精确。 在实践中,VSM 在混合方面做得非常好,这减少了必要的偏移量。 如上所述,VSM 的头号缺点是轻微出血。
VSM 和 PCF 表示 GPU 计算能力和 GPU 纹理带宽之间的权衡。 VSM 需要执行更多数学运算来计算方差。 PCF 需要更多的纹理内存带宽。 大型 PCF 内核可能会很快受到纹理带宽的瓶颈。 随着 GPU 计算能力的增长速度比 GPU 带宽快,VSM 正变得越来越实用这两种算法。 由于混合和筛选,VSM 在分辨率较低的阴影映射中看起来也更好。
总结
CSM 为透视别名问题提供了解决方案。 有多种可能的配置可以获取游戏所需的视觉保真度。 PCF 和 VSM 广泛使用,应与 CSM 结合使用以减少别名。
参考
唐纳利,W.和劳里森,A. 方差阴影地图。 在 SI3D '06:2006 年交互式 3D 图形和游戏研讨会的论文集。 2006. 第 161-165 页。 纽约,纽约,美国:ACM出版社。
劳里森,安德鲁和麦考尔,迈克尔。 分层方差阴影映射。 2008 年 5 月 28 日至 30 日,加拿大安大略省温莎。
恩格尔,沃夫冈F.第4节。 级联阴影映射。 ShaderX5, Advanced Rendering Techniques, Wolfgang F. Engel, Ed. 查尔斯河媒体,波士顿,马萨诸塞州。 2006. 第 197-206 页。