Directx11學習筆記【十七】紋理貼圖


本文由zhangbaochong原創,轉載請注明出處http://www.cnblogs.com/zhangbaochong/p/5596180.html

     

      在之前的例子中,我們實現了光照和材質使得場景大大增加了真實感,然而材質提供的細節只是在頂點級別上,要想在像素級別提供細節還得借助於紋理,這次讓我們學習dx11中一些有關紋理的基礎。

1.紋理坐標

1image

       在direct3d中,紋理坐標用一個二維向量(u,v)表示,紋理左上角為原點,u正方向沿紋理水平向右,v正方向沿紋理垂直向下,且0<=u,v<=1。所以,(0,0)代表左上角,(1,1)代表右下角,(0.5,0.25)代表圖中點的位置。這樣,我們在指定紋理坐標時就不需要考慮紋理大小了,無論對於256*256,512*512還是1024*1024的紋理,(0.5,0.5)表示的都是正中間。

       如果給出了一個三角形三個頂點對應的紋理坐標,那么三角形內部的紋理坐標就需要插值來計算了。如下圖所示,一個三角形三個頂點(p1,p2,p3)對應紋理坐標(q1,q2,q3),那么三角形內部任意一點(x,y,z)=p0 + s(p1 - p0) + t(p2 - p0),那么對應的紋理坐標(u,v)=q0 + s(q1 - q0) + t(q2 - q0)。

image

2.紋理過濾

       紋理貼圖的元素實際上是由離散的顏色值組成的,而不能看做是一塊矩形區域。理想情況是一張512*512紋理恰好投影在一塊512*512大小的屏幕上,一個像素對應一個紋理值,但是通常情況並不是這樣的。當屏幕像素不能和紋理值一一對應時應該怎么做呢?這種情況也分為兩種:一種是攝像機不斷靠近紋理,這時一張很小的紋理就可能覆蓋整個屏幕,於是一個紋理值就要對應很多個像素;第二種情況恰恰相反,攝像機不斷遠離紋理圖片,當投影在屏幕的足夠小時,就有可能很多紋理值會投影在同一像素上。這種情況,就需要紋理過濾,兩種情況也對應兩種紋理過濾方式:Magnification和Minification情況。

2.1放大(Magnification)

      假設一張 256*256的紋理覆蓋了1024*1024的屏幕大小,那么這時一個紋理對應16個屏幕像素,這16個像素對應同一個紋理值,如何取得顏色呢?d3d主要有兩種過濾方式:取最近點和線性插值。

2.1.1取最近點(Nearest neighbor point)

      對於任意像素對應的紋理坐標值,取距離最近的紋理值。

     image

2.1.2線性插值(Linear interpolate)

      與坐標最近的4個紋理值進行插值得到最終結果。看下圖就很清楚了:

image

image

2.2縮小(Minification)

       在Minification情況下,多個紋理元素被投影在屏幕上同一個像素位置。比如一個1024*1024的紋理,投影在屏幕上256*256范圍的空間內,平均每個像素覆蓋16個紋理值。這種情況下,最流行的過濾方法稱為Mipmaping。在該方法中,使用到一個Mipmap鏈。Mipmap鏈是原紋理組成的一個數組,數組中第一個紋理為原始紋理,后面的第一個紋理在u、v尺寸上為上一個紋理的一半,依次計算,直接紋理尺寸為1為止。如下圖所示:

image

      這樣在運行時,硬件會選擇合適的紋理。選擇合適的紋理也有兩種方法:Point Filtering和Linear Filtering。原理與上面說的很相近就不再說明了。

2.3各向異性過濾(Anisotropic Filtering)

      還有一種高級的過濾方式稱為各向異性過濾,這種過濾方式對於視線與物體表面法線成90度時情況的效果比較好。

image

右邊圖使用了各向異性過濾方式。

3.紋理坐標尋址(Address modes)

     紋理坐標被限制在(0,1)之間,但為頂點指點(0,1)之外的紋理坐標仍然是被允許的,這時解析出來的坐標值就和紋理坐標尋址方式有關了。d3d中支持的紋理尋址方式主要有以下幾種:

3.1wrap

     貼圖在物體表面重復,類似於我們設置桌面是圖片的平鋪方式。當坐標大於1時,通過去掉整數部分,根據得到的小數部分來得到紋理值;坐標小於0,則加上一個最小正數,讓坐標大於0。

image

3.2border

     當紋理坐標不在(0,1)范圍時,我們可以手動指定一個值來使用。

image

3.3clamp

       坐標超過正常范圍時,使用在(0,1)范圍的坐標與該坐標最近的點的紋理值。例如,坐標(u,v),u在(0,1)范圍內,而v>1,則使用(u,1)作為最終紋理值。如果u>1,v>1,則使用(1,1)作為紋理值。

image

3.4mirror

      每當紋理坐標越過一個整數值時,使用的紋理與越過整數值之前的紋理成鏡像關系。

image

4.使用紋理

        d3d11中紋理對應的接口為ID3D11Texture2D,一個紋理可以在管線的多個階段使用,使用的時候要先綁定到相應階段。而真正綁定到管線的不是紋理本身,而是紋理對應的視圖。下面來介紹一下c++和HLSL中紋理的使用方式。

4.1 c++中

      在c++讀取圖片使用紋理主要有兩個步驟:創建紋理和創建相應階段的視圖。在dx11龍書中這兩個步驟是通過一個函數D3DX11CreateShaderResourceViewFromFile實現的,函數原型如下:

HRESULT D3DX11CreateShaderResourceViewFromFile(
  _In_  ID3D11Device             *pDevice,
  _In_  LPCTSTR                  pSrcFile,
  _In_  D3DX11_IMAGE_LOAD_INFO   *pLoadInfo,
  _In_  ID3DX11ThreadPump        *pPump,
  _Out_ ID3D11ShaderResourceView **ppShaderResourceView,
  _Out_ HRESULT                  *pHResult
);

      但是在最新的sdk中該方法已經廢棄了,針對這一點MSDN的解釋:

Note  The D3DX (D3DX 9, D3DX 10, and D3DX 11) utility library is deprecated for Windows 8 and is not supported for Windows Store apps.

Note  Instead of using this function, we recommend that you use these:

  • DirectXTK library (runtime), CreateXXXTextureFromFile (where XXX is DDS or WIC)
  • DirectXTex library (tools), LoadFromXXXFile (where XXX is WIC, DDS, or TGA; WIC doesn't support DDS and TGA; D3DX 9 supported TGA as a common art source format for games) then CreateShaderResourceView

     為了加載紋理圖片,我們可以到github上下載DirectXTK或者DirectXTex,使用框架中的方法來代替D3DX11CreateShaderResourceViewFromFile。這里以DirectXTex為例,從github上下載下來,把DDSTextureLoader的代碼載入到我們的工程中,使用CreateDDSTextureFromFile加載紋理圖像(紋理格式為.dds,加載其他格式紋理請自行查看文檔說明),函數原型如下:

HRESULT CreateDDSTextureFromFile( _In_ ID3D11Device* d3dDevice,
                                      _In_z_ const wchar_t* szFileName,
                                      _Outptr_opt_ ID3D11Resource** texture,
                                      _Outptr_opt_ ID3D11ShaderResourceView** textureView,
                                      _In_ size_t maxsize = 0,
                                      _Out_opt_ DDS_ALPHA_MODE* alphaMode = nullptr
                                    );

  c++程序中使用:首先定義一個 ID3D11ShaderResourceView接口保存創建的視圖, 然后調用CreateDDSTextureFromFile函數創建。

hr = CreateDDSTextureFromFile(m_pd3dDevice, L"Texture/Wood.dds", nullptr, &m_pTexSRV);
    if (FAILED(hr))
    {
        MessageBox(nullptr, L"create texture failed!", L"error",MB_OK);
        return hr;
    }

         調用后相應的視圖就創建好了。

         為了將紋理資源傳遞到effect文件中,我們還需要定義一個指向effect全局變量的接口:ID3DX11EffectShaderResourceVariable,然后在編譯shader時得到:

m_pFxTex = m_pFx->GetVariableByName("g_tex")->AsShaderResource();

 

       在Update函數中調用SetResource方法將創建的紋理視圖賦值給effect中的全局變量。

4.2 effect中    

      在HLSL中對應2D紋理的是Texture2D,但是要注意的是Texture2D的定義不能放在cbuffer中

      在c++程序中我們可以通過ID3D11DeviceContext::PSSetSamplers來設置紋理過濾方式,在effect文件中我們同樣可以設置過濾方式(推薦在effect中設置),設置方法如下:

//設置過濾方式
SamplerState samTex
{
    Filter = MIN_MAG_MIP_LINEAR;
};

  這里Minification、Magnification和Mipmap中紋理過濾方式全部使用線性過濾方法,如需要查看其它過濾方法,可以查看MSDN中D3D11_FILTER的說明。

      由於使用了紋理,頂點結構也需要改變。我們不再使用光照了,所以去掉法線,添加紋理坐標:

struct VertexIn
{
    float3 pos : POSITION;
    float2 tex : TEXCOORD;
};

struct VertexOut
{
    float4 posH  : SV_POSITION;
    float2 tex : TEXCOORD;           
};

  頂點着色器很簡單:

VertexOut VS(VertexIn vin)
{
    VertexOut vout;    
    vout.posH = mul(float4(vin.pos, 1.0f), g_worldViewProj);    
    vout.tex = vin.tex;    
    return vout;
}

  像素着色器中需要使用紋理資源,通過函數Texture2D::sample(SamplerState samper, float2 texcoord)實現:

float4 PS(VertexOut pin) : SV_Target
{
    float4 texColor = g_tex.Sample(samTex,pin.tex);
    return texColor;
}

5.運行結果

image

源碼下載:http://files.cnblogs.com/files/zhangbaochong/TextureDemo.zip


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM