DirectX11 With Windows SDK--11 混合狀態

前言

這一章會着重講述混合狀態,在下一章則會講述深度/模板狀態html

DirectX11 With Windows SDK完整目錄git

Github項目源碼github

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

混合等式

對於兩個相同位置的像素點,規定\(C_{src}\)爲源像素的顏色(從像素着色器輸出的像素),\(C_{dst}\)爲目標像素的顏色(已經存在於後備緩衝區上的像素)。在Direct3D中使用下面的混合等式來將源像素色和目標像素色進行混合:函數

\[ \mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} \boxplus \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}\]佈局

其中\(\otimes\)運算符爲份量乘法,即\(\mathbf{C}_{src} \otimes \mathbf{F}_{src}\) 實際上獲得的是\((R_{src}*R_{dst}, G_{src}*G_{dst}, B_{src}*B_{dst})\)測試

\(\mathbf{F}_{src}\)\(\mathbf{F}_{dst}\)的值,以及運算符 \(\boxplus\) 的具體含義都須要在程序中進行指定。spa

對於Alpha通道的值,運算公式和上面的相似,而且兩個等式的運算是分開進行的:3d

\[ A = A_{src} * F_{src} \boxplus A_{dst} * F_{dst}\]指針

同理該運算符 \(\boxplus\) 的含義也須要另外進行設置。

混合狀態

混合運算符的設置

對於運算符 \(\boxplus\) 的含義,可使用下面的枚舉類型D3D11_BLEND_OP來描述:

枚舉值 含義
D3D11_BLEND_OP_ADD = 1 \(\mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} + \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}\)\(A = A_{src} * F_{src} + A_{dst} * F_{dst}\)
D3D11_BLEND_OP_SUBTRACT = 2 \(\mathbf{C} = \mathbf{C}_{dst} \otimes \mathbf{F}_{dst} - \mathbf{C}_{src} \otimes \mathbf{F}_{src}\)\(A = A_{dst} * F_{dst} - A_{src} * F_{src}\)
D3D11_BLEND_OP_REV_SUBTRACT = 3 \(\mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} - \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}\)\(A = A_{src} * F_{src} - A_{dst} * F_{dst}\)
D3D11_BLEND_OP_MIN = 4 \(\mathbf{C} = min(\mathbf{C}_{src}, \mathbf{C}_{dst})\)\(A = min(A_{src}, A_{dst})\)
D3D11_BLEND_OP_MAX = 5 \(\mathbf{C} = max(\mathbf{C}_{src}, \mathbf{C}_{dst})\)\(A = max(A_{src}, A_{dst})\)

再次提醒,你能夠分開指定運算顏色和Alpha通道的運算符。

混合因子的設置

對於混合公式,咱們能夠按須要設置混合因子。混合因子使用枚舉類型D3D11_BLEND類型進行描述:

枚舉值 含義
D3D11_BLEND_ZERO = 1 \(\mathbf{F}=(0,0,0)\)\(F=0\)
D3D11_BLEND_ONE = 2 \(\mathbf{F}=(1,1,1)\)\(F=1\)
D3D11_BLEND_SRC_COLOR = 3 \(\mathbf{F}=(r_{src},g_{src},b_{src})\)
D3D11_BLEND_INV_SRC_COLOR = 4 \(\mathbf{F}=(1-r_{src},1-g_{src},1-b_{src})\)
D3D11_BLEND_SRC_ALPHA = 5 \(\mathbf{F}=(a_{src},a_{src},a_{src})\)\(F=a_{src}\)
D3D11_BLEND_INV_SRC_ALPHA = 6 \(\mathbf{F}=(1-a_{src},1-a_{src},1-a_{src})\)\(F=1-a_{src}\)
D3D11_BLEND_DEST_ALPHA = 7 \(\mathbf{F}=(a_{dst},a_{dst},a_{dst})\)\(F=a_{dst}\)
D3D11_BLEND_INV_DEST_ALPHA = 8 \(\mathbf{F}=(1-a_{dst},1-a_{dst},1-a_{dst})\)\(F=1-a_{dst}\)
D3D11_BLEND_DEST_COLOR = 9 \(\mathbf{F}=(r_{dst},g_{dst},b_{dst})\)
D3D11_BLEND_INV_DEST_COLOR = 10 \(\mathbf{F}=(1-r_{dst},1-g_{dst},1-b_{dst})\)
D3D11_BLEND_SRC_ALPHA_SAT = 11 \(\mathbf{F}=(sat(a_{src}),sat(a_{src}),sat(a_{src}))\)\(F=sat(a_{src})\)
D3D11_BLEND_BLEND_FACTOR = 14 \(\mathbf{F}\) 的值來自於ID3D11DeviceContext::OMSetBlendState方法的BlendFactor參數
D3D11_BLEND_INV_BLEND_FACTOR = 15 \(\mathbf{F}\) 的值來自於ID3D11DeviceContext::OMSetBlendState方法的BlendFactor參數,並設爲1 - BlendFactor

