一文带你了解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渲染时,数据一般都是这样组织的
相关推荐
扶尔魔ocy20 小时前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行并进行态势标绘(四)
qt·opengl·osg
:mnong2 天前
Web 三维可视化技术对比:ParaView Web vs 本项目 wasm + Three.js
前端·webgl·opengl·opencl·科学可视化
扶尔魔ocy3 天前
【qt openGL入门笔记】OSG实现显示飞机模型飞行的点迹(二)
qt·opengl·osg
扶尔魔ocy3 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行(三)
opengl·osg
扶尔魔ocy4 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行
qt·opengl·osg
扶尔魔ocy8 天前
【qt openGL入门笔记】FPS相机
qt·opengl
扶尔魔ocy8 天前
qt openGL入门笔记】FPS相机和轨道相机
qt·opengl
扶尔魔ocy10 天前
【qt openGL入门笔记】基础光照
qt·opengl
扶尔魔ocy10 天前
【qt openGL入门笔记】坐标系统和摄像机
qt·opengl
茉莉玫瑰花茶13 天前
OpenGL [ 纹理 ]
开发语言·c++·opengl