经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 游戏设计 » 查看文章
DirectX11--深入理解与使用2D纹理资源
来源:cnblogs  作者:X_Jun  时间:2019/1/14 9:19:38  对本文有异议

前言

写教程到现在,我发现有关纹理资源的一些解说和应用都写的太过分散,导致连我自己找起来都不方便。现在决定把这部分的内容整合起来,尽可能做到一篇搞定所有2D纹理相关的内容,其中包括:

  1. 2D纹理的一般创建方法
  2. DDSTextureLoader和WICTextureLoader
  3. 2D纹理数组的一般创建方法
  4. 2D纹理立方体的一般创建方法(未完工)
  5. 纹理子资源
  6. 纹理资源的完整复制(未完工)
  7. 纹理子资源指定区域的复制(未完工)
  8. 纹理从GPU映射回CPU进行修改
  9. 使用内存初始化纹理

你必须要先了解纹理映射,然后再来看这部分内容。

由于这篇的工作量实在是太大了,目前下面这些代码我还没有提交到项目,而且这篇博客还在施工中,等我全部写完再提交一遍。

章节回顾
09 纹理映射与采样器状态

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

2D纹理

Direct3D 11允许我们创建1D纹理、2D纹理、3D纹理,分别对应的接口为ID3D11Texture1D, ID3D11Texture2DID3D11Texture3D。创建出来的对象理论上不仅在内存中占用了它的实现类所需空间,还在显存中占用了一定空间以存放纹理的实际数据。

由于实际上我们最常用到的就是2D纹理,因此这里不会讨论1D纹理和3D纹理的内容。

首先让我们看看D3D11对一个2D纹理的描述:

  1. typedef struct D3D11_TEXTURE2D_DESC
  2. {
  3. UINT Width; // 纹理宽度
  4. UINT Height; // 纹理高度
  5. UINT MipLevels; // 允许的Mip等级数
  6. UINT ArraySize; // 可以用于创建纹理数组,这里指定纹理的数目,单个纹理使用1
  7. DXGI_FORMAT Format; // DXGI支持的数据格式,默认DXGI_FORMAT_R8G8B8A8_UNORM
  8. DXGI_SAMPLE_DESC SampleDesc; // MSAA描述
  9. D3D11_USAGE Usage; // 使用D3D11_USAGE枚举值指定数据的CPU/GPU访问权限
  10. UINT BindFlags; // 使用D3D11_BIND_FLAG枚举来决定该数据的使用类型
  11. UINT CPUAccessFlags; // 使用D3D11_CPU_ACCESS_FLAG枚举来决定CPU访问权限
  12. UINT MiscFlags; // 使用D3D11_RESOURCE_MISC_FLAG枚举
  13. } D3D11_TEXTURE2D_DESC;
  14. typedef struct DXGI_SAMPLE_DESC
  15. {
  16. UINT Count; // MSAA采样数
  17. UINT Quality; // MSAA质量等级
  18. } DXGI_SAMPLE_DESC;

这里特别要讲一下MipLevels

  1. 如果你希望它不产生mipmap,则应当指定为1(只包含最大的位图本身)
  2. 如果你希望它能够产生完整的mipmap,可以指定为0,这样你就不需要手工去算这个纹理最大支持的mipmap等级数了,在创建好纹理后,可以再调用ID3D11Texture2D::GetDesc来查看实际的MipLevels值是多少
  3. 如果你指定的是其它的值,这里举个例子,该纹理的宽高为400x400,mip等级为3时,该纹理会产生400x400200x200100x100的mipmap

对于经常作为着色器资源的纹理,通常是不能对其开启MSAA的,应当把Count设为1,Quality设为0

紧接着是DXGI_FORMAT

它用于指定纹理存储的数据格式,最常用的就是DXGI_FORMAT_R8G8B8A8_UNORM了。这种格式在内存的排布可以用下面的结构体表示:

  1. struct {
  2. uint8_t a;
  3. uint8_t b;
  4. uint8_t g;
  5. uint8_t r;
  6. };

了解这个对我们后期通过内存填充纹理十分重要。

然后是Usage

D3D11_USAGE CPU读 CPU写 GPU读 GPU写
D3D11_USAGE_DEFAULT
D3D11_USAGE_IMMUTABLE
D3D11_USAGE_DYNAMIC
D3D11_USAGE_STAGING

