3.12.3输出合并阶段处理
输出合并阶段执行两种类型的操作:可见性测试和混合操作。
- 可见性测试提供了可配置的操作,用于确定是否应将每个特定片段混合到附加到输出合并的输出资源中。如果片段通过了深度测试和模具测试,则会将其传递给混合函数,以便与渲染目标组合。
- 混合功能也是可配置的,允许使用多种方法将像素着色器输出应用于渲染目标。
在本节中,我们将遵循片段在输出合并阶段所遵循的路径。我们首先讨论两个可见性测试中的每一个,以更好地了解测试是如何操作的,以及可以对它们进行哪些修改。接下来,我们研究混合函数如何执行其职责,并检查可用于混合的模式。
可见性测试
输出合并阶段对传递给它的每个片段同时执行两个可见性测试:深度测试和模版测试。这两个测试都使用深度模版资源,该资源绑定到具有深度模具视图(DSV)的输出合并阶段,并且要求为深度模具资源选择适当的数据格式。可用于这种资源的格式通常包含用于存储当前深度值的一部分和用于存储当前模板值的另一部分。这种格式的一个例子是DXGI_F0RMAT_D24_UN0RM_S8_UINT,其中24位专用于保持深度值,8位专用于保存模版值。有些格式只包含深度值,没有专用于模具缓冲区的数据。在这些情况下,模板测试将被禁用,并且将始终通过。我们将了解这些数量中的每一个在其各自的可见性确定测试中是如何使用的。
启用后,将对发送到输出合并器的每个片段执行深度测试和模板测试。如果启用了MSAA,将对管道产生的每个子样本执行测试。这允许以光栅化器阶段使用的相同粒度执行可见性测试,并提高几何相交区域的图像质量。
模板测试
我们将考虑的第一个可见性测试是模板测试。此测试允许应用程序对渲染目标执行掩蔽操作,以控制何时将特定片段写入目标渲染目标。测试本身是可配置的,有相对大量的可能配置。此测试有两个组成部分。第一个是实际测试本身,第二个是用于更新模具缓冲区的更新机制。为了理解测试是如何工作的,我们将首先定义模具测试实现的等式。方程(3.6)提供了模版测试的伪代码。
(StencilRef & StencilMask) CompFunc (StencilBufferValue & StencilMask)
等式(3.6)评估为真或假,其中真结果表示测试已经通过,假结果表示测试失败。模板测试的各个参数是“输出合并状态配置”部分中讨论的可配置状态的组合,我们将在此处对每个参数进行审查。从方程的左侧开始,我们得到了模板参考值,该值与模板掩码逐位进行“与”运算。模具引用值是应用程序在ID3DllDeviceContext::OMSetDepthStencilState()方法中提供的无符号整数值,而模具掩码设置为深度模具状态描述的StencilReadMask成员。
如果我们花一点时间来考虑这个等式所代表的内容,我们会在左侧看到一个基于可以使用设备上下文方法配置的参数的自变量。右侧是一个基于当前模具缓冲区值的参数。这两个参数都被屏蔽,以允许选择包含在其源变量中的位的子集。然后将这两个参数与API提供的几个比较函数之一进行比较。因此,该方程的所有三个主要部分都是可配置的,允许进行各种可能的测试.
因为这个测试是高度可配置的,所以很难可视化它的操作方式。为了帮助阐明此功能,我们将为通过模板测试的单个片段提供一个小示例场景。在这种情况下,模版参考值将设置位0、1和3,而模版缓冲区设置位1和2。模具掩码设置了位0和位1。这些值如图3.81所示。
根据选择的比较函数,可以为模版测试找到不同的结果。例如,如果使用D3D11_COMPARIS0N_LESS,模板测试将失败,因为等式的左侧大于右侧。当然,如果使用D3D11_C0MPARIS0N_GREATER,则可以颠倒此功能。可用的比较函数如清单3.28所示。
enum D3Dll_COMPARISON_FUNC {
D3D11_C0MPARIS0N_NEVER,
D3D11_C0MPARIS0N_LESS,
D3Dll_COMPARISON_EQUAL,
D3Dll_COMPARISON_LESS_EQUAL,
D3Dll_COMPARISON_GREATER,
D3Dll_COMPARISON_NOT_EQUAL,
D3D11_C0MPARIS0N_GREATER_EQUAL,
D3D11_C0MPARIS0N_ALWAYS
}
考虑到指定模具参考值、模具掩码和比较功能的能力,开发人员或多或少可以完全控制模具测试的操作方式。测试完成后,需要采取一些措施,具体取决于测试是否通过。事实上,所采取的操作依赖于模板测试结果和深度测试结果(我们将在下一节中讨论)。为了与模板测试的可配置特性保持一致,可以在深度模板状态对象中指定各种可能性。可以为模具和深度测试结果的以下组合中的每一个设置不同的操作:
1.模板测试失败。
2.模板试验合格,但深度试验不合格。
3.模板试验合格,深度试验合格。
可以从D3D11_STENCIL_OP枚举的一个成员中选择要采取的操作,如清单3.29所示。
enum D3D11_STENCIL_0P {
D3D11_STENCIL_0P_KEEP,
D3D11_STENCIL_0P_ZER0,
D3D11_STENCIL_0P_REPLACE,
D3Dll_STENCIL_OP_INCR_SAT,
D3D11_STENCIL_0P_DECR_SAT,
D3D11_STENCIL_0P_INVERT,
D3D11_STENCIL_0P_INCR,
D3Dll_STENCIL_OP_DECR
}
这些选项决定在测试完成后对模具缓冲区值执行什么操作。有保留现有模具缓冲区值的选项;将其清零;将其替换为模具参考值;以及递增、递减或反转。同样,这些选项为各种不同的算法使用模板缓冲区提供了很大的自由度,因为根据测试结果可以采取不同的操作。在将模具值写入模具缓冲区之前,将执行最后一个操作。模具操作产生的值在存储之前与StencilWriteMask进行位“与”运算。
在本节中,我们看到了完整模板测试的配置。但是,还有一些其他配置需要讨论。上面显示的所有设置都可以分别针对深度模具状态对象的“正面”和“背面”成员的正面和背面进行配置。这些成员实际上是封装一组模板测试配置(比较函数和在每个测试结果用例中执行的操作)的结构。这样就可以实现对正面和背面执行不同选项的特殊测试。以这种方式使用不同设置的最常见示例是阴影体积算法。除了能够对正面和背面使用不同配置外,还可以使用StencilEnable布尔参数启用或禁用整个测试。如果模具测试被禁用,则始终认为它已通过,并且片段将永远不会因为模具测试而被剔除。
深度测试
在进行模版测试的同时,还进行深度测试。该测试基本上实现了用于执行可见性测试的经典Z缓冲算法(Williams,1978)。基本概念是保留与预期渲染目标大小相同的第二个缓冲区。然而,当对基元进行光栅化时,它会存储每个片段的归一化设备坐标的Z分量,而不是将光栅化的颜色值存储在缓冲区中。这会产生一个缓冲区,该缓冲区包含值在[0.0,1.0]范围内的片段位置的后分割Z分量。在输出合并阶段,该Z缓冲区被实现为深度模板缓冲区的深度部分,并且通常被称为深度缓冲区,因为Z分量表示与观看者的距离的度量。
当存储这些深度值时,任何被光栅化以创建片段的附加基元都可以将它们自己的深度值与存储在深度缓冲区中的深度值进行比较。如果存储在缓冲区中的深度值小于(更靠近观看者)新片段的值,则可以丢弃新片段,因为它位于场景中另一个对象的后面。如果存储的深度缓冲区值大于新片段(离观看者更远),则新片段的颜色值被传递到管道的其余部分,并且Z缓冲区值被更新以表示新的可见性信息。
以这种方式,Z缓冲器提供每个像素(或者如果使用MSAA,则提供每个样本)的可见性确定。输出合并实现了具有几个额外配置的Z缓冲区算法,用于准备深度值,以及实际执行深度比较并将结果写入Z缓冲区。这些配置主要包含在深度模具状态对象中,视口除外。我们将以与模板测试相同的方式跟踪片段进行深度测试,以更好地了解流程的工作方式。
可以使用深度模具状态对象的DepthEnable成员启用或禁用深度测试。此参数仅确定是否执行深度测试;它不控制深度写入功能是启用还是禁用。深度值直接从光栅化器阶段接收,或者如果像素着色器程序修改深度值,则从像素着色器阶段接收。然后将该值钳制为在用于生成片段的视口结构中指定的最小和最大深度值。该箝位是以深度缓冲器格式适当的方式执行的。在深度范围被箝位后,从深度模板缓冲区读取深度值,并将这两个值与可选深度比较函数进行比较,该函数是用深度模板状态对象的DepthFunc成员选择的。清单3.30提供了所有可用的比较函数。
enum D3D11_C0MPARIS0N_FUNC {
D3D11_C0MPARIS0N_NEVER,
D3D11_COMPARISON_LESS,
D3D11_COMPARISON_EQUAL,
D3D11_C0MPARIS0N_LESS_EQUAL,
D3D11_C0MPARIS0N_GREATER,
D3D11_C0MPARIS0N_N0T_EQUAL,
D3D11_C0MPARIS0N_GREATER_EQUAL,
D3D11_C0MPARIS0N_ALWAYS
}
这些比较是用左侧的片段深度值和右侧的深度缓冲值来执行的。如果比较结果为true,则深度测试已经通过,并且片段在该过程中继续。如果比较结果为false,则结束深度测试,并丢弃片段。例如,标准比较是使用D3Dll_COCOMPARION_LESS,它提供了与原始Z缓冲区算法类似的功能。如果碎片深度小于深度缓冲区值,它将通过深度缓冲区测试。这表示新片段离观看者更近,并且应该是可见的.
如果深度测试失败,则丢弃该片段。如果通过,它将被传递到混合功能(将在下一节中介绍)。深度缓冲器可以根据一些条件进行更新。如果深度测试和模具测试都已通过,则深度值的命运取决于是否启用深度写入。这是用深度模具状态的DepthWriteMask成员指定的。如果该值设置为D3D11_DEPTH_WRITE_MASK_ALL,则可以在深度缓冲器中更新深度值;否则,丢弃深度值。
深度缓冲区可以用于深度测试,但其配置仍然允许不写入,这似乎违反直觉。事实上,在许多情况下,这是现代渲染方案中的首选行为。使用上述标准Z缓冲区技术,第一次渲染过程用于用值填充深度缓冲区是非常常见的。然后,任何后续过程都将按原样使用深度缓冲区,因为所有几何体之前都已光栅化。实际上,可见性已经确定,因此不需要启用深度写入。当深度写入被禁用时,性能优势是可用的.
这种“只读深度缓冲区”的概念可以更进一步。您可能还记得,在第2章中,我们看到可以使用标志创建深度模具视图,以指示深度组件或模具组件,或两者都可以创建为只读。这意味着视图本身确保只能从中读取资源,从而允许在管道中的多个位置使用资源。该资源可以绑定在深度测试中使用,同时用作着色器资源。当深度缓冲区用作稍后渲染过程的输入,但仍需要同时用于深度测试时,这可能非常有益。
混合
如果一个片段在模版测试和深度测试中都存活了下来,那么它将被传递给混合函数。混合功能可以在将两个可选值写入输出渲染目标之前对其进行组合,用于组合值的功能也是可选的。该功能传统上用于执行alpha混合,以实现部分透明的渲染材质。然而,由于有大量可能的混合源、操作和写入选项,因此有许多附加功能可用于其他不太传统的技术。混合功能由混合状态对象中包含的配置控制。清单3.31提供了可以在混合状态下操作的列表成员。
typedef struct D3D11_BLEND_DESC {
BOOL AlphaToCoverageEnable;
BOOL IndependentBlendEnable;
D3D11_RENDER_TARGET_BLEND_DESC RenderTarget[8];
} D3D11_BLEND_DESC;
在这里,我们找到了两个顶级配置,然后是一个由八个渲染目标混合描述结构组成的阵列。第一个顶级成员是AlphaToCoverageEnable参数,我们已经在本章的“像素着色器”部分讨论过了。第二个顶级成员是另一个布尔值,用于确定在输出合并阶段中绑定输出的所有渲染目标是否将独立混合,或者它们是否都使用相同的混合配置。正如您现在可能已经猜到的那样,混合配置存储在混合状态的八元素阵列成员中,因为最多有八个渲染目标槽可用。如果通过将IndependentBlendEnable设置为true来启用单个混合,则这些阵列元素中的每个元素都定义了用于相应渲染目标窗的混合模式。如果禁用了独立混合,则所有渲染目标都将使用数组索引0中的混合配置。如果不需要,则应禁用独立混合以获得更好的性能。
为了深入了解混合配置是如何使用的,我们将使用与模板和深度测试相同的范式,并遵循通过混合功能运行的片段的路径。清单3.32显示了D3D11_RENDER_TARGET_BLEND_DESC结构的成员。
typedef struct D3D11_RENDER_TARGET_BLEND_DESC {
BOOL BlendEnable;
D3D11_BLEND SrcBlend;
D3D11_BLEND DestBlend;
D3D11_BLEND_OP BlendOp;
D3D11_BLEND SrcBlendAlpha;
D3D11_BLEND DestBlendAlpha;
D3D11_BLEND_OP BlendOpAlpha;
UINT8 RenderTargetWriteMask;
} D3D11_RENDER_TARGET_BLEND_DESC;
第一个成员BlendEnable执行其名称所表示的操作。它启用或禁用混合功能。我们将假设此示例启用了混合。接下来,我们看到两组三态。BlendEnable之后的三个成员用于混合颜色值(片段颜色的RGB分量),接下来的三个用于混合alpha值(片段色的A分量)。这种混合状态的分离允许将完全不同的混合模式用于alpha和color,这可以使它们用于不同的目的。
为颜色和alpha提供的三个成员用于定义混合方程式。混合方程式的两个数据源由用于颜色混合的SrcBlend和DestBlend参数以及用于alpha混合的Src BlendAlpha和DestBlendAlpha参数选择。这些参数将其数据源定义为D3D11_BLEND枚举的成员之一,如清单3.33所示
enum D3D11_BLEND {
D3D11_BLEND_ZER0,
D3D11_BLEND_0NE,
D3D11_BLEND_SRC_C0L0R,
D3D11_BLEND_INV_SRC_C0L0R,
D3D11_BLEND_SRC_ALPHA,
D3D11_BLEND_INV_SRC_ALPHA,
D3D11_BLEND_DEST_ALPHAJ
D3D11_BLEND_INV_DEST_ALPHA,
D3D11_BLEND_DEST_C0L0R,
D3D11_BLEND_INV_DEST_C0L0R,
D3D11_BLEND_SRC_ALPHA_SAT,
D3D11_BLEND_BLEND_FACT0R,
D3D11_BLEND_INV_BLEND_FACT0R,
D3D11_BLEND_SRC1_C0L0RJ
D3D11_BLEND_INV_SRC1_COLOR,
D3D11_BLEND_SRC1_ALPHA,
D3D11_BLEND_INV_SRC1_ALPHA
}
正如您所看到的,有大量可能的数据源。此列表中的常规选项包括数据源,如常数值和颜色/alpha值,以及几个可能的修饰符。颜色和alpha源都提供源和目标修改器,其中源是来自当前片段的值,目标是位于“目标”渲染目标中的值。这些值中的每一个还提供一个“反”值,该值是通过使用1减去该值而生成的。例如,D3D11_BLEND_SRC_COLOR选择当前片段颜色作为其源。但是,D3D11_BLEND_INV_DEST_COLOR选择目标渲染目标值的倒数作为其源。除了这些修改的颜色和alpha值中的每一个之外,还可以使用几个常数值中的一个。可以选择的常数值是0、1或应用程序使用ID3DllDeviceContext::OMSetBlendState()方法设置的混合因子。混合因子也提供了一个反向值。
您还可能注意到,有几个参数的名称中带有SRC1修饰符,例如D3D11_BLEND_SRC1_C0L0R和D3D11_BLEND_INV_SRC1_ALPHA。这些值实际上取自像素着色器中的第二输出寄存器,并且可以与第一输出寄存器同时用于混合操作。这允许从当前片段读取两个源数量,而不是使用目标缓冲区的内容。使用这两个输出寄存器值被称为双源颜色混合,并且只能与单个渲染目标输出一起使用(否则,第二个颜色值将传递给第二个渲染目标)。
一旦选择了数据源,就必须选择混合操作。此操作将两个数据源值组合为一个值,以写入输出渲染目标目标。清单3.34提供了可用的操作。可用的操作是不言自明的,并且实现了它们的名称所暗示的内容。有三种算术运算(A+B、A-B、B-A),以及最小和最大选择选项。
enum D3D11_BLEND_0P {
D3D11_BLEND_0P_ADD,
D3D11_BLEND_0P_SUBTRACT,
D3D11_BLEI\ID_0P_REV_SUBTRACT,
D3D11_BLEND_0P_MIN,
D3D11_BLEND_0P_MAX
}
如前所述,可以分别对颜色值和alpha值执行这些混合操作。一旦混合操作完成,就可以使用完整的四组分RGBA颜色进行书写。混合过程的最后一步是使用渲染目标写入掩码来确定实际将写入输出颜色的哪些通道。这是在混合状态结构的RenderTargetWriteMask成员中指定的。RGBA通道对应于掩模的比特位置0、1、2和3。设置该位后,通道将写入渲染目标。