现代OGL Shader中VAO,VBO和GL_ELEMENT_ARRAY_BUFFER之间的关系

本文主要参考自:http://www.zwqxin.com/archives/opengl/vao-and-vbo-stuff.html

前提和概念

老式的客户端的 VertexArrayObject是在客户端的,维护了指向的VBO以及如何解析VBO,还在客户端(CPU端)维护了一些渲染状态的设置,老式的VAO相关状态是在CPU端维护的。而现代的 GPU Shader中使用的VAO包括了VertexArrayObject 和 VertexAttributeObject这些索引数据都是在GPU显存里。VBO是显卡中数据块,没有格式声明。VAO是索引结构,用来指明渲染物体需要的VBO块存放的位置,块中的顶点属性如何解释,什么类型,偏移位置,大小等解析信息。如果没有使用VAO那么加载VBO时候也要说明顶点属性信息,默认启用的VAO标示符是0。
先看下 https://www.opengl.org/wiki/Vertex_Specification 中的描述:
A  Vertex Array Object  (VAO) is an  OpenGL Object that stores all of the state needed to supply vertex data (with one minor exception noted below). It stores the format of the vertex data as well as the Buffer Objects (see below) providing the vertex data arrays.  Note that VAOs do not copy, freeze or store the  contents  of the referenced buffers - if you change any of the data in the buffers referenced by an existing VAO, those changes will be seen by users of the VAO.

VAO记录的是一次绘制中所需要的信息,这包括“数据在哪里glBindBuffer”、“数据的格式是怎么样的glVertexAttribPointer”、shader-attribute的location的启用glEnableVertexAttribArray。

具体结构关系

struct VertexAttribute  
{  
    bool bIsEnabled = GL_FALSE;  
    int iSize = 4; //This is the number of elements in this attribute, 1-4.  
    unsigned int iStride = 0;  
    VertexAttribType eType = GL_FLOAT;  
    bool bIsNormalized = GL_FALSE;  
    bool bIsIntegral = GL_FALSE;  
    void * pBufferObjectOffset = 0;  
    BufferObject * pBufferObj = 0;  
};  

struct VertexArrayObject  
{  
    BufferObject *pElementArrayBufferObject = NULL;  
    VertexAttribute attributes[GL_MAX_VERTEX_ATTRIB];  
}  
 
    VAO首先关联了一个或多个VBO, 然后有一个大VertexAtrributeObject数组;VertexAtrributeObject记录了所属的VBO和在该VBO中的偏移,以及指定该VBO中该VertexAttribute顶点属性索引指向的顶点属性数据的格式,例如顶点类型,大小,偏移,是否开启法向量规范化等信息。
    关联的时候,因为一个opengl context下只要一个全局的VERTEX_ARRAY和GL_ARRAY_BUFFER,绑定指定的VAO,VBO到这些全局的指针下即可,后续的操作不再需要指定操作谁,例如:glVertexAttribPointer中的NULL,glDrawElements中的NULL。当然VBO数据如果更新了格式不对了,那么glEnableVertexAttribArray,glVertexAttribPointer需要重新绑定,VAO也要重新绑定。
    当draw call 从显卡中取得几何数据时候,根据VAO指向的 VertexAttribute数据,并行的取得单个顶点的完整数据(完整数据可能在不同的VBO中,每个VBO只取当前的偏移单位量),也就是根据 VertexAttribute数组来迭代循环处理了
glGenVertexArrays(1, &m_nQuadVAO);  
glBindVertexArray(m_nQuadVAO);  

glGenBuffers(1, &m_nQuadPositionVBO);  
glBindBuffer(GL_ARRAY_BUFFER, m_nQuadPositionVBO);  
glBufferData(GL_ARRAY_BUFFER, sizeof(fQuadPos), fQuadPos, GL_STREAM_DRAW);  

glEnableVertexAttribArray(VAT_POSITION);  
glVertexAttribPointer(VAT_POSITION, 2, GL_INT, GL_FALSE, 0, NULL);

glGenBuffers(1, &m_nQuadTexcoordVBO);  
glBindBuffer(GL_ARRAY_BUFFER, m_nQuadTexcoordVBO);  
glBufferData(GL_ARRAY_BUFFER, sizeof(fQuadTexcoord), fQuadTexcoord, GL_STREAM_DRAW);  

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

glGenBuffers(1, &m_nQuadIndexVBO);  
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nQuadIndexVBO);  
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(nQuadIndex), nQuadIndex, GL_STREAM_DRAW);  

glBindVertexArray(NULL);  
glBindBuffer(GL_ARRAY_BUFFER, NULL);  
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL); 
渲染时候:
glBindVertexArray(m_nQuadVAO);  
// 使用了VAO更多使用 glDrawArrays,因为提供了一个渲染数组,
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);  
glBindVertexArray(NULL); 

