VAO与VBO

部分内容摘自:http://www.zwqxin.com/archives/opengl/vao-and-vbo-stuff.html

一.VAO(Vertex Array Object)


1.VAO对象是什么

     VAO的全名是Vertex Array Object,首先,它不是Buffer-Object,所以不用作存储数据;其次,它针对”顶点“而言,也就是说它跟”顶点的绘制“息息相关,在GL3.0的世界观里,这相当于”与VBO息息相关“。

      按上所述,它的定位是state-object(状态对象,记录存储状态信息)。这明显区别于buffer-object。VAO记录的是一次绘制中做需要的信息,这包括”数据在哪里-glBindBuffer(GL_ARRAY_BUFFER)“、”数据的格式是怎样的-glVertexAttribPointer“(顶点位置的数据在哪里,顶点位置的数据的格式是怎样的/纹理坐标的数据在哪里,纹理坐标的数据的格式是怎样的....视乎你让它关联多少个VBO、VBO里有多少种数据),顺带一提的是,这里的状态还包括这些属性关联的shader-attribute的location的启用(glEnableVertexAttribArray)、这些顶点属性对应的顶点索引数据的位置(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER),如果你指定了的话)。

      所以综上所述,VAO相当于保存了顶点相关的各种信息,记录了各种状态。当我们要修改它的状态时,我们先激活它(glBindVertexArray())。当我们要使用其中的状态时,如我们要绘图,我们也先激活它,然后调用相关的函数。


2.创建VAO

glGenVertexArrays(1,vao);
glBindVertexArray(vao[0]);

3.解释代码

(1) void glGenVertexArrays ( GLSizei n , GLuint *arrays );

作用:产生n个VAO对象的名字,相当于句柄一样,不过此时是没有分配内存空间的。

n: 要产生VAO对象的个数。

arrays: 储存返回的vao对象的名字。



(2) void glBindVertexArray ( GLuint array );

作用: 

(1)当传递的参数是非0且是glGenVertexArray()第一次返回的,此时会将传递的参数名字绑定到新创建的顶点数组对象。

(2)当传递的参数是已经被绑定过的对象名称,则此时的作用是激活顶点数组对象,后续的相关操作将作用到该顶点数组对象。

(3)当传递的参数是0,则此时是解除先前的绑定。

array: 指明了顶点数组的名字。


4.影响vao状态的函数

(1) glVertexAttribPointer()

(2) glEnableVertexAttribArray()

(3) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER);

注: glBindBuffer(GL_ARRAY_BUFFER,?) 并不会影响vao的状态。因为关联缓冲区对象(GL_ARRAY_BUFFER类型)和顶点属性是通过glVertexAttribPointer函数.当我们调用

glVeretexAttribPointer函数,OpenGL获取此时绑定到GL_ARRAY_BUFFER的缓冲区对象然后关联它到glVeretexAttribPointer指定的顶点属性(相当于确定数据来源和数据的输出)。所以在调用glVeretexAttribPointer之后我们可以调用:glBindBuffer(GL_ARRAY_BUFFER,0) 来解除绑定,此时也不会影响图形的绘制。所以:VAO并不记录GL_ARRAY_BUFFER绑定点的状态。


5.示例代码


void InitializeVertexBuffer()
{
glGenBuffers(1,%vertexBufferObject);

glBindBuffer(GL_ARRAY_BUFFER,vertexBufferObject);
glBufferData(GL_ARRAY_BUFFER,sizeof(vertexData),vertexData,GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER,0);

glGenBuffers(1,&indexBufferObject);

glBindBuffer(GL_ARRAY_BUFFER,indexBufferObject);
glBufferData(GL_ARRAY_BUFFER,sizeof(indexData),indexData,GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER,0);
}


  <pre name="code" class="cpp"> void init()
{
InitializeVertexBuffer();

size_t colorDataOffset = sizeof(float)*3*numberOfVertices;
glBindBuffer(GL_ARRAY_BUFFER,vertexBufferObject);

glGenVertexArrays(1,&vao);
glBindVertexArray(vao);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,0);
<pre name="code" class="cpp">glVertexAttribPointer(1,4,GL_FLOAT,GL_FALSE,0,(void*)colorDataOffset);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,indexBufferObject);
glBindVertexArray(0);
}

void display()
{
.....省略
glBindVertexArray(vao);

glDrawElements(GL_TRIANGLES,ARRAY_COUNT(indexData),GL_UNSIGNED_SHOR,0);
.....省略
}
 

  可以从上面的init代码中看到,我们的调用glBindVertexArray激活后的VAO对象并没有记录 glBindBuffer(GL_ARRAY_BUFFER,vertexBufferObject); 但是任然能正确绘制出图形。

如果:如果我们调用glBindVertexArray激活的vao的代码放在 glEnableVertexAttribArray() , glVertexAttribPointer,glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,...) 之后

此时在display()函数中,使用vao中记录的状态来绘制图形,就会不成功。所以这几个函数会影响vao中的状态。


6.使用vao的好处

