之前我们介绍过DX12_Mesh Shaders Render,但是基于MeshShader我们能做的还很多,比如实例化和剔除(视锥与遮挡),这也就直接解决了现在主流的GPU-Driven管线方法,是不是一举两得了(毕竟MS就是变种的CS嘛)。那么我们一步步来,先来说一下Mesh Shader实例化如何实现吧。

本部分主要基于之前文章拓展实例化部分的代码,具体流程想回顾的直接看以前文章即可。
一、实例化数据
传统实例化你肯定知道,
- 一种是将实例化数据放在与VertexBuffer绑定位同级的管线布局上并设置管线布局,之后调用DrawInstance即可;
- 另一种就是放到常量缓冲区上,调用DrawInstance后在Shader中使用SV_InstanceID/gl_InstanceIndex进行绘制即可。
其实Mesh Shader的实例化就是和第二种方式一样,使用实例化的数据直接在MS中生成对应的Meshlet数据使用PS接上即可,当然了这种方式和传统API的实例化还是有区别的:
- 效率比传统实例化快(MS->PS > VS->PS,更不说把臃肿的TS与GS加上了),原因是Mesh Shader的数据量更适合硬件并行计算,充分发挥了GPU算力
- 更加灵活,可拓展完全GPU-Driven的算法实现
说了这么多还是上代码把,这样更直观:
这一步很简单,就是的在CPU端创建实例化的SRV,然后更新数据
void D3D12MeshletInstancing::RegenerateInstances()
{
m_updateInstances = true;
const float radius = m_model.GetBoundingSphere().Radius;
const float padding = 0.0f;
const float spacing = (1.0f + padding) * radius * 2.0f;
const uint32_t width = m_instanceLevel * 2 + 1;
const float extents = spacing * m_instanceLevel;
m_instanceCount = width * width * width;
const uint32_t instanceBufferSize = (uint32_t)GetAlignedSize(m_instanceCount * sizeof(Instance));
// 实例化数量改变时重新创建默认堆数据
if (!m_instanceBuffer || m_instanceBuffer->GetDesc().Width < instanceBufferSize)
{
WaitForGpu();
const CD3DX12_HEAP_PROPERTIES instanceBufferDefaultHeapProps(D3D12_HEAP_TYPE_DEFAULT);
const CD3DX12_RESOURCE_DESC instanceBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(instanceBufferSize);
// 创建Buffer(常变数据,所以放共享显存中,最后析构再UnMap)
ThrowIfFailed(m_device->CreateCommittedResource(
&instanceBufferDefaultHeapProps,
D3D12_HEAP_FLAG_NONE,
&instanceBufferDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&m_instanceBuffer)
));
const CD3DX12_HEAP_PROPERTIES instanceBufferUploadHeapProps(D3D12_HEAP_TYPE_UPLOAD);
// 创建上传堆
ThrowIfFailed(m_device->CreateCommittedResource(
&instanceBufferUploadHeapProps,
D3D12_HEAP_FLAG_NONE,
&instanceBufferDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&m_instanceUpload)
));
m_instanceUpload->Map(0, nullptr, reinterpret_cast<void**>(&m_instanceData));
}
// CPU更新实例化数据
for (uint32_t i = 0; i < m_instanceCount; ++i)
{
XMVECTOR index = XMVectorSet(float(i % width), float((i / width) % width), float(i / (width * width)), 0);
XMVECTOR location = index * spacing - XMVectorReplicate(extents);
XMMATRIX world = XMMatrixTranslationFromVector(location);
auto& inst = m_instanceData[i];
XMStoreFloat4x4(&inst.World, XMMatrixTranspose(world));
XMStoreFloat4x4(&inst.WorldInvTranspose, XMMatrixTranspose(XMMatrixInverse(nullptr, XMMatrixTranspose(world))));
}
}
因DX12使用命令队列录制,我们还必须保证实例化数据在使用之前已经被正确的拷贝完毕,因此在绘制之前,需要使用屏障来同步显存数据:
// 仅实例化场景变更时更新
if (m_updateInstances)
{
const auto toCopyBarrier = CD3DX12_RESOURCE_BARRIER::Transition(m_instanceBuffer.Get(), D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_STATE_COPY_DEST);
m_commandList->ResourceBarrier(1, &toCopyBarrier);
m_commandList->CopyResource(m_instanceBuffer.Get(), m_instanceUpload.Get());
const auto toGenericBarrier = CD3DX12_RESOURCE_BARRIER::Transition(m_instanceBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ);
m_commandList->ResourceBarrier(1, &toGenericBarrier);
m_updateInstances = false;
}
二、Instance Mesh Shader实现
主要就是加了SRV(t4),大家可以自定对比之前MS与本部分实例化的MS,主要就是布局和main中的实现,具体流程见注释。
#define ROOT_SIG "CBV(b0), \
RootConstants(b1, num32bitconstants=2), \
RootConstants(b2, num32bitconstants=3), \
SRV(t0), \
SRV(t1), \
SRV(t2), \
SRV(t3), \
SRV(t4)"
struct Constants
{
float4x4 World;
float4x4 WorldView;
float4x4 WorldViewProj

本文详细介绍使用Mesh Shader实现实例化的过程,包括实例化数据的创建与更新、资源同步及Mesh Shader的具体实现,并通过代码示例说明如何提高绘制效率。

3103

被折叠的 条评论
为什么被折叠?