所以 VAO只是记录了指向的各个VBO,以及如何解析这些VBO数据块中的数据,draw call时候根据VAO索引并解析数据即可。

注意:

(1)完整的记录VAO关联的VBO
VBO 和 vertex attribute 的具体绑定是在 glVertexAttribPointer 中完成的
glBindBuffer(GL_ARRAY_BUFFER, NULL)/glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL)一定要在glBindVertexArray(NULL)后面(不然VAO就把它们也包含了,最后就渲染不出东西了)。
GL_ELEMENT_ARRAY_BUFFER索引缓存区中的数据,VAO没有直接记录,但是通过VAO关联的GL_ARRAY_BUFFER,GL_ARRAY_BUFFER关联到GL_ELEMENT_ARRAY_BUFFER,渲染时候通过glBindVertexArray可以间接索引到。
(2)VAO关联的VBO在处理状态和数据上的区别
glBindVertexArray(NULL)时候所有该VAO相关的状态都关掉了,所以不需要glDisableVertexAttribArray了。当再次display时候,glBindVertexArray导致VAO开启的存放在显存中的VAO状态被重新激活,但是VAO指向的VBO标示符,VBO存放的glBufferData数据却是没有重新加载的,因为glBufferData在CPU中存放的数据在第一次加载后,都直接丢弃了。
(3)如果VBO中数据用glBufferSubData更新了,格式改变了那么需要重新绑定VAO,VBO,GL_ELEMENT_ARRAY_BUFFER索引缓存数据。
(4)不需要VAO了,删除VAO只是删除索引结构,VBO中的数据还是在其中,除非显式删除VBO数据块。

问题回答

问题:正常的opengl vao操作是 在bind 和 unbind之间,去初始化及绑定一系列相关的vbo,那么最后,opengl在显存里记录的到底是什么数据,这些vbo?还是类似于显示列表,记录了这之间的操作?比如,我在vao 的bind 和 unbind之间,调用 glGenBuffer(),是不是每次绑定该vao时都会重新 gen 一次buffer
回答:
前提:下面说的都是现代的基于Shader下的VAO,它包括了VertexArrayObject和VertexAttribute, 而非老版的CPU端的需要glEnableClientState的VAO(VertexArrayObject)。
第一个问题:显存里记录的是VBO图形数据块,和VAO索引数据。关于VAO索引数据结构
见: Vertex Specification
结构如下:
struct VertexAttribute 

bool bIsEnabled = GL_FALSE; 
int iSize = 4; //This is the number of elements in this attribute, 1-4. 
unsigned int iStride = 0; 
VertexAttribType eType = GL_FLOAT; // 顶点属性类型
bool bIsNormalized = GL_FALSE; 
bool bIsIntegral = GL_FALSE; 
void * pBufferObjectOffset = 0; // 关联的VBO下的偏移
BufferObject * pBufferObj = 0; // 关联的VBO
}; 
struct VertexArrayObject 

BufferObject *pElementArrayBufferObject = NULL; 
VertexAttribute attributes[GL_MAX_VERTEX_ATTRIB]; 

第二个问题:不是类似显示列表,显示列表不仅记录了数据(而且这些数据不再需要解析格式),还记录了draw call渲染命令,放置在显存中躺着,激活时候就是直接绘制。
而VBO, VAO显存里记录的是VBO图形数据块,和VAO索引数据,并没有你说的之间的操作draw call调用。VAO draw call时glBindVertexArray 的时候将躺在显存中的该VAO数组索引结构激活,将关联的VBO数据按照VAO索引结构来取得数据(shader取得顶点时候是并行独立的取得完整一个顶点信息的,顶点信息内部各个属性pos,uv,color,等可能在不同的VBO,那么按照根据VertexAttribute 数组索引,迭代循环取得顶点数据即可)。
第三个问题:
每次绑定该vao时都会重新 gen 一次buffer?不会的。但VAO索引结构中记录的状态设置却是会重新激活的,关联的VBO数据却是没有重新申请标示符,申请和填充数据块的。因为glBufferData在CPU中存放的数据在第一次加载后,都直接丢弃了。见: https://www.opengl.org/sdk/docs/man/html/glBindVertexArray.xhtml
glBindVertexArray binds the vertex array object with name  arrayarray is the name of a vertex array object previously returned from a call to glGenVertexArrays。If the bind is successful no change is made to the state of the vertex array object, and any previous vertex array object binding is broken.绑定只是设置当前VAO是该VAO和激活VAO相关的状态设置,例如:glEnableVertexAttribArray状态。但是不会重新生成VBO和申请填充VBO中的数据块。


你可能感兴趣的:(现代OGL Shader中VAO,VBO和GL_ELEMENT_ARRAY_BUFFER之间的关系)