是人都能懂,通俗解释VBO和VAO在OpenGL中的作用

在这里插入图片描述
一切都是从顶点数据开始的。
OpenGl只有当坐标在[-1,1]的范围内才处理它,所以我们传入的顶点数据要在这个范围之内。
我们定义一个double数组:

float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

这一段数组其实有点让人莫名其妙,因为他完全可以写成:

float vertices[] = {
    -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f,  0.5f, 0.0f
};

并不是什么三维数组,就是普普通通的C语言里的一维数组。那怎么让程序通过这个一维数组知道他要在三维空间中画三个顶点呢?先不管这个,当下的任务是先将这个数组传递给GPU,作为顶点着色器的输入。顶点着色器接着就会处理GPU内存中的各个顶点。

要管理这个内存,我们用顶点缓冲对象VBO(Vertex Buffer Object)

分开来看就好懂了。为啥叫这么个名字?

  • Vertex:顶点,代表这个东西是管理顶点数据的。
  • Buffer:这个单词在各个领域都经常出现,java里也有个StringBuffer。其实就是缓冲的意思。举个例子,如果你有一部10G的电影要下载,是每下载1K就写入一次硬盘合理?还是读取了一定量,比如100M后,再写入硬盘比较合理?如果是前者,硬盘估计没两天就要坏了吧,毕竟硬盘是有存取寿命的。其实这就解释了Buffer的作用:先存着点,然后一起写。再看回来,这里Buffer也是同样的作用,把一堆顶点数据,一起发给GPU,显然比一次发一个顶点合理嘛。
  • Object:对象,熟悉javaC++的应该都知道对象,其实OpenGL里的对象也差不多,和C的结构体一样,每一个对象有一堆自己的属性,在得到了对象后,我们就能对它的属性进行一些修改。再举一个例子,整个OpenGL就像一个游戏,对象就像游戏里的各种设置,比如画面设置、音效设置等等。你想改变设置,就生成一种这个设置的文件,一读取就好了。我们如果准备了很多不同的设置文件,改变游戏设置不就方便多了吗?

所以VBO也就是一个OpenGL里的结构体,它用来存一些顶点数据方便一次性给GPU内存多发一点。

说了一堆,具体如果使用?
首先,按照惯例,先生成一个缓冲对象(注意这里还不是顶点缓冲对象哦),得到它的id:

unsigned int VBO;//声明一个int作为VBO对象的id
glGenBuffers(1, &VBO);//生成一个Buffer对象,id为VBO

这一步就是将缓冲对象绑定到顶点缓冲对象,相当于就是指定了这个缓冲对象是啥类型的

glBindBuffer(GL_ARRAY_BUFFER, VBO);  

现在,我们任何在GL_ARRAY_BUFFER上的缓冲调用都是对VBO这个对象起作用的。这里有点难理解吧?我再来举例子。

C语言总学过吧
我们声明一个变量是用 int a;
给他初始化后就能用 addOne(a)各种函数来调用了。

OpenGL里呢,没有那么方便,因为他的函数很多是直接调用“类型”的,就这样的:
addOne(int),你传一个a进去,不认得啊。
所以呢要首先bind(int,a)把a跟int这个类型绑定,告诉它现在int就是专指a了!然后addOne(int)这个函数呢,就知道是给a加一了。简单理解,这个例子里int就是GL_ARRAY_BUFFER,a就是VBO。

最后终于来了,把我们之前的那个一维数组,传给了GL_ARRAY_BUFFER对象,注意它已经被我们的VBO给绑定了哦,所以就传给了VBO对象。

介绍一下各个参数:

。它的第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。第三个参数是我们希望发送的实际数据。
第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
GL_STATIC_DRAW :数据不会或几乎不会改变。
GL_DYNAMIC_DRAW:数据会被改变很多。
GL_STREAM_DRAW :数据每次绘制时都会改变。

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

说那么多,其实就一句话,把一个一维数组,给了一个结构体来存储。为啥要给它,因为它功能比一个小小的一维数组多得多嘛。

现在我们回到之前的一个问题了,还记得吗?一个简简单单的一维数组,咋让顶点着色器知道这代表了三个顶点呢?只需要两行:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

第一行是告诉OpenGL如何解释数据,第二行是启用顶点属性。
简单来说就是一维数组是如何变成三维点的。注意这里一个点的信息只有位置坐标,之后还会有颜色、贴图等信息,这样就要修改一个单位的大小和单位之间的偏移量了。
> 这里是引用

在说VAO前先要讲一下,啥是顶点属性?很简单,位置、颜色、贴图坐标都是顶点属性,就是属顶点的信息而已,只不过上面的例子中指用到了位置这一个顶点属性。我们上面就是在配置顶点属性,把顶点属性输入给顶点着色器。

如果我们有100个物体,每个物体要传顶点信息时难道都要像上面那样配置一遍顶点属性吗?肯定不必,这就自然地引出了下一个对象VAO(Vertex Array Object)你可能就猜到了,这个VAO的作用,就是记录我们上面已经配置好的顶点属性,下一次用到相同的顶点属性,我们就不用再写一遍啦,直接用VAO里存好的属性配置就好了。
在这里插入图片描述
这张图看起来挺唬人的,其实就是一句话,VAO保存了顶点属性配置(即一个顶点多大,从哪个地方开始,顶点之间偏移量多少,是一个规则,就是咱们之前用那个一堆参数的函数定制的)

怎么用VAO呢?跟VBO很像:

首先也是生成对象,注意这次不是缓冲对象了,是一个顶点数组对象。

unsigned int VAO;
glGenVertexArrays(1, &VAO);
// ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: ..
// 1. 绑定VAO
glBindVertexArray(VAO);
// 2. 把顶点数组复制到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

[...]

// ..:: 绘制代码(渲染循环中) :: ..
// 4. 绘制物体
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();

看完是不是有个疑问,这个VAO啥时候跟VBO建立联系了?其实是这样,在生成VBO复制数据、设定顶点属性之前,先绑定VAO。把VBO整明白了,就可以解绑VAO了,这个时候该VAO就有了VBO里的所有信息,下一次想再次用这个VBO里的信息,就不用再次设置一遍顶点属性了,直接绑定这个VAO就好了。

看到这里相信读者对VAO和VBO没那么迷糊了吧? ^ - ^

猜你喜欢

转载自blog.csdn.net/weixin_42189888/article/details/107671393