睿诚科技协会

directx 模板缓冲技术

什么是模板缓冲?

想象一下,你有一张普通的画布(颜色缓冲/深度缓冲),现在你在这张画布上再覆盖一张“遮罩纸”,这张“遮罩纸”就是模板缓冲

directx 模板缓冲技术-图1
(图片来源网络,侵删)
  • 它是什么? 模板缓冲是一个与渲染目标(通常是后台缓冲)大小相同的像素缓冲区,但它的存储内容不是颜色值,而是无符号整数(通常是 8 位,即 0-255)。
  • 它的作用是什么? 它本身不直接参与最终的图像显示,而是像一个“模板”或“遮罩”,用来控制后续像素的写入操作,你可以通过特定的渲染操作来修改这个模板上的值,然后利用这些值来决定哪些像素应该被绘制,哪些应该被丢弃。

模板缓冲技术就是利用一个整数缓冲区来精确控制哪些像素片段应该被绘制到屏幕上。


核心工作原理:模板测试

模板缓冲的工作核心是 模板测试,这个测试在深度测试之后,光栅化阶段进行。

对于每一个待绘制的像素片段,它会执行以下流程:

  1. 读取模板值:从模板缓冲中读取当前像素位置的模板值。
  2. 进行比较:将读取到的值与一个参考值进行比较,这个参考值是在绘制时由应用程序设置的。
  3. 执行操作:根据比较的结果(通过/失败),以及一个预设的模板操作,来更新模板缓冲中该位置的值。

这个“比较”和“操作”的过程是通过一个状态对象来配置的,这个状态对象就是 D3D12_DEPTH_STENCIL_DESC

directx 模板缓冲技术-图2
(图片来源网络,侵删)

关键概念:深度/模板状态描述

在 DirectX 12 中,模板功能是和深度功能捆绑在一起的,通过 CD3DX12_DEPTH_STENCIL_DESC 结构体来配置,这个结构体包含两个主要部分:对深度缓冲的配置和对模板缓冲的配置。

我们重点关注与模板相关的字段:

  • DepthEnable / StencilEnable:分别启用深度测试和模板测试。
  • StencilReadMask:模板读取掩码,在读取模板缓冲的值时,会先与这个掩码进行按位与操作,这让你可以只关心模板值的某些位。
  • StencilWriteMask:模板写入掩码,在写入模板缓冲的值时,会先与这个掩码进行按位与操作,这让你在修改模板值时,只能修改某些位,保护其他位不变。
  • FrontFaceBackFace:这两个结构体分别定义了三角形正面和背面朝向相机时的模板测试规则,它们各自包含:
    • StencilFunc模板测试函数,定义如何比较 StencilReadMask 处理后的模板值和参考值。
      • 常用值:D3D12_COMPARISON_FUNC_ALWAYS (总是通过), D3D12_COMPARISON_FUNC_NEVER (总是失败), D3D12_COMPARISON_FUNC_EQUAL (相等), D3D12_COMPARISON_FUNC_NOT_EQUAL (不相等), D3D12_COMPARISON_FUNC_LESS (小于) 等。
    • StencilPassOp模板测试通过时执行的操作
      • 常用值:D3D12_STENCIL_OP_KEEP (保持不变), D3D12_STENCIL_OP_ZERO (置零), D3D12_STENCIL_OP_REPLACE (替换为参考值), D3D12_STENCIL_OP_INCR (加1), D3D12_STENCIL_OP_DECR (减1), D3D12_STENCIL_OP_INCR_SAT (饱和加1, 不超过255), D3D12_STENCIL_OP_DECR_SAT (饱和减1, 不低于0)。
    • StencilFailOp模板测试失败时执行的操作
    • StencilDepthFailOp模板测试通过,但深度测试失败时执行的操作

工作流程示例