如果一个纹理以D3D11_USAGE_DEFAULT的方式创建,那么它可以使用下面的这些方法来更新纹理:

  1. ID3D11DeviceContext::UpdateSubresource
  2. ID3D11DeviceContext::CopyResource
  3. ID3D11DeviceContext::CopySubresourceRegion

通过DDSTextureLoaderWICTextureLoader创建出来的纹理默认都是这种类型

而如果一个纹理以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_DYNAMICD3D11_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纹理:

  1. HRESULT ID3D11Device::CreateTexture2D(
  2. const D3D11_TEXTURE2D_DESC *pDesc, // [In] 2D纹理描述信息
  3. const D3D11_SUBRESOURCE_DATA *pInitialData, // [In] 用于初始化的资源
  4. ID3D11Texture2D **ppTexture2D); // [Out] 获取到的2D纹理

过程我就不演示了。

2D纹理的资源视图(以着色器资源视图为例)

创建好纹理后,我们还需要让它绑定到资源视图,然后再让该资源视图绑定到渲染管线的指定阶段。

D3D11_SHADER_RESOURCE_VIEW_DESC的定义如下:

  1. typedef struct D3D11_SHADER_RESOURCE_VIEW_DESC
  2. {
  3. DXGI_FORMAT Format;
  4. D3D11_SRV_DIMENSION ViewDimension;
  5. union
  6. {
  7. D3D11_BUFFER_SRV Buffer;
  8. D3D11_TEX1D_SRV Texture1D;
  9. D3D11_TEX1D_ARRAY_SRV Texture1DArray;
  10. D3D11_TEX2D_SRV Texture2D;
  11. D3D11_TEX2D_ARRAY_SRV Texture2DArray;
  12. D3D11_TEX2DMS_SRV Texture2DMS;
  13. D3D11_TEX2DMS_ARRAY_SRV Texture2DMSArray;
  14. D3D11_TEX3D_SRV Texture3D;
  15. D3D11_TEXCUBE_SRV TextureCube;
  16. D3D11_TEXCUBE_ARRAY_SRV TextureCubeArray;
  17. D3D11_BUFFEREX_SRV BufferEx;
  18. } ;
  19. } D3D11_SHADER_RESOURCE_VIEW_DESC;
  20. };

其中Format要和纹理创建时的Format一致,对于2D纹理来说,应当指定D3D11_SRV_DIMENSIOND3D11_SRV_DIMENSION_TEXTURE2D

然后D3D11_TEX2D_SRV结构体定义如下:

  1. typedef struct D3D11_TEX2D_SRV
  2. {
  3. UINT MostDetailedMip;
  4. UINT MipLevels;
  5. } D3D11_TEX2D_SRV;

通过MostDetailedMap我们可以指定开始使用的纹理子资源,MipLevels则指定使用的子资源数目。如果要使用完整mipmaps,则需要指定MostDetailedMap为0, MipLevels为-1.

例如我想像下图那样使用mip等级为1到2的纹理子资源,可以指定MostDetailedMip为1,MipLevels为2.

创建着色器资源视图的演示如下:

  1. D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
  2. srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
  3. srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
  4. srvDesc.Texture2D.MipLevels = 1;
  5. srvDesc.Texture2D.MostDetailedMip = 0;
  6. HR(md3dDevice->CreateShaderResourceView(tex.Get(), &srvDesc, texSRV.GetAddressOf()));

DDSTextureLoader和WICTextureLoader库

作为一个纹理相关的综合教程,我可能会重复之前我讲过的内容。

DDS位图和WIC位图

DDS是一种图片格式,是DirectDraw Surface的缩写,它是DirectX纹理压缩(DirectX Texture Compression,简称DXTC)的产物。由NVIDIA公司开发。大部分3D游戏引擎都可以使用DDS格式的图片用作贴图,也可以制作法线贴图。

WIC(Windows Imaging Component)是一个可以扩展的平台,为数字图像提供底层API,它可以支持bmp、dng、ico、jpeg、png、tiff等格式的位图。

如何添加进你的项目

DirectXTex中找到DDSTextureLoader文件夹和WICTextureLoader文件夹中分别找到对应的头文件和源文件(不带12的),并加入到你的项目中

