一文带你了解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渲染时,数据一般都是这样组织的
相关推荐
闲暇部落5 小时前
Android OpenGL ES详解——绘制圆角矩形
opengl·圆形·矩形·圆角矩形
凌云行者1 天前
OpenGL入门008——环境光在片段着色器中的应用
c++·cmake·opengl
闲暇部落5 天前
Android OpenGL ES详解——立方体贴图
opengl·天空盒·立方体贴图·环境映射·动态环境贴图
闲暇部落6 天前
Android OpenGL ES详解——实例化
android·opengl·实例化·实例化数组·小行星带
闲暇部落8 天前
Android OpenGL ES详解——几何着色器
opengl·法线·法向量·几何着色器
刘好念13 天前
[OpenGL]使用OpenGL实现硬阴影效果
c++·计算机图形学·opengl
闲暇部落14 天前
Android OpenGL ES详解——纹理:纹理过滤GL_NEAREST和GL_LINEAR的区别
opengl·texture·linear·纹理过滤·nearest·邻近过滤·线性过滤
凌云行者15 天前
OpenGL入门005——使用Shader类管理着色器
c++·cmake·opengl
凌云行者15 天前
OpenGL入门006——着色器在纹理混合中的应用
c++·cmake·opengl
凌云行者18 天前
OpenGL入门004——使用EBO绘制矩形
c++·cmake·opengl