VBO用于存储顶点数据,包括顶点颜色、坐标、法线,以及顶点的indices。
VAO则用于存储图形处理器将怎么使用VBO里面的数据,及顶点数据中哪些是坐标、哪些是颜色、哪些是法线等信息。
之前对于这些总是不是太明白,因此我猜测也有一部分跟我一样不明白,所以我准备通过Cocos2d-x的renderer代码来说明有VAO和没有VAO的时候的区别,来加深对VAO的理解。
首先我们来看初始化代码:
void Renderer::setupBuffer()
{
if(Configuration::getInstance()->supportsShareableVAO())
{
setupVBOAndVAO();
}
else
{
setupVBO();
}
}
从上面代码来看我们知道有VAO和没有VAO的时候初始化代码是不一样的。那么接下来我们就分别来看一看setupVBOAndVAO和setupVBO的区别在哪里。
先看有VAO的情况:
void Renderer::setupVBOAndVAO()
{
//generate vbo and vao for trianglesCommand
glGenVertexArrays(1, &_buffersVAO);
GL::bindVAO(_buffersVAO);
glGenBuffers(2, &_buffersVBO[0]);
glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(_verts[0]) * VBO_SIZE, _verts, GL_DYNAMIC_DRAW);
// vertices
glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_POSITION);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, vertices));
// colors
glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_COLOR);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, colors));
// tex coords
glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_TEX_COORD);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, texCoords));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_indices[0]) * INDEX_VBO_SIZE, _indices, GL_STATIC_DRAW);
// Must unbind the VAO before changing the element buffer.
GL::bindVAO(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//generate vbo and vao for quadCommand
glGenVertexArrays(1, &_quadVAO);
GL::bindVAO(_quadVAO);
glGenBuffers(2, &_quadbuffersVBO[0]);
glBindBuffer(GL_ARRAY_BUFFER, _quadbuffersVBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(_quadVerts[0]) * VBO_SIZE, _quadVerts, GL_DYNAMIC_DRAW);
// vertices
glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_POSITION);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, vertices));
// colors
glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_COLOR);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, colors));
// tex coords
glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_TEX_COORD);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, texCoords));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadbuffersVBO[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_quadIndices[0]) * INDEX_VBO_SIZE, _quadIndices, GL_STATIC_DRAW);
// Must unbind the VAO before changing the element buffer.
GL::bindVAO(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
CHECK_GL_ERROR_DEBUG();
}
在上面这段代码中,前半部分是初始化三角形VAO,后半部分是初始化四边形的VAO,模式基本是一样的,因此只看上半部分,
在这里先是创建了一个VAO并绑定到,说明接下来就是用整个VAO,然后生成了2个VBO,用于存放顶点数据和定点indicis数据。
然后给顶点数据绑定数据,即告诉opengles顶点数据在哪里,然后设置定点的颜色、法线、坐标信息。
然后继续绑定indices数据。关闭VAO,VBO。
再看没有VBO的情况:
void Renderer::setupVBO()
{
glGenBuffers(2, &_buffersVBO[0]);
glGenBuffers(2, &_quadbuffersVBO[0]);
mapBuffers();
}
void Renderer::mapBuffers()
{
// Avoid changing the element buffer for whatever VAO might be bound.
GL::bindVAO(0);
glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(_verts[0]) * VBO_SIZE, _verts, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, _quadbuffersVBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(_quadVerts[0]) * VBO_SIZE, _quadVerts, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_indices[0]) * INDEX_VBO_SIZE, _indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadbuffersVBO[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_quadIndices[0]) * INDEX_VBO_SIZE, _quadIndices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
CHECK_GL_ERROR_DEBUG();
}
从代码可以看出,在没有VAO的情况下,除了不设置定点的颜色、坐标和法线信息以为,其他的和有VAO的时候基本都是一样的。
从初始化可以看出来一部分区别了,因为VAO可以存储顶点的使用信息,所以在有VAO的时候我们就可以设置这些信息到VAO,但是设置到VAO之后有什么好处呢,就让我们继续看看下面使用的代码了。
void Renderer::drawBatchedQuads()
{
//TODO: we can improve the draw performance by insert material switching command before hand.
int indexToDraw = 0;
int startIndex = 0;
//Upload buffer to VBO
if(_numberQuads <= 0 || _batchQuadCommands.empty())
{
return;
}
if (Configuration::getInstance()->supportsShareableVAO())
{
//Bind VAO
GL::bindVAO(_quadVAO);
//Set VBO data
glBindBuffer(GL_ARRAY_BUFFER, _quadbuffersVBO[0]);
// option 1: subdata
// glBufferSubData(GL_ARRAY_BUFFER, sizeof(_quads[0])*start, sizeof(_quads[0]) * n , &_quads[start] );
// option 2: data
// glBufferData(GL_ARRAY_BUFFER, sizeof(quads_[0]) * (n-start), &quads_[start], GL_DYNAMIC_DRAW);
// option 3: orphaning + glMapBuffer
glBufferData(GL_ARRAY_BUFFER, sizeof(_quadVerts[0]) * _numberQuads * 4, nullptr, GL_DYNAMIC_DRAW);
void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
memcpy(buf, _quadVerts, sizeof(_quadVerts[0])* _numberQuads * 4);
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadbuffersVBO[1]);
}
else
{
#define kQuadSize sizeof(_verts[0])
glBindBuffer(GL_ARRAY_BUFFER, _quadbuffersVBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(_quadVerts[0]) * _numberQuads * 4 , _quadVerts, GL_DYNAMIC_DRAW);
GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);
// vertices
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, vertices));
// colors
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, colors));
// tex coords
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, texCoords));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadbuffersVBO[1]);
}
......
}
省略号后面还有清除代码如下:
if (Configuration::getInstance()->supportsShareableVAO())
{
//Unbind VAO
GL::bindVAO(0);
}
else
{
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
这个函数是用于绘制四边形的,三角形基本和这个函数类似。
在有VAO的时候:
1、启用VAO
2、更新顶点buff的数据(因为顶点可能已经被更新)
3、启用GL_ELEMENT_ARRAY_BUFFER供后面glDrawElements使用。
这里因为顶点的使用信息(颜色、做包、法线的位置)已经保存到了VAO,所以不需要再次设置这些信息到opengles。
在没有VAO的时候:
1、更新顶点数据,同上面的第二步。
2、设置顶点的使用信息,这里有额外的opengles数据交互。
3、同有VAO的时候的第三步。
由此可见,因为VAO保存了顶点的使用信息,减少了每次绘制的时候都要设置顶点的使用信息所导致的图形处理器和cpu的交互,节省了渲染时间。
这也就是VAO的作用。