Windows 8 Directx 開發學習筆記(十三)利用模板實現木箱鏡像

假設牆上有一面鏡子,鏡子前面有個木箱。如果觀察角度合適,整個木箱鏡像都會在鏡子裏,計算起來還比較簡單;而變換個角度,木箱的鏡像可能只有一部分在鏡子裏,這時單純依靠計算來實現就很麻煩。DirectX提供了模板技術以方便地完成這個任務。我印象中用到模板就是噴漆的時候。將設計好的圖案在一塊板上刻出來,然後把這塊板扣在要噴塗的地方,不管三七二十一直接噴漆,最後把板拿下來,圖案就噴好了。DirectX中的模板也一樣,啓用模板後,後續的繪製就被限制在模板鏤空(即模板測試通過)的地方。不過DirectX中的模板更加靈活複雜。

使用模板時只有兩個結果,通過和不通過。測試方法如下:

StencilRef & StencilReadMask⊴ Value & StencilReadMask

其中StencilRef是模板參考值,StencilReadMask是模板掩碼,Value是當前像素點的模板值,&是按位與運算,而⊴代表比較函數。與混合一樣,模板也是通過組合StencilReadMask和⊴的不同枚舉值來實現各種效果。在DirectX中使用模板的細節內容見DirectX 10 遊戲編程入門和MSDN,這裏只關注鏡像效果的實現。

鏡像效果可以仿照噴漆來做,給牆壁蓋上一塊只能露出鏡子的模板,畫完反射模型之後再把模板拿掉。大致可以分爲四步:

1、創建牆壁、地板、木箱

2、創建鏡子模版

3、繪製木箱和地板的反射

4、混合鏡子,木箱和地板的紋理

基於第十篇的紋理木箱來實現鏡像效果。其中HLSL代碼只修改頂點着色器即可,方便紋理座標變換,即調整ConstantBuffer的結構。

cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
    matrix model;
    matrix view;
    matrix projection;
    matrix texTransform;
};

並在main方法中進行紋理座標變換。

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

然後可以直接進入C++代碼部分。首先依然是修改CubeRenderer.h中的ModelViewProjectionConstantBuffer定義,使其與頂點着色器中的一致。接着添加新的私有成員和方法。

    Microsoft::WRL::ComPtr<ID3D11BlendState>m_alphaBlendState;
    Microsoft::WRL::ComPtr<ID3D11DepthStencilState>m_markDepthStencilState;
    Microsoft::WRL::ComPtr<ID3D11DepthStencilState>m_reflectDepthStencilState;
    Microsoft::WRL::ComPtr<ID3D11RasterizerState>m_rasterizerState;
    ModelViewProjectionConstantBufferm_constantBufferData;
    ModelViewProjectionConstantBufferm_reflectConstantBufferData;
 
    // 立方體模型
    void CreateCubeModel();
    void RenderCubeModel(ModelViewProjectionConstantBuffer*constantBufferData);
    Microsoft::WRL::ComPtr<ID3D11Buffer>m_vertexCubeBuffer;
    Microsoft::WRL::ComPtr<ID3D11Buffer>m_indexCubeBuffer;
    Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_woodSRV;
    uint32 m_indexCubeCount;
   
    // 牆體模型
    void CreateWallModel();
    void RenderWallModel();
    Microsoft::WRL::ComPtr<ID3D11Buffer>m_vertexWallBuffer;
    Microsoft::WRL::ComPtr<ID3D11Buffer>m_indexWallBuffer;
    Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_wallSRV;
    uint32 m_indexWallCount;
 
    // 地板模型
    void CreateFloorModel();
    void RenderFloorModel(ModelViewProjectionConstantBuffer*constantBufferData);
    Microsoft::WRL::ComPtr<ID3D11Buffer>m_vertexFloorBuffer;
    Microsoft::WRL::ComPtr<ID3D11Buffer>m_indexFloorBuffer;
    Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_floorSRV;
    uint32 m_indexFloorCount;
 
    // 鏡子模型
    voidCreateMirrorModel();
    voidRenderMirrorModel();
    Microsoft::WRL::ComPtr<ID3D11Buffer>m_vertexMirrorBuffer;
    Microsoft::WRL::ComPtr<ID3D11Buffer>m_indexMirrorBuffer;
    Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_mirrorSRV;
    uint32 m_indexMirrorCount;

