我想大家都已经熟悉VBO了吧。在GL3.0时代的VBO大体还是处于最重要的地位,但是与此同时也出现了不少新的用法和辅助役,其中一个就是VAO。本文大致小记一下这两者的联系,帮助大家理解一下这个角色。——ZwqXin.com

VBO?See[学一学,VBO]

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/opengl/vao-and-vbo-stuff.html

如果你也逐渐步进GL3.0开始的新标准,你大概会留意到传统的绘图方式(glVertex)已经要被废掉了,不仅如此,以最高绘制速度为标记的显示列表方式也已经被印上deprecated了,这样,在以前的文章([学一学,VBO] )中的讨论,在新标准的面前都显得没什么必要了。我想说的是,OpenGL对GPU的入口“顶点传送”——或者说,绘制方式,尽量不要再选择传统方式(glVertex)或显示列表(glCallList)甚至VA(vertex array)了。哪怕你是用的一个compatable的GL-context,哪怕顶点数据部分持续变化或者恒定不变,也得注意要尽量尽量使用VBO来组织你的数据。

另外的一点,就是尽量不要以客户端状态函数来使用VBO了。我是说——glEnableClientState/glDisableClientState,还有glVertexPointer这类函数。VBO的本意是把本地(GL客户端)的数据完全交给GPU(GL服务端)来管理,所以若非为了数据的更新,你完全可以在调用glBufferData之后选择扔弃保存在本地内存中的数据。VBO可以说只有在传输数据的时候跟本地客户端有联系,它的状态是服务端(我们的流水线)管理的,当初沿用VA的那些客户端状态函数,还有一个原因就是它们方便地与shader里面的固定attribute(gl_Position之类)建立联系【见[OpenGL/GLSL数据传递小记(2.x)]】,但是GLSL已经也不推荐使用那些attrbute了。(事实上,以上这些都是deprecated的了。)

C++代码1
  1. glBindBuffer(GL_ARRAY_BUFFER, m_nPositionVBO);
  2. glEnableClientState(GL_VERTEX_ARRAY);
  3. glVertexPointer(2, GL_FLOAT, 0, NULL);
  4. glBindBuffer(GL_ARRAY_BUFFER, m_nTexcoordVBO);
  5. glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  6. glTexCoordPointer(2, GL_FLOAT, 0, NULL);
  7. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nIndexVBO);
  8. glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
  9. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);
  10. glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  11. glDisableClientState(GL_VERTEX_ARRAY);
  12. glBindBuffer(GL_ARRAY_BUFFER, NULL);
C++代码2
  1. glBindBuffer(GL_ARRAY_BUFFER, m_nQuadPositionVBO);
  2. glEnableVertexAttribArray(VAT_POSITION);
  3. glVertexAttribPointer(VAT_POSITION, 2, GL_INT, GL_FALSE, 0, NULL);
  4. glBindBuffer(GL_ARRAY_BUFFER, m_nQuadTexcoordVBO);
  5. glEnableVertexAttribArray(VAT_TEXCOORD);
  6. glVertexAttribPointer(VAT_TEXCOORD, 2, GL_INT, GL_FALSE, 0, NULL);
  7. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nQuadIndexVBO);
  8. glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
  9. glDisableVertexAttribArray(VAT_POSITION);
  10. glDisableVertexAttribArray(VAT_TEXCOORD);
  11. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);
  12. glBindBuffer(GL_ARRAY_BUFFER, NULL);

以上两段是效果一致的VBO渲染部分的代码。尽量用第二种吧。使用第二种的前提是你使用shader来进行顶点处理,VAT_POSITION/VAT_TEXCOORD需要与Shader里代表顶点/纹理坐标的attribute变量建立联系(参考[OpenGL/GLSL数据传递小记(2.x)]),在这个GL3.0之后的时代里,这种前提也算不上什么前提就是了。我们来囫囵吞枣地猜测一下OpenGL是怎么处理VBO的数据的。

1. VBO

与其他buffer object一样,VBO归根到底是显卡存储空间里的一块缓存区(Buffer)而已,这个Buffer有它的名字(VBO的ID),OpenGL在GPU的某处记录着这个ID和对应的显存地址(或者地址偏移,类似内存)。用代码看看吧:

