写教程到如今,我发现有关纹理资源的一些解说和应用都写的太过度散,致使连我本身找起来都不方便。如今决定把这部分的内容整合起来,尽量作到一篇搞定全部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,以及有什么问题也能够在这里汇报。