寫教程到如今,我發現有關紋理資源的一些解說和應用都寫的太過度散,致使連我本身找起來都不方便。如今決定把這部分的內容整合起來,儘量作到一篇搞定全部2D紋理相關的內容,其中包括:html
DirectX11 With Windows SDK完整目錄git
Github項目源碼github
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。編程
因爲內容重複,這裏只給出跳轉連接:數組
紋理座標系網絡
過濾器app
對紋理進行採樣函數
DDS是一種圖片格式,是DirectDraw Surface的縮寫,它是DirectX紋理壓縮(DirectX Texture Compression,簡稱DXTC)的產物。由NVIDIA公司開發。大部分3D遊戲引擎均可以使用DDS格式的圖片用做貼圖,也能夠製做法線貼圖。其中dds格式支持1D紋理、2D紋理、2D紋理數組、2D紋理立方體、3D紋理,支持mipmaps,並且你還能夠進行紋理壓縮。佈局
WIC(Windows Imaging Component)是一個能夠擴展的平臺,爲數字圖像提供底層API,它能夠支持bmp、dng、ico、jpeg、png、tiff等格式的位圖的編碼與解碼。優化
在DirectXTex中打開DDSTextureLoader
文件夾和WICTextureLoader
文件夾,分別找到對應的頭文件和源文件(不帶12的),並加入到你的項目中
HRESULT CreateDDSTextureFromFile( ID3D11Device* d3dDevice, // [In]D3D設備 const wchar_t* szFileName, // [In]dds圖片文件名 ID3D11Resource** texture, // [Out]輸出一個指向資源接口類的指針,也能夠填nullptr ID3D11ShaderResourceView** textureView, // [Out]輸出一個指向着色器資源視圖的指針,也能夠填nullptr size_t maxsize = 0, // [In]限制紋理最大寬高,若超過則內部會縮放,默認0不限制 DDS_ALPHA_MODE* alphaMode = nullptr); // [In]忽略 HRESULT CreateDDSTextureFromFile( ID3D11Device* d3dDevice, // [In]D3D設備 ID3D11DeviceContext* d3dContext, // [In]D3D設備上下文 const wchar_t* szFileName, // [In]dds圖片文件名 ID3D11Resource** texture, // [Out]輸出一個指向資源接口類的指針,也能夠填nullptr ID3D11ShaderResourceView** textureView, // [Out]輸出一個指向着色器資源視圖的指針,也能夠填nullptr size_t maxsize = 0, // [In]限制紋理最大寬高,若超過則內部會縮放,默認0不限制 DDS_ALPHA_MODE* alphaMode = nullptr); // [In]忽略
第二個重載版本用於爲DDS位圖生成mipmaps,但大多數狀況下你能載入的DDS位圖自己都自帶mipmaps了,與其運行時生成,不如提早爲它製做mipmaps。
上面兩個函數都使用了這個函數,並且若是你想要更強的擴展性,就能夠了解一下:
HRESULT CreateDDSTextureFromFileEx( ID3D11Device* d3dDevice, // [In]D3D設備 const wchar_t* szFileName, // [In].dds文件名 size_t maxsize, // [In]限制紋理最大寬高,若超過則內部會縮放,默認0不限制 D3D11_USAGE usage, // [In]使用D3D11_USAGE枚舉值指定數據的CPU/GPU訪問權限 unsigned int bindFlags, // [In]使用D3D11_BIND_FLAG枚舉來決定該數據的使用類型 unsigned int cpuAccessFlags, // [In]D3D11_CPU_ACCESS_FLAG枚舉值 unsigned int miscFlags, // [In]D3D11_RESOURCE_MISC_FLAG枚舉值 bool forceSRGB, // [In]強制使用SRGB,默認false ID3D11Resource** texture, // [Out]獲取建立好的紋理(可選) ID3D11ShaderResourceView** textureView, // [Out]獲取建立好的紋理資源視圖(可選) DDS_ALPHA_MODE* alphaMode = nullptr); // [Out]忽略(可選) HRESULT CreateDDSTextureFromFileEx( ID3D11Device* d3dDevice, // [In]D3D設備 ID3D11DeviceContext* d3dContext, // [In]D3D設備上下文 const wchar_t* szFileName, // [In].dds文件名 size_t maxsize, // [In]限制紋理最大寬高,若超過則內部會縮放,默認0不限制 D3D11_USAGE usage, // [In]使用D3D11_USAGE枚舉值指定數據的CPU/GPU訪問權限 unsigned int bindFlags, // [In]使用D3D11_BIND_FLAG枚舉來決定該數據的使用類型 unsigned int cpuAccessFlags, // [In]D3D11_CPU_ACCESS_FLAG枚舉值 unsigned int miscFlags, // [In]D3D11_RESOURCE_MISC_FLAG枚舉值 bool forceSRGB, // [In]強制使用SRGB,默認false ID3D11Resource** texture, // [Out]獲取建立好的紋理(可選) ID3D11ShaderResourceView** textureView, // [Out]獲取建立好的紋理資源視圖(可選) DDS_ALPHA_MODE* alphaMode = nullptr); // [Out]忽略(可選)
這裏我只介紹簡易版本的,由於跟上面提到的函數差異只是讀取來源不同,其他參數我就再也不贅述:
HRESULT CreateDDSTextureFromMemory( ID3D11Device* d3dDevice, // [In]D3D設備 const uint8_t* ddsData, // [In]原dds文件讀取到的完整二進制流 size_t ddsDataSize, // [In]原dds文件的大小 ID3D11Resource** texture, // [Out]獲取建立好的紋理(可選) ID3D11ShaderResourceView** textureView, // [Out]獲取建立好的紋理資源視圖(可選) size_t maxsize = 0, // [In]限制紋理最大寬高,若超過則內部會縮放,默認0不限制 DDS_ALPHA_MODE* alphaMode = nullptr); // [Out]忽略(可選)
若是你須要生成mipmaps,就使用帶D3D設備上下文的重載版本。
因爲用法上和DDSTextureLoader
大同小異,我這裏也只提CreateWICTextureFromFileEx
函數:
HRESULT CreateWICTextureFromFileEx( ID3D11Device* d3dDevice, // [In]D3D設備 const wchar_t* szFileName, // [In]位圖文件名 size_t maxsize, // [In]限制紋理最大寬高,若超過則內部會縮放,默認0不限制 D3D11_USAGE usage, // [In]使用D3D11_USAGE枚舉值指定數據的CPU/GPU訪問權限 unsigned int bindFlags, // [In]使用D3D11_BIND_FLAG枚舉來決定該數據的使用類型 unsigned int cpuAccessFlags, // [In]D3D11_CPU_ACCESS_FLAG枚舉值 unsigned int miscFlags, // [In]D3D11_RESOURCE_MISC_FLAG枚舉值 unsigned int loadFlags, // [In]默認WIC_LOADER_DEAULT ID3D11Resource** texture, // [Out]獲取建立好的紋理(可選) ID3D11ShaderResourceView** textureView);// [Out]獲取建立好的紋理資源視圖(可選)
ScreenGrab
既能夠用於屏幕截屏輸出,也能夠將你在程序中製做好的紋理輸出到文件。
在DirectXTex中找到ScreenGrab
文件夾,將ScreenGrab.h
和ScreenGrab.cpp
加入到你的項目中便可使用。
但爲了能保存WIC類別的位圖,還須要包含頭文件wincodec.h
以使用裏面一些關於WIC控件的GUID。ScreenGrab
的函數位於名稱空間DirectX
內。
HRESULT SaveDDSTextureToFile( ID3D11DeviceContext* pContext, // [In]設備上下文 ID3D11Resource* pSource, // [In]必須爲包含ID3D11Texture2D接口類的指針 const wchar_t* fileName ); // [In]輸出文件名
理論上它能夠保存紋理、紋理數組、紋理立方體。
HRESULT SaveWICTextureToFile( ID3D11DeviceContext* pContext, // [In]設備上下文 ID3D11Resource* pSource, // [In]必須爲包含ID3D11Texture2D接口類的指針 REFGUID guidContainerFormat, // [In]須要轉換的圖片格式對應的GUID引用 const wchar_t* fileName, // [In]輸出文件名 const GUID* targetFormat = nullptr, // [In]忽略 std::function<void(IPropertyBag2*)> setCustomProps = nullptr ); // [In]忽略
下表給出了經常使用的GUID:
GUID | 文件格式 |
---|---|
GUID_ContainerFormatPng | png |
GUID_ContainerFormatJpeg | jpg |
GUID_ContainerFormatBmp | bmp |
GUID_ContainerFormatTiff | tif |
這裏演示瞭如何保存後備緩衝區紋理到文件:
ComPtr<ID3D11Texture2D> backBuffer; // 輸出截屏 mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf())); HR(SaveDDSTextureToFile(md3dImmediateContext.Get(), backBuffer.Get(), L"Screenshot\\output.dds")); HR(SaveWICTextureToFile(md3dImmediateContext.Get(), backBuffer.Get(), GUID_ContainerFormatPng, L"Screenshot\\output.png"));
若是輸出的dds文件打開後發現圖像質量貌似有問題,你能夠檢驗輸出紋理的alpha
值(關閉Alpha通道查看位圖一般能夠恢復正常),也能夠嘗試用DDSView程序來打開文件觀看(圖像自己可能沒有問題但程序不能完美預覽高版本產生的dds文件)。
Direct3D 11容許咱們建立1D紋理、2D紋理、3D紋理,分別對應的接口爲ID3D11Texture1D
, ID3D11Texture2D
和ID3D11Texture3D
。建立出來的對象理論上不只在內存中佔用了它的實現類所需空間,還在顯存中佔用了必定空間以存放紋理的實際數據。
因爲實際上咱們最經常使用到的就是2D紋理,所以這裏不會討論1D紋理和3D紋理的內容。
首先讓咱們看看D3D11對一個2D紋理的描述:
typedef struct D3D11_TEXTURE2D_DESC { UINT Width; // 紋理寬度 UINT Height; // 紋理高度 UINT MipLevels; // 容許的Mip等級數 UINT ArraySize; // 能夠用於建立紋理數組,這裏指定紋理的數目,單個紋理使用1 DXGI_FORMAT Format; // DXGI支持的數據格式,默認DXGI_FORMAT_R8G8B8A8_UNORM DXGI_SAMPLE_DESC SampleDesc; // MSAA描述 D3D11_USAGE Usage; // 使用D3D11_USAGE枚舉值指定數據的CPU/GPU訪問權限 UINT BindFlags; // 使用D3D11_BIND_FLAG枚舉來決定該數據的使用類型 UINT CPUAccessFlags; // 使用D3D11_CPU_ACCESS_FLAG枚舉來決定CPU訪問權限 UINT MiscFlags; // 使用D3D11_RESOURCE_MISC_FLAG枚舉 } D3D11_TEXTURE2D_DESC; typedef struct DXGI_SAMPLE_DESC { UINT Count; // MSAA採樣數 UINT Quality; // MSAA質量等級 } DXGI_SAMPLE_DESC;
這裏特別要講一下MipLevels
:
ID3D11Texture2D::GetDesc
來查看實際的MipLevels
值是多少400x400
,mip等級爲3時,該紋理會產生400x400
,200x200
和100x100
的mipmap對於常常做爲着色器資源的紋理,一般是不能對其開啓MSAA的,應當把Count
設爲1,Quality
設爲0
緊接着是DXGI_FORMAT
:
它用於指定紋理存儲的數據格式,最經常使用的就是DXGI_FORMAT_R8G8B8A8_UNORM
了。這種格式在內存的排布能夠用下面的結構體表示:
struct { uint8_t r; uint8_t g; uint8_t b; uint8_t a; };
瞭解這個對咱們後期經過內存填充紋理十分重要。
而後是Usage
:
D3D11_USAGE | CPU讀 | CPU寫 | GPU讀 | GPU寫 |
---|---|---|---|---|
D3D11_USAGE_DEFAULT | √ | √ | ||
D3D11_USAGE_IMMUTABLE | √ | |||
D3D11_USAGE_DYNAMIC | √ | √ | ||
D3D11_USAGE_STAGING | √ | √ | √ | √ |
若是一個紋理以D3D11_USAGE_DEFAULT
的方式建立,那麼它可使用下面的這些方法來更新紋理:
ID3D11DeviceContext::UpdateSubresource
ID3D11DeviceContext::CopyResource
ID3D11DeviceContext::CopySubresourceRegion
經過DDSTextureLoader
或WICTextureLoader
建立出來的紋理默認都是這種類型
而若是一個紋理以D3D11_USAGE_IMMUTABLE
的方式建立,則必須在建立階段就完成紋理資源的初始化。此後GPU只能讀取,也沒法對紋理再進行修改
D3D11_USAGE_DYNAMIC
建立的紋理一般須要頻繁從CPU寫入,使用ID3D11DeviceContext::Map
方法將顯存映射回內存,通過修改後再調用ID3D11DeviceContext::UnMap
方法應用更改。並且它對紋理有諸多的要求,直接從下面的ERROR能夠看到:
D3D11 ERROR: ID3D11Device::CreateTexture2D: A D3D11_USAGE_DYNAMIC Resource must have ArraySize equal to 1. [ STATE_CREATION ERROR #101: CREATETEXTURE2D_INVALIDDIMENSIONS]
D3D11 ERROR: ID3D11Device::CreateTexture2D: A D3D11_USAGE_DYNAMIC Resource must have MipLevels equal to 1. [ STATE_CREATION ERROR #102: CREATETEXTURE2D_INVALIDMIPLEVELS]
上面說到,紋理只能是單個,不能是數組,且mip等級只能是1,即不能有mipmaps
而D3D11_USAGE_STAGING
則徹底容許在CPU和GPU之間的數據傳輸,但它只能做爲一個相似中轉站的資源,而不能綁定到渲染管線上,即你也不能用該紋理生成mipmaps。好比說有一個D3D11_USAGE_DEFAULT
你想要從顯存拿到內存,只能經過它以ID3D11DeviceContext::CopyResource
或者ID3D11DeviceContext::CopySubresourceRegion
方法來複制一份到本紋理,而後再經過ID3D11DeviceContext::Map
方法取出到內存。
如今來到BindFlags
:
如下是和紋理有關的D3D11_BIND_FLAG
枚舉成員:
D3D11_BIND_FLAG | 描述 |
---|---|
D3D11_BIND_SHADER_RESOURCE | 紋理能夠做爲着色器資源綁定到渲染管線 |
D3D11_BIND_STREAM_OUTPUT | 紋理能夠做爲流輸出階段的輸出點 |
D3D11_BIND_RENDER_TARGET | 紋理能夠做爲渲染目標的輸出點,而且指定它能夠用於生成mipmaps |
D3D11_BIND_DEPTH_STENCIL | 紋理能夠做爲深度/模板緩衝區 |
D3D11_BIND_UNORDERED_ACCESS | 紋理能夠綁定到無序訪問視圖做爲輸出 |
再看看CPUAccessFlags
:
D3D11_CPU_ACCESS_FLAG | 描述 |
---|---|
D3D11_CPU_ACCESS_WRITE | 容許經過映射方式從CPU寫入,它不能做爲管線的輸出,且只能用於D3D11_USAGE_DYNAMIC 和D3D11_USAGE_STAGING 綁定的資源 |
D3D11_CPU_ACCESS_READ | 容許經過映射方式給CPU讀取,它不能做爲管線的輸出,且只能用於D3D11_USAGE_STAGING 綁定的資源 |
能夠用按位或的方式同時指定上述枚舉值,若是該flag設爲0能夠得到更好的資源優化操做。
最後是和紋理相關的MiscFlags
:
D3D11_RESOURCE_MISC_FLAG | 描述 |
---|---|
D3D11_RESOURCE_MISC_GENERATE_MIPS | 容許經過ID3D11DeviceContext::GenerateMips 方法生成mipmaps |
D3D11_RESOURCE_MISC_TEXTURECUBE | 容許該紋理做爲紋理立方體使用,要求必須是至少包含6個紋理的Texture2DArray |
填充好D3D11_TEXTURE2D_DESC
後,你才能夠用它建立一個2D紋理:
HRESULT ID3D11Device::CreateTexture2D( const D3D11_TEXTURE2D_DESC *pDesc, // [In] 2D紋理描述信息 const D3D11_SUBRESOURCE_DATA *pInitialData, // [In] 用於初始化的資源 ID3D11Texture2D **ppTexture2D); // [Out] 獲取到的2D紋理
過程我就不演示了。
建立好紋理後,咱們還須要讓它綁定到資源視圖,而後再讓該資源視圖綁定到渲染管線的指定階段。
D3D11_SHADER_RESOURCE_VIEW_DESC
的定義以下:
typedef struct D3D11_SHADER_RESOURCE_VIEW_DESC { DXGI_FORMAT Format; D3D11_SRV_DIMENSION ViewDimension; union { D3D11_BUFFER_SRV Buffer; D3D11_TEX1D_SRV Texture1D; D3D11_TEX1D_ARRAY_SRV Texture1DArray; D3D11_TEX2D_SRV Texture2D; D3D11_TEX2D_ARRAY_SRV Texture2DArray; D3D11_TEX2DMS_SRV Texture2DMS; D3D11_TEX2DMS_ARRAY_SRV Texture2DMSArray; D3D11_TEX3D_SRV Texture3D; D3D11_TEXCUBE_SRV TextureCube; D3D11_TEXCUBE_ARRAY_SRV TextureCubeArray; D3D11_BUFFEREX_SRV BufferEx; } ; } D3D11_SHADER_RESOURCE_VIEW_DESC; };
其中Format
要和紋理建立時的Format
一致,對於2D紋理來講,應當指定D3D11_SRV_DIMENSION
爲D3D11_SRV_DIMENSION_TEXTURE2D
。
而後D3D11_TEX2D_SRV
結構體定義以下:
typedef struct D3D11_TEX2D_SRV { UINT MostDetailedMip; UINT MipLevels; } D3D11_TEX2D_SRV;
經過MostDetailedMap
咱們能夠指定開始使用的紋理子資源,MipLevels
則指定使用的子資源數目。若是要使用完整mipmaps,則須要指定MostDetailedMap
爲0, MipLevels
爲-1.
例如我想像下圖那樣使用mip等級爲1到2的紋理子資源,能夠指定MostDetailedMip
爲1,MipLevels
爲2.
建立着色器資源視圖的演示以下:
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = 1; srvDesc.Texture2D.MostDetailedMip = 0; HR(md3dDevice->CreateShaderResourceView(tex.Get(), &srvDesc, texSRV.GetAddressOf()));
咱們建立着色器資源的目的就是以它做爲媒介,傳遞給着色器使用。上面打*意味着渲染管線的全部可編程着色器階段都有該方法。
此外,着色器資源視圖不只能夠綁定紋理資源,還能夠綁定緩衝區資源。
目前在DDSTextureLoader
和WICTextureLoader
中,咱們只須要用到紋理的着色器資源。這裏以ID3D11DeviceContext::PSSetShaderResources
爲例:
void ID3D11DeviceContext::PSSetShaderResources( UINT StartSlot, // [In]起始槽索引,對應HLSL的register(t*) UINT NumViews, // [In]着色器資源視圖數目 ID3D11ShaderResourceView * const *ppShaderResourceViews // [In]着色器資源視圖數組 );
一般咱們將包含mipmaps的紋理稱做紋理,那麼紋理子資源實際上指的就是其中的一個mip等級對應的2維數組(針對2維紋理來講)。好比512x512的紋理加載進來包含的mipmap等級數(Mipmap Levels)爲10,包含了從512x512, 256x256, 128x128...到1x1的10個二維數組顏色數據,這十個紋理子資源在紋理中的內存是緊湊的,沒有內存填充。
例如:上述紋理(R8G8B8A8格式) mip等級爲1的紋理子資源首元素地址 爲 從mip等級爲0的紋理子資源首元素地址再偏移512x512x4字節的地址。
Direct3D API使用Mip切片(Mip slice)來指定某一mip等級的紋理子資源,也有點像索引。好比mip slice值爲0時,對應的是512x512的紋理,而mip slice值1對應的是256x256,以此類推。
若是你想要爲2D紋理進行初始化,那麼你要接觸到的結構體類型爲D3D11_SUBRESOURCE_DATA
。定義以下:
typedef struct D3D11_SUBRESOURCE_DATA { const void *pSysMem; // 用於初始化的數據 UINT SysMemPitch; // 當前子資源一行所佔的字節數(2D/3D紋理使用) UINT SysMemSlicePitch; // 當前子資源一個完整切片所佔的字節數(僅3D紋理使用) } D3D11_SUBRESOURCE_DATA;
而若是你使用的是ID3D11DeviceContext::Map
方法來獲取一個紋理子資源,那麼獲取到的是D3D11_MAPPED_SUBRESOURCE
,其定義以下:
typedef struct D3D11_MAPPED_SUBRESOURCE { void *pData; // 映射到內存的數據or須要提交的地址範圍 UINT RowPitch; // 當前子資源一行所佔的字節數(2D/3D紋理有意義) UINT DepthPitch; // 當前子資源一個完整切片所佔的字節數(僅3D紋理有意義) } D3D11_MAPPED_SUBRESOURCE;
若一張512x512的紋理(R8G8B8A8),那麼它的RowPitch
爲512x4=2048字節,同理在初始化一個512x512的紋理(R8G8B8A8),它的RowPitch
有可能爲512x4=2048字節。
注意:在運行的時候,
RowPitch
和DepthPitch
有可能會比你所指望的值更大一些,由於在每一行的數據之間有可能會填充數據進去以對齊。
一般狀況下咱們但願讀出來的RGBA是連續的,然而下述映射回內存的作法是錯誤的,由於每一行的數據都有填充,讀出來的話你可能會發現圖像會有錯位:
std::vector<unsigned char> imageData; m_pd3dImmediateContext->Map(texOutputCopy.Get(), 0, D3D11_MAP_READ, 0, &mappedData); memcpy_s(imageData.data(), texWidth * texHeight * 4, mappedData.pData, texWidth * texHeight * 4); m_pd3dImmediateContext->Unmap(texOutputCopy.Get(), 0);
下面的讀取方式纔是正確的:
std::vector<unsigned char> imageData; m_pd3dImmediateContext->Map(texOutputCopy.Get(), 0, D3D11_MAP_READ, 0, &mappedData); unsigned char* pData = reinterpret_cast<unsigned char*>(mappedData.pData); for (UINT i = 0; i < texHeight; ++i) { memcpy_s(&imageData[i * texWidth], texWidth * 4, pData, texWidth * 4); pData += mappedData.RowPitch; } pImpl->d3dContext->Unmap(texOutputCopy.Get(), 0);
一般這種資源的類型有多是D3D11_USAGE_IMMUTABLE
或者D3D11_USAGE_DEFAULT
。咱們須要按下面的步驟進行:
D3D11_USAGE_STAGING
的紋理,指定CPU讀取權限,紋理寬高一致,Mip等級和數組大小都爲1;ID3D11DeviceContext::CopyResource
方法拷貝一份到咱們新建立的紋理,注意須要嚴格按照上面提到的讀取方式進行讀取,最後解除映射。該方法經過GPU將一份完整的源資源複製到目標資源:
void ID3D11DeviceContext::CopyResource( ID3D11Resource *pDstResource, // [InOut]目標資源 ID3D11Resource *pSrcResource // [In]源資源 );
可是須要注意:
D3D11_USAGE_IMMUTABLE
建立的目標資源DXGI_FORMAT_R32G32B32_FLOAT
,DXGI_FORMAT_R32G32B32_UINT
和DXGI_FORMAT_R32G32B32_TYPELESS
相互間就能夠複製。ID3D11DeviceContext::Map
方法又沒有Unmap
)如今咱們嘗試經過代碼的形式來建立一個紋理(以項目09做爲修改),代碼以下:
uint32_t ColorRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return (r | (g << 8) | (b << 16) | (a << 24)); } bool GameApp::InitResource() { uint32_t black = ColorRGBA(0, 0, 0, 255), orange = ColorRGBA(255, 108, 0, 255); // 紋理內存映射,用黑色初始化 std::vector<uint32_t> textureMap(128 * 128, black); uint32_t(*textureMap)[128] = reinterpret_cast<uint32_t(*)[128]>(textureArrayMap.data()); for (int y = 7; y <= 17; ++y) for (int x = 25 - y; x <= 102 + y; ++x) textureMap[y][x] = textureMap[127 - y][x] = orange; for (int y = 18; y <= 109; ++y) for (int x = 7; x <= 120; ++x) textureMap[y][x] = orange; // 建立紋理 D3D11_TEXTURE2D_DESC texDesc; texDesc.Width = 128; texDesc.Height = 128; texDesc.MipLevels = 1; texDesc.ArraySize = 1; texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; texDesc.SampleDesc.Count = 1; // 不使用多重採樣 texDesc.SampleDesc.Quality = 0; texDesc.Usage = D3D11_USAGE_DEFAULT; texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; texDesc.CPUAccessFlags = 0; texDesc.MiscFlags = 0; // 指定須要生成mipmap D3D11_SUBRESOURCE_DATA sd; uint32_t * pData = textureMap.data(); sd.pSysMem = pData; sd.SysMemPitch = 128 * sizeof(uint32_t); sd.SysMemSlicePitch = 128 * 128 * sizeof(uint32_t); ComPtr<ID3D11Texture2D> tex; HR(m_pd3dDevice->CreateTexture2D(&texDesc, &sd, tex.GetAddressOf())); D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = 1; srvDesc.Texture2D.MostDetailedMip = 0; HR(m_pd3dDevice->CreateShaderResourceView(tex.Get(), &srvDesc, m_pTexSRV.GetAddressOf())); // ... }
其它部分的代碼修改就不講了,最終效果以下:
可是若是你想要以初始化的方式來建立帶mipmap的Texture2D
紋理,則在初始化的時候須要提供D3D11_SUBRESOURCE_DATA
數組,元素數目爲MipLevels
.
再或者若是你是要以初始化的方式來建立帶mipmap的Texture2D
紋理數組,則提供的元素數目爲MipLevels * ArraySize
.
以前提到,D3D11_TEXTURE2D_DESC
中能夠經過指定ArraySize
的值來將其建立爲紋理數組。
首先來到HLSL代碼,咱們之因此不使用下面的這種形式建立紋理數組:
Texture2D gTexArray[7] : register(t0); // 像素着色器 float4 PS(VertexPosHTex pIn) : SV_Target { float4 texColor = gTexArray[gTexIndex].Sample(gSam, float2(pIn.Tex)); return texColor; }
是由於這樣作的話HLSL編譯器會報錯:sampler array index must be a literal experssion,即pin.PrimID的值也必須是個字面值,而不是變量。但咱們仍是想要可以根據變量取對應紋理的能力。
正確的作法應當是聲明一個Texture2DArray
的數組:
Texture2DArray gTexArray : register(t0);
Texture2DArray
一樣也具備Sample
方法,用法示例以下:
// 像素着色器 float4 PS(VertexPosHTex pIn) : SV_Target { float4 texColor = gTexArray.Sample(gSam, float3(pIn.Tex, gTexIndex)); return texColor; }
Sample方法的第一個參數依然是採樣器
而第二個參數則是一個3D向量,其中x與y的值對應的仍是紋理座標,而z份量即使是個float
,主要是用於做爲索引值選取紋理數組中的某一個具體紋理。同理索引值0對應紋理數組的第一張紋理,1對應的是第二張紋理等等...
使用紋理數組的優點是,咱們能夠一次性預先建立好全部須要用到的紋理,並綁定到HLSL的紋理數組中,而不須要每次都從新綁定一個紋理。而後咱們再使用索引值來訪問紋理數組中的某一紋理。
對於紋理數組,每一個元素都會包含一樣的mip等級數。Direct3D API使用數組切片(array slice)來訪問不一樣紋理,也是至關於索引。這樣咱們就能夠把全部的紋理資源用下面的圖來表示,假定下圖有4個紋理,每一個紋理包含3個子資源,則當前指定的是Array Slice爲2,Mip Slice爲1的子資源。
而後給定當前紋理數組每一個紋理的mipmap等級數(Mipmap Levels),數組切片(Array Slice)和Mip切片(Mip Slice),咱們就能夠用下面的函數來求得指定子資源的索引值:
inline UINT D3D11CalcSubresource(UINT MipSlice, UINT ArraySlice, UINT MipLevels ) { return MipSlice + ArraySlice * MipLevels; }
如今咱們手頭上僅有的就是DDSTextureLoader.h
和WICTextureLoader.h
中的函數,但這裏面的函數每次都只能加載一張紋理。咱們還須要修改龍書樣例中讀取紋理的函數,具體的操做順序以下:
ID3D11Texture2D
對象,這裏的每一個對象單獨包含一張紋理;ID3D11Texture2D
對象,它同時也是一個紋理數組;爲了不出現一些問題,這裏實現的紋理數組加載的函數只考慮寬度和高度、數據格式、mip等級都一致的狀況。
在d3dUtil.h
中實現了這樣兩個函數:
// ------------------------------ // CreateDDSTexture2DArrayFromFile函數 // ------------------------------ // 該函數要求全部的dds紋理的寬高、數據格式、mip等級一致 // [In]d3dDevice D3D設備 // [In]d3dDeviceContext D3D設備上下文 // [In]fileNames dds文件名數組 // [OutOpt]textureArray 輸出的紋理數組資源 // [OutOpt]textureArrayView 輸出的紋理數組資源視圖 // [In]generateMips 是否生成mipmaps HRESULT CreateDDSTexture2DArrayFromFile( ID3D11Device * d3dDevice, ID3D11DeviceContext * d3dDeviceContext, const std::vector<std::wstring>& fileNames, ID3D11Texture2D** textureArray, ID3D11ShaderResourceView** textureArrayView, bool generateMips = false); // ------------------------------ // CreateWICTexture2DArrayFromFile函數 // ------------------------------ // 該函數要求全部的dds紋理的寬高、數據格式、mip等級一致 // [In]d3dDevice D3D設備 // [In]d3dDeviceContext D3D設備上下文 // [In]fileNames dds文件名數組 // [OutOpt]textureArray 輸出的紋理數組資源 // [OutOpt]textureArrayView 輸出的紋理數組資源視圖 // [In]generateMips 是否生成mipmaps HRESULT CreateWICTexture2DArrayFromFile( ID3D11Device * d3dDevice, ID3D11DeviceContext * d3dDeviceContext, const std::vector<std::wstring>& fileNames, ID3D11Texture2D** textureArray, ID3D11ShaderResourceView** textureArrayView, bool generateMips = false);
還有就是d3dUtil.cpp
用到的函數CreateTexture2DArray
第一步是紋理的加載,這裏`CreateDDSTexture2DArrayFromFile函數的實現以下:
HRESULT CreateDDSTexture2DArrayFromFile( ID3D11Device * d3dDevice, ID3D11DeviceContext * d3dDeviceContext, const std::vector<std::wstring>& fileNames, ID3D11Texture2D** textureArray, ID3D11ShaderResourceView** textureArrayView, bool generateMips) { // 檢查設備、着色器資源視圖、文件名數組是否非空 if (!d3dDevice || !textureArrayView || fileNames.empty()) return E_INVALIDARG; HRESULT hResult; // ****************** // 讀取全部紋理 // UINT arraySize = (UINT)fileNames.size(); std::vector<ID3D11Texture2D*> srcTexVec(arraySize); std::vector<D3D11_TEXTURE2D_DESC> texDescVec(arraySize); for (UINT i = 0; i < arraySize; ++i) { // 因爲這些紋理並不會被GPU使用,咱們使用D3D11_USAGE_STAGING枚舉值 // 使得CPU能夠讀取資源 hResult = CreateDDSTextureFromFileEx(d3dDevice, fileNames[i].c_str(), 0, D3D11_USAGE_STAGING, 0, D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ, 0, false, (ID3D11Resource**)&srcTexVec[i], nullptr); // 讀取失敗則釋放以前讀取的紋理並返回 if (FAILED(hResult)) { for (UINT j = 0; j < i; ++j) SAFE_RELEASE(srcTexVec[j]); return hResult; } // 讀取建立好的紋理信息 srcTexVec[i]->GetDesc(&texDescVec[i]); // 須要檢驗全部紋理的mipLevels,寬度和高度,數據格式是否一致, // 若存在數據格式不一致的狀況,請使用dxtex.exe(DirectX Texture Tool) // 將全部的圖片轉成一致的數據格式 if (texDescVec[i].MipLevels != texDescVec[0].MipLevels || texDescVec[i].Width != texDescVec[0].Width || texDescVec[i].Height != texDescVec[0].Height || texDescVec[i].Format != texDescVec[0].Format) { for (UINT j = 0; j < i; ++j) SAFE_RELEASE(srcTexVec[j]); return E_FAIL; } } hResult = CreateTexture2DArray(d3dDevice, d3dDeviceContext, srcTexVec, D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE | (generateMips ? D3D11_BIND_RENDER_TARGET : 0), 0, (generateMips ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0), textureArray, textureArrayView); for (UINT i = 0; i < arraySize; ++i) SAFE_RELEASE(srcTexVec[i]); return hResult; }
而WIC版的區別僅在於把CreateDDSTextureFromFileEx
替換爲CreateWICTextureFromFileEx
:
hResult = CreateWICTextureFromFileEx(d3dDevice, fileNames[i].c_str(), 0, D3D11_USAGE_STAGING, 0, D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ, 0, WIC_LOADER_DEFAULT, (ID3D11Resource**)&srcTexVec[i], nullptr);
因爲咱們給紋理設置的是D3D11_USAGE_STAGING
,它沒法綁定到渲染管線上生成mipmaps,若是讀進來的是dds紋理,它可能自己就自帶mipmaps,也可能沒有。因此建立mipmap的操做得在後續建立的紋理數組來完成。
在瞭解CreateTexture2DArray
函數的實現前,你須要下面這些內容:
HRESULT ID3D11DeviceContext::Map( ID3D11Resource *pResource, // [In]包含ID3D11Resource接口的資源對象 UINT Subresource, // [In]子資源索引 D3D11_MAP MapType, // [In]D3D11_MAP枚舉值,指定讀寫相關操做 UINT MapFlags, // [In]填0,忽略 D3D11_MAPPED_SUBRESOURCE *pMappedResource // [Out]獲取到的已經映射到內存的子資源 );
D3D11_MAP枚舉值類型的成員以下:
D3D11_MAP成員 | 含義 |
---|---|
D3D11_MAP_READ | 映射到內存的資源用於讀取。該資源在建立的時候必須綁定了 D3D11_CPU_ACCESS_READ標籤 |
D3D11_MAP_WRITE | 映射到內存的資源用於寫入。該資源在建立的時候必須綁定了 D3D11_CPU_ACCESS_WRITE標籤 |
D3D11_MAP_READ_WRITE | 映射到內存的資源用於讀寫。該資源在建立的時候必須綁定了 D3D11_CPU_ACCESS_READ和D3D11_CPU_ACCESS_WRITE標籤 |
D3D11_MAP_WRITE_DISCARD | 映射到內存的資源用於寫入,以前的資源數據將會被拋棄。該 資源在建立的時候必須綁定了D3D11_CPU_ACCESS_WRITE和 D3D11_USAGE_DYNAMIC標籤 |
D3D11_MAP_WRITE_NO_OVERWRITE | 映射到內存的資源用於寫入,但不能複寫已經存在的資源。 該枚舉值只能用於頂點/索引緩衝區。該資源在建立的時候須要 有D3D11_CPU_ACCESS_WRITE標籤,在Direct3D 11不能用於 設置了D3D11_BIND_CONSTANT_BUFFER標籤的資源,但在 11.1後能夠。具體能夠查閱MSDN文檔 |
這個函數在以前咱們主要是用來將內存數據拷貝到常量緩衝區中,如今咱們也能夠用它將內存數據拷貝到紋理的子資源當中:
void ID3D11DeviceContext::UpdateSubresource( ID3D11Resource *pDstResource, // [In]目標資源對象 UINT DstSubresource, // [In]對於2D紋理來講,該參數爲指定Mip等級的子資源 const D3D11_BOX *pDstBox, // [In]這裏一般填nullptr,或者拷貝的數據寬高比當前子資源小時能夠指定範圍 const void *pSrcData, // [In]用於拷貝的內存數據 UINT SrcRowPitch, // [In]該2D紋理的 寬度*數據格式的位數 UINT SrcDepthPitch // [In]對於2D紋理來講並不須要用到該參數,所以能夠任意設置 );
void ID3D11DeviceContext::Unmap( ID3D11Resource *pResource, // [In]包含ID3D11Resource接口的資源對象 UINT Subresource // [In]須要取消的子資源索引 );
在建立着色器目標視圖時,你還須要填充共用體中的D3D11_TEX2D_ARRAY_SRV
結構體:
typedef struct D3D11_TEX2D_ARRAY_SRV { UINT MostDetailedMip; UINT MipLevels; UINT FirstArraySlice; UINT ArraySize; } D3D11_TEX2D_ARRAY_SRV;
經過FirstArraySlice
咱們能夠指定開始使用的紋理,ArraySize
則指定使用的紋理數目。
例如我想指定像上面那樣的範圍,能夠指定FirstArraySlice
爲1,ArraySize
爲2,MostDetailedMip
爲1,MipLevels
爲2.
因爲經過該函數讀取進來的紋理mip等級可能只有1,若是還須要建立mipmap鏈的話,還須要用到下面的方法。
void ID3D11DeviceContext::GenerateMips( ID3D11ShaderResourceView *pShaderResourceView // [In]須要建立mipamp鏈的SRV );
好比一張1024x1024的紋理,通過該方法調用後,就會生成剩餘的512x512, 256x256 ... 1x1的子紋理資源,加起來一共是11級mipmap。
在調用該方法以前,須要作好大量的準備:
Usage
爲D3D11_USAGE_DEFAULT
以容許GPU寫入,BindFlags
要綁定D3D11_BIND_RENDER_TARGET
和D3D11_BIND_SHADER_RESOURCE
,MiscFlags
設置D3D11_RESOURCE_MISC_GENERATE_MIPS
枚舉值,mipLevels
設置爲0使得在建立紋理的時候會自動預留出其他mipLevel所須要用到的內存大小。D3D11CalcSubresource
爲全部紋理元素的首mipLevel來填充圖片。MostDetailedMip
爲0,MipLevels
爲-1以訪問完整mipmaps。作好這些準備後你才能夠調用GenerateMips
,不然可能會產生異常。
最終CreateTexture2DArray
的實現以下:
HRESULT CreateTexture2DArray( ID3D11Device * d3dDevice, ID3D11DeviceContext * d3dDeviceContext, std::vector<ID3D11Texture2D*>& srcTexVec, D3D11_USAGE usage, UINT bindFlags, UINT cpuAccessFlags, UINT miscFlags, ID3D11Texture2D** textureArray, ID3D11ShaderResourceView** textureArrayView) { if (!textureArray && !textureArrayView || !d3dDevice || !d3dDeviceContext || srcTexVec.empty()) return E_INVALIDARG; HRESULT hResult; UINT arraySize = (UINT)srcTexVec.size(); bool generateMips = (bindFlags & D3D11_BIND_RENDER_TARGET) && (miscFlags & D3D11_RESOURCE_MISC_GENERATE_MIPS); // ****************** // 建立紋理數組 // D3D11_TEXTURE2D_DESC texDesc; srcTexVec[0]->GetDesc(&texDesc); D3D11_TEXTURE2D_DESC texArrayDesc; texArrayDesc.Width = texDesc.Width; texArrayDesc.Height = texDesc.Height; texArrayDesc.MipLevels = generateMips ? 0 : texDesc.MipLevels; texArrayDesc.ArraySize = arraySize; texArrayDesc.Format = texDesc.Format; texArrayDesc.SampleDesc.Count = 1; // 不能使用多重採樣 texArrayDesc.SampleDesc.Quality = 0; texArrayDesc.Usage = usage; texArrayDesc.BindFlags = bindFlags; texArrayDesc.CPUAccessFlags = cpuAccessFlags; texArrayDesc.MiscFlags = miscFlags; ID3D11Texture2D* texArray = nullptr; hResult = d3dDevice->CreateTexture2D(&texArrayDesc, nullptr, &texArray); if (FAILED(hResult)) { return hResult; } texArray->GetDesc(&texArrayDesc); // ****************** // 將全部的紋理子資源賦值到紋理數組中 // UINT minMipLevels = (generateMips ? 1 : texArrayDesc.MipLevels); // 每一個紋理元素 for (UINT i = 0; i < texArrayDesc.ArraySize; ++i) { // 紋理中的每一個mipmap等級 for (UINT j = 0; j < minMipLevels; ++j) { D3D11_MAPPED_SUBRESOURCE mappedTex2D; // 容許映射索引i紋理中,索引j的mipmap等級的2D紋理 d3dDeviceContext->Map(srcTexVec[i], j, D3D11_MAP_READ, 0, &mappedTex2D); d3dDeviceContext->UpdateSubresource( texArray, D3D11CalcSubresource(j, i, texArrayDesc.MipLevels), // i * mipLevel + j nullptr, mappedTex2D.pData, mappedTex2D.RowPitch, mappedTex2D.DepthPitch); // 中止映射 d3dDeviceContext->Unmap(srcTexVec[i], j); } } // ****************** // 建立紋理數組的SRV // if (textureArrayView) { D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc; viewDesc.Format = texArrayDesc.Format; viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY; viewDesc.Texture2DArray.MostDetailedMip = 0; viewDesc.Texture2DArray.MipLevels = texArrayDesc.MipLevels; viewDesc.Texture2DArray.FirstArraySlice = 0; viewDesc.Texture2DArray.ArraySize = arraySize; hResult = d3dDevice->CreateShaderResourceView(texArray, &viewDesc, textureArrayView); // 生成mipmaps if (hResult == S_OK && generateMips) { d3dDeviceContext->GenerateMips(*textureArrayView); } } // 檢查是否須要紋理數組 if (textureArray) { *textureArray = texArray; } else { SAFE_RELEASE(texArray); } return hResult; }
2D紋理立方體的其實是在以2D紋理數組資源的基礎上建立出來的着色器紋理資源視圖,經過視圖指定哪6個連續的紋理做爲紋理立方體。這也意味着你能夠在一個2D紋理數組上建立多個紋理立方體。
Direct3D提供了枚舉類型D3D11_TEXTURECUBE_FACE
來標識立方體某一表面:
typedef enum D3D11_TEXTURECUBE_FACE { D3D11_TEXTURECUBE_FACE_POSITIVE_X = 0, D3D11_TEXTURECUBE_FACE_NEGATIVE_X = 1, D3D11_TEXTURECUBE_FACE_POSITIVE_Y = 2, D3D11_TEXTURECUBE_FACE_NEGATIVE_Y = 3, D3D11_TEXTURECUBE_FACE_POSITIVE_Z = 4, D3D11_TEXTURECUBE_FACE_NEGATIVE_Z = 5 } D3D11_TEXTURECUBE_FACE;
能夠看出:
使用立方體映射意味着咱們須要使用3D紋理座標進行尋址,經過向量的形式來指定使用立方體某個表面的其中一點。
在HLSL中,立方體紋理用TextureCube
來表示。
對於建立好的DDS立方體紋理,咱們只須要使用DDSTextureLoader
就能夠很方便地讀取進來:
HR(CreateDDSTextureFromFile( device.Get(), cubemapFilename.c_str(), nullptr, textureCubeSRV.GetAddressOf()));
然而從網絡上可以下到的天空盒資源常常要麼是一張天空盒貼圖,要麼是六張天空盒的正方形貼圖,用DXTex導入仍是比較麻煩的一件事情。咱們也能夠本身編寫代碼來構造立方體紋理。
將一張天空盒貼圖轉化成立方體紋理須要經歷如下4個步驟:
而將六張天空盒的正方形貼圖轉換成立方體須要經歷這4個步驟:
能夠看到這兩種類型的天空盒資源在處理上有不少類似的地方。
// ------------------------------ // CreateWICTexture2DCubeFromFile函數 // ------------------------------ // 根據給定的一張包含立方體六個面的位圖,建立紋理立方體 // 要求紋理寬高比爲4:3,且按下面形式佈局: // . +Y . . // -X +Z +X -Z // . -Y . . // [In]d3dDevice D3D設備 // [In]d3dDeviceContext D3D設備上下文 // [In]cubeMapFileName 位圖文件名 // [OutOpt]textureArray 輸出的紋理數組資源 // [OutOpt]textureCubeView 輸出的紋理立方體資源視圖 // [In]generateMips 是否生成mipmaps HRESULT CreateWICTexture2DCubeFromFile( ID3D11Device * d3dDevice, ID3D11DeviceContext * d3dDeviceContext, const std::wstring& cubeMapFileName, ID3D11Texture2D** textureArray, ID3D11ShaderResourceView** textureCubeView, bool generateMips = false); // ------------------------------ // CreateWICTexture2DCubeFromFile函數 // ------------------------------ // 根據按D3D11_TEXTURECUBE_FACE索引順序給定的六張紋理,建立紋理立方體 // 要求位圖是一樣寬高、數據格式的正方形 // 你也能夠給定超過6張的紋理,而後在獲取到紋理數組的基礎上自行建立更多的資源視圖 // [In]d3dDevice D3D設備 // [In]d3dDeviceContext D3D設備上下文 // [In]cubeMapFileNames 位圖文件名數組 // [OutOpt]textureArray 輸出的紋理數組資源 // [OutOpt]textureCubeView 輸出的紋理立方體資源視圖 // [In]generateMips 是否生成mipmaps HRESULT CreateWICTexture2DCubeFromFile( ID3D11Device * d3dDevice, ID3D11DeviceContext * d3dDeviceContext, const std::vector<std::wstring>& cubeMapFileNames, ID3D11Texture2D** textureArray, ID3D11ShaderResourceView** textureCubeView, bool generateMips = false);
如今咱們要將位圖讀進來,這是讀取一張完成天空盒的實現版本的開頭
HRESULT CreateWICTexture2DCubeFromFile( ID3D11Device * d3dDevice, ID3D11DeviceContext * d3dDeviceContext, const std::wstring & cubeMapFileName, ID3D11Texture2D ** textureArray, ID3D11ShaderResourceView ** textureCubeView, bool generateMips) { // 檢查設備、設備上下文是否非空 // 紋理數組和紋理立方體視圖只要有其中一個非空便可 if (!d3dDevice || !d3dDeviceContext || !(textureArray || textureCubeView)) return E_INVALIDARG; // ****************** // 讀取天空盒紋理 // ID3D11Texture2D* srcTex = nullptr; ID3D11ShaderResourceView* srcTexSRV = nullptr; // 該資源用於GPU複製 HRESULT hResult = CreateWICTextureFromFileEx(d3dDevice, d3dDeviceContext, cubeMapFileName.c_str(), 0, D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE | (generateMips ? D3D11_BIND_RENDER_TARGET : 0), 0, (generateMips ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0), WIC_LOADER_DEFAULT, (ID3D11Resource**)&srcTex, (generateMips ? &srcTexSRV : nullptr)); // 文件未打開 if (FAILED(hResult)) { return hResult; } // ...
如今咱們能夠利用CreateWICTextureFromFileEx
函數內部幫咱們預先生成mipmaps,必需要同時提供d3dDeviceContext
和srcTexSRV
才能生成。
並且關於紋理的拷貝操做能夠不須要從GPU讀到CPU再進行,而是直接在GPU之間進行拷貝,所以能夠將usage
設爲D3D11_USAGE_DEFAULT
,cpuAccessFlags
設爲0.
接下來須要建立一個新的紋理數組。首先須要填充D3D11_TEXTURE2D_DESC
結構體內容,這裏的大部分參數能夠從天空盒紋理取得。
對於mip等級須要特別處理,好比一個4096x3072的完成天空盒位圖,其生成的mip等級是13,可是對於裏面的1024x1024立方體表面,其生成的mip等級是11,能夠獲得這樣的一個關係:紋理數組的mip等級比讀進來的天空盒位圖mip等級少2.
UINT squareLength = texDesc.Width / 4; texArrayDesc.Width = squareLength; texArrayDesc.Height = squareLength; texArrayDesc.MipLevels = (generateMips ? texDesc.MipLevels - 2 : 1); // 立方體的mip等級比整張位圖的少2 texArrayDesc.ArraySize = 6; texArrayDesc.Format = texDesc.Format; texArrayDesc.SampleDesc.Count = 1; texArrayDesc.SampleDesc.Quality = 0; texArrayDesc.Usage = D3D11_USAGE_DEFAULT; texArrayDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; texArrayDesc.CPUAccessFlags = 0; texArrayDesc.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE; // 容許從中建立TextureCube ID3D11Texture2D* texArray = nullptr; hResult = d3dDevice->CreateTexture2D(&texArrayDesc, nullptr, &texArray); if (FAILED(hResult)) { SAFE_RELEASE(srcTex); SAFE_RELEASE(srcTexSRV); return hResult; }
D3D11_BIND_SHADER_RESOURCE
和D3D11_RESOURCE_MISC_TEXTURECUBE
的標籤記得不要遺漏。
如今咱們須要對源位圖進行節選,但節選以前,首先咱們須要瞭解定義3D盒的結構體D3D11_BOX
:
typedef struct D3D11_BOX { UINT left; UINT top; UINT front; UINT right; UINT bottom; UINT back; } D3D11_BOX;
3D box使用的是下面的座標系,和紋理座標系很像:
因爲選取像素採用的是半開半閉區間,如[left, right)
,在指定left, top, front的值時會選到該像素,而不對想到right, bottom, back對應的像素。
對於1D紋理來講,是沒有Y軸和Z軸的,所以須要令back=0, front=1, top=0, bottom=1
才能表示當前的1D紋理,若是出現像back和front相等的狀況,則不會選到任何的紋理像素區間。
而2D紋理沒有Z軸,在選取像素區域前須要置back=0, front=1
。
3D紋理(體積紋理)能夠看作一系列紋理的堆疊,所以front
和back
能夠用來選定哪些紋理須要節選。
void ID3D11DeviceContext::CopySubresourceRegion( ID3D11Resource *pDstResource, // [In/Out]目標資源 UINT DstSubresource, // [In]目標子資源索引 UINT DstX, // [In]目標起始X值 UINT DstY, // [In]目標起始Y值 UINT DstZ, // [In]目標起始Z值 ID3D11Resource *pSrcResource, // [In]源資源 UINT SrcSubresource, // [In]源子資源索引 const D3D11_BOX *pSrcBox // [In]指定複製區域 );
例如如今咱們要將該天空盒的+X面對應的mipmap鏈拷貝到ArraySlice
爲0(即D3D11_TEXTURECUBE_FACE_POSITIVE_X
)的目標資源中,則能夠像下面這樣寫:
D3D11_BOX box; // box座標軸以下: // front // / // /_____right // | // | // bottom box.front = 0; box.back = 1; for (UINT i = 0; i < texArrayDesc.MipLevels; ++i) { // +X面拷貝 box.left = squareLength * 2; box.top = squareLength; box.right = squareLength * 3; box.bottom = squareLength * 2; d3dDeviceContext->CopySubresourceRegion( texArray, D3D11CalcSubresource(i, D3D11_TEXTURECUBE_FACE_POSITIVE_X, texArrayDesc.MipLevels), 0, 0, 0, srcTex, i, &box); // -X面拷貝 box.left = 0; box.top = squareLength; box.right = squareLength; box.bottom = squareLength * 2; d3dDeviceContext->CopySubresourceRegion( texArray, D3D11CalcSubresource(i, D3D11_TEXTURECUBE_FACE_NEGATIVE_X, texArrayDesc.MipLevels), 0, 0, 0, srcTex, i, &box); // +Y面拷貝 box.left = squareLength; box.top = 0; box.right = squareLength * 2; box.bottom = squareLength; d3dDeviceContext->CopySubresourceRegion( texArray, D3D11CalcSubresource(i, D3D11_TEXTURECUBE_FACE_POSITIVE_Y, texArrayDesc.MipLevels), 0, 0, 0, srcTex, i, &box); // -Y面拷貝 box.left = squareLength; box.top = squareLength * 2; box.right = squareLength * 2; box.bottom = squareLength * 3; d3dDeviceContext->CopySubresourceRegion( texArray, D3D11CalcSubresource(i, D3D11_TEXTURECUBE_FACE_NEGATIVE_Y, texArrayDesc.MipLevels), 0, 0, 0, srcTex, i, &box); // +Z面拷貝 box.left = squareLength; box.top = squareLength; box.right = squareLength * 2; box.bottom = squareLength * 2; d3dDeviceContext->CopySubresourceRegion( texArray, D3D11CalcSubresource(i, D3D11_TEXTURECUBE_FACE_POSITIVE_Z, texArrayDesc.MipLevels), 0, 0, 0, srcTex, i, &box); // -Z面拷貝 box.left = squareLength * 3; box.top = squareLength; box.right = squareLength * 4; box.bottom = squareLength * 2; d3dDeviceContext->CopySubresourceRegion( texArray, D3D11CalcSubresource(i, D3D11_TEXTURECUBE_FACE_NEGATIVE_Z, texArrayDesc.MipLevels), 0, 0, 0, srcTex, i, &box); // 下一個mipLevel的紋理寬高都是原來的1/2 squareLength /= 2; }
最後就是建立紋理立方體着色器資源視圖了:
if (textureCubeView) { D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc; viewDesc.Format = texArrayDesc.Format; viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE; viewDesc.TextureCube.MostDetailedMip = 0; viewDesc.TextureCube.MipLevels = texArrayDesc.MipLevels; hResult = d3dDevice->CreateShaderResourceView(texArray, &viewDesc, textureCubeView); } // 檢查是否須要紋理數組 if (textureArray) { *textureArray = texArray; } else { SAFE_RELEASE(texArray); } SAFE_RELEASE(srcTex); SAFE_RELEASE(srcTexSRV); return hResult; }
第一步是讀取六張紋理,並根據須要生成mipmaps:
HRESULT CreateWICTexture2DCubeFromFile( ID3D11Device * d3dDevice, ID3D11DeviceContext * d3dDeviceContext, const std::vector<std::wstring>& cubeMapFileNames, ID3D11Texture2D ** textureArray, ID3D11ShaderResourceView ** textureCubeView, bool generateMips) { // 檢查設備與設備上下文是否非空 // 文件名數目須要不小於6 // 紋理數組和資源視圖只要有其中一個非空便可 UINT arraySize = (UINT)cubeMapFileNames.size(); if (!d3dDevice || !d3dDeviceContext || arraySize < 6 || !(textureArray || textureCubeView)) return E_INVALIDARG; // ****************** // 讀取紋理 // HRESULT hResult; std::vector<ID3D11Texture2D*> srcTexVec(arraySize, nullptr); std::vector<ID3D11ShaderResourceView*> srcTexSRVVec(arraySize, nullptr); std::vector<D3D11_TEXTURE2D_DESC> texDescVec(arraySize); for (UINT i = 0; i < arraySize; ++i) { // 該資源用於GPU複製 hResult = CreateWICTextureFromFile(d3dDevice, (generateMips ? d3dDeviceContext : nullptr), cubeMapFileNames[i].c_str(), (ID3D11Resource**)&srcTexVec[i], (generateMips ? &srcTexSRVVec[i] : nullptr)); // 讀取建立好的紋理信息 srcTexVec[i]->GetDesc(&texDescVec[i]); // 須要檢驗全部紋理的mipLevels,寬度和高度,數據格式是否一致, // 若存在數據格式不一致的狀況,請使用dxtex.exe(DirectX Texture Tool) // 將全部的圖片轉成一致的數據格式 if (texDescVec[i].MipLevels != texDescVec[0].MipLevels || texDescVec[i].Width != texDescVec[0].Width || texDescVec[i].Height != texDescVec[0].Height || texDescVec[i].Format != texDescVec[0].Format) { for (UINT j = 0; j < i; ++j) { SAFE_RELEASE(srcTexVec[j]); SAFE_RELEASE(srcTexSRVVec[j]); } return E_FAIL; } }
而後是建立數組,即使提供的紋理數目超過6,也是容許的:
// ****************** // 建立紋理數組 // D3D11_TEXTURE2D_DESC texArrayDesc; texArrayDesc.Width = texDescVec[0].Width; texArrayDesc.Height = texDescVec[0].Height; texArrayDesc.MipLevels = (generateMips ? texDescVec[0].MipLevels : 1); texArrayDesc.ArraySize = arraySize; texArrayDesc.Format = texDescVec[0].Format; texArrayDesc.SampleDesc.Count = 1; texArrayDesc.SampleDesc.Quality = 0; texArrayDesc.Usage = D3D11_USAGE_DEFAULT; texArrayDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; texArrayDesc.CPUAccessFlags = 0; texArrayDesc.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE; // 容許從中建立TextureCube ID3D11Texture2D* texArray = nullptr; hResult = d3dDevice->CreateTexture2D(&texArrayDesc, nullptr, &texArray); if (FAILED(hResult)) { for (UINT i = 0; i < arraySize; ++i) { SAFE_RELEASE(srcTexVec[i]); SAFE_RELEASE(srcTexSRVVec[i]); } return hResult; }
因爲咱們不須要指定源位圖的具體區域,能夠將pSrcBox
設置爲nullptr
:
// ****************** // 將原紋理的全部子資源拷貝到該數組中 // texArray->GetDesc(&texArrayDesc); for (UINT i = 0; i < arraySize; ++i) { for (UINT j = 0; j < texArrayDesc.MipLevels; ++j) { d3dDeviceContext->CopySubresourceRegion( texArray, D3D11CalcSubresource(j, i, texArrayDesc.MipLevels), 0, 0, 0, srcTexVec[i], j, nullptr); } }
最後就是建立立方體紋理着色器資源視圖,默認只指定0到5索引的紋理,若是這個紋理數組包含索引6-11的紋理,你還想建立一個新的視圖的話,就能夠拿取建立好textureArray
,而後本身再寫建立視圖相關的調用:
// ****************** // 建立立方體紋理的SRV // if (textureCubeView) { D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc; viewDesc.Format = texArrayDesc.Format; viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE; viewDesc.TextureCube.MostDetailedMip = 0; viewDesc.TextureCube.MipLevels = texArrayDesc.MipLevels; hResult = d3dDevice->CreateShaderResourceView(texArray, &viewDesc, textureCubeView); } // 檢查是否須要紋理數組 if (textureArray) { *textureArray = texArray; } else { SAFE_RELEASE(texArray); } // 釋放全部資源 for (UINT i = 0; i < arraySize; ++i) { SAFE_RELEASE(srcTexVec[i]); SAFE_RELEASE(srcTexSRVVec[i]); } return hResult; }
至此有關2D紋理相關的講解就基本上結束了。
DirectX11 With Windows SDK完整目錄
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。