前面幾個成員的功能在後面用到時詳細介紹。而後面四個模型的成員和方法只是名字不同,功能一致,寫的很囉嗦,有待修改。定義好上面的成員和方法就轉到實現部分。首先在CreateDeviceResources方法中載入四種紋理並初始化各個狀態

        DX::ThrowIfFailed(
           CreateWICTextureFromFile(
           m_d3dDevice.Get(),
           m_d3dContext.Get(),
           L"wood.jpg",
           NULL,
           m_woodSRV.GetAddressOf()
           )
           );
 
       ……
 
       D3D11_SAMPLER_DESC samplerDesc;
       samplerDesc.Filter= D3D11_FILTER_MIN_MAG_MIP_LINEAR;
       samplerDesc.AddressU= D3D11_TEXTURE_ADDRESS_WRAP;
       samplerDesc.AddressV= D3D11_TEXTURE_ADDRESS_WRAP;
       samplerDesc.AddressW= D3D11_TEXTURE_ADDRESS_WRAP;
       samplerDesc.MipLODBias= 0;
       samplerDesc.MaxAnisotropy= 1;
       samplerDesc.ComparisonFunc= D3D11_COMPARISON_NEVER;
       samplerDesc.BorderColor[0 ] = 1.0f;
       samplerDesc.BorderColor[1 ] = 1.0f;
       samplerDesc.BorderColor[2 ] = 1.0f;
       samplerDesc.BorderColor[3 ] = 1.0f;
       samplerDesc.MinLOD= -3.402823466e+38F; // -FLT_MAX
       samplerDesc.MaxLOD= 3.402823466e+38F; // FLT_MAX
 
       DX::ThrowIfFailed(
           m_d3dDevice->CreateSamplerState(
           &samplerDesc,
           m_samplerState.GetAddressOf()
           )
           );

       // 初始化Alpha混合狀態
       D3D11_BLEND_DESC alphaBlendDesc ={0};
       alphaBlendDesc.RenderTarget[0].BlendEnable= TRUE;
       alphaBlendDesc.RenderTarget[0].SrcBlend= D3D11_BLEND_SRC_ALPHA;    // Color_Fsrc
       alphaBlendDesc.RenderTarget[0].DestBlend= D3D11_BLEND_INV_SRC_ALPHA; // Color_Fdst
       alphaBlendDesc.RenderTarget[0].BlendOp= D3D11_BLEND_OP_ADD; // Color_Operation
       alphaBlendDesc.RenderTarget[0].SrcBlendAlpha= D3D11_BLEND_ONE; // Alpha_Fsrc
       alphaBlendDesc.RenderTarget[0].DestBlendAlpha= D3D11_BLEND_ZERO; // Alpha_Fdst
       alphaBlendDesc.RenderTarget[0].BlendOpAlpha= D3D11_BLEND_OP_ADD; // Alpha_Operation
       alphaBlendDesc.RenderTarget[0].RenderTargetWriteMask= D3D11_COLOR_WRITE_ENABLE_ALL;
 
       DX::ThrowIfFailed(
           m_d3dDevice->CreateBlendState(
           &alphaBlendDesc,
           &m_alphaBlendState
           )
           );
 
       // 初始化鏡子模版
       D3D11_DEPTH_STENCIL_DESC mirrorDesc;
       mirrorDesc.DepthEnable      = true;
       mirrorDesc.DepthWriteMask   = D3D11_DEPTH_WRITE_MASK_ALL;
       mirrorDesc.DepthFunc        = D3D11_COMPARISON_LESS;
       mirrorDesc.StencilEnable    = true;
       mirrorDesc.StencilReadMask  = 0xff;
       mirrorDesc.StencilWriteMask= 0xff;
 
       mirrorDesc.FrontFace.StencilFailOp      = D3D11_STENCIL_OP_KEEP;
       mirrorDesc.FrontFace.StencilDepthFailOp= D3D11_STENCIL_OP_KEEP;
       mirrorDesc.FrontFace.StencilPassOp      = D3D11_STENCIL_OP_REPLACE;
       mirrorDesc.FrontFace.StencilFunc        = D3D11_COMPARISON_ALWAYS;
 
       // 背面參數設置無影響
       mirrorDesc.BackFace.StencilFailOp       = D3D11_STENCIL_OP_KEEP;
       mirrorDesc.BackFace.StencilDepthFailOp  = D3D11_STENCIL_OP_KEEP;
       mirrorDesc.BackFace.StencilPassOp       = D3D11_STENCIL_OP_REPLACE;
       mirrorDesc.BackFace.StencilFunc         = D3D11_COMPARISON_ALWAYS;
 
       DX::ThrowIfFailed(
           m_d3dDevice->CreateDepthStencilState(
           &mirrorDesc,
           &m_markDepthStencilState
           )
           );
 
       // 初始化繪製反射時的模板
       D3D11_DEPTH_STENCIL_DESC drawReflectionDesc;
       drawReflectionDesc.DepthEnable      = true;
       drawReflectionDesc.DepthWriteMask   = D3D11_DEPTH_WRITE_MASK_ZERO;
       drawReflectionDesc.DepthFunc        = D3D11_COMPARISON_ALWAYS;
       drawReflectionDesc.StencilEnable    = true;
       drawReflectionDesc.StencilReadMask  = 0xff;
       drawReflectionDesc.StencilWriteMask= 0xff;
 
       drawReflectionDesc.FrontFace.StencilFailOp      = D3D11_STENCIL_OP_KEEP;
       drawReflectionDesc.FrontFace.StencilDepthFailOp= D3D11_STENCIL_OP_KEEP;
       drawReflectionDesc.FrontFace.StencilPassOp= D3D11_STENCIL_OP_KEEP;
       drawReflectionDesc.FrontFace.StencilFunc   = D3D11_COMPARISON_EQUAL;
 
       // 背面參數設置無影響
       drawReflectionDesc.BackFace.StencilFailOp      = D3D11_STENCIL_OP_KEEP;
       drawReflectionDesc.BackFace.StencilDepthFailOp= D3D11_STENCIL_OP_KEEP;
       drawReflectionDesc.BackFace.StencilPassOp= D3D11_STENCIL_OP_KEEP;
       drawReflectionDesc.BackFace.StencilFunc   = D3D11_COMPARISON_EQUAL;
 
       DX::ThrowIfFailed(
           m_d3dDevice->CreateDepthStencilState(
           &drawReflectionDesc,
           &m_reflectDepthStencilState
           )
           );

