简介
对于OpenGL而言,一个模型所需要的所有顶点信息(例如:位置,法线,贴图...)都需要放在GPU的显存里,如何高效的管理且有序的管理这些数据便是OpenGL所考虑的问题,了解了OpenGL管理这些数据的方式,我们也就明白了VAO/VBO/EBO三者之间的区别与联系。
如图所示,这是在blender中建模的一个模型,由四个顶点构成了两个三角形,将其导出成obj格式后,并且用txt格式打开便可以看到下面的数据。
首先我们来解读下图中的信息:
- O : 文件名称
- V : 顶点位置信息
- Vt: 顶点uv信息
- Vn:平面法线信息
- f:平面的顶点构成 对于一些稍微有美术经验的程序员来说,当他打开obj格式文件,模型的大致信息他就一目了然,哪些数据属于顶点位置信息,哪些数据属于顶点的uv信息...可是对于GPU来说,它得到的数据是由一串连续数字组成的长字符,这串字符包含了一个模型的所有数据,它们的杂糅的组织在一起,并且晦涩难懂。
-1.000000 0.000000 1.000000 0.000000 0.065193 1.000000 0.751341 1.000000 1.000000 0.000000.......
理解了上述概念后,便可以知道OpenGL一系列方法和一组对象具体的功能和作用了,这里先给出一张本人的概念理解图便于大家总体上的理解
- VBO相当于上述杂乱无章的字符串
- VAO就相当于GPU能看懂的obj格式,
- EBO就相当于obj格式中的f规定哪些点规定一个三角形,可以避免顶点的重复设置(比如一个正方形若不引用EBO,则需要六个顶点构成两个三角形,若引入EBO则只需要四个顶点就可以构成两三角形。)
顶点缓冲对象(Vertex Buffer Objects,VBO)
顶点缓冲对象VBO是在显卡存储空间中开辟出的一块内存缓存区,用于存储顶点的各类属性信息,如顶点坐标,顶点法向量,顶点颜色数据等。在渲染时,可以直接从VBO中取出顶点的各类属性数据,由于VBO在显存而不是在内存中,不需要从CPU传输数据,处理效率更高。
VBO的创建与配置
- 创建VBO的第一步需要开辟(声明/获得)显存空间并分配VBO的ID:
- 把创建的VBO缓冲对象绑定到对应的缓冲类型上。
- 将Cpu中的顶点数据传送到该缓冲类型上。 代码如下所示:
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
顶点数组对象(Vertex Arrary Object,VAO)
VBO保存了一个模型的顶点属性信息,每次绘制模型之前需要绑定顶点的所有信息,当数据量很大时,重复这样的动作变得非常麻烦。VAO可以把这些所有的配置都存储在一个对象中,每次绘制模型时,只需要绑定这个VAO对象就可以了。
VAO是一个保存了所有顶点数据属性的状态结合,它对应上一个VBO和一个EBO(如果存在)。VAO中的数据才是顶点着色器中获取数据输入的地方
VAO的创建与配置
- 创建VAO的第一步需要开辟(声明/获得)显存空间并分配VAO的ID:
- 绑定VAO
- 从VBO中获取对应数据到VAO对应的attribute point上面
- 启用对应的attribute point 代码如下所示:
unsigned int VAO;
glGenVertexArrays(1, &VAO);
// ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: ..
// 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(float), (void*)0); glEnableVertexAttribArray(0);
[...]
索引缓冲对象(Element Buffer Object,EBO)
索引缓冲对象EBO相当于OpenGL中的顶点数组的概念,是为了解决同一个顶点多洗重复调用的问题,可以减少内存空间浪费,提高执行效率。当需要使用重复的顶点时,通过顶点的位置索引来调用顶点,而不是对重复的顶点信息重复记录,重复调用。
EBO的创建与配置
EBO的创建类似于VBO
- 创建EBO的第一步需要开辟(声明/获得)显存空间并分配EBO的ID:
- 把创建的EBO缓冲对象绑定到对应的缓冲类型上。
- 将Cpu中的顶点索引数据传送到该缓冲类型上。 代码如下所示:
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
当用EBO绑定顶点索引的方式绘制模型时,需要使用glDrawElements而不是glDrawArrays:
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
重点函数详解
1. glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
- 参数1:填充的缓冲对象类型,里面已经绑定了一个对应的缓冲对象
- 参数2:填充的数据大小
- 参数3:填充的数据来源
- 参数4;显卡如何管理我们的数据
-
GL_STATIC_DRAW:数据不会或几乎不会改变。
-
GL_DYNAMIC_DRAW:数据会被改变很多。
-
GL_STREAM_DRAW:数据每次绘制时都会改变。
2. glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void)0);*
- 参数1:指定我们要配置的顶点属性,与顶点着色器中layout(location=0)对应。
- 参数2:指定顶点属性的大小。顶点属性是一个
vec3,它由3个值组成,所以大小是3。 - 参数3:指定数据的类型,这里是
GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。 - 参数4:是否希望数据被标准化(Normalize)。如果我们设置为
GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。 - 参数5:指定连续的顶点属性组之间的间隔。它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个
float之后,我们把步长设置为3 * sizeof(float)。 - 参数6:指定我们的位置数据在缓冲区起始位置的偏移量。