Direct3D 11 Tutorial 7:Texture Mapping and Constant Buffers_Direct3D 11 教程7:紋理對映和常量緩衝區
概述
在上一個教程中,我們為專案引入了照明。 現在我們將通過向我們的立方體新增紋理來構建它。 此外,我們將介紹常量緩衝區的概念,並解釋如何使用緩衝區通過最小化頻寬使用來加速處理。
本教程的目的是修改中心立方體以將紋理對映到其上。
資源目錄
(SDK root)\Samples\C++\Direct3D11\Tutorials\Tutorial07
紋理對映
紋理對映是指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 );
最終效果