samplerDesc和alphaBlendDesc在以前的文章中已經介紹過,主要看下mirrorDesc和drawReflectionDesc。結合模板公式和MSDN的介紹,對這兩個結構能有基本的瞭解。

http://msdn.microsoft.com/zh-cn/library/windows/desktop/ff476101.aspx

http://msdn.microsoft.com/zh-cn/library/windows/desktop/ff476219.aspx

D3D11_COMPARISON_LESS

If the source data is lessthan the destination data, the comparison passes.

D3D11_COMPARISON_EQUAL

If the source data is equalto the destination data, the comparison passes.

D3D11_COMPARISON_ALWAYS

Always pass the comparison.

D3D11_STENCIL_OP_KEEP

Keep the existing stencil data.

D3D11_STENCIL_OP_ZERO

Set the stencil data to 0.

mirrorDesc就是一直通過模板測試,因爲繪製鏡子模板之前還沒有任何模板,測試也無意義。但是深度測試是必須的,鏡子在牆前面,通過深度測試時就會將已有模板值替換爲當前的模板值,實現鏡子形狀的模板繪製。

drawReflectionDesc則是一直通過深度測試,只進行模板測試。因爲要繪製的木箱和地板實際是在鏡子後面,所以深度測試無意義。模板測試的通過條件是兩個模板值相等。因爲鏡子覆蓋區域的模板值已被修改,其他區域都是默認。所以這次測試時,只有鏡子區域的模板值能達成相等的條件,對應位置像素被更新,從而實現限制繪製區域的目的。

