3.12.2输出合并状态配置
输出合并阶段由一对状态对象控制:深度模版状态和混合状态。除此之外,输出合并还可以绑定资源,这些资源最终表示渲染管道的最终输出。在本节中,我们将考虑如何配置这些属性中的每一个,以及处理每种可能的状态和资源有哪些限制
深度模版状态
深度测试和模版测试都配置有相同的状态对象ID3DllDepthStencilState。与Direct3D 11中的所有资源一样,该对象是通过设备接口创建的。深度模版状态是使用ID3DllDevice::Create DepthStencilState方法创建的,该方法将指针指向包含所需状态选项的描述结构。
清单3.25列出了深度模板状态描述的成员
typedef struct D3D11_DEPTH_STENCIL_DESC {
BOOL DepthEnable;
D3D11_DEPTH_WRITE_MASK DepthWriteMask;
D3D11_COMPARISON_FUNC DepthFunc;
BOOL StencilEnable;
UINT8 StencilReadMask;
UINT8 StencilWriteMask;
D3D11_DEPTH_STENCILOP_DESC FrontFace;
D3D11_DEPTH_STENCILOP_DESC BackFace;
} D3D11_DEPTH_STENCIL_DESC;
结构的前三个参数配置深度测试功能,而其余参数用于配置模板测试。一旦创建了深度模具状态,它是不可变的,不能更改。如果应用程序需要另一个状态处于活动状态,则必须创建一个单独的状态对象,并将其绑定到管道中以代替当前状态。这是通过ID3DllDeviceContext::OMSet DepthStencilState方法实现的。通常,有一个相应的get方法来检索绑定到管道的当前状态。“输出合并阶段处理”部分将更详细地讨论这些成员所做的工作。
混合状态/Blend State
输出合并支持的第二个状态对象是ID3D11BlendState。此混合状态对象控制在将颜色值写入输出渲染目标之前如何将其混合在一起。此阶段对象是使用ID3DllDevice::Create BlendState()方法创建的,该方法获取一个指向描述结构的指针,该描述结构指定状态对象将表示的配置。清单3.26显示了混合状态描述的内容。
typedef struct D3D11_BLEND_DESC {
BOOL AlphaToCoverageEnable;
BOOL IndependentBlendEnable;
D3D11_RENDER_TARGET_BLEND_DESC RenderTarget[8];
} D3D11_BLEND_DESC;
第一个参数确定输出颜色的alpha值是否将用于确定子样本覆盖率,而不是光栅化器覆盖率值,或者如果像素着色器写入SV_Coverage系统值,则确定SV_Coverage系统值语义。这可以用于实现透明渲染的形式,而无需首先对场景中的对象进行排序。该结构的第二构件和第三构件彼此结合使用。IndependentBlendEnable参数指示在使用多个渲染目标时,是否会为每个渲染目标指定单独的混合模式。如果该参数为true,则RenderTarget数组中的八个元素中的每一个都会为其各自的渲染目标提供混合状态。如果“独立混合”为false,则索引0处的元素将用于所有渲染目标。创建混合状态对象后,将使用ID3DllDeviceContext::OMSetBlendstate()方法通过设备上下文将其绑定到管道。和往常一样,有一个相应的get方法来检索当前绑定到管道的状态。我们将在“输出合并阶段处理”部分探讨混合配置的细节。
渲染目标状态
输出合并的渲染目标状态表示接收整个管道中执行的所有渲染计算结果的实际资源。可以使用相对大量的不同配置来接收此输出,当然,对可以一起使用的渲染目标组合也有限制。我们将首先讨论可以绑定到输出合并的各种类型的渲染目标,然后看看应用程序是如何操作它们的。
输出合并具有八个渲染目标槽和一个深度模具槽。每个渲染目标通过渲染目标视图(RTV)绑定到管道,深度模具目标通过深度模具视图(DSV)绑定。在传统的渲染配置中,单个渲染目标与单个深度目标绑定。然而,这不是一项要求。
该应用程序可以同时绑定1到8个总渲染目标,以生成同一光栅化场景的多个版本。这些渲染目标的类型(如Texture2D、Texture2DArray等)和大小(包括宽度、高度、深度、阵列大小和采样数)必须匹配,但可能具有不同的格式。当绑定了多个渲染目标时,它被称为多渲染目标(MRT)配置。
这种安排也使用单个深度模具目标,即使有多个渲染目标在使用中也是如此。由于深度模具目标用于执行深度和模具测试,因此它的类型和大小必须与绑定为渲染目标的资源相同,但其格式将是深度模具格式之一。如上所述,这些大小匹配要求也适用于MSAA渲染目标。如果MSAA渲染目标绑定为输出,则深度模具目标也必须是具有相同数量子样本的MSAA资源。此外,资源的数组大小必须在渲染目标和深度模具目标之间匹配。您可能还记得在第2章中,资源可以创建为数组资源,其中在同一资源中创建多个纹理切片。如果渲染目标具有六个阵列切片以生成立方体贴图,则深度模具目标也必须具有六个数组切片。
一种最终配置也是可能的,它根本不使用渲染目标。可以仅将深度模具视图绑定到管道。在这种情况下,通过将NULL值绑定到每个渲染目标槽来清空它,而深度模具视图则像在常规渲染配置中一样被绑定。这通常用于在继续进行其他渲染过程之前,用场景的深度信息填充深度模具目标。
MRT vs. render target arrays
应该注意MRT和渲染目标阵列之间的区别。可以创建八个单独的纹理资源,然后将它们绑定到输出合并阶段以用于MRT配置。也可以创建具有八个纹理切片的单个纹理阵列资源,然后将该资源绑定到输出合并以用于单个渲染目标配置。尽管它们将提供相同数量的有效渲染目标,但在使用这些配置的机制及其功能方面存在一些差异。MRT配置使用多个渲染目标槽——每个要使用的渲染目标都有一个槽。另一方面,基于阵列的资源在输出合并中仅占用单个渲染目标槽。这在两种配置之间产生了另一个差异——MRT设置同时写入所有渲染目标,而基于阵列的设置一次只写入一个切片。由于像素着色器可以同时写入所有MRT渲染目标,因此只需要调用单个像素着色器即可写入所有目标。由于在基于阵列的配置中,像素着色器只能写入单个渲染目标,因此需要调用一个像素着色器才能写入其每个纹理切片。
这种情况似乎表明MRT配置是一个更好的选择,因为这可以减少像素着色器调用的次数,同时仍然写入相同数量的渲染目标。但是,渲染目标阵列资源也有几个优点。从某种意义上说,我们可以说MRT配置对其所有渲染目标使用相同的光栅化。这意味着,如果将三角形光栅化到渲染目标的右上角,则它将显示在写入的所有渲染目标的同一位置。每个渲染目标阵列都使用可以根据SV_RenderTargetArrayIndex系统值语义自定义的单独光栅化。这允许在光栅化之前对几何体使用不同的视图变换,因此也允许将基本体光栅化到渲染目标内的不同位置。
我们可以将这两种配置视为拆分管道以最终写入多个渲染目标,但它们将其拆分在不同的位置。MRT配置在像素着色器阶段分割管道,而基于阵列的配置在光栅化器阶段之前分割管道。这两种配置在不同的情况下都很有用,具体取决于需要什么类型的管道输出。这种差异如图3.79所示。
关于渲染和深度模具目标的绑定,还有另一个方面需要考虑。我们在上面提到,这些资源与资源视图绑定——RTV和DSV。由于资源的子资源可以由资源视图指定,因此可以通过使用该资源的较小部分的资源视图将较大的资源绑定到管道。当将此与上述所有其他选项和配置一起考虑时,开发人员有大量不同的选项可用于创建专门的渲染算法。
绑定渲染目标
渲染目标和深度模具目标都绑定到单个设备上下文方法调用中的输出合并。ID3DllDeviceContext::OMSetRenderTargets()方法(如清单3.27所示)除了获取一个指定数组中目标数量的整数外,还获取一个指向渲染目标视图的指针数组的指针和一个指向深度模具视图的指针。绑定渲染目标后,管道将保留对渲染目标的引用,并将保留该引用,直到用NULL引用替换渲染目标为止。
另一种资源类型可以绑定到输出合并阶段。我们在像素着色器阶段的部分中看到,它可以使用无序访问视图(UAV)。但是,像素着色器阶段不能接受无人机进行绑定。相反,它们以与渲染目标相同的方式绑定到输出合并阶段。使用无人机时,它们与ID3D11DeviceContext::OMSetRenderTargetsAndUnorderedAccessViews()方法绑定。该方法的前三个参数与仅渲染目标的版本相同,而其余四个参数用于绑定无人机。共有八个无人机插槽可用,受此调用影响的插槽范围是通过UAVStartSlot和NumUAVs参数选择的。ppUnorderedAccessView参数是指向要绑定的无人机阵列的指针。当然,此阵列中的无人机数量必须与NumUAVs参数中指定的视图数量相匹配。
此方法的最后一个参数是指向UINT值数组的另一个指针。这些值提供当前缓冲区计数器值,以便在附加/消耗缓冲区中使用。如第2章所述,与UAV一起使用的缓冲器包含一个内部计数器,用于指示缓冲器中存在的元素数量。此计数器隐藏在缓冲区中,由运行时维护。但是,绑定时存储在这些计数器中的值由传递到此数组中的值控制。这使应用程序能够根据需要有效地重置缓冲区中的数据,或者如果传递了-1的值,则保持当前的内部缓冲区计数器值。
绑定的渲染目标和无人机的总数不得超过8个。然而,任何加起来不超过八个的组合都是允许的,包括使用八个无序访问视图和无渲染目标。如果没有绑定到管道的渲染目标,则UAC表示整个管道的唯一输出。将呈现目标绑定到输出合并的两种方法如清单3.27所示
void OMSetRenderTargets(
UINT NumViews,
ID3D11RenderTargetView **ppRenderTargetViews,
ID3D11DepthStencilView *pDepthStencilView
);
void OMSetRenderTargetsAndUnorderedAccessViews(
UINT NumViews,
ID3D11RenderTargetView **ppRenderTargetViews,
ID3D11DepthStencilView *ppDepthStencilView,
UINT UAVStartSlot,
UINT NumUAVs,
ID3DllUnorderedAccessView **ppUnorderedAccessView,
const UINT *pUAVInitialCounts
);
Direct3D 11中的另一个新功能是在输出合并阶段中使用深度模板资源作为深度/模板目标的能力,以及在可编程着色器阶段之一中通过着色器资源视图同时查看其内容的能力。乍一看,这似乎违反了不能同时从不同资源读取和写入资源的规则,因为深度模板资源将在输出合并的深度测试部分写入.
然而,如果深度模具视图是用适当的只读标志创建的,这确保了输出合并阶段不会写入资源。相反,它只由输出合并读取,以确定深度测试是否通过或失败(如果当前启用了深度测试)。这有效地使资源绑定到的两个管道位置都是只读访问点,这仍然遵循同时读/写规则。该配置如图3.80所示。这很有用,因为辅助渲染过程可以使用深度缓冲区内容,而不必将它们复制到另一个资源中,同时该资源也用于深度测试.