Windows 8 Directx 開發學習筆記(十)紋理貼圖實現旋轉的木箱

紋理貼圖映射(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()
       );

運行後效果如下圖:

 

本篇文章的源代碼:

DirectX_CubeTexture

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