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);
以上程式碼會在後面給出完整的,在這裡只需要理解其能實現的具體作用即可。