正如前面的回答所述,每次更新整个缓冲区都会很慢,具体取决于模型大小。
解决方案确实是实现部分更新,有两种可能:您想要更新单个顶点,或者您想要更新任意索引(例如,您想要一次移动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
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 ++绑定代码会太长,但是这里是概念:
- 顶点缓冲区必须具有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(着色器可见,只读)
然后更新过程如下:
- 在结构化缓冲区中写入修改后的顶点和位置(如果必须复制较少,则使用映射丢弃即可)
- 附加两个结构化缓冲区以进行读取
- 附加用于写入的ID3D11UnorderedAccessView
- 设置您的计算着色器
- 调用发布
- 分离用于写入的ID3D11UnorderedAccessView(这非常重要)
这是一个示例计算着色器代码(为了简单起见,我假设您的顶点仅包含位置)
cbuffer cbUpdateCount : register(b0)
{
uint updateCount;
};
RWByteAddressBuffer RWVertexPositionBuffer : register(u0);
StructuredBuffer<float3> ModifiedVertexBuffer : register(t0);
StructuredBuffer<uint> ModifiedVertexIndicesBuffer : register(t0);
#define WRITE_STRIDE 12
[numthreads(64, 1, 1)]
void CS( uint3 tid : SV_DispatchThreadID )
{
if (tid.x >= updateCount) { return; }
uint readIndex = tid.x;
uint writeIndex = ModifiedVertexIndicesBuffer[readIndex];
float3 vertex = ModifiedVertexBuffer[readIndex];
RWVertexPositionBuffer.Store3(writeIndex * WRITE_STRIDE, asuint(vertex));
}
vIndice
是索引缓冲还是变更集缓冲? - Alex