其中sat函數將值限定在[0.0, 1.0]之間。

ID3D11Device::CreateBlendState方法--建立混合狀態

在建立混合狀態前,須要填充D3D11_BLEND_DESC結構體:

typedef struct D3D11_BLEND_DESC
{
    BOOL AlphaToCoverageEnable;    // 默認關閉,這裏
    BOOL IndependentBlendEnable;   // 是否每一個渲染目標都有獨立的混合混合描述,關閉的話都使用索引爲0的描述信息
    D3D11_RENDER_TARGET_BLEND_DESC RenderTarget[ 8 ];
}   D3D11_BLEND_DESC;

typedef struct D3D11_RENDER_TARGET_BLEND_DESC
{
    BOOL BlendEnable;             // 是否開啓混合
    D3D11_BLEND SrcBlend;         // 源顏色混合因子
    D3D11_BLEND DestBlend;        // 目標顏色混合因子
    D3D11_BLEND_OP BlendOp;       // 顏色混合運算符
    D3D11_BLEND SrcBlendAlpha;    // 源Alpha混合因子
    D3D11_BLEND DestBlendAlpha;   // 目標Alpha混合因子
    D3D11_BLEND_OP BlendOpAlpha;  // Alpha混合運算符
    UINT8 RenderTargetWriteMask;  // D3D11_COLOR_WRITE_ENABLE枚舉類型來指定能夠寫入的顏色
}   D3D11_RENDER_TARGET_BLEND_DESC;

枚舉類型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 能夠寫入ALPHA通道
D3D11_COLOR_WRITE_ENABLE_ALL = 15 能夠寫入全部顏色

若你想指定紅色和ALPHA通道能夠寫入,能夠用位運算與結合起來,即D3D11_COLOR_WRITE_ENABLE_RED | D3D11_COLOR_WRITE_ENABLE_ALPHA

ID3D11Device::CreateBlendState含義以下:

HRESULT ID3D11Device::CreateBlendState( 
    const D3D11_BLEND_DESC *pBlendStateDesc,    // [In]混合狀態描述
    ID3D11BlendState **ppBlendState);           // [Out]輸出混合狀態

ID3D11DeviceContext::OMSetBlendState方法--輸出合併階段設置混合狀態

方法以下:

void ID3D11DeviceContext::OMSetBlendState(
  ID3D11BlendState *pBlendState,      // [In]混合狀態,若是要使用默認混合狀態則提供nullptr
  const FLOAT [4]  BlendFactor,       // [In]混合因子,如不須要能夠爲nullptr
  UINT             SampleMask);       // [In]採樣掩碼,默認爲0xffffffff

默認混合狀態以下:

AlphaToCoverageEnable = false;
IndependentBlendEnable = false;
RenderTarget[0].BlendEnable = false;
RenderTarget[0].SrcBlend = D3D11_BLEND_ONE
RenderTarget[0].DestBlend = D3D11_BLEND_ZERO
RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD
RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE
RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO
RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD
RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL

採樣掩碼的設置主要是針對多重採樣的操做,若採樣掩碼的第i位爲0,則對應第i次採樣將不進行,但這得在實際上進行不小於i次的採樣時纔會起做用。一般狀況下設爲0xffffffff來容許全部採樣操做

經常使用混合等式

無顏色寫入混合

