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。
文件下载(已下载 941 次)发布时间:2014/8/15 下午8:49:23 阅读次数:5391
