1. 程式人生 > >D3D12渲染技術之混合

D3D12渲染技術之混合

本篇部落格給讀者介紹混合,見下圖所示, 我們首先繪製地形,然後繪製木箱,然後將地形和木箱畫素放在後面的緩衝區上。 我們使用混合將水面繪製到後面的緩衝區,以便水畫素與地形混合,並在後面緩衝區上放置畫素,使地形和木箱通過水顯示。 從現在起,我們將研究混合技術,這些技術允許我們將當前光柵化的畫素(所謂的源畫素)與先前光柵化到後緩衝區的畫素(所謂的目標畫素)混合(組合)。 除了上面我們提到的,這種技術使我們能夠渲染半透明物體,如水和玻璃。
在這裡插入圖片描述
為了便於學習,我們特別提到後緩衝區作為渲染目標, 但是,稍後我們將展示我們可以渲染到“螢幕外”渲染目標。 混合適用於這些渲染目標,目標畫素是先前光柵化到這些螢幕外渲染目標的畫素值。下面先介紹混合方程。

混合方程

設Csrc是我們當前光柵化的第i個畫素(源畫素)的畫素著色器的顏色輸出,並且讓Cdst是當前在後緩衝器(目標畫素)上的第i個畫素的顏色。 如果沒有混合,Csrc將覆蓋Cdst(假設它通過深度/模板測試)併成為第i個後緩衝區畫素的新顏色。 但是通過混合,Csrc和Cdst被混合在一起以獲得將覆蓋Cdst的新顏色C(即,混合顏色C將被寫入後緩衝器的第i個畫素)。 Direct3D使用以下混合方程來混合源畫素和目標畫素顏色:
在這裡插入圖片描述
顏色Fsrc(源混合因子)和Fdst(目標混合因子)可以是上述公式中描述的任何值,它們允許我們以各種方式修改原始源畫素和目標畫素,允許實現不同的效果, ⊗運算子意味著定義的顏色向量的分量乘法; 運算子可以是任何二元運算子。
上述混合方程僅適用於顏色的RGB分量, alpha元件實際上由一個單獨的類似方程式處理:
在這裡插入圖片描述


等式基本相同,但混合因子和二元運算可能不同, 將RGB與alpha分離的動機很簡單,因此我們可以獨立處理它們,使用不同地方式處理它們,這會出現更多種可能性。
與混合RGB元件相比,混合alpha元件的頻率要低得多, 這主要是因為我們不關心後緩衝區alpha值,如果我們有一些需要目標alpha值的演算法,則後緩衝區alpha值才是重要的。

混合操作

混合方程中使用的二元運算子可以是以下之一:
在這裡插入圖片描述
在最小/最大操作中忽略混合因子。
這些相同的運算子也適用於alpha混合方程, 此外,我們可以為RGB和alpha指定不同的運算子。 例如,可以新增兩個RGB術語,但減去兩個alpha術語。

最近新增到Direct3D的一個功能是使用邏輯運算子而不是上面的傳統混合方程來混合源顏色和目標顏色。 可用的邏輯運算子如下:

typedef 
enum D3D12_LOGIC_OP
{
  D3D12_LOGIC_OP_CLEAR   = 0,
  D3D12_LOGIC_OP_SET   = ( D3D12_LOGIC_OP_CLEAR + 1 ) ,
  D3D12_LOGIC_OP_COPY   = ( D3D12_LOGIC_OP_SET + 1 ) ,
  D3D12_LOGIC_OP_COPY_INVERTED   = ( D3D12_LOGIC_OP_COPY + 1 ) ,
  D3D12_LOGIC_OP_NOOP   = ( D3D12_LOGIC_OP_COPY_INVERTED + 1 ) ,
  D3D12_LOGIC_OP_INVERT   = ( D3D12_LOGIC_OP_NOOP + 1 ) ,
  D3D12_LOGIC_OP_AND   = ( D3D12_LOGIC_OP_INVERT + 1 ) ,
  D3D12_LOGIC_OP_NAND   = ( D3D12_LOGIC_OP_AND + 1 ) ,
  D3D12_LOGIC_OP_OR   = ( D3D12_LOGIC_OP_NAND + 1 ) ,
  D3D12_LOGIC_OP_NOR   = ( D3D12_LOGIC_OP_OR + 1 ) ,
  D3D12_LOGIC_OP_XOR   = ( D3D12_LOGIC_OP_NOR + 1 ) ,
  D3D12_LOGIC_OP_EQUIV   = ( D3D12_LOGIC_OP_XOR + 1 ) ,
  D3D12_LOGIC_OP_AND_REVERSE   = ( D3D12_LOGIC_OP_EQUIV + 1 ) ,
  D3D12_LOGIC_OP_AND_INVERTED   = ( D3D12_LOGIC_OP_AND_REVERSE + 1 ) ,
  D3D12_LOGIC_OP_OR_REVERSE   = ( D3D12_LOGIC_OP_AND_INVERTED + 1 ) ,
  D3D12_LOGIC_OP_OR_INVERTED   = ( D3D12_LOGIC_OP_OR_REVERSE + 1 ) 
} D3D12_LOGIC_OP;

