顶点数据应该基于每个属性还是每个属性进行布局?

时间:2014-10-23 03:40:01

标签: c++ opengl vertex

我有一段渲染网格的OpenGL代码。我使用VBO来渲染它们。现在,网格由具有以下属性的顶点组成:

glm::vec3 position;
glm::vec2 uv;
glm::vec4 color;
glm::vec3 normal;
glm::vec3 tangent;
glm::vec3 binormal;

目前,我按照每个顶点渲染顶点:

// Upload a vector of vertices
glBindBuffer(GL_ARRAY_BUFFER, &m_vbo);
glBufferData(GL_ARRAY_BUFFER, m_vertices.size() * sizeof(Vertex), &m_vertices[0], GL_STATIC_DRAW);

// Set the "layout" of the vertex attributes
// Binormal
glVertexAttribPointer(5, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*) (sizeof(glm::vec3) * 3 + sizeof(glm::vec2) + sizeof(glm::vec4)));
// Tangent
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*) (sizeof(glm::vec3) * 2 + sizeof(glm::vec2) + sizeof(glm::vec4)));
// Normal
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*) (sizeof(glm::vec3) + sizeof(glm::vec2) + sizeof(glm::vec4)));
// Color
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*) (sizeof(glm::vec3) + sizeof(glm::vec2)));
// UV
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*) (sizeof(glm::vec3)));
// Position
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*) 0);

// Draw
glDrawElements(GL_TRIANGLES, m_indices.size(), GL_UNSIGNED_SHORT, 0);

现在,我看到人们做的有点不同了。有些首先上传所有顶点位置,然后是UV数据,然后是法线等等。要对数据布局进行粗略的可视化:

// P = position, U = uv, N = normal

// Per-vertex layout
PUNPUNPUNPUNPUNPUNPUNPUNPUNPUNPUNPUN

// Per-attribute layout
PPPPPPPPPPPPUUUUUUUUUUUUNNNNNNNNNNNN

这两种布局之间有什么区别吗?是其中一个导致任何性能问题,特别是如果数据不断更新?

1 个答案:

答案 0 :(得分:2)

您描述的第一个布局通常称为" interleaved",并且通常被认为是有利的。原因是它导致更多的本地内存访问模式,这更加缓存友好。

使用不同布局的一个很好的理由是,如果某些属性比其他属性更新频率更高。在极端情况下,其中一些是静态的,而另一些是经常更新的,将静态属性保留在一个VBO中,使用GL_STATIC_DRAW实际上是有益的,并使用一个单独的缓冲区{{1}用于经常更改的属性。

@leemes在上面的评论中提出了另一个有趣的案例:如果你经常只使用一些属性进行绘制调用,那么也可能值得对它们进行不同的分组。在这种情况下,您可以拥有在交错布局中始终使用的属性,并将较少使用的属性分开。

尽管如此,渲染管道中通常会遇到更大的瓶颈,因此在目标合成基准测试之外可能难以测量差异。尽管如此,我认为保持一切尽可能精简是最值得的。特别是因为现在大多数计算机/设备依靠电池供电,你不想浪费任何东西。