一文带你了解VAO、VBO与IBO功能与用法

1. 一个绘制矩形的最简示例

c++ 复制代码
static const GLfloat vertices[] = {
	-1.0f, -1.0f,
	1.0f, -1.0f,
    -1.0f,  1.0f,
    1.0f,  1.0f,
};

static const GLfloat textureCoordinates[] = {
    0, 0,
    1, 0,
    0, 1,
    1, 1
};

// 将顶点数据指定给顶点着色器中的索引_renderPositionSlot的attribute变量
glEnableVertexAttribArray(_renderPositionSlot);
glVertexAttribPointer(_renderPositionSlot, 2, GL_FLOAT, 0, 0, vertices);

// 将纹理坐标数据指定给片段着色器中的索引renderTextureCoordSlot的attribute变量
glEnableVertexAttribArray(_renderTextureCoordSlot);
glVertexAttribPointer(_renderTextureCoordSlot, 2, GL_FLOAT, 0, 0, textureCoordinates);

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

上述写法存在的问题

glVertexAttribPointer不会将顶点数据一直存储在显存中,而我们渲染的时候,所有要访问的数据必需在显存中,因此,每次渲染时,OpenGL需要将这些顶点数据从内存再复制到显存 。如果顶点数据量大的时候,比如3D模型渲染可以有上万个顶点,每次渲染都做这样的一次复制,会明显增加性能开销

因此为了避免以上问题,在编写openGL代码时,我们应该尽可能地使用VAO、VBO以及IBO等缓冲对象。

2. 顶点缓冲对象(Vertex Buffer Objects, VBO)

VBO用于可以和顶点数据绑定,来管理这些数据。它的优势有如下几点:

  • 一次性发送一大批数据到显存中,当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点。
  • 在显存中储存这些数据,每次渲染时无需重复数据复制操作

使用流程

  • 上传数据到显存
c++ 复制代码
// 创建VBO
GLuint VBO; 
glGenBuffers(1, &VBO);
// 绑定VBO
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 上传顶点数据到VBO,第四个参数指定了希望显卡如何管理数据
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
  • 绑定着色器中的顶点属性
c++ 复制代码
// 绑定顶点属性,告诉openGL如何解析顶点数据
glEnableVertexAttribArray(0);
// 第一个参数是着色器中属性索引,第五个参数是步长,指定了两个数据之间的间隔
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
....
// 绘制
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

每当我们绘制一个物体的时候都必须重复这一过程。这看起来可能不多,但是如果有超过5个顶点属性,上百个不同物体呢(对于3D场景这并不罕见),绑定正确的缓冲对象,为每个物体配置所有顶点属性复杂度就会越来越高。VAO的出现就是为了解决这个问题。

3. 顶点数组对象(Vertex Array Object, VAO)

VAO可以像VBO那样被绑定,紧随气候的顶点属性绑定操作都会被储存在这个VAO中,之后再绘制物体的时候只需要绑定相应的VAO就行了,不需要重新绑定顶点数据。

VAO的优势 VAO使得在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。设置的所有状态都将存储在VAO中。

使用流程

c++ 复制代码
// ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: .. 
// 1. 绑定VAO 
glBindVertexArray(VAO); 
// 2. 把顶点数组复制到缓冲中供OpenGL使用 
glBindBuffer(GL_ARRAY_BUFFER, VBO); 
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 设置顶点属性指针 
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0); glEnableVertexAttribArray(0); 
//4. 解绑VAO 
glBindVertexArray(0); 

[...] 

// ..:: 绘制代(游戏循环中) :: .. 
// 5. 绘制物体 
glUseProgram(shaderProgram); 
glBindVertexArray(VAO); 
someOpenGLFunctionThatDrawsOurTriangle(); 
glBindVertexArray(0);

4. 索引缓冲对象(Index Buffer Objects, IBO)

索引缓冲对象的作用是为了复用顶点数据。以绘制两个三角形组成的矩形为例:

c++ 复制代码
GLfloat vertices[] = { 
// 第一个三角形 
0.5f, 0.5f, 0.0f, // 右上角 
0.5f, -0.5f, 0.0f, // 右下角 
-0.5f, 0.5f, 0.0f, // 左上角 

// 第二个三角形 
0.5f, -0.5f, 0.0f, // 右下角 
-0.5f, -0.5f, 0.0f, // 左下角 
-0.5f, 0.5f, 0.0f // 左上角 
};

从数据上可以看到,有几个顶点坐标重复,原因很明显,矩形只有4个而不是6个顶点,因此必然有重复,这样带来的问题就是有50%的额外内存开销,在3D场景这个问题就被放大到无法接受。IBO专门储存索引,在告知OpenGL在绘制时到底要使用哪几个顶点。

c++ 复制代码
// 用索引数据改造后
GLfloat vertices[] = { 
0.5f, 0.5f, 0.0f, // 右上角 
0.5f, -0.5f, 0.0f, // 右下角 
-0.5f, -0.5f, 0.0f, // 左下角 
-0.5f, 0.5f, 0.0f // 左上角 
}; 

GLuint indices[] = { 
// 注意索引从0开始! 
0, 1, 3, // 第一个三角形 
1, 2, 3 // 第二个三角形 
};

当时用索引的时候,我们只定义了4个顶点,而不是6个。然后通过indices告诉openGL在绘制三角形使用哪几个顶点。从而大大增加了顶点的复用率,提升性能

使用流程

  • 上传数据
c++ 复制代码
// 创建缓冲对象
GLuint IBO; 
glGenBuffers(1, &IBO);
// 上传索引数据到IBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO); 
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,   GL_STATIC_DRAW);
  • 绘制时绑定IBO
c++ 复制代码
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 
// 第二个参数指定需要绘制的顶点个数
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

将IBO绑定到VAO 顶点数组对象同样可以保存索引缓冲对象的绑定状态。

c++ 复制代码
// ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: .. 
// 1. 绑定VAO 
glBindVertexArray(VAO); 
...
// 2. 复制我们的索引数组到一个索引缓冲中,供OpenGL使
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
...
// 3. 解绑VAO 
glBindVertexArray(0); 
tips 复制代码
在定义顶点数据时,一般会将顶点和纹理坐标组合起来,这也是配合VBO和IBO的常规优化用法
它的好处让顶点和纹理坐标在存储上靠近,利于OpenGL取数据,提高性能
特别是在3D渲染时,数据一般都是这样组织的
相关推荐
阳光开朗_大男孩儿19 小时前
为什么glfwWindowHint设置的属性,glfwCreateWindow可以直接使用?
前端·数据库·opengl
刘好念3 天前
[OpenGL]使用TransformFeedback实现粒子效果
c++·计算机图形学·opengl
吃豆腐长肉5 天前
着色器 (三)
opengl·着色器
吃豆腐长肉5 天前
opengl 着色器 (四)最终章收尾
opengl·着色器
德林恩宝6 天前
WebGPU、WebGL 和 OpenGL/Vulkan对比分析
web·webgl·opengl·webgpu
zaizai10079 天前
LearnOpenGL学习(高级OpenGL -> 高级GLSL,几何着色器,实例化)
opengl
刘好念10 天前
[OpenGL] Transform feedback 介绍以及使用示例
c++·计算机图形学·opengl
爱看书的小沐10 天前
【小沐学GIS】基于C++绘制三维数字地球Earth(OpenGL、glfw、glut、QT)第三期
c++·qt·opengl·earth·osm·三维地球·数字地球
闲暇部落13 天前
OpenGL ES详解——多个纹理实现混叠显示
opengl·纹理叠加
LiQingCode14 天前
OpenTK中文教程——1.7变换
c#·opengl