(1) 不适用vao的渲染代码

   1.  glBindBuffer(GL_ARRAY_BUFFER, m_nQuadPositionVBO);  
   2. glEnableVertexAttribArray(VAT_POSITION);  
   3. glVertexAttribPointer(VAT_POSITION, 2, GL_INT, GL_FALSE, 0, NULL);  
   4.   
   5. glBindBuffer(GL_ARRAY_BUFFER, m_nQuadTexcoordVBO);  
   6. glEnableVertexAttribArray(VAT_TEXCOORD);  
   7. glVertexAttribPointer(VAT_TEXCOORD, 2, GL_INT, GL_FALSE, 0, NULL);  
   8.   
   9. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nQuadIndexVBO);  
  10.   
  11. glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);  
  12.   
  13. glDisableVertexAttribArray(VAT_POSITION);  
  14. glDisableVertexAttribArray(VAT_TEXCOORD);  
  15.   
  16. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);  
  17. glBindBuffer(GL_ARRAY_BUFFER, NULL);  


(2) 使用vao的代码


//初始化代码
glGenVertexArrays(1, &m_nQuadVAO)


<pre name="code" class="cpp">//渲染代码
glBindVertexArray(m_nQuadVAO);  
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);  
glBindVertexArray(NULL);  

 

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

于是,VAO诞生了。




二.VBO(Vertex Buffer Object)


1.VBO是什么

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


2.创建VBO并传递数据

glGenBuffers(1,&VBO);
glBindBuffer(GL_ARRAY_BUFFER,VBO);
glBufferData(GL_ARRAY_BUFFER,sizeof(vertex_position),vertex_position,GL_STATIC_DRAW);
glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,NULL);
glEnableVertexAttribArray(0);


3.解释代码

void glGenBuffers( GLsizei n , GLuint *buffers);

作用:创建了一个缓冲区对象的名字, 相当于一个句柄,此时对象并分配。

n: 产生多少个缓冲区名字。

buffers: 储存返回的缓冲区名字。


void glBindBuffer( GLenum target , GLuint buffer);

作用:创建一个绑定到target上的缓冲区对象。

target: 创建缓冲区对象的类型。

buffer: glGenBuffers返回的对象的名称。


void glBufferData( GLenum target, GLsizeiptr size,  const GLvoid * data,  GLenum usage);

作用:在opengl的显存中分配size个字节的显存空间,然后将data中的数据拷贝到刚才所分配的显存空间中区。这时的数据储存到的缓冲区对象是最近调用glBindBuffer()激活的缓冲区对象,注:此时的参数target要与 glBindBuffer()中的target匹配哦。

参数含义:查文档。


void glVertexAttribPointer( GLuint index,  GLint size, GLenum type, GLboolean normalized, GLsizei stride,   const GLvoid * pointer);

作用:

(1)我们调用glBufferData所传递的只是一些毫无意义的数据,我们要告诉opengl里面所储存数据的格式。glVertexAttribPointer()就是完成这个工作的函数。这是作用之一。

(2)我们使用着色器来进行编程,在顶点着色器阶段。我们使用这个函数来给着色器中的in 类型的属性变量传递数据。是怎么和着色器中变量联系起来的呢?glVertexAttribPointer()这个函数的第一个参数index,就是指明了着色器程序中变量的下标的作用。我们在着色器程序中可以这样写:

layout( location=index ) in vec4 position;        如果这个index和glVertexAttribPointer 的第一个参数一样,那么相关缓冲区的数据就会传递到这个position变量中去。


4.疑问

(1)glVertexAttribPointer() 怎么知道要指定的格式的数据从哪里来?

答:因为在调用glVertexAttribPointer()之前,首先调用了glBindVertexArray(),它所产生的vao会记录下相关的数据来源。

三.示例代码分析


1.在init()函数中调用:

glGenVertexArray(1,&vao)
glBindVertexArray(vao);

glGenBuffers(1,&vbo);
glBindBuffer(GL_ARRAY_BUFFER,vbo);
glBufferData(GL_ARRAY_BUFFER,sizeof(vertexPositions),vertexPositions,GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER,0);

2.在display()中调用:

glBindBuffer(GL_ARRAY_BUFFER,vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);

glDrawArrays(GL_TRIANGLES,0,3);

上面的代码中并没有使用vao,只是在init中创建vbo然后分配空间赋值,最后一句接触绑定. 在display中重新激活vbo然后指定了vbo中数据的格式和着色器程序中变量的关联,最后绘制图形.


3.将display()改为如下的调用.

glBindBuffer(GL_ARRAY_BUFFER,vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);

glBindBuffer(GL_ARRAY_BUFFER,0);
glDrawArrays(GL_TRIANGLES,0,3);

虽然我们在绘制函数glDrawArrays之前关闭了当前激活的vbo对象.此时图形任然能正确绘制,于是我们可以知道:

glEnableVertexAttribArray(0);
glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);

这两句有关键性作用,他们会使用在它们之前最近激活的vbo,并获取其中的数据位置使数据和着色器之间进行关联,以便传输数据.并且指定了vbo中无规则数据的格式。此后我们

就算关闭vbo也不能影响正确的数据传输,用于图形的绘制.







  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值