OpenGL缓冲区对象之VAO

时间:2021-10-21 05:48:13

简介

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();
OpenGL缓冲区对象之VAO


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

VAO包含的内容

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

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):
OpenGL缓冲区对象之VAO

示例程序

#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);
glGenVertexArrays(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;
}