在以前的DirectX SDK中,紋理的讀取使用的是D3DX11CreateShaderResourceViewFromFile
函數,如今在Windows SDK中已經沒有這些函數,咱們須要找到DDSTextureLoader
和WICTextureLoader
這兩個庫來讀取DDS位圖和WIC位圖html
DirectX11 With Windows SDK完整目錄git
Github項目源碼github
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。編程
紋理座標系和屏幕、圖片座標系的有些類似,它們的U軸都是水平朝右,V軸豎直向下。可是紋理的X和Y的取值範圍都爲[0.0, 1.0]
,分別映射到[0, Width]
和[0, Height]
數組
對於一個3D的三角形,經過給這三個頂點額外的紋理座標信息,那麼三個紋理座標就能夠映射到紋理指定的某片三角形區域。app
這樣的話已知三個頂點的座標p0
,p1
和p2
以及三個紋理座標q0
,q1
和q2
,就能夠求出頂點座標映射與紋理座標的對應關係:函數
\[(x, y, z) = \mathbf{p_0} + s(\mathbf{p_1} - \mathbf{p_0}) + t(\mathbf{p_2} - \mathbf{p_0})\]佈局
\[(u, v) = \mathbf{q_0} + s(\mathbf{q_1} - \mathbf{q_0}) + t(\mathbf{q_2} - \mathbf{q_0})\]性能
而且還須要知足\(s >= 0, t >= 0, s + t <= 1\)動畫
因此頂點結構體的內容會有所變化:
struct VertexPosNormalTex { DirectX::XMFLOAT3 pos; DirectX::XMFLOAT3 normal; DirectX::XMFLOAT2 tex; static const D3D11_INPUT_ELEMENT_DESC inputLayout[3]; };
對應的每一個輸入元素的描述爲:
const D3D11_INPUT_ELEMENT_DESC VertexPosNormalTex::inputLayout[3] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}, { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0} };
DDS是一種圖片格式,是DirectDraw Surface的縮寫,它是DirectX紋理壓縮(DirectX Texture Compression,簡稱DXTC)的產物。由NVIDIA公司開發。大部分3D遊戲引擎均可以使用DDS格式的圖片用做貼圖,也能夠製做法線貼圖。
WIC(Windows Imaging Component)是一個能夠擴展的平臺,爲數字圖像提供底層API,它能夠支持bmp、dng、ico、jpeg、png、tiff等格式的位圖。
要使用這兩個庫,有兩種方案。
第一種:在DirectXTex中找到DDSTextureLoader
文件夾和WICTextureLoader
文件夾中分別找到對應的頭文件和源文件(不帶12的),並加入到你的項目中
第二種:將DirectXTK庫添加到你的項目中,這裏再也不贅述
這以後就能夠包含DDSTextureLoader.h
和WICTextureLoader.h
進項目中了。
如今讀取DDS紋理的操做變得更簡單了:
HRESULT CreateDDSTextureFromFile( ID3D11Device* d3dDevice, // [In]D3D設備 const wchar_t* szFileName, // [In]dds圖片文件名 ID3D11Resource** texture, // [Out]輸出一個指向資源接口類的指針,也能夠填nullptr ID3D11ShaderResourceView** textureView, // [Out]輸出一個指向着色器資源視圖的指針,也能夠填nullptr size_t maxsize = 0, // [In]忽略 DDS_ALPHA_MODE* alphaMode = nullptr); // [In]忽略
下面是一個調用的例子:
// 初始化木箱紋理 HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\WoodCrate.dds", nullptr, m_pWoodCrate.GetAddressOf()));
函數原型以下:
HRESULT CreateWICTextureFromFile( ID3D11Device* d3dDevice, // [In]D3D設備 const wchar_t* szFileName, // [In]wic所支持格式的圖片文件名 ID3D11Resource** texture, // [Out]輸出一個指向資源接口類的指針,也能夠填nullptr ID3D11ShaderResourceView** textureView, // [Out]輸出一個指向着色器資源視圖的指針,也能夠填nullptr size_t maxsize = 0); // [In]忽略
下面是一個調用的例子:
// 初始化火焰紋理 WCHAR strFile[40]; m_pFireAnims.resize(120); for (int i = 1; i <= 120; ++i) { wsprintf(strFile, L"Texture\\FireAnim\\Fire%03d.bmp", i); HR(CreateWICTextureFromFile(m_pd3dDevice.Get(), strFile, nullptr, m_pFireAnims[i - 1].GetAddressOf())); }
這裏咱們只須要建立着色器資源視圖,而不是紋理資源。緣由在後面會提到。
圖片在通過放大操做後,除了圖片原有的像素被拉伸,還須要對其他空缺的像素位置選用合適的方式來進行填充。好比一個2x2位圖被拉伸成8x8的,除了角上4個像素,還須要對其他60個像素進行填充。下面介紹幾種方法
對於2x2位圖,它的寬高表示範圍都爲[0,1]
,而8x8位圖的都爲[0,7]
,且只容許取整數。那麼對於放大後的像素點(1, 4)
就會映射到(1/7, 4/7)
上。
常量插值法的作法十分簡單粗暴,就是對X和Y值都進行四捨五入操做,而後取鄰近像素點的顏色。好比對於映射後的值若是落在[20.5, 21.5)
的範圍,最終都會取21。根據上面的例子,最終會落入到像素點(0, 1)
上,而後取該像素點的顏色。
如今只討論一維狀況,已知第20個像素點的顏色p0
和第21個像素點的顏色p1
,而且通過拉伸放大後,有一個像素點落在範圍(20, 21)
之間,咱們就可使用線性插值法求出最終的顏色(t取(0,1)
):
\[\mathbf{p} = t\mathbf{p_1} + (1 - t)\mathbf{p_0}\]
對於二維狀況,會有三種使用線性插值法的狀況:
下圖展現了雙線性插值法的過程,已知4個相鄰像素點,當前採樣的紋理座標在這四個點內,則首先根據x方向的紋理座標進行線性插值,而後根據y方向的紋理座標再進行一遍線性插值:
而下圖則演示了兩種插值法的效果,其中左邊使用了常量插值法,右邊使用了二維線性插值法
圖片在通過縮小操做後,須要拋棄掉一些像素。但顯然每次繪製都按實際寬高來進行縮小會對性能有很大影響。 在d3d中可使用mipmapping技術,以額外犧牲一些內存代價的方式來得到高效的擬合效果。
這裏估計使用的是金字塔下采樣的原理。一張256x256的紋理,經過不斷的向下採樣,能夠得到256x25六、128x12八、64x64...一直到1x1的一系列位圖,這些位圖構建了一條mipmap鏈,而且不一樣的紋理標註有不一樣的mipmap等級
其中mipmap等級爲0的紋理即爲原來的紋理,等級爲1的紋理所佔內存爲等級爲0的1/4,等級爲2的紋理所佔內存爲等級爲1的1/4...以此類推咱們能夠知道包含完整mipmap的紋理佔用的內存空間不超過原來紋理的
\[\lim_{n \to +\infty} \frac{1(1-(\frac{1}{4})^n)}{1-\frac{1}{4}} = \frac{1}{1-\frac{1}{4}} = \frac{4}{3}\]
接下來會有兩種狀況:
Anisotropic Filtering能夠幫助咱們處理那些不與屏幕平行的平面,須要額外使用平面的法向量和攝像機的觀察方向向量。雖然使用該種過濾器會有比較大的性能損耗,可是能誕生出比較理想的效果。
下面左圖使用了線性過濾法,右邊使用的是各向異性過濾,能夠看到頂面紋理比左邊的更加清晰
所謂採樣,就是根據紋理座標取出紋理對應位置最爲接近的像素,在HLSL的寫法以下:
g_Tex.Sample(g_SamLinear, pIn.Tex);
但大多數時候繪製出的紋理會比所用的紋理大或小,這樣就還涉及到了採樣器使用什麼方式(如常量插值法、線性插值法、各向異性過濾)來處理圖片放大、縮小的狀況。
Basic.hlsli
代碼以下:
#include "LightHelper.hlsli" Texture2D gTex : register(t0); SamplerState gSamLinear : register(s0); cbuffer VSConstantBuffer : register(b0) { matrix g_World; matrix g_View; matrix g_Proj; matrix g_WorldInvTranspose; } cbuffer PSConstantBuffer : register(b1) { DirectionalLight g_DirLight[10]; PointLight g_PointLight[10]; SpotLight g_SpotLight[10]; Material g_Material; int g_NumDirLight; int g_NumPointLight; int g_NumSpotLight; float g_Pad1; float3 g_EyePosW; float g_Pad2; } struct VertexPosNormalTex { float3 PosL : POSITION; float3 NormalL : NORMAL; float2 Tex : TEXCOORD; }; struct VertexPosTex { float3 PosL : POSITION; float2 Tex : TEXCOORD; }; struct VertexPosHWNormalTex { float4 PosH : SV_POSITION; float3 PosW : POSITION; // 在世界中的位置 float3 NormalW : NORMAL; // 法向量在世界中的方向 float2 Tex : TEXCOORD; }; struct VertexPosHTex { float4 PosH : SV_POSITION; float2 Tex : TEXCOORD; };
Basic_VS_2D.hlsl
的代碼:
// Basic_VS_2D.hlsl #include "Basic.hlsli" // 頂點着色器(2D) VertexPosHTex VS_2D(VertexPosTex vIn) { VertexPosHTex vOut; vOut.PosH = float4(vIn.PosL, 1.0f); vOut.Tex = vIn.Tex; return vOut; }
Basic_PS_2D.hlsl
的代碼:
// Basic_PS_2D.hlsl #include "Basic.hlsli" // 像素着色器(2D) float4 PS_2D(VertexPosHTex pIn) : SV_Target { return g_Tex.Sample(g_SamLinear, pIn.Tex); }
Basic_VS_3D.hlsl
的代碼:
// Basic_VS_3D.hlsl #include "Basic.hlsli" // 頂點着色器(3D) VertexPosHWNormalTex VS_3D(VertexPosNormalTex vIn) { VertexPosHWNormalTex vOut; matrix viewProj = mul(g_View, g_Proj); float4 posW = mul(float4(vIn.PosL, 1.0f), g_World); vOut.PosH = mul(posW, viewProj); vOut.PosW = posW.xyz; vOut.NormalW = mul(vIn.NormalL, (float3x3) g_WorldInvTranspose); vOut.Tex = vIn.Tex; return vOut; }
Basic_PS_3D.hlsl
的代碼:
// Basic_PS_3D.hlsl #include "Basic.hlsli" // 像素着色器(3D) float4 PS_3D(VertexPosHWNormalTex pIn) : SV_Target { // 標準化法向量 pIn.NormalW = normalize(pIn.NormalW); // 頂點指向眼睛的向量 float3 toEyeW = normalize(g_EyePosW - pIn.PosW); // 初始化爲0 float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f); float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f); float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f); float4 A = float4(0.0f, 0.0f, 0.0f, 0.0f); float4 D = float4(0.0f, 0.0f, 0.0f, 0.0f); float4 S = float4(0.0f, 0.0f, 0.0f, 0.0f); int i; // 強制展開循環以減小指令數 [unroll] for (i = 0; i < g_NumDirLight; ++i) { ComputeDirectionalLight(g_Material, g_DirLight[i], pIn.NormalW, toEyeW, A, D, S); ambient += A; diffuse += D; spec += S; } [unroll] for (i = 0; i < g_NumPointLight; ++i) { ComputePointLight(g_Material, g_PointLight[i], pIn.PosW, pIn.NormalW, toEyeW, A, D, S); ambient += A; diffuse += D; spec += S; } [unroll] for (i = 0; i < g_NumSpotLight; ++i) { ComputeSpotLight(g_Material, g_SpotLight[i], pIn.PosW, pIn.NormalW, toEyeW, A, D, S); ambient += A; diffuse += D; spec += S; } float4 texColor = g_Tex.Sample(g_SamLinear, pIn.Tex); float4 litColor = texColor * (ambient + diffuse) + spec; litColor.a = texColor.a * g_Material.Diffuse.a; return litColor; }
其中Texture2D
類型保存了2D紋理的信息,在這是全局變量。而register(t0)
對應起始槽索引0.
SamplerState
類型肯定採樣器應如何進行採樣,一樣也是全局變量,register(s0)
對應起始槽索引0.
上述兩種變量都須要在C++應用層中初始化和綁定後才能使用。
Texture2D
類型擁有Sample
方法,須要提供採樣器狀態和2D紋理座標方可以使用,而後返回一個包含RGBA信息的float4
向量。
[unroll]
屬性用於展開循環,避免沒必要要的跳轉,但可能會產生大量的指令
除此以外,上面的HLSL代碼容許每種燈光最多10盞,而後還提供了2D和3D版本的頂點/像素着色器供使用。
注意Basic.hlsli
和LightHelper.hlsli
是不參與生成的。其他着色器文件須要按照第2章的方式去設置好。
須要注意的是,紋理並不能直接綁定到着色器中,須要爲紋理建立對應的着色器資源視圖纔可以給着色器使用。上面打*意味着渲染管線的全部可編程着色器階段都有該方法。
此外,着色器資源視圖不只能夠綁定紋理資源,還能夠綁定緩衝區資源。有關緩衝區資源綁定到着色器資源視圖的應用,咱們留到後面的章節再講。
目前在DDSTextureLoader
和WICTextureLoader
中,咱們只須要用到紋理的着色器資源。這裏以ID3D11DeviceContext::PSSetShaderResources
爲例:
void ID3D11DeviceContext::PSSetShaderResources( UINT StartSlot, // [In]起始槽索引,對應HLSL的register(t*) UINT NumViews, // [In]着色器資源視圖數目 ID3D11ShaderResourceView * const *ppShaderResourceViews // [In]着色器資源視圖數組 );
而後調用方法以下:
m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pWoodCrate.GetAddressOf());
這樣在HLSL裏對應regisgter(t0)
的g_Tex
存放的就是木箱表面的紋理了。
在C++代碼層中,咱們只能經過D3D設備建立採樣器狀態,而後綁定到渲染管線中,使得在HLSL中能夠根據過濾器、尋址模式等進行採樣。
在建立採樣器狀態以前,須要先填充結構體D3D11_SAMPLER_DESC
來描述採樣器狀態:
typedef struct D3D11_SAMPLER_DESC { D3D11_FILTER Filter; // 所選過濾器 D3D11_TEXTURE_ADDRESS_MODE AddressU; // U方向尋址模式 D3D11_TEXTURE_ADDRESS_MODE AddressV; // V方向尋址模式 D3D11_TEXTURE_ADDRESS_MODE AddressW; // W方向尋址模式 FLOAT MipLODBias; // mipmap等級偏移值,最終算出的mipmap等級會加上該偏移值 UINT MaxAnisotropy; // 最大各向異性等級(1-16) D3D11_COMPARISON_FUNC ComparisonFunc; // 這節不討論 FLOAT BorderColor[ 4 ]; // 邊界外的顏色,使用D3D11_TEXTURE_BORDER_COLOR時須要指定 FLOAT MinLOD; // 若mipmap等級低於MinLOD,則使用等級MinLOD。最小容許設爲0 FLOAT MaxLOD; // 若mipmap等級高於MaxLOD,則使用等級MaxLOD。必須比MinLOD大 } D3D11_SAMPLER_DESC;
D3D11_FILTER
部分枚舉含義以下:
枚舉值 | 縮小 | 放大 | mipmap |
---|---|---|---|
D3D11_FILTER_MIN_MAG_MIP_POINT | 點採樣 | 點採樣 | 點採樣 |
D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR | 點採樣 | 點採樣 | 線性採樣 |
D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT | 點採樣 | 線性採樣 | 點採樣 |
D3D11_FILTER_MIN_MAG_MIP_LINEAR | 線性採樣 | 線性採樣 | 線性採樣 |
D3D11_FILTER_ANISOTROPIC | 各向異性 | 各向異性 | 各向異性 |
D3D11_TEXTURE_ADDRESS_MODE
是單個方向的尋址模式,有時候紋理座標會超過1.0或者小於0.0,這時候尋址模式能夠解釋邊界外的狀況,含義以下:
D3D11_TEXTURE_ADDRESS_WRAP
使用重複的紋理去覆蓋整個實數域,能夠當作fmod(X, 1.0f)
D3D11_TEXTURE_ADDRESS_MIRROR
用重複的紋理覆蓋整個實數域,只不過相鄰的兩個紋理知足按交線對稱
D3D11_TEXTURE_ADDRESS_CLAMP
對U軸和V軸,小於0的值都取做0,大於1的值都取做1
D3D11_TEXTURE_BORDER_COLOR
對於紋理範圍外的區域都使用BorderColor
進行填充
D3D11_TEXTURE_ADDRESS_MIRROR_ONCE
至關於MIRROR和CLAMP的結合,僅[-1,1]的範圍內鏡像有效,其他範圍都會取到-1或者1
最後就是ID3D11Device::CreateSamplerState
方法:
HRESULT ID3D11Device::CreateSamplerState( const D3D11_SAMPLER_DESC *pSamplerDesc, // [In]採樣器狀態描述 ID3D11SamplerState **ppSamplerState); // [Out]輸出的採樣器
接下來演示瞭如何建立採樣器狀態;
// 初始化採樣器狀態描述 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(m_pd3dDevice->CreateSamplerState(&sampDesc, m_pSamplerState.GetAddressOf()));
void ID3D11DeviceContext::PSSetSamplers( UINT StartSlot, // [In]起始槽索引 UINT NumSamplers, // [In]採樣器狀態數目 ID3D11SamplerState * const * ppSamplers); // [In]採樣器數組
根據前面的HLSL代碼,samLinear
使用了索引爲0起始槽,因此須要這樣調用:
// 像素着色階段設置好採樣器 m_pd3dImmediateContext->PSSetSamplers(0, 1, m_pSamplerState.GetAddressOf());
這樣HLSL中對應的採樣器狀態就可使用了。
如今咱們須要編譯出4個着色器,2個頂點佈局,以區分2D和3D的部分。
bool GameApp::InitEffect() { ComPtr<ID3DBlob> blob; // 建立頂點着色器(2D) HR(CreateShaderFromFile(L"HLSL\\Basic_VS_2D.cso", L"HLSL\\Basic_VS_2D.hlsl", "VS_2D", "vs_5_0", blob.ReleaseAndGetAddressOf())); HR(m_pd3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pVertexShader2D.GetAddressOf())); // 建立頂點佈局(2D) HR(m_pd3dDevice->CreateInputLayout(VertexPosTex::inputLayout, ARRAYSIZE(VertexPosTex::inputLayout), blob->GetBufferPointer(), blob->GetBufferSize(), m_pVertexLayout2D.GetAddressOf())); // 建立像素着色器(2D) HR(CreateShaderFromFile(L"HLSL\\Basic_PS_2D.cso", L"HLSL\\Basic_PS_2D.hlsl", "PS_2D", "ps_5_0", blob.ReleaseAndGetAddressOf())); HR(m_pd3dDevice->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pPixelShader2D.GetAddressOf())); // 建立頂點着色器(3D) HR(CreateShaderFromFile(L"HLSL\\Basic_VS_3D.cso", L"HLSL\\Basic_VS_3D.hlsl", "VS_3D", "vs_5_0", blob.ReleaseAndGetAddressOf())); HR(m_pd3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pVertexShader3D.GetAddressOf())); // 建立頂點佈局(3D) HR(m_pd3dDevice->CreateInputLayout(VertexPosNormalTex::inputLayout, ARRAYSIZE(VertexPosNormalTex::inputLayout), blob->GetBufferPointer(), blob->GetBufferSize(), m_pVertexLayout3D.GetAddressOf())); // 建立像素着色器(3D) HR(CreateShaderFromFile(L"HLSL\\Basic_PS_3D.cso", L"HLSL\\Basic_PS_3D.hlsl", "PS_3D", "ps_5_0", blob.ReleaseAndGetAddressOf())); HR(m_pd3dDevice->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pPixelShader3D.GetAddressOf())); return true; }
雖然如今容許同時放入多盞燈光了,但在該項目咱們只使用一盞點光燈,而且僅用於3D木盒的顯示。對於2D火焰動畫,實質上是由120張bmp位圖構成,咱們須要按順序在每一幀切換下一張位圖來達到火焰在動的效果。
bool GameApp::InitResource() { // 初始化網格模型並設置到輸入裝配階段 auto meshData = Geometry::CreateBox(); ResetMesh(meshData); // ****************** // 設置常量緩衝區描述 D3D11_BUFFER_DESC cbd; ZeroMemory(&cbd, sizeof(cbd)); cbd.Usage = D3D11_USAGE_DYNAMIC; cbd.ByteWidth = sizeof(VSConstantBuffer); cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; // 新建用於VS和PS的常量緩衝區 HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[0].GetAddressOf())); cbd.ByteWidth = sizeof(PSConstantBuffer); HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[1].GetAddressOf())); // ****************** // 初始化紋理和採樣器狀態 // 初始化木箱紋理 HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\WoodCrate.dds", nullptr, m_pWoodCrate.GetAddressOf())); // 初始化火焰紋理 WCHAR strFile[40]; m_pFireAnims.resize(120); for (int i = 1; i <= 120; ++i) { wsprintf(strFile, L"Texture\\FireAnim\\Fire%03d.bmp", i); HR(CreateWICTextureFromFile(m_pd3dDevice.Get(), strFile, nullptr, m_pFireAnims[i - 1].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(m_pd3dDevice->CreateSamplerState(&sampDesc, m_pSamplerState.GetAddressOf())); // ****************** // 初始化常量緩衝區的值 // 初始化用於VS的常量緩衝區的值 m_VSConstantBuffer.world = XMMatrixIdentity(); m_VSConstantBuffer.view = XMMatrixTranspose(XMMatrixLookAtLH( XMVectorSet(0.0f, 0.0f, -5.0f, 0.0f), XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f), XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f) )); m_VSConstantBuffer.proj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(XM_PIDIV2, AspectRatio(), 1.0f, 1000.0f)); m_VSConstantBuffer.worldInvTranspose = XMMatrixIdentity(); // 初始化用於PS的常量緩衝區的值 // 這裏只使用一盞點光來演示 m_PSConstantBuffer.pointLight[0].Position = XMFLOAT3(0.0f, 0.0f, -10.0f); m_PSConstantBuffer.pointLight[0].Ambient = XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f); m_PSConstantBuffer.pointLight[0].Diffuse = XMFLOAT4(0.7f, 0.7f, 0.7f, 1.0f); m_PSConstantBuffer.pointLight[0].Specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f); m_PSConstantBuffer.pointLight[0].Att = XMFLOAT3(0.0f, 0.1f, 0.0f); m_PSConstantBuffer.pointLight[0].Range = 25.0f; m_PSConstantBuffer.numDirLight = 0; m_PSConstantBuffer.numPointLight = 1; m_PSConstantBuffer.numSpotLight = 0; m_PSConstantBuffer.eyePos = XMFLOAT4(0.0f, 0.0f, -5.0f, 0.0f); // 這裏容易遺漏,已補上 // 初始化材質 m_PSConstantBuffer.material.Ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f); m_PSConstantBuffer.material.Diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); m_PSConstantBuffer.material.Specular = XMFLOAT4(0.1f, 0.1f, 0.1f, 5.0f); // 注意不要忘記設置此處的觀察位置,不然高亮部分會有問題 m_PSConstantBuffer.eyePos = XMFLOAT4(0.0f, 0.0f, -5.0f, 0.0f); // 更新PS常量緩衝區資源 D3D11_MAPPED_SUBRESOURCE mappedData; HR(m_pd3dImmediateContext->Map(m_pConstantBuffers[1].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData)); memcpy_s(mappedData.pData, sizeof(PSConstantBuffer), &m_PSConstantBuffer, sizeof(PSConstantBuffer)); m_pd3dImmediateContext->Unmap(m_pConstantBuffers[1].Get(), 0); // ****************** // 給渲染管線各個階段綁定好所需資源 // 設置圖元類型,設定輸入佈局 m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout3D.Get()); // 默認綁定3D着色器 m_pd3dImmediateContext->VSSetShader(m_pVertexShader3D.Get(), nullptr, 0); // VS常量緩衝區對應HLSL寄存於b0的常量緩衝區 m_pd3dImmediateContext->VSSetConstantBuffers(0, 1, m_pConstantBuffers[0].GetAddressOf()); // PS常量緩衝區對應HLSL寄存於b1的常量緩衝區 m_pd3dImmediateContext->PSSetConstantBuffers(1, 1, m_pConstantBuffers[1].GetAddressOf()); // 像素着色階段設置好採樣器 m_pd3dImmediateContext->PSSetSamplers(0, 1, m_pSamplerState.GetAddressOf()); m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pWoodCrate.GetAddressOf()); m_pd3dImmediateContext->PSSetShader(m_pPixelShader3D.Get(), nullptr, 0); // 像素着色階段默認設置木箱紋理 m_CurrMode = ShowMode::WoodCrate; return true; }
該項目能夠選擇播放3D木箱或2D火焰動畫,則須要有個當前正在播放內容的狀態值,並隨之進行更新。
其中ShowMode
是一個枚舉類,能夠選擇WoodCrate
或者FireAnim
。
void GameApp::UpdateScene(float dt) { Keyboard::State state = m_pKeyboard->GetState(); m_KeyboardTracker.Update(state); // 鍵盤切換模式 if (m_KeyboardTracker.IsKeyPressed(Keyboard::D1)) { // 播放木箱動畫 m_CurrMode = ShowMode::WoodCrate; m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout3D.Get()); auto meshData = Geometry::CreateBox(); ResetMesh(meshData); m_pd3dImmediateContext->VSSetShader(m_pVertexShader3D.Get(), nullptr, 0); m_pd3dImmediateContext->PSSetShader(m_pPixelShader3D.Get(), nullptr, 0); m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pWoodCrate.GetAddressOf()); } else if (m_KeyboardTracker.IsKeyPressed(Keyboard::D2)) { m_CurrMode = ShowMode::FireAnim; m_CurrFrame = 0; m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout2D.Get()); auto meshData = Geometry::Create2DShow(); ResetMesh(meshData); m_pd3dImmediateContext->VSSetShader(m_pVertexShader2D.Get(), nullptr, 0); m_pd3dImmediateContext->PSSetShader(m_pPixelShader2D.Get(), nullptr, 0); m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pFireAnims[0].GetAddressOf()); } if (m_CurrMode == ShowMode::WoodCrate) { static float phi = 0.0f, theta = 0.0f; phi += 0.00003f, theta += 0.00005f; XMMATRIX W = XMMatrixRotationX(phi) * XMMatrixRotationY(theta); m_VSConstantBuffer.world = XMMatrixTranspose(W); m_VSConstantBuffer.worldInvTranspose = XMMatrixInverse(nullptr, W); // 兩次轉置抵消 // 更新常量緩衝區,讓立方體轉起來 D3D11_MAPPED_SUBRESOURCE mappedData; HR(m_pd3dImmediateContext->Map(m_pConstantBuffers[0].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData)); memcpy_s(mappedData.pData, sizeof(VSConstantBuffer), &m_VSConstantBuffer, sizeof(VSConstantBuffer)); m_pd3dImmediateContext->Unmap(m_pConstantBuffers[0].Get(), 0); } else if (m_CurrMode == ShowMode::FireAnim) { // 用於限制在1秒60幀 static float totDeltaTime = 0; totDeltaTime += dt; if (totDeltaTime > 1.0f / 60) { totDeltaTime -= 1.0f / 60; m_CurrFrame = (m_CurrFrame + 1) % 120; m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pFireAnims[m_CurrFrame].GetAddressOf()); } } }
最終的顯示效果以下:
粗體字爲自定義題目
Geometry::MeshData
建立的立方體網格數據(不能對其修改)的基礎上,讓立方體的六個面使用不一樣的紋理來繪製,可使用魔方項目裏的紋理Texture2DArray
DirectX11 With Windows SDK完整目錄
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。