在opengl中所有的数据都要放在显存中,我们通过一定的手段去管理它,既要提供地方存放它,还要提供方法去正确地提取它们,去使用它们,opengl通过VAO,VBO,EBO这些手段来解决这些问题。 (一)VBO(Vertex Buffer Objects,顶点缓冲对象)介绍: 首先我们要明白VBO是一种管理手段,它的中文名中“缓冲”,就决定了它是管理存储的一种手段(methon),总的来 说,为什么要设置这个VBO,是应该从着色器程序说起。 着色器的第一个顶点着色器接受到我们数据流传入的顶点数据后,它会把这些数据放到GPU的显存上面(等同内存) 接下来向OpenGL中配置如何去解释这些内存,未来如何发给显卡处理。接下来着色器才会按照我们编的代码做我们想做 事情。 我们设立VBO的原因就是要管理这些内存,这样做有很多好处比如:1.我们可以往一个VBO管理的内存中放很多顶点 的数据,这样我们就可以一下子把这些数据都从cpu的数据流上送到GPU(显卡)的显存上,这样节省时间,因为从CPU往 GPU的过程中,要经过很多“关卡”,送一“筐”和送一“卡车”是没有区别的。 (二)声明一个VBO如下:
unsigned int VBO;//声明一个句柄
glGenBuffers(1, &VBO);//1是一个缓冲ID,用Gen生成一个缓冲区。
glBindBuffer(GL_ARRAY_BUFFER, VBO); //用这个函数将这个缓冲区变成一个GL_ARRAY_BUFFER,这是顶点缓冲类型的意思。
//注意这是一个绑定函数,现在所有输入GL_ARRAY_BUFFER的数据,都会直接被送入现在这个VBO,而不是其他的VBO2,VBO3之类的什么,如果
我们不要用这个VBO,想换一个VBO2,我们必须先解绑这个VBO。
//注意我们不会去直接用VBO,因为我们的数据必须全部输入到目标缓冲GL_ARRAY_BUFFER里,这样表明我们输入的是顶点属性,而不是别的
什么。
//这就相当于我们可以输入的缓冲对象类型,已经被设定好了,我们只能往这些地方输入数据,位于它们后面缓冲区,可以不断更换用来实现
不同目的。
(三)往VBO里面放入数据:
接下来这个函数是很重要的,它是将数据从cpu的流中提取出来,放入显存中的VBO管理的区域里。
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//首先是缓冲类型声明,GL_ARRAY_BUFFER表明数据将被传入刚才绑定这个类型的缓冲区里,第二个是数据流的大小,第三个是数据流名字,
第四个是希望显卡如何管理给定的数据,要告诉显卡这些数据未来会不会被改变。要是希望经常改变的话,显卡会把数据放在能够高速写入
的内存部分,这样节省资源。
GL_STATIC_DRAW :数据不会或几乎不会改变。
GL_DYNAMIC_DRAW:数据会被改变很多。
GL_STREAM_DRAW :数据每次绘制时都会改变。
//到这一步,我们的数据流已经没有用了,放在内存上的缓冲区的坐标数据,将被传入显卡上的着色器,被用来使用。
接下来就是着色器的任务了。如何去解释数据流,将一条连续的float流数据解读为三维的顶点数据(就是将数字变成具有几何意义的坐标点)
这都是着色器的任务了。
在解释什么是VAO之前,我们先要解释一下着色器的一个任务,正是着色器的这个任务,导致我们必须使用VAO这种方式来设置。
复制顶点数组到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
// 3. 绘制物体
someOpenGLFunctionThatDrawsOurTriangle();
//这就是我们渲染一个物体的步骤,其中最重要的就是1.将数据从cpu数据流传入CPU的VBO缓冲区管理的显存里2.将数据流正确的解释成三维
坐标。
//这里使用了一个属于叫做顶点属性指针,他就是用来解释该如何去解释数据流的,使用不同的属性指针可以得到不同的解释。
(四)设立VAO,顶点数组对象(Vertex Array Object):
一个VAO中包含两个关键东西,glVertexAttribPointer函数绑定的VBO和顶点属性配置(也就是数据放在哪里 ,和如何读取数据)。此外还有glEnableVertexAttribArray和glDisableVertexAttribArray这两个函数用来启 用顶点属性,和取消顶点属性链接。 这样就造成了一个你绑定一个VAO就可以知道,去哪里读,用什么方式去解读。
这样你在绑定一个VAO的情况下,便可以绘制这个物体,只要它们的读取方式(顶点属性配置)一样。
注意把顶点数组复制到缓冲中供OpenGL使用,是在VBO里面布置的,
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//举个例子,你想绘制一个边长3cm的立方体,就绑定这个VAO,OPENGL这个状态机下一刻就会绘制出来这个
//如果你想绘制一个长方体,就解绑之前哪个VAO,绑定VAO1,就下一刻绘制出长方体了。
(五)设立EBO,索引缓冲对象(Element Buffer Object):
索引缓冲对象EBO,不是我们必须设置的东西,我们必须设置VAO,来告诉OpenGL如何读取,将数据放入哪里。但我们不必一定
要有EBO。我们之所以发明EBO是用来简化我们的程序的渲染步骤的,它是用来告诉我们如何去绘制的。
这要从我们3d世界说起,电脑里面的所有3d立体图形,无论是球形,还是不规则图形,它们都是由三角形构成的,也就是说
都是由三个点构成一个面,无数个面构成一个立体图形。这样在不规则立体图形里,必然由许多的点是共用的,我们为了能在输入
数据的时候少输入一些点(传输数据很花时间的),就要想办法将这些共用的点利用起来,只输入一次,这就是EBO的发明原因。
在操作时,我们先输入所有点的数据(VBO,和设置顶点属性指针),再设置将哪些点绘制成面(EBO),这就形成了正规的3d立体图形。
float vertices[] = {
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
unsigned int indices[] = {
// 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
我们也是用数据流来表明,哪些点构成一个面。
接下来的操作似曾相识,将数据索引复制到缓冲里。
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
在渲染的时候,因为调用了EBO,所以不能用绘制VAO的函数glDrawArrays,要使用兼容glDrawElements,用法是类似的。
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
//6的意思是一共绘制六个顶点。0是一个偏移量,这里填0就好了
在绘制的时候,EBO和VBO都可以看作缓存,它们都被VAO管理着,所以实际渲染中,我们只需要绑定VAO,就可以了。
,并且将一条连续的float流数据解读为三维的顶点数据 (就是将数字变成具有几何意义的坐标点)。