OpenGL 4.3 的更新中,使用 glVertexAttribFormat 和 glBindVertexBuffer 取代 glVertexAttribPointer 来将 VBO 绑定到 VAO。由于老是忘记写法,故记录下来。本文使用 OpenGL 4.5 新增的 DSA 函数,最后会贴出与 4.3 函数的关系表,它们是一一对应的。
glVertexAttribPointer 函数有两个缺陷。
第一个缺陷是它依赖于 GL_ARRAY_BUFFER 的内容。调用 glVertexAttribPointer 前,必须绑定 vbo,然后将特定被绑定 vbo 的索引、格式信息复制到 vao 中。这是一种非常笨拙的方式,因为这样 vao 和特定 vbo 处于绑死的关系,一旦对这个特定 vbo 有任何修改,都要重新绑定 vbo 的信息到 vao 中(根据实现有所不同),这一点上很容易产生 bug,云风的博客也说明过这个问题。
不仅如此,glVertexAttribPointer 最后一个参数使用指针而不是整数来表示偏移量。使用时都要加一个丑陋的转换,相信很多人都碰到过下述的写法。
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), static_cast<void*>(6 * sizeof(float)));
第二个缺点是它将逻辑上分离的两个操作合在一起:获取数据、组织数据。这也带来性能的浪费。如果只要改变组织数据的方式,比如改变偏移量,也必须一次性指定 buffer 信息和组织格式;切换绑定的 buffer 也是如此。
话虽如此,即使在写 OpenGL 4.5 程序的时候,这两个操作几乎是永远伴随在一起的(大笑)。这也是早期定义 glVertexAttribPointer 的原因,更先进的 OpenGL 将它们区分开,给予程序员选择的自由。
OpenGL 4.5 可以使用 glVertexArrayVertexBuffer 来将 vbo 绑定到 vao 独有的绑定点,与 ubo 的绑定点相似,但相互不干扰。然后使用 glVertexArrayAttribBinding 将 glsl 中的 attrib 变量绑定到同一个绑定点即可。
void glVertexArrayVertexBuffer(GLuint vaobj, // vao 系数
GLuint bindingindex, // 绑定点
GLuint buffer, // 需要绑定的 vbo
GLintptr offset, // vbo 在 buffer 内的偏移字节数
GLsizei stride); // 每个 vertex 的字节数(pos,normal 等表示一个 vertex)
void glVertexArrayAttribBinding(GLuint vaobj, // vao 系数
GLuint attribindex, // attrib 位置(可以在 glsl 中指定)
GLuint bindingindex); // 绑定点
glVertexArrayAttribFormat 被用来指定 attrib 的组织形式。它是绑定到 attrib 上的,而不是 bindingindex 上。
void glVertexAttribFormat(GLuint attribindex, // attrib 位置
GLint size, // 数据分量的个数,RGB 为 3
GLenum type, // 每个分量的格式
GLboolean normalized, // 整形是否已经可以映射到 0-1
GLuint relativeoffset); // 不同变量的相对偏移,比如 pos 和 uv
比如,对于下面的例子,位置、颜色和纹理坐标存储在一个 vbo 中。
struct Vertex
{
GLfloat pos[3];
GLubyte color[4];
GLushort texCoord[2];
};
就可以使用下面的方式组织数据。
glVertexAttribFormat(0, ..., offsetof(Vertex, pos)); //AKA: 0
glVertexAttribFormat(1, ..., offsetof(Vertex, color)); //Probably 12
glVertexAttribFormat(2, ..., offsetof(Vertex, texCoord)); //Probably 16
既然上述函数是拆分后的结果,就可以用新函数来表示老函数,如下所示。
void glVertexAttrib*Pointer(GLuint index, GLint size, GLenum type, {GLboolean normalized,} GLsizei stride, const GLvoid * pointer)
{
glVertexAttrib*Format(index, size, type, {normalized,} 0);
glVertexAttribBinding(index, index);
GLuint buffer;
glGetIntegerv(GL_ARRAY_BUFFER_BINDING, buffer);
if(buffer == 0)
glErrorOut(GL_INVALID_OPERATION); //Give an error.
if(stride == 0)
stride = CalcStride(size, type);
GLintptr offset = reinterpret_cast<GLintptr>(pointer);
glBindVertexBuffer(index, buffer, offset, stride);
}
OpenGL 4.5 | OpenGL 4.3 |
---|---|
glVertexArrayVertexBuffer | glBindVertexBuffer |
glVertexArrayAttribBinding | glVertexAttribBinding |
glVertexArrayAttribFormat | glVertexAttribFormat |