DDSTextureLoader

CreateDDSTextureFromFile函数--从文件读取DDS纹理

  1. HRESULT CreateDDSTextureFromFile(
  2. ID3D11Device* d3dDevice, // [In]D3D设备
  3. const wchar_t* szFileName, // [In]dds图片文件名
  4. ID3D11Resource** texture, // [Out]输出一个指向资源接口类的指针,也可以填nullptr
  5. ID3D11ShaderResourceView** textureView, // [Out]输出一个指向着色器资源视图的指针,也可以填nullptr
  6. size_t maxsize = 0, // [In]限制纹理最大宽高,若超过则内部会缩放,默认0不限制
  7. DDS_ALPHA_MODE* alphaMode = nullptr); // [In]忽略
  8. HRESULT CreateDDSTextureFromFile(
  9. ID3D11Device* d3dDevice, // [In]D3D设备
  10. ID3D11DeviceContext* d3dContext, // [In]D3D设备上下文
  11. const wchar_t* szFileName, // [In]dds图片文件名
  12. ID3D11Resource** texture, // [Out]输出一个指向资源接口类的指针,也可以填nullptr
  13. ID3D11ShaderResourceView** textureView, // [Out]输出一个指向着色器资源视图的指针,也可以填nullptr
  14. size_t maxsize = 0, // [In]限制纹理最大宽高,若超过则内部会缩放,默认0不限制
  15. DDS_ALPHA_MODE* alphaMode = nullptr); // [In]忽略

第二个重载版本用于为DDS位图生成mipmaps,但大多数情况下你能载入的DDS位图本身都自带mipmaps了,与其运行时生成,不如提前为它制作mipmaps。

CreateDDSTextureFromFileEx函数--从文件读取DDS纹理的增强版

上面两个函数都使用了这个函数,而且如果你想要更强的扩展性,就可以了解一下:

  1. HRESULT CreateDDSTextureFromFileEx(
  2. ID3D11Device* d3dDevice, // [In]D3D设备
  3. const wchar_t* szFileName, // [In].dds文件名
  4. size_t maxsize, // [In]限制纹理最大宽高,若超过则内部会缩放,默认0不限制
  5. D3D11_USAGE usage, // [In]使用D3D11_USAGE枚举值指定数据的CPU/GPU访问权限
  6. unsigned int bindFlags, // [In]使用D3D11_BIND_FLAG枚举来决定该数据的使用类型
  7. unsigned int cpuAccessFlags, // [In]D3D11_CPU_ACCESS_FLAG枚举值
  8. unsigned int miscFlags, // [In]D3D11_RESOURCE_MISC_FLAG枚举值
  9. bool forceSRGB, // [In]强制使用SRGB,默认false
  10. ID3D11Resource** texture, // [Out]获取创建好的纹理(可选)
  11. ID3D11ShaderResourceView** textureView, // [Out]获取创建好的纹理资源视图(可选)
  12. DDS_ALPHA_MODE* alphaMode = nullptr); // [Out]忽略(可选)
  13. HRESULT CreateDDSTextureFromFileEx(
  14. ID3D11Device* d3dDevice, // [In]D3D设备
  15. ID3D11DeviceContext* d3dContext, // [In]D3D设备上下文
  16. const wchar_t* szFileName, // [In].dds文件名
  17. size_t maxsize, // [In]限制纹理最大宽高,若超过则内部会缩放,默认0不限制
  18. D3D11_USAGE usage, // [In]使用D3D11_USAGE枚举值指定数据的CPU/GPU访问权限
  19. unsigned int bindFlags, // [In]使用D3D11_BIND_FLAG枚举来决定该数据的使用类型
  20. unsigned int cpuAccessFlags, // [In]D3D11_CPU_ACCESS_FLAG枚举值
  21. unsigned int miscFlags, // [In]D3D11_RESOURCE_MISC_FLAG枚举值
  22. bool forceSRGB, // [In]强制使用SRGB,默认false
  23. ID3D11Resource** texture, // [Out]获取创建好的纹理(可选)
  24. ID3D11ShaderResourceView** textureView, // [Out]获取创建好的纹理资源视图(可选)
  25. DDS_ALPHA_MODE* alphaMode = nullptr); // [Out]忽略(可选)