C++代码
  1. //生成一个Buffer的ID,不管是什么类型的
  2. glGenBuffers(1, &m_nQuadVBO);
  3. //绑定ID,同时也指定该ID对应的buffer的信息类型是GL_ARRAY_BUFFER
  4. glBindBuffer(GL_ARRAY_BUFFER, m_nQuadVBO);
  5. //为该ID指定一块指定大小的存储区域(区域的位置大抵由末参数影响),  传输数据
  6. glBufferData(GL_ARRAY_BUFFER, sizeof(fQuadData), fQuadData, GL_STREAM_DRAW);

这里是VBO的初始化阶段。在这里我们看到了这是对位置,还是颜色,还是纹理坐标,还是法线,还是其他顶点属性进行设置的吗?是的,这个信息是:起码在初始化阶段,一个VBO对于交给它存储的数据到底是什么,完全不知道。我们此时再看回上面两段渲染部分的代码,就明白了:哦,原来这都是在渲染时确定的!

对于第一段渲染代码,glVertexPointer(2, GL_FLOAT, 0, NULL)这个函数指定了VBO里的是什么数据——顶点位置,float类型,2个float指涉一个顶点位置,在区域里无偏移地采集数据,等等。之后的glDrawElements只不过根据组织模式(GL_TRIANGLES,这个是直接交给vertex处理后的Geometry处理的)和索引数据去采集VBO里的这些数据罢了——它从某个地方获取了glBindBuffer指定的位置,还有glVertexPointer设定的信息(由glEnableClientState启用),它进行绘制所需要的一切——这个地方,就是所谓的GL-Context吧,那个保存了所有运行时流水线状态的东西。

对于第二段渲染代码,大体是一样的,只是glVertexAttribPointer使用第一个参数(location)指涉对应vertex-shader里哪个in attribute。VBO在渲染阶段才指定数据位置和“顶点信息”(Vertex Specification),然后根据此信息去解析缓存区里的数据,联系这两者中间的桥梁是GL-Contenxt。GL-context整个程序一般只有一个,所以如果一个渲染流程里有两份不同的绘制代码,GL-context就负责在它们之间进行状态切换。这也是为什么要在渲染过程中,在每份绘制代码之中有glBindBuffer/glEnableVertexAttribArray/glVertexAttribPointer。那么优化方法就来了——把这些都放到初始化时候完成吧!——这样做的限制条件是“负责记录状态的GL-context整个程序一般只有一个”,那么就不直接用GL-context记录,用别的东西做状态记录吧——这个东西针对"每份绘制代码“有一个,记录该次绘制所需要的所有VBO所需信息,把它保存到GPU特定位置,绘制的时候直接在这个位置取信息绘制。

于是,VAO诞生了。

2.VAO

VAO的全名是Vertex Array Object,首先,它不是Buffer-Object,所以不用作存储数据;其次,它针对”顶点“而言,也就是说它跟”顶点的绘制“息息相关,在GL3.0的世界观里,这相当于”与VBO息息相关“。(提示,它跟VA真是虾米关系都没有的,嘛,虽然这的确让人误会,我最初见到这个名词时也误会了的说。)

按上所述,它的定位是state-object(状态对象,记录存储状态信息)。这明显区别于buffer-object。如果有人碎碎念”既然是记录顶点的信息,为什么不叫vertex attribute object“呢?我想说这些孩子你们真没认真看文章嘛——VAO记录的是一次绘制中做需要的信息,这包括”数据在哪里-glBindBuffer(GL_ARRAY_BUFFER)“、”数据的格式是怎样的-glVertexAttribPointer“(顶点位置的数据在哪里,顶点位置的数据的格式是怎样的/纹理坐标的数据在哪里,纹理坐标的数据的格式是怎样的....视乎你让它关联多少个VBO、VBO里有多少种数据),顺带一提的是,这里的状态还包括这些属性关联的shader-attribute的location的启用(glEnableVertexAttribArray)、这些顶点属性对应的顶点索引数据的位置(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER),如果你指定了的话)。在GL的wiki里把这些”信息“抽象成一个属性数据体:

  1. struct VertexAttribute
  2. {
  3. bool bIsEnabled = GL_FALSE;
  4. int iSize = 4; //This is the number of elements in this attribute, 1-4.
  5. unsigned int iStride = 0;
  6. VertexAttribType eType = GL_FLOAT;
  7. bool bIsNormalized = GL_FALSE;
  8. bool bIsIntegral = GL_FALSE;
  9. void * pBufferObjectOffset = 0;
  10. BufferObject * pBufferObj = 0;
  11. };
  12. struct VertexArrayObject
  13. {
  14. BufferObject *pElementArrayBufferObject = NULL;
  15. VertexAttribute attributes[GL_MAX_VERTEX_ATTRIB];
  16. }