無顏色寫入混合公式以下:
\(\mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} \boxplus \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}\)
\(\mathbf{C} = \mathbf{C}_{src} \otimes (0,0,0) + \mathbf{C}_{dst} \otimes (1,1,1)\)
\(\mathbf{C} = \mathbf{C}_{dst}\)
一樣,Alpha值也應當保留
\(A = A_{dst}\)

顏色加法混合

顏色加法混合公式以下:
\(\mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} \boxplus \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}\)
\(\mathbf{C} = \mathbf{C}_{src} \otimes (1,1,1) + \mathbf{C}_{dst} \otimes (1,1,1)\)
\(\mathbf{C} = \mathbf{C}_{src} + \mathbf{C}_{dst}\)
最終的Alpha值是多少並不影響前面的運算,所以能夠設爲任意值,這裏設爲源像素Alpha值:
\(A = A_{src}\)

透明混合

透明混合公式以下:
\(\mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} \boxplus \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}\)
\(\mathbf{C} = \mathbf{C}_{src} \otimes (A_{src},A_{src},A_{src}) + \mathbf{C}_{dst} \otimes ((1-A_{src}),(1-A_{src}),(1-A_{src}))\)
\(\mathbf{C} = A_{src}\mathbf{C}_{src} + (1-A_{src})\mathbf{C}_{dst}\)
最終的Alpha值是多少並不影響前面的運算,所以能夠設爲任意值,這裏設爲源像素Alpha值:
\(A = A_{src}\)

但須要注意的是,透明混合的繪製順序是十分重要的。首先必須按照攝像機到物體的距離,對物體進行排序,而後按照從後到前的順序進行混合。由於若是一個對象是透明的,咱們就能夠經過它看到背後的場景。若是先繪製較前的透明物體,那麼深度緩衝區的值會被刷新,而後較後的透明物體會由於深度測試不經過而不被繪製:

能夠看到,上圖是先繪製水面而後繪製籬笆盒,這樣會致使籬笆盒的下半部分由於深度比水面大而致使不經過深度測試,從而沒有被繪製出來。因此在繪製透明物體前,要麼關閉深度測試,要麼對物體到攝像機的前後順序進行排序,並按從後到前的順序進行繪製。

HLSL代碼的變化

首先在常量緩衝區上,須要將材質移到每物體繪製的常量緩衝區內,由於如今從如今的例子開始,不一樣的物體在材質上是不一樣的,須要頻繁更新:

cbuffer CBChangesEveryDrawing : register(b0)
{
    matrix g_World;
    matrix g_WorldInvTranspose;
    Material g_Material;
}

cbuffer CBChangesEveryFrame : register(b1)
{
    matrix g_View;
    float3 g_EyePosW;
}

cbuffer CBChangesOnResize : register(b2)
{
    matrix g_Proj;
}

cbuffer CBChangesRarely : register(b3)
{
    DirectionalLight g_DirLight[10];
    PointLight g_PointLight[10];
    SpotLight g_SpotLight[10];
    int g_NumDirLight;
    int g_NumPointLight;
    int g_NumSpotLight;
    float g_Pad;
}

而後在像素着色器上,能夠對alpha值太低的像素進行裁剪,經過調用clip函數,若參數的值小於0,則該像素會被裁剪掉,從而避免後續的光照運算。在下面的例子中,alpha值低於0.1的像素都會被裁剪掉。

// Basic_PS_3D.hlsl
#include "Basic.hlsli"

// 像素着色器(3D)
float4 PS_3D(VertexPosHWNormalTex pIn) : SV_Target
{
    // 提早進行裁剪,對不符合要求的像素能夠避免後續運算
    float4 texColor = g_Tex.Sample(g_SamLinear, pIn.Tex);
    clip(texColor.a - 0.1f);

   // ...
    
    // 計算   
    float4 litColor = texColor * (ambient + diffuse) + spec;
    litColor.a = texColor.a * g_Material.Diffuse.a;
    return litColor;
}
// Basic_PS_2D.hlsl
#include "Basic.hlsli"

// 像素着色器(2D)
float4 PS_2D(VertexPosHTex pIn) : SV_Target
{
    float4 color = g_Tex.Sample(g_SamLinear, pIn.Tex);
    clip(color.a - 0.1f);
    return color;
}