CreateDDSTextureFromMemory函数--从内存创建DDS纹理

这里我只介绍简易版本的,因为跟上面提到的函数差别只是读取来源不一样,其余参数我就不再赘述:

  1. HRESULT CreateDDSTextureFromMemory(
  2. ID3D11Device* d3dDevice, // [In]D3D设备
  3. const uint8_t* ddsData, // [In]原dds文件读取到的完整二进制流
  4. size_t ddsDataSize, // [In]原dds文件的大小
  5. ID3D11Resource** texture, // [Out]获取创建好的纹理(可选)
  6. ID3D11ShaderResourceView** textureView, // [Out]获取创建好的纹理资源视图(可选)
  7. size_t maxsize = 0, // [In]限制纹理最大宽高,若超过则内部会缩放,默认0不限制
  8. DDS_ALPHA_MODE* alphaMode = nullptr); // [Out]忽略(可选)

如果你需要生成mipmaps,就使用带D3D设备上下文的重载版本。

WICTextureLoader

CreateWICTextureFromFileEx

由于用法上和DDSTextureLoader大同小异,我这里也只提CreateWICTextureFromFileEx函数:

  1. HRESULT CreateWICTextureFromFileEx(
  2. ID3D11Device* d3dDevice, // [In]D3D设备
  3. const wchar_t* szFileName, // [In]位图文件名
  4. size_t maxsize, // [In]限制纹理最大宽高,若超过则内部会缩放,默认0不限制
  5. D3D11_USAGE usage, // [In]使用D3D11_USAGE枚举值指定数据的CPU/GPU访问权限
  6. unsigned int bindFlags, // [In]使用D3D11_BIND_FLAG枚举来决定该数据的使用类型
  7. unsigned int cpuAccessFlags, // [In]D3D11_CPU_ACCESS_FLAG枚举值
  8. unsigned int miscFlags, // [In]D3D11_RESOURCE_MISC_FLAG枚举值
  9. unsigned int loadFlags, // [In]默认WIC_LOADER_DEAULT
  10. ID3D11Resource** texture, // [Out]获取创建好的纹理(可选)
  11. ID3D11ShaderResourceView** textureView);// [Out]获取创建好的纹理资源视图(可选)

