纹理贴图(texture mapping)将图像数据映射到三角形网格上,使场景更具真实感。
1.纹理结构
struct Texture
{
//材质名(唯一)
std::string Name;
//材质路径
std::wstring Filename;
//载有图像数据的纹理资源
Microsoft::WRL::ComPtr<ID3D12Resource> Resource = nullptr;
//纹理资源,作为上传堆,将图像数据复制到默认堆
Microsoft::WRL::ComPtr<ID3D12Resource> UploadHeap = nullptr;
};
2.加载DDS纹理
DDS纹理能够很好地满足3D图形开发。它具有如下特征:
- mipmap
- GPU能自行解压的压缩格式
- 纹理数组
- 立方体贴图(cube map)
- 体纹理(volume texture)
DirectX12提供读取DDS纹理的方法。
auto bricksTex = std::make_unique<Texture>();
bricksTex->Name = "bricksTex";
bricksTex->Filename = L"../../Textures/bricks.dds";
ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(
md3dDevice.Get(), // D3D设备
mCommandList.Get(), // 提交GPU的命令列表
bricksTex->Filename.c_str(), // 图像文件路径
bricksTex->Resource, // 载有图像数据的纹理资源
bricksTex->UploadHeap)); // 纹理资源,作为上传堆,将图像数据复制到默认堆
3.创建描述符堆
创建描述符堆,用以存储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.创建SRV(着色器资源视图)
通过描述资源的类型、格式、维数、mipmap数量等描述SRV描述符。
// 纹理资源
auto bricksTex = mTextures["bricksTex"]->Resource;
auto stoneTex = mTextures["stoneTex"]->Resource;
auto tileTex = mTextures["tileTex"]->Resource;
// 获取描述符堆起始处的指针
CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
// 该对象详述了资源信息
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; //在着色器中对纹理采样时,它将返回特定纹理坐标处的纹理数据向量
srvDesc.Format = bricksTex->GetDesc().Format; //视图的格式
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; //资源的维数
srvDesc.Texture2D.MostDetailedMip = 0; //指定此视图中,图像细节最详尽的mipmap层级的索引
srvDesc.Texture2D.MipLevels = bricksTex->GetDesc().MipLevels; //此视图的mipmap层级数量
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f; //指定可以访问的最小mipmap层级。0表示可以访问所有的层级
md3dDevice->CreateShaderResourceView(bricksTex.Get(), &srvDesc, hDescriptor); //创建SRV
// 偏移到堆中下一个描述符处
hDescriptor.Offset(1, mCbvSrvDescriptorSize);
srvDesc.Format = stoneTex->GetDesc().Format;
srvDesc.Texture2D.MipLevels = stoneTex->GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(stoneTex.Get(), &srvDesc, hDescriptor); //创建SRV
// 偏移到堆中下一个描述符处
hDescriptor.Offset(1, mCbvSrvDescriptorSize);
srvDesc.Format = tileTex->GetDesc().Format;
srvDesc.Texture2D.MipLevels = tileTex->GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(tileTex.Get(), &srvDesc, hDescriptor); //创建SRV
5.将纹理绑定到流水线
向材质的定义中添加一个索引,引用相关联的纹理描述堆中的一个SRV(着色器资源视图)。
struct Material
{
...
int DiffuseSrvHeapIndex = -1; //漫反射纹理在SRV堆中的索引
...
}
假设根签名被定义为需要把由着色器资源视图构成的描述符表绑定到第0个槽处,那么可以通过下列代码绘制渲染项
void TexWavesApp::DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector<RenderItem*>& ritems)
{
UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
UINT matCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(MaterialConstants));
auto objectCB = mCurrFrameResource->ObjectCB->Resource();
auto matCB = mCurrFrameResource->MaterialCB->Resource();
// 对每一个渲染项
for(size_t i = 0; i < ritems.size(); ++i)
{
auto ri = ritems[i];
cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView());
cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView());
cmdList->IASetPrimitiveTopology(ri->PrimitiveType);
CD3DX12_GPU_DESCRIPTOR_HANDLE tex(mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
tex.Offset(ri->Mat->DiffuseSrvHeapIndex, mCbvSrvDescriptorSize);
D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = objectCB->GetGPUVirtualAddress() + ri->ObjCBIndex*objCBByteSize;
D3D12_GPU_VIRTUAL_ADDRESS matCBAddress = matCB->GetGPUVirtualAddress() + ri->Mat->MatCBIndex*matCBByteSize;
cmdList->SetGraphicsRootDescriptorTable(0, tex);
cmdList->SetGraphicsRootConstantBufferView(1, objCBAddress);
cmdList->SetGraphicsRootConstantBufferView(3, matCBAddress);
cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);
}
}