在這後面還要調用四個方法來初始化模型,即初始化模型的頂點緩衝區和索引緩衝區。代碼很簡單,大同小異,不進行介紹,細節可以參照源代碼。接着看下在Render方法中的詳細繪製流程。

    //------------------------------------------------
    // 第一步  繪製正常模型,牆壁,地板和原始木箱
    //------------------------------------------------
    RenderWallModel();
    RenderFloorModel(&m_constantBufferData);
    RenderCubeModel(&m_constantBufferData);
 
    //------------------------------------------------
    // 第二步  繪製鏡子模版
    //------------------------------------------------
 
    // 設置模板狀態
    m_d3dContext->OMSetDepthStencilState(m_markDepthStencilState.Get(),1);
 
    RenderMirrorModel();
 
    // 清除設置
    m_d3dContext->OMSetDepthStencilState(0,0);
 
    //------------------------------------------------
    // 第三步  繪製反射木箱和地板
    //------------------------------------------------
 
    // 設置光柵化狀態和模板狀態
    m_d3dContext->RSSetState(m_rasterizerState.Get());
    m_d3dContext->OMSetDepthStencilState(m_reflectDepthStencilState.Get(),1);
 
    RenderFloorModel(&m_reflectConstantBufferData);
    RenderCubeModel(&m_reflectConstantBufferData);
   
    // 清除設置
    m_d3dContext->RSSetState(0);
    m_d3dContext->OMSetDepthStencilState(0,0);
 
    //------------------------------------------------
    // 第四步  繪製鏡子
    //------------------------------------------------
    m_d3dContext->OMSetBlendState(m_alphaBlendState.Get(),blendFactors, 0xffffffff);
   
    RenderMirrorModel();
   
    m_d3dContext->OMSetBlendState(0,blendFactors, 0xffffffff);

以上代碼與開始時介紹的流程一致。注意在繪製鏡像時還更改了光柵化狀態。因爲繪製鏡像時,模型索引的環繞順序和平面法線都不翻轉,造成鏡像的法線方向錯誤,所以需要更改光柵化狀態。代碼中的RenderXXXModel方法也很簡單,就是把Render方法中負責渲染的代碼抽出來,僅以繪製正方體的方法爲例:

void CubeRenderer::RenderCubeModel(ModelViewProjectionConstantBuffer* constantBufferData)
{
    UINT stride = sizeof(VertexPositionColor);
    UINT offset = 0;
 
    XMMATRIX cubeScale =XMMatrixScaling(1.0f, 1.0f, 0.0f);
    XMStoreFloat4x4(&constantBufferData->transform,XMMatrixTranspose(cubeScale));
 
    m_d3dContext->UpdateSubresource(
       m_constantBuffer.Get(),
       0,
       NULL,
       constantBufferData,
       0,
       0
       );
 
    m_d3dContext->IASetVertexBuffers(
       0,
       1,
       m_vertexCubeBuffer.GetAddressOf(),
       &stride,
       &offset
       );
 
    m_d3dContext->IASetIndexBuffer(
       m_indexCubeBuffer.Get(),
       DXGI_FORMAT_R16_UINT,
       0
       );
 
    // 設置材質
    m_d3dContext->PSSetShaderResources(
       0,
       1,
       m_woodSRV.GetAddressOf()
       );
 
    // 設置紋理採樣
    m_d3dContext->PSSetSamplers(
       0,
       1,
       m_samplerState.GetAddressOf()
       );
 
    m_d3dContext->DrawIndexed(
       m_indexCubeCount,
       0,
       0
       );
}

還要注意m_reflectConstantBufferData。它代表鏡子裏的空間,是現實空間的鏡像,在Update方法中更新。本例中鏡子是在XY平面上,所以用XMMatrixReflect方法計算XY平面的對稱變換矩陣,並增加平移變換,使模型能夠轉移到鏡子內的對應位置。

    // 計算反射空間矩陣
    XMVECTOR mirrorPlane =XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); // xy 平面
    XMMATRIX T =XMMatrixTranslation(0.0f, 0.0f, -4.0f);   
    XMMATRIX R =XMMatrixReflect(mirrorPlane) * T;
    XMStoreFloat4x4(&m_reflectConstantBufferData.projection,XMLoadFloat4x4(&m_constantBufferData.projection) );
    XMStoreFloat4x4(&m_reflectConstantBufferData.view,XMLoadFloat4x4(&m_constantBufferData.view));
    XMStoreFloat4x4(&m_reflectConstantBufferData.model,XMMatrixTranspose(XMMatrixRotationY(0) * R));

程序運行效果如下圖:


本篇文章雖然實現了鏡像效果,但是代碼中有很多重複的地方,像渲染模型的功能就可以只用一個方法,通過輸入參數進行不同模型的渲染,時間緊沒有整理,會在以後更新。

本篇文章的源代碼:Direct3DApp_CubeMirror

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

-------------------------------------------------------------------------------------------------------------------

修改後的源代碼:http://download.csdn.net/detail/raymondcode/4975386

 

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