紋理貼圖映射(texturemapping)是可以顯著提高場景細節和真實感的一種技術,基本原理是將圖像數據映射到3D三角形表面(之前的文章提到過,三維模型其實是由很多個三角形拼接而成)。當使用紋理資源時,只要將每個3D三角形與紋理資源上的三角形對應,就可以實現貼圖效果。如圖1,有一個立方體模型和紋理貼圖,將立方體上的點與紋理貼圖上的點對應,就像給一個沒有顏色的正方體貼一層木紋包裝紙。
Direct3D的紋理座標系由表示圖像水平方向的u軸和表示圖像垂直方向的v軸組成。座標 (u,v) 指定了紋理上的一個元素,該元素稱爲紋理元素(texture element),其中 0≤u,v≤1。將規範化座標區間設爲[0,1]是因爲這樣可以使Direct3D擁有一個獨立於紋理尺寸的座標空間。即無論紋理的實際尺寸是 256×256還是512×512,(0.5,0.5)永遠表示中間的紋理元素。
另外還有一個問題,紋理資源不管多精細都是由離散的數據點組成,如果指定的紋理座標(u,v)與任何一個紋理元素點都不對應時該怎麼辦?如圖1中的立方體。假設木頭紋理的分辨率爲 512×512,顯示器的分辨率爲 1024×1024,當觀察點逐漸靠近立方體,立方體會被放大,甚至能蓋住整個屏幕。這時就需要用很少的紋理元素來覆蓋很多的像素,稱爲倍增。與倍增相反的問題是縮減,要用很多的紋理元素來覆蓋很少的像素。DirectX爲解決這些問題定義了多種過濾器,如點過濾和線性過濾。使用時只要對應好頂點和其紋理座標,過濾器就能通過插值或抽取估計頂點之間每個像素的顏色。
下面就來實現紋理貼圖映射。
使用模版新建Direct3D立方體項目。首先依然是更改HLSL代碼。
頂點着色器部分:使用紋理資源時不需要指定顏色,所以用這部分空間存儲頂點的法向量,用於計算光照效果。添加紋理座標成員,它與3D頂點座標對應。這樣,每3個頂點構成的3D三角形在紋理空間中都會有一個對應的2D紋理三角形。代碼如下:
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix model;
matrix view;
matrix projection;
};
struct VertexShaderInput
{
float3 pos : POSITION;
float3 normal : NORMAL;
float2 tex : TEXCOOD;
};
struct VertexShaderOutput
{
float4 pos : SV_POSITION;
float3 normal : NORMAL;
float2 tex : TEXCOOD;
};
VertexShaderOutput main(VertexShaderInput input)
{
VertexShaderOutputoutput;
float4 pos = float4(input.pos, 1.0f);
// 轉換座標到投影空間
pos = mul(pos,model);
pos = mul(pos,view);
pos = mul(pos,projection);
output.pos =pos;
// 轉換法向量到世界空間用於光照計算
float4 normal = float4(normalize(input.normal),0.0f);
normal =mul(normal, model);
output.normal =normalize(normal.xyz);
// 紋理座標不需要改動
output.tex =input.tex;
return output;
}
像素着色器部分:輸入結構體定義必須和頂點着色器的輸出結構體格式一致。還要添加紋理資源,並在main方法中添加簡單漫反射光的計算。另外,使用過濾器訪問紋理資源需要通過採樣器。代碼如下:
SamplerState samplerLinear : register(s0);
Texture2D woodDiffuse : register(t0);
struct PixelShaderInput
{
float4 pos : SV_POSITION;
float3 normal : NORMAL;
float2 tex : TEXCOOD;
};
float4 main(PixelShaderInput input) : SV_TARGET
{
float3 lightDirection =normalize(float3(1, -1, 0));
float4 texelColor = woodDiffuse.Sample(samplerLinear,input.tex);
// 計算簡單漫反射
float lightMagnitude =0.8f * saturate(dot(input.normal, -lightDirection)) + 0.2f;
return texelColor *lightMagnitude;
}
HLSL代碼完成後就要修改主程序。在Windows 8 Store App中載入紋理資源可以使用WICTextureLoader。它支持讀取多種圖片資源(jpg、png)創建紋理。使用時將.cpp和.h文件加入項目即可。如果想載入DDS格式的資源可以看DirectXTex的說明。
完成後在CubeRenderer類裏更改結構體定義,與着色器對應:
struct VertexPositionColor
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT3 normal;
DirectX::XMFLOAT2 tex;
};
然後添加三個成員以使用紋理資源和採樣器:
ID3D11Resource* tex;
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_WoodSRV;
Microsoft::WRL::ComPtr<ID3D11SamplerState> m_Sampler;
接着就開始修改初始化部分。在CreateDeviceResources方法中在載入頂點着色器和像素着色器之後添加createWoodTexTask,用於初始化紋理資源和採樣器:
auto createWoodTexTask = (createPSTask &&createVSTask).then([this] () {
DX::ThrowIfFailed(
CreateWICTextureFromFile(
m_d3dDevice.Get(),
m_d3dContext.Get(),
L"wood.jpg",
&tex,
m_WoodSRV.GetAddressOf()
)
);
D3D11_SAMPLER_DESC samplerDesc;
samplerDesc.Filter= D3D11_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU= D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressV= D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressW= D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.MipLODBias= 0;
samplerDesc.MaxAnisotropy= 1;
samplerDesc.ComparisonFunc= D3D11_COMPARISON_NEVER;
samplerDesc.BorderColor[0 ] = 1.0f;
samplerDesc.BorderColor[1 ] = 1.0f;
samplerDesc.BorderColor[2 ] = 1.0f;
samplerDesc.BorderColor[3 ] = 1.0f;
samplerDesc.MinLOD= -3.402823466e+38F; // -FLT_MAX
samplerDesc.MaxLOD= 3.402823466e+38F; // FLT_MAX
DX::ThrowIfFailed(
m_d3dDevice->CreateSamplerState(
&samplerDesc,
m_Sampler.GetAddressOf()
)
);
});
接下來自然是設置頂點緩衝區和索引數組。頂點的數據需要根據結構體的變化修改:
auto createCubeTask = createWoodTexTask.then([this] () {
VertexPositionColor cubeVertices[] =
{
// +x
{XMFLOAT3( 0.5f, 0.5f, -0.5f), XMFLOAT3( 1.0f, 0.0f, 0.0f),XMFLOAT2(0.0f, 0.0f), },
{XMFLOAT3( 0.5f, 0.5f, 0.5f), XMFLOAT3( 1.0f, 0.0f, 0.0f), XMFLOAT2(1.0f, 0.0f), },
{XMFLOAT3( 0.5f, -0.5f, 0.5f), XMFLOAT3( 1.0f, 0.0f, 0.0f),XMFLOAT2(1.0f, 1.0f), },
{XMFLOAT3( 0.5f, -0.5f,-0.5f), XMFLOAT3( 1.0f, 0.0f, 0.0f), XMFLOAT2(0.0f, 1.0f), },
// -x
{XMFLOAT3(-0.5f, 0.5f, -0.5f), XMFLOAT3(-1.0f, 0.0f, 0.0f),XMFLOAT2(0.0f, 0.0f), },
{XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT3(-1.0f, 0.0f, 0.0f), XMFLOAT2(1.0f, 0.0f), },
{XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT3(-1.0f, 0.0f, 0.0f),XMFLOAT2(1.0f, 1.0f), },
{XMFLOAT3(-0.5f, -0.5f,-0.5f), XMFLOAT3(-1.0f, 0.0f, 0.0f), XMFLOAT2(0.0f, 1.0f), },
// +y
{XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT3( 0.0f, 1.0f, 0.0f), XMFLOAT2(1.0f, 0.0f), },
{XMFLOAT3( 0.5f, 0.5f, 0.5f), XMFLOAT3( 0.0f, 1.0f, 0.0f), XMFLOAT2(0.0f, 1.0f), },
{XMFLOAT3( 0.5f, 0.5f, -0.5f), XMFLOAT3( 0.0f, 1.0f, 0.0f),XMFLOAT2(1.0f, 1.0f), },
{XMFLOAT3(-0.5f, 0.5f, -0.5f), XMFLOAT3( 0.0f, 1.0f, 0.0f),XMFLOAT2(0.0f, 0.0f), },
// -y
{XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT3( 0.0f, -1.0f,0.0f), XMFLOAT2(1.0f, 0.0f), },
{XMFLOAT3( 0.5f, -0.5f, 0.5f), XMFLOAT3( 0.0f, -1.0f,0.0f), XMFLOAT2(0.0f, 1.0f), },
{XMFLOAT3( 0.5f, -0.5f,-0.5f), XMFLOAT3( 0.0f, -1.0f, 0.0f), XMFLOAT2(1.0f, 1.0f), },
{XMFLOAT3(-0.5f, -0.5f,-0.5f), XMFLOAT3( 0.0f, -1.0f, 0.0f), XMFLOAT2(0.0f, 0.0f), },
// +z
{XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT3( 0.0f, 0.0f, 1.0f), XMFLOAT2(0.0f, 0.0f), },
{XMFLOAT3( 0.5f, 0.5f, 0.5f), XMFLOAT3( 0.0f, 0.0f, 1.0f), XMFLOAT2(1.0f, 0.0f), },
{XMFLOAT3( 0.5f, -0.5f, 0.5f), XMFLOAT3( 0.0f, 0.0f, 1.0f),XMFLOAT2(1.0f, 1.0f), },
{XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT3( 0.0f, 0.0f, 1.0f),XMFLOAT2(0.0f, 1.0f), },
// -z
{XMFLOAT3(-0.5f, 0.5f, -0.5f), XMFLOAT3( 0.0f, 0.0f,-1.0f), XMFLOAT2(1.0f, 0.0f), },
{XMFLOAT3( 0.5f, 0.5f, -0.5f), XMFLOAT3( 0.0f, 0.0f,-1.0f), XMFLOAT2(0.0f, 0.0f), },
{XMFLOAT3( 0.5f, -0.5f,-0.5f), XMFLOAT3( 0.0f, 0.0f, -1.0f), XMFLOAT2(0.0f, 1.0f), },
{XMFLOAT3(-0.5f, -0.5f,-0.5f), XMFLOAT3( 0.0f, 0.0f, -1.0f), XMFLOAT2(1.0f, 1.0f), },
};
D3D11_SUBRESOURCE_DATA vertexBufferData ={0};
vertexBufferData.pSysMem= cubeVertices;
vertexBufferData.SysMemPitch= 0;
vertexBufferData.SysMemSlicePitch= 0;
CD3D11_BUFFER_DESC vertexBufferDesc(sizeof(cubeVertices), D3D11_BIND_VERTEX_BUFFER);
DX::ThrowIfFailed(
m_d3dDevice->CreateBuffer(
&vertexBufferDesc,
&vertexBufferData,
&m_vertexBuffer
)
);
unsigned short cubeIndices[] =
{
0,2,1, // +x
0,3,2,
4,5,6, // -x
4,6,7,
8,10,9, // +y
8,11,10,
12,13,14,// -y
12,14,15,
16,17,18,// +z
16,18,19,
20,22,21,// -z
20,23,22,
};
m_indexCount= ARRAYSIZE(cubeIndices);
D3D11_SUBRESOURCE_DATA indexBufferData ={0};
indexBufferData.pSysMem= cubeIndices;
indexBufferData.SysMemPitch= 0;
indexBufferData.SysMemSlicePitch= 0;
CD3D11_BUFFER_DESC indexBufferDesc(sizeof(cubeIndices), D3D11_BIND_INDEX_BUFFER);
DX::ThrowIfFailed(
m_d3dDevice->CreateBuffer(
&indexBufferDesc,
&indexBufferData,
&m_indexBuffer
)
);
});
完成後就可以在渲染時使用紋理資源了。在Render方法裏添加以下代碼使紋理資源生效:
// 設置紋理資源
m_d3dContext->PSSetShaderResources(
0,
1,
m_WoodSRV.GetAddressOf()
);
// 設置紋理採樣器
m_d3dContext->PSSetSamplers(
0,
1,
m_Sampler.GetAddressOf()
);
運行後效果如下圖:
本篇文章的源代碼: