D3D12渲染技术之创建和启用纹理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jxw167/article/details/82835088

纹理在任何引擎都有使用,它其实就是为模型提供的纹理材质,既然我们介绍的是D3D12,那就从dds文件介绍说起。

加载DDS文件

Microsoft提供了轻量级源代码来加载DDS文件:
https://github.com/Microsoft/DirectXTK/wiki/DDSTextureLoader

但是,代码仅支持DirectX 11,我们修改了DDSTextureLoader.h / .cpp文件并为DirectX 12提供了另一种方法(这些修改过的文件可以在Common文件夹中找到或者 可下载的来源):

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);

要从名为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));

SRV 堆

一旦创建了纹理资源,我们需要为它创建一个SRV描述符,我们可以将其设置为根签名参数槽以供着色器程序使用。 为此,我们首先需要使用ID3D12Device :: CreateDescriptorHeap创建描述符堆来存储SRV描述符。 以下代码构建一个具有三个描述符的堆,这些描述符可以存储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)));

创建SRV描述符

一旦我们有了SRV堆,我们就需要创建实际的描述符, 通过填写D3D12_SHADER_RESOURCE_VIEW_DESC对象来描述SRV描述符,该对象描述资源的使用方式和其他信息 - 其格式,维度,mipmaps数量等。

typedef struct D3D12_SHADER_RESOURCE_VIEW_DESC
{
  DXGI_FORMAT Format;
  D3D12_SRV_DIMENSION ViewDimension;
  UINT Shader4ComponentMapping;
  union 
  {
    D3D12_BUFFER_SRV Buffer;
    D3D12_TEX1D_SRV Texture1D;
    D3D12_TEX1D_ARRAY_SRV Texture1DArray;
    D3D12_TEX2D_SRV Texture2D;
    D3D12_TEX2D_ARRAY_SRV Texture2DArray;
    D3D12_TEX2DMS_SRV Texture2DMS;
    D3D12_TEX2DMS_ARRAY_SRV Texture2DMSArray;
    D3D12_TEX3D_SRV Texture3D;
    D3D12_TEXCUBE_SRV TextureCube;
    D3D12_TEXCUBE_ARRAY_SRV TextureCubeArray;
  };
} D3D12_SHADER_RESOURCE_VIEW_DESC;
 
typedef struct D3D12_TEX2D_SRV
{
  UINT MostDetailedMip;
  UINT MipLevels;
  UINT PlaneSlice;
  FLOAT ResourceMinLODClamp;
} D3D12_TEX2D_SRV;

关于上述函数参数的含义,大家可以查阅SDK文档,下面将我们在上一节中创建的堆填充为三个资源的实际描述符:

// Suppose the following texture resources are already created.
// ID3D12Resource* bricksTex;
// ID3D12Resource* stoneTex;
// ID3D12Resource* tileTex;
 
// Get pointer to the start of the heap.
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_TEXTUsrvDesc.Format = stoneTex->GetDesc().Format;
srvDesc.Texture2D.MipLevels = stoneTex->GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(stoneTex.Get(), &srvDesc, hDescriptor);
 
// offset to next descriptor in heap
hDescriptor.Offset(1, mCbvSrvDescriptorSize);
 
srvDesc.Format = tileTex->GetDesc().Format;RE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = bricksTex->GetDesc().MipLevels;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
md3dDevice->CreateShaderResourceView(bricksTex.Get(), &srvDesc, hDescriptor);
 
// offset to next descriptor in heap
hDescriptor.Offset(1, mCbvSrvDescriptorSize);
srvDesc.Texture2D.MipLevels = tileTex->GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(tileTex.Get(), &srvDesc, hDescriptor);

绑定纹理到管道

现在我们通过更改材质常量缓冲区来指定每个绘制调用的材质, 这意味着绘制调用中的所有几何体都将具有相同的材质值, 因为我们无法指定每个像素的材质变化,所以我们的场景缺乏细节。 纹理贴图的想法是从纹理贴图而不是材质常量缓冲区获取材质数据, 这允许像素变化,这增加了我们场景的细节和真实感。
在本博客中,我们添加一个漫反射反照率纹理贴图来指定材质的漫反射反照率分量, FresnelR0和Roughness材质值仍将通过材质常数缓冲区以每个绘制调用频率指定;但是,在后面的博客中,我们将介绍如何使用纹理来指定每像素级别的粗糙度, 请注意,通过纹理化,我们仍然会将DiffuseAlbedo组件保留在材质常量缓冲区中。 实际上,我们将在像素着色器中以下列方式将其与纹理漫反射反照率值组合:

// Get diffuse albedo at this pixel from texture.
float4 texDiffuseAlbedo = gDiffuseMap.Sample(
  gsamAnisotropicWrap, pin.TexC);
  // Multiple texture sample with constant buffer albedo.
float4 diffuseAlbedo = texDiffuseAlbedo * gDiffuseAlbedo;

通常,我们将设置DiffuseAlbedo =(1,1,1,1),以便不修改texDiffuseAlbedo。 但是,有时稍微调整漫反射而不必创建新纹理, 例如,假设我们有一个砖纹理,而美工想要略微淡化蓝色, 这可以通过设置DiffuseAlbedo =(0.9,0.9,1,1)来减少红色和绿色成分来实现。
我们在材质定义中添加一个索引,该索引引用描述符堆中的SRV,指定与材质关联的纹理:

struct Material
{
  …
 
  // Index into SRV heap for diffuse texture.
  int DiffuseSrvHeapIndex = -1;
 
  …
};

然后,假设已经定义了根签名以期望将着色器资源视图表绑定到第0个槽参数,我们可以使用以下代码绘制具有纹理的渲染项:

void CrateApp::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 each render item…
  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);
  }
}

任何着色器(顶点,几何或像素着色器)实际上都可以使用纹理资源, 现在,我们将在像素着色器中使用它们。 正如我们所提到的,纹理本质上是支持GPU上特殊操作的特殊数组,所以不难想象它们在其他着色器程序中也很有用。
另外,纹理图集可以提高性能,因为它可以通过一次绘制调用绘制更多几何图形。 例如,假设我们使用了前面博客中所提到的的纹理图集,其中包含板条箱,草和砖纹理。 然后,通过将每个对象的纹理坐标调整到其对应的子纹理,我们可以将所有几何体放在一个渲染项中(假设每个对象不需要更改其他参数)。 绘制调用有开销,因此最好使用这样的技术将它们最小化,尽管我们注意到与早期版本的Direct3D相比,Direct3D 12的开销显着降低。

阅读更多

扫码向博主提问

海洋_

博客专家

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • 3D引擎架构
  • 服务器架构
  • GPU渲染
  • 客户端架构
  • 引擎优化
去开通我的Chat快问
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页