directx - 如何快速更新动态顶点缓冲区?

标签 directx transformation directx-11 vertex-buffer

我正在尝试制作一个简单的 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 可见。

接下来创建第二个具有单个顶点大小的动态缓冲区(在这种情况下您不需要任何绑定(bind)标志,因为它将仅用于副本)
_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 是修改后的顶点的索引:
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++ 绑定(bind)代码会太长,但这里是概念:
  • 你的顶点缓冲区必须有 BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_UNORDERED_ACCESS;//这允许写与计算
  • 您需要为此缓冲区创建一个 ID3D11UnorderedAccessView(以便着色器可以写入)
  • 您需要以下杂项标志:D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS//这允许写入为 RWByteAddressBuffer
  • 然后你创建两个动态结构化缓冲区(我更喜欢那些而不是字节地址,但顶点缓冲区和结构化在 dx11 中是不允许的,所以对于写一个你需要原始的)
  • 第一个结构化缓冲区的步幅为 ST_Vertex(这是您的变更集)
  • 第二个结构化缓冲区的步幅为 4(uint,这些是索引)
  • 两个结构化缓冲区都获得任意元素计数(通常我使用 1024 或 2048),因此这将是您可以在一次通过中更新的最大顶点数量。
  • 两个结构化缓冲区都需要一个 ID3D11ShaderResourceView(着色器可见,只读)

  • 然后更新过程如下:
  • 在结构化缓冲区中写入修改后的顶点和位置(使用 map 丢弃,如果您必须少复制它就可以了)
  • 附加两个结构化缓冲区以供读取
  • 附加 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));
    }
    

    关于directx - 如何快速更新动态顶点缓冲区?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60841420/

    相关文章:

    c++ - 在 Direct X 9.0c 中获取 LPDIRECT3DTEXTURE9 的尺寸?

    opencv - 寻找两帧之间的转换

    c++ - 一个 Constant Buffer 可以用于多个 Object 吗?

    html - 具有笛卡尔坐标系的简单可缩放 SVG 图形

    java - 如何使用 Javax.xml.transformer API 将 XML 文档传递给 XSL 文件?

    c++ - 使用 DirectX 11 API 读取 HLSL 语义和注释?

    c++ - 在D3D11中锁定

    opengl - 为什么我们需要 3d 甚至 4d 纹理?

    c++ - Direct2D如何打开共享纹理

    c++ - 对 OnResetDevice() 和 OnLostDevice() 的一般调用