更新 Direct3D 网格的顶点缓冲区的最快方法

本文关键字:方法 缓冲区 顶点 Direct3D 网格 更新 | 更新日期: 2023-09-27 18:36:03

>我有一个地形网格,其中每个顶点的 Z 值只需要每帧更新一次。 我当前的方法如下所示:

int stepping = CustomVertex.PositionNormalTextured.StrideSize / 4;
//ZPtr points to the Z value of the first PositionNormalTextured in the mesh.  
//This way we don't have to dereference ->Z for each vertex.
float* ZPtr = &(((CustomVertex.PositionNormalTextured*)
    TerrainMesh.LockVertexBuffer(LockFlags.NoOverwrite).InternalDataPointer)->Z);
float* DPtr = TerrainHeight; //point to begin scanning result
float* EndPtr = DPtr + TerrainMesh.NumberVertices; //point to stop scanning result
do { *ZPtr = *DPtr; ZPtr += stepping; } while (++DPtr < EndPtr); //copy data
TerrainMesh.UnlockVertexBuffer(); //unlock

在这里,TerrainHeight是一个浮点数组,使用Marshal.AllocHGlobal表示地形高度。 基本上,它会扫描整个 TerrainHeight 数组,并将每个值复制到网格中相应 PositionNormalTextured 的 Z 值。 我使用 LockFlags.NoOverwrite 来避免创建数组的新副本,尽管这似乎并不比 LockFlags.Discard 快。

更新网格所需的时间与在CPU中计算新地形所需的时间一样长或更长,这使我相信应该有更快的方法。 我一直无法在谷歌上找到有关此的信息。 有没有更好的方法来更新顶点缓冲区? 在重要的情况下,网格的大小是用户设置的,可能包括超过一百万个顶点(这是通过多个网格完成的),但默认设置是 32k 顶点,这是单个 D3D 网格的最大值。

更新 Direct3D 网格的顶点缓冲区的最快方法

您似乎不了解DiscardNoOverwrite标志的后果。请阅读 DirectX SDK 帮助中性能优化下的使用动态顶点和索引缓冲区部分。假设您使用的是动态顶点缓冲区,则Discard表示"我正在替换整个缓冲区",NoOverwrite表示"我正在写入缓冲区的未使用部分,我保证不会更改我已经使用过的任何部分"。

使用任一标志,您都必须编写地形顶点的每个组件,甚至是在新帧中没有更改的组件。

如果不使用动态顶点缓冲区,则在尝试锁定下一帧的顶点缓冲区时(如果 GPU 仍在使用它),则可能会遇到停滞。在这种情况下,您需要使用多个顶点缓冲区,并根据地形高度变化的每一帧使用不同的缓冲区进行锁定、更新、解锁和渲染。您还必须使用所有地形顶点数据初始化所有这些缓冲区。

我建议将网格的 z 值分离到它们自己的顶点缓冲区中 - 假设您没有更新位置上的 x 和 y、法线(如果 z 发生变化,则可能不正确)或纹理坐标。这样,您就可以在每一帧原封不动的情况下使用PositionNormalTextured(不带位置 z)顶点缓冲区,并从一开始就使用 Discard 标志填充每一帧的动态 z 位置缓冲区,而无需跨步到每个 Z 值。由于步伐消失了,您可以使用平面内存副本来做到这一点。

您将为顶点着色器提供带有 SetStreamSource( 1, ZPositionVB, ... ) 的 z 位置值。您需要调整顶点声明以从流 1 读取 z 位置值,并调整顶点着色器以在转换之前合并 z 位置值。

如果其中一些不适合 C#,请道歉。