这里,VertexArrayObject 就包括了一个Index-VBO【[索引顶点的VBO与多重纹理下的VBO] 】(可以没有,例如绘制用的是glDrawArray)还有一些VertexAttribute。后者包括顶点属性的格式和位置和一个启用与否的状态。这些都对应了上述讨论的那几个函数(注意glVertexAttribPointer和glVertexAttribIPointer的选择决定bool bIsIntegral,数据是否整型不可规范化)。那么,现在我们可以知道VAO的用法了:

C++代码 - 初始化部分
  1. glGenVertexArrays(1, &m_nQuadVAO);
  2. glBindVertexArray(m_nQuadVAO);
  3. glGenBuffers(1, &m_nQuadPositionVBO);
  4. glBindBuffer(GL_ARRAY_BUFFER, m_nQuadPositionVBO);
  5. glBufferData(GL_ARRAY_BUFFER, sizeof(fQuadPos), fQuadPos, GL_STREAM_DRAW);
  6. glEnableVertexAttribArray(VAT_POSITION);
  7. glVertexAttribPointer(VAT_POSITION, 2, GL_INT, GL_FALSE, 0, NULL);
  8. glGenBuffers(1, &m_nQuadTexcoordVBO);
  9. glBindBuffer(GL_ARRAY_BUFFER, m_nQuadTexcoordVBO);
  10. glBufferData(GL_ARRAY_BUFFER, sizeof(fQuadTexcoord), fQuadTexcoord, GL_STREAM_DRAW);
  11. glEnableVertexAttribArray(VAT_TEXCOORD);
  12. glVertexAttribPointer(VAT_TEXCOORD, 2, GL_INT, GL_FALSE, 0, NULL);
  13. glGenBuffers(1, &m_nQuadIndexVBO);
  14. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nQuadIndexVBO);
  15. glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(nQuadIndex), nQuadIndex, GL_STREAM_DRAW);
  16. glBindVertexArray(NULL);
  17. glBindBuffer(GL_ARRAY_BUFFER, NULL);
  18. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);
C++代码 - 渲染部分
  1. glBindVertexArray(m_nQuadVAO);
  2. glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
  3. glBindVertexArray(NULL);

以上就是VAO的使用方法了,很直观吧?

使用VAO的好处?看上面那么简洁的渲染部分代码就够了。

你甚至可以认为VAO就是一个状态容器,其中粗体字的那几行就是它以及它所”包含“的东西——填充了”VertexArrayObject结构体“的东西。注意:1.没有一个合适的地方给glDisableVertexAttribArray了,事实上调用glBindVertexArray(NULL)的时候里面所有状态都”关掉“了,也就没所谓针对顶点属性的location做其他什么;2.glBindBuffer(GL_ARRAY_BUFFER, NULL)/glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL)一定要在glBindVertexArray(NULL)后面(不然VAO就把它们也包含了,最后就渲染不出东西了);3.glDrawElements里面的东西(顶点索引的属性状态)VAO可没记录保存哦;4.glVertexPointer那类函数理论上也可以,但是建议还是不要混用deprecated的函数进去了。

