顶点数据和索引数据创建本来是很简单的东西,但是简单得过于自然,以至于很多地方被我们忽略掉了。随便去网上一搜,发现一大堆人在使用龙书给的代码框架。
geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader);
很多人都没有探索到内部的具体实现。仔细想了下反正都研究到这里了,不如往里再研究试试。所以我这里打算从头开始,不使用书里给的d3dUtil来创建顶点和索引缓冲,顺便查一些资料,把一些细节的地方弄清楚。
首先我们的整体思路是:在内存里拿到顶点和索引数据。然后为顶点和索引缓冲分别创建两份资源,一份在Upload堆,一份在default堆,然后把内存里的顶点索引数据拷贝到Upload堆,然后再把Upload堆的数据拷贝到Default堆,最后渲染的时候我们使用Default堆的资源。因为Default堆的资源访问速度最快。
【1】创建顶点和索引原始数据
顶点数据和索引数据我们需要手动或从外部导入创建,然后获取它们缓冲的字节大小。最后声明ID3D12Resource类型的指针。
//创建或加载顶点数据和索引数据
std::array<Vertex, 4> vertices =
{
Vertex({ XMFLOAT3(-1.0f, -1.0f, 0.0f), XMFLOAT4(0, 1, 0, 1) }),
Vertex({ XMFLOAT3(-1.0f, +1.0f, 0.0f), XMFLOAT4(0, 0, 0, 1) }),
Vertex({ XMFLOAT3(+1.0f, +1.0f, 0.0f), XMFLOAT4(1, 0, 0, 1) }),
Vertex({ XMFLOAT3(+1.0f, -1.0f, 0.0f), XMFLOAT4(1, 1, 0, 1) }),
};
std::array<std::uint32_t, 6> indices =
{
// front face
0, 1, 2,
0, 2, 3,
};
const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);
const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint32_t);
//Default buffer
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
//Upload buffer
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;
【2】创建堆参数
默认堆和上传堆
//默认堆,上传堆
D3D12_HEAP_PROPERTIES defaultHeap;
memset(&defaultHeap, 0, sizeof(defaultHeap));
defaultHeap.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_HEAP_PROPERTIES uploadheap;
memset(&uploadheap, 0, sizeof(uploadheap));
uploadheap.Type = D3D12_HEAP_TYPE_UPLOAD;
【3】创建顶点缓冲的资源描述
DirectX12里所有资源都是ID3D12Resource,区别它们不同的就是创建时候的描述符。因为对于GPU而言它们就是一堆数据,关键看我们如何识别使用它们。
//创建VertexBuffer的资源描述
D3D12_RESOURCE_DESC DefaultVertexBufferDesc;
memset(&DefaultVertexBufferDesc, 0, sizeof(D3D12_RESOURCE_DESC));
DefaultVertexBufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
DefaultVertexBufferDesc.Alignment = 0;
DefaultVertexBufferDesc.Width = vbByteSize;
DefaultVertexBufferDesc.Height = 1;
DefaultVertexBufferDesc.DepthOrArraySize = 1;
DefaultVertexBufferDesc.MipLevels = 1;
DefaultVertexBufferDesc.Format = DXGI_FORMAT_UNKNOWN;
DefaultVertexBufferDesc.SampleDesc.Count = 1;
DefaultVertexBufferDesc.SampleDesc.Quality = 0;
DefaultVertexBufferDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
DefaultVertexBufferDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
【4】创建VertexBuffer和VertexBufferUpload
这两个Buffer分别被创建到default堆和upload堆。VertexBuffer被创建到default堆,创建的时候其初始化状态就被设置成了D3D12_RESOURCE_STATE_COPY_DEST
//为VertexBuffer和VertexBufferUploader创建资源
// Create the actual default buffer resource.
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&defaultHeap,
D3D12_HEAP_FLAG_NONE,
&DefaultVertexBufferDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(VertexBufferGPU.GetAddressOf())));
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&uploadheap,
D3D12_HEAP_FLAG_NONE,
&DefaultVertexBufferDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(VertexBufferUploader.GetAddressOf())));
【5】获取 VertexBuffer footprint
//获取 VertexBuffer footprint
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint;
UINT64 vertex_total_bytes = 0;
md3dDevice->GetCopyableFootprints(&DefaultVertexBufferDesc, 0, 1, 0, &footprint, nullptr, nullptr, &vertex_total_bytes);
VertexBufferUploader->Unmap(0, nullptr);
【6】映射内存地址,并把数据拷贝到VertexBufferUploader里
void* ptr_vertex = nullptr;
VertexBufferUploader->Map(0, nullptr, &ptr_vertex);
memcpy(reinterpret_cast<char*>(ptr_vertex) + footprint.Offset, vertices.data(), vbByteSize);
【7】拷贝,把VertexBufferUploader里的数据拷贝到VertexBufferGPU里
mCommandList->CopyBufferRegion(VertexBufferGPU.Get(), 0, VertexBufferUploader.Get(), 0, vertex_total_bytes);
【8】为VertexBufferGPU插入资源屏障,因为一开始是以D3D12_RESOURCE_STATE_COPY_DEST的状态创建的资源,所以拷贝完以后需要给它设置好资源屏障。
D3D12_RESOURCE_BARRIER barrier_vertex;
memset(&barrier_vertex, 0, sizeof(barrier_vertex));
barrier_vertex.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier_vertex.Transition.pResource = VertexBufferGPU.Get();
barrier_vertex.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrier_vertex.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
barrier_vertex.Transition.StateAfter = D3D12_RESOURCE_STATE_GENERIC_READ;
mCommandList->ResourceBarrier(1, &barrier_vertex);
【9】创建IndexBuffer
到这里我们就完成了对VertexBuffer的处理,下面类似,需要对IndexBuffer进行处理,两者过程及其类似,我就不一步一步赘述了。
//创建IndexBuffer的资源描述
D3D12_RESOURCE_DESC DefaultIndexBufferDesc;
memset(&DefaultIndexBufferDesc, 0, sizeof(D3D12_RESOURCE_DESC));
DefaultIndexBufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
DefaultIndexBufferDesc.Alignment = 0;
DefaultIndexBufferDesc.Width = ibByteSize;
DefaultIndexBufferDesc.Height = 1;
DefaultIndexBufferDesc.DepthOrArraySize = 1;
DefaultIndexBufferDesc.MipLevels = 1;
DefaultIndexBufferDesc.Format = DXGI_FORMAT_UNKNOWN;
DefaultIndexBufferDesc.SampleDesc.Count = 1;
DefaultIndexBufferDesc.SampleDesc.Quality = 0;
DefaultIndexBufferDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
DefaultIndexBufferDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
//为IndexBuffer和IndexBufferUploader创建资源
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&defaultHeap,
D3D12_HEAP_FLAG_NONE,
&DefaultIndexBufferDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(IndexBufferGPU.GetAddressOf())));
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&uploadheap,
D3D12_HEAP_FLAG_NONE,
&DefaultIndexBufferDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(IndexBufferUploader.GetAddressOf())));
//获取 IndexBuffer footprint
D3D12_PLACED_SUBRESOURCE_FOOTPRINT indexBufferFootprint;
UINT64 index_total_bytes = 0;
md3dDevice->GetCopyableFootprints(&DefaultIndexBufferDesc, 0, 1, 0, &indexBufferFootprint, nullptr, nullptr, &index_total_bytes);
//映射内存地址,并把数据拷贝到IndexBufferUploader里
void* ptr_index = nullptr;
IndexBufferUploader->Map(0, nullptr, &ptr_index);
memcpy(reinterpret_cast<char*>(ptr_index) + indexBufferFootprint.Offset, indices.data(), ibByteSize);
IndexBufferUploader->Unmap(0, nullptr);
//拷贝,把IndexBufferUploader里的数据拷贝到IndexBufferGPU里
mCommandList->CopyBufferRegion(IndexBufferGPU.Get(), 0, IndexBufferUploader.Get(), 0, index_total_bytes);
//为IndexBufferGPU插入资源屏障
D3D12_RESOURCE_BARRIER barrier_index;
memset(&barrier_index, 0, sizeof(barrier_index));
barrier_index.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier_index.Transition.pResource = IndexBufferGPU.Get();
barrier_index.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrier_index.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
barrier_index.Transition.StateAfter = D3D12_RESOURCE_STATE_GENERIC_READ;
mCommandList->ResourceBarrier(1, &barrier_index);
【10】最后我们用VertexBufferGPU和IndexBufferGPU创建VerteBufferView和IndexBufferView来渲染。
//VertexBufferView
D3D12_VERTEX_BUFFER_VIEW vbv;
vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
vbv.StrideInBytes = sizeof(Vertex);
vbv.SizeInBytes = vbByteSize;
//IndexBufferView
D3D12_INDEX_BUFFER_VIEW ibv;
ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();
ibv.Format = DXGI_FORMAT_R32_UINT;
ibv.SizeInBytes = ibByteSize;
下面是我用上述代码绘制的结果
Shader
struct VertexIn
{
float3 PosL : POSITION;
float4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// Transform to homogeneous clip space.
vout.PosH = float4(vin.PosL, 1.0f);
// Just pass vertex color into the pixel shader.
vout.Color = vin.Color;
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
return pin.Color;
}
虽然简单但是我们实现了一个非常基础的功能,搭建一个UV画布:这是做一切后处理,Ray marching的基础。
其实不太理解为什么龙书要封装大量代码叫我们直接使用,这样反而对初学者不是非常友好,很容易忽略内部的细节。
有几个点需要注意一下:
【1】上面的初始化一定要放到Close之前,因为如果close以后,commandlist是无法记录命令的。其实在DX12上应该养成一个这样的习惯,不然很容易犯错,确认自己在记录command之前CommandList是否是打开的。
【2】Draw之前要记得设置好绘制状态,DX12比DX11多了一些东西。
特别注意别把mCommandList->SetGraphicsRootSignature和mCommandList->SetComputeRootSignature搞混了,有时候智能补全没看清楚就把mCommandList->SetComputeRootSignature敲了上去,查半天bug发现是这个坑。
2020/10/3