代码工程地址:
https://github.com/jiabaodan/Direct12BookReadingNotes
学习目标
- 学习如何定义将一个纹理映射到一个三角形上;
- 学习如何创建和启用纹理;
- 学习纹理如何被过滤后生产一个更加平滑的图像;
- 学习如何将一个纹理通过地址模式展开多次;
- 学习如何将多个纹理合并成一个新贴图和特殊效果;
- 学习一些基本的纹理动画生成的效果。
1 纹理和资源回顾
回顾我们已经在第四章使用过的纹理,深度缓存和后台缓存都是一张2D纹理,它们由ID3D12Resource包含的值为D3D12_RESOURCE_DIMENSION_TEXTURE2D的D3D12_RESOURCE_DESC::Dimension属性的接口来表示。
纹理和缓冲(buffer)不同,它不仅仅是保存数据,它还可以包含纹理细化等级(mipmap levels),并且GPU可以对它做特殊操作,比如应用过滤器(filters)和多重纹理映射。因为要支持这些特殊操作,所以它的格式有限制。格式通过DXGI_FORMAT枚举来定义:
纹理格式可以定义完整的类型,比如:DXGI_FORMAT_R32G32B32_FLOAT,包含3个32位浮点数;也可以定义无类型格式,比如:DXGI_FORMAT_R8G8B8A8_TYPELESS。
根据DX11的文档:定义完整类型的格式,可以进行运行时的优化。也就是说为了性能,只有当你真正需要无类型格式,否则都定义成完整类型的格式。
纹理可以绑定到渲染管线很多阶段,重用的方式是做为一个渲染目标,或者着色器的资源。为了让纹理用作渲染目标和着色器资源,我们需要创建2个描述:1、D3D12_DESCRIPTOR_HEAP_TYPE_RTV;2、D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV:
// Bind as render target.
CD3DX12_CPU_DESCRIPTOR_HANDLE rtv = …;
CD3DX12_CPU_DESCRIPTOR_HANDLE dsv = …;
cmdList->OMSetRenderTargets(1, &rtv, true, &dsv);
// Bind as shader input to root parameter.
CD3DX12_GPU_DESCRIPTOR_HANDLE tex = …;
cmdList->SetGraphicsRootDescriptorTable(rootParamIndex, tex);
资源描述本质上做了2件事情:1、告诉D3D该资源将如何被使用;2、如果资源定义时是无类型的,那么需要定义它的类型。
2 纹理坐标
为了添加贴图u,v坐标,修改定点结构如下:
struct Vertex
{
DirectX::XMFLOAT3 Pos;
DirectX::XMFLOAT3 Normal;
DirectX::XMFLOAT2 TexC;
};
>>>>>>>>>>>>>>>>>>
std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout =
{
{
"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0
},
{
"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0
},
{
"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0
},
};
我们可以将多个图片放到一个大纹理中(texture atlas),然后应用于多个物体。这样可以避免多次资源加载,减少DrawCall,优化性能。
3 纹理数据资源
对于实时图形应用,DDS(DirectDraw Surface format)文件格式更好,GPU可以直接原生使用很多它的文件格式;并且它支持压缩,可以让GPU原生解压缩。
3.1 DDS概览
DDS是3D图形更理想的格式是因为,它支持专门针对3D图形设计的特殊格式和纹理类型。它本质上是针对GPU设计的图像类型。比如DDS支持下面的特征:
- mipmaps;
- compressed formats that the GPU can natively decompress;
- texture arrays;
- cube maps;
- volume textures。
DDS文件支持DXGI_FORMAT枚举中的部分类型(不是全部),对于非压缩的数据可以使用:
- DXGI_FORMAT_B8G8R8A8_UNORM或者DXGI_FORMAT_B8G8R8X8_UNORM,用以low-dynamic-range图像;
- DXGI_FORMAT_R16G16B16A16_FLOAT用以high-dynamic-range图像。
为了让纹理能够快速应用,我们需要将它们放到GPU内存中。为了解决这些需求,D3D支持压缩纹理格式:BC1, BC2, BC3, BC4, BC5, BC6, 和 BC7:
- BC1 (DXGI_FORMAT_BC1_UNORM):一个支持三个颜色通道和一个1bit alpha组件的压缩格式;
- BC2 (DXGI_FORMAT_BC2_UNORM):一个支持三个颜色通道和一个4bit alpha组件的压缩格式;
- BC3 (DXGI_FORMAT_BC3_UNORM):一个支持三个颜色通道和一个8bit alpha组件的压缩格式;
- BC4 (DXGI_FORMAT_BC4_UNORM):一个支持完整一个颜色通道的压缩格式;
- BC5 (DXGI_FORMAT_BC5_UNORM):一个支持2个完整颜色通道的压缩格式;
- BC6 (DXGI_FORMAT_BC6_UF16):一个支持压缩HDR图像数据的压缩格式;
- BC7 (DXGI_FORMAT_BC7_UNORM):一个支持高质量RGBA的压缩格式,它主要用以减少压缩法相贴图导致的错误。
一个压缩的纹理只能用以着色器输入参数,不能用以渲染目标。
因为块压缩算法是以4x4像素块来工作的,所以纹理的大小必须是4的倍数。
再次声明,使用这个格式的好处是它们可以压缩保存在GPU内存,并且当要使用的时候可以直接被GPU解压缩;另一个好处是可以节省你的硬盘空间。
3.2 创建DDS文件
下面是两种可以将传统文件(.bmp .png等)转换成DDS文件的方法:
- NVIDIA有一个PS的插件可以支持在PS中导出DDS文件:https://developer.nvidia.com/nvidiatexture-tools-adobe-photoshop,它可以支持设置DXGI_FORMAT格式和生成mipmaps;
- 微软提供了一个叫texconv的命令行工具,可以转换成DDS文件。它还可以用来改变图像大小,修改像素格式生成mipmaps等,你可以找到对应文档和下载链接在:https://directxtex.codeplex.com/wikipage?title=Texconv&referringTitle=Documentation
下面的命令就是一个例子,输入一个bricks.bmp文件,导出一个bricks.dds文件并设置格式为BC3_UNORM并且创建10 mipmaps:
texconv -m 10 -f BC3_UNORM treeArray.dds
微软提供了另一个叫texassemble的命令行工具,可以用来创建保存了texture arrays, volume maps, and cube maps的DDS文件,它的文档和下载链接:https://directxtex.codeplex.com/wikipage?title=Texassemble&referringTitle=Documentation
VS2015有一个内置的图像编辑器可以支持DDS文件。你可以直接拖拽DDS文件到VS中查看。
4 创建和使用一个纹理
4.1 加载DDS文件
微软提供了一个轻量级源代码来加载DDS文件:
https://github.com/Microsoft/DirectXTK/wiki/DDSTextureLoader
但是在写本书的时候,这个代码只能支持DX11。我们需要修改DDSTextureLoader.h/.cpp文件来支持DX12:
HRESULT DirectX::CreateDDSTextureFromFile12(
_In_ ID3D12Device* device,
_In_ ID3D12GraphicsCommandList* cmdList,
_In_z_ const wchar_t* szFileName,
_Out_ Microsoft::WRL::ComPtr<ID3D12Resource>& texture,
_Out_ Microsoft::WRL::ComPtr<ID3D12Resource>& textureUploadHeap);
- device:指向要创建纹理的D3D设备;
- cmdList:向GPU提交命令的命令列表;
- szFileName:需要加载的文件名称;
- texture:返回加载好数据的纹理资源;
- textureUploadHeap:返回一个使用为将资源赋值到默认堆的上传堆的纹理资源,这个资源在GPU指向完命令前不能被销毁。
为了创建加载一个叫WoodCreate01.dds的纹理,我们可以这样写:
struct Texture
{
// Unique material name for lookup.
std::string Name;
std::wstring Filename;
Microsoft::WRL::ComPtr<ID3D12Resource> Resource = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> UploadHeap = nullptr;
};
auto woodCrateTex = std::make_unique<Texture>();
woodCrateTex->Name = "woodCrateTex";
woodCrateTex->Filename = L"Textures/WoodCrate01.dds";
ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(
md3dDevice.Get(), mCommandList.Get(),
woodCrateTex->Filename.c_str(),
woodCrateTex->Resource,
woodCrateTex->UploadHeap));
4.2 SRV堆
当一个纹理资源被创建以后,我们需要创建一个可以让我们把它设置到根签名参数槽来让着色器程序使用的SRV描述。为了达到这个目的,我们需要先使用ID3D12Device::CreateDescriptorHeap创建一个描述堆来保存SRV描述。下面的代码创建了3个描述可以保存CBV,SRV或者UAV描述:
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
srvHeapDesc.NumDescriptors = 3;
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&srvHeapDesc,
IID_PPV_ARGS(&mSrvDescriptorHeap)));
4.3 创建SRV描述
当我们创建后SRV对后,我们需要创建真正的描述。一个SRV描述是通过填D3D12_SHADER_RESOURCE_VIEW_DESC对象来创建: