d3d12龍書閱讀----繪製幾何體(上) 課後習題
練習1 完成相應的頂點結構體的輸入-佈局物件
typedef struct D3D12_INPUT_ELEMENT_DESC
{
一個特定字串
將頂點結構體陣列裡面的頂點對映到頂點著色器的輸入簽名
LPCSTR SemanticName;
語義索引 如果語義名相同的話 使用索引進行區分
UINT SemanticIndex;
頂點元素的格式 與頂點結構體元素的大小相對應
DXGI_FORMAT Format;
輸入槽索引
UINT InputSlot;
代表著頂點結構體各元素的偏移量
UINT AlignedByteOffset;
D3D12_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} D3D12_INPUT_ELEMENT_DESC;
而對於上圖中的頂點結構體 頂點輸入佈局描述如下:
inputLayoutDesc =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 36, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 44, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 52, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
練習2 使用多個輸入槽
主要分為兩步 第一步不詳細貼程式碼了 主要是對輔助幾何結構體進行更改 將vertex拆分成位置與顏色:
struct MeshGeometry
{
std::string Name;
Microsoft::WRL::ComPtr<ID3DBlob> PositionBufferCPU = nullptr;
Microsoft::WRL::ComPtr<ID3DBlob> ColorBufferCPU = nullptr;
Microsoft::WRL::ComPtr<ID3DBlob> IndexBufferCPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> PositionBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> ColorBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> PositionBufferUploader = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> ColorBufferUploader = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;
UINT VertexPosByteStride = 0;
UINT VertexPosBufferByteSize = 0;
UINT VertexColorByteStride = 0;
UINT VertexColorBufferByteSize = 0;
DXGI_FORMAT IndexFormat = DXGI_FORMAT_R16_UINT;
UINT IndexBufferByteSize = 0;
std::unordered_map<std::string, SubmeshGeometry> DrawArgs;
D3D12_VERTEX_BUFFER_VIEW PositionBufferView()const
{
D3D12_VERTEX_BUFFER_VIEW pbv;
pbv.BufferLocation = PositionBufferGPU->GetGPUVirtualAddress();
pbv.StrideInBytes = VertexPosByteStride;
pbv.SizeInBytes = VertexPosBufferByteSize;
return pbv;
}
D3D12_VERTEX_BUFFER_VIEW ColorBufferView()const
{
D3D12_VERTEX_BUFFER_VIEW cbv;
cbv.BufferLocation = ColorBufferGPU->GetGPUVirtualAddress();
cbv.StrideInBytes = VertexColorByteStride;
cbv.SizeInBytes = VertexColorBufferByteSize;
return cbv;
}
D3D12_INDEX_BUFFER_VIEW IndexBufferView()const
{
D3D12_INDEX_BUFFER_VIEW ibv;
ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();
ibv.Format = IndexFormat;
ibv.SizeInBytes = IndexBufferByteSize;
return ibv;
}
void DisposeUploaders()
{
PositionBufferUploader = nullptr;
ColorBufferUploader = nullptr;
IndexBufferUploader = nullptr;
}
};
第二步在draw函式中使用不同的輸入槽進行繪製:
mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo->PositionBufferView());
mCommandList->IASetVertexBuffers(1, 1, &mBoxGeo->ColorBufferView());
練習3 使用不同的圖元拓撲 練習8 使用線框模式 練習9 背面與正面剔除
這裡啟用線框模式可以幫助我們更好的觀察
只需要在buildPSo函式中對光柵器狀態進行修改即可
背面與正面剔除的設定也在這裡修改
使用不同的圖元拓撲在:
mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST)
修改即可
練習4 繪製金字塔
定義頂點陣列 與 索引陣列
這裡要特別注意索引順序 看不見的面使用逆時針
看見的面使用順時針
std::array<Vertex, 5> pyramid_vertices =
{
Vertex({ XMFLOAT3(-1.0f, 0.0f, -1.0f), XMFLOAT4(Colors::Green) }),
Vertex({ XMFLOAT3(-1.0f, 0.0f, 1.0f), XMFLOAT4(Colors::Green) }),
Vertex({ XMFLOAT3(+1.0f, 0.0f, -1.0f), XMFLOAT4(Colors::Green) }),
Vertex({ XMFLOAT3(+1.0f, 0.0f, 1.0f), XMFLOAT4(Colors::Green) }),
Vertex({ XMFLOAT3(0.0f, 2.0f, 0.0f), XMFLOAT4(Colors::Red) })
};
std::array<std::uint16_t, 18> pyramid_indices =
{
// 底面 逆時針
0, 2, 1,
1, 2, 3,
// 側面 順時針
1, 4, 0,
0, 4, 2,
2, 4, 3,
// 背面 逆時針
1, 3, 4
};
練習6 設定常量gTime
修改如下:
struct ObjectConstants
{
XMFLOAT4X4 WorldViewProj = MathHelper::Identity4x4();
float gTime = 0.0f;
};
然後在update函式進行相應修改:
ObjectConstants objConstants;
XMStoreFloat4x4(&objConstants.WorldViewProj, XMMatrixTranspose(worldViewProj));
objConstants.gTime = gt.TotalTime();
mObjectCB->CopyData(0, objConstants);
練習7 將金字塔與立方體合併到一個大的頂點緩衝區與索引緩衝區 同時使用不同的世界矩陣使得兩者互不相交
本題的方法可以在完成第七章的學習之後再來看
我們可以先從初始化開始:
BuildDescriptorHeaps();
BuildConstantBuffers();
BuildRootSignature();
BuildShadersAndInputLayout();
BuildBoxGeometry();
BuildPSO();
第一步 構建常量緩衝區描述符堆(因為我們這裡根簽名還是採用描述符表的形式 所以需要構建描述符堆來儲存 cbv描述符)
因為之前只繪製一個物體 而這裡要繪製兩個物體 每個物體都需要一個常量資料對應的描述子:
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
cbvHeapDesc.NumDescriptors = 2;//修改為2 這裡就不再改為更通用的表示 第七章示例程式碼已經做出了示範
cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
cbvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&cbvHeapDesc,
IID_PPV_ARGS(&mCbvHeap)));
第二步 構建常量緩衝區檢視
void BoxApp::BuildConstantBuffers()
{
mObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(md3dDevice.Get(), 2, true);
UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = mObjectCB->Resource()->GetGPUVirtualAddress();
for (UINT i = 0; i < 2; ++i)
{
// 偏移到第i個物體常量資料
cbAddress += i * objCBByteSize;
// 偏移到對應的描述符堆的第i個cbv
int heapIndex = 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;
md3dDevice->CreateConstantBufferView(&cbvDesc, handle);
}
}
第三步 構建根簽名 不變 要記住我們還是隻使用了一個常量緩衝區來儲存不同物體的常量資料 對應著色器的b0 而在第七章這裡發生變化 因為使用了兩個常量緩衝區 一個物體一個過程 對應著色器的b0 b1
第四步 著色器設定 與頂點輸入佈局描述設定 不變
第五步 構建box幾何體 要變 需要設定兩個物體的頂點 與 索引緩衝區 並且合併
void BoxApp::BuildBoxGeometry()
{
std::array<Vertex, 8> box_vertices =
{
Vertex({ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::White) }),
Vertex({ XMFLOAT3(-1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(+1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Red) }),
Vertex({ XMFLOAT3(+1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::Green) }),
Vertex({ XMFLOAT3(-1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Blue) }),
Vertex({ XMFLOAT3(-1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Yellow) }),
Vertex({ XMFLOAT3(+1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Cyan) }),
Vertex({ XMFLOAT3(+1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Magenta) })
};
std::array<std::uint16_t, 36> box_indices =
{
// front face
0, 1, 2,
0, 2, 3,
// back face
4, 6, 5,
4, 7, 6,
// left face
4, 5, 1,
4, 1, 0,
// right face
3, 2, 6,
3, 6, 7,
// top face
1, 5, 6,
1, 6, 2,
// bottom face
4, 0, 3,
4, 3, 7
};
std::array<Vertex, 5> pyramid_vertices =
{
Vertex({ XMFLOAT3(-1.0f, 0.0f, -1.0f), XMFLOAT4(Colors::Green) }),
Vertex({ XMFLOAT3(-1.0f, 0.0f, 1.0f), XMFLOAT4(Colors::Green) }),
Vertex({ XMFLOAT3(+1.0f, 0.0f, -1.0f), XMFLOAT4(Colors::Green) }),
Vertex({ XMFLOAT3(+1.0f, 0.0f, 1.0f), XMFLOAT4(Colors::Green) }),
Vertex({ XMFLOAT3(0.0f, 2.0f, 0.0f), XMFLOAT4(Colors::Red) })
};
std::array<std::uint16_t, 18> pyramid_indices =
{
// 底面 逆時針
0, 2, 1,
1, 2, 3,
// 側面 順時針
1, 4, 0,
0, 4, 2,
2, 4, 3,
// 背面 逆時針
1, 3, 4
};
UINT boxVertexOffset = 0;
UINT pyramidVertexOffset = (UINT)box_vertices.size();
UINT boxIndexOffset = 0;
UINT pyramidIndexOffset = (UINT)box_indices.size();
SubmeshGeometry boxSubmesh;
boxSubmesh.IndexCount = (UINT)box_indices.size();
boxSubmesh.StartIndexLocation = boxIndexOffset;
boxSubmesh.BaseVertexLocation = boxVertexOffset;
SubmeshGeometry pyramidSubmesh;
pyramidSubmesh.IndexCount = (UINT)pyramid_indices.size();
pyramidSubmesh.StartIndexLocation = pyramidIndexOffset;
pyramidSubmesh.BaseVertexLocation = pyramidVertexOffset;
auto totalVertexCount =
box_vertices.size() +
pyramid_indices.size();
std::vector<Vertex> vertices(totalVertexCount);
UINT k = 0;
for (size_t i = 0; i < box_vertices.size(); ++i, ++k)
{
vertices[k].Pos = box_vertices[i].Pos;
vertices[k].Color = box_vertices[i].Color;
}
for (size_t i = 0; i < pyramid_vertices.size(); ++i, ++k)
{
vertices[k].Pos = pyramid_vertices[i].Pos;
vertices[k].Color = pyramid_vertices[i].Color;
}
std::vector<std::uint16_t> indices;
indices.insert(indices.end(), std::begin(box_indices), std::end(box_indices));
indices.insert(indices.end(), std::begin(pyramid_indices), std::end(pyramid_indices));
const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);
const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);
mBoxGeo = std::make_unique<MeshGeometry>();
mBoxGeo->Name = "boxGeo";
ThrowIfFailed(D3DCreateBlob(vbByteSize, &mBoxGeo->VertexBufferCPU));
CopyMemory(mBoxGeo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);
ThrowIfFailed(D3DCreateBlob(ibByteSize, &mBoxGeo->IndexBufferCPU));
CopyMemory(mBoxGeo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);
mBoxGeo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices.data(), vbByteSize, mBoxGeo->VertexBufferUploader);
mBoxGeo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices.data(), ibByteSize, mBoxGeo->IndexBufferUploader);
mBoxGeo->VertexByteStride = sizeof(Vertex);
mBoxGeo->VertexBufferByteSize = vbByteSize;
mBoxGeo->IndexFormat = DXGI_FORMAT_R16_UINT;
mBoxGeo->IndexBufferByteSize = ibByteSize;
mBoxGeo->DrawArgs["box"] = boxSubmesh;
mBoxGeo->DrawArgs["pyramid"] = pyramidSubmesh;
}
第六步 構建渲染流水線狀態 不變
最後我們需要在繪製階段 根據金字塔與正方體的不同頂點與索引起點繪製:
mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo->VertexBufferView());
mCommandList->IASetIndexBuffer(&mBoxGeo->IndexBufferView());
mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
UINT cbvIndex = 0;
auto cbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(mCbvHeap->GetGPUDescriptorHandleForHeapStart());
cbvHandle.Offset(cbvIndex, mCbvSrvUavDescriptorSize);
mCommandList->SetGraphicsRootDescriptorTable(0, cbvHandle);
mCommandList->DrawIndexedInstanced(mBoxGeo->DrawArgs["box"].IndexCount, 1, mBoxGeo->DrawArgs["box"].StartIndexLocation, mBoxGeo->DrawArgs["box"].BaseVertexLocation, 0);
cbvIndex++;
cbvHandle.Offset(cbvIndex, mCbvSrvUavDescriptorSize);
mCommandList->SetGraphicsRootDescriptorTable(0, cbvHandle);
mCommandList->DrawIndexedInstanced(mBoxGeo->DrawArgs["pyramid"].IndexCount, 1, mBoxGeo->DrawArgs["pyramid"].StartIndexLocation, mBoxGeo->DrawArgs["pyramid"].BaseVertexLocation, 0);
同時為了將金字塔與立方體分開 還需要對它們的世界矩陣進行一些調整,在update函式中修改:
XMMATRIX world_box = XMLoadFloat4x4(&mWorld_box);
XMMATRIX proj = XMLoadFloat4x4(&mProj);
XMMATRIX boxworldViewProj = world_box*view*proj;
XMMATRIX world_pyramid = XMLoadFloat4x4(&mWorld_pyramid);
world_pyramid = XMMatrixTranslation(-3.0f, 0.0f, -1.0f);
XMMATRIX pyramidworldViewProj = world_pyramid * view * proj;
ObjectConstants objConstants_box;
XMStoreFloat4x4(&objConstants_box.WorldViewProj, XMMatrixTranspose(boxworldViewProj));
mObjectCB->CopyData(0, objConstants_box);
ObjectConstants objConstants_pyramid;
XMStoreFloat4x4(&objConstants_pyramid.WorldViewProj, XMMatrixTranspose(pyramidworldViewProj));
mObjectCB->CopyData(1, objConstants_pyramid);
最終結果:
練習5 練習11
三角形內的畫素透過 三個頂點的屬性插值得到
這兩種都是能正常工作的
第一種雖然color 與 pos排列的順序變了
但是它們與頂點結構體元素的偏移還是對應的
第二種 頂點著色器 與 頂點結構體的對應是透過語義來間接對應的 所以交換順序也不會影響
練習12 調整視口 練習13 裁剪測試
視口修改:
mScreenViewport.TopLeftX = 0;
mScreenViewport.TopLeftY = 0;
mScreenViewport.Width = static_cast<float>(mClientWidth);
mScreenViewport.Height = static_cast<float>(mClientHeight);
mScreenViewport.MinDepth = 0.0f;
mScreenViewport.MaxDepth = 1.0f;
裁剪修改:
mScissorRect = { 0, 0, mClientWidth, mClientHeight };