1. 程式人生 > >Windows 8 Directx 開發學習筆記(十一)地形紋理貼圖

Windows 8 Directx 開發學習筆記(十一)地形紋理貼圖

前一篇實現木箱貼圖時,木箱的六個面都正好用一整張紋理圖,即六個面的紋理座標均在[0,1]內。然而在為比較大的模型貼圖時,像山峰河谷模型,如果只用一張紋理圖,那麼每個三角形只得到幾個紋理元素,無法為提供足夠高的解析度。這時可以在模型表面上平鋪紋理貼圖,像給牆面貼磁磚一樣,只需要知道一個單位的貼圖,就能鋪滿整個表面,從而獲得較高的解析度。實現起來其實很簡單,只要變換紋理座標的範圍,同時將紋理定址模式設定為重複即可。

紋理座標範圍變換有兩種方式,很簡單,如圖1:


其中x,y是原始紋理座標,為了能與4*4的變換矩陣相乘,將其擴充套件為4元向量。等號左邊是一種變換方式,使用變換矩陣進行紋理座標變換,可用GPU計算。等號右邊是第二種變換方式,直接生成變換後的紋理座標,由CPU計算。本文選擇的是第一種變換方式。

下面就開始給學習筆記(九)實現的山峰和水面模型貼圖。

依然是先修改HLSL程式碼。在頂點著色器的輸入輸出結構中新增用於儲存紋理座標的成員tex。並在ModelViewProjectionConstantBuffer裡新增texTransform成員,用於儲存縮放和平移變換矩陣。

struct VertexShaderInput
{
    float3 pos : POSITION;
    float3 normal : NORMAL;
    float2 tex : TEXCOOD;
};
 
struct VertexShaderOutput
{
    float4 posH : SV_POSITION;
    float3 posW   : POSITION;
    float3 normal : NORMAL;
    float2 tex : TEXCOOD;
};
 
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
    matrix model;
    matrix view;
    matrix projection;
    matrix texTransform;
};

變換紋理座標時只要在main方法中新增下面的語句即可:

// 紋理座標變換
output.tex = mul(float4(input.tex, 0.0f, 1.0f),texTransform).xy;

在畫素著色器中要定義取樣器和紋理資源,並更改輸入結構體,與頂點著色器的輸出對應。

SamplerState samplerLinear : register(s0);
Texture2D texDiffuse : register(t0);
 
struct PixelShaderInput
{
    float4 posH : SV_POSITION;
    float3 posW : POSITION;
    float3 normal : NORMAL;
    float2 tex : TEXCOOD;
};

為了減少更改,在main方法中還是使用Material結構進行光照計算,只是Diffuse成員由紋理取樣獲得。

Material textureMat = gMaterial;
textureMat.Diffuse = texDiffuse.Sample(samplerLinear,input.tex);

         HLSL程式碼修改至此完成,接下來就要修改C++程式碼。

結構先行,修改Directl3DBase.h中的頂點結構體和常量緩衝區定義,保證它們的結構和頂點著色器中定義的一致。

struct VertexPosition
{
    DirectX::XMFLOAT3 pos;
    DirectX::XMFLOAT3 normal;
    DirectX::XMFLOAT2 tex;
};
struct ModelViewProjectionConstantBuffer
{
    DirectX::XMFLOAT4X4 model;
    DirectX::XMFLOAT4X4 view;
    DirectX::XMFLOAT4X4 projection;
    DirectX::XMFLOAT4X4 gTexTransform;
};

由於頂點結構體改變,HillModel和WaterModel裡頂點初始化程式碼需要修改。HillModel的Initialize方法裡,頂點初始化程式碼修改如下:

const float dx = 1.0f;
const float du = 1.0f/(xRange-1);
const float dv = 1.0f/(zRange-1);
 
VertexPosition *Vertices = new VertexPosition[xRange*zRange];
 
for(int row=0; row<zRange; ++row)
{
    float zPos = row*dx;
 
    for(int col=0;col<xRange; ++col)
    {
       float xPos = col*dx;
       float yPos = 0.3f *(zPos*sinf(0.1f*xPos) + xPos*cosf(0.1f*zPos));
       Vertices[xRange*row+ col].pos = XMFLOAT3(xPos, yPos, zPos);
       Vertices[xRange*row+ col].normal = GetHillNormal(xPos, zPos);
       Vertices[xRange*row+ col].tex.x = row*du;
       Vertices[xRange*row+ col].tex.y = col*dv;
    }
}

dudv可保證生成的紋理座標正好在[0,1]範圍內,目的是方便理解縮放比例,並不是必須的。WaterModel生成紋理座標的過程與上面類似,是在Update方法中進行的。

// 更新頂點緩衝區
const float du = 1.0f/xRange;
const float dv = 1.0f/zRange;
 
