目录
一、缓冲区对象概念
二、分类
三、顶点缓冲区对象VBO
1、概念
2、为什么使用VBO
3、如何使用VBO
生成缓冲区对象
绑定缓冲区对象
输入缓冲区数据
更新缓冲区中的数据
删除缓冲区
4、VBO应用
四、顶点数组对象VAO
1、概念
2、为什么使用VAO
3、如何使用VAO
生成VAO
绑定VAO
解绑VAO
删除VAO
4、VAO应用
五、索引缓冲区对象EBO/IBO
1、概念
2、为什么使用IBO/EBO
3、如何使用EBO
生成EBO
绑定EBO
输入数据到缓冲区
链接属性
绘制
4、EBO应用
5、绘制过程
六、总结
1、常用各类缓冲区分析
2、无法删除缓冲区
七、源码下载
一、缓冲区对象概念
缓冲区对象(Buffer Object)是OpenGL中用于在GPU内存中存储数据的一种机制,它们使得数据传输和渲染更加高效。
二、分类
常用的缓冲区分类如下:
VAO(Vertex Array Object): 顶点数组对象,用于存储顶点数据
VBO(Vertex Buffer Object): 顶点缓冲对象,用于存储顶点数据
EBO(Element Buffer Object): 元素缓冲对象,用于存储顶点的索引数据
IBO(Index Buffer Object): 索引缓冲对象,用于存储索引数据
FBO(Index Buffer Object):帧缓冲对象,用于渲染到纹理或离屏渲染。
PBO(Pixel Buffer Object):像素缓冲区对象
GL_ARRAY_BUFFER
标志指定的数组缓冲区对象用于创建保存顶点数据的缓冲区对象。GL_ELEMENT_ARRAY_BUFFER
标志指定的元素数组缓冲区对象用于创建保存图元索引的缓冲区对象。
应用程序对顶点属性数据和元素索引使用顶点缓冲区对象。
三、顶点缓冲区对象VBO
1、概念
VBO(Vertex Buffer Object)顶点缓冲对象,是在显卡存储空间(即,GPU内存)中开辟的一块区域,在显卡存储空间中开辟一块区域,用于存放顶点的各类属性信息。如顶点坐标、纹理坐标、顶点颜色等数据。
在渲染时直接从显VBO去取数据而不必与CPU进行数据交换。
2、为什么使用VBO
什么是顶点缓冲区对象,为什么要使用它们
解释1:
将对象数据存储在客户端内存中,只有在渲染时将其传输到GPU内存(即,显存)中。没有大量数据传输时,这很好,但随着我们的场景越来越复杂,会有更多的物体和三角形,这会给CPU和内存增加额外的成本。
我们能做些什么呢?我们可以使用顶点缓冲对象,而不是每帧从客户端内存传输顶点信息,信息将被传输一次,然后渲染器将从该图形存储器缓存中得到数据。
解释2:
直接只用顶点数组来绘图,在客户端制定顶点数据,每次绘制时从先从内存中加载这些数据,这样会带来绘制延时,因此我们想着在显存中开辟一块区域,在每次绘制间隔就将顶点数据加载过来,这样绘制时直接读取显存中的数据就可以了,明显提高渲染速度。于是,我们需要引入顶点缓冲区。
一般在两种不同处理速度的物理组件之间进行数据传输时都要用到缓冲区,OpenGL由于需要处理内存与GPU的数据传输,也要用到一系列缓冲区对象,顶点缓冲区只是其中之一。在顶点数组的基础上使用定点缓冲区可以提高渲染速率。
解释3:
VBO允许开发者将大量顶点数据从CPU传输到GPU,减少每帧的CPU-GPU通信量,提高渲染效率。缓冲区对象通过在 GPU 上存储数据,提高了数据传输和渲染的效率。
使用顶点数组指定的顶点数据保存在客户内存中。在进行 glDrawArrays或者
glLDrawElements 等绘图调用时,这些数据必须从客户内存复制到图形内存。但是,如果我们没有必要在每次绘图调用时都复制顶点数据,而是在图形内存中缓存这些数据,那就好得多了。这种方法可以显著地改进渲染性能,也会降低内存带宽和电力消耗需求,对于手持设备相当重要。这是顶点缓冲区对象发挥作用的地方。
顶点组冲区对象使应用程序可以在高性能的图形内存中分配和缓存顶点数据,并从这个内存进行泻染,从而避免在每次绘制图元的时候重新发送数据。不仅是项点数据,描述图元顶点索引、作为 glDrawElements 参数传递的元素索引也可以缓存。
解释4:
通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。
从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。
解释5:
为什么需要使用VBO
将顶点数据保存在内存中,在调用glDrawArrays或者glDrawElements等绘制方法前需要调用相应的方法将数据送入显存,I/O开销大,性能不够好。
若采用顶点缓冲区对象存放顶点数据,则不需要在每次绘制前都将顶点数据复制进显存,而是在初始化顶点缓冲区对象时一次性将顶点数据送入显存,
每次绘制时直接使用显存中的数据,可以大大提高渲染性能。
3、如何使用VBO
顶点缓冲区的使用需要经历一下步骤:
生成缓冲区对象
glGenBuffers(GLsizei n, GLuint* buffers);
分配n个缓冲区对象标识符,返回的标识符存储在buffers数组中。每一个标识符表示一个已经开始使用的缓冲区对象。具体如何分配有OpenGL内部决定,与调用者无关。此外,该方法调用之后在buffers中存储的标识符并不一定是连续的数字,而且0作为一个被保留的缓冲区对象名称,该方法从来不会返回0标识符。
绑定缓冲区对象
glBindBuffer(GLenum target, GLuint buffer);
绑定缓冲区对象。OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。buffer是glGenBuffers()返回的标识符数组的一个值,target表示改缓冲区应该绑定为什么类型的缓冲区对象,有GL_ARRAY_BUFFER(顶点数组缓冲区)、GL_ELEMENT_ARRAY_BUFFER(索引数组缓冲区)等。需要注意的是(以GL_ARRAY_BUFFER为例):
- 如果buffer为0,则停用顶点数组缓冲区;
- 如果buffer为非零整数,且buffer代表的缓冲区之前未绑定过,则创建一个新的缓冲区对象和buffer相对应;
- 如果buffer之前绑定过,则激活buffer对应的缓冲区。
OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上。
输入缓冲区数据
glBufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
初始化缓冲区对象,并指定缓冲区数据。target和glBindBuffer()中的target一样,data表示要写入到缓冲区的数据数组头指针,size表示data数组的字节数,usage表示数据在分配到缓冲区之后如何进行读取和写入,主要用来提供性能,指定了我们希望显卡如何管理给定的数据。其值为“GL_频率_操作”格式,有
GL_STREAM_DRAW、
GL_STREAM_READ、
GL_STREAM_COPY、
GL_STATIC_*、
GL_DYNAMIC_*。
其中,“频率”指缓冲区数据的读取或者渲染速率,有流模式、静态模式,动态模式:
- STREAM:流模式,当缓冲区中的数据更新频率高,但使用频率低时使用;
- STATIC:静态模式,当缓冲区中的数据只需制定一次,但使用频率比较高时使用;
- DYNAMIC:动态模式,当缓冲区中的数据更新频率高,并且使用频率高时使用。
“操作”有绘制 、读取和拷贝:
- DRAW:绘制,缓冲区中的数据直接用于渲染,也就是内存中的数据直接作为渲染数据;
- READ:读取,缓冲区中的数据主要用于应用程序的计算,而不是直接作用于渲染;
- COPY:拷贝,缓冲区中的数据对渲染来说是只读数据,需要拷贝后将拷贝数据作为渲染数据。
例如,如下三种形式的含义:
- GL_STATIC_DRAW 数据不会或几乎不会改变。仅在显存上进行读取,不进行写入,适用于静态数据。
- GL_DYNAMIC_DRAW数据会被改变很多。仅在显存上进行读取,并允许写入,数据经常改变。
- GL_STREAM_DRAW 数据每次绘制时都会改变。仅在显存上进行读取,不进行写入,数据每次绘制都改变。
一般情况下,位置数据不会改变,每次渲染调用时都保持原样,所以它的使用类型最好是GL_STATIC_DRAW。如果,比如说一个缓冲中的数据将频繁被改变,那么使用的类型就是GL_DYNAMIC_DRAW或GL_STREAM_DRAW,这样就能确保显卡把数据放在能够高速写入的内存部分。现在我们已经把顶点数据储存在显卡的内存中,用VBO这个顶点缓冲对象管理。
更新缓冲区中的数据
glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
更新缓冲区中的数据。target和之前两个方法的参数一样,data指写入缓冲区的数据所在数组头指针,size指data数组中要写入缓冲区的数据的字节数,offset指要写入缓冲区的第一个数据在data数组的位置。也即将data数组中从offset(字节为单位)开始的size个字节写入缓冲区。
删除缓冲区
glDeleteBuffers(GLsizei n, const GLuint* buffers);
删除buffers中前n个缓冲区对象,他们的名称就是buffers数组中的元素。
4、VBO应用
创建VBO四步:
val buffers = IntArray(1)
//1.生成一个VBO,并ID存储在VBO中,此时VBO拥有了其唯一的id
GLES20.glGenBuffers(buffers.size, buffers, 0)
if (buffers[0] == 0) {
Log.d(TAG, "can not create a new vertex buffer object.")
}
bufferId = buffers[0]
//2.绑定缓冲区
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[0])
val vertexArray = ByteBuffer
.allocateDirect(indexData.size * BYTES_PER_SHORT)
.order(ByteOrder.nativeOrder())
.asShortBuffer()
.put(indexData)
vertexArray.position(0)
//3.将顶点数据复制到缓冲中,并指定数据用以静态访问,
将顶点数据上传到GPU内存中,以便后续的渲染操作。
GLES20.glBufferData(
GLES20.GL_ARRAY_BUFFER,
vertexArray.capacity() * BYTES_PER_SHORT,
vertexArray,
GLES20.GL_STATIC_DRAW
)
//4.将buffer id设置为0,解绑缓冲区
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0)
四、顶点数组对象VAO
1、概念
VAO(vertex Array Object):顶点数组对象。
注意: VAO是OpenGL ES 3.0之后才推出的新特性, 所以在使用VAO前需要确定OpenGL ES的版本是否是3.0之后的版本。
顶点数组对象可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中。
VAO用于保存顶点属性数组的状态,包括与VBO的绑定以及顶点属性的配置。
2、为什么使用VAO
解释1:
顶点属性存储有2种方式:客户端顶点数组和顶点缓冲区对象。VAO使得客户端顶点数组的数据传入VBO的速度加快。
通过VAO,开发者可以一次性配置好顶点数组的所有状态,在后续的绘制中快速恢复这些状态,减少OpenGL函数调用次数,提升效率。
解释2:
在上面VBO的介绍中我们知道每次在绘制的时候都需要频繁地绑定与解绑VBO,每次绘制还需要取出VBO中的数据进行赋值之后才能进行绘制渲染。当数据量大的时候,重复这些操作就会变得很繁琐。通过VAO就可以简化这个过程,因此VAO可以简单理解成VBO的管理者,避免在帧绘制时再去手动操纵VBO,VAO不能单独使用,
需要搭配VBO使用。
对于GPU来说VBO就是一堆数据,但是这堆数据怎么解析使用,需要glEnableVertexAttribArray
等相关函数在每次绘制的时候告诉GPU,那么VAO的作用就是简化这个过程的,只需要在初始化的时候将这些解析逻辑与VAO绑定一次即可,
然后每次在绘制的时候只需绑定对应的VAO,而不必每次再绑定VBO,然后去告诉GPU如何解析相关数据了,可以说是一个性能的优化了。
解释3:
当我们再绘制不同的顶点数据或者是其他数据时,我们需要重新进行一遍绑定设置等等操作来完成绘制不同,这时候就引出顶点数组对象。
在OpenGL中,顶点数组对象(Vertex Array Object,VAO)是一种OpenGL对象,用于存储顶点数据的状态信息,包括顶点坐标、法线、纹理坐标等。VAO可以看作是一种包含了多个顶点属性配置的容器,使得你可以在绘制时轻松地切换和使用不同的顶点数据。
顶点数组对象的使用可以帮助你更有效地组织和管理顶点数据,特别是在绘制多个物体或者在渲染循环中多次切换不同的顶点数据时。
其实可能还不太明白,其实使用的时候确实都是要走绑定顶点缓冲区,然后设置顶点的布局等等,但是我们现在把上面一套流程放进了一个叫VAO的对象中,也就是生成绑定它就相当于走完了内部的一套流程,在换顶点数据或者顶点布局的时候,就不需要再写一遍这么麻烦了,相当于是封装了一层,我们后续可以看看其实也是真的要封装一层的,我们可以通过在换顶点布局或者顶点数据的时候体现流程:
老一套的流程是:
1.解绑并重新绑定新的着色器程序 | glUseProgram(0); glUseProgram(shaderID); |
2.解绑和重新绑定顶点缓冲区 | glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, buffer)); |
3.重新设置顶点的布局 | glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0); |
4.绑定我们的索引缓冲区 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)); |
5.绘制调用 | glDrawElements |
引入VAO之后,我们要更改数据就变成了这样:
1.解绑并重新绑定新的着色器程序 | glUseProgram(0); glUseProgram(shaderID); |
2.绑定顶点数组(包含了绑定顶点缓冲区,设置顶点的布局,方便直接绑定别的,可以切换) | glBindVertexArray(vao); |
3.绑定索引缓冲 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); |
4.绘制调用 | glDrawElements |
另外这个顶点数组对象对于OpenGL是强制性的,因为即使我们没有创建它,还是走的老一套流程,其实状态还是由顶点数组对象来维护的。
3、如何使用VAO
生成VAO
unsigned int VAO;
glGenVertexArrays(1, &VAO);
绑定VAO
glBindVertexArray(VAO)
解绑VAO
glBindVertexArray(0)
删除VAO
glDeleteVertexArrays
4、VAO应用
// 生成VAO和VBO
GLuint vao;
glGenVertexArrays(1, &vao);
GLuint vbo;
glGenBuffers(1, &vbo);
// 绑定VAO
glBindVertexArray(vao);
// 绑定VBO并设置顶点数据
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 配置顶点属性,用于将当前的顶点属性与顶点缓冲对象(VBO)关联起来
//配置顶点属性指针: 使用 glVertexAttribPointer 函数配置顶点属性指针,告诉OpenGL如何解释顶点数据。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 解绑VAO和VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
五、索引缓冲区对象EBO/IBO
1、概念
IBO/EBO, Element Buffer Objec,索引缓冲区对象。EBO用于存储顶点的索引数据,允许通过索引引用顶点,避免重复存储相同的顶点数据。
索引缓冲区和顶点缓冲区类似,也是显存上的一段内存,只不过上面的数据用处不同,索引缓冲区故名思义里面的数据是用于索引,主要作用是用于复用顶点缓冲区里的数据。
使用的GL_ELEMENT_ARRAY_BUFFER表示索引缓冲区。
2、为什么使用IBO/EBO
解释1:
EBO提高了渲染效率,减少了顶点数据的冗余,尤其在渲染复杂网格时,通过索引复用顶点能够显著节省内存。
解释2:
OpenGL渲染都是渲染三角形,如果我们想渲染一个正方形,就要通过渲染两个三角形,拼接成一个正方形,那么这两个三角形有两个顶点是重合的,如果没有索引缓冲区,两个三角形则需要六个顶点,而实际上一个正方形只有四个顶点,这里有两个顶点时数据冗余。仅仅一个正方形就有这么多冗余,那么一个复杂的游戏场景就会浪费非常多的内存。
解释3:
为什么使用EBO
正常画一个立方体,需要8个顶点,合计6个面。
但实际我们是用了4*6=24个点来画的,因为4个点组成一个面,我们能看出来,有很多重复点,为了能节省内存空间,我们想只用8个点描画立方体,这需要用到EBO
VertexData vc[] = {
//正面
{QVector3D(-0.5f, 0.0f, 0.5f), QVector3D(1.0f, 0.0f, 0.0f)}, //1
{QVector3D(0.5f, 0.0f, 0.5f), QVector3D(0.0f, 1.0f, 0.0f)}, //2
{QVector3D(0.5f, 1.0f, 0.5f), QVector3D(0.0f, 0.0f, 1.0f)}, //3
{QVector3D(-0.5f, 1.0f, 0.5f), QVector3D(1.0f, 1.0f, 1.0f)}, //4
//右面
{QVector3D(0.5f, 0.0f, 0.5f), QVector3D(0.0f, 1.0f, 0.0f)}, //2
{QVector3D(0.5f, 0.0f, -0.5f), QVector3D(1.0f, 1.0f, 0.0f)}, //5
{QVector3D(0.5f, 1.0f, -0.5f), QVector3D(0.0f, 1.0f, 1.0f)}, //6
{QVector3D(0.5f, 1.0f, 0.5f), QVector3D(0.0f, 0.0f, 1.0f)}, //3
//左面
{QVector3D(-0.5f, 0.0f, 0.5f), QVector3D(1.0f, 0.0f, 0.0f)}, //1
{QVector3D(-0.5f, 0.0f, -0.5f),QVector3D(1.0f, 0.0f, 1.0f)}, //8
{QVector3D(-0.5f, 1.0f, -0.5f),QVector3D(1.0f, 0.6f, 0.0f)}, //7
{QVector3D(-0.5f, 1.0f, 0.5f), QVector3D(1.0f, 1.0f, 1.0f)}, //4
//背面
{QVector3D(0.5f, 0.0f, -0.5f), QVector3D(1.0f, 1.0f, 0.0f)}, //5
{QVector3D(0.5f, 1.0f, -0.5f), QVector3D(0.0f, 1.0f, 1.0f)}, //6
{QVector3D(-0.5f, 1.0f, -0.5f), QVector3D(1.0f, 0.6f, 0.0f)}, //7
{QVector3D(-0.5f, 0.0f, -0.5f), QVector3D(1.0f, 0.0f, 1.0f)}, //8
//顶面
{QVector3D(0.5f, 1.0f, 0.5f), QVector3D(0.0f, 0.0f, 1.0f)}, //3
{QVector3D(0.5f, 1.0f, -0.5f), QVector3D(0.0f, 1.0f, 1.0f)}, //6
{QVector3D(-0.5f, 1.0f, -0.5f), QVector3D(1.0f, 0.6f, 0.0f)}, //7
{QVector3D(-0.5f, 1.0f, 0.5f), QVector3D(1.0f, 1.0f, 1.0f)}, //4
//底面
{QVector3D(0.5f, 0.0f, 0.5f), QVector3D(0.0f, 1.0f, 0.0f)}, //2
{QVector3D(0.5f, 0.0f, -0.5f), QVector3D(1.0f, 1.0f, 0.0f)}, //5
{QVector3D(-0.5f, 0.0f, -0.5f), QVector3D(1.0f, 0.0f, 1.0f)}, //8
{QVector3D(-0.5f, 0.0f, 0.5f), QVector3D(1.0f, 0.0f, 0.0f)}, //1
};
EBO原理
//顶点有8个
VertexData vcs[] = {
//正面
{QVector3D(-0.5f, 0.0f, 0.5f), QVector3D(1.0f, 0.0f, 0.0f)}, //1
{QVector3D(0.5f, 0.0f, 0.5f), QVector3D(0.0f, 1.0f, 0.0f)}, //2
{QVector3D(0.5f, 1.0f, 0.5f), QVector3D(0.0f, 0.0f, 1.0f)}, //3
{QVector3D(-0.5f, 1.0f, 0.5f), QVector3D(1.0f, 1.0f, 1.0f)}, //4
{QVector3D(0.5f, 0.0f, -0.5f), QVector3D(1.0f, 1.0f, 0.0f)}, //5
{QVector3D(0.5f, 1.0f, -0.5f), QVector3D(0.0f, 1.0f, 1.0f)}, //6
{QVector3D(-0.5f, 1.0f, -0.5f),QVector3D(1.0f, 0.6f, 0.0f)}, //7
{QVector3D(-0.5f, 0.0f, -0.5f),QVector3D(1.0f, 0.0f, 1.0f)}, //8
};
//索引 对应8个面,每个面说明使用哪4个顶点
GLuint indices[] = { // 起始于0!
0, 1, 2, 3, // face 1
1, 4, 5, 2, // face 2
0, 7, 6, 3, // face 3
4, 5, 6, 7, // face 4
2, 5, 6, 3, // face 5
1, 4, 7, 0, // face 6
};
3、如何使用EBO
生成EBO
glGenBuffers(1, &EBO);
绑定EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
输入数据到缓冲区
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
链接属性
glVertexAttribPointer(m_posAttr, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (GLvoid*)0);
glVertexAttribPointer(m_colAttr, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (GLvoid*)sizeof(QVector3D));
绘制
/*
glDrawElements是用于渲染索引缓冲区,
第一个和其他DrawCall一样,
第二个参数是count,表示有多少顶点需要渲染,
第三个参数是索引缓冲区参数类型,必须是GLES30.GL_UNSIGNED_SHORT或者GL_UNSIGNED_BYTE,
第四个参数是offset。
*/
GLES30.glDrawElements(GLES30.GL_TRIANGLES, count, type, offset)
PS: 绑定和解绑的顺序很重要,勿更改,另外EBO不用解绑,就算要解绑,也要在VAO解绑后。
4、EBO应用
//1 使用glGenBuffers函数生成一个缓冲ID
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
//2 绑定vao
glBindVertexArray(VAO);
//3 使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER缓冲类型上
glBindBuffer(GL_ARRAY_BUFFER, VBO); //(绑定和解绑的顺序很重要,勿更改)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
//4 把用户定的义数据复制到当前绑定缓冲的函数
glBufferData(GL_ARRAY_BUFFER, sizeof(vcs), vcs, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//5 链接顶点属性
//indx: 属性名
//size: 顶点大小
//type: 数据类型
//normalized:数据被标准化
//stride: 步长
//ptr: 数据在缓冲中起始位置的偏移量
glVertexAttribPointer(m_posAttr, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (GLvoid*)0);
glVertexAttribPointer(m_colAttr, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (GLvoid*)sizeof(QVector3D));
//6 解绑缓存(绑定和解绑的顺序很重要,勿更改)
glBindBuffer(GL_ARRAY_BUFFER, 0);
//7 解绑VAO
glBindVertexArray(0);
5、绘制过程
//1 绑定vao
glBindVertexArray(VAO);
//2 开启顶点属性
glEnableVertexAttribArray(0);
//颜色值
glEnableVertexAttribArray(1);
//3 绘制四边形,24个索引值
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_INT, (GLvoid*)0);
//4 停用对应的顶点属性数组
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(0);
//5 解绑VAO
glBindVertexArray(0);
六、总结
1、常用各类缓冲区分析
VBO: 存储大量顶点,因而可以利用VBO一次性发送大量数据到显卡
VAO: 配置并告诉了OpenGL如何使用VBO,以及使用哪个VBO
EBO/IBO:
索引缓冲区里面就是存储了一系列索引,用于复用顶点缓冲区。
有个问题,大家可能会想之前用6个顶点也不过18个数,使用了顶点缓冲区后变为12个,但是索引缓冲区还有6个,好像没有节省多少内存。
但是实际上顶点可能包含了非常多的数据,比如我们之前用它来存颜色,它还可以存纹理等额外数据,实际应用场景一个顶点可能有非常多数据,所以复用可节省的内存是非常可观的。
2、无法删除缓冲区
OpenGL并没有提供直接删除缓冲区的函数或方法。这是因为OpenGL的设计理念是尽可能地提供高性能和灵活性,而不是提供高级别的内存管理功能。因此,开发者需要手动管理缓冲区的生命周期。
要删除缓冲区,开发者需要按照以下步骤进行操作:
-
解绑缓冲区:在删除缓冲区之前,需要先解绑缓冲区。可以使用
glBindBuffer
函数将当前的缓冲区绑定到一个无效的缓冲区对象上,例如glBindBuffer(GL_ARRAY_BUFFER, 0)
。 -
删除缓冲区对象:使用
glDeleteBuffers
函数来删除缓冲区对象。该函数接受一个整数参数,表示要删除的缓冲区对象的数量,以及一个指向缓冲区对象的指针。例如,glDeleteBuffers(1, &bufferObject)
将删除名为bufferObject
的缓冲区对象。
需要注意的是,删除缓冲区对象并不会自动释放缓冲区所占用的内存。开发者需要在删除缓冲区对象之前,确保已经释放了缓冲区所占用的内存,以避免内存泄漏。
七、源码下载
VAO顶点数组对象应用演示demo源码下载:
https://download.****.net/download/github_27263697/89915626
VBO顶点缓冲区对象应用演示demo源码下载:
https://download.****.net/download/github_27263697/89915617
EBO/IBO索引缓冲区对象应用演示demo源码下载:
https://download.****.net/download/github_27263697/89916753
参考文章
OpenGL入门(四)顶点缓冲区_顶点缓冲器有什么用-****博客
Android OpenGL ES 2.0(七)--- 顶点缓冲区对象-****博客
OpenGL 入门(二)— 顶点数组对象(VAO)和顶点缓冲对象(VBO)_glvertexattribpointer-****博客
你好,三角形 - LearnOpenGL CN
https://segmentfault.com/a/1190000042610598?sort=newest#item-1
OpenGL学习总结-数据缓存区(四)_opengl genbuffer-****博客
一篇搞懂OpenGL uniforms变量和顶点数组对象(Vertex Array Object,VAO)_glgenvertexarrays-****博客OpenGL无法删除缓冲区_OpenGL/OpenTK -多帧缓冲区_使用OpenGL内插数据缓冲区? 小白学opengl 第五课 之 索引缓冲对象 - 踏月清风 - 博客园OpenGL无法删除缓冲区_OpenGL/OpenTK -多帧缓冲区_使用OpenGL内插数据缓冲区? 一篇搞懂OpenGL uniforms变量和顶点数组对象(Vertex Array Object,VAO)_glgenvertexarrays-****博客
OpenGL ES 索引缓冲区(4)_opengl索引缓冲区-****博客