11.3 纹理数组
11.3.1 概述
纹理数组(texture array)对象用于存储一个纹理阵列。在C++代码中,纹理数组对象由ID3D11Texture2D接口表示(该接口也用于表示单个纹理对象)。其实,在创建ID3D11Texture2D对象时有一个称为ArraySize的属性可以设置所要存储的纹理对象的数量。不过,我们总是使用D3DX来创建纹理,所以不必直接设置这个数据成员。在effect文件中,纹理数组对象由Texture2DArray类型表示:
Texture2DArray gTreeMapArray;
现在,你可能不明白我们为什么要使用纹理数组对象。为什么不这样用:
Texture2D TexArray[4]; ... float4 PS(GeoOut pin) : SV_Target { float4 c = TexArray[pin.PrimID%4].Sample(samLinear, pin.Tex);
这会引发一个错误:“采样器数组索引必须是一个常量表达式”。换句话说,它不希望数组索引随着像素而变化。当我们指定一个常量数组索引时,该语句可以正常运行:
float4 c = TexArray[2].Sample(samLinear, pin.Tex);
但是与第一种方案相比,这条语句没多大用处。
11.3.2 对纹理数组进行采样
在树广告牌演示程序中,我们用如下代码对纹理数组进行采样:
float3 uvw = float3(pin.Tex, pin.PrimID%4); float4 diffuse = gTreeMapArray.Sample(samLinear,uvw);
当使用纹理数组时,我们需要3个纹理坐标。前两个纹理坐标是普通的2D纹理坐标;第3个纹理坐标是纹理数组的索引。例如,0.0是数组中的第1个纹理的索引,1.0是数组中的第2个纹理的索引,2.0是数组中的第3个纹理的索引,以此类推。
在树广告牌演示程序中,我们使用了一个包含4个元素的纹理数组,每个元素带有一种不同的树图像 (参见图11.7)。不过,我们绘制的图元数量不只4个,图元ID的值会大于 3。所以,我们让图元ID以4为模(pin.primID%4),把图元ID映射为0、1、2、3,得到一个有效的数组索引。
使用纹理数组的好处之一是,我们可以在一次绘图调用中使用不同的纹理来绘制一组图元。以前,我们必须这样做(伪代码):
SetTextureA(); DrawPrimitivesWithTextureA(); SetTextureB(); DrawPrimitivesWithTextureB(); ... SetTextureZ(); DrawPrimitivesWithTextureZ();
每个Set和Draw调用都会带来一些额外的系统开销。通过使用纹理数组,我们可以将其简化为一次Set调用和一次Draw调用:
SetTextureArray(); DrawPrimitivesWithTextureArray();
11.3.3 载入纹理数组
在撰写本书时,Direct3D并没有提供将一批图像文件同时载入纹理数组的D3DX函数。所以,我们必须自己完成一任务。实现过程归纳如下:
- 分别创建每个纹理对象。
- 创建纹理数组对象。
- 将每个纹理对象依次复制到纹理数组元素中去。
- 为纹理数组对象创建一个着色器资源视图。
我们在d3dUtil.h/cpp中实现了一个辅助函数用来从一个文件名列表创建纹理数组。这些纹理的大小要求是相同的。具体的实现代码如下。
ID3D11ShaderResourceView* d3dHelper::CreateTexture2DArraySRV( ID3D11Device* device, ID3D11DeviceContext* context, std::vector<std::wstring>& filenames, DXGI_FORMAT format, UINT filter, UINT mipFilter) { // // 从文件加载单独的纹理元素。这些纹理不能被GPU使用(0 bind flags), // 只是用来从文件中加载图像数据。我们使用STAGING让CPU可以访问资源。 // UINT size = filenames.size(); std::vector<ID3D11Texture2D*> srcTex(size); for(UINT i = 0; i < size; ++i) { D3DX11_IMAGE_LOAD_INFO loadInfo; loadInfo.Width = D3DX11_FROM_FILE; loadInfo.Height = D3DX11_FROM_FILE; loadInfo.Depth = D3DX11_FROM_FILE; loadInfo.FirstMipLevel = 0; loadInfo.MipLevels = D3DX11_FROM_FILE; loadInfo.Usage = D3D11_USAGE_STAGING; loadInfo.BindFlags = 0; loadInfo.CpuAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ; loadInfo.MiscFlags = 0; loadInfo.Format = format; loadInfo.Filter = filter; loadInfo.MipFilter = mipFilter; loadInfo.pSrcInfo = 0; HR(D3DX11CreateTextureFromFile(device, filenames[i].c_str(), &loadInfo, 0, (ID3D11Resource**)&srcTex[i], 0)); } // // 创建纹理数组。每个纹理元素都具有相同的格式/大小。 // D3D11_TEXTURE2D_DESC texElementDesc; srcTex[0]->GetDesc(&texElementDesc); D3D11_TEXTURE2D_DESC texArrayDesc; texArrayDesc.Width = texElementDesc.Width; texArrayDesc.Height = texElementDesc.Height; texArrayDesc.MipLevels = texElementDesc.MipLevels; texArrayDesc.ArraySize = size; texArrayDesc.Format = texElementDesc.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 = 0; ID3D11Texture2D* texArray = 0; HR(device->CreateTexture2D( &texArrayDesc, 0, &texArray)); // // 将单独的纹理元素复制到纹理数组中。 // // 处理每个纹理元素... for(UINT texElement = 0; texElement < size; ++texElement) { // 处理每个渐进纹理层... for(UINT mipLevel = 0; mipLevel < texElementDesc.MipLevels; ++mipLevel) { D3D11_MAPPED_SUBRESOURCE mappedTex2D; HR(context->Map(srcTex[texElement], mipLevel, D3D11_MAP_READ, 0, &mappedTex2D)); context->UpdateSubresource(texArray, D3D11CalcSubresource(mipLevel, texElement, texElementDesc.MipLevels), 0, mappedTex2D.pData, mappedTex2D.RowPitch, mappedTex2D.DepthPitch); context->Unmap(srcTex[texElement], mipLevel); } } // // 创建纹理数组的资源视图 // 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 = size; ID3D11ShaderResourceView* texArraySRV = 0; HR(device->CreateShaderResourceView(texArray, &viewDesc, &texArraySRV)); // // 清除--我们要的只是资源视图 // ReleaseCOM(texArray); for(UINT i = 0; i < size; ++i) ReleaseCOM(srcTex[i]); return texArraySRV; }
ID3D11DeviceContext::UpdateSubresource方法通过CPU将一个子资源复制给另一个子资源(该方法的参数描述请参见SDK文档)。
void ID3D11DeviceContext::UpdateSubresource( ID3D11Resource *pDstResource, UINT DstSubresource, const D3D11_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch);
1.pDstResource:目标资源对象。
2.DstSubresource:我们要更新的目标资源中的子资源索引(见11.3.4节)。
3.pDstBox:指向一个D3D11_BOX实例的指针,指定要更新的目标子资源的大小,若设定为null则更新整个子资源。
4.pSrcData:指向源数据的指针。
5.SrcRowPitch:源数据一行的大小,以字节为单位。
6.SrcDepthPitch:源数据一个深度的大小,以字节为单位。
注意,对于2D纹理而言,SrcDepthPitch参数看起来是不必要的;但是这个方法还要用于更新3D纹理,这种情况下此参数可被视为一个2D纹理堆。
我们在初始化时调用前面的函数创建树图像的纹理数组:
ID3D11ShaderResourceView* mTreeTextureMapArraySRV; mTreeTextureMapArraySRV = d3dHelper::CreateTexture2DArraySRV(md3dDevice,md3dImmediateContext, treeFilenames ,DXGI_FORMAT_R8G8B8A8_UNORM);
11.3.4 纹理子资源
现在我们已经讨论了纹理数组,下面我们来讨论子资源(subresources)的概念。图11.8展示了一个包含多个纹理的纹理数组。其中的每个纹理都有它自己的多级渐近纹理链。Direct3D API使用术语“数组切片(array slice)”表示一个完整的多级渐近纹理链中的一个元素,使用术语 “多级渐近切片(mip slice)”表示纹理数组中的所有多级渐近纹理链的特定层。“子资源”表示纹理数组元素中的单个多级渐近纹理层。
每个纹理带有3个多级渐近纹理层。只要给定一个数组切片索引和一个多级渐近切片索引,我们就可以访问纹理数组中的任何一个子资源。不过,子资源也可以通过线性索引来标识;Direct3D按照如图11.9所示的顺序标识线性索引。
下面的工具函数用于计算线性子资源索引,它的3个参数分别表示多级渐近切片的索引、数组切片的索引和多级渐近纹理层的数量:
inline UINT D3D11CalcSubresource( UINT MipSlice, UINT ArraySlice, UINT MipLevels);
使用的公式很简单:k = ArraySlice*MipLevels + MipSlice。
文件下载(已下载 939 次)发布时间:2014/8/15 下午8:49:23 阅读次数:4678