Opengl的VBO和VAO的概要详细说明什么是VBO,什么是VBO,什么是VAO,什么是VAO,VAO的概要
前言
关于Opengl的VBO和VAO,我想很多人都很熟悉这两个名字,但有时很容易混淆或者不理解这两个概念。 我对Opengl的理解也很浅,所以专门学习了这两个概念。 在这里记录下来,便于以后查看。 此处使用的Opengl版本为3.3版本,某些api可能会在较旧版本的Opengl中报告错误。
VBO VBO全名顶点缓冲对象(Vertex Buffer Object)是什么? 他的主要作用是可以一次性将大量的顶点数据发送到显卡,而不是逐顶点。 我知道CPU将数据传输到GPU其实很花时间,所以通过尽可能一次将所有需要的顶点数据传输到GPU,顶点着色器几乎可以立即访问顶点,从而有助于提高顶点着色器的效率。
如何创建VBO,现在所有的游戏引擎都以VBO机制为基础,让我们来看看VBO是如何在Opengl中创建的。
首先,需要在Opengl中生成缓冲区类型的ID。
unsigned int VBO; Lgenbuffers(1,VBO ); 这里创建了缓冲区的ID。 当然,也可以从数组中批量创建一系列VBO的ID
无信号输入vbo [3]; Lgenbuffers(3,VBO ); 然后,将此ID绑定到指定的类型,并告诉Opengl此缓冲区是什么类型;
lbindbuffer(GL_Array_buffer,VBO ); 在此,将VBO赋予GL_ARRAY_BUFFER的模型,并告诉Opengl该VBO变量是顶点缓冲对象。
请注意,从现在开始,在GL_ARRAY_BUFFER目标中使用的所有缓冲区调用都将用于配置当前绑定的缓冲区(VBO )
对VBO的声明到此结束。 其实很简单。 让我们回顾一下。
1 )生成缓冲区类型的标识;
2 )给定ID的缓冲区类型为GL_ARRAY_BUFFER;
接下来,我们来看看如何为VBO赋值。 假设有以下三角形的顶点数据。
float vertices[]={//位置//颜色0.5f、-0.5f、-0.0f、0.0f、/右下-0.5f、-0.5f、0.0f、1.0f、0.0f 对于上面三角形的顶点数据,代码应为:
lbufferdata(GL_array_buffer,sizeof ) vertices )、vertices、GL_STATIC_DRAW ); 第一个参数表示目标的缓冲区类型,其中表示当前绑定到GL_ARRAY_BUFFER的顶点缓冲区对象,第二个参数以字节为单位表示数据大小,第三个参数实际发出第四个参数GL_STATIC_DRAW表示Opengl如何处理上载的数据。 Opengl有以下三种类型:
GL_STATIC_DRAW :数据保持不变或几乎不变。 GL_DYNAMIC_DRAW :数据会有很大的变化。 GL_STREAM_DRAW :数据在每次绘制时都会改变。
在这个阶段,我们的数据已经上传到GPU了。 VBO过程也结束了。
现在提出问题,我们看到vertices有注释,表示这个数组包含两种类型的数据。 一个是顶点坐标,另一个是顶点颜色。 那个都被放入float的排列中。 Opengl如何区分他们呢? 这里使用一个名为glVertexAttribPointer的界面来帮助Opengl处理数据。 让我们来看看对vertices进行数据处理的代码。 //位置属性glvertexattribpointer (0,3,GL_FLOAT,GL_FALSE,6*sizeof ) float,) void* )0); glenablevertexattribarray(0; //颜色属性glvertexattribpointer (1,3,GL_FLOAT,GL_FALSE,6*sizeof ) float )、void* )3*sizeof ) float ) glenat 分析此代码,首先说明glVertexAttribPointer各参数的含义。
第一个参数指示将数据的哪些部分放置在相应的位置。 这可能主要出现在shader的部分。 让我们看一下顶点着色器的代码。 # version 330核心布局(location=0) in vec3 aPos; layout(location=1) in vec3 aColor; 根据变量名可以推测出location大致为0的是顶点坐标,location大致为1的是色值。 此处的location与上述代码类型glVe相对应
rtexAttribPointer第一个参数,location没有准确的位置定义,并没有说location为0的一定要是顶点坐标属性,也可以是颜色或者uv坐标属性,只是常规来说,我们习惯将坐标放在第一个位置。
glVertexAttribPointer第二个参数表示属性大小,坐标和颜色的大小都是3,所以这里填3;第三个参数表示数据类型;第四个参数表示我们是否希望数据标准化。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE;第五个参数表示步长,数据之间的间隔,我们这里不管坐标还是颜色都是3个分量,所以坐标和坐标数据之间隔了6个float字节,颜色和颜色之间隔了6个float字节;第六个参数表示偏移量,即在一段数据中,指定的数据偏移多少位置开始。在这里,坐标数据都是每段数据的起始位置,所以偏移量是0,而颜色数据在坐标数据之后,坐标数据有3个分量,所以每个颜色数据偏移三个float字节开始算;
每次设定好一个location的值之后,记得要开启对应的位置数据glEnableVertexAttribArray,因为Opengl默认是全关闭的。
经过glVertexAttribPointer执行之后,shader中对应layout的位置数据就有对应的值了。 什么VAO
VAO全名顶点数组对象(Vertex Array Object),每当我们绘制一个物体的时候都必须重复这一过程。这看起来可能不多,但是如果有超过5个顶点属性,上百个不同物体呢(这其实并不罕见)。绑定正确的缓冲对象,为每个物体配置所有顶点属性很快就变成一件麻烦事。VAO的作用就是顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。简单来说有点类似复用的概念。
如何执行VAO
VAO的生成和绑定都和VBO很类似;
unsigned int VAO;glGenVertexArrays(1, &VAO);glBindVertexArray(VAO);
其过程也是先从生成一个ID开始,然后将ID绑定到顶点数组对象上。任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。概括来说就是从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO供之后使用。
一个顶点数组对象会储存以下这些内容:
glEnableVertexAttribArray和glDisableVertexAttribArray的调用。通过glVertexAttribPointer设置的顶点属性配置。通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。
我们来举个例子,我们还是使用上面三角形数据:
float vertices[] = {// 位置 // 颜色0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 顶部};
借用GLM的数学库,我们生成2个位置向量:
glm::vec3 traPositions[] = {glm::vec3(0.0f, 0.0f, 0.0f),glm::vec3(0.5f, 0.5f, 0.5f),};
生成VBO和VAO的ID:
unsigned int VBO, VAO;glGenVertexArrays(1, &VAO);glBindVertexArray(VAO);glGenBuffers(1, &VBO);glBindBuffer(GL_ARRAY_BUFFER, VBO);
复制顶点数据到缓冲:
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
分别处理位置和颜色的数据:
// 位置属性glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// 颜色属性glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);glBindBuffer(GL_ARRAY_BUFFER, 0);//解除VAO绑定glBindVertexArray(0);
生成一个model的矩阵变量(这个model矩阵做用就是model->world的转变矩阵)
glm::mat4 model = glm::mat4(1.0f);
创建model是方便做测试,在世界坐标中绘制不同位置的三角形,其在顶点着色器的作用如下代码:
#version 330 corelayout (location = 0) in vec3 aPos;layout (location = 1) in vec3 aColor;out vec3 ourColor;uniform mat4 model;void main(){ gl_Position = model*vec4(aPos, 1.0); ourColor = aColor;}
我们的片段着色器很简单,仅仅是把传入的颜色直接输出:
#version 330 coreout vec4 FragColor;in vec3 ourColor;void main(){ FragColor = vec4(ourColor, 1.0f);}
好,至此准备工作都完成了,我们开始做渲染:
while (!glfwWindowShouldClose(window)){…glBindVertexArray(VAO);for (int i = 0; i < 2; i++){glm::mat4 model = glm::mat4(1.0f);model = glm::translate(model, traPositions[i]);outShader.setMat4(“model”, model);glDrawArrays(GL_TRIANGLES, 0, 3);}…}
运行结果就是:
我放出了关键代码,其他代码不是我们关注的重点。我们看到glBindVertexArray(VAO)这个方法之后,代码逻辑就是直接循环绘制了2个三角形,我们注意到这其中我们不需要重复创建VBO,重复设定顶点数据,只需要绑定我们需要绘制的VAO对象,我们就能绘制任意数量的图形,这就是VAO带来的重复绘制的高效性。
总结
对于VBO/VAO的讲解基本差不多了,基于我本人对opengl的认识程度也不深,如果有理解不到位或者有误的地方,还望各位看官不吝赐教!
[参考]:
https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/