【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分別是兩個顏色對應的混合因子。注意方程中的
以上計算公式只適合於顏色值中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. 示例程式
在本節的示例程式中,演示的是水面的透明效果。場景為一個很大的水池,透過水麵,可以看到水池底部以及水中的物體。當然,這裡的水面僅僅是靜態的水面,沒有任何波動效果。以下是程式截圖:
操作說明:滑鼠左鍵按下旋轉螢幕,右鍵按下調整鏡頭遠近。
以下是示例程式原始碼: