Windows 8 Directx 開發學習筆記(十二)利用混合實現浮在水面的木箱

在場景中繪製多個不透明物體時很簡單,哪個物體離得近,看到的就是哪個物體。但如果加入一個透明的物體,像玻璃,如何渲染就有些麻煩。拿一塊紅色的玻璃擋住眼睛,看到的物體都偏紅,換成藍色的玻璃,物體都偏藍。DirectX中的“混合(Blending)”技術可以解決這個問題。混合技術其實也不難,但是通過不同運算方式和係數的組合,它能實現很多效果。它的基本原理就是混合方程:


其中乘號表示向量對應元素相乘,C和A分別表示顏色和Alpha的混合結果,其它參數可以自由指定。詳細的介紹可以看DirectX 10遊戲編程入門,雖然版本不同,但是原理沒有區別。繼續拿玻璃做例子,個人感覺混合技術就像一個調色板,只有兩種顏料,分別是玻璃的顏色Csrc和它後面對應位置的物體顏色Cdst,而最終的顏色C是兩種顏料按不同比例混合後的顏色,其中Fsrc控制玻璃的顏料比例,Fdst控制物體的顏料比例。Alpha通道的混合原理與顏色混合類似,不過少了兩個分量。

因爲Csrc和Cdst隨物體不同而變化,所以實際使用混合時每個方程需要指定三個參數:混合方式、Fsrc和Fdst。在DirectX 11中,與混合相關的結構體定義如下:

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

typedef struct D3D11_RENDER_TARGET_BLEND_DESC
{
    BOOL BlendEnable;
    D3D11_BLEND SrcBlend;   // Csrc
    D3D11_BLEND DestBlend;  // Cdst
    D3D11_BLEND_OP BlendOp; // 顏色混合方式
    D3D11_BLEND SrcBlendAlpha;  // Fsrc
    D3D11_BLEND DestBlendAlpha; // Fdst
    D3D11_BLEND_OP BlendOpAlpha;   // Alpha混合方式
UINT8 RenderTargetWriteMask;
}   D3D11_RENDER_TARGET_BLEND_DESC;

其他參數的說明可以參照MSDN

http://msdn.microsoft.com/ZH-CN/library/windows/desktop/ff476087(v=vs.85).aspx

瞭解混合技術的原理之後就要着手將混合效果添加到上一篇文章中已實現的模型中去,給水面添加透明的效果,使它更真實些。首先從修改頂點着色器代碼開始。打算將Alpha紋理一整張鋪在水面上,不需要複製。所以在頂點着色器的輸出中添加一個texa成員,用於保存Alpha紋理座標。

struct VertexShaderOutput
{
    float4 posH : SV_POSITION;
    float3 posW   : POSITION;
    float3 normal : NORMAL;
    float2 tex : TEXCOOD;
    float2 texa :TEXCOOD_ALPHA;
};

main方法中,正常紋理座標需要變換,但是Alpha紋理座標不需要。

// 紋理座標變換
output.tex = mul(float4(input.tex, 0.0f, 1.0f),texTransform).xy;
 
// Alpha紋理不進行變換
output.texa = input.tex;

然後就要修改像素着色器部分,像素着色器的輸入自然與頂點着色器的輸出對應。同時,像素着色器還要添加一個全局變量texAlpha存儲Alpha紋理資源。

Texture2D texDiffuse : register(t0);
Texture2D texAlpha : register(t1);
 
struct PixelShaderInput
{
    float4 posH : SV_POSITION;
    float3 posW : POSITION;
    float3 normal : NORMAL;
    float2 tex : TEXCOOD;
    float2 texa :TEXCOOD_ALPHA;
};

注意texDiffusetexAlpha後面的register,它用來說明資源的存儲位置。Alpha紋理的使用與普通紋理使用沒有區別,不過這次需要用採樣器採的是紋理的Alpha分量,用.a表示。

// 只有水模型會設置並使用Alpha紋理
// 繪製其他模型時不保證texAlpha一定有內容
// 不能實際應用
// 按原始透明度顯示太透明所以乘4
finalColor.a = texAlpha.Sample(samplerLinear,input.texa).a * 4;      

接下來就是修改C++代碼。需要改動的地方只有Renderer.h和Renderer.cpp兩個文件而已。這次載入的Alpha紋理資源是dds格式的,所以向項目中添加DDSTextureLoader.h和DDSTextureLoader.cpp文件,並在Renderer.h中包含DDSTextureLoader.h。此外,還要在Renderer類中添加兩個成員,用來保存Alpha紋理視圖和混合狀態。

Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_WaterAlphaSRV;
Microsoft::WRL::ComPtr<ID3D11BlendState> m_BlendState;

完成後就可以在CreateDeviceResources方法中的createTexTask部分添加Alpha紋理載入代碼並初始化混合狀態。

DX::ThrowIfFailed(
    CreateDDSTextureFromFile(
    m_d3dDevice.Get(),
    L"Texture/water_alpha.dds",
    &tex,
    m_WaterAlphaSRV.GetAddressOf()
    )
    );
 
// 初始化混合狀態
D3D11_BLEND_DESC blendDesc = {0};
blendDesc.RenderTarget[0].BlendEnable = TRUE;
blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; // Color_Fsrc
blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; // Color_Fdst
blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; // Color_Operation
blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; // Alpha_Fsrc
blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; // Alpha_Fdst
blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; // Alpha_Operation
blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
 
DX::ThrowIfFailed(
    m_d3dDevice->CreateBlendState(
    &blendDesc,
    &m_BlendState
    )
    );

主要說明下顏色混合的設置。顏色的混合參數Fsrc和Fdst的說明可以從MSDN找到

http://msdn.microsoft.com/ZH-CN/library/windows/desktop/ff476086(v=vs.85).aspx

D3D11_BLEND_SRC_ALPHA

The blend factoris (As, As, As, As), that is alpha data (A) from a pixel shader. No pre-blendoperation.

D3D11_BLEND_INV_SRC_ALPHA

The blend factoris ( 1 - As, 1 - As, 1 - As, 1 - As), that is alpha data (A) from a pixelshader. The pre-blend operation inverts the data, generating 1 - A.

D3D11_BLEND_OP_ADD

Add source 1 andsource 2.

將其代入混合方程可得出實際的計算公式:


假設當前玻璃的透明度是0.2,那麼最終的混合結果就是0.2份的玻璃顏色加上0.8份的物體顏色。創建好混合狀態之後就能夠使用了。不過這時要注意物體的繪製順序,需要遵守下面的規則:

首先繪製不透明物體。然後,根據透明物體與攝像機之間的距離進行排序,按照從後向前的順序繪製透明物體。因爲混合時,透明物體要在不透明物體的前面,從而能夠透過它看到後面的物體。如果這時不透明物體還沒有畫出來,那什麼都看不到。所以,必須將透明物體後面的物體繪製出來後再進行混合。

這樣,在Render方法中,要完成陸地渲染後再設置混合狀態,並設置Alpha紋理,最後渲染水面,完成後清除混合狀態。

// 設置水面紋理和Alpha紋理
m_d3dContext->PSSetShaderResources(
    0,  // 對應像素着色器中register(t0)
    1,
    m_WaterSRV.GetAddressOf()
    );
 
m_d3dContext->PSSetShaderResources(
    1,  // 對應像素着色器中register(t1)
    1,
    m_WaterAlphaSRV.GetAddressOf()
);
 
// 設置混合狀態
FLOAT blendFactors[4] = { 0, };
m_d3dContext->OMSetBlendState(m_BlendState.Get(),blendFactors, 0xffffffff);
 
……
 
// 清除混合模式狀態
m_d3dContext->OMSetBlendState(0, blendFactors,0xffffffff);

實際運行效果如下圖:


圖1是使用Alpha紋理貼圖的效果,圖2是統一水面的Alpha爲0.6。藍圈中是兩圖類似的部分,紅圈則是不同部分,不是特別明顯。

還可以在這個模型中加入之前實現的木箱,細節不用多說,注意要在繪製水面之前渲染木箱。效果如下圖:

 

本篇文章的源代碼:Direct3DApp_HillWaveBlend

原文地址:http://blog.csdn.net/raymondcode/article/details/8456714

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章