我们在绘制对象时需要设置多个参数,例如绑定顶点和索引缓冲区,绑定对象常量,设置基元类型以及指定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> mAllRitems;
// Render items divided by PSO.
std::vector mOpaqueRitems;
std::vector mTransparentRitems;
从上一篇博客中可以看出,我们在帧资源类中引入了一个新的常量缓冲区:
std::unique_ptr> 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);
以上代码会在后面给出完整的,在这里只需要理解其能实现的具体作用即可。