(转载请注明出处)
有点时间没更新了,原因是找了一个多星期的bug结果发现是少了一次加法运算。。。。
不过反正没人看也就没影响啦!_(:3」∠)_
这次的目的是做一个旋转的彩色立方体:
这是自己在学D3D11的时候做过的一个例子,现在搬到D3D12上, 这次增加的内容有:
- 常量缓存
- 深度缓存
- 顶点/索引缓存
- 常量缓存, 常量缓存可以放一些GPU只读的数据, 这里就是存放的是转换矩阵,D3D12中,常量缓存是以256字节对齐的(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT), 所以请注意.
这样可以创建一个常量缓存并创建CBV绑定到一个DESCRIPTOR上
- 常量缓存, 常量缓存可以放一些GPU只读的数据, 这里就是存放的是转换矩阵,D3D12中,常量缓存是以256字节对齐的(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT), 所以请注意.
// 创建常量缓存
if (SUCCEEDED(hr)) {
hr = m_pd3dDevice->CreateCommittedResource(
&CD3D12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_MISC_NONE,
&CD3D12_RESOURCE_DESC::Buffer(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT),
D3D12_RESOURCE_USAGE_GENERIC_READ,
nullptr,
IID_ID3D12Resource,
reinterpret_cast<void**>(&m_pCBufferMatrix)
);
}
// 绑定到常量缓存视图
if (SUCCEEDED(hr)) {
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = m_pCBufferMatrix->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT;
m_pd3dDevice->CreateConstantBufferView(
&cbvDesc,
m_aCpuHandleCSU[CSU_MatrixCBuffer]
);
}
这部分代码中, 使用D3D12_HEAP_TYPE_UPLOAD是因为我们每帧都要改写,这样比较方便, 相应的, 效率可能就会损失点.
还有就是之前提到的把所有相同的DESCRIPTOR集中放到一起, 所以写了个简单的框架, 使用m_aCpuHandleXXX数组存放的.
我们在这里存放3个矩阵: 世界、视角、透视转换矩阵
2. 深度缓存, 深度缓存的作用就不用说了, 灵活使用可以获取到不错的效果, 创建DSV绑定到DESCRIPTOR. 这里不使用模板, 仅仅使用深度:
// 创建深度缓存
if (SUCCEEDED(hr)) {
D3D12_RESOURCE_DESC resourceDesc = CD3D12_RESOURCE_DESC::Tex2D(
DXGI_FORMAT_R32_TYPELESS, m_uBufferWidth, m_uBufferHeight,
1, 1, 1, 0,
D3D12_RESOURCE_MISC_ALLOW_DEPTH_STENCIL,
D3D12_TEXTURE_LAYOUT_UNKNOWN, 0
);
D3D12_CLEAR_VALUE dsvClearValue;
dsvClearValue.Format = DXGI_FORMAT_D32_FLOAT;
dsvClearValue.DepthStencil.Depth = 1.0f;
dsvClearValue.DepthStencil.Stencil = 0;
hr = m_pd3dDevice->CreateCommittedResource(
&CD3D12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_MISC_NONE,
&resourceDesc,
D3D12_RESOURCE_USAGE_DEPTH,
&dsvClearValue,
IID_ID3D12Resource,
reinterpret_cast<void**>(&m_pDepthBuffer)
);
}
// 绑定深度缓存到DSV
if (SUCCEEDED(hr)) {
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
dsvDesc.Format = DXGI_FORMAT_D32_FLOAT;
dsvDesc.Texture2D.MipSlice = 0;
dsvDesc.Flags = D3D12_DSV_NONE;
/*no return*/m_pd3dDevice->CreateDepthStencilView(
m_pDepthBuffer, &dsvDesc,
m_aCpuHandleDSV[DSV_MainDSV]
);
}
感觉没什么可以说的, D3D12提供的Helper很方便, 默认值就行,详细的可以查看官方文档.
3. 顶点/索引缓存, 渲染一个立方体所需要的东西, 目前我们需要: 顶点颜色与顶点坐标, 索引就是三角索引了
// 创建带颜色的正方体
auto SceneRenderer::CreateColoredCube(
ID3D12Resource*& vibuffer,
D3D12_VERTEX_BUFFER_VIEW& vbuffer_view,
D3D12_INDEX_BUFFER_VIEW& ibuffer_view) noexcept -> HRESULT {
HRESULT hr = S_OK;
ID3D12Resource* pVIBuffer = nullptr;
// 立方体的8个顶点 与相应颜色
VertexColor vertices[] = {
{ DirectX::XMFLOAT3(-1.f, -1.f, -1.f), DirectX::XMFLOAT4(0.f, 0.f, 0.f, 1.f) },
{ DirectX::XMFLOAT3(-1.f, 1.f, -1.f), DirectX::XMFLOAT4(1.f, 0.f, 0.f, 1.f) },
{ DirectX::XMFLOAT3(1.f, 1.f, -1.f), DirectX::XMFLOAT4(0.f, 1.f, 0.f, 1.f) },
{ DirectX::XMFLOAT3(1.f, -1.f, -1.f), DirectX::XMFLOAT4(0.f, 0.f, 1.f, 1.f) },
{ DirectX::XMFLOAT3(-1.f, -1.f, 1.f), DirectX::XMFLOAT4(0.f, 1.f, 1.f, 1.f) },
{ DirectX::XMFLOAT3(-1.f, 1.f, 1.f), DirectX::XMFLOAT4(1.f, 1.f, 0.f, 1.f) },
{ DirectX::XMFLOAT3(1.f, 1.f, 1.f), DirectX::XMFLOAT4(1.f, 0.f, 1.f, 1.f) },
{ DirectX::XMFLOAT3(1.f, -1.f, 1.f), DirectX::XMFLOAT4(1.f, 1.f, 1.f, 1.f) }
};
// 立方体 6个面 12个三角面 36个顶点
uint16_t indices[] = {
0, 1, 2, 0, 2, 3,
4, 5, 1, 4, 1, 0,
7, 6, 5, 7, 5, 4,
3, 2, 6, 3, 6, 7,
1, 5, 6, 1, 6, 2,
4, 0, 3, 4, 3, 7
};
// 创建顶点缓存-索引缓存共用缓冲区
if (SUCCEEDED(hr)) {
hr = m_pd3dDevice->CreateCommittedResource(
&CD3D12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_MISC_NONE,
&CD3D12_RESOURCE_DESC::Buffer(sizeof(vertices)+sizeof(indices)),
D3D12_RESOURCE_USAGE_GENERIC_READ,
nullptr,
IID_ID3D12Resource,
reinterpret_cast<void**>(&pVIBuffer)
);
}
// 映射
void* buffer = nullptr;
if (SUCCEEDED(hr)) {
hr = pVIBuffer->Map(0, nullptr, &buffer);
}
// 复制-取消映射-设置
if (SUCCEEDED(hr)) {
::memcpy(buffer, vertices, sizeof(vertices));
::memcpy(
reinterpret_cast<uint8_t*>(buffer)+ sizeof(vertices),
indices, sizeof(indices)
);
pVIBuffer->Unmap(0, nullptr);
// 设置
vbuffer_view.BufferLocation = pVIBuffer->GetGPUVirtualAddress();
vbuffer_view.StrideInBytes = sizeof(VertexColor);
vbuffer_view.SizeInBytes = sizeof(vertices);
ibuffer_view.BufferLocation = vbuffer_view.BufferLocation + sizeof(vertices);
ibuffer_view.Format = DXGI_FORMAT_R16_UINT;
ibuffer_view.SizeInBytes = sizeof(indices);
vibuffer = ::SafeAcquire(pVIBuffer);
}
::SafeRelease(pVIBuffer);
return hr;
}
得益于D3D12/Win10的虚拟GPU地址, 我们可以将顶点缓存缓存与索引缓存一起申请, 提高效率. 对应的输入布局:
// 输入布局
D3D12_INPUT_ELEMENT_DESC inputLayout[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, sizeof(DirectX::XMFLOAT3), D3D12_INPUT_PER_VERTEX_DATA, 0 },
};
其实颜色也是可以用3个浮点数就行, 这样每个节点就能节约1个浮点了.对应的shader就是:
// C Buffer 0 : 储存转换矩阵
cbuffer MatrixBuffer : register (b0) {
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
};
// VS 输入
struct VertexInputType {
float4 position : POSITION;
float4 color : COLOR;
};
// VS 输出
struct PixelInputType {
float4 position : SV_POSITION;
float4 color : COLOR;
};
// 处理
PixelInputType ColorVertexShader(VertexInputType input) {
PixelInputType output;
// 坐标转换
output.position = mul(float4(input.position.xyz, 1), worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);
// 直接输出
output.color = input.color;
return output;
}
// 像素着色器处理
float4 ColorPixelShader(PixelInputType input) : SV_TARGET {
return input.color;
}
请注意, 因为都算是“固定管线”,所以我们在着色器里面使用的一切都要说明:
这里使用了在b0寄存器上cbuffer.
毕竟输入小,管线占用资源就少, 序列化RootSignature时提供的参数D3D12_ROOT_SIGNATURE表明了这个:
D3D12_ROOT_SIGNATURE rootSigDesc = D3D12_ROOT_SIGNATURE();
D3D12_ROOT_PARAMETER params[1];
D3D12_DESCRIPTOR_RANGE descRange[1];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_CBV, 1, 0);
params[0].InitAsDescriptorTable(lengthof(descRange), descRange);
// 初始化
rootSigDesc.NumParameters = lengthof(params);
rootSigDesc.pParameters = params;
rootSigDesc.Flags = D3D12_ROOT_SIGNATURE_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
目前仅需一个DESCRIPTOR表即可,并且只有一个常量缓存视图(CBV), 所以才是descRange[0].Init(D3D12_DESCRIPTOR_RANGE_CBV, 1, 0);
这次清空命令多了个清空深度缓存:
}
// 执行清空命令
if (SUCCEEDED(hr)) {
this->SetResourceBarrier(m_pCmdClear, m_pTargetBuffer, D3D12_RESOURCE_USAGE_PRESENT, D3D12_RESOURCE_USAGE_RENDER_TARGET);
m_pCmdClear->RSSetViewports(1, &view);
float clearColor[4] = { 0.4f, 0.8f, 1.0f, 1.0f};
m_pCmdClear->ClearRenderTargetView(
m_aCpuHandleRTV[RTV_MainRTV],
clearColor, nullptr, 0
);
m_pCmdClear->ClearDepthStencilView(
m_aCpuHandleDSV[DSV_MainDSV], D3D12_CLEAR_DEPTH, 1.0f,
0, nullptr, 0
);
this->SetResourceBarrier(m_pCmdClear, m_pTargetBuffer, D3D12_RESOURCE_USAGE_RENDER_TARGET, D3D12_RESOURCE_USAGE_PRESENT);
hr = m_pCmdClear->Close();
}
刻画命令多了: 设置DESCRIPTOR表(对应管线), 与DESCRIPTOR堆(提供资源),以及设置顶点缓存和索引缓存:
// 执行命令
if (SUCCEEDED(hr)) {
ID3D12DescriptorHeap* heaps[] = {
m_pCSUDescriptors
};
D3D12_RECT scissor = {};
scissor.right = m_uBufferWidth;
scissor.bottom = m_uBufferHeight;
//
this->SetResourceBarrier(m_pCmdDraw, m_pTargetBuffer, D3D12_RESOURCE_USAGE_PRESENT, D3D12_RESOURCE_USAGE_RENDER_TARGET);
m_pCmdDraw->RSSetViewports(1, &view);
m_pCmdDraw->RSSetScissorRects(1, &scissor);
m_pCmdDraw->SetRenderTargets(m_aCpuHandleRTV + RTV_MainRTV, true, 1, m_aCpuHandleDSV + DSV_MainDSV);
m_pCmdDraw->SetGraphicsRootSignature(m_prsPipeline);
m_pCmdDraw->SetDescriptorHeaps(heaps, lengthof(heaps));
m_pCmdDraw->SetGraphicsRootDescriptorTable(0, m_pCSUDescriptors->GetGPUDescriptorHandleForHeapStart());
m_pCmdDraw->SetPipelineState(m_pPipelineState);
m_pCmdDraw->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
m_pCmdDraw->SetVertexBuffers(0, &m_cubeVBV, 1);
m_pCmdDraw->SetIndexBuffer(&m_cubeIBV);
m_pCmdDraw->DrawIndexedInstanced(36, 1, 0, 0, 0);
this->SetResourceBarrier(m_pCmdDraw, m_pTargetBuffer, D3D12_RESOURCE_USAGE_RENDER_TARGET, D3D12_RESOURCE_USAGE_PRESENT);
hr = m_pCmdDraw->Close();
}
更新常量缓存直接修改即可, 会自动上传的:
代码下载地址: 点击这里