VAO和VBO的更多相关文章

  1. CSharpGL(7)对VAO和VBO的封装

    CSharpGL(7)对VAO和VBO的封装 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码中包含10多个独立的Demo,更适合入门参考 ...

  2. 几张图看明白VAO、VBO、EBO的关系和代码顺序

    0.详细教程可看https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/ 1.可以简单地认为VAO的 ...

  3. 【OpenGL】VAO与VBO

    1.我们先了解什么是OpenGL对象(OpenGL Object) 根据OpenGL Wiki的定义: An OpenGL Object is an OpenGL construct that con ...

  4. OpenGL 4.0 GLSL 基础教程概览——VAO和VBO常用操作接口

    (一) OpenGL  4.3 最新渲染管线图 从OpenGL 2.0 到 OpenGL 3.0变化非常大,但从OpenGL 3.0 到OpenGL 4.0 变化不是太大. 着色器程序直接运行在GPU ...

  5. (Python OpenGL)【 0】关于VAO和VBO以及OpenGL新特性

    (Python OpenGL)关于新版OpenGL需要了解的: 随着OpenGL状态和固定管线模式的移除,我们不在用任何glEnable函数调用,而且也不会有glVertex.glColor等函数调用 ...

  6. OpenGL(二十四) VAO、VBO和着色器使用示例

    1. 新建一个工程,新建一个头文件Shader.h,内容如下: #ifndef _SHADER_H_ #define _SHADER_H_ #include <vector> #inclu ...

  7. 图形渲染的大致过程和关于OpenGL渲染管线的一些零碎知识,openglpipeline,vao,vbo,ebo.

    重要!!! OpenGL新人一枚,希望可以再此和大家分享有用的知识,少走弯路 文章会定期更新,把前面几段已经整理过的知识更完后,接下来每周至少会更两次. 文章如果有不对的,理解错误的地方,也非常希望在 ...

  8. OpenGL中glVertex、显示列表(glCallList)、顶点数组(Vertex array)、VBO及VAO区别

    OpenGL中glVertex.显示列表(glCallList).顶点数组(Vertex array).VBO及VAO区别 1.glVertex 最原始的设置顶点方法,在glBegin和glEnd之间 ...

  9. VAO VBO IBO大乱炖

    最近对程序中绘制卡顿的问题忍无可忍,终于决定下手处理了.程序涉及的绘制比较多,除了点.线.三角形.多边形.圆柱体之外,还有自组格式模型.开始想全部采用显示列表优化,毕竟效率最高,虽然显示列表存在编译之 ...

随机推荐

  1. 写点恐怖小说为自己打call

    https://github.com/zhangbo2008/TryingWriteHorrorStory

  2. canvas制图学习

    <!DOCTYPE html> <html lang="zh-en"> <head> <meta charset="UTF-8& ...

  3. 关于webpack require.context() 的那点事

    先说 webpack里面有这么一招:使用require.context()方法来自动导入模块 官方文档有点深奥,老衲百度下拿了一段来直接使用,但是想看下它是如何运行的 本篇这里不会有太深入的研究,只是 ...

  4. Longest Common Subsequence (DP)

    Given two strings, find the longest common subsequence (LCS). Your code should return the length of  ...

  5. Educational Codeforces Round 72 (Rated for Div. 2) A题

    Problem Description: You play your favourite game yet another time. You chose the character you didn ...

  6. 8月清北学堂培训 Day6

    今天是杨思祺老师的讲授~ 图论 双连通分量 在无向图中,如果无论删去哪条边都不能使得 u 和 v 不联通, 则称 u 和 v 边双连通: 在无向图中,如果无论删去哪个点(非 u 和 v)都不能使得 u ...

  7. Jmeter linux 运行

    一.在Linux服务器先安装sdk 1.先从客户端下载jdk1.8.0_144.tar.gz,再上传到服务器 2.解压:tar -xzf jdk1.8.0_144.tar.gz,生成文件夹 jdk1. ...

  8. 使用Flask设计带认证token的RESTful API接口

    大数据时代 Just a record. 使用Flask设计带认证token的RESTful API接口[翻译] 上一篇文章, 使用python的Flask实现一个RESTful API服务器端  简 ...

  9. try catch块的秘密

    最近有同事遇到问题: 她在4处手动抛运行异常,5处存在return语句,结果程序在2出现异常时没有抛出运行异常,导致事务不一致. 我们都知道,当程序出现异常时候并且在不采取任何措施的情况下,是会抛出异 ...

  10. jmeter怎么上传图片

    1.使用Fiddler抓取上传图片的接口地址,将地址接口按规定粘贴到Jmeter的HTTP请求内(复制粘贴注意空格)(我已经有HTTP默认请求页,所以这里不需要配置) 2.HTTP请求页选择[高级-客 ...