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

原文地址:http://www.zwqxin.com/archives/opengl/vao-and-vbo-stuff.html

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

另外的一点,就是尽量不要以客户端状态函数来使用VBO了。我是说——glEnableClientState/glDisableClientState,还有glVertexPointer这类函数。VBO的本意是把本地(GL客户端)的数据完全交给GPU(GL服务端)来管理,所以若非为了数据的更新,你完全可以在调用glBufferData之后选择扔弃保存在本地内存中的数据。VBO可以说只有在传输数据的时候跟本地客户端有联系,它的状态是服务端(我们的流水线)管理的,当初沿用VA的那些客户端状态函数,还有一个原因就是它们方便地与shader里面的固定attribute(gl_Position之类)建立联系【见[OpenGL与GLSL之间变量的传递小记]】,但是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之间变量的传递小记]),在这个GL3.0之后的时代里,这种前提也算不上什么前提就是了。我们来囫囵吞枣地猜测一下OpenGL是怎么处理VBO的数据的。

1. VBO

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

于是,VAO诞生了。

2.VAO

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

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

  1. structVertexAttribute
  2. {
  3. bool bIsEnabled =GL_FALSE;
  4. int iSize = 4; //This isthe 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. structVertexArrayObject
  13. {
  14. BufferObject *pElementArrayBufferObject =NULL;
  15. VertexAttributeattributes[GL_MAX_VERTEX_ATTRIB];
  16. }

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

那么,既然AB是一家,那就两个站都吧!哦,不对,两个O都用一用吧!

VBO与VAO 【转】的更多相关文章

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

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

  2. GLES2学习VBO和VAO的使用

    在GLES2中使用VBO和VAO对象,已经简单vs,ps绘制一个三角形. 1. 初始化操作代码,创建VBO.VAO,编译和链接shader program. void DebugApplication ...

  3. OpenGL图形渲染管线、VBO、VAO、EBO概念及用例

    图形渲染管线(Pipeline) 图形渲染管线指的是对一些原始数据经过一系列的处理变换并最终把这些数据输出到屏幕上的整个过程. 图形渲染管线的整个处理流程可以被划分为几个阶段,上一个阶段的输出数据作为 ...

  4. [转]OpenGL图形渲染管线、VBO、VAO、EBO概念及用例

    直接给出原文链接吧 1.OpenGL图形渲染管线.VBO.VAO.EBO概念及用例 2.OpenGL中glVertex.显示列表(glCallList).顶点数组(Vertex array).VBO及 ...

  5. VBO、VAO和EBO

    Vertex Buffer Object 对于经历过fixed pipeline的我来讲,VBO的出现对于渲染性能提升让人记忆深刻.完了,暴露年龄了~ //immediate mode glBegin ...

  6. Opengl ES之VBO和VAO

    前言 本文主要介绍了什么是VBO/VAO,为什么需要使用VBO/VAO以及如何使用VBO和VAO. VBO 什么是VBO VBO(vertex Buffer Object):顶点缓冲对象.是在显卡存储 ...

  7. openGL 提升渲染性能 之 顶点数组 VBO IBO VAO

    使用openGL图形库绘制,都需要通过openGL接口向图像显卡提交顶点数据,显卡根据提交的数据绘制出相应的图形. openGL绘制方式有:直接模式,显示列表,顶点数组,顶点索引. 直接模式:最简单, ...

  8. 【OpenGL4.0】GLSL渲染语言入门与VBO、VAO使用:绘制一个三角形 【转】

    http://blog.csdn.net/xiajun07061225/article/details/7628146 以前都是用Cg的,现在改用GLSL,又要重新学,不过两种语言很多都是相通的. 下 ...

  9. OpenGL渲染管道,Shader,VAO&VBO&EBO

    OpenGL渲染管线 (也就是)OpenGL渲染一帧图形的流程 以下列举最简单的,渲染一个三角形的流程,你可以将它视为 精简版OpenGL渲染管线 更复杂的流程也仅仅就是:在此基础上的各个流程中 添加 ...

随机推荐

  1. Location of ESXi 5.1 log files

    Purpose This article provides the default location of log files on an ESXi 5.1 host. For other produ ...

  2. urllib2.URLError: <ulropn error [Errno 10060] >

    在抓网页的时候,如果抓取频率很高,很容易出现这个错误: 意思是服务器拒绝响应.解决的方法为,隔段时间再试,不过这个方法不靠谱.靠谱的方法是增加一个延迟函数 import time time.sleep ...

  3. Sublime Text3 注册码激活码(持续更新中2018-11-20)

    Sublime Text 3的注册码 个人记录,便于查找 谢谢各位的认可 11.20版本 ----- BEGIN LICENSE ----- sgbteam Single User License E ...

  4. AGC007 - C Pushing Ball

    Description 题目链接 懒得写详细题意了, 放个链接 \(n\le 2*10^5\) 个球, \(n+1\) 个坑, 排成数轴, 球坑交替. 相邻球-坑距离为等差数列 \(d\). 给定首项 ...

  5. hdu1024 Max Sum Plus Plus的另一种解法

    传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1024 http://www.51nod.com/onlineJudge/questionCode.htm ...

  6. Js String 属性扩展

    String.prototype.startsWith = function (startStr) {  var d = startStr.length;  return (d >= 0 &am ...

  7. SPI总线介绍

    1. 简介 SPI, Serial Peripheral Interface, 串行外设接口, 是一种高速的.全双工.同步的通信总线SPI在芯片的管脚上只占用四根线 SPI接口主要用于MCU与各种外围 ...

  8. linux内核分析之进程地址空间【转】

    转自:http://blog.csdn.net/bullbat/article/details/7106094 版权声明:本文为博主原创文章,未经博主允许不得转载. 本文主要介绍linux内核中进程地 ...

  9. Vue全局异常捕获

    之前没注意过这么个小技巧 , 可能在Vue文档里也有  暂时先记下了 方便摘要 Vue全局配置 errorHandler可以进行全局错误收集,我们可以根据这个特性对前端异常做这样的处理:业务错误直接写 ...

  10. ipv6nginx错误

    400 Bad Request The plain HTTP request was sent to HTTPS port错误参考官方文档解决方法如下: server {listen 80;liste ...