DirectX11 With Windows SDK--11 混合狀態

前言

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

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

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

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

DirectX11 With Windows SDK完整目錄

Github項目源碼

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

混合等式

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

C=CsrcFsrcCdstFdst \mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} \boxplus \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}

其中\otimes運算符爲分量乘法,即CsrcFsrc\mathbf{C}_{src} \otimes \mathbf{F}_{src} 實際上得到的是(RsrcRdst,GsrcGdst,BsrcBdst)(R_{src}*R_{dst}, G_{src}*G_{dst}, B_{src}*B_{dst})

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

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

A=AsrcFsrcAdstFdst A = A_{src} * F_{src} \boxplus A_{dst} * F_{dst}

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

混合狀態

混合運算符的設置

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

枚舉值 --------------------------------含義---------------------------------
D3D11_BLEND_OP_ADD = 1 C=CsrcFsrc+CdstFdst\mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} + \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}A=AsrcFsrc+AdstFdstA = A_{src} * F_{src} + A_{dst} * F_{dst}
D3D11_BLEND_OP_SUBTRACT = 2 C=CdstFdstCsrcFsrc\mathbf{C} = \mathbf{C}_{dst} \otimes \mathbf{F}_{dst} - \mathbf{C}_{src} \otimes \mathbf{F}_{src}A=AdstFdstAsrcFsrcA = A_{dst} * F_{dst} - A_{src} * F_{src}
D3D11_BLEND_OP_REV_SUBTRACT = 3 C=CsrcFsrcCdstFdst\mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} - \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}A=AsrcFsrcAdstFdstA = A_{src} * F_{src} - A_{dst} * F_{dst}
D3D11_BLEND_OP_MIN = 4 C=min(Csrc,Cdst)\mathbf{C} = min(\mathbf{C}_{src}, \mathbf{C}_{dst}) 或== A=min(Asrc,Adst)A = min(A_{src}, A_{dst})
D3D11_BLEND_OP_MAX = 5 C=max(Csrc,Cdst)\mathbf{C} = max(\mathbf{C}_{src}, \mathbf{C}_{dst})A=max(Asrc,Adst)A = max(A_{src}, A_{dst})

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

混合因子的設置

對於混合公式,我們可以按需要設置混合因子。混合因子使用枚舉類型D3D11_BLEND類型進行描述:

枚舉值 含義
D3D11_BLEND_ZERO = 1 F=(0,0,0)\mathbf{F}=(0,0,0)F=0F=0
D3D11_BLEND_ONE = 2 F=(1,1,1)\mathbf{F}=(1,1,1)F=1F=1
D3D11_BLEND_SRC_COLOR = 3 F=(rsrc,gsrc,bsrc)\mathbf{F}=(r_{src},g_{src},b_{src})
D3D11_BLEND_INV_SRC_COLOR = 4 F=(1rsrc,1gsrc,1bsrc)\mathbf{F}=(1-r_{src},1-g_{src},1-b_{src})
D3D11_BLEND_SRC_ALPHA = 5 F=(asrc,asrc,asrc)\mathbf{F}=(a_{src},a_{src},a_{src})F=asrcF=a_{src}
D3D11_BLEND_INV_SRC_ALPHA = 6 F=(1asrc,1asrc,1asrc)\mathbf{F}=(1-a_{src},1-a_{src},1-a_{src})F=1asrcF=1-a_{src}
D3D11_BLEND_DEST_ALPHA = 7 F=(adst,adst,adst)\mathbf{F}=(a_{dst},a_{dst},a_{dst})F=adstF=a_{dst}
D3D11_BLEND_INV_DEST_ALPHA = 8 F=(1adst,1adst,1adst)\mathbf{F}=(1-a_{dst},1-a_{dst},1-a_{dst})F=1adstF=1-a_{dst}
D3D11_BLEND_DEST_COLOR = 9 F=(rdst,gdst,bdst)\mathbf{F}=(r_{dst},g_{dst},b_{dst})
D3D11_BLEND_INV_DEST_COLOR = 10 F=(1rdst,1gdst,1bdst)\mathbf{F}=(1-r_{dst},1-g_{dst},1-b_{dst})
D3D11_BLEND_SRC_ALPHA_SAT = 11 F=(sat(asrc),sat(asrc),sat(asrc))\mathbf{F}=(sat(a_{src}),sat(a_{src}),sat(a_{src}))F=sat(asrc)F=sat(a_{src})
D3D11_BLEND_BLEND_FACTOR = 14 F\mathbf{F} 的值來自於ID3D11DeviceContext::OMSetBlendState方法的BlendFactor參數
D3D11_BLEND_INV_BLEND_FACTOR = 15 F\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來允許所有采樣操作

常用混合等式

無顏色寫入混合

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

顏色加法混合

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

透明混合

透明混合公式如下:
C=CsrcFsrcCdstFdst\mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} \boxplus \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}
C=Csrc(Asrc,Asrc,Asrc)+Cdst((1Asrc),(1Asrc),(1Asrc))\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}))
C=AsrcCsrc+(1Asrc)Cdst\mathbf{C} = A_{src}\mathbf{C}_{src} + (1-A_{src})\mathbf{C}_{dst}
最終的Alpha值是多少並不影響前面的運算,因此可以設爲任意值,這裏設爲源像素Alpha值:
A=AsrcA = 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.SetWorldMatrix(XMMatrixTranslation(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)));
		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(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,以及有什麼問題也可以在這裏彙報。

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