0评论
想免费获取内部独家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对象中存储的内容包括:
- VAO开启或者关闭的状态(glEnableVertexAttribArray和glDisableVertexAttribArray)
- 使用glVertexAttribPointer对顶点属性进行的设置
- 存储顶点数据的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; }
腾讯游戏学院公众号
