0评论

OpenGL缓冲区对象之VAO详解

清风 2019-03-11 84浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏开发行业精英群711501594

在《OpenGL渲染管线、VAO/VBO/EBO》中给大家介绍了下VAO的概念,但这篇介绍的要更详细的多。VAO ( Vertex Array Object )是OpenGL用来处理顶点数据的一个缓冲区对象,它不能单独使用,都是结合VBO来一起使用的。VAO是OpenGL CoreProfile 引入的一个特性。事实上在CoreProfile中做顶点数据传入时,必须使用VAO方式。

当我们使用VBO传入顶点数据时,一般的处理如下:
// 绑定VBO,设置VBO中的数据
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 设置顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);  
// 2. 使用Shader程序
glUseProgram(shaderProgram);
// 3. 绘制
someOpenGLFunctionThatDrawsOurTriangle();  

每当我们绘制一个几何体时,我们需要重复同样的工作(首先绑定缓冲区、然后设置顶点属性)。当需要绘制的物体很多时,这个过程就显得有些耗时。那么我们有没有一种方式来简化这一过程呢?这就是VAO做的事情,它将所有顶点绘制过程中的这些设置和绑定过程集中存储在一起,当我们需要时,只需要使用相应的VAO即可。

VAO的这种方式有点像一个中介,把所有繁琐的绑定和顶点设置工作都集中起来处理,我们需要绘制时,直接找这个中介就好了。

VAO包含的内容

VAO对象中存储的内容包括:
  1. VAO开启或者关闭的状态(glEnableVertexAttribArray和glDisableVertexAttribArray)
  2. 使用glVertexAttribPointer对顶点属性进行的设置
  3. 存储顶点数据的VBO对象

VAO的使用

1.创建VAO

创建VAO使用glGenVertexArrays参数与创建其他缓冲区对象的glGenBuffers类似
void glGenVertexArrays( GLsizei n,
    GLuint *arrays);

2.绑定并设置VAO

在创建VAO之后,需要使用glBindVertexArray设置它为当前操作的VAO,之后我们所有关于顶点数据的设置(包括数据使用的VBO对象,顶点的属性设置的信息都会被存储在VAO之中),在设置完成之后一般会解绑VAO,然后在需要绘制的时候启用相应的VAO对象。具体的代码如下所示:
//创建VAO
GLuint VAO;
glGenVertexArrays(1, &VAO);
//设置当前VAO,之后所有操作(注意:这些操作必须是上文VAO中包含的内容所注明的调用,其他非VAO中存储的内容即使调用了也不会影响VAO)存储在该VAO中
glBindVertexArray(VAO);
   glBindBuffer(GL_ARRAY_BUFFER, VBO); //设置了VBO
   glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//设置VBO中的数据
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0); //设置顶点属性(索引为0的属性,与shader中的内容有交互)
    glEnableVertexAttribArray(0); //设置开启顶点属性(索引为0的属性,与shader中的内容有交互)
glBindVertexArray(0); //解绑VAO(解绑主要是为了不影响后续VAO的设置,有点类似于C++中指针delete后置空,是个好习惯)

通过上面的代码就完成了对VAO的设置,当我们需要绘制的时候,使用的代码类似于:
glUseProgram(shaderProgram);
glBindVertexArray(VAO); //绑定我们需要的VAO,会导致上面所有VAO保存的设置自动设置完成
someOpenGLFunctionThatDrawsOurTriangle();   
glBindVertexArray(0);   //解绑VAO

另外需要注意的是,当我们使用EBO的时候,VAO中也会记录索引信息,因此完整的VAO所包含的内容图如下所示(添加了EBO):

示例程序
#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "freeglut.lib")
#include <stdio.h>
#include <iostream>
#include <gl/glew.h>
#include <gl/glut.h>
GLuint eboID;
GLuint vboID;
GLuint vaoID;
GLuint shaderProgram;
const GLchar* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"void main()\n"
"{\n"
"gl_Position = vec4(position.x, position.y, position.z, 1.0);\n"
"}\0";
const GLchar* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
"color = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
GLfloat vertices[] = {
    0.5f, 0.5f, -1.0f,   // 右上角
    0.5f, -0.5f, -1.0f,  // 右下角
    -0.5f, -0.5f, -1.0f, // 左下角
    -0.5f, 0.5f, -1.0f   // 左上角
};
GLuint indices[] = {
    // 起始于0!
    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};
void ChangeSize(int w, int h)
{
    if (h == 0)
        h = 1;
    glViewport(0, 0, w, h);
}
void initShaders()
{
    // Build and compile our shader program
    // Vertex shader
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // Check for compile time errors
    GLint success;
    GLchar infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // Fragment shader
    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    // Check for compile time errors
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // Link shaders
    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    // Check for linking errors
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
}
void SetupRC()
{
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glGenBuffers(1, &eboID);
    glGenBuffers(1, &vboID);
    glGenBuffers(1, &vaoID);
    //首先绑定vaoID,之后所有设置都存储在vaoID中
    glBindVertexArray(vaoID);
    glBindBuffer(GL_ARRAY_BUFFER, vboID);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboID);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    //注意:这里是可以的,因为glVertexAttribPointer将注册eboID作为它的VBO,因此这里可以安全的解绑
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
    initShaders();
}
void RenderScene(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glUseProgram(shaderProgram);
    glBindVertexArray(vaoID);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);
    glutSwapBuffers();
}
int main(int argc, char* argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    glutInitWindowSize(800, 600);
    glutCreateWindow("OpenGL");
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    SetupRC();
    glutMainLoop();
    glDeleteVertexArrays(1, &vaoID);
    glDeleteBuffers(1, &vboID);
    glDeleteBuffers(1, &eboID);
    return 0;
}