最近在学习OpenGL的时候,看到了一篇关于讲解VAO和VBO比较好的文章,这里做一下搬运和改良。

原文地址:AB是一家?VAO与VBO

VBO

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

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

这里是VBO的初始化阶段。在这里我们看到了这是对位置,还是颜色,还是纹理坐标,还是法线,还是其他顶点属性进行设置的吗?是的,这个信息是:起码在初始化阶段,一个VBO对于交给它存储的数据到底是什么,完全不知道。这些信息都是在渲染的时候确定的。

以下是渲染代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
glBindBuffer(GL_ARRAY_BUFFER, PositionVBO);  
glEnableVertexAttribArray(VAT_POSITION);
glVertexAttribPointer(VAT_POSITION, 2, GL_INT, GL_FALSE, 0, NULL);

glBindBuffer(GL_ARRAY_BUFFER, TexcoordVBO);
glEnableVertexAttribArray(VAT_TEXCOORD);
glVertexAttribPointer(VAT_TEXCOORD, 2, GL_INT, GL_FALSE, 0, NULL);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexVBO);

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);

glDisableVertexAttribArray(VAT_POSITION);
glDisableVertexAttribArray(VAT_TEXCOORD);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);
glBindBuffer(GL_ARRAY_BUFFER, NULL);

在这段渲染代码中,glVertexAttribPointer(VAT_POSITION, 2, GL_INT, GL_FALSE, 0, NULL) 函数指定了VBO里的是什么数据——顶点位置,int类型,2个int指定一个顶点位置,在区域里无偏移的采集数据等等。

之后的glDrawElements只不过根据组织模式(GL_TRIANGLES)和索引数据去采集VBO里的这些数据罢了,具体的表现是,它从GL-context获取了glBindBuffer指定的位置等信息,进行绘制。

总的来说,VBO在渲染阶段才指定数据位置和“顶点信息”(Vertex Specification),然后根据此信息去解析缓存区里的数据,联系这两者中间的桥梁是GL-Contenxt。

GL-context整个程序一般只有一个,所以如果一个渲染流程里有两份不同的绘制代码,GL-context就负责在它们之间进行状态切换。这也是为什么要在渲染过程中,在每份绘制代码之中有glBindBuffer/glEnableVertexAttribArray/glVertexAttribPointer。

那么优化方法就来了——把这些都放到初始化时候完成吧!——这样做的限制条件是“负责记录状态的GL-context整个程序一般只有一个”,那么就不直接用GL-context记录,用别的东西做状态记录吧——这个东西针对"每份绘制代码“有一个,记录该次绘制所需要的所有VBO所需信息,把它保存到GPU特定位置,绘制的时候直接在这个位置取信息绘制。

VAO

VAO的全名是Vertex Array Object,首先,它不是Buffer-Object,所以不用作存储数据;其次,它针对”顶点“而言,也就是说它跟”顶点的绘制“息息相关。

按上所述,它的定位是state-object(状态对象,记录存储状态信息)。这明显区别于buffer-object。VAO记录的是一次绘制中做需要的信息,这包括

  • ”数据在哪里-glBindBuffer(GL_ARRAY_BUFFER)“、

  • ”数据的格式是怎样的-glVertexAttribPointer“(顶点位置的数据在哪里,顶点位置的数据的格式是怎样的/纹理坐标的数据在哪里,纹理坐标的数据的格式是怎样的…视乎你让它关联多少个VBO、VBO里有多少种数据)

    顺带一提的是,这里的状态还包括这些属性关联的shader-attribute的location的启用(glEnableVertexAttribArray)、这些顶点属性对应的顶点索引数据的位置(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER),如果你指定了的话)。

使用VAO可以使渲染的部分变得简单。