目录
- VBO(Vertex Buffer Object)
- EBO(Element Buffer Object)
- VAO(Vertex Array Object)
VBO(Vertex Buffer Object)
EBO(Element Buffer Object)
VBO(Vertex Buffer Object)
实际是指顶点缓冲器对象
在 opengl-es 2.0
的编程中,用于绘制图元的顶点数据是从 CPU 传递到显存中的,具体的方式通过 glVertexAttribPointer
设定顶点或者顶点颜色的值到具体的 index中,OES 在shader 程序中根据 index 获取对应的值,编程方法如下面的示例所示:
static GLfloat vertices[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
static GLfloat verticesColor[] = {
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
};
....
{
// 指定对应的 vertices 到 index 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, verticesColor);
glEnableVertexAttribArray(1);
// Draw the triangle
glDrawArrays(GL_TRIANGLES, 0, 3);
}
但是如果每次都要绘制相同的图元,就可以把 顶点的值缓存到GPU内存中,不必每次都执行设定加拷贝的过程,这里就可以使用 VBO
不仅顶点的坐标可以缓存到GPU显存中,不同的顶点构成的基本图元索引也可以进行缓存,这就是 EBO(Element buffer Obejct)
VAO 和 EBO 的作用是在显存中提前开辟好一块内存,用于缓存顶点数据或者图元索引数据,从而避免绘制时的 CPU 和 GPU之间的内存拷贝,可以改进渲染性能,降低内存带宽和功耗
GL_ARRAY_BUFFER
标志指定的缓冲区对象用于保存顶点数组
GL_ELEMENT_ARRAY_BUFFER
标志指定的缓存区对象用于保存图元索引
单独使用 VBO
缓存进行绘制的代码如下(实际也使用了VAO
):
static void DrawPrimitiveWithVBOs(ESContext *esContext)
{
UserData *userData = esContext->userData;
GLuint offset = 0;
// vboIds[0] - used to store vertex attribute data
// vboIds[l] - used to store element indices
if (userData->vboIds[0] == 0 && userData->vboIds[1] == 0) {
// Only allocate on the first draw
glGenBuffers(2, userData->vboIds);
glGenVertexArrays(1, &userData->vaoId);
glBindVertexArray(userData->vaoId);
// 用于缓存顶点的数据
glBindBuffer(GL_ARRAY_BUFFER, userData->vboIds[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3,GL_FLOAT, GL_FALSE, 3*sizeof(GLfloat) , NULL);
glEnableVertexAttribArray(0);
// notice using GL_ARRAY_BUFFER we not using EBO there
// 用于缓存顶点颜色数据
// 顶点和顶点颜色都是使用 VBO 进行缓存
glBindBuffer(GL_ARRAY_BUFFER, userData->vboIds[1]);
glBufferData(GL_ARRAY_BUFFER,sizeof(verticesColor), verticesColor, GL_STATIC_DRAW);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3*sizeof(GLfloat) ,NULL);
glEnableVertexAttribArray(1);
glBindVertexArray(0);
}
glBindVertexArray(userData->vaoId);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
}
注意上面使用了 VBO
同时缓存了顶点和顶点颜色数据,但是没有使用 EBO
,但是也使用了VAO
,根据chatgpt
的回答,无法只使用 VBO
不结合 VAO
,但是可以不是 VAO
注意 VBO 的使用方法需要先 glBindBuffer
最后 glEnableVertexAttribArray
同时使用 VBO
和 EBO
进行绘制的代码如下:
unsigned int indices[] = {
// 前面
0, 1, 2,
2, 3, 0,
// 后面
4, 5, 6,
6, 7, 4,
// 左面
8, 9, 10,
10, 11, 8,
// 右面
12, 13, 14,
14, 15, 12,
// 顶面
16, 17, 18,
18, 19, 16,
// 底面
20, 21, 22,
22, 23, 20
};
static void DrawPrimitiveWithVBOs(ESContext *esContext)
{
UserData *userData = esContext->userData;
GLuint offset = 0;
// vboIds[0] - used to store vertex attribute data
// vboIds[l] - used to store element indices
if (userData->vboIds[0] == 0 && userData->vboIds[1] == 0) {
// Only allocate on the first draw
glGenBuffers(2, userData->vboIds);
glGenVertexArrays(1, &userData->vaoId);
printf("gen vbo id:%d %d vao id:%d.\n",userData->vboIds[0],userData->vboIds[1],userData->vaoId);
glBindVertexArray(userData->vaoId);
// 使用 VBO 缓存 顶点和顶点颜色数据,注意这里只使用了一个 VBO, 使用 stride 和 offset 进行区分
glBindBuffer(GL_ARRAY_BUFFER, userData->vboIds[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3,GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat) , NULL);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat) ,(void*)(3*sizeof(GLfloat)));
glEnableVertexAttribArray(1);
// notice using GL_ELEMENT_ARRAY_BUFFER
// 使用 VBO 缓存顶点的索引数据
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices), indices, GL_STATIC_DRAW);
glBindVertexArray(0);
}
glBindVertexArray(userData->vaoId);
glUniformMatrix4fv(userData->mvpLoc, 1, GL_FALSE, (GLfloat *)&userData->mvpMatrix.m[0][0]);
glDrawElements(GL_TRIANGLES,36,GL_UNSIGNED_INT,0);
glBindVertexArray(0);
}
上面的代码中同时使用了VBO
,EBO
和VAO
进行绘制,注意这里有一个技巧:一个VBOID
可以缓存多个数据,可以通过Stride
和 offset
的方法进行区分
对于每个顶点来说,顶点坐标位于前面,offset
为 0 sizeof(GLFloat))
,长度为 3 sizeof(GLFloat))
,纹理坐标位于后面, offset
为 3 sizeof(GLFloat))
, 长度为 2 sizeof(GLFloat))
,每个顶点的步长(stride
)就是3+2(sizeof(GLFloat))
VAO(Vertex Array Object)
VAO 是指顶点数组对象,主要用于管理VBO
或者EBO
,减少 glBindBuffer
、glEnableVertexAttribArray
、 glVertexAttribPointer
这些调用操作,高效的在顶点属性配置之间切换
使用VAO绘制的示例如下:
static void DrawPrimitiveWithVBOs(ESContext *esContext)
{
UserData *userData = esContext->userData;
GLuint offset = 0;
// vboIds[0] - used to store vertex attribute data
// vboIds[l] - used to store element indices
if (userData->vboIds[0] == 0 && userData->vboIds[1] == 0) {
// Only allocate on the first draw
glGenBuffers(2, userData->vboIds);
glGenVertexArrays(1, &userData->vaoId);
printf("gen vbo id:%d %d vao id:%d.\n",userData->vboIds[0],userData->vboIds[1],userData->vaoId);
// 开始绑定 vao id
glBindVertexArray(userData->vaoId);
// 使用 VBO 缓存 顶点和顶点颜色数据,注意这里只使用了一个 VBO, 使用 stride 和 offset 进行区分
glBindBuffer(GL_ARRAY_BUFFER, userData->vboIds[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3,GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat) , NULL);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat) ,(void*)(3*sizeof(GLfloat)));
glEnableVertexAttribArray(1);
// notice using GL_ELEMENT_ARRAY_BUFFER
// 使用 VBO 缓存顶点的索引数据
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices), indices, GL_STATIC_DRAW);
// 结束绑定
glBindVertexArray(0);
}
// 开始使用
glBindVertexArray(userData->vaoId);
glUniformMatrix4fv(userData->mvpLoc, 1, GL_FALSE, (GLfloat *)&userData->mvpMatrix.m[0][0]);
glDrawElements(GL_TRIANGLES,36,GL_UNSIGNED_INT,0);
glBindVertexArray(0);
}
主要步骤是:
glGenVertexArrays
生产 vao idglBindVertexArray(userData->vaoId);
开始绑定 vao 的操作glBindVertexArray(0);
结束绑定 vao 的操作