VBO定义
VBO(Vertex Buffer Object) 是指顶点缓冲区对象,而EBO(Element Buffer Object)是指图元索引缓冲区对象,VAO和EBO实际上是同一类buffer按照用途的不同称呼
OpenGL-ES2.0 编程中,用于绘制的顶点数组数据首先保存在系统内存,在调用glDrawArrays或者glDrawElements等进行绘制的时候,需要将顶点数组数据从系统内存拷贝到显存,但是很多时候我们没必要在每次绘制的时候进行内存拷贝,如果可以在显存中缓存这些数据,就可以在很大程度上降低内存拷贝带来的开销
OpenGL-ES 支持的缓冲区对象包含:
- 数组缓冲区对象 GL_ARRAY_BUFFER 指定数组缓冲区对象用于创建保存顶点数据的缓冲区对象
- 元素数组缓冲区对象 GL_ELEMENT_ARRAY_BUFFER用于创建保存图元索引的缓冲区对象
- 统一变量缓冲区
- 变换反馈缓冲区
- 像素解包缓冲区
- 像素包装缓冲器
- 复制缓冲区
OpenGL-ES3.0 编程中,VAO和EBO的出现就是为了解决这个问题
VAO和EBO的作用就是在显存中提前开辟好一段内存,用于缓存顶点数据或者图元索引数据,从而避免每次绘制时 CPU和 GPU之间的内存拷贝,改进渲染性能,降低内存带宽和功耗 - GL_Array_Buffer 标志指定的缓冲区对象用于保存顶点数组
- GL_Element_Array_Buffer 用于标识指定缓冲区对象用于保存图元索引
VBO创建
VBO创建的基本流程如下所示:
// 创建两个VBO
glGenBuffers(2, userData->vboIds);
//绑定第一个VBO,拷贝顶点数组到显存
glBindBuffer(GL_ARRAY_BUFFER, userData->vboIds[0]);
glBufferData(GL_ARRAY_BUFFER, vtxStride * numVertices, vtxBuf, GL_STATIC_DRAW);
//绑定第二个VBO,拷贝图元索引的显存(EBO)
//可以认为图元索引也需要同时标志为(Element Buffer object)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(GLushort) * numIndices, indices, GL_STATIC_DRAW);
void glBufferData( GLenum target, GLsizeiptr size, const void * data, GLenum usage);
注意第二个和第三个参数:
- GLsizeiptr size : 顶点存储区的实际大小(Bytes 为单位)
- const void * data : 指向顶点存储区的指针
设定Vertex color 属性
前面章节三角形的demo 中,Vertex Array 的值得单独设定的,但是Vertex的颜色是使用的固定值,这里可以给每个Vertex 单独设定颜色
顶点位置和顶点颜色可以放在同一个数组中,通过glVertexAttribPointer 的起始位置和stride 进行区分,示意如下:
示例代码如下:(不使用VBO绘制)
#define VERTEX_POS_SIZE 3 // x, y and z
#define VERTEX_COLOR_SIZE 4 // r, g, b, and a
#define VERTEX_POS_INDX 0
#define VERTEX_COLOR_INDX 1
//代码起点,绘制函数
void Draw ( ESContext *esContext )
{
UserData *userData = esContext->userData;
// 3 vertices, with (x,y,z) ,(r, g, b, a) per-vertex
GLfloat vertices[3 * ( VERTEX_POS_SIZE + VERTEX_COLOR_SIZE )] =
{
-0.5f, 0.5f, 0.0f, // v0
1.0f, 0.0f, 0.0f, 1.0f, // c0
-1.0f, -0.5f, 0.0f, // v1
0.0f, 1.0f, 0.0f, 1.0f, // c1
0.0f, -0.5f, 0.0f, // v2
0.0f, 0.0f, 1.0f, 1.0f, // c2
};
// Index buffer data
GLushort indices[3] = { 0, 1, 2 };
glViewport ( 0, 0, esContext->width, esContext->height );
glClear ( GL_COLOR_BUFFER_BIT );
glUseProgram ( userData->programObject );
glUniform1f ( userData->offsetLoc, 0.0f );
DrawPrimitiveWithoutVBOs ( vertices,
sizeof ( GLfloat ) * ( VERTEX_POS_SIZE + VERTEX_COLOR_SIZE ),
3, indices );
}
void DrawPrimitiveWithoutVBOs ( GLfloat *vertices,
GLint vtxStride,
GLint numIndices,
GLushort *indices )
{
GLfloat *vtxBuf = vertices;
glBindBuffer ( GL_ARRAY_BUFFER, 0 );
glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, 0 );
glEnableVertexAttribArray ( VERTEX_POS_INDX );
glEnableVertexAttribArray ( VERTEX_COLOR_INDX );
glVertexAttribPointer ( VERTEX_POS_INDX, VERTEX_POS_SIZE,
GL_FLOAT, GL_FALSE, vtxStride,
vtxBuf );
vtxBuf += VERTEX_POS_SIZE;
//注意vertex color 加上了偏移量
glVertexAttribPointer ( VERTEX_COLOR_INDX,
VERTEX_COLOR_SIZE, GL_FLOAT,
GL_FALSE, vtxStride, vtxBuf );
glDrawElements ( GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT,
indices );
glDisableVertexAttribArray ( VERTEX_POS_INDX );
glDisableVertexAttribArray ( VERTEX_COLOR_INDX );
}
使用VBO绘制
使用VBO绘制的参考代码如下:
#define VERTEX_POS_SIZE 3 // x, y and z
#define VERTEX_COLOR_SIZE 4 // r, g, b, and a
#define VERTEX_POS_INDX 0
#define VERTEX_COLOR_INDX 1
void Draw ( ESContext *esContext )
{
UserData *userData = esContext->userData;
// 3 vertices, with (x,y,z) ,(r, g, b, a) per-vertex
GLfloat vertices[3 * ( VERTEX_POS_SIZE + VERTEX_COLOR_SIZE )] =
{
-0.5f, 0.5f, 0.0f, // v0
1.0f, 0.0f, 0.0f, 1.0f, // c0
-1.0f, -0.5f, 0.0f, // v1
0.0f, 1.0f, 0.0f, 1.0f, // c1
0.0f, -0.5f, 0.0f, // v2
0.0f, 0.0f, 1.0f, 1.0f, // c2
};
// Index buffer data
GLushort indices[3] = { 0, 1, 2 };
glViewport ( 0, 0, esContext->width, esContext->height );
glClear ( GL_COLOR_BUFFER_BIT );
glUseProgram ( userData->programObject );
glUniform1f ( userData->offsetLoc, 0.0f );
DrawPrimitiveWithVBOs(esContext, 4, vertices,
sizeof(GLfloat) * (VERTEX_POS_SIZE + VERTEX_COLOR_SIZE),
4, indices);
}
static void DrawPrimitiveWithVBOs(ESContext *esContext,
GLint numVertices, GLfloat *vtxBuf,
GLint vtxStride, GLint numIndices,
GLushort *indices)
{
UserData *userData = esContext->userData;
GLuint offset = 0;
// vboIds[0] - used to store vertex attribute data
// vboIds[l] - used to store element indices (EBO)
if (userData->vboIds[0] == 0 && userData->vboIds[1] == 0)
{
// Only allocate on the first draw
glGenBuffers(2, userData->vboIds);
glBindBuffer(GL_ARRAY_BUFFER, userData->vboIds[0]);
glBufferData(GL_ARRAY_BUFFER, vtxStride * numVertices, vtxBuf, GL_STATIC_DRAW);
// Using EBO (Element Buffer Object)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(GLushort) * numIndices, indices, GL_STATIC_DRAW);
}
//bind VBO every time (VBO including vertex array & color array)
glBindBuffer(GL_ARRAY_BUFFER, userData->vboIds[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1]);
glEnableVertexAttribArray(VERTEX_POS_INDX);
glEnableVertexAttribArray(VERTEX_COLOR_INDX);
// notice offset value(not using buffer pointer)
glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE,GL_FLOAT, GL_FALSE, vtxStride, (const void *)offset);
offset += VERTEX_POS_SIZE * sizeof(GLfloat);
glVertexAttribPointer(VERTEX_COLOR_INDX,VERTEX_COLOR_SIZE,GL_FLOAT, GL_FALSE, vtxStride,(const void *)offset);
glDrawArrays(GL_TRIANGLES, 0, 3);
//glDrawElements ( GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT,
0 );
glDisableVertexAttribArray(VERTEX_POS_INDX);
glDisableVertexAttribArray(VERTEX_COLOR_INDX);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
注意的是此时 glVertexAttribPointer 最后一个参数offset 设置的是 0和VERTEX_POS_SIZE
要特别注意的是 使用VBO绘制时,glDrawElements 最后一个参数indices: 指向一段存储区,缓存在顶点数组中顶点的偏移 设置为0
VAO
VAO(Vertex Array Object)是指顶点数组对象,主要用于管理VAO或者EBO,减少 glBindBuffer,glEnableVertexAttribArray,glVertexAttribPointer这些调用操作,高效实现在顶点数组之间切换
- 正常情况下每次绘制使用 VBO EBO 的操作如下
//第一次需要创建VBO,EBO index,然后拷贝相应数据
......
//excute every time
glBindBuffer ( GL_ARRAY_BUFFER, userData->vboIds[0] );
glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1] );
glEnableVertexAttribArray ( VERTEX_POS_INDX );
glEnableVertexAttribArray ( VERTEX_COLOR_INDX );
glVertexAttribPointer ( VERTEX_POS_INDX, VERTEX_POS_SIZE,
GL_FLOAT, GL_FALSE, VERTEX_STRIDE, ( const void * ) 0 );
glVertexAttribPointer ( VERTEX_COLOR_INDX, VERTEX_COLOR_SIZE,
GL_FLOAT, GL_FALSE, VERTEX_STRIDE,
( const void * ) ( VERTEX_POS_SIZE * sizeof ( GLfloat ) ) );
glDisableVertexAttribArray(VERTEX_POS_INDX);
glDisableVertexAttribArray(VERTEX_COLOR_INDX);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
- 现在使用VAO,只需要第一次 bind 上面的操作,以后每次 enbale VAO 就可以
示例代码:
#define VERTEX_POS_SIZE 3 // x, y and z
#define VERTEX_COLOR_SIZE 4 // r, g, b, and a
#define VERTEX_POS_INDX 0
#define VERTEX_COLOR_INDX 1
#define VERTEX_STRIDE ( sizeof(GLfloat) * \
( VERTEX_POS_SIZE + \
VERTEX_COLOR_SIZE ) )
int Init ( ESContext *esContext )
{
UserData *userData = esContext->userData;
const char vShaderStr[] =
"#version 300 es \n"
"layout(location = 0) in vec4 a_position; \n"
"layout(location = 1) in vec4 a_color; \n"
"out vec4 v_color; \n"
"void main() \n"
"{ \n"
" v_color = a_color; \n"
" gl_Position = a_position; \n"
"}";
const char fShaderStr[] =
"#version 300 es \n"
"precision mediump float; \n"
"in vec4 v_color; \n"
"out vec4 o_fragColor; \n"
"void main() \n"
"{ \n"
" o_fragColor = v_color; \n"
"}" ;
GLuint programObject;
// 3 vertices, with (x,y,z) ,(r, g, b, a) per-vertex
GLfloat vertices[3 * ( VERTEX_POS_SIZE + VERTEX_COLOR_SIZE )] =
{
0.0f, 0.5f, 0.0f, // v0
1.0f, 0.0f, 0.0f, 1.0f, // c0
-0.5f, -0.5f, 0.0f, // v1
0.0f, 1.0f, 0.0f, 1.0f, // c1
0.5f, -0.5f, 0.0f, // v2
0.0f, 0.0f, 1.0f, 1.0f, // c2
};
// Index buffer data
GLushort indices[3] = { 0, 1, 2 };
// Create the program object
programObject = esLoadProgram ( vShaderStr, fShaderStr );
if ( programObject == 0 )
{
return GL_FALSE;
}
// Store the program object
userData->programObject = programObject;
// Generate VBO Ids and load the VBOs with data
glGenBuffers ( 2, userData->vboIds );
glBindBuffer ( GL_ARRAY_BUFFER, userData->vboIds[0] );
glBufferData ( GL_ARRAY_BUFFER, sizeof ( vertices ),
vertices, GL_STATIC_DRAW );
glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1] );
glBufferData ( GL_ELEMENT_ARRAY_BUFFER, sizeof ( indices ),
indices, GL_STATIC_DRAW );
// Generate VAO Id
glGenVertexArrays ( 1, &userData->vaoId );
// Bind the VAO and then setup the vertex
// attributes 将下面VBO的操作绑定
glBindVertexArray ( userData->vaoId );
glBindBuffer ( GL_ARRAY_BUFFER, userData->vboIds[0] );
glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1] );
glEnableVertexAttribArray ( VERTEX_POS_INDX );
glEnableVertexAttribArray ( VERTEX_COLOR_INDX );
glVertexAttribPointer ( VERTEX_POS_INDX, VERTEX_POS_SIZE,
GL_FLOAT, GL_FALSE, VERTEX_STRIDE, ( const void * ) 0 );
glVertexAttribPointer ( VERTEX_COLOR_INDX, VERTEX_COLOR_SIZE,
GL_FLOAT, GL_FALSE, VERTEX_STRIDE,
( const void * ) ( VERTEX_POS_SIZE * sizeof ( GLfloat ) ) );
// Reset to the default VAO
glBindVertexArray ( 0 );
glClearColor ( 1.0f, 1.0f, 1.0f, 0.0f );
return GL_TRUE;
}
void Draw ( ESContext *esContext )
{
UserData *userData = esContext->userData;
glViewport ( 0, 0, esContext->width, esContext->height );
glClear ( GL_COLOR_BUFFER_BIT );
glUseProgram ( userData->programObject );
// Bind the VAO 因为上面已经绑定了VAO,现在只需要这一步操作就可以了
glBindVertexArray ( userData->vaoId );
// Draw with the VAO settings
glDrawElements ( GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, ( const void * ) 0 );
// Return to the default VAO
glBindVertexArray ( 0 );
}
参考文档:
VBO、EBO 和 VAO