配置 Depth-Stencil 功能

本部分介绍设置深度模板缓冲区的步骤,以及输出合并阶段的深度模板状态。

了解如何使用深度模板缓冲区和相应的深度模板状态之后,请参阅 高级模板技术

创建 Depth-Stencil 资源

使用纹理资源创建深度模板缓冲区。

ID3D11Texture2D* pDepthStencil = NULL;
D3D11_TEXTURE2D_DESC descDepth;
descDepth.Width = backBufferSurfaceDesc.Width;
descDepth.Height = backBufferSurfaceDesc.Height;
descDepth.MipLevels = 1;
descDepth.ArraySize = 1;
descDepth.Format = pDeviceSettings->d3d11.AutoDepthStencilFormat;
descDepth.SampleDesc.Count = 1;
descDepth.SampleDesc.Quality = 0;
descDepth.Usage = D3D11_USAGE_DEFAULT;
descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
descDepth.CPUAccessFlags = 0;
descDepth.MiscFlags = 0;
hr = pd3dDevice->CreateTexture2D( &descDepth, NULL, &pDepthStencil );

创建 Depth-Stencil 状态

深度模板状态告知输出合并阶段如何执行 深度模板测试。 深度模板测试用于确定是否绘制给定像素。

D3D11_DEPTH_STENCIL_DESC dsDesc;

// Depth test parameters
dsDesc.DepthEnable = true;
dsDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
dsDesc.DepthFunc = D3D11_COMPARISON_LESS;

// Stencil test parameters
dsDesc.StencilEnable = true;
dsDesc.StencilReadMask = 0xFF;
dsDesc.StencilWriteMask = 0xFF;

// Stencil operations if pixel is front-facing
dsDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
dsDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

// Stencil operations if pixel is back-facing
dsDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
dsDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

// Create depth stencil state
ID3D11DepthStencilState * pDSState;
pd3dDevice->CreateDepthStencilState(&dsDesc, &pDSState);

DepthEnable 和 StencilEnable 启用或禁用深度和模板测试功能。 将 DepthEnable 设置为 FALSE 可禁用深度测试并阻止写入深度缓冲区。 将 StencilEnable 设置为 FALSE 以禁用模板测试并防止写入模板缓冲区(当 DepthEnable 为 FALSE 且 StencilEnable 为 TRUE 时,深度测试在模板操作中总是会通过)。

DepthEnable 仅影响输出合并阶段,它不会影响数据输入到像素着色器之前对值的裁剪、深度偏差或限制。

将 Depth-Stencil 数据绑定到 OM 阶段

绑定深度模板状态。

// Bind depth stencil state
pDevice->OMSetDepthStencilState(pDSState, 1);

使用视图绑定深度模板资源。

D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
descDSV.Format = DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
descDSV.Texture2D.MipSlice = 0;

// Create the depth stencil view
ID3D11DepthStencilView* pDSV;
hr = pd3dDevice->CreateDepthStencilView( pDepthStencil, // Depth stencil texture
                                         &descDSV, // Depth stencil desc
                                         &pDSV );  // [out] Depth stencil view

// Bind the depth stencil view
pd3dDeviceContext->OMSetRenderTargets( 1,          // One rendertarget view
                                &pRTV,      // Render target view, created earlier
                                pDSV );     // Depth stencil view for the render target

渲染目标视图数组可以传递到 ID3D11DeviceContext::OMSetRenderTargets,但是所有这些渲染目标视图都将对应于单个深度模板视图。 Direct3D 11 中的呈现目标数组是一项功能,使应用程序能够在基元级别同时呈现到多个呈现目标上。 通过使用渲染目标数组来提高性能,而不是通过多次调用 ID3D11DeviceContext::OMSetRenderTargets(这实际上是 Direct3D 9 中的方法)来单独设置渲染目标。

呈现目标必须是同一类型的资源。 如果使用多重采样抗锯齿,则所有绑定的呈现目标和深度缓冲区必须具有相同的采样数。

当缓冲区用作渲染目标时,不支持深度模板测试和多个渲染目标。

  • 可以同时绑定多达 8 个渲染目标。
  • 所有呈现目标的尺寸在各个维度上都必须相同(宽度、高度,以及 3D 的深度或数组类型的数组大小)。
  • 每个呈现器目标可能具有不同的数据格式。
  • 写入掩码用于控制数据写入到渲染目标的操作。 输出写掩码在每个渲染目标和每个组件的级别上控制写入哪些数据到渲染目标。

高级模版技术

深度模板缓冲区的模板部分可用于创建渲染效果,如合成、贴花和轮廓化。

合成

您的应用程序可以使用模板缓冲区将 2D 或 3D 图像合成到 3D 场景中。 模板缓冲区中的掩码用于遮蔽渲染目标表面的区域。 然后,可以将存储的 2D 信息(如文本或位图)写入遮挡区域。 或者,应用程序可以将其他 3D 基元渲染到渲染目标图面的模板掩码区域。 它甚至可以呈现整个场景。

游戏通常将多个 3D 场景组合在一起。 例如,驾驶游戏通常显示后视镜。 后视镜展示了驾驶员后方的3D场景视图。 它实质上是一个与司机的前视景相叠加的第二个3D场景。

贴花

