部分内容摘自: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
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 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的渲染代码
(2) 使用vao的代码
可以发现使用了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并传递数据
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() 怎么知道要指定的格式的数据从哪里来?
三.示例代码分析
1.在init()函数中调用:
2.在display()中调用:
上面的代码中并没有使用vao,只是在init中创建vbo然后分配空间赋值,最后一句接触绑定. 在display中重新激活vbo然后指定了vbo中数据的格式和着色器程序中变量的关联,最后绘制图形.
3.将display()改为如下的调用.
虽然我们在绘制函数glDrawArrays之前关闭了当前激活的vbo对象.此时图形任然能正确绘制,于是我们可以知道:
这两句有关键性作用,他们会使用在它们之前最近激活的vbo,并获取其中的数据位置使数据和着色器之间进行关联,以便传输数据.并且指定了vbo中无规则数据的格式。此后我们
就算关闭vbo也不能影响正确的数据传输,用于图形的绘制.