directx - 如何快速更新动态顶点缓冲区?
问题描述
我正在尝试制作一个简单的 3D 建模工具。
有一些工作可以移动一个顶点(或多个顶点)来转换模型。
我使用动态顶点缓冲区是因为认为它需要大量更新。
但是即使我只改变一个顶点,高多边形模型的性能也太低了。
还有其他方法吗?还是我做错了?
这是我的 D3D11_BUFFER_DESC
Usage = D3D11_USAGE_DYNAMIC;
CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
BindFlags = D3D11_BIND_VERTEX_BUFFER;
ByteWidth = sizeof(ST_Vertex) * _nVertexCount
D3D11_SUBRESOURCE_DATA d3dBufferData;
d3dBufferData.pSysMem = pVerticesInfo;
hr = pd3dDevice->CreateBuffer(&descBuffer, &d3dBufferData, &_pVertexBuffer);
和我的更新功能
D3D11_MAPPED_SUBRESOURCE d3dMappedResource;
pImmediateContext->Map(_pVertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &d3dMappedResource);
ST_Vertex* pBuffer = (ST_Vertex*)d3dMappedResource.pData;
for (int i = 0; i < vIndice.size(); ++i)
{
pBuffer[vIndice[i]].xfPosition.x = pVerticesInfo[vIndice[i]].xfPosition.x;
pBuffer[vIndice[i]].xfPosition.y = pVerticesInfo[vIndice[i]].xfPosition.y;
pBuffer[vIndice[i]].xfPosition.z = pVerticesInfo[vIndice[i]].xfPosition.z;
}
pImmediateContext->Unmap(_pVertexBuffer, 0);
解决方案
如上一个答案中所述,您每次都在更新整个缓冲区,这取决于模型大小,速度会很慢。
解决方案确实是实现部分更新,有两种可能,你想更新单个顶点,或者你想更新任意索引(例如,你想一次移动N个顶点,在不同的位置,比如例如顶点 1,20,23。
第一个解决方案相当简单,首先使用以下描述创建缓冲区:
Usage = D3D11_USAGE_DEFAULT;
CPUAccessFlags = 0;
BindFlags = D3D11_BIND_VERTEX_BUFFER;
ByteWidth = sizeof(ST_Vertex) * _nVertexCount
D3D11_SUBRESOURCE_DATA d3dBufferData;
d3dBufferData.pSysMem = pVerticesInfo;
hr = pd3dDevice->CreateBuffer(&descBuffer, &d3dBufferData, &_pVertexBuffer);
这可确保您的顶点缓冲区仅 gpu 可见。
接下来创建一个具有单个顶点大小的第二个动态缓冲区(在这种情况下您不需要任何绑定标志,因为它将仅用于副本)
_pCopyVertexBuffer
Usage = D3D11_USAGE_DYNAMIC; //Staging works as well
CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
BindFlags = 0;
ByteWidth = sizeof(ST_Vertex);
D3D11_SUBRESOURCE_DATA d3dBufferData;
d3dBufferData.pSysMem = NULL;
hr = pd3dDevice->CreateBuffer(&descBuffer, &d3dBufferData, &_pCopyVertexBuffer);
when you move a vertex, copy the changed vertex in the copy buffer :
ST_Vertex changedVertex;
D3D11_MAPPED_SUBRESOURCE d3dMappedResource;
pImmediateContext->Map(_pVertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &d3dMappedResource);
ST_Vertex* pBuffer = (ST_Vertex*)d3dMappedResource.pData;
pBuffer->xfPosition.x = changedVertex.xfPosition.x;
pBuffer->.xfPosition.y = changedVertex.xfPosition.y;
pBuffer->.xfPosition.z = changedVertex.xfPosition.z;
pImmediateContext->Unmap(_pVertexBuffer, 0);
由于您使用 D3D11_MAP_WRITE_DISCARD,请确保在此处写入所有属性(不仅是位置)。
现在完成后,您可以使用ID3D11DeviceContext::CopySubresourceRegion仅复制当前位置的修改顶点:
我假设 vertexID 是修改后的 vertex 的索引:
pd3DeviceContext->CopySubresourceRegion(_pVertexBuffer,
0, //must be 0
vertexID * sizeof(ST_Vertex), //location of the vertex in you gpu vertex buffer
0, //must be 0
0, //must be 0
_pCopyVertexBuffer,
0, //must be 0
NULL //in this case we copy the full content of _pCopyVertexBuffer, so we can set to null
);
现在,如果您想更新顶点列表,事情会变得更加复杂,您有几个选择:
-首先你在一个循环中应用这个单顶点技术,如果你的变更集很小,这将工作得很好。
- 如果您的变更集非常大(接近几乎完整的顶点大小,您可能可以改写整个缓冲区)。
- 一种中间技术是使用计算着色器来执行更新(这是我通常使用的最灵活的版本)。发布所有 c++ 绑定代码会太长,但这里是概念:
- 你的顶点缓冲区必须有 BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_UNORDERED_ACCESS;//这允许写wioth计算
- 您需要为此缓冲区创建一个 ID3D11UnorderedAccessView (以便着色器可以写入它)
- 您需要以下其他标志: D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS //这允许写入为 RWByteAddressBuffer
- 然后,您创建两个动态结构化缓冲区(我更喜欢那些超过 byteaddress 的缓冲区,但 dx11 中不允许顶点缓冲区和结构化缓冲区,因此对于写入,您需要 raw 代替)
- 第一个结构化缓冲区的步幅为 ST_Vertex(这是您的变更集)
- 第二个结构化缓冲区的步幅为 4(uint,这些是索引)
- 两个结构化缓冲区都获得任意元素计数(通常我使用 1024 或 2048),因此这将是您一次可以更新的最大顶点数量。
- 两个结构化缓冲区都需要一个 ID3D11ShaderResourceView(着色器可见,只读)
然后更新过程如下:
- 在结构化缓冲区中写入修改后的顶点和位置(使用地图丢弃,如果你必须复制更少的话)
- 附加两个结构化缓冲区以供读取
- 附加 ID3D11UnorderedAccessView 进行写入
- 设置您的计算机着色器
- 呼叫调度
- 分离 ID3D11UnorderedAccessView 以进行写入(这非常重要)
这是一个示例计算着色器代码(为简单起见,我假设您的顶点只是位置)
cbuffer cbUpdateCount : register(b0)
{
uint updateCount;
};
RWByteAddressBuffer RWVertexPositionBuffer : register(u0);
StructuredBuffer<float3> ModifiedVertexBuffer : register(t0);
StructuredBuffer<uint> ModifiedVertexIndicesBuffer : register(t0);
//this is the stride of your vertex buffer, since here we use float3 it is 12 bytes
#define WRITE_STRIDE 12
[numthreads(64, 1, 1)]
void CS( uint3 tid : SV_DispatchThreadID )
{
//make sure you do not go part element count, as here we runs 64 threads at a time
if (tid.x >= updateCount) { return; }
uint readIndex = tid.x;
uint writeIndex = ModifiedVertexIndicesBuffer[readIndex];
float3 vertex = ModifiedVertexBuffer[readIndex];
//byte address buffers do not understand float, asuint is a binary cast.
RWVertexPositionBuffer.Store3(writeIndex * WRITE_STRIDE, asuint(vertex));
}
推荐阅读
- angular6 - 带有角度 6 的 Dialogflow 我应该使用 api-ai-javascript 还是基本 url?
- ios - 如何在 iPhone X 上将 ARSCNView 适配到 SafeArea?
- c# - 如何拥有列表视图行的所有元素?
- asp.net - asp.net MVC Web API - 记录发送给用户的新承载令牌
- python - 使用 Python 请求在提示中单击 OK 按钮
- c# - 将 JSON 结构动态转换为数组
- android - 将哈希图存储在数组中,反之亦然
- tensorflow - 多个 Inception V3 训练模型的 GPU 问题
- jquery - 引导表中的动态字符串
- sql - 需要决策树的想法