Direct3D 应用程序使用贴图来控制从特定基元图像绘制到渲染目标表面的像素。 应用程序将贴花应用于基元的图像,使共面多边形能够正确呈现。

例如,将轮胎标记和黄色线条应用于道路时,标记应直接显示在道路顶部。 但是,标记和道路的 z 值是相同的。 因此,深度缓冲区可能不会在两者之间产生干净分隔。 后基元中的某些像素可能呈现在前基元的顶部,反之亦然。 生成的图像显示为从帧到帧闪闪发光。 这种现象称为 z值争斗或闪烁。

若要解决此问题,请使用模板来屏蔽后图元部分,其中会出现贴花。 关闭z缓冲,并将前图元的图像渲染到呈现目标表面的已掩蔽区域。

多个纹理混合可用于解决此问题。

轮廓和剪影

您可以使用模板缓冲区来实现更抽象的效果,例如描边和剪影效果。

如果您的应用程序进行两次渲染传递——第一次用于生成模板掩码,第二次用于将模板掩码应用于图像,但在第二次传递中图元略小——则生成的图像将仅包含图元的轮廓。 然后,应用程序可以使用纯色填充图像的模板遮罩区域,使基元具有浮雕效果。

如果模板掩码的大小和形状与您正在渲染的基元相同,则生成的图像会在基元应所在的位置出现一个孔。 然后,应用程序可以使用黑色填充空白处,以生成原始图形的剪影。

Two-Sided 模板

阴影体积用于通过模板缓冲区绘制阴影。 应用程序通过计算遮挡几何体的轮廓边缘,并将其从光源挤出形成一组3D体积,从而计算阴影体积。 然后将这些卷两次渲染到模板缓冲区中。

第一个渲染绘制正向多边形,并递增模板缓冲区值。 第二次渲染绘制阴影体积的背面多边形,并递减模板缓冲区的值。 通常,所有递增和递减的值都会相互抵消。然而,场景已经使用常规几何图形进行渲染,导致在渲染阴影体积时,某些像素未通过 z 缓冲区测试。 模板缓冲区中剩余的值对应于阴影中的像素。 这些剩余的模板缓冲区内容用作掩码,将一个大型的全黑矩形通过 alpha 混合到场景中。 当模板缓冲区充当掩码时,结果是使阴影中的像素变暗。

这意味着每个光源绘制两次阴影几何图形,从而给 GPU 的顶点吞吐量施加压力。 双面模板功能旨在缓解这种情况。 在此方法中,有两组模板状态(如下所示),一组用于正面三角形,另一组用于背面三角形。 这样,每个阴影体积(每个光源)只绘制一个通道。

可以找到双面模板实现的一个示例,它位于 ShadowVolume10 示例中。

将 Depth-Stencil 缓冲区作为纹理读取

着色器可以将非活动的深度模板缓冲区作为纹理来读取。 读取深度模板缓冲区作为纹理的应用程序分为两个阶段渲染,第一阶段写入深度模板缓冲区,第二阶段从缓冲区读取数据。 这样,着色器就可以将以前写入缓冲区的深度或模板值与当前正在渲染的像素值进行比较。 比较结果可用于在粒子系统中创建阴影映射或软粒子等效果。

若要创建可用作深度模具资源和着色器资源的深度模具缓冲区,需要对 “创建 Depth-Stencil 资源 ”部分中的示例代码进行一些更改。

  • 深度模板资源必须具有无类型的格式,例如DXGI_FORMAT_R32_TYPELESS。

    descDepth.Format = DXGI_FORMAT_R32_TYPELESS;
    
  • 深度模板资源必须同时使用D3D10_BIND_DEPTH_STENCIL和D3D10_BIND_SHADER_RESOURCE绑定标志。

    descDepth.BindFlags = D3D10_BIND_DEPTH_STENCIL | D3D10_BIND_SHADER_RESOURCE;
    

此外,还需要使用 D3D11_SHADER_RESOURCE_VIEW_DESC 结构和 ID3D11Device::CreateShaderResourceView 为深度缓冲区创建着色器资源视图。 着色器资源视图将使用类型化格式,例如 DXGI_FORMAT_R32_FLOAT ,该格式等效于创建深度模具资源时指定的无类型格式。

在第一次渲染过程中,绑定深度缓冲区,如 将 Depth-Stencil 数据绑定到 OM 阶段 部分中所述。 请注意,传递给 D3D11_DEPTH_STENCIL_VIEW_DESC的格式。格式将使用类型化格式,例如 DXGI_FORMAT_D32_FLOAT。 在第一次渲染过程后,深度缓冲区将包含场景的深度值。

在第二个渲染通道中,使用 ID3D11DeviceContext::OMSetRenderTargets 函数将深度模板视图设置为 NULL 或不同的深度模板资源,并使用 ID3D11EffectShaderResourceVariable::SetResource 将着色器资源视图传递给着色器。 这使着色器能够查找在第一次渲染过程中计算的深度值。 请注意,如果第一个呈现传递的视点不同于第二个呈现传递,则需要应用转换来检索深度值。 例如,如果使用阴影映射技术,则第一个渲染通道将从光源的角度进行渲染,而第二个渲染通道将从查看者的角度进行渲染。

Output-Merger 阶段

管道阶段(Direct3D 10)