opengl VAO and VBO

本文通过Cocos2d-x的renderer代码解释了VAO(Vertex Array Object)与没有VAO的区别,阐述了VAO如何减少渲染时间,通过设置VAO和VAO相关参数来优化顶点数据的使用。

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的作用。


### OpenGLVAOVBO 的概念及用法 #### 一、VBO (Vertex Buffer Object) VBO 是一种缓冲对象,用于存储大量顶点数据到 GPU 显存中。这使得图形处理单元可以直接访问这些数据而无需通过 CPU 进行传输,从而提高了性能。 在 Opengl 中创建 VBO 的过程如下: ```cpp unsigned int VBO; glGenBuffers(1, &VBO); ``` 这段代码生成了一个新的缓冲 ID 并将其赋值给变量 `VBO`[^1]。 接下来需要激活这个新创建的缓冲,并向其中填充实际的数据: ```cpp // 绑定缓冲区目标为GL_ARRAY_BUFFER glBindBuffer(GL_ARRAY_BUFFER, VBO); // 向当前绑定的缓冲区写入指定数量字节的数据 glBufferData( GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW // 数据不会频繁改变 ); ``` #### 二、VAO (Vertex Array Object) VAO 被设计用来简化状态设置的过程。它保存了关于如何读取来自一个或多个 VBOs 的顶点属性的信息。当渲染时只要简单地绑定对应的 VAO 即可完成复杂的配置工作。 具体操作包括定义哪些属性应该被启用(`glEnableVertexAttribArray`),以及描述每个属性应该如何映射至输入流 (`glVertexAttribPointer`) 。一旦完成了上述设定,则可以通过下面的方式来进行绘制: ```cpp void Triangle::onDraw() { if(mShader == nullptr || !mShader->useProgram()) return; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClearColor(0.5f, 0.6f, 0.7f, 1.0f); glBindVertexArray(VAO); // 只需这一句就能恢复之前所有的配置 glDrawArrays(GL_TRIANGLES, 0, 3); // 开始绘图命令 glFlush(); mShader->unuseProgram(); } ``` 这里展示了如何利用已经预先配置好的 VAO 来快速执行绘图指令[^2]。 #### 三、两者区别 - **作用范围不同**: VBO 主要负责储存原始几何信息;而 VAO 则是用来记录一组特定于应用程序的状态集合。 - **效率差异**: 使用 VAO 可以显著减少重复性的 API 调用次数,因为所有必要的参数都可以一次性建立好并储存在该对象内部。相比之下,如果每次都手动去重新指定相同的选项将会非常低效[^3]. - **内存位置区分**: 缓冲对象(如 VBO)位于显卡上的专用区域——即所谓的 "显存" ,而不是普通的系统 RAM 内部[^4].
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值