C++代碼的變化

RenderStates類

RenderStates類能夠一次性建立出全部可能須要用到的狀態對象,而後在須要的時候能夠獲取它的靜態成員,而且由於使用了ComPtr智能指針,無需管理內存:

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<ID3D11SamplerState> SSLinearWrap;         // 採樣器狀態:線性過濾
    static ComPtr<ID3D11SamplerState> SSAnistropicWrap;     // 採樣器狀態:各項異性過濾

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

而具體實現以下:

using namespace Microsoft::WRL;

ComPtr<ID3D11RasterizerState> RenderStates::RSNoCull        = nullptr;
ComPtr<ID3D11RasterizerState> RenderStates::RSWireframe     = nullptr;

ComPtr<ID3D11SamplerState> RenderStates::SSAnistropicWrap   = nullptr;
ComPtr<ID3D11SamplerState> RenderStates::SSLinearWrap       = nullptr;

ComPtr<ID3D11BlendState> RenderStates::BSAlphaToCoverage    = nullptr;
ComPtr<ID3D11BlendState> RenderStates::BSNoColorWrite       = nullptr;
ComPtr<ID3D11BlendState> RenderStates::BSTransparent        = nullptr;

void RenderStates::InitAll(ID3D11Device * device)
{
    // 先前初始化過的話就不必重來了
    if (IsInit())
        return;

    // ***********初始化光柵化器狀態***********
    D3D11_RASTERIZER_DESC rasterizerDesc;
    ZeroMemory(&rasterizerDesc, sizeof(rasterizerDesc));

    // 線框模式
    rasterizerDesc.FillMode = D3D11_FILL_WIREFRAME;
    rasterizerDesc.CullMode = D3D11_CULL_NONE;
    rasterizerDesc.FrontCounterClockwise = false;
    rasterizerDesc.DepthClipEnable = true;
    HR(device->CreateRasterizerState(&rasterizerDesc, RSWireframe.GetAddressOf()));

    // 無背面剔除模式
    rasterizerDesc.FillMode = D3D11_FILL_SOLID;
    rasterizerDesc.CullMode = D3D11_CULL_NONE;
    rasterizerDesc.FrontCounterClockwise = false;
    rasterizerDesc.DepthClipEnable = true;
    HR(device->CreateRasterizerState(&rasterizerDesc, RSNoCull.GetAddressOf()));

    
    // ***********初始化採樣器狀態***********
    D3D11_SAMPLER_DESC sampDesc;
    ZeroMemory(&sampDesc, sizeof(sampDesc));

    // 線性過濾模式
    sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    sampDesc.MinLOD = 0;
    sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
    HR(device->CreateSamplerState(&sampDesc, SSLinearWrap.GetAddressOf()));

    // 各向異性過濾模式
    sampDesc.Filter = D3D11_FILTER_ANISOTROPIC;
    sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    sampDesc.MaxAnisotropy = 4;
    sampDesc.MinLOD = 0;
    sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
    HR(device->CreateSamplerState(&sampDesc, SSAnistropicWrap.GetAddressOf()));
    
    // ***********初始化混合狀態***********
    D3D11_BLEND_DESC blendDesc;
    ZeroMemory(&blendDesc, sizeof(blendDesc));
    auto& rtDesc = blendDesc.RenderTarget[0];
    // Alpha-To-Coverage模式
    blendDesc.AlphaToCoverageEnable = true;
    blendDesc.IndependentBlendEnable = false;
    rtDesc.BlendEnable = false;
    rtDesc.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
    HR(device->CreateBlendState(&blendDesc, BSAlphaToCoverage.GetAddressOf()));

    // 透明混合模式
    // Color = SrcAlpha * SrcColor + (1 - SrcAlpha) * DestColor 
    // Alpha = SrcAlpha
    blendDesc.AlphaToCoverageEnable = false;
    blendDesc.IndependentBlendEnable = false;
    rtDesc.BlendEnable = true;
    rtDesc.SrcBlend = D3D11_BLEND_SRC_ALPHA;
    rtDesc.DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
    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, BSTransparent.GetAddressOf()));
    
    // 無顏色寫入混合模式
    // Color = DestColor
    // Alpha = DestAlpha
    rtDesc.SrcBlend = D3D11_BLEND_ZERO;
    rtDesc.DestBlend = D3D11_BLEND_ONE;
    rtDesc.BlendOp = D3D11_BLEND_OP_ADD;
    rtDesc.SrcBlendAlpha = D3D11_BLEND_ZERO;
    rtDesc.DestBlendAlpha = D3D11_BLEND_ONE;
    rtDesc.BlendOpAlpha = D3D11_BLEND_OP_ADD;
    HR(device->CreateBlendState(&blendDesc, BSNoColorWrite.GetAddressOf()));
    
}

