OpenGLES2.0 编程中,用于绘制的顶点数组数据首先保存在 CPU 内存,在调用 glDrawArrays 或者 glDrawElements 等进行绘制时,需要将顶点数组数据从 CPU 内存拷贝到显存。
但是很多时候我们没必要每次绘制的时候都去进行内存拷贝,如果可以在显存中缓存这些数据,就可以在很大程度上降低内存拷贝带来的开销。
OpenGLES3.0 VBO 和 EBO 的出现就是为了解决这个问题。 VBO 和 EBO 的作用是在显存中提前开辟好一块内存,用于缓存顶点数据或者图元索引数据,从而避免每次绘制时的 CPU 与 GPU 之间的内存拷贝,可以改进渲染性能,降低内存带宽和功耗。
OpenGLES3.0 支持两类缓冲区对象:顶点数组缓冲区对象、图元索引缓冲区对象。GL_ARRAY_BUFFER 标志指定的缓冲区对象用于保存顶点数组,GL_ELEMENT_ARRAY_BUFFER 标志指定的缓存区对象用于保存图元索引。
缓存的作用是:尽量减少cpu和gpu之间的传输成本
1、VBO
VBO,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。
VBO使用方式大致如下:
GLfloat vertices[] = {
0.5f, 0.5f, 0.0f, // 右上角
1.0, 0.0, 0.0,//右上角颜色
0.5f, -0.5f, 0.0f, // 右下角
0.0, 0.0, 1.0,//右下角颜色
-0.5f, -0.5f, 0.0f, // 左下角
0.0, 1.0, 0.0,//左下角颜色
-0.5f, 0.5f, 0.0f, // 左上角
0.5, 0.5, 0.5,//左上角颜色
};
glGenBuffers(2, mVboIds);
glBindBuffer(GL_ARRAY_BUFFER, mVboIds[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (const void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6* sizeof(float), (const void*)(3 * sizeof(float)));
首先,生成buffer,再绑定buffer,再绑定buffer data,即是将真实的顶点数据绑定到vbo中,然后是解析顶点数据。典型的状态机写法
2、EBO
EBO是一个缓冲区,就像一个顶点缓冲区对象一样,它存储 OpenGL 用来决定要绘制哪些顶点的索引,它的使用方式如下:
// Index buffer data
unsigned int indices[] = {
// 注意索引从0开始!
// 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
// 这样可以由下标代表顶点组合成矩形
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
glGenBuffers(2, mVboIds);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVboIds[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//绘制
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (const void*)0);
glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。它的第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。第三个参数是我们希望发送的实际数据。
第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
GL_STATIC_DRAW:数据不会或几乎不会改变。GL_DYNAMIC_DRAW:数据会被改变很多。GL_STREAM_DRAW:数据每次绘制时都会改变。
注意上面代码中indices数组的元素类型。使用EBO之后,我们可以使用 glDrawElements 绘制。还记得之前我们是使用 glDrawArrays 绘制三角形。
glDrawElements方法参数的官方注释
-
mode -
Specifies what kind of primitives to render. Symbolic constants
GL_POINTS,GL_LINE_STRIP,GL_LINE_LOOP,GL_LINES,GL_TRIANGLE_STRIP,GL_TRIANGLE_FANandGL_TRIANGLESare accepted. -
count -
Specifies the number of elements to be rendered.
-
type -
Specifies the type of the values in
indices. Must be one ofGL_UNSIGNED_BYTE,GL_UNSIGNED_SHORT, orGL_UNSIGNED_INT. -
indices -
Specifies a byte offset (cast to a pointer type) into the buffer bound to
GL_ELEMENT_ARRAY_BUFFERto start reading indices from. If no buffer is bound, specifies a pointer to the location where the indices are stored.
注意,第三个参数type是指indices数组的元素类型,如果这个类型没写对,图形将无法绘制
glDrawElements的功能就是根据 indices 数组中索引绘制图形,所以它的参数类型一定要和索引数组元素类型一致。
3、VAO
顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。
VAO 的主要作用是用于管理 VBO 或 EBO ,减少 glBindBuffer 、glEnableVertexAttribArray、 glVertexAttribPointer 这些调用操作,高效地实现在顶点数组配置之间切换。
VAO主要用法:
glGenVertexArrays(1, &mVaoId);
glBindVertexArray(mVaoId);
//操作VBO、VEO
glBindVertexArray(GL_NONE);
在绑定VAO,解绑VAO之间操作VBO、VEO,相当于把这一组 VBO、VEO 绑定到了当前的 VAO 上,后面要绘制的时候就特别简单了,直接绑定 VAO 就可以了,然后就直接绘制了,不用再处理顶点数据了
glUseProgram(m_ProgramObj);
glBindVertexArray(mVaoId);
//注意,glDrawElements函数的第三个参数,本例中为GL_UNSIGNED_INT,必须要与indices数组的类型相同,如果不同,绘制会失败
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (const void*)0);
glBindVertexArray(GL_NONE);
上面绘制的代码是不是特别清爽了
上述所有代码,可以去本人github中取