DirectX11

您所在的位置:网站首页 shader代码中获取纹理的宽高 DirectX11

DirectX11

2024-07-11 18:30| 来源: 网络整理| 查看: 265

前言

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

纹理映射的基础回顾DirectXTex库中的DDSTextureLoader、WICTextureLoader和ScreenGrab2D纹理的一般创建方法2D纹理数组的一般创建方法2D纹理立方体的一般创建方法纹理子资源纹理资源的完整复制纹理子资源指定区域的复制纹理从GPU映射回CPU进行读写使用内存初始化纹理

你必须要先了解纹理映射,然后再来看这部分内容,原本分散在各个章节的部分现在都已经删去。

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

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入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 setCustomProps = nullptr ); // [In]忽略

下表给出了常用的GUID:

GUID文件格式GUID_ContainerFormatPngpngGUID_ContainerFormatJpegjpgGUID_ContainerFormatBmpbmpGUID_ContainerFormatTifftif

这里演示了如何保存后备缓冲区纹理到文件:

ComPtr backBuffer; // 输出截屏 mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast(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 r; uint8_t g; uint8_t b; uint8_t a; };

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

然后是Usage:

D3D11_USAGECPU读CPU写GPU读GPU写D3D11_USAGE_DEFAULT√√D3D11_USAGE_IMMUTABLE√D3D11_USAGE_DYNAMIC√√D3D11_USAGE_STAGING√√√√

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

ID3D11DeviceContext::UpdateSubresourceID3D11DeviceContext::CopyResourceID3D11DeviceContext::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纹理可以作为渲染目标的输出点,并且指定它可以用于生成mipmapsD3D11_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方法生成mipmapsD3D11_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())); ID3D11DeviceContext::*SSetShaderResources方法–设置着色器资源

我们创建着色器资源的目的就是以它作为媒介,传递给着色器使用。上面打*意味着渲染管线的所有可编程着色器阶段都有该方法。

此外,着色器资源视图不仅可以绑定纹理资源,还可以绑定缓冲区资源。

目前在DDSTextureLoader和WICTextureLoader中,我们只需要用到纹理的着色器资源。这里以ID3D11DeviceContext::PSSetShaderResources为例:

void ID3D11DeviceContext::PSSetShaderResources( UINT StartSlot, // [In]起始槽索引,对应HLSL的register(t*) UINT NumViews, // [In]着色器资源视图数目 ID3D11ShaderResourceView * const *ppShaderResourceViews // [In]着色器资源视图数组 ); 纹理子资源(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,以此类推。

描述一个纹理子资源的两种结构体:D3D11_SUBRESOURCE_DATA 和 D3D11_MAPPED_SUBRESOURCE

如果你想要为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 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 imageData; m_pd3dImmediateContext->Map(texOutputCopy.Get(), 0, D3D11_MAP_READ, 0, &mappedData); unsigned char* pData = reinterpret_cast(mappedData.pData); for (UINT i = 0; i Unmap(texOutputCopy.Get(), 0); 获取一份不允许CPU读写的纹理到内存中

通常这种资源的类型有可能是D3D11_USAGE_IMMUTABLE或者D3D11_USAGE_DEFAULT。我们需要按下面的步骤进行:

创建一个D3D11_USAGE_STAGING的纹理,指定CPU读取权限,纹理宽高一致,Mip等级和数组大小都为1;进行内存映射,然后使用ID3D11DeviceContext::CopyResource方法拷贝一份到我们新创建的纹理,注意需要严格按照上面提到的读取方式进行读取,最后解除映射。 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)允许深度/模板缓冲区作为源或目标资源 通过内存初始化纹理

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

uint32_t ColorRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return (r | (g CreateShaderResourceView(pTexArray, &viewDesc, &pTexArraySRV); if (FAILED(hr)) { SAFE_RELEASE(pTexArray); return hr; } // 生成mipmaps if (generateMips) { d3dDeviceContext->GenerateMips(pTexArraySRV); } if (textureArrayView) *textureArrayView = pTexArraySRV; else SAFE_RELEASE(pTexArraySRV); } if (textureArray) *textureArray = pTexArray; else SAFE_RELEASE(pTexArray); return S_OK; } 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& 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轴的,因此需要令front=0, back=1, top=0, bottom=1才能表示当前的1D纹理,如果出现像back和front相等的情况,则不会选到任何的纹理像素区间。

而2D纹理没有Z轴,在选取像素区域前需要置front=0, back=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 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& cubeMapFileNames, ID3D11Texture2D ** textureArray, ID3D11ShaderResourceView ** textureCubeView, bool generateMips) { // 检查设备与设备上下文是否非空 // 文件名数目需要不小于6 // 纹理数组和资源视图只要有其中一个非空即可 UINT arraySize = (UINT)cubeMapFileNames.size(); if (!d3dDevice || !d3dDeviceContext || arraySize


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3