GameApp類的變化

首先內含的GameObject類須要添加Material類的存儲,並提供GameObject::SetMaterial方法用於設置材質。這裏不詳細描述。

GameApp::InitResource方法的變化

該方法有以下變化:

  1. 初始化了籬笆盒、牆體、地板和靜止水面物體
  2. 將攝像機設置爲僅第三人稱
  3. 設置了光柵化狀態爲無背面裁剪模式(由於透明狀況下能夠看到物體的背面)
  4. 設置了混合狀態爲透明混合模式
bool GameApp::InitResource()
{
    
    // ******************
    // 設置常量緩衝區描述
    //
    D3D11_BUFFER_DESC cbd;
    ZeroMemory(&cbd, sizeof(cbd));
    cbd.Usage = D3D11_USAGE_DYNAMIC;
    cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    // 新建用於VS和PS的常量緩衝區
    cbd.ByteWidth = sizeof(CBChangesEveryDrawing);
    HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[0].GetAddressOf()));
    cbd.ByteWidth = sizeof(CBChangesEveryFrame);
    HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[1].GetAddressOf()));
    cbd.ByteWidth = sizeof(CBChangesOnResize);
    HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[2].GetAddressOf()));
    cbd.ByteWidth = sizeof(CBChangesRarely);
    HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[3].GetAddressOf()));
    // ******************
    // 初始化遊戲對象
    //
    ComPtr<ID3D11ShaderResourceView> texture;
    Material material{};
    material.ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
    material.diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
    material.specular = XMFLOAT4(0.2f, 0.2f, 0.2f, 16.0f);
    // 初始化籬笆盒
    HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\WireFence.dds", nullptr, texture.GetAddressOf()));
    m_WireFence.SetBuffer(m_pd3dDevice.Get(), Geometry::CreateBox());
    m_WireFence.SetTexture(texture.Get());
    m_WireFence.SetMaterial(material);
    
    // 初始化地板
    HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\floor.dds", nullptr, texture.ReleaseAndGetAddressOf()));
    m_Floor.SetBuffer(m_pd3dDevice.Get(),
        Geometry::CreatePlane(XMFLOAT3(0.0f, -1.0f, 0.0f), XMFLOAT2(20.0f, 20.0f), XMFLOAT2(5.0f, 5.0f)));
    m_Floor.SetTexture(texture.Get());
    m_Floor.SetMaterial(material);

    // 初始化牆體
    m_Walls.resize(4);
    HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\brick.dds", nullptr, texture.ReleaseAndGetAddressOf()));
    // 這裏控制牆體四個面的生成
    for (int i = 0; i < 4; ++i)
    {
        m_Walls[i].SetBuffer(m_pd3dDevice.Get(),
            Geometry::CreatePlane(XMFLOAT3(), XMFLOAT2(20.0f, 8.0f), XMFLOAT2(5.0f, 1.5f)));
        XMMATRIX world = XMMatrixRotationX(-XM_PIDIV2) * XMMatrixRotationY(XM_PIDIV2 * i)
            * XMMatrixTranslation(i % 2 ? -10.0f * (i - 2) : 0.0f, 3.0f, i % 2 == 0 ? -10.0f * (i - 1) : 0.0f);
        m_Walls[i].SetMaterial(material);
        m_Walls[i].SetWorldMatrix(world);
        m_Walls[i].SetTexture(texture.Get());
    }
        
    // 初始化水
    material.ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
    material.diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 0.5f);
    material.specular = XMFLOAT4(0.8f, 0.8f, 0.8f, 32.0f);
    HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\water.dds", nullptr, texture.ReleaseAndGetAddressOf()));
    m_Water.SetBuffer(m_pd3dDevice.Get(),
        Geometry::CreatePlane(XMFLOAT3(), XMFLOAT2(20.0f, 20.0f), XMFLOAT2(10.0f, 10.0f)));
    m_Water.SetTexture(texture.Get());
    m_Water.SetMaterial(material);
    
    // ******************
    // 初始化常量緩衝區的值
    //

    // 初始化每幀可能會變化的值
    auto camera = std::shared_ptr<ThirdPersonCamera>(new ThirdPersonCamera);
    m_pCamera = camera;
    camera->SetViewPort(0.0f, 0.0f, (float)m_ClientWidth, (float)m_ClientHeight);
    camera->SetTarget(XMFLOAT3(0.0f, 0.5f, 0.0f));
    camera->SetDistance(5.0f);
    camera->SetDistanceMinMax(2.0f, 14.0f);

    // 初始化僅在窗口大小變更時修改的值
    m_pCamera->SetFrustum(XM_PI / 3, AspectRatio(), 0.5f, 1000.0f);
    m_CBOnResize.proj = XMMatrixTranspose(m_pCamera->GetProjXM());

    // ******************
    // 初始化不會變化的值
    //

    // 環境光
    m_CBRarely.dirLight[0].ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
    m_CBRarely.dirLight[0].diffuse = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f);
    m_CBRarely.dirLight[0].specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
    m_CBRarely.dirLight[0].direction = XMFLOAT3(0.0f, -1.0f, 0.0f);
    // 燈光
    m_CBRarely.pointLight[0].position = XMFLOAT3(0.0f, 15.0f, 0.0f);
    m_CBRarely.pointLight[0].ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
    m_CBRarely.pointLight[0].diffuse = XMFLOAT4(0.6f, 0.6f, 0.6f, 1.0f);
    m_CBRarely.pointLight[0].specular = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
    m_CBRarely.pointLight[0].att = XMFLOAT3(0.0f, 0.1f, 0.0f);
    m_CBRarely.pointLight[0].range = 25.0f;
    m_CBRarely.numDirLight = 1;
    m_CBRarely.numPointLight = 1;
    m_CBRarely.numSpotLight = 0;


    // 更新不容易被修改的常量緩衝區資源
    D3D11_MAPPED_SUBRESOURCE mappedData;
    HR(m_pd3dImmediateContext->Map(m_pConstantBuffers[2].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
    memcpy_s(mappedData.pData, sizeof(CBChangesOnResize), &m_CBOnResize, sizeof(CBChangesOnResize));
    m_pd3dImmediateContext->Unmap(m_pConstantBuffers[2].Get(), 0);

    HR(m_pd3dImmediateContext->Map(m_pConstantBuffers[3].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
    memcpy_s(mappedData.pData, sizeof(CBChangesRarely), &m_CBRarely, sizeof(CBChangesRarely));
    m_pd3dImmediateContext->Unmap(m_pConstantBuffers[3].Get(), 0);

    // 初始化全部渲染狀態
    RenderStates::InitAll(m_pd3dDevice.Get());
    
    
    // ******************
    // 給渲染管線各個階段綁定好所需資源
    //

    // 設置圖元類型,設定輸入佈局
    m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout3D.Get());
    // 預先綁定各自所需的緩衝區,其中每幀更新的緩衝區須要綁定到兩個緩衝區上
    m_pd3dImmediateContext->VSSetConstantBuffers(0, 1, m_pConstantBuffers[0].GetAddressOf());
    m_pd3dImmediateContext->VSSetConstantBuffers(1, 1, m_pConstantBuffers[1].GetAddressOf());
    m_pd3dImmediateContext->VSSetConstantBuffers(2, 1, m_pConstantBuffers[2].GetAddressOf());
    // 默認綁定3D着色器
    m_pd3dImmediateContext->VSSetShader(m_pVertexShader3D.Get(), nullptr, 0);

    m_pd3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());

    m_pd3dImmediateContext->PSSetConstantBuffers(0, 1, m_pConstantBuffers[0].GetAddressOf());
    m_pd3dImmediateContext->PSSetConstantBuffers(1, 1, m_pConstantBuffers[1].GetAddressOf());
    m_pd3dImmediateContext->PSSetConstantBuffers(3, 1, m_pConstantBuffers[3].GetAddressOf());
    m_pd3dImmediateContext->PSSetShader(m_pPixelShader3D.Get(), nullptr, 0);
    m_pd3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());

    m_pd3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF);

    return true;
}

