VAO和VBO:
顶 点数组对象(Vertex Array Object即VAO)是一个包含一个或数个顶点缓冲区对象(Vertex Buffer Object, 即VBO)的对象,一般存储一个可渲染物体的所有信息。顶点缓冲区对象(VertexBuffer ObjectVBO)是你显卡内存中的一块高速内存缓冲区,用来存储顶点的所有信息。
这些概念显得很晦涩,简而言之,一般我们绘制一些图形需要将所有顶点的信息存储在一个数组里,但是经常会出现一些点是被重复使用的,这样就会出现一个点的信息的存储空间被重复使用的问题,这样第一会造成存储控件的浪费,第二就是如果我们要修改这个点的信息,需要改多次。所以我们采用索引的方式来描述图形,这样可以用一个数组存储点的信息,另外一个数组存储点的索引,这样所有的点都是不同的,另外把顶点信息存储在显卡的内存中,减少了cpu向gpu传输数据的时间,提高了程序的渲染效率,这就是VBO,在OpenGL3.0中,出现了更进一步的VAO,VBO通过绘制上下文获得绘制状态,VAO可以拥有多个VBO,它记录所有绘制状态,它的代码更简洁,效率更高,在Cocos2d-x的绘制中,我们会判断底层是否支持VAO ,如果支持VAO,那么优先采用VAO绘制。二者的区别可以从初始化就可以看出来:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 | void Renderer::setupBuffer() {
if (Configuration::getInstance()->supportsShareableVAO())
{
//初始化VBO和VAO
setupVBOAndVAO();
}
else
{
//不支持VAO,只初始化VBO
setupVBO();
} } void Renderer::setupVBOAndVAO() {
//一个VAO
glGenVertexArrays(1,&_quadVAO);
//绑定VAO
GL::bindVAO(_quadVAO);
//创建生成两个VBO
glGenBuffers(2,&_buffersVBO[0]);
//顶点Buffer
glBindBuffer(GL_ARRAY_BUFFER,_buffersVBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof (_quads[0])*VBO_SIZE,_quads,GL_DYNAMIC_DRAW);
//这里就是VAO和VBO的区别,VAO把这些放到初始化中,无论后面绘制多少次,只要他不被改变,这段代码只会被调用一次,而VBO中,这个功能的代码会在每次被绘制时调用,这样就节约了效率
//位置
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));
//颜色
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));
//纹理坐标数据
glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_TEX_COORDS);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORDS,2,GL_FLOAT,GL_FALSE, sizeof (V3F_C4B_T2F),(GLvoid*)offsetof(V3F_C4B_T2F,texCoords));
//索引Buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,_buffersVBO[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof (_indices[0])*VBO_SIZE*6,_indices,GL_STATIC_DRAW);
//取消VAO
GL::bindVAO(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
glBindBuffer(GL_ARRAY_BUFFER,0);
CHECK_GL_ERROR_DEBUG(); } void Renderer::setupVBO() {
//创建生成两个VBO
glGenBuffers(2,&_buffersVBO[0]);
//调用函数绑定buffer
mapBuffers(); } void Renderer::mapBuffers() {
//GL_ARRAY_BUFFER表示顶点数据
//GL_ELEMENT_ARRAY_BUFFER表示索引数据
//避免改变buffer元素
GL::bindVAO(0);
//绑定id顶点数据
glBindBuffer(GL_ARRAY_BUFFER,_buffersVBO[0]);
//为改id制定一段内存区域
glBufferData(GL_ARRAY_BUFFER, sizeof (_quads[0])*VBO_SIZE,_quads,GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER,0);
//第二个VBO索引数据
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,_buffersVBO[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof (_indices[0])*VBO_SIZE*6,_indices,GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
CHECK_GL_ERROR_DEBUG(); } |
需要介绍的两个关键的函数
当传入的第二个参数第一次使用一个非零无符号整数时,创建一个新的缓冲区对象;当第二个参数是之前使用过的,这个缓冲区对象成为活动缓冲区对象;如果第二个参数值为0时,停止使用缓冲区对象。
glBufferData参数介绍
参数1,目标GL_ARRAY_BUFFER或者GL_ELEMENT_ARRAY_BUFFER
参数2,内存容量
参数3,用于初始化缓冲区对象,可以使一个指针,也可以是空
参数4,如何读写,可以选择如下几种:
从初始化的代码上,为什么VAO反倒复杂了呢?因为他只是把绘制时需要做的一些事情提前放到初始化函数中,来看一下绘制流程。
123456789101112131415161718192021222324252627282930313233 |
//当前的openGL是否支持VAO
if (Configuration::getInstance()->supportsShareableVAO())
{
//绑定顶点数组
glBindBuffer(GL_ARRAY_BUFFER,_buffersVBO[0]);
//向缓冲区申请空间并指定数据传输方式
glBufferData(GL_ARRAY_BUFFER, sizeof (_quads[0])*(_numQuads),nullptr,GL_DYNAMIC_DRAW);
//提供缓冲区对象包含整个数据集合的更新
void *buf=glMapBuffer(GL_ARRAY_BUFFER,GL_WRITE_ONLY);
memcpy (buf,_quads, sizeof (_quads[0])*(_numQuads));
//缓冲区对象的更新完成
glUnmapBuffer(GL_ARRAY_BUFFER);
//为了禁用缓冲区对象,可以用0作为缓冲区对象的标识符来调用glBindBuffer()函数。这将把OpenGL切换为默认的不使用缓冲区对象的模式。
glBindBuffer(GL_ARRAY_BUFFER,0);
//BindVAO
GL::bindVAO(_quadVAO);
}
else
{ #definekQuadSizesizeof(_quads[0].bl)
glBindBuffer(GL_ARRAY_BUFFER,_buffersVBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof (_quads[0])*_numQuads,_quads,GL_DYNAMIC_DRAW);
//激活顶点颜色纹理坐标的属性
GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);
//顶点
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION,3,GL_FLOAT,GL_FALSE,kQuadSize,(GLvoid*)offsetof(V3F_C4B_T2F,vertices));
//颜色
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR,4,GL_UNSIGNED_BYTE,GL_TRUE,kQuadSize,(GLvoid*)offsetof(V3F_C4B_T2F,colors));
//纹理坐标
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORDS,2,GL_FLOAT,GL_FALSE,kQuadSize,(GLvoid*)offsetof(V3F_C4B_T2F,texCoords));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,_buffersVBO[1]);
} |
可以看到,这些设置属性的函数放在了绘制函数里,虽然看似是一样的,但是绘制函数会被调用的更频繁,所以把这些函数放到初始化函数中可以大幅提高程序的效率。
这里介绍VAO的两个函数:
最后需要调用绘制元素函数,绘制这些信息:
1 | glDrawElements(GL_TRIANGLES,(GLsizei)quadsToDraw*6,GL_UNSIGNED_SHORT,(GLvoid*)(startQuad*6* sizeof (_indices[0]))); |
它根据索引绘图(注意:顶点数据和索引各自使用不同的缓冲区)。
需要注意的是在Renderer的析构函数中要调用glDeleteBuffers来释放它的资源,并使它的标识可以其他缓冲区对象使用。
上一篇中介绍的几种渲染命令中的QUAD_COMMAND(这里把它称作四边形绘制)命令回调用drawBatchedQuads调用绘制函数,处理这个逻辑的命令是这样的:
12345678910111213141516171819 | if (commandType==RenderCommand::Type::QUAD_COMMAND) {
autocmd= static_cast (command);
CCASSERT(nullptr!=cmd, "IllegalcommandforRenderCommandTagedasQUAD_COMMAND" );
//如果Quad数据量超过VBO的大小,那么调用绘制,将缓存的命令全部绘制
if (_numQuads+cmd->getQuadCount()>VBO_SIZE)
{
CCASSERT(cmd->getQuadCount()>=0&&cmd->getQuadCount()<VBO_SIZE, "VBOisnotbigenoughforquaddata,pleasebreakthequaddatadownorusecustomizedrendercommand" );
drawBatchedQuads();
}
//将命令缓存起来,先不调用绘制
_batchedQuadCommands.push_back(cmd);
memcpy (_quads+_numQuads,cmd->getQuads(), sizeof (V3F_C4B_T2F_Quad)*cmd->getQuadCount());
//转换成世界坐标
convertToWorldCoordinates(_quads+_numQuads,cmd->getQuadCount(),cmd->getModelView());
//记录下四边形数量
_numQuads+=cmd->getQuadCount(); } |
1234567 | void Renderer::flush() {
//绘制
drawBatchedQuads();
//清空
_lastMaterialID=0; } |
这个处理主要是把命令存入_batchedQuadCommands中,如果如果Quad数据量超过VBO的大小,那么调用绘制,将缓存的命令全部绘制。
如果一直没有超过VBO的大小,drawBatchedQuads绘制函数将在flush被调用时调用。
如有错误,欢迎指出。下一篇介绍图形渲染和批处理。
来源网址:http://blog.csdn.net/bill_man/article/details/38314077