[toc]

# 利用 Direct3D 绘制几何体 (续)

# 构建 Shader

流程:
- 初始化 - 编译着色器并缓存到 ID3DBlob 中 - 设置着色器所需要从 CPU 端接受的点点着色器输入的布局描述 - 构建几何资源: BuildShapeGeometry (在这一步使用上传堆填充上面的布局描述) - 构建流水线时,将顶点着色器 Blob / 片元着色器 Blob/cb 布局以及数据等填充进 PSO (D3D12_GRAPHICS_PIPELINE_STATE_DESC) - 填充 constantBuffer,(包含 ObjectConstantsBuffer 和 PassConstantBuffer) - 构建流水线状态 (CreateGraphicsPipelineState) - Tips: 这里可以有多种流水线状态,如只显示网格流水线,显示三角填充流水线

  • 后初始化 (初始化后初次赋值) 和每次界面发生变更时
    • 逐 Object 判断是否是 Dirty, 同时填充有变动的 Object 的 Buffer

# 编译着色器

1
2
3
4
5
6
7
8
9
10
11
void ShapesApp::BuildShadersAndInputLayout()
{
mShaders["standardVS"] = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "VS", "vs_5_1");
mShaders["opaquePS"] = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "PS", "ps_5_1");

mInputLayout =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
}

在编译着色器阶段顺便设置了输入的布局信息 (还未绑定到 PSO 上), 这个是描述了下面这张图中的部分

# 绑定 ConstantBuffer

constantBuffer 是 Shader 中的 cb 部分,使用 register (寄存器) 表示,实际上是 GPU 上的一块内存空间,

  • 根签名中先配置两个 COnstantBufferView
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void ShapesApp::BuildRootSignature()
{
//描述符表,ConstantBufferTable0,这里代表的是ObjectConstantBuffer
CD3DX12_DESCRIPTOR_RANGE cbvTable0;
cbvTable0.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
//PassConstantBuffer
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);

//RootSignature是root Parameters的数组
// 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);

//RootSignature也需要序列化,传递到GPU和CPU的映射中
// create a root signature with a single slot which points to a descriptor range consisting of a single constant buffer
ComPtr<ID3DBlob> serializedRootSig = nullptr;
ComPtr<ID3DBlob> errorBlob = nullptr;
HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());

if (errorBlob != nullptr)
{
::OutputDebugStringA((char*)errorBlob->GetBufferPointer());
}
ThrowIfFailed(hr);
//创建RootSignature,放到mRootSignature中
ThrowIfFailed(md3dDevice->CreateRootSignature(
0,
serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(mRootSignature.GetAddressOf())));
}
  • 针对每个 Object 和 Pass 构建 ConstantBuffer

Tips:

所有 Object 的 ConstantBuffer 是在一个 ConstantBuffer 下的,使用 ConstantBufferView 来设置每个 Object 的 ObjectConstantBuffer 在那段内存空间内 (GPU)
所有 Pass 和 Object 同理,不过所有 Object 在每个 Pass 里共享一段 PassConstantBufferView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void ShapesApp::BuildConstantBufferViews()
{
//分配ObjectConstants的描述符
//每个Object都有一个ObjectConstants cb
UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

UINT objCount = (UINT)mOpaqueRitems.size();

// Need a CBV descriptor for each object for each frame resource.
for (int frameIndex = 0; frameIndex < gNumFrameResources; ++frameIndex)
{
auto objectCB = mFrameResources[frameIndex]->ObjectCB->Resource();
for (UINT i = 0; i < objCount; ++i)
{
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = objectCB->GetGPUVirtualAddress();

// Offset to the ith object constant buffer in the buffer.
cbAddress += i * objCBByteSize;

// Offset to the object cbv in the descriptor heap.
int heapIndex = frameIndex * objCount + i;
auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(mCbvHeap->GetCPUDescriptorHandleForHeapStart());
handle.Offset(heapIndex, mCbvSrvUavDescriptorSize);

D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes = objCBByteSize;
//对每个Object实例化一个ObjectConstantBuffer(View),即一个ConstantBuffer下的一部分
md3dDevice->CreateConstantBufferView(&cbvDesc, handle);
}
}

//分配PassConstants的描述符
//一个Pass一个
UINT passCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(PassConstants));

// Last three descriptors are the pass CBVs for each frame resource.
for (int frameIndex = 0; frameIndex < gNumFrameResources; ++frameIndex)
{
auto passCB = mFrameResources[frameIndex]->PassCB->Resource();
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = passCB->GetGPUVirtualAddress();

// Offset to the pass cbv in the descriptor heap.
int heapIndex = mPassCbvOffset + frameIndex;
auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(mCbvHeap->GetCPUDescriptorHandleForHeapStart());
handle.Offset(heapIndex, mCbvSrvUavDescriptorSize);

D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes = passCBByteSize;

md3dDevice->CreateConstantBufferView(&cbvDesc, handle);
}
}
  • 更新 ConstantBuffer

现在实际上每个 Object 的 ConstantBuffer 里只有一个 world

更新ObjectConstntBuffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void ShapesApp::UpdateObjectCBs(const GameTimer& gt)
{
auto currObjectCB = mCurrFrameResource->ObjectCB.get();
for (auto& e : mAllRitems)
{
//每个Object都有一个ObjectConstant Buffer
// Only update the cbuffer data if the constants have changed.
// This needs to be tracked per frame resource.
//如果发生变化(标记为脏数据,则更新对应Obj的Cb)
if (e->NumFramesDirty > 0)
{
XMMATRIX world = XMLoadFloat4x4(&e->World);

ObjectConstants objConstants;
XMStoreFloat4x4(&objConstants.World, XMMatrixTranspose(world));

currObjectCB->CopyData(e->ObjCBIndex, objConstants);

// 脏数据计数器
e->NumFramesDirty--;
}
}
}

更新MainBuffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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();

//PassCB和ObjectCB是在FrameResource.cpp中初始化的
auto currPassCB = mCurrFrameResource->PassCB.get();
currPassCB->CopyData(0, mMainPassCB);
}

# ConstantBuffer 如何绑定到每个 Object 的绘制中?

通过 CommandList 中设置 constantBufferViewHandle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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);

//在ObjectConstantBuffer Heap中
// 通过偏移找到当前Object的ConstantBuffer View的Index
// Offset to the CBV in the descriptor heap for this object and for this frame resource.
UINT cbvIndex = mCurrFrameResourceIndex * (UINT)mOpaqueRitems.size() + ri->ObjCBIndex;
auto cbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(mCbvHeap->GetGPUDescriptorHandleForHeapStart());
cbvHandle.Offset(cbvIndex, mCbvSrvUavDescriptorSize);

//设置根签名中的ConstantBuffer渲染Hanlde(cbvTable0),PS: CommandList是有顺序的
cmdList->SetGraphicsRootDescriptorTable(0, cbvHandle);

cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);
}

注意:上面是逐 Object 进行的,即每个命令执行完之后,会在 GPU 中更改当前的 ObjectConstantBufferView 地址

# 添加绘制 CommandList