GameApp::UpdateScene方法的變化

如今攝像機只有第三人稱:

void GameApp::UpdateScene(float dt)
{

    // 更新鼠標事件,獲取相對偏移量
    Mouse::State mouseState = m_pMouse->GetState();
    Mouse::State lastMouseState = m_MouseTracker.GetLastState();
    m_MouseTracker.Update(mouseState);

    Keyboard::State keyState = m_pKeyboard->GetState();
    m_KeyboardTracker.Update(keyState);

    // 獲取子類
    auto cam3rd = std::dynamic_pointer_cast<ThirdPersonCamera>(m_pCamera);

    // ******************
    // 第三人稱攝像機的操做
    //

    // 繞原點旋轉
    cam3rd->RotateX(mouseState.y * dt * 1.25f);
    cam3rd->RotateY(mouseState.x * dt * 1.25f);
    cam3rd->Approach(-mouseState.scrollWheelValue / 120 * 1.0f);

    // 更新觀察矩陣,並更新每幀緩衝區
    m_pCamera->UpdateViewMatrix();
    m_CBFrame.eyePos = m_pCamera->GetPositionXM();
    m_CBFrame.view = XMMatrixTranspose(m_pCamera->GetViewXM());
    

    // 重置滾輪值
    m_pMouse->ResetScrollWheelValue();
    
    
    // 退出程序,這裏應向窗口發送銷燬信息
    if (m_KeyboardTracker.IsKeyPressed(Keyboard::Escape))
        SendMessage(MainWnd(), WM_DESTROY, 0, 0);
    
    D3D11_MAPPED_SUBRESOURCE mappedData;
    HR(m_pd3dImmediateContext->Map(m_pConstantBuffers[1].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
    memcpy_s(mappedData.pData, sizeof(CBChangesEveryFrame), &m_CBFrame, sizeof(CBChangesEveryFrame));
    m_pd3dImmediateContext->Unmap(m_pConstantBuffers[1].Get(), 0);
}

GameApp::DrawScene方法的變化

對於3D物體的,要先繪製不透明的物體,而後再繪製透明的物體。而對於透明的物體,這裏必定要先繪製靠後的物體,而後纔是靠前的物體。而對於不透明的物體,不管視角怎麼變化,物體的前後順序都是不會改變的,因此不會出現有物體的一部分沒法繪製的狀況:

void GameApp::DrawScene()
{
    assert(m_pd3dImmediateContext);
    assert(m_pSwapChain);

    m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), reinterpret_cast<const float*>(&Colors::Black));
    m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

    // ********************
    // 1. 繪製不透明對象
    //
    m_pd3dImmediateContext->RSSetState(nullptr);
    m_pd3dImmediateContext->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF);

    for (auto& wall : m_Walls)
        wall.Draw(m_pd3dImmediateContext.Get());
    m_Floor.Draw(m_pd3dImmediateContext.Get());

    // ********************
    // 2. 繪製透明對象
    //
    m_pd3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    m_pd3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF);

    // 籬笆盒稍微擡起一點高度
    m_WireFence.SetWorldMatrix(XMMatrixTranslation(2.0f, 0.01f, 0.0f));
    m_WireFence.Draw(m_pd3dImmediateContext.Get());
    m_WireFence.SetWorldMatrix(XMMatrixTranslation(-2.0f, 0.01f, 0.0f));
    m_WireFence.Draw(m_pd3dImmediateContext.Get());
    // 繪製了籬笆盒後再繪製水面
    m_Water.Draw(m_pd3dImmediateContext.Get());
    


    // ********************
    // 繪製Direct2D部分
    //
    
    // ...

    HR(m_pSwapChain->Present(0, 0));
}

最終效果以下:

DirectX11 With Windows SDK完整目錄

Github項目源碼

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

相關文章
相關標籤/搜索