admin 管理员组

文章数量: 1087652

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

以上代码会在后面给出完整的,在这里只需要理解其能实现的具体作用即可。

本文标签: D3D12渲染技术之渲染