OpenGL缓冲对象:VBO、IBO和VAO
VBO
VBO即Vertex Buffer Object(顶点缓冲对象),顶点数据块,在显存中,可被GPU直接访问。
创建并填充VBO
创建VBO,并将顶点数组中数据来填充到VBO(内存 --》显存)。
float vertices[] = { // postion // color 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下 -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下 -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 左上 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 1.0f // 右上 }; unsigned int VBO; glGenBuffers(1, &VBO); //分配1个独一无二的id,并赋值给VBO glBindBuffer(GL_ARRAY_BUFFER, VBO); // 把id为VBO绑定到GL_ARRAY_BUFFER类型的Buffer上 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 拷贝vertices数组(内存)到GL_ARRAY_BUFFER类型的Buffer(显存)
注:glBufferData的第四个参数指定了我们希望显卡如何管理给定的数据。它有3种形式:
GL_STATIC_DRAW :数据不会或几乎不会改变。
GL_DYNAMIC_DRAW:数据会被改变很多。
GL_STREAM_DRAW :数据每次绘制时都会改变。
三角形的位置数据不会改变,每次渲染调用时都保持原样,所以它的使用类型最好是GL_STATIC_DRAW。
如果一个缓冲中的数据将频繁被改变,那么使用的类型就是GL_DYNAMIC_DRAW或GL_STREAM_DRAW,这样就能确保显卡把数据放在能够高速写入的内存部分。
VBO显存布局
释放VBO
glDeleteBuffers(1, &VBO);
IBO
IBO即Index Buffer Object(索引缓冲对象),又称EBO(Element Buffer Object,元素缓冲对象)。索引数据块,在显存中,可被GPU直接访问。
创建并填充IBO
创建IBO,并将索引数组中数据来填充到IBO(内存 --》显存)。
unsigned int indices[] = { // note that we start from 0! 0, 1, 3, // first Triangle 1, 2, 3 // second Triangle }; unsigned int IBO; glGenBuffers(1, &IBO); //分配1个独一无二的id,并赋值给IBO glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO); // 把id为IBO绑定到GL_ELEMENT_ARRAY_BUFFER类型的Buffer上 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 拷贝indices数组(内存)到GL_ELEMENT_ARRAY_BUFFER类型的Buffer(显存)
IBO显存布局
释放IBO
glDeleteBuffers(1, &IBO);
VAO
VAO即Vertex Array Object(顶点数组对象),在显存中,用来记录顶点属性(Vertex Attribute)和IBO的上下文信息。创建并绑定VAO后,后续的顶点属性调用都会储存在这个VAO中。
使用VAO好处是,当配置顶点属性指针时,只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。
OpenGL3.3及之后,必须绑定VAO后,才能绘制对象。
在3.3之前,可以不使用VAO来绘制对象。
仅使用VBO的示例代码如下:
// 0. copy our vertices array in a buffer for OpenGL to use glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 1. then set the vertex attributes pointers glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // 2. use our shader program when we want to render an object glUseProgram(shaderProgram); // 3. draw the object glDrawArrays(GL_TRIANGLES, 0, 3);
使用VBO和IBO的示例代码如下:
// 0. copy our vertices array in a buffer for OpenGL to use glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 1. then set the vertex attributes pointers glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // 2. use our shader program when we want to render an object glUseProgram(shaderProgram); // 3. draw the object glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
创建并记录上下文信息到VAO
unsigned int VAO;
glGenVertexArrays(1, &VAO); //分配1个独一无二的id,并赋值给VAO
glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); // 把id为VBO绑定到GL_ARRAY_BUFFER类型的Buffer上 // position顶点属性 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // 启用location为0的顶点属性 注:顶点属性默认是禁用的 // color顶点属性 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float))); glEnableVertexAttribArray(1); // 启用location为1的顶点属性 注:顶点属性默认是禁用的 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO); // 把id为IBO绑定到GL_ELEMENT_ARRAY_BUFFER类型的Buffer上
glVertexAttribPointer函数的参数非常多,各参数含义如下:
① 第一个参数index指定vs中layout的location值。如上在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)。
② 第二个参数size指定顶点属性的大小。如position顶点属性是一个vec3,它由3个值组成,所以大小是3。
③ 第三个参数type指定数据的类型,这里是GL_FLOAT(GLSL中vec3都是由浮点数值组成的)。
④ 第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。如果不需要,则设置为GL_FALSE。
⑤ 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。
要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列【Tightly Packed】时才可用)。
一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。
⑥ 最后一个参数的类型是void*。表示位置数据在缓冲中起始位置的偏移量(Offset)。如postion数据在数组的开头,所以这里是0。
顶点着色器(vs)代码如下:
#version 330 core layout (location = 0) in vec3 aPos; // position的属性位置值为 0 layout (location = 1) in vec3 aColor; // color的属性位置值为 1 out vec3 ourColor; // 向片段着色器输出一个颜色 void main() { gl_Position = vec4(aPos, 1.0); ourColor = aColor; // 将ourColor设置为我们从顶点数据那里得到的输入颜色 }
VAO显存布局
使用VAO中的上下文信息来绘制对象
有了VAO,在渲染循环中,就可以直接使用VAO来切换顶点属性和IBO的上下文信息,然后调用Draw命令来绘制对象了。
while (1) {
// Sleep for a while
glBindVertexArray(VAO);
//glDrawArrays(GL_TRIANGLES, 0, 3); // 参数1为绘制的图元类型;参数2为顶点数组的起始索引;参数3为绘制的顶点数 注:没有IBO时,使用glDrawArrays来绘制 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); // 参数1为绘制的图元类型;参数2为绘制的顶点数;参数3为索引的类型;参数4为IBO的起始偏移 }
释放VAO
glDeleteVertexArrays(1, &VAO);