0

我使用如下渲染循环;

  1. 孤立数据并映射缓冲区。
  2. 记录命令并将生成的顶点写入缓冲区。
  3. 取消映射缓冲区。
  4. 迭代可以更改状态、绑定纹理或绘制的命令。

目前,我使用单一交错顶点格式 (SoA),它具有我的任何着色器都可以使用的所有属性。

struct OneSizeFitAllVertex
{
    float pos[3];
    float uv0[2];
    float uv1[2];
    float col[4];
};

例如,当使用仅使用位置和颜色的更简单的着色器时,我只会在映射内存中写入我关心的属性,并且着色器代码会简单地忽略所有未使用的属性。

因为这感觉很浪费,我正在考虑为我的每个着色器使用不同的顶点格式。

使用简单着色器渲染的简单对象将使用 SimpleVertex:

struct SimpleVertex
{
    float pos[3];
    float col[4];
};

而其他多纹理对象将使用多纹理着色器并使用 MultitextureVertex 进行渲染:

struct MultitextureVertex
{
    float pos[3];
    float uv0[2];
    float uv1[2];
}; 

我应该如何处理这些不同的格式?

我应该在同一个映射缓冲区中写入所有不同格式的顶点并在绘制之前更改我的 AttribPointers 吗?这样可以节省一些空间。

我应该为每种顶点格式映射不同的缓冲区吗?或许更有效率。

还是我应该保持“一刀切”的顶点格式?这更容易。

我很想知道在这种情况下最好的做法是什么。谢谢。

4

1 回答 1

0

根据您的底层系统架构,可能会有很多变化,但我们假设您使用的是带有专用图形内存的独立 GPU(例如,AMD 或 NVIDIA)。

您没有提及是否将属性交错在结构数组AoS)中(可能类似于以下内容):

struct Vertex {
    float position[3];
    float normal[3];
    float uv[2];
    ...
};

或将相似的属性组合在一起(通常称为数组结构或(SoA))

struct VertexAttributes {
    float positions[N][3];
    float normals[N][3];
    float uv[N][2];
    ...
};

鉴于您使用的是缓冲区映射方法,这很重要。当您映射整个缓冲区时,GPU 可能需要将其缓冲区版本复制到 CPU,CPU 为您提供更新值的指针。当您取消映射缓冲区时,驱动程序会将缓冲区复制回 GPU。使用AoS布局和您的技术,您将触及整个缓冲区的小部分,并且由于 GPU 驱动程序不知道您更新了哪些内存位,因此唯一的办法是将整个内容复制回 GPU。根据大小的不同,这可能会在多个级别产生重大影响(CPU 缓存读取利用率低,消耗大量 CPU 到 GPU 总线带宽等)。不幸的是,如果您只更新一小部分顶点属性,则没有很好的选择。但是,如果您要更新所有属性,则这种方法是可以的(尽管通常建议使用glBufferSubData或类似的命令,它将从 GPU 读回的内容保存到 CPU)。

相反,如果您使用SoA方法,将整个缓冲区映射到其中会导致类似的问题,但情况可能会更好。由于特定属性的值在内存中是连续的,因此您可以使用仅映射到您需要的内存中的东西(但同样,出于前面所述的原因glMapBufferRange,我仍然建议使用)。glBufferSubData鉴于您当前的情况,这是我建议的。

于 2017-06-06T14:46:32.873 回答