1. 程式人生 > >D3D12渲染技術之建立和啟用紋理

D3D12渲染技術之建立和啟用紋理

紋理在任何引擎都有使用,它其實就是為模型提供的紋理材質,既然我們介紹的是D3D12,那就從dds檔案介紹說起。

載入DDS檔案

但是,程式碼僅支援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的開銷顯著降低。