1. 程式人生 > >D3D12渲染技術之渲染

D3D12渲染技術之渲染

我們在繪製物件時需要設定多個引數,例如繫結頂點和索引緩衝區,繫結物件常量,設定基元型別以及指定DrawIndexedInstanced引數。 當我們開始在場景中繪製更多物件時,建立一個儲存繪製物件所需資料的輕量級結構會很有幫助。 這些資料因應用程式而異,因為我們添加了需要不同繪圖資料的新功能。 我們將提交完整繪製所需的資料集稱為渲染管道渲染項,我們的Render Item結構如下所示:

// Lightweight structure stores parameters to draw a shape. This will
// vary from app-to-app.
struct RenderItem
{
  RenderItem() = default;
 
  // World matrix of the shape that describes the object’s local space
  // relative to the world space, which defines the position, 
  // orientation, and scale of the object in the world.
  XMFLOAT4X4 World = MathHelper::Identity4x4();
 
  // Dirty flag indicating the object data has changed and we need 
  // to update the constant buffer. Because we have an object 
  // cbuffer for each FrameResource, we have to apply the
  // update to each FrameResource. Thus, when we modify obect data we
  // should set
  //  NumFramesDirty = gNumFrameResources so that each frame resource
  // gets the update.
  int NumFramesDirty = gNumFrameResources;
 
  // Index into GPU constant buffer corresponding to the ObjectCB 
  // for this render item.
  UINT ObjCBIndex = -1;
 
  // Geometry associated with this render-item. Note that multiple
  // render-items can share the same geometry.
  MeshGeometry* Geo = nullptr;
 
  // Primitive topology.
  D3D12_PRIMITIVE_TOPOLOGY PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
 
  // DrawIndexedInstanced parameters.
  UINT IndexCount = 0;
  UINT StartIndexLocation = 0;
  int BaseVertexLocation = 0;
};

我們的應用程式將根據需要繪製的方式維護渲染專案列表;也就是說,需要不同PSO的渲染專案將儲存在不同的列表中。

std::vector<std::unique_ptr<RenderItem>> mAllRitems;
 
// Render items divided by PSO.
std::vector<RenderItem*> mOpaqueRitems;
std::vector<RenderItem*> mTransparentRitems;

傳遞常數

從上一篇部落格中可以看出,我們在幀資源類中引入了一個新的常量緩衝區:

std::unique_ptr<UploadBuffer<PassConstants>> PassCB = nullptr;

在Demo中,此緩衝區儲存在給定渲染過程中固定的常量資料,例如攝像機位置,檢視和投影矩陣,以及有關螢幕(渲染目標)尺寸的資訊; 它還包括遊戲計時資訊,這是在著色器程式中可以訪問的有用資料。 請注意,我們的演示不一定會使用所有這些常量資料,但可以方便地使用,並且提供額外資料的成本很低。 例如,雖然我們現在不需要渲染目標大小,但是當我們實現一些後期處理效果時,將需要具有該資訊。

cbuffer cbPass : register(b1)
{
  float4x4 gView;
  float4x4 gInvView;
  float4x4 gProj;
  float4x4 gInvProj;
  float4x4 gViewProj;
  float4x4 gInvViewProj;
  float3 gEyePosW;
  float cbPerObjectPad1;
  float2 gRenderTargetSize;
  float2 gInvRenderTargetSize;
  float gNearZ;
  float gFarZ;
  float gTotalTime;
  float gDeltaTime;
};

我們還修改了每個物件常量緩衝區,以僅儲存與物件關聯的常量。 到目前為止,我們與繪圖物件關聯的唯一常量資料是其世界矩陣:

cbuffer cbPerObject : register(b0)
{
   float4x4 gWorld; 
};

這些更改的想法是根據更新頻率對常量進行分組, 每次通過常量只需要在每次渲染過程中更新一次,並且物件常量只需要在物件的世界矩陣發生變化時進行更改。 如果我們在場景中有一個靜態物件,就像一棵樹,我們只需要將其世界矩陣設定一次到一個常量緩衝區,然後再也不要更新常量緩衝區。 在我們的Demo中,我們實現了以下方法來處理每次傳遞和每個物件常量緩衝區的更新, Update方法中每幀呼叫一次這些方法。

