1.VBO
VBO为顶点缓冲对象,是显卡存储空间中开辟出的一块内存缓存区,用于存储顶点的各类属性信息,如顶点坐标,顶点法向量,顶点颜色数据等。在渲染时,可以直接从VBO中取出顶点的各类属性数据。
所以可以理解为VBO就是显存中的一个存储区域,可以保持大量的顶点属性信息。并且可以开辟很多个VBO,每个VBO在OpenGL中有它的唯一标识ID,这个ID对应着具体的VBO的显存地址,通过这个ID可以对特定的VBO内的数据进行存取操作。
如上图,定义两个vbo,我们绘制vbo数据下的三角形,但是出现的却是vbo2下的三角形,为什么呢?
因为,我们在绘制前,已经定义了显存中位置0的地方指向的是内存vbo,但是后面我们又把位置显存中位置0的地方指向给了vbo2,所以我们看到的就是最后一个三角形。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); //从layout为0开始,一个顶点包含三个数据,跨6个顶点为一组,从开头为0读取
那么怎么解决这个问题呢?
如上,只需要我们在绘制的时候指定一个临时的显存位置来绘制,就可以看到绘制的三角形为正常的了。
1.VAO
VAO中定义了顶点的属性,每一种属性有特定的读取VBO的方法,它相当于是一个VBO数据对象的集合,使用glVertexAttribPointer对顶点属性进行的设置,这样我们就知道某一个vbo应该具体的是哪一些信息。如图:
这里就可以解决上面vbo绘制两个三角形时,永远出现的最后一个的问题,如下代码:
这个时候,我们看到的就是,他绘制为第一个三角形。
EBO
举个例子:假设我们绘制一个矩形。我们就可以绘制两个三角形来组成一个矩形。这样我们就需要六个顶点集合,并且会有两个重合的顶点(左下角和右上角的点)。这样当顶点变多时,会产生很大的浪费。所以最好的解决办法是每个顶点只存一次,当我们需要使用这些顶点时,只调用顶点的索引。
EBO中存储的内容就是顶点位置的索引indices,EBO跟VBO类似,也是在显存中的一块内存缓冲器,只不过EBO保存的是顶点的索引。
当用EBO绑定顶点索引的方式绘制模型时,需要使用glDrawElements而不是glDrawArrays: glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
- 第一个参数指定了要绘制的模式;
- 第二个参数指定要绘制的顶点个数;
- 第三个参数是索引的数据类型;
- 第四个参数是可选的EBO中偏移量设定。
如下,使用EBO绘制两个三角形组成的矩形:
//使用EBO绘制矩形(两个三角形)
#include <GL/glew.h>
#include <GL/freeglut.h>
void userInit(); //自定义初始化
void reshape(int w, int h); //重绘
void display(void);
void keyboardAction(unsigned char key, int x, int y); //键盘退出事件
GLuint eboId;//element buffer object句柄
GLuint vboId;//vertext buffer object句柄
GLuint vaoId;//vertext array object句柄
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(512, 512);
glutCreateWindow("Rectangle demo");
//使用glew,需要执行glewInit,不然运行过程会报错
//glewInit要放在glut完成了基本的初始化之后执行
glewInit();
//自定义初始化,生成VAO,VBO,EBO
userInit();
//重绘函数
glutReshapeFunc(reshape);
glutDisplayFunc(display);
//注册键盘按键退出事件
glutKeyboardFunc(keyboardAction);
glutMainLoop();
return 0;
}
//自定义初始化函数
void userInit()
{
glClearColor(0.0, 0.0, 0.0, 0.0);
//创建顶点数据
const GLfloat vertices[] = {
-0.5f,-0.5f,0.0f,1.0f,
0.5f,-0.5f,0.0f,1.0f,
0.5f,0.5f,0.0f,1.0f,
-0.5f,0.5f,0.0f,1.0f,
};
// 索引数据
GLshort indices[] = {
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
//创建VAO对象
glGenVertexArrays(1, &vaoId);
glBindVertexArray(vaoId);
//创建VBO对象,把顶点数组复制到一个顶点缓冲中,供OpenGL使用
glGenBuffers(1, &vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//创建EBO对象
glGenBuffers(1, &eboId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId);
//传入EBO数据
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//解释顶点数据方式
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
//解绑VAO
glBindVertexArray(0);
//解绑EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
//解绑VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
//调整窗口大小回调函数
void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
}
//绘制回调函数
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
//绑定VAO
glBindVertexArray(vaoId);
//绘制模型
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
glutSwapBuffers();
}
//键盘按键回调函数
void keyboardAction(unsigned char key, int x, int y)
{
switch (key)
{
case 033: // Escape key
exit(EXIT_SUCCESS);
break;
}
}
把一个矩形拆分在六个顶点的三角形,分别为0,1,2,1,2,3再绘制