前言
首先感谢知乎的一篇文章,解答了我的疑惑,在此记录一下心得。
那么我们先简单介绍一下这三个对象,以及一些必要操作。
VBO
VBO,全称Vertex Buffer Object,中文名称:顶点缓冲对象。我们利用这个对象来管理显存,如果你想往显卡上发送数据,就需要用到这个对象,大体流程如下:
链接顶点属性
对应的函数名称:glVertexAttribPointer。这个函数用来干嘛呢?我们利用VBO向显卡传送数据,但是没有告诉显卡怎么处理这些数据,这个函数就是用来干这件事的。通过调用glVertexAttribPointer显卡知道如何处理数据了,我们还需要调用glEnableVertexAttribArray(Index)告诉显卡索引为Index的顶点属性现在已经启用了。那么现在流程变为:
EBO(IBO)
EBO或者IBO,全称Element(Index) Buffer Object,中文名称:索引缓冲对象。虽然利用之前的对象和方法就可绘制了,但是如果使用指定每个三角面片的坐标这种方法构造数据时,显然会有很多重复出现的点,造成空间、时间浪费。EBO就是用来解决这个问题的,我们可以一次性提供模型的所有顶点信息(无重复项),然后指定一个索引数组,其内存储要绘制的物体坐标在顶点数组内的索引。这样就可以减少重复数据所占据的空间了。
VAO
VAO,全称Vertex Array Object,中文名称:顶点数组对象。上面提到的绘制方法复用性太差了,每次绘制我们都要绑定VBO、EBO并链接顶点属性,有没有办法简化这些操作呢?利用VAO就可以做到。简而言之,它是用来存储其它对象的状态的,只要我们在初始化时设置好了VAO,在绘制时就可以直接绑定对应的VAO而无需上述的繁琐操作,极大的增加了复用性。
注意:上图没有给出绑定EBO的操作,我们可以在绑定VBO之后绑定EBO。
绑定顺序
如前言中所说,先绑定VAO,然后绑定VBO和EBO,再链接顶点属性指针即可。
解绑顺序
这个是比较迷惑的一点,我们在绑定VBO并链接顶点属性指针之后,即可解绑该VBO,但是如果解绑EBO的操作一定要放到解绑VAO的操作之后。也就是说解绑顺序是:VBO(注意要先链接顶点属性指针)、VAO、EBO。这是为什么呢?和VAO的具体实现有很大的关系,我在知乎上找到了一个回答的很好的文章,可以看一下VAO的实现(伪代码,原理应该相似)。
作者:「已注销」
链接:https://www.zhihu.com/question/39082624/answer/79638826
来源:知乎
struct VertexAttributeState
{
bool bIsEnabled = false;
int iSize = 4; //This is the number of elements in each attrib, 1-4.
unsigned int iStride = 0;
VertexAttribType eType = GL_FLOAT;
bool bIsNormalized = false;
bool bIsIntegral = false;
void * pPtrOrBufferObjectOffset = 0;
BufferObject * pBufferObj = 0;
};
struct VertexArrayObjectState
{
BufferObject *pElementArrayBufferObject = NULL;
VertexAttributeState attributes[MAX_VERTEX_ATTRIB];
}
static VertexArrayObjectState *pContextVAOState = new VertexArrayObjectState();
static BufferObject *pCurrentArrayBuffer = NULL;
//绑定缓冲对象 如VBO EBO等
void glBindBuffer(enum target, uint buffer)
{
BufferObject *pBuffer = ConvNameToBufferObj(buffer);
switch(target)
{
//绑定VBO 先用一个临时变量存储
case GL_ARRAY_BUFFER:
pCurrentArrayBuffer = pBuffer;
break;
//绑定EBO 直接更新
case GL_ELEMENT_ARRAY_BUFFER:
pContextVAOState->pElementArrayBufferObject = pBuffer;
break;
...
}
}
//禁用属性
void glEnableVertexAttribArray(uint index)
{
pContextVAOState->attributes[index].bIsEnabled = true;
}
//启用属性
void glDisableVertexAttribArray(uint index)
{
pContextVAOState->attributes[index].bIsEnabled = false;
}
//链接顶点属性指针
void glVertexAttribPointer(uint index, int size, enum type, boolean normalized, sizei stride, const void *pointer)
{
VertexAttributeState &currAttrib = pContextVAOState->attributes[index];
currAttrib.iSize = size;
currAttrib.eType = type;
currAttrib.iStride = stride;
currAttrib.bIsNormalized = normalized;
currAttrib.bIsIntegral = true;
currAttrib.pPtrOrBufferObjectOffset = pointer;
//此时才会保存VBO!!
currAttrib.pBufferObj = pCurrentArrayBuffer;
}
代码不长,阅读后我们可以发现VAO的奥妙。其实它存储的是一组VertexAttributeState和一个ElementArrayBufferObject。也就是说根据需要,我们可以为一个VAO绑定多个不同的VBO并设置对应的顶点属性,但是一个VAO只能有一个EBO。所以如果在解绑VAO之前解绑了EBO,那么该VAO存储的EBO就无了,自然绘制不出东西。而且这段代码也体现了VBO和顶点属性指针的关系,在绑定VBO后,一定要设置好顶点属性指针,不然就等于白给呀。
最后贴几张回复贴吧,感觉有点意思。