DirectX11 With Windows SDK--11 混合狀態


前言

雖然這一部分的內容主要偏向於混合(Blending),但這里還需提及一下,關於渲染管線可以綁定的狀態主要有如下四種:

  1. 光柵化狀態(光柵化階段)
  2. 采樣器狀態(像素着色階段)
  3. 混合狀態(輸出合並階段)
  4. 深度/模板狀態(輸出合並階段)

Direct3D是基於狀態機的,我們可以通過修改這些狀態來修改渲染管線的當前行為。

實際上這一章會講述光柵化狀態和混合狀態這兩個部分,在后續的章節會主要講述深度/模板狀態

DirectX11 With Windows SDK完整目錄

Github項目源碼

歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。

混合等式

對於兩個相同位置的像素點,規定\(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\) 的具體含義都需要在程序中進行指定。

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

\[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(XMFLOAT2(20.0f, 20.0f), XMFLOAT2(5.0f, 5.0f)));
    m_Floor.SetTexture(texture.Get());
    m_Floor.GetTransform().SetPosition(0.0f, -1.0f, 0.0f);
    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(XMFLOAT2(20.0f, 8.0f), XMFLOAT2(5.0f, 1.5f)));
        m_Walls[i].SetMaterial(material);
        Transform& wallTransform = m_Walls[i].GetTransform();
        wallTransform.SetRotation(-XM_PIDIV2, XM_PIDIV2 * i, 0.0f);
        wallTransform.SetPosition(i % 2 ? -10.0f * (i - 2) : 0.0f, 3.0f, i % 2 == 0 ? -10.0f * (i - 1) : 0.0f);
        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(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(8.0f);
    camera->SetDistanceMinMax(2.0f, 14.0f);
    camera->SetRotationX(XM_PIDIV4);

    // 初始化僅在窗口大小變動時修改的值
    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_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);

    // 籬笆盒稍微抬起一點高度
    Transform& wireFrameTransform = m_WireFence.GetTransform();
    wireFrameTransform.SetPosition(2.0f, 0.01f, 0.0f);
    m_WireFence.Draw(m_pd3dImmediateContext.Get());
    wireFrameTransform.SetPosition(-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,以及有什么問題也可以在這里匯報。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM