在上一個教程中,我們爲項目引入了照明。 現在我們將通過向我們的立方體添加紋理來構建它。 此外,我們將介紹常量緩衝區的概念,並解釋如何使用緩衝區通過最小化帶寬使用來加速處理。
本教程的目的是修改中心立方體以將紋理映射到其上。
紋理映射
紋理映射是指2D圖像在3D幾何體上的投影。 我們可以把它想象成包裝禮物,將裝飾紙放在一個平淡無奇的盒子上。 爲此,我們必須指定幾何體表面上的點如何與2D圖像對應。
訣竅是正確地將模型的座標與紋理對齊。 對於複雜模型,很難手動確定紋理的座標。 因此,3D建模包通常將導出具有相應紋理座標的模型。 由於我們的示例是一個立方體,因此很容易確定匹配紋理所需的座標。 紋理座標在頂點處定義,然後針對曲面上的各個像素進行插值。
從紋理和採樣器狀態創建着色器資源
紋理是從文件中檢索並用於創建着色器資源視圖的2D圖像,以便可以從着色器中讀取它。
hr = D3DX11CreateShaderResourceViewFromFile( g_pd3dDevice, L"seafloor.dds", NULL, NULL,
&g_pTextureRV, NULL );
我們還需要創建一個採樣器狀態來控制着色器如何處理過濾,MIP和尋址。 在本教程中,我們將啓用簡單的採樣器狀態,以啓用線性過濾和換行尋址。 要創建採樣器狀態,我們將使用ID3D11Device :: CreateSamplerState()。
// Create the sample state
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 = g_pd3dDevice->CreateSamplerState( &sampDesc, &g_pSamplerLinear );
定義座標
在我們將圖像映射到立方體之前,我們必須首先在立方體的每個頂點上定義紋理座標。 由於圖像可以是任何大小,因此使用的座標系已標準化爲[0,1]。 紋理的左上角對應於(0,0),右下角對應於(1,1)。
在這個例子中,我們將整個紋理分佈在立方體的每一側。 這簡化了座標的定義,沒有混淆。 但是,完全可以指定要在所有六個面上拉伸的紋理,儘管定義點更加困難,並且它會顯得拉伸和扭曲。
首先,我們更新了用於定義頂點的結構,以包含紋理座標。
struct SimpleVertex
{
XMFLOAT3 Pos;
XMFLOAT2 Tex;
};
接下來,我們將輸入佈局更新爲着色器以包括這些座標。
// Define the input layout
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
由於輸入佈局已更改,因此還必須修改相應的頂點着色器輸入以匹配添加。
struct VS_INPUT
{
float4 Pos : POSITION;
float2 Tex : TEXCOORD;
};
最後,我們準備在我們在教程4中定義的頂點中包含紋理座標。注意第二個參數輸入是包含紋理座標的D3DXVECTOR2。 立方體上的每個頂點都對應於紋理的一角。 這將創建一個簡單的映射,其中每個頂點得到(0,0)(0,1)(1,0)或(1,1)作爲座標。
// Create vertex buffer
SimpleVertex vertices[] =
{
{ XMFLOAT3( -1.0f, 1.0f, -1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
{ XMFLOAT3( 1.0f, 1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
{ XMFLOAT3( 1.0f, 1.0f, 1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
{ XMFLOAT3( -1.0f, 1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) },
{ XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
{ XMFLOAT3( 1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
{ XMFLOAT3( 1.0f, -1.0f, 1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
{ XMFLOAT3( -1.0f, -1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) },
{ XMFLOAT3( -1.0f, -1.0f, 1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
{ XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
{ XMFLOAT3( -1.0f, 1.0f, -1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
{ XMFLOAT3( -1.0f, 1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) },
{ XMFLOAT3( 1.0f, -1.0f, 1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
{ XMFLOAT3( 1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
{ XMFLOAT3( 1.0f, 1.0f, -1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
{ XMFLOAT3( 1.0f, 1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) },
{ XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
{ XMFLOAT3( 1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
{ XMFLOAT3( 1.0f, 1.0f, -1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
{ XMFLOAT3( -1.0f, 1.0f, -1.0f ), XMFLOAT2( 0.0f, 1.0f ) },
{ XMFLOAT3( -1.0f, -1.0f, 1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
{ XMFLOAT3( 1.0f, -1.0f, 1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
{ XMFLOAT3( 1.0f, 1.0f, 1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
{ XMFLOAT3( -1.0f, 1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) },
};
當我們對紋理進行採樣時,我們需要使用下面幾何體的材質顏色對其進行調製。
將紋理綁定爲着色器資源
紋理和採樣器狀態是我們在前面的教程中看到的常量緩衝區之類的對象。 在着色器可以使用它們之前,需要使用ID3D11DeviceContext :: PSSetSamplers()和ID3D11DeviceContext :: PSSetShaderResources()API進行設置。
g_pImmediateContext->PSSetShaderResources( 0, 1, &g_pTextureRV );
g_pImmediateContext->PSSetSamplers( 0, 1, &g_pSamplerLinear );
應用紋理
要在幾何體頂部映射紋理,我們將在像素着色器中調用紋理查找功能。 函數Sample將執行2D紋理的紋理查找,然後返回採樣的顏色。 下面顯示的像素着色器調用此函數並將其乘以底層網格顏色(或材質顏色),然後輸出最終顏色。
當我們將資源視圖g_pTextureRV綁定到它時,txDiffuse是存儲我們從上面的代碼傳入的紋理的對象。
samLinear將在下面描述; 它是紋理查找的採樣器規範。
input.Tex是我們在源中指定的紋理的座標。
// Pixel Shader
float4 PS( PS_INPUT input) : SV_Target
{
return txDiffuse.Sample( samLinear, input.Tex ) * vMeshColor;
}
我們必須記住的另一件事是通過頂點着色器傳遞紋理座標。 如果不這樣做,數據在到達像素着色器時就會丟失。 在這裏,我們只需將輸入的座標複製到輸出中,然後讓硬件處理剩下的部分。
// Vertex Shader
PS_INPUT VS( VS_INPUT input )
{
PS_INPUT output = (PS_INPUT)0;
output.Pos = mul( input.Pos, World );
output.Pos = mul( output.Pos, View );
output.Pos = mul( output.Pos, Projection );
output.Tex = input.Tex;
return output;
}
常量緩衝區
在Direct3D 11中,應用程序可以使用常量緩衝區來設置着色器常量(着色器變量)。使用類似於C樣式結構的語法聲明常量緩衝區。常量緩衝區通過允許將着色器常量組合在一起並同時提交來減少更新着色器常量所需的帶寬,而不是單獨調用單獨提交每個常量。
在前面的教程中,我們使用單個常量緩衝區來保存我們需要的所有着色器常量。但是,有效使用常量緩衝區的最佳方法是根據更新頻率將着色器變量組織到常量緩衝區中。這允許應用程序最小化更新着色器常量所需的帶寬。例如,本教程將常量分爲三個結構:一個用於更改每個幀的變量,一個用於僅在窗口大小更改時更改的變量,另一個用於設置一次然後不更改的變量。
本教程的.fx文件中定義了以下常量緩衝區。
cbuffer cbNeverChanges
{
matrix View;
};
cbuffer cbChangeOnResize
{
matrix Projection;
};
cbuffer cbChangesEveryFrame
{
matrix World;
float4 vMeshColor;
};
要使用這些常量緩衝區,您需要爲每個緩衝區創建一個ID3D11Buffer對象。 然後,您可以調用ID3D11DeviceContext :: UpdateSubresource()來在需要時更新每個常量緩衝區,而不會影響其他常量緩衝區。
//
// Update variables that change once per frame
//
CBChangesEveryFrame cb;
cb.mWorld = XMMatrixTranspose( g_World );
cb.vMeshColor = g_vMeshColor;
g_pImmediateContext->UpdateSubresource( g_pCBChangesEveryFrame, 0, NULL, &cb, 0, 0 );