在DirectX12中使用Texture
这次让我们来看看如何在DirectX12中使用Texture。由于我们创建的texture最终是和const buffer一样,存放在同一个DescriptorHeap
中的,所以有必要在之前的基础上扩充一下heap的大小:
mCbvSrvHeapIncSize = mDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
D3D12_DESCRIPTOR_HEAP_DESC cbvSrvHeapDesc;
cbvSrvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvSrvHeapDesc.NumDescriptors = objCbCount + passCbCount + matCbCount + shaderResCount;
cbvSrvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
cbvSrvHeapDesc.NodeMask = 0;
ThrowIfFailed(mDevice->CreateDescriptorHeap(&cbvSrvHeapDesc, IID_PPV_ARGS(&mCbvSrvHeap)));
接下来,我们知道,采样一个纹理有多种采样过滤模式,point,linear,anisotropic;寻址模式也有wrap,mirror,clamp等等。我们可以事先定义好用到的sampler:
mStaticSamplers.push_back(CD3DX12_STATIC_SAMPLER_DESC(0, D3D12_FILTER_MIN_MAG_MIP_POINT,
D3D12_TEXTURE_ADDRESS_MODE_WRAP, D3D12_TEXTURE_ADDRESS_MODE_WRAP));
mStaticSamplers.push_back(CD3DX12_STATIC_SAMPLER_DESC(1, D3D12_FILTER_MIN_MAG_MIP_POINT,
D3D12_TEXTURE_ADDRESS_MODE_CLAMP, D3D12_TEXTURE_ADDRESS_MODE_CLAMP));
mStaticSamplers.push_back(CD3DX12_STATIC_SAMPLER_DESC(2, D3D12_FILTER_MIN_MAG_MIP_LINEAR,
D3D12_TEXTURE_ADDRESS_MODE_WRAP, D3D12_TEXTURE_ADDRESS_MODE_WRAP));
mStaticSamplers.push_back(CD3DX12_STATIC_SAMPLER_DESC(3, D3D12_FILTER_MIN_MAG_MIP_LINEAR,
D3D12_TEXTURE_ADDRESS_MODE_CLAMP, D3D12_TEXTURE_ADDRESS_MODE_CLAMP));
mStaticSamplers.push_back(CD3DX12_STATIC_SAMPLER_DESC(4, D3D12_FILTER_ANISOTROPIC,
D3D12_TEXTURE_ADDRESS_MODE_WRAP, D3D12_TEXTURE_ADDRESS_MODE_WRAP));
mStaticSamplers.push_back(CD3DX12_STATIC_SAMPLER_DESC(5, D3D12_FILTER_ANISOTROPIC,
D3D12_TEXTURE_ADDRESS_MODE_CLAMP, D3D12_TEXTURE_ADDRESS_MODE_CLAMP));
然后,在创建root signature
的时候,将samplers作为参数传进去(存放于寄存器s0,s1,…)。由于这次需要传递texture资源给shader使用(存放于寄存器t0,t1,…),我们需要增加一个根参数:
CD3DX12_DESCRIPTOR_RANGE cbvSrvTable[4];
cbvSrvTable[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
cbvSrvTable[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 1);
cbvSrvTable[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 2);
cbvSrvTable[3].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
CD3DX12_ROOT_PARAMETER rootParams[4];
rootParams[0].InitAsDescriptorTable(1, &cbvSrvTable[0]);
rootParams[1].InitAsDescriptorTable(1, &cbvSrvTable[1]);
rootParams[2].InitAsDescriptorTable(1, &cbvSrvTable[2]);
rootParams[3].InitAsDescriptorTable(1, &cbvSrvTable[3]);
CD3DX12_ROOT_SIGNATURE_DESC sigDesc(_countof(rootParams), rootParams, mStaticSamplers.size(),
mStaticSamplers.data(), D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
这些准备工作做完之后,就可以正式去加载texture资源了,调用加载接口之后,不要忘记手动向GPU提交执行,否则这个texture就只能CPU看见,GPU还是一无所知。加载成功之后,还要记得创建对应的shader resource view
,将资源绑定到descriptor heap
上,并存放到正确的位置:
ThrowIfFailed(mCommandAlloc->Reset());
ThrowIfFailed(mCommandList->Reset(mCommandAlloc.Get(), nullptr));
ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(mDevice.Get(), mCommandList.Get(), srcFile.c_str(), res, uploadHeap));
ThrowIfFailed(mCommandList->Close());
ID3D12CommandList *cmdList[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdList), cmdList);
FlushCommandQueue();
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = res->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = res->GetDesc().MipLevels;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
CD3DX12_CPU_DESCRIPTOR_HANDLE handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(
mCbvSrvHeap->GetCPUDescriptorHandleForHeapStart());
UINT srvHeapIndex = objCbCount + passCbCount + matCbCount + texId;
handle.Offset(srvHeapIndex, mCbvSrvHeapIncSize);
mDevice->CreateShaderResourceView(res.Get(), &srvDesc, handle);
return true;
最后,在绘制阶段,我们要显式地告诉GPU从哪儿读取texture资源加载到对应的寄存器上:
UINT srvHeapIndex = objCbCount + passCbCount + matCbCount + object->mTexture->mTexId;
CD3DX12_GPU_DESCRIPTOR_HANDLE srvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(
mCbvSrvHeap->GetGPUDescriptorHandleForHeapStart());
srvHandle.Offset(srvHeapIndex, mCbvSrvHeapIncSize);
mCommandList->SetGraphicsRootDescriptorTable(3, srvHandle);
这里,可能就有疑问了,为啥要把shader resource和const buffer都存放到一个descriptor heap
上管理呢?分开用两个heap,各用各的不是更好吗?具体的解释可以参考这里。简单来说,首先SetDescriptorHeaps
这个API同一类型的heap同时只能设置一个,而存放const buffer和shader resource的heap的type都是D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV 。其次,如果我们在绘制过程中频繁调用这个API去切换使用不同的heap,开销会非常高。所以,推荐的解决方法,就是使用一个大的heap,然后划分好不同的区域给const buffer和shader resource使用。
如果你觉得我的文章有帮助,欢迎关注我的微信公众号(大龄社畜的游戏开发之路)-