VAO与VBO

来源:互联网 发布:网络发国际传真 编辑:程序博客网 时间:2024/06/06 10:00

部分内容摘自: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

[cpp] view plain copy
  1. glGenVertexArrays(1,vao);  
  2. 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.示例代码


[cpp] view plain copy
  1. void InitializeVertexBuffer()  
  2. {  
  3. glGenBuffers(1,%vertexBufferObject);  
  4.   
  5. glBindBuffer(GL_ARRAY_BUFFER,vertexBufferObject);  
  6. glBufferData(GL_ARRAY_BUFFER,sizeof(vertexData),vertexData,GL_STATIC_DRAW);  
  7. glBindBuffer(GL_ARRAY_BUFFER,0);  
  8.   
  9. glGenBuffers(1,&indexBufferObject);  
  10.   
  11. glBindBuffer(GL_ARRAY_BUFFER,indexBufferObject);  
  12. glBufferData(GL_ARRAY_BUFFER,sizeof(indexData),indexData,GL_STATIC_DRAW);  
  13. glBindBuffer(GL_ARRAY_BUFFER,0);  
}


[cpp] view plain copy
  1.   <pre name="code" class="cpp"void init()  
  2. {  
  3. InitializeVertexBuffer();  
  4.   
  5. size_t colorDataOffset = sizeof(float)*3*numberOfVertices;  
  6. glBindBuffer(GL_ARRAY_BUFFER,vertexBufferObject);  
  7.   
  8. glGenVertexArrays(1,&vao);  
  9. glBindVertexArray(vao);  
  10. glEnableVertexAttribArray(0);  
  11. glEnableVertexAttribArray(1);  
  12. glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,0);  
  13. <pre name="code" class="cpp">glVertexAttribPointer(1,4,GL_FLOAT,GL_FALSE,0,(void*)colorDataOffset);  
  14. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,indexBufferObject);  
  15. 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的渲染代码

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

(2) 使用vao的代码

[cpp] view plain copy
  1. //初始化代码  
  2. glGenVertexArrays(1, &m_nQuadVAO)  

[cpp] view plain copy
  1. <pre name="code" class="cpp">//渲染代码  
  2. glBindVertexArray(m_nQuadVAO);    
  3. glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);    
  4. 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并传递数据

[cpp] view plain copy
  1. glGenBuffers(1,&VBO);  
  2. glBindBuffer(GL_ARRAY_BUFFER,VBO);  
  3. glBufferData(GL_ARRAY_BUFFER,sizeof(vertex_position),vertex_position,GL_STATIC_DRAW);  
  4. glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,NULL);  
  5. 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()函数中调用:

[cpp] view plain copy
  1. glGenVertexArray(1,&vao)  
  2. glBindVertexArray(vao);  
  3.   
  4. glGenBuffers(1,&vbo);  
  5. glBindBuffer(GL_ARRAY_BUFFER,vbo);  
  6. glBufferData(GL_ARRAY_BUFFER,sizeof(vertexPositions),vertexPositions,GL_STATIC_DRAW);  
  7. glBindBuffer(GL_ARRAY_BUFFER,0);  

2.在display()中调用:

[cpp] view plain copy
  1. glBindBuffer(GL_ARRAY_BUFFER,vbo);  
  2. glEnableVertexAttribArray(0);  
  3. glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);  
  4.   
  5. glDrawArrays(GL_TRIANGLES,0,3);  

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


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

[cpp] view plain copy
  1. glBindBuffer(GL_ARRAY_BUFFER,vbo);  
  2. glEnableVertexAttribArray(0);  
  3. glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);  
  4.   
  5. glBindBuffer(GL_ARRAY_BUFFER,0);  
  6. glDrawArrays(GL_TRIANGLES,0,3);  

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

[cpp] view plain copy
  1. glEnableVertexAttribArray(0);  
  2. glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);  

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

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


原创粉丝点击