假设我们要实现一个效果:在屏幕中央绘制一个镂空的矩形,矩形内部不绘制任何东西。

  1. 初始化

    directx 模板缓冲技术-图3
    (图片来源网络,侵删)
    • 创建一个包含深度和模板的纹理(D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)。
    • 在渲染通道 中,设置该纹理为深度/模板附件。
    • 清除渲染目标时,同时清除颜色、深度和模板缓冲,将整个模板缓冲清除为 0
  2. 第一步:绘制“模板”

    • 目标:在模板缓冲上“刻”出矩形的形状。
    • 配置
      • 设置 StencilEnable = true
      • 设置 StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS (总是通过测试)。
      • 设置 StencilPassOp = D3D12_STENCIL_OP_REPLACE (如果测试通过,就将模板值替换为我们的参考值,1)。
      • 设置 StencilFailOp = D3D12_STENCIL_OP_KEEP (如果测试失败,保持原样)。
    • 绘制:绘制一个实心矩形,这个矩形的像素会通过模板测试,并将模板缓冲中对应区域的值从 0 改为 1,模板缓冲上有一个值为 1 的矩形区域。
  3. 第二步:绘制最终内容

    • 目标:绘制我们想要看到的场景,但要受到上一步“模板”的限制。
    • 配置
      • 设置 StencilEnable = true
      • 设置 StencilFunc = D3D12_COMPARISON_FUNC_NOT_EQUAL (只有当模板值不等于参考值 1 时,测试才通过)。
      • 设置 StencilPassOp = D3D12_STENCIL_OP_KEEP (通过测试,保持模板值不变)。
      • 设置 StencilFailOp = D3D12_STENCIL_OP_KEEP (测试失败,保持模板值不变,并且不绘制该像素)。
    • 绘制:绘制你的整个场景(比如一个旋转的立方体)。
      • 对于模板值为 0 的区域(矩形外),NOT_EQUAL 测试通过,像素被绘制。
      • 对于模板值为 1 的区域(矩形内),NOT_EQUAL 测试失败,像素被丢弃,不会绘制。

最终效果:场景被绘制,但中央矩形区域是空的,完美实现了一个“窗口”效果。


典型应用场景

模板缓冲技术是许多高级图形效果的基石:

  1. 轮廓渲染

    • 方法:启用深度测试,禁用模板测试,绘制所有物体,启用模板测试,将模板函数设置为 ALWAYS,并将 StencilPassOp 设置为 INCR,再绘制一遍物体,这会将所有可见物体区域的模板值加 1,改变物体的渲染方式(比如用线框模式或一个稍大的实体),并将模板函数设置为 NOT_EQUAL,参考值为 1,这样,只有模板值为 1 的边缘区域才会被绘制出来,从而形成轮廓。
  2. 阴影体

    • 方法:这是实现软阴影的经典技术,从光源视角渲染场景,将所有面向光源的面的模板值加 1,将背向光源的面的模板值减 1,回到主视角,在绘制阴影区域时,检查模板值,如果模板值为 0,说明该点既没有被光照物体遮挡,也没有被阴影体覆盖,则正常绘制,如果模板值不为 0,说明该点在阴影中。
  3. 反射/折射

    • 方法:先绘制一个定义反射/折射区域的形状(如一个平面),并利用模板测试将这个区域的模板值设置为特定值,在渲染反射/折射内容时,只向模板值匹配的区域写入像素,从而精确控制反射/折射图像只出现在指定区域内。
  4. 复杂裁剪

    在游戏中实现一个有复杂形状的洞(如栅栏、格栅),你可以先绘制这个洞的形状到模板缓冲中,然后再渲染场景,场景就会自动被这个洞的形状裁剪掉。

  5. 多通道渲染

    在一个渲染通道中,利用模板测试来选择性地更新场景的特定部分,而其他部分保持不变,实现更精细的渲染控制。


DirectX 12 中的实现要点

在 DX12 中使用模板缓冲,你需要:

  1. 创建资源:创建一个 D3D12_RESOURCE_DESC,并将其 Flags 设置为 D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL
  2. 配置清除值:使用 CD3DX12_CLEAR_VALUE 来指定深度和模板的初始清除值。
  3. 创建深度模板视图:使用 D3D12_DEPTH_STENCIL_VIEW_DESC 来描述这个资源的视图格式。
  4. 配置渲染通道:在 D3D12_RENDER_PASS_DEPTH_STENCIL_DESC 中,指定深度/模板附件、以及清除和保留操作。
  5. 设置管线状态:创建 CD3DX12_DEPTH_STENCIL_DESC,根据你的需求配置模板测试和操作规则,并将其放入 D3D12_GRAPHICS_PIPELINE_STATE_DESC 中,最终创建 ID3D12PipelineState 对象。
  6. 绘制:在绘制调用之前,确保设置了正确的管线状态和根签名。

模板缓冲技术是一个“幕后英雄”,它不直接生成颜色,但通过一个灵活的、基于像素的遮罩系统,赋予了你前所未有的渲染控制力,它就像是图形渲染流程中的一个智能开关,让你能够精确地告诉 GPU:“只在满足这些条件的像素上绘制东西”。

虽然配置起来比简单的颜色绘制要复杂一些,但一旦掌握,它就能让你实现许多原本需要复杂算法或多个渲染通道才能完成的效果,是通往高级图形编程的必经之路。

分享:
扫描分享到社交APP
上一篇
下一篇