請注意,我們不能同時使用傳統的混合和邏輯運算子混合, 只能挑選一個或另一個。 另請注意,為了使用邏輯運算子混合,渲染目標格式必須支援 - 它應該是UINT變體的格式,否則將收到如下錯誤:

D3D12 ERROR: ID3D12Device::CreateGraphicsPipelineState: The render target format at slot 0 is format (R8G8B8A8_UNORM). This format does not support logic ops. The Pixel Shader output signature indicates this output could be written, and the Blend State indicates logic op is enabled for this slot. [ STATE_CREATION ERROR #678: CREATEGRAPHICSPIPELINESTATE_OM_RENDER_TARGET_DOES_NOT_SUPPORT_LOGIC_OPS]
D3D12 WARNING: ID3D12Device::CreateGraphicsPipelineState: Pixel Shader output ‘SV_Target0’ has type that is NOT unsigned int, while the corresponding Output Merger RenderTarget slot [0] has logic op enabled. This happens to be well defined: the raw bits output from the shader will simply be interpreted as UINT bits in the blender without any data conversion. This warning is to check that the application developer really intended to rely on this behavior. [ 
STATE_CREATION WARNING #677: CREATEGRAPHICSPIPELINESTATE_PS_OUTPUT_TYPE_MISMATCH]

混合因子

通過為源和目標混合因子設定不同的組合以及不同的混合運算子,可以實現許多不同的混合效果。 我們將在後面說明一些組合,但我們需要嘗試其他組合以瞭解它們的作用。 以下列表描述了適用於Fsrc和Fdst的基本混合因子, 有關其他一些高階混合因子,請參閱SDK文件中的D3D12_BLEND列舉型別。 設Csrc =(rs,gs,bs),Asrc = as(從畫素著色器輸出的RGBA值),Cdst =(rd,gd,bd),Adst = ad(已儲存在渲染目標中的RGBA值), F是Fsrc或Fdst而F是Fsrc或Fdst,我們有:

D3D12_BLEND_ZERO: F = (0, 0, 0) and F = 0
D3D12_BLEND_ONE: F = (1, 1, 1) and F = 1
D3D12_BLEND_SRC_COLOR: F = (rs, gs, bs)
D3D12_BLEND_INV_SRC_COLOR: Fsrc = (1 − rs, 1 − gs, 1 − bs)
D3D12_BLEND_SRC_ALPHA: F = (as, as, as) and F = as
D3D12_BLEND_INV_SRC_ALPHA: F = (1 − as, 1 − as, 1 − as) and F = (1 − as)
D3D12_BLEND_DEST_ALPHA: F = (ad, ad, ad) and F = ad
D3D12_BLEND_INV_DEST_ALPHA: F = (1 − ad, 1 − ad, 1 − ad) and F = (1 − ad) 
D3D12_BLEND_DEST_COLOR: F = (rd, gd, bd) 
D3D12_BLEND_INV_DEST_COLOR: F = (1 − rd, 1 − gd, 1 − bd)
D3D12_BLEND_SRC_ALPHA_SAT: F = (a′s, a′s, a′s) and F = a′s
  

其中a =clamp(as,0, 1)
D3D12_BLEND_BLEND_FACTOR:F =(r,g,b)和 F = a,其中顏色(r,g,b,a)被提供給ID3D12GraphicsCommandList :: OMSetBlendFactor方法的第二個引數。 這允許我們指定要直接使用的混合因子顏色,但是,在更改混合狀態之前,它是恆定的。

D3D12_BLEND_INV_BLEND_FACTOR:F =(1-r,1-g,1-b)和F = 1-a,其中顏色(r,g,b,a)由ID3D12GraphicsCommandList :: OMSetBlendFactor方法的第二個引數提供, 這允許我們指定要直接使用的混合因子顏色; 但是,在您更改混合狀態之前,它是恆定的。

所有上述混合因子都適用於RGB混合方程, 對於alpha混合等式,不允許以_COLOR結尾的混合因子。

Clamp函式定義如下:
在這裡插入圖片描述
我們可以使用以下函式設定混合因子顏色:

void ID3D12GraphicsCommandList::OMSetBlendFactor(
  const FLOAT BlendFactor[ 4 ]);
傳遞空指標會恢復預設的混合因子(1,1,1,1)。

混合狀態

我們已經討論過混合運算子和混合因子,但是我們在哪裡用Direct3D設定這些值? 與其他Direct3D狀態一樣,混合狀態是PSO的一部分。 到目前為止,我們一直在使用預設的混合狀態,它禁用混合:

D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePsoDesc;
ZeroMemory(&opaquePsoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
…
opaquePsoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);

要配置非預設混合狀態,我們必須填寫D3D12_BLEND_DESC結構。 D3D12_BLEND_DESC結構的定義如下:

typedef struct D3D12_BLEND_DESC {
 BOOL AlphaToCoverageEnable;  // Default: False
 BOOL IndependentBlendEnable;  // Default: False
 D3D11_RENDER_TARGET_BLEND_DESC RenderTarget[8];
} D3D11_BLEND_DESC;

1、AlphaToCoverageEnable:指定true以啟用alpha-to-coverage,這是一種在渲染樹葉或柵欄紋理時非常有用的多重取樣技術。 指定false以禁用alpha-to-coverage。 Alpha-to-coverage需要啟用多重取樣(即,使用多重取樣建立背面和深度緩衝區)。
2、IndependentBlendEnable:Direct3D支援同時渲染多達八個渲染目標。 當此標誌設定為true時,表示可以對每個渲染目標執行不同的混合(不同的混合因子,不同的混合操作,混合禁用/啟用等)。 如果此標誌設定為false,則表示將按照D3D12_BLEND_DESC :: RenderTarget陣列中第一個元素所描述的相同方式混合所有渲染目標。 多個渲染目標用於高階演算法; 現在,假設我們一次只渲染到一個渲染目標。
3、RenderTarget:一個包含8個D3D12_RENDER_TARGET_BLEND_DESC元素的陣列,其中第i個元素描述瞭如何為第i個同時渲染目標進行混合。 如果IndependentBlendEnable設定為false,則所有渲染目標都使用RenderTarget [0]進行混合。
D3D12_RENDER_TARGET_BLEND_DESC結構的定義如下:

typedef struct D3D12_RENDER_TARGET_BLEND_DESC
{
  BOOL BlendEnable; // Default: False
  BOOL LogicOpEnable; // Default: False
  D3D12_BLEND SrcBlend; // Default: D3D12_BLEND_ONE
  D3D12_BLEND DestBlend; // Default: D3D12_BLEND_ZERO
  D3D12_BLEND_OP BlendOp; // Default: D3D12_BLEND_OP_ADD
  D3D12_BLEND SrcBlendAlpha; // Default: D3D12_BLEND_ONE
  D3D12_BLEND DestBlendAlpha; // Default: D3D12_BLEND_ZERO
  D3D12_BLEND_OP BlendOpAlpha; // Default: D3D12_BLEND_OP_ADD
  D3D12_LOGIC_OP LogicOp;   // Default: D3D12_LOGIC_OP_NOOP
  UINT8 RenderTargetWriteMask; // Default: D3D12_COLOR_WRITE_ENABLE_ALL
} D3D12_RENDER_TARGET_BLEND_DESC;

1、BlendEnable:指定true以啟用混合,指定false以禁用它。 請注意,BlendEnable和LogicOpEnable不能都設定為true; 可以使用常規混合或邏輯運算子混合。
2、LogicOpEnable:指定true以啟用邏輯混合操作, 請注意,BlendEnable和LogicOpEnable不能都設定為true,可以使用常規混合或邏輯運算子混合。
3、SrcBlend:D3D12_BLEND列舉型別的成員,它指定RGB混合的源混合因子Fsrc。
4、DestBlend:D3D12_BLEND列舉型別的成員,它指定RGB混合的目標混合因子Fdst。
5、BlendOp:D3D12_BLEND_OP列舉型別的成員,用於指定RGB混合運算子。
6、SrcBlendAlpha:D3D12_BLEND列舉型別的成員,它指定用於Alpha混合的目標混合因子Fsrc。
7、DestBlendAlpha:D3D12_BLEND列舉型別的成員,它指定用於Alpha混合的目標混合因子Fdst。
8、BlendOpAlpha:D3D12_BLEND_OP列舉型別的成員,指定alpha混合運算子。
9、LogicOp:D3D12_LOGIC_OP列舉型別的成員,指定用於混合源顏色和目標顏色的邏輯運算子。
10、RenderTargetWriteMask:以下一個或多個標誌的組合:

typedef enum D3D12_COLOR_WRITE_ENABLE {
 D3D12_COLOR_WRITE_ENABLE_RED   = 1,
 D3D12_COLOR_WRITE_ENABLE_GREEN  = 2,
 D3D12_COLOR_WRITE_ENABLE_BLUE  = 4,
 D3D12_COLOR_WRITE_ENABLE_ALPHA  = 8,
 D3D12_COLOR_WRITE_ENABLE_ALL   = 
   ( D3D12_COLOR_WRITE_ENABLE_RED | D3D12_COLOR_WRITE_ENABLE_GREEN | 
    D3D12_COLOR_WRITE_ENABLE_BLUE | D3D12_COLOR_WRITE_ENABLE_ALPHA ) 
} D3D12_COLOR_WRITE_ENABLE;

這些標誌控制在混合後寫入後緩衝區中的哪些顏色通道? 例如,我們可以通過指定D3D12_COLOR_WRITE_ENABLE_ALPHA禁用對RGB通道的寫入,並僅寫入Alpha通道。 這種靈活性對於高階技術非常有用。 禁用混合時,將使用從畫素著色器返回的顏色,而不應用寫入蒙版。
混合不是免費的,並且需要額外的每畫素工作,因此只有在需要時才啟用它,並在完成後將其關閉。
以下程式碼顯示了建立和設定混合狀態的示例程式碼:

// Start from non-blended PSO 
D3D12_GRAPHICS_PIPELINE_STATE_DESC transparentPsoDesc = opaquePsoDesc;
 
D3D12_RENDER_TARGET_BLEND_DESC transparencyBlendDesc;
transparencyBlendDesc.BlendEnable = true;
transparencyBlendDesc.LogicOpEnable = false;
transparencyBlendDesc.SrcBlend = D3D12_BLEND_SRC_ALPHA;
transparencyBlendDesc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
transparencyBlendDesc.BlendOp = D3D12_BLEND_OP_ADD;
transparencyBlendDesc.SrcBlendAlpha = D3D12_BLEND_ONE;
transparencyBlendDesc.DestBlendAlpha = D3D12_BLEND_ZERO;
transparencyBlendDesc.BlendOpAlpha = D3D12_BLEND_OP_ADD;
transparencyBlendDesc.LogicOp = D3D12_LOGIC_OP_NOOP;
transparencyBlendDesc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
transparentPsoDesc.BlendState.RenderTarget[0] = transparencyBlendDesc;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(
  &transparentPsoDesc, IID_PPV_ARGS(&mPSOs["transparent"])));

與其他PSO一樣,我們應該在應用程式初始化時建立它們,然後根據需要使用ID3D12GraphicsCommandList :: SetPipelineState方法在它們之間切換。