首先声明,本人是自学DX12,有很多的理解也许不到位,不过都是自己的理解。在很长一段时间里边,我从迷茫到有一天开始能看懂,现在是第三次开始刷DX12了,于是在此表明写作的初衷:
1.有一些DX12的学习心得,希望发出来,有大佬如果愿意指教,万分感谢;
2.如果对于才入门的人来说,这可能是我的白话教程,也许会对你有所帮助,但不可尽信,因为我也不确定我对不对;
3.DX12的概念很多,也是想把这作为自己的学习笔记来做,希望对自己也有帮助,如果有一天我发现哪里错了会及时回来更正。
那么话不多说,现在开始!!!
一、概述
纹理给人的感觉就是图片,但这种理解比较局限.....
你懂的,都是些套话,我不想这么写,因为我不想水字数,因此我就直接上强度吧:
1.纹理也是ID3D12Resource类型,但是不同于缓冲区,因为纹理可以有多个mipmap,这是要依赖与特定的数据结构的。
2.我们创建数据的时候一般都会给一个数据类型,但是有时候也可能是typeless,使用无类型。无类型会增加开销,因为涉及到数据的转化。不过对于无类型在创建描述符(视图)来进行解释的时候一定要明确。
3.纹理都要作为渲染管线使用的资源,都要存储到显存当中,为了减少存储空间的占用,一般会压缩纹理,但是经过压缩之后的纹理只可以作为着色器资源,不可以作为渲染目标。
二、创建纹理与数据传输
前提:使用DDS纹理,亲测PS方法有效,不过注意尺寸为4整数倍。
1.加载纹理资源(DDS文件)——数据
函数直接返回加载的数据texture以及对应的上传堆textureUploadHeap。
auto waterTex = std::make_unique<Texture>();
waterTex->Name = "waterTex";
waterTex->Filename = L"../../Textures/water1.dds";
ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(md3dDevice.Get(),
mCommandList.Get(), waterTex->Filename.c_str(),
waterTex->Resource, waterTex->UploadHeap));
2.资源描述符堆——存数据的“数组”
创建可存储三个纹理的描述符堆:
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)));
3.创建描述符——将数据放入“数组”
CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());//获得描述符堆的地址
偏移到准确地址:hDescriptor.Offset(1, mCbvSrvDescriptorSize);
waterTex = mTextures["waterTex"]->Resource;//数据的上传堆
//填写解释结构体
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = waterTex->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = -1;
//使用结构体创建:数据资源上传堆,解释结构,堆地址——将数据及其解释方法放到数组存起来
md3dDevice->CreateShaderResourceView(waterTex.Get(), &srvDesc, hDescriptor);
4.绑定到渲染流水线——根签名
CD3DX12_DESCRIPTOR_RANGE texTable;
//数量,寄存器:(书记)
texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
CD3DX12_ROOT_PARAMETER slotRootParameter[1];
slotRootParameter[0].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL);
根描述符表定义寄存器的槽,然后传入根参数,创建根签名后设置:
CD3DX12_GPU_DESCRIPTOR_HANDLE tex(mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
tex.Offset(ri->Mat->DiffuseSrvHeapIndex, mCbvSrvDescriptorSize);//获得纹理数据在显存的位置
cmdList->SetGraphicsRootDescriptorTable(0, tex);//根参数0号索引定义了寄存器槽,与显存资源绑定
根据绑定结果在shader中自动填充以下结构:
Texture2D gDiffuseMap : register(t0);
三、使用纹理资源
我们已经将纹理数据存入了显存,并且设置了其绑定到流水线,可自动填充shader中变量,但是该如何使用?——采样器。
采样器也是一种资源,我们先介绍一种简单的用法:
1.静态采样器
这种采用采样器虽然也是资源,但是不用创建堆来存储、解释,只需要进行如下两步设置:
1).填写静态采样器结构:
const CD3DX12_STATIC_SAMPLER_DESC pointWrap(
0, // shaderRegister
D3D12_FILTER_MIN_MAG_MIP_POINT, // 过滤方式
D3D12_TEXTURE_ADDRESS_MODE_WRAP, // 寻址方式
D3D12_TEXTURE_ADDRESS_MODE_WRAP, // 寻址方式
D3D12_TEXTURE_ADDRESS_MODE_WRAP); // 寻址方式
......
都传入数据staticSamplers;
2).使用根签名预留参数传入渲染流水线
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, slotRootParameter,
(UINT)staticSamplers.size(), staticSamplers.data(),//这里就是上边静态采样器的数据,以前我们总是设置为0和nullptr
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
经过以上两步设置,我们在shader中就能为如下结构传递数据:
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);
静态采样器最多可以创建2032个,有点类似跟常量或者是根描述符,不需要堆,但是会有一定的空间占用,那么如果超过2032个,我们还可以使用根描述符表来设置,这是需要堆来解释的,如下。
2.使用根描述符表设置的采样器
这部分我主要粘贴D3D的图片,因为静态采样的2032个采样器一般我们是用不完的,所以这部分没有多大的使用必要。
1).创建根签名
使用 rootSigDesc...完成根签名的创建,把采样器放在根参数的1号索引上
2).创建描述符堆和描述符来对采样器资源进行存储与解释
这个资源是解释采样的方式和参数等,比如过滤和寻址模式等:
堆:
描述符:将数据描述放入堆存起来
2).绑定到渲染流水线
根参数的1号索引记录的寄存器槽与描述符堆地址串联,方便数据自动填充到shader中
大概的形式如下:
SamplerState gsamPointWrap : register(s0);
3.shader中使用采样器
gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC);
//纹理.Sample(采样器,纹理坐标);
上述过程虽然没有一个完整的代码展示,但是只要你了解我们之前在初级篇讲解的所有内容,不难发现我们实际是没有很多新东西的,纹理资源的创建传输,采样器的使用等都有迹可循,希望你喜欢。