Using OpenGL More Efficiently

如何优雅地使用OpenGL

Posted by Lyle on September 1, 2022

Vertex Array

不同于其他任何图形API,OpenGL中存在Vertex Array的概念,在默认情况下(OpenGL Profile被设定为 GLFW_OPENGL_COMPAT_PROFILE)OpenGL会为我们自动创建1个vertex array。 因此一般情况下如果我们只绑定一个vertex buffer,是感受不到vertex array的存在的。

float positions[] = {
    -0.5f, -0.5f,
     0.5f, -0.5f,
     0.5f,  0.5f,
    -0.5f,  0.5f
};
unsigned int indices[] = {
    0, 1, 2,
    2, 3, 0
};

unsigned int bufferId;
glGenBuffers(1, &bufferId);
glBindBuffer(GL_ARRAY_BUFFER, bufferId);
glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(float), positions, GL_STATIC_DRAW);

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, false, 2 * sizeof(float), (const void*)0);
// 此时vertex array已被自动创建,并使VertexAttribPointer指向index=0的buffer

unsigned int ibo;
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW);

上述流程其实是隐式地将vertex array与vertex buffer进行了绑定。我们知道vertex buffer本质是一堆数据,它并不知道自己的类型,因此仅通过vertex buffer无法获得整个buffer的布局。

而vertex array的作用就是将数据的布局与vertex buffer进行绑定,使得index buffer可以根据索引获取相应的数据。 因此,从严谨性的角度看,我们应该显示地指定vertex array(按照以下方式调整OpenGL Profile后,不指定vertex array会出现错误)。

// 设定版本为3.3
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);

glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

定义vertex array:

unsigned int vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

unsigned int bufferId;
glGenBuffers(1, &bufferId);
glBindBuffer(GL_ARRAY_BUFFER, bufferId);
glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(float), positions, GL_STATIC_DRAW);

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, false, 2 * sizeof(float), (const void*)0);
// 此时vertex buffer通过VertexAttribPointer与位于vao处的vertex array绑定

那么,除了严谨的角度以外,我们为什么仍然有指定vertex array的必要呢? 考虑一种情况,在已经有vertex buffer被绑定并且屏幕已经绘制出图形的情况下,我们需要在某一时间用不同的buffer去绘制其他的图形,这个时候我们需要重新绑定不同的vertex buffer:

while (!glfwWindowShouldClose(window))
{
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(shader);

    // 仅有一个全局vertex array的情况,以下部分需要重新执行
    glBindBuffer(GL_ARRAY_BUFFER, bufferId);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, false, 2 * sizeof(float), (const void*)0);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

    glDrawElements(...);
    ...
}

而在我们定义vertex array后,上述过程可以简化为

while (!glfwWindowShouldClose(window))
{
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(shader);

    glBindVertexArray(vao);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

    glDrawElements(...);
    ...
}

在把OpenGL的相应流程封装至类中时,vertex array可以更多地增强代码的可读性。