1. 程式人生 > >【D3D11遊戲程式設計】學習筆記十五:混合(Blending)

【D3D11遊戲程式設計】學習筆記十五:混合(Blending)

        (注:【D3D11遊戲程式設計】學習筆記系列由CSDN作者BonChoix所寫,轉載請註明出處:http://blog.csdn.net/BonChoix,謝謝~)

       在D3D11中,“混合”發生在畫素著色器階段的下一階段,即Output Merger Stage。整個場景在全部經歷過畫素著色器階段後,對應於螢幕上每一點畫素,可能有多個片段(Fragment)。如下圖所示:

        該圖中,場景中有三個點P1,P2,P3投影在螢幕上同一個點P。這樣在畫素著色器階段後,針對P1,P2,P3將有三個片段與畫素P對應。預設情況下,在渲染管線中,混合是被關閉的。這時為了確定畫素P的最終顯示顏色,主要依據是深度測試(這時暫不考慮模板測試等其他因素)。通過深度測試的片段將自身顏色替代後緩衝區中P點的當前顏色,未通過的片段被拋棄。

       當混合功能被開啟時,決定最終顏色的方法有所不同。當一個片段通過深度測試後,並不是直接取代後緩衝區中P點的當前顏色,而是通過一定的比例因子與之進行插值(混合),並將結果作為P點的當前值。當然,未通過深度測試的片段依然被拋棄。這個是“混合”的一種最簡單例子。除此之外,D3D11針對混合階段有非常多的配置,從而實現各種特殊效果。

       1. 混合方程

       學習混合的第一步,就是要了解混合方程,方程如下:

      

       該方程針對每個畫素逐一進行。方程左邊的C為混合結果,右邊Csrc(我們稱為源顏色)和Cdst(我們稱為目標顏色)分別為即將要處理的片段的顏色和後緩衝區中該畫素當前的顏色;Fsrc和Fdst分別是兩個顏色對應的混合因子。注意方程中的

在這裡為“分量相乘”(Componentwise multiplication),即針對顏色值中的R、G、B三種分量分別進行相乘。源顏色和目標顏色分別與相應的混合因子分量相乘後,將結果進行“op"(混合操作)操作,作為當前片段處理的最終顏色,並替換後緩衝區中該畫素處的顏色。

       以上計算公式只適合於顏色值中RGB三個分量。此外,Alpha分量的計算公式完全一致:

      

       2. 混合操作

針對上述公式中的混合操作”op",在D3D11中定義在如下列舉型別中:

typedef enum D3D11_BLEND_OP {
  D3D11_BLEND_OP_ADD            = 1,
  D3D11_BLEND_OP_SUBTRACT       = 2,
  D3D11_BLEND_OP_REV_SUBTRACT   = 3,
  D3D11_BLEND_OP_MIN            = 4,
  D3D11_BLEND_OP_MAX            = 5 
} D3D11_BLEND_OP;

我們省略各個變數的字首D3D11_BLEND_OP_。

ADD表示相加操作,即

SUBTRACT表示相關(目標-源),即

REV_SUBTRACT表示反射的相關(源-目標),即

MIN表示取源、目標顏色中較小值,即

MAX表示取源、目標顏色中較大值,即

注意:MIN和MAX操作與混合因子無關。

同樣,所有這些操作也適合於計算Alpha值。

3. 混合因子

針對方程中的混合因子F,在D3D11中有如下幾種:

D3D11_BLEND_ZERO:此外針對顏色混合,F為(0,0,0),針對alpha值,F為0。

D3D11_BLEND_ONE:針對顏色混合為(1,1,1),針對alpha值為1;

D3D11_BLEND_SRC_COLOR:針對顏色混合為(Rs,Gs, Bs),針對alpha值為As;

D3D11_BLEND_INV_SRC_COLOR:針對顏色混合為(1-Rs,1-Gs,1-Bs),針對alpha值為1-As;

D3D11_BLEND_SRC_ALPHA:針對顏色混合為(As,As,As),針對alpha值為As;

D3D11_BLEND_INV_SRC_ALPHA:針對顏色混合為(1-As,1-As,1-As),針對alpha值為1-As;

D3D11_BLEND_DEST_COLOR:針對顏色混合為(Rd,Gd,Bd),針對alpha值為Ad;

D3D11_BLEND_INV_DESC_COLOR:針對顏色混合為(1-Rd,1-Gd,1-Bd),針對alpha值為1-Ad;

D3D11_BLEND_DEST_ALPHA:針對顏色混合為(Ad,Ad,Ad),針對alpha值為Ad;

D3D11_BLEND_INV_DEST_ALPHA:針對顏色混合為(1-Rd,1-Rd,1-Rd),針對alpha值為1-Ad;

D3D11_BLEND_BLEND_FACTOR:此時的混合因子為程式設計師指定的顏色值(R,G,B,A),針對顏色混合為(R,G,B),針對alpha值為A。該顏色值通過函式ID3D11DeviceContext::OMSetBlendState來指定;

D3D11_BLEND_INV_BLEND_FACTOR:同上,為程式設計師指定顏色值,針對顏色混合為(1-R,1-G,1-B),針對alpha值為1-A。

4. 混合狀態

D3D11中,設定混合狀態前要先建立相應的混合狀態介面ID3D11BlendState,建立函式如下:

HRESULT CreateBlendState(
  [in]   const D3D11_BLEND_DESC *pBlendStateDesc,
  [out]  ID3D11BlendState **ppBlendState
);

第二個引數為要建立的介面的地址,第一個引數為一個用來描述混合狀態引數的結構,定義如下:

typedef struct D3D11_BLEND_DESC {
  BOOL                           AlphaToCoverageEnable;
  BOOL                           IndependentBlendEnable;
  D3D11_RENDER_TARGET_BLEND_DESC RenderTarget[8];
} D3D11_BLEND_DESC;


第一個引數設定是否開啟AlphaToCoverage,AlphaToCoverage在後面會詳細介紹,暫時先不用,設定為false;

第二個引數設定是否針對不同的RenderTarget使用不同的混合狀態。在D3D11中,一共可以支援多達8個的渲染物件(RenderTarget),如果針對不同的物件想使用不同的混合方式,則設定為true。由於我們暫時用不到,因此設定為false;

第三個引數為針對8個RenderTarget分別指定的混合狀態引數,當第二個引數為false時,這裡我們只需要設定陣列中第一個元素即可。

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;

第一個需要設定為true,以開啟混合狀態;

後面的引數即我們的混合方程中的引數:

SrcBlend、DestBlend分別為源、目標顏色混合因子;

BlendOp為源、目標顏色的混合操作;

SrcBlendAlpha、DestBlendAlpha為源、目標alpha值的混合因子;

BlendOpAlpha為源、目標alpha值的混合操作;

RenderTargetWriteMask為最終混合結果在寫到緩衝區時的掩碼,即用來指定哪些位寫進去,哪些們不能寫。針對該引數有如下列舉型別:

typedef enum D3D11_COLOR_WRITE_ENABLE {
  D3D11_COLOR_WRITE_ENABLE_RED     = 1,
  D3D11_COLOR_WRITE_ENABLE_GREEN   = 2,
  D3D11_COLOR_WRITE_ENABLE_BLUE    = 4,
  D3D11_COLOR_WRITE_ENABLE_ALPHA   = 8,
  D3D11_COLOR_WRITE_ENABLE_ALL     = 
      ( D3D11_COLOR_WRITE_ENABLE_RED | D3D11_COLOR_WRITE_ENABLE_GREEN |  
        D3D11_COLOR_WRITE_ENABLE_BLUE | D3D11_COLOR_WRITE_ENABLE_ALPHA ) 
} D3D11_COLOR_WRITE_ENABLE;

其中,RED、GREEN、BLUE和ALPHA分別表示只允許寫入R、G、B、A部分的值。預設情況下,我們把帶個顏色值替換緩衝區中的值,因此該引數我們指定為D3D11_COLOR_WRITE_ENABLE_ALL。

建立好ID3D11BlendState介面後,通過以下函式來設定為指定的狀態:

void OMSetBlendState(
  [in]  ID3D11BlendState *pBlendState,
  [in]  const FLOAT* BlendFactor,
  [in]  UINT SampleMask
);

第一個引數即建立的介面;

第二個引數為程式設計師手動指定的混合因子,即剛介紹混合因子時,如果指定引數為D3D11_BLEND_BLEND_FACTOR或D3D11_BLEND_INV_BLEND_FACTOR,則使用第二個引數指定的顏色值為了混合因子;

第三個引數為取樣點掩碼。D3D11中的多重取樣可以支援32個取樣點,該引數用來決定”使用/丟棄"哪些取樣點。該引數型別為UINT,32位,其中從最低位到最高位分別代表一個取樣點。比如,如果第5位指定為0,則第5個取樣點將被丟棄。當然,只有當開啟至少5重取樣時該設定才有效。如果當前設定為單個取樣點,則只有最低位才對我們有用。預設情況下,該引數值為0xFFFFFFFF,即對所有采樣點有效。

Voila,使用混合的所有步驟就這些~ 初次接觸是不是覺得有點頭暈?有太多的結構要填,引數名又非常長! 其實一點也不難,引數名長有一個很大的好處,就是“自解釋”,看到名字一下子就能明白其用意,而且還很容易記住。只要理解了混合的基本原理,這些過程其實是一氣呵成的事。以下是一個開啟混合的例子:

//開啟透明
D3D11_BLEND_DESC transDesc;
//先建立一個混合狀態的描述
transDesc.AlphaToCoverageEnable = false;		//關閉AlphaToCoverage
transDesc.IndependentBlendEnable = false;		//不針對多個RenderTarget使用不同的混合狀態
//因此只設置第一個陣列元素即可
transDesc.RenderTarget[0].BlendEnable = true;
transDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
transDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
transDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
transDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
transDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
transDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
transDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
//建立ID3D11BlendState介面
device->CreateBlendState(&transDesc,&TransparentBS);

//現在可以設定混合狀態了
//先指定混合因子,一般不用它,除非在上面混合因子指定為使用blend factor
float factor[4] = {1.f,1.f,1.f,1.f};
//使用該狀態
deviceContext->OMSetBlendState(TransparentBS,factor,0xffffffff);	//32個取樣點都有效

渲染完後,一般要恢復預設狀態,這時使用NULL引數即可,如下:

deviceContext->OMSetBlendState(0,factor,0xffffffff);


5. 幾種混合的例子

在D3D中通過合理地設定不同的混合狀態,可以實現各種各樣的效果。以下是幾個常見的例子:

5.1 禁止顏色寫入

有時候,在渲染過程中,我們只希望修改深度/模板緩衝區部分,而且希望保持後緩衝區中的原有顏色值,這時修,我們需要“禁止顏色寫入”。

一種方法是把目標混合因子設為D3D11_BLEND_ONE,把源混合因子設為D3D11_BLEND_ZERO,這樣混合方程中源部分相乘後結果為0,目標部分相乘後保持原樣,相加結果仍為原來的顏色,這樣即禁止了顏色的寫入;還有另一種更為直觀的方法是,直接把描述中D3D11_RENDER_TARGET_BLEND_DESC::RenderTargetWriteMask成員設為0,即任何一位都無法寫入。

5.2 把顏色相加、相減

在處理片段時,如果我們希望把片段顏色與後緩衝區中當前顏色值相加。這時,可以通過把源、目標混合因子全部設為D3D11_BLEND_ONE。這樣,在乘以相應的混合因子後,源、目標顏色保持不變。對於混合操作,如果是實現相加,可以設定為D3D11_BLEND_OP_ADD,如果為相減,可以設定為D3D11_BLEND_OP_SUBTRACT或D3D11_BLEND_OP_REV_SUBTRACT(取決於源顏色值減目標顏色值,還是相反)。

5.3 把顏色相乘

如果希望把片段顏色值與後緩衝區中對應的當前值相乘,可以設定目標混合因子為D3D11_BLEND_SRC_COLOR,而把源混合因為設為D3D11_BLEND_ZERO。這樣,混合方程中源部分變為0,對於目標部分,由於混合因子是片段的顏色值,因為目標顏色乘以混合因子,實際上就是目標顏色與源顏色相乘了。至於混合操作,設定為相加即可,即D3D11_BLEND_OP_ADD。

5.4 透明效果

有時候,我們需要渲染透明的物體,比如玻璃。透過玻璃,我們可以看見其後面的物體。一般情況下,實現透明效果時,我們需要用到該物體的alpha值。比如對於alpha為0.4,意思是該物體60%透明,即最終我們觀察到的顏色40%來自該物體,60%來自其後面的物體。要實現這種效果,我們可以為源顏色指明混合因子為D3D11_BLEND_SRC_ALPHA,即該片段對應的alpha值,目標因子設為D3D11_BLEND_INV_ALPHA。混合操作設定相加:D3D11_BLEND_OP_ADD。這時,比如透明物體的alpha值為0.25,即源混合因子為0.25,目標混合因子為0.75。

6. 使用“透明”效果時的注意事項

在場景中包含透明物體時需要額外注意的是,要先渲染不透明物體,然後把透明物體由遠到近逐個渲染。

原因很簡單,如果很渲染了透明物體,這樣當渲染該透明物體後面的物體時,將無法通過深度測試而被丟棄,從而導致無法再看到透明物體後面的物體。

7. 示例程式

在本節的示例程式中,演示的是水面的透明效果。場景為一個很大的水池,透過水麵,可以看到水池底部以及水中的物體。當然,這裡的水面僅僅是靜態的水面,沒有任何波動效果。以下是程式截圖:

操作說明:滑鼠左鍵按下旋轉螢幕,右鍵按下調整鏡頭遠近。

以下是示例程式原始碼: