DirectX11 With Windows SDK--14 深度測試

前言

當使用加法/減法/乘法顏色混合,或者使用透明混合的時候,在通過深度測試時可能會引起一些問題。例如如今咱們須要使用加法混合來繪製一系列對象,而這些對象彼此之間不會相互阻擋。若咱們仍使用原來的深度測試,就必須保證某一像素下的全部片元須要按照從遠到近的順序來進行繪製,但這很難作到,尤爲在繪製一些幾何體的時候可能會出如今前面的像素片元擋住了後面的像素片元的狀況。如今,咱們有能力經過調整深度測試這一行爲,來改變最終的顯示結果。html

在此以前須要額外瞭解的章節以下:node

章節回顧
11 混合狀態與光柵化狀態
12 深度/模板狀態、反射繪製
13 動手實現一個簡易Effects框架、陰影效果繪製

DirectX11 With Windows SDK完整目錄git

Github項目源碼github

歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。框架

交換律

加法/減法/乘法的混合運算是知足交換律的(對C0到Cn-1來講),這說明咱們繪製像素的前後順序應該是不會對結果產生影響的:
\(\mathbf{B'}=\mathbf{B}+\mathbf{C_0}+\mathbf{C_1}+...+\mathbf{C_{n-1}}\)
\(\mathbf{B'}=\mathbf{B}-\mathbf{C_0}-\mathbf{C_1}-...-\mathbf{C_{n-1}}\)
\(\mathbf{B'}=\mathbf{B} \otimes \mathbf{C_0} \otimes \mathbf{C_1} \otimes ... \otimes \mathbf{C_{n-1}}\)測試

可是混合的前後順序會對結果有影響,因此不知足交換律(不管alpha值如何):
\(\mathbf{B'}= 0.5(0.5\mathbf{B} + 0.5\mathbf{C_0}) + 0.5\mathbf{C_1} = 0.25\mathbf{B} + 0.25\mathbf{C_0} + 0.5\mathbf{C_1}\)
\(\mathbf{B'}= 0.5(0.5\mathbf{B} + 0.5\mathbf{C_1}) + 0.5\mathbf{C_0} = 0.25\mathbf{B} + 0.25\mathbf{C_1} + 0.5\mathbf{C_0}\)動畫

深度測試

關閉深度測試

在某一個階段關閉深度測試後,若某一像素位置有新的像素片元出現,那麼該像素片元就會直接不通過深度測試來到後面的混合階段。此時混合沒開的話,該像素片元就會直接取代後備緩衝區的對應像素,此時須要按從後到前的順序來繪製物體才能保證正確的顯示效果。但關閉深度測試的一個主要用途是繪製場景內的一系列透明物體。固然,前提是這一堆透明物體中間沒有不透明物體在阻擋,不然不透明物體後面的透明物也會被繪製出來。spa

開啓深度測試但關閉深度寫入

相比上面的方式,這是一種更爲合理的作法。咱們只須要在渲染的時候先繪製不透明物體,而後就能夠按任意的順序來繪製透明物體。這是由於當繪製透明物體的時候,若它前面有不透明物體阻擋,則不會經過深度測試。因此十分適合用於處理透明物體和不透明物體相互交叉的狀況。3d

利用深度測試和模板測試來繪製閃電特效與其鏡面

一個閃電動畫其實是由60張按順序播放的位圖構成:
code

而後在繪製的時候,每秒繪製60幀閃電動畫,即1秒一個循環。切換下一幀的時候,只須要更換下一張紋理便可。

// 更新閃電動畫
mBoltAnim.SetTexture(mBoltSRVs[currBoltFrame].Get());
if (frameTime > 1.0f / 60)
{
    currBoltFrame = (currBoltFrame + 1) % 60;
    frameTime -= 1.0f / 60;
}
frameTime += dt;

RenderStates類的變化

如今RenderStates類有以下可用的狀態:

class RenderStates
{
public:
    template <class T>
    using ComPtr = Microsoft::WRL::ComPtr<T>;

    static void InitAll(ID3D11Device * device);
    // 使用ComPtr無需手工釋放

public:
    static ComPtr<ID3D11RasterizerState> RSWireframe;       // 光柵化器狀態:線框模式
    static ComPtr<ID3D11RasterizerState> RSNoCull;          // 光柵化器狀態:無背面裁剪模式
    static ComPtr<ID3D11RasterizerState> RSCullClockWise;   // 光柵化器狀態:順時針裁剪模式

    static ComPtr<ID3D11SamplerState> SSLinearWrap;         // 採樣器狀態:線性過濾
    static ComPtr<ID3D11SamplerState> SSAnistropicWrap;     // 採樣器狀態:各項異性過濾

    static ComPtr<ID3D11BlendState> BSNoColorWrite;     // 混合狀態:不寫入顏色
    static ComPtr<ID3D11BlendState> BSTransparent;      // 混合狀態:透明混合
    static ComPtr<ID3D11BlendState> BSAlphaToCoverage;  // 混合狀態:Alpha-To-Coverage
    static ComPtr<ID3D11BlendState> BSAdditive;         // 混合狀態:加法混合


    static ComPtr<ID3D11DepthStencilState> DSSWriteStencil;     // 深度/模板狀態:寫入模板值
    static ComPtr<ID3D11DepthStencilState> DSSDrawWithStencil;  // 深度/模板狀態:對指定模板值的區域進行繪製
    static ComPtr<ID3D11DepthStencilState> DSSNoDoubleBlend;    // 深度/模板狀態:無二次混合區域
    static ComPtr<ID3D11DepthStencilState> DSSNoDepthTest;      // 深度/模板狀態:關閉深度測試
    static ComPtr<ID3D11DepthStencilState> DSSNoDepthWrite;     // 深度/模板狀態:僅深度測試,不寫入深度值
    static ComPtr<ID3D11DepthStencilState> DSSNoDepthTestWithStencil;   // 深度/模板狀態:關閉深度測試,對指定模板值的區域進行繪製
    static ComPtr<ID3D11DepthStencilState> DSSNoDepthWriteWithStencil;  // 深度/模板狀態:僅深度測試,不寫入深度值,對指定模板值的區域進行繪製
};

加法混合

加法混合模式的建立以下:

D3D11_BLEND_DESC blendDesc;
ZeroMemory(&blendDesc, sizeof(blendDesc));
auto& rtDesc = blendDesc.RenderTarget[0];
blendDesc.AlphaToCoverageEnable = false;
blendDesc.IndependentBlendEnable = false;
rtDesc.BlendEnable = true;

// 加法混合模式
// Color = SrcColor + DestColor
// Alpha = SrcAlpha
rtDesc.SrcBlend = D3D11_BLEND_ONE;
rtDesc.DestBlend = D3D11_BLEND_ONE;
rtDesc.BlendOp = D3D11_BLEND_OP_ADD;
rtDesc.SrcBlendAlpha = D3D11_BLEND_ONE;
rtDesc.DestBlendAlpha = D3D11_BLEND_ZERO;
rtDesc.BlendOpAlpha = D3D11_BLEND_OP_ADD;

HR(device->CreateBlendState(&blendDesc, BSAdditive.GetAddressOf()));

關閉深度測試

須要準備好默認狀況下的繪製和指定模板值繪製兩種狀況:

D3D11_DEPTH_STENCIL_DESC dsDesc;

// 關閉深度測試的深度/模板狀態
// 若繪製非透明物體,務必嚴格按照繪製順序
// 繪製透明物體則不須要擔憂繪製順序
// 而默認狀況下模板測試就是關閉的
dsDesc.DepthEnable = false;
dsDesc.StencilEnable = false;

HR(device->CreateDepthStencilState(&dsDesc, DSSNoDepthTest.GetAddressOf()));

// 關閉深度測試
// 若繪製非透明物體,務必嚴格按照繪製順序
// 繪製透明物體則不須要擔憂繪製順序
// 對知足模板值條件的區域才進行繪製
dsDesc.StencilEnable = true;
dsDesc.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK;
dsDesc.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK;

dsDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilFunc = D3D11_COMPARISON_EQUAL;
// 對於背面的幾何體咱們是不進行渲染的,因此這裏的設置可有可無
dsDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilFunc = D3D11_COMPARISON_EQUAL;

HR(device->CreateDepthStencilState(&dsDesc, DSSNoDepthTestWithStencil.GetAddressOf()));

容許深度測試,但不寫入深度值的狀態

同理也須要準備好默認繪製和指定模板值繪製兩種狀況:

// 進行深度測試,但不寫入深度值的狀態
// 若繪製非透明物體時,應使用默認狀態
// 繪製透明物體時,使用該狀態能夠有效確保混合狀態的進行
// 而且確保較前的非透明物體能夠阻擋較後的一切物體
dsDesc.DepthEnable = true;
dsDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;
dsDesc.DepthFunc = D3D11_COMPARISON_LESS;
dsDesc.StencilEnable = false;

HR(device->CreateDepthStencilState(&dsDesc, DSSNoDepthWrite.GetAddressOf()));

// 進行深度測試,但不寫入深度值的狀態
// 若繪製非透明物體時,應使用默認狀態
// 繪製透明物體時,使用該狀態能夠有效確保混合狀態的進行
// 而且確保較前的非透明物體能夠阻擋較後的一切物體
// 對知足模板值條件的區域才進行繪製
dsDesc.StencilEnable = true;
dsDesc.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK;
dsDesc.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK;

dsDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilFunc = D3D11_COMPARISON_EQUAL;
// 對於背面的幾何體咱們是不進行渲染的,因此這裏的設置可有可無
dsDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilFunc = D3D11_COMPARISON_EQUAL;

HR(device->CreateDepthStencilState(&dsDesc, DSSNoDepthWriteWithStencil.GetAddressOf()));

BasicEffect類的變化

下面四個方法都是專門用於繪製閃電動畫的,使用了加法混合。

BasicEffect::SetDrawBoltAnimNoDepthTest方法

該方法關閉了深度測試,用於繪製閃電動畫(但默認並非使用這個,你須要自行修改代碼替換調用來查看區別)

void BasicEffect::SetDrawBoltAnimNoDepthTest(ID3D11DeviceContext * deviceContext)
{
    deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get());
    deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0);
    deviceContext->RSSetState(RenderStates::RSNoCull.Get());
    deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0);
    deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    deviceContext->OMSetDepthStencilState(RenderStates::DSSNoDepthTest.Get(), 0);
    deviceContext->OMSetBlendState(RenderStates::BSAdditive.Get(), nullptr, 0xFFFFFFFF);
}

BasicEffect::SetDrawBoltAnimNoDepthWrite方法

該方法容許深度測試,但關閉深度值寫入,用於繪製閃電動畫(在程序中默認使用這種模式)

void BasicEffect::SetDrawBoltAnimNoDepthWrite(ID3D11DeviceContext * deviceContext)
{
    deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get());
    deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0);
    deviceContext->RSSetState(RenderStates::RSNoCull.Get());
    deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0);
    deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    deviceContext->OMSetDepthStencilState(RenderStates::DSSNoDepthWrite.Get(), 0);
    deviceContext->OMSetBlendState(RenderStates::BSAdditive.Get(), nullptr, 0xFFFFFFFF);
}

BasicEffect::SetDrawBoltAnimNoDepthTestWithStencil方法

該方法關閉了深度測試,用於繪製鏡面區域的閃電動畫(默認不使用這種模式)

void BasicEffect::SetDrawBoltAnimNoDepthTestWithStencil(ID3D11DeviceContext * deviceContext, UINT stencilRef)
{
    deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get());
    deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0);
    deviceContext->RSSetState(RenderStates::RSNoCull.Get());
    deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0);
    deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    deviceContext->OMSetDepthStencilState(RenderStates::DSSNoDepthTestWithStencil.Get(), stencilRef);
    deviceContext->OMSetBlendState(RenderStates::BSAdditive.Get(), nullptr, 0xFFFFFFFF);
}

BasicEffect::SetDrawBoltAnimNoDepthWriteWithStencil方法

該方法開啓深度測試,但不容許寫入深度值,用於繪製鏡面區域的閃電動畫(默認使用這種模式)

void BasicEffect::SetDrawBoltAnimNoDepthWriteWithStencil(ID3D11DeviceContext * deviceContext, UINT stencilRef)
{
    deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get());
    deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0);
    deviceContext->RSSetState(RenderStates::RSNoCull.Get());
    deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0);
    deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    deviceContext->OMSetDepthStencilState(RenderStates::DSSNoDepthWriteWithStencil.Get(), stencilRef);
    deviceContext->OMSetBlendState(RenderStates::BSAdditive.Get(), nullptr, 0xFFFFFFFF);
}

場景繪製

如今的場景繪製能夠說算是比較複雜的了,須要同時處理透明物體、非透明物體的繪製,以及繪製鏡面和陰影的效果。所以嚴格的按照正確順序去繪製在這裏就變得十分重要。

第1步: 鏡面區域寫入模板緩衝區

和以前同樣,先標記好鏡面區域:

// *********************
// 1. 給鏡面反射區域寫入值1到模板緩衝區
// 

m_BasicEffect.SetWriteStencilOnly(m_pd3dImmediateContext.Get(), 1);
m_Mirror.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);

第2步:繪製不透明的反射物體

// ***********************
// 2. 繪製不透明的反射物體
//

// 開啓反射繪製
m_BasicEffect.SetReflectionState(true); // 反射開啓
m_BasicEffect.SetRenderDefaultWithStencil(m_pd3dImmediateContext.Get(), 1);

m_Walls[2].Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
m_Walls[3].Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
m_Walls[4].Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
m_Floor.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
    
m_WoodCrate.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);

第3步:繪製不透明反射物體的陰影

// ***********************
// 3. 繪製不透明反射物體的陰影
//

m_WoodCrate.SetMaterial(m_ShadowMat);
m_BasicEffect.SetShadowState(true);         // 反射開啓,陰影開啓
m_BasicEffect.SetRenderNoDoubleBlend(m_pd3dImmediateContext.Get(), 1);

m_WoodCrate.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);

// 恢復到原來的狀態
m_BasicEffect.SetShadowState(false);
m_WoodCrate.SetMaterial(m_WoodCrateMat);

第4步:繪製須要混合的反射閃電動畫和透明物體

// ***********************
// 4. 繪製須要混合的反射閃電動畫和透明物體
//

m_BasicEffect.SetDrawBoltAnimNoDepthWriteWithStencil(m_pd3dImmediateContext.Get(), 1);
m_BoltAnim.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);

m_BasicEffect.SetReflectionState(false);        // 反射關閉

m_BasicEffect.SetRenderAlphaBlendWithStencil(m_pd3dImmediateContext.Get(), 1);
m_Mirror.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);

第5步:繪製不透明的正常物體

// ************************
// 5. 繪製不透明的正常物體
//
m_BasicEffect.SetRenderDefault(m_pd3dImmediateContext.Get());
    
for (auto& wall : m_Walls)
    wall.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
m_Floor.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
m_WoodCrate.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);

第6步:繪製不透明正常物體的陰影

// ************************
// 6. 繪製不透明正常物體的陰影
//
m_WoodCrate.SetMaterial(m_ShadowMat);
m_BasicEffect.SetShadowState(true);         // 反射關閉,陰影開啓
m_BasicEffect.SetRenderNoDoubleBlend(m_pd3dImmediateContext.Get(), 0);

m_WoodCrate.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);

m_BasicEffect.SetShadowState(false);            // 陰影關閉
m_WoodCrate.SetMaterial(m_WoodCrateMat);

第7步:繪製須要混合的閃電動畫

// ************************
// 7. 繪製須要混合的閃電動畫
m_BasicEffect.SetDrawBoltAnimNoDepthWrite(m_pd3dImmediateContext.Get());
m_BoltAnim.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);

最終動畫效果以下:

DirectX11 With Windows SDK完整目錄

Github項目源碼

歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。

相關文章
相關標籤/搜索