for(uint32 i = 0; i < m_vertexCount; ++i)
{
    vertex[i].pos =mCurrSolution[i];
    vertex[i].tex.x= (i/xRange)*du;
    vertex[i].tex.y= (i%zRange)*dv;
    vertex[i].normal= mNormal[i];
}

這裡雖然每次更新都要重新計算一樣的紋理座標,效率很低,不過鑑於規模小,方便理解就直接修改了。

完成以上工作後就可以開始載入紋理。與木箱貼圖一樣,向工程中新增WICTextureLoader的標頭檔案和原始檔。然後在Renderer類裡新增以下成員,儲存兩種紋理資源,分別代表陸地和水面。

ID3D11Resource* tex;
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_LandSRV;
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_WaterSRV;
Microsoft::WRL::ComPtr<ID3D11SamplerState> m_Sampler;

然後在CreateDeviceResources方法中載入並初始化紋理資源,初始化取樣器,當然還要修改輸入佈局:

const D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
    {
       { "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 },
       { "TEXCOOD",  0, DXGI_FORMAT_R32G32_FLOAT,    0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };
 
……
 
auto createTexTask = (createPSTask &&createVSTask).then([this] () {
    // 載入紋理資源
    DX::ThrowIfFailed(
       CreateWICTextureFromFile(
       m_d3dDevice.Get(),
       m_d3dContext.Get(),
       L"Texture/grass.jpg",
       &tex,
       m_LandSRV.GetAddressOf()
       )
       );
 
    DX::ThrowIfFailed(
       CreateWICTextureFromFile(
       m_d3dDevice.Get(),
       m_d3dContext.Get(),
       L"Texture/water.jpg",
       &tex,
       m_WaterSRV.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.MinLOD= -3.402823466e+38F; // -FLT_MAX
    samplerDesc.MaxLOD= 3.402823466e+38F; // FLT_MAX
 
    DX::ThrowIfFailed(
       m_d3dDevice->CreateSamplerState(
       &samplerDesc,
       m_Sampler.GetAddressOf()
       )
       );
});

初始化完成後,就能在Render方法設定並使用紋理資源。

// 設定紋理取樣器
m_d3dContext->PSSetSamplers(
   0,
   1,
   m_Sampler.GetAddressOf()
   );
 
// 設定陸地紋理
m_d3dContext->PSSetShaderResources(
    0,
    1,
    m_LandSRV.GetAddressOf()
    );
 
// 設定陸地紋理縮放
XMMATRIX landScale = XMMatrixScaling(5.0f, 5.0f, 0.0f);
XMStoreFloat4x4(&m_constantBufferData.gTexTransform,XMMatrixTranspose(landScale));
 
// 設定陸地材質
m_constantLightBufferData.gMaterial= m_landMat;
 
……
 
// 設定水面紋理
m_d3dContext->PSSetShaderResources(
    0,
    1,
    m_WaterSRV.GetAddressOf()
    );
 
// 設定水面紋理縮放
XMMATRIX waterScale = XMMatrixScaling(5.0f, 5.0f, 0.0f);
XMStoreFloat4x4(&m_constantBufferData.gTexTransform,XMMatrixTranspose(waterScale));
 
// 設定水面材質
m_constantLightBufferData.gMaterial = m_wavesMat;

DirectX裡包含多個生成變換矩陣的方法,如上面的XMMatrixScaling方法生成縮放變換矩陣,後面將用到的XMMatrixTranslation方法可以生成平移變換矩陣。另外注意每次設定好紋理材質後,不但要呼叫UpdateSubresource方法更新m_constantLightBuffer,還要用它更新m_constantBuffer,因為紋理縮放矩陣是ModelViewProjectionConstantBuffer的成員。

圖1載入的是grass紋理:


圖2載入的是rock紋理:


以上兩個模型中,水面紋理是不變的,而且沒有流動的效果。簡單的流動效果可以通過紋理座標隨時間偏移實現。

在Renderer類裡新增偏移量成員:

XMFLOAT2 m_waterTexOffset;

在Update方法裡新增程式碼,讓偏移量隨時間變化。

// 更新紋理偏移量
m_waterTexOffset.y += 0.1f*timeDelta;
m_waterTexOffset.x += 0.1f*timeDelta;

然後在Render方法中使用偏移量生成平移變換矩陣,並與縮放變換矩陣相乘,這樣水面紋理座標就會保持平鋪效果,並隨時間平移,實現類似流動的效果:

// 設定水面紋理縮放和偏移量
XMMATRIX waterScale = XMMatrixScaling(5.0f, 3.0f, 0.0f);
XMMATRIX waterOffset = XMMatrixTranslation(m_waterTexOffset.x,m_waterTexOffset.y, 0.0f);
XMStoreFloat4x4(&m_constantBufferData.gTexTransform,XMMatrixTranspose(waterScale*waterOffset));

 本篇文章的原始碼