前言
寫教程到現在,我發現有關紋理資源的一些解說和應用都寫的太過分散,導致連我自己找起來都不方便。現在決定把這部分的內容整合起來,儘可能做到一篇搞定所有2D紋理相關的內容,其中包括:
- DDSTextureLoader和WICTextureLoader
- ScreenGrab
- 2D紋理的一般創建方法
- 2D紋理數組的一般創建方法
- 2D紋理立方體的一般創建方法
- 紋理子資源
- 紋理資源的完整複製
- 紋理子資源指定區域的複製
- 紋理從GPU映射回CPU進行讀寫
- 使用內存初始化紋理
你必須要先了解紋理映射,然後再來看這部分內容,原本分散在各個章節的部分現在都已經刪去。
章節回顧 |
---|
09 紋理映射與採樣器狀態 |
DirectX11 With Windows SDK完整目錄
歡迎加入QQ羣: 727623616 可以一起探討DX11,以及有什麼問題也可以在這裏彙報。
DDSTextureLoader和WICTextureLoader庫
DDS位圖和WIC位圖
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的),並加入到你的項目中
DDSTextureLoader
CreateDDSTextureFromFile函數–從文件讀取DDS紋理
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。
CreateDDSTextureFromFileEx函數–從文件讀取DDS紋理的增強版
上面兩個函數都使用了這個函數,而且如果你想要更強的擴展性,就可以瞭解一下:
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]忽略(可選)
CreateDDSTextureFromMemory函數–從內存創建DDS紋理
這裏我只介紹簡易版本的,因爲跟上面提到的函數差別只是讀取來源不一樣,其餘參數我就不再贅述:
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設備上下文的重載版本。
WICTextureLoader
CreateWICTextureFromFileEx
由於用法上和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庫
ScreenGrab
既可以用於屏幕截屏輸出,也可以將你在程序中製作好的紋理輸出到文件。
在DirectXTex中找到ScreenGrab
文件夾,將ScreenGrab.h
和ScreenGrab.cpp
加入到你的項目中即可使用。
但爲了能保存WIC類別的位圖,還需要包含頭文件wincodec.h
以使用裏面一些關於WIC控件的GUID。ScreenGrab
的函數位於名稱空間DirectX
內。
SaveDDSTextureToFile函數–以.dds格式保存紋理
HRESULT SaveDDSTextureToFile(
ID3D11DeviceContext* pContext, // [In]設備上下文
ID3D11Resource* pSource, // [In]必須爲包含ID3D11Texture2D接口類的指針
const wchar_t* fileName ); // [In]輸出文件名
理論上它可以保存紋理、紋理數組、紋理立方體。
SaveWICTextureToFile函數–以指定WIC型別的格式保存紋理
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文件)。
2D紋理
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
:
- 如果你希望它不產生mipmap,則應當指定爲1(只包含最大的位圖本身)
- 如果你希望它能夠產生完整的mipmap,可以指定爲0,這樣你就不需要手工去算這個紋理最大支持的mipmap等級數了,在創建好紋理後,可以再調用
ID3D11Texture2D::GetDesc
來查看實際的MipLevels
值是多少 - 如果你指定的是其它的值,這裏舉個例子,該紋理的寬高爲
400x400
,mip等級爲3時,該紋理會產生400x400
,200x200
和100x100
的mipmap
對於經常作爲着色器資源的紋理,通常是不能對其開啓MSAA的,應當把Count
設爲1,Quality
設爲0
緊接着是DXGI_FORMAT
:
它用於指定紋理存儲的數據格式,最常用的就是DXGI_FORMAT_R8G8B8A8_UNORM
了。這種格式在內存的排布可以用下面的結構體表示:
struct {
uint8_t a;
uint8_t b;
uint8_t g;
uint8_t r;
};
瞭解這個對我們後期通過內存填充紋理十分重要。
然後是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 |
ID3D11Device::CreateTexture2D–創建一個2D紋理
填充好D3D11_TEXTURE2D_DESC
後,你纔可以用它創建一個2D紋理:
HRESULT ID3D11Device::CreateTexture2D(
const D3D11_TEXTURE2D_DESC *pDesc, // [In] 2D紋理描述信息
const D3D11_SUBRESOURCE_DATA *pInitialData, // [In] 用於初始化的資源
ID3D11Texture2D **ppTexture2D); // [Out] 獲取到的2D紋理
過程我就不演示了。
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()));
獲取一份不允許CPU讀寫的紋理到內存中
通常這種資源的類型有可能是D3D11_USAGE_IMMUTABLE
或者D3D11_USAGE_DEFAULT
。我們可以創建一個D3D11_USAGE_STAGING
的紋理,指定CPU讀取權限,然後通過下面的方法拷貝一份到我們新創建的紋理,最後進行映射讀取。
ID3D11DeviceContext::CopyResource方法–複製一份資源
該方法通過GPU將一份完整的源資源複製到目標資源:
void ID3D11DeviceContext::CopyResource(
ID3D11Resource *pDstResource, // [InOut]目標資源
ID3D11Resource *pSrcResource // [In]源資源
);
但是需要注意:
- 不支持以
D3D11_USAGE_IMMUTABLE
創建的目標資源 - 兩者資源類型要一致
- 兩者不能是同一個指針
- 要有一樣的維度(包括寬度,高度,深度,大小)
- 要有兼容的DXGI格式,兩者格式最好是能相同,或者至少是相同的組別,比如
DXGI_FORMAT_R32G32B32_FLOAT
,DXGI_FORMAT_R32G32B32_UINT
和DXGI_FORMAT_R32G32B32_TYPELESS
相互間就可以複製。 - 兩者任何一個在調用該方法的時候不能被映射(先前調用過
ID3D11DeviceContext::Map
方法又沒有Unmap
) - 允許深度/模板緩衝區作爲源或目標資源
紋理子資源(Texture Subresources)
通常我們將包含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_SUBRESOURCE_DATA結構體
,其定義如下:
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),它的SysMemPitch
爲512x4=2048字節。
通過內存初始化紋理
現在我們嘗試通過代碼的形式來創建一個紋理(以項目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> textureArrayMap(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 texArrayDesc;
texArrayDesc.Width = 128;
texArrayDesc.Height = 128;
texArrayDesc.MipLevels = 1;
texArrayDesc.ArraySize = 1;
texArrayDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
texArrayDesc.SampleDesc.Count = 1; // 不使用多重採樣
texArrayDesc.SampleDesc.Quality = 0;
texArrayDesc.Usage = D3D11_USAGE_DEFAULT;
texArrayDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
texArrayDesc.CPUAccessFlags = 0;
texArrayDesc.MiscFlags = 0; // 指定需要生成mipmap
D3D11_SUBRESOURCE_DATA sd;
uint32_t * pData = textureArrayMap.data();
sd.pSysMem = pData;
sd.SysMemPitch = 128 * sizeof(uint32_t);
sd.SysMemSlicePitch = 128 * 128 * sizeof(uint32_t);
ComPtr<ID3D11Texture2D> tex;
HR(md3dDevice->CreateTexture2D(&texArrayDesc, &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(md3dDevice->CreateShaderResourceView(tex.Get(), &srvDesc, mTexSRV.GetAddressOf()));
// ...
}
其它部分的代碼修改就不講了,最終效果如下:
但是如果你想要以初始化的方式來創建帶mipmap的Texture2D
紋理,則在初始化的時候需要提供D3D11_SUBRESOURCE_DATA
數組,元素數目爲MipLevels
.
再或者如果你是要以初始化的方式來創建帶mipmap的Texture2D
紋理數組,則提供的元素數目爲MipLevels * ArraySize
.
2D紋理數組
之前提到,D3D11_TEXTURE2D_DESC
中可以通過指定ArraySize
的值來將其創建爲紋理數組。
HLSL中的2D紋理數組
首先來到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的紋理數組中,而不需要每次都重新綁定一個紋理。然後我們再使用索引值來訪問紋理數組中的某一紋理。
D3D11CalcSubresource函數–計算子資源的索引值
對於紋理數組,每個元素都會包含同樣的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
對象,它同時也是一個紋理數組; - 將之前讀取的所有紋理有條理地複製到剛創建的紋理數組對象中;
- 爲該紋理數組對象創建創建一個紋理資源視圖(Shader Resource View)。
爲了避免出現一些問題,這裏實現的紋理數組加載的函數只考慮寬度和高度、數據格式、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
函數的實現前,你需要下面這些內容:
ID3D11DeviceContext::Map函數–獲取指向子資源中數據的指針並拒絕GPU對該子資源的訪問
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文檔 |
ID3D11DeviceContext::UpdateSubresource函數[2]–將內存數據拷貝到不可進行映射的子資源中
這個函數在之前我們主要是用來將內存數據拷貝到常量緩衝區中,現在我們也可以用它將內存數據拷貝到紋理的子資源當中:
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紋理來說並不需要用到該參數,因此可以任意設置
);
ID3D11DeviceContext::UnMap函數–讓指向資源的指針無效並重新啓用GPU對該資源的訪問權限
void ID3D11DeviceContext::Unmap(
ID3D11Resource *pResource, // [In]包含ID3D11Resource接口的資源對象
UINT Subresource // [In]需要取消的子資源索引
);
D3D11_TEX2D_ARRAY_SRV結構體
在創建着色器目標視圖時,你還需要填充共用體中的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.
ID3D11DeviceContext::GenerateMips–爲紋理資源視圖綁定的所有紋理創建完整的mipmap鏈
由於通過該函數讀取進來的紋理mip等級可能只有1,如果還需要創建mipmap鏈的話,還需要用到下面的方法。
void ID3D11DeviceContext::GenerateMips(
ID3D11ShaderResourceView *pShaderResourceView // [In]需要創建mipamp鏈的SRV
);
比如一張1024x1024的紋理,經過該方法調用後,就會生成剩餘的512x512, 256x256 … 1x1的子紋理資源,加起來一共是11級mipmap。
但是在調用該方法之前,需要確保在創建紋理時,bindFlags需要額外設置D3D11_BIND_RENDER_TARGET
枚舉值,然後在miscFlags中設置爲D3D11_RESOURCE_MISC_GENERATE_MIPS
枚舉值,否則調用上面的方法將無效。
無論是否需要生成mipmap鏈,D3D11_BIND_SHADER_RESOURCE
標籤是必須的,因爲它通常會用在着色器資源的綁定。
最終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紋理立方體的實際上是在以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;
可以看出:
- 索引0指向+X表面;
- 索引1指向-X表面;
- 索引2指向+Y表面;
- 索引3指向-Y表面;
- 索引4指向+Z表面;
- 索引5指向-Z表面;
使用立方體映射意味着我們需要使用3D紋理座標進行尋址,通過向量的形式來指定使用立方體某個表面的其中一點。
在HLSL中,立方體紋理用TextureCube
來表示。
創建一個紋理立方體
對於創建好的DDS立方體紋理,我們只需要使用DDSTextureLoader
就可以很方便地讀取進來:
HR(CreateDDSTextureFromFile(
device.Get(),
cubemapFilename.c_str(),
nullptr,
textureCubeSRV.GetAddressOf()));
然而從網絡上能夠下到的天空盒資源經常要麼是一張天空盒貼圖,要麼是六張天空盒的正方形貼圖,用DXTex導入還是比較麻煩的一件事情。我們也可以自己編寫代碼來構造立方體紋理。
將一張天空盒貼圖轉化成立方體紋理需要經歷以下4個步驟:
- 讀取天空盒的貼圖
- 創建包含6個紋理的數組
- 選取原天空盒紋理的6個子正方形區域,拷貝到該數組中
- 創建立方體紋理的SRV
而將六張天空盒的正方形貼圖轉換成立方體需要經歷這4個步驟:
- 讀取這六張正方形貼圖
- 創建包含6個紋理的數組
- 將這六張貼圖完整地拷貝到該數組中
- 創建立方體紋理的SRV
可以看到這兩種類型的天空盒資源在處理上有很多相似的地方。
// ------------------------------
// 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
的標籤記得不要遺漏。
D3D11_BOX結構體
現在我們需要對源位圖進行節選,但節選之前,首先我們需要了解定義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
可以用來選定哪些紋理需要節選。
ID3D11DeviceContext::CopySubresourceRegion方法–從指定資源選取區域複製到目標資源特定區域
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,以及有什麼問題也可以在這裏彙報。