纹理子资源(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。定义如下:

  1. typedef struct D3D11_SUBRESOURCE_DATA
  2. {
  3. const void *pSysMem; // 用于初始化的数据
  4. UINT SysMemPitch; // 当前子资源一行所占的字节数(2D/3D纹理使用)
  5. UINT SysMemSlicePitch; // 当前子资源一个完整切片所占的字节数(仅3D纹理使用)
  6. } D3D11_SUBRESOURCE_DATA;

而如果你使用的是ID3D11DeviceContext::Map方法来获取一个纹理子资源,那么获取到的是D3D11_SUBRESOURCE_DATA结构体,其定义如下:

  1. typedef struct D3D11_MAPPED_SUBRESOURCE {
  2. void *pData; // 映射到内存的数据or需要提交的地址范围
  3. UINT RowPitch; // 当前子资源一行所占的字节数(2D/3D纹理有意义)
  4. UINT DepthPitch; // 当前子资源一个完整切片所占的字节数(仅3D纹理有意义)
  5. } D3D11_MAPPED_SUBRESOURCE;

若一张512x512的纹理(R8G8B8A8),那么它的RowPitch为5124=2048字节,同理在初始化一个512x512的纹理(R8G8B8A8),它的SysMemPitch为5124=2048字节。

通过内存初始化纹理

现在我们尝试通过代码的形式来创建一个纹理(以项目09作为修改),代码如下:

  1. uint32_t ColorRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
  2. {
  3. return (r | (g << 8) | (b << 16) | (a << 24));
  4. }
  5. bool GameApp::InitResource()
  6. {
  7. uint32_t black = ColorRGBA(0, 0, 0, 255), orange = ColorRGBA(255, 108, 0, 255);
  8. // 纹理内存映射,用黑色初始化
  9. std::vector<uint32_t> textureArrayMap(128 * 128, black);
  10. uint32_t(*textureMap)[128] = reinterpret_cast<uint32_t(*)[128]>(textureArrayMap.data());
  11. for (int y = 7; y <= 17; ++y)
  12. for (int x = 25 - y; x <= 102 + y; ++x)
  13. textureMap[y][x] = textureMap[127 - y][x] = orange;
  14. for (int y = 18; y <= 109; ++y)
  15. for (int x = 7; x <= 120; ++x)
  16. textureMap[y][x] = orange;
  17. // 创建纹理数组
  18. D3D11_TEXTURE2D_DESC texArrayDesc;
  19. texArrayDesc.Width = 128;
  20. texArrayDesc.Height = 128;
  21. texArrayDesc.MipLevels = 1;
  22. texArrayDesc.ArraySize = 1;
  23. texArrayDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
  24. texArrayDesc.SampleDesc.Count = 1; // 不使用多重采样
  25. texArrayDesc.SampleDesc.Quality = 0;
  26. texArrayDesc.Usage = D3D11_USAGE_DEFAULT;
  27. texArrayDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
  28. texArrayDesc.CPUAccessFlags = 0;
  29. texArrayDesc.MiscFlags = 0; // 指定需要生成mipmap
  30. D3D11_SUBRESOURCE_DATA sd;
  31. uint32_t * pData = textureArrayMap.data();
  32. sd.pSysMem = pData;
  33. sd.SysMemPitch = 128 * sizeof(uint32_t);
  34. sd.SysMemSlicePitch = 128 * 128 * sizeof(uint32_t);
  35. ComPtr<ID3D11Texture2D> tex;
  36. HR(md3dDevice->CreateTexture2D(&texArrayDesc, &sd, tex.GetAddressOf()));
  37. D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
  38. srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
  39. srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
  40. srvDesc.Texture2D.MipLevels = 1;
  41. srvDesc.Texture2D.MostDetailedMip = 0;
  42. HR(md3dDevice->CreateShaderResourceView(tex.Get(), &srvDesc, mTexSRV.GetAddressOf()));
  43. // ...
  44. }

其它部分的代码修改就不讲了,最终效果如下:

但是如果你想要以初始化的方式来创建带mipmap的Texture2D纹理,则在初始化的时候需要提供D3D11_SUBRESOURCE_DATA数组,元素数目为MipLevels.

2D纹理数组

之前提到,D3D11_TEXTURE2D_DESC中可以通过指定ArraySize的值来将其创建为纹理数组。

HLSL中的2D纹理数组

首先来到HLSL代码,我们之所以不使用下面的这种形式创建纹理数组:

  1. Texture2D gTexArray[7] : register(t0);
  2. // 像素着色器
  3. float4 PS(VertexPosHTex pIn) : SV_Target
  4. {
  5. float4 texColor = gTexArray[gTexIndex].Sample(gSam, float2(pIn.Tex));
  6. return texColor;
  7. }

是因为这样做的话HLSL编译器会报错:sampler array index must be a literal experssion,即pin.PrimID的值也必须是个字面值,而不是变量。但我们还是想要能够根据变量取对应纹理的能力。

正确的做法应当是声明一个Texture2DArray的数组:

Texture2DArray gTexArray : register(t0);

Texture2DArray同样也具有Sample方法,用法示例如下:

  1. // 像素着色器
  2. float4 PS(VertexPosHTex pIn) : SV_Target
  3. {
  4. float4 texColor = gTexArray.Sample(gSam, float3(pIn.Tex, gTexIndex));
  5. return texColor;
  6. }

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),我们就可以用下面的函数来求得指定子资源的索引值:

  1. inline UINT D3D11CalcSubresource(UINT MipSlice, UINT ArraySlice, UINT MipLevels )
  2. { return MipSlice + ArraySlice * MipLevels; }

创建一个纹理数组

现在我们手头上仅有的就是DDSTextureLoader.hWICTextureLoader.h中的函数,但这里面的函数每次都只能加载一张纹理。我们还需要修改龙书样例中读取纹理的函数,具体的操作顺序如下:

  1. 一个个读取存有纹理的文件,创建出一系列ID3D11Texture2D对象,这里的每个对象单独包含一张纹理;
  2. 创建一个ID3D11Texture2D对象,它同时也是一个纹理数组;
  3. 将之前读取的所有纹理有条理地复制到刚创建的纹理数组对象中;
  4. 为该纹理数组对象创建创建一个纹理资源视图(Shader Resource View)。