void ShapesApp::UpdateObjectCBs(const GameTimer& gt)
{
  auto currObjectCB = mCurrFrameResource->ObjectCB.get();
  for(auto& e : mAllRitems)
  {
    // Only update the cbuffer data if the constants have changed. 
    // This needs to be tracked per frame resource.
    if(e->NumFramesDirty > 0)
    {
      XMMATRIX world = XMLoadFloat4x4(&e->World);
 
      ObjectConstants objConstants;
      XMStoreFloat4x4(&objConstants.World, XMMatrixTranspose(world));
 
      currObjectCB->CopyData(e->ObjCBIndex, objConstants);
 
      // Next FrameResource need to be updated too.
      e->NumFramesDirty--;
    }
  }
}

 void ShapesApp::UpdateMainPassCB(const GameTimer& gt)
{
  XMMATRIX view = XMLoadFloat4x4(&mView);
  XMMATRIX proj = XMLoadFloat4x4(&mProj);
 
  XMMATRIX viewProj = XMMatrixMultiply(view, proj);
  XMMATRIX invView = XMMatrixInverse(&XMMatrixDeterminant(view), view);
  XMMATRIX invProj = XMMatrixInverse(&XMMatrixDeterminant(proj), proj);
  XMMATRIX invViewProj = XMMatrixInverse(&XMMatrixDeterminant(viewProj), viewProj);
 
  XMStoreFloat4x4(&mMainPassCB.View, XMMatrixTranspose(view));
  XMStoreFloat4x4(&mMainPassCB.InvView, XMMatrixTranspose(invView));

XMStoreFloat4x4(&mMainPassCB.Proj, XMMatrixTranspose(proj));
  XMStoreFloat4x4(&mMainPassCB.InvProj, XMMatrixTranspose(invProj));
  XMStoreFloat4x4(&mMainPassCB.ViewProj, XMMatrixTranspose(viewProj));
  XMStoreFloat4x4(&mMainPassCB.InvViewProj, XMMatrixTranspose(invViewProj));
  mMainPassCB.EyePosW = mEyePos;
  mMainPassCB.RenderTargetSize = XMFLOAT2((float)mClientWidth, (float)mClientHeight);
  mMainPassCB.InvRenderTargetSize = XMFLOAT2(1.0f / mClientWidth, 1.0f / mClientHeight);
  mMainPassCB.NearZ = 1.0f;
  mMainPassCB.FarZ = 1000.0f;
  mMainPassCB.TotalTime = gt.TotalTime();
  mMainPassCB.DeltaTime = gt.DeltaTime();
 
  auto currPassCB = mCurrFrameResource->PassCB.get();
  currPassCB->CopyData(0, mMainPassCB);
}

我們相應地更新頂點著色器以支援這些常量緩衝區更改:

VertexOut VS(VertexIn vin)
{
  VertexOut vout;
   
  // Transform to homogeneous clip space.
  float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
  vout.PosH = mul(posW, gViewProj);
   
  // Just pass vertex color into the pixel shader.
  vout.Color = vin.Color;
  
  return vout;
}

我們需要相應地更新根簽名以獲取兩個描述符表(我們需要兩個表,因為CBV將被設定為不同的頻率 - 每次傳遞CBV僅需要在每個渲染過程中設定一次,而每個物件CBV需要 每個渲染項設定):

D3DX12_DESCRIPTOR_RANGE cbvTable0;
cbvTable0.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
 
CD3DX12_DESCRIPTOR_RANGE cbvTable1;
cbvTable1.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 1);
 
// Root parameter can be a table, root descriptor or root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[2];
 
// Create root CBVs.
slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable0);
slotRootParameter[1].InitAsDescriptorTable(1, &cbvTable1);
 
// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(2, slotRootParameter, 0, nullptr, 
  D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

以上程式碼會在後面給出完整的,在這裡只需要理解其能實現的具體作用即可。