为了避免出现一些问题,这里实现的纹理数组加载的函数只考虑宽度和高度、数据格式、mip等级都一致的情况。

d3dUtil.h中实现了这样两个函数:

  1. // ------------------------------
  2. // CreateDDSTexture2DArrayFromFile函数
  3. // ------------------------------
  4. // 该函数要求所有的dds纹理的宽高、数据格式、mip等级一致
  5. // [In]d3dDevice D3D设备
  6. // [InOpt]d3dDeviceContext D3D设备上下文
  7. // [In]fileNames dds文件名数组
  8. // [OutOpt]textureArray 输出的纹理数组资源
  9. // [OutOpt]textureArrayView 输出的纹理数组资源视图
  10. // [In]generateMips 是否生成mipmaps
  11. HRESULT CreateDDSTexture2DArrayFromFile(
  12. ID3D11Device * d3dDevice,
  13. ID3D11DeviceContext * d3dDeviceContext,
  14. const std::vector<std::wstring>& fileNames,
  15. ID3D11Texture2D** textureArray,
  16. ID3D11ShaderResourceView** textureArrayView,
  17. bool generateMips = false);
  18. // ------------------------------
  19. // CreateWICTexture2DArrayFromFile函数
  20. // ------------------------------
  21. // 该函数要求所有的dds纹理的宽高、数据格式、mip等级一致
  22. // [In]d3dDevice D3D设备
  23. // [InOpt]d3dDeviceContext D3D设备上下文
  24. // [In]fileNames dds文件名数组
  25. // [OutOpt]textureArray 输出的纹理数组资源
  26. // [OutOpt]textureArrayView 输出的纹理数组资源视图
  27. // [In]generateMips 是否生成mipmaps
  28. HRESULT CreateWICTexture2DArrayFromFile(
  29. ID3D11Device * d3dDevice,
  30. ID3D11DeviceContext * d3dDeviceContext,
  31. const std::vector<std::wstring>& fileNames,
  32. ID3D11Texture2D** textureArray,
  33. ID3D11ShaderResourceView** textureArrayView,
  34. bool generateMips = false);

还有就是d3dUtil.cpp用到的函数CreateTexture2DArray

第一步先讨论纹理的加载,这里`CreateDDSTexture2DArrayFromFile函数的实现如下:

  1. HRESULT CreateDDSTexture2DArrayFromFile(
  2. ID3D11Device * d3dDevice,
  3. ID3D11DeviceContext * d3dDeviceContext,
  4. const std::vector<std::wstring>& fileNames,
  5. ID3D11Texture2D** textureArray,
  6. ID3D11ShaderResourceView** textureArrayView,
  7. bool generateMips)
  8. {
  9. // 检查设备、着色器资源视图、文件名数组是否非空
  10. if (!d3dDevice || !textureArrayView || fileNames.empty())
  11. return E_INVALIDARG;
  12. HRESULT hResult;
  13. // ******************
  14. // 读取所有纹理
  15. //
  16. UINT arraySize = (UINT)fileNames.size();
  17. std::vector<ID3D11Texture2D*> srcTex(arraySize, nullptr);
  18. for (size_t i = 0; i < arraySize; ++i)
  19. {
  20. // 由于这些纹理并不会被GPU使用,我们使用D3D11_USAGE_STAGING枚举值
  21. // 使得CPU可以读取资源
  22. hResult = CreateDDSTextureFromFileEx(d3dDevice, d3dDeviceContext,
  23. fileNames[i].c_str(), 0, D3D11_USAGE_STAGING, 0,
  24. D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ,
  25. 0, false, (ID3D11Resource**)&srcTex[i], nullptr);
  26. if (FAILED(hResult))
  27. break;
  28. }
  29. if (hResult == S_OK)
  30. {
  31. hResult = CreateTexture2DArray(d3dDevice, d3dDeviceContext, srcTex,
  32. D3D11_USAGE_DEFAULT,
  33. D3D11_BIND_SHADER_RESOURCE | (generateMips ? D3D11_BIND_RENDER_TARGET : 0),
  34. 0,
  35. (generateMips ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0),
  36. textureArray,
  37. textureArrayView);
  38. }
  39. for (size_t i = 0; i < arraySize; ++i)
  40. if (srcTex[i])
  41. srcTex[i]->Release();
  42. return hResult;
  43. }

而WIC版的区别仅在于把CreateDDSTextureFromFileEx替换为CreateWICTextureFromFileEx

  1. hResult = CreateWICTextureFromFileEx(d3dDevice, d3dDeviceContext,
  2. fileNames[i].c_str(), 0, D3D11_USAGE_STAGING, 0,
  3. D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ,
  4. 0, WIC_LOADER_DEFAULT, (ID3D11Resource**)&srcTex[i], nullptr);

在了解CreateTexture2DArray函数的实现前,你需要下面这些内容:

ID3D11DeviceContext::Map函数--获取指向子资源中数据的指针并拒绝GPU对该子资源的访问

  1. HRESULT ID3D11DeviceContext::Map(
  2. ID3D11Resource *pResource, // [In]包含ID3D11Resource接口的资源对象
  3. UINT Subresource, // [In]子资源索引
  4. D3D11_MAP MapType, // [In]D3D11_MAP枚举值,指定读写相关操作
  5. UINT MapFlags, // [In]填0,忽略
  6. D3D11_MAPPED_SUBRESOURCE *pMappedResource // [Out]获取到的已经映射到内存的子资源
  7. );

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]--将内存数据拷贝到不可进行映射的子资源中

这个函数在之前我们主要是用来将内存数据拷贝到常量缓冲区中,现在我们也可以用它将内存数据拷贝到纹理的子资源当中:

  1. void ID3D11DeviceContext::UpdateSubresource(
  2. ID3D11Resource *pDstResource, // [In]目标资源对象
  3. UINT DstSubresource, // [In]对于2D纹理来说,该参数为指定Mip等级的子资源
  4. const D3D11_BOX *pDstBox, // [In]这里通常填nullptr,或者拷贝的数据宽高比当前子资源小时可以指定范围
  5. const void *pSrcData, // [In]用于拷贝的内存数据
  6. UINT SrcRowPitch, // [In]该2D纹理的 宽度*数据格式的位数
  7. UINT SrcDepthPitch // [In]对于2D纹理来说并不需要用到该参数,因此可以任意设置
  8. );

ID3D11DeviceContext::UnMap函数--让指向资源的指针无效并重新启用GPU对该资源的访问权限

  1. void ID3D11DeviceContext::Unmap(
  2. ID3D11Resource *pResource, // [In]包含ID3D11Resource接口的资源对象
  3. UINT Subresource // [In]需要取消的子资源索引
  4. );

D3D11_TEX2D_ARRAY_SRV结构体

在创建着色器目标视图时,你还需要填充共用体中的D3D11_TEX2D_ARRAY_SRV结构体:

  1. typedef struct D3D11_TEX2D_ARRAY_SRV
  2. {
  3. UINT MostDetailedMip;
  4. UINT MipLevels;
  5. UINT FirstArraySlice;
  6. UINT ArraySize;
  7. } D3D11_TEX2D_ARRAY_SRV;

通过FirstArraySlice我们可以指定开始使用的纹理,ArraySize则指定使用的纹理数目。

例如我想指定像上面那样的范围,可以指定FirstArraySlice为1,ArraySize为2,MostDetailedMip为1,MipLevels为2.

最终CreateTexture2DArray的实现如下:

  1. static HRESULT CreateTexture2DArray(
  2. ID3D11Device * d3dDevice,
  3. ID3D11DeviceContext * d3dDeviceContext,
  4. std::vector<ID3D11Texture2D*>& srcTex,
  5. D3D11_USAGE usage,
  6. UINT bindFlags,
  7. UINT cpuAccessFlags,
  8. UINT miscFlags,
  9. ID3D11Texture2D** textureArray,
  10. ID3D11ShaderResourceView** textureArrayView)
  11. {
  12. if (!textureArray && !textureArrayView || !d3dDevice || !d3dDeviceContext || srcTex.empty())
  13. return E_INVALIDARG;
  14. // 需要检验所有纹理的mipLevels,宽度和高度,数据格式是否一致,
  15. // 若存在数据格式不一致的情况,请使用dxtex.exe(DirectX Texture Tool)
  16. // 将所有的图片转成一致的数据格式
  17. UINT arraySize = (UINT)srcTex.size();
  18. std::vector<D3D11_TEXTURE2D_DESC> texDesc(arraySize);
  19. for (int i = 0; i < arraySize; ++i)
  20. {
  21. if (texDesc[i].MipLevels != texDesc[0].MipLevels || texDesc[i].Width != texDesc[0].Width ||
  22. texDesc[i].Height != texDesc[0].Height || texDesc[i].Format != texDesc[0].Format)
  23. {
  24. return E_FAIL;
  25. }
  26. }
  27. HRESULT hResult;
  28. bool generateMips = (bindFlags & D3D11_BIND_RENDER_TARGET) &&
  29. (miscFlags & D3D11_RESOURCE_MISC_GENERATE_MIPS);
  30. // ******************
  31. // 创建纹理数组
  32. //
  33. D3D11_TEXTURE2D_DESC texArrayDesc;
  34. texArrayDesc.Width = texDesc[0].Width;
  35. texArrayDesc.Height = texDesc[0].Height;
  36. texArrayDesc.MipLevels = generateMips ? 0 : texDesc[0].MipLevels;
  37. texArrayDesc.ArraySize = arraySize;
  38. texArrayDesc.Format = texDesc[0].Format;
  39. texArrayDesc.SampleDesc.Count = 1; // 不能使用多重采样
  40. texArrayDesc.SampleDesc.Quality = 0;
  41. texArrayDesc.Usage = usage;
  42. texArrayDesc.BindFlags = bindFlags;
  43. texArrayDesc.CPUAccessFlags = cpuAccessFlags;
  44. texArrayDesc.MiscFlags = miscFlags;
  45. ID3D11Texture2D* texArray;
  46. hResult = d3dDevice->CreateTexture2D(&texArrayDesc, nullptr, &texArray);
  47. if (FAILED(hResult))
  48. {
  49. for (size_t i = 0; i < arraySize; ++i)
  50. srcTex[i]->Release();
  51. return E_FAIL;
  52. }
  53. texArray->GetDesc(&texArrayDesc);
  54. // ******************
  55. // 将所有的纹理子资源赋值到纹理数组中
  56. //
  57. UINT minMipLevels = (generateMips ? 1 : texArrayDesc.MipLevels);
  58. // 每个纹理元素
  59. for (UINT i = 0; i < texArrayDesc.ArraySize; ++i)
  60. {
  61. // 纹理中的每个mipmap等级
  62. for (UINT j = 0; j < minMipLevels; ++j)
  63. {
  64. D3D11_MAPPED_SUBRESOURCE mappedTex2D;
  65. // 允许映射索引i纹理中,索引j的mipmap等级的2D纹理
  66. d3dDeviceContext->Map(srcTex[i],
  67. j, D3D11_MAP_READ, 0, &mappedTex2D);
  68. d3dDeviceContext->UpdateSubresource(
  69. texArray,
  70. D3D11CalcSubresource(j, i, texArrayDesc.MipLevels), // i * mipLevel + j
  71. nullptr,
  72. mappedTex2D.pData,
  73. mappedTex2D.RowPitch,
  74. mappedTex2D.DepthPitch);
  75. // 停止映射
  76. d3dDeviceContext->Unmap(srcTex[i], j);
  77. }
  78. }
  79. // ******************
  80. // 创建纹理数组的SRV
  81. //
  82. if (textureArrayView)
  83. {
  84. D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc;
  85. viewDesc.Format = texArrayDesc.Format;
  86. viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
  87. viewDesc.Texture2DArray.MostDetailedMip = 0;
  88. viewDesc.Texture2DArray.MipLevels = texArrayDesc.MipLevels;
  89. viewDesc.Texture2DArray.FirstArraySlice = 0;
  90. viewDesc.Texture2DArray.ArraySize = arraySize;
  91. hResult = d3dDevice->CreateShaderResourceView(texArray, &viewDesc, textureArrayView);
  92. // 生成mipmaps
  93. if (hResult == S_OK && generateMips)
  94. {
  95. d3dDeviceContext->GenerateMips(*textureArrayView);
  96. }
  97. }
  98. // 检查是否需要纹理数组
  99. if (textureArray)
  100. {
  101. *textureArray = texArray;
  102. }
  103. else
  104. {
  105. texArray->Release();
  106. }
  107. return hResult;
  108. }

(未完待续)

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号