在这里要先了解一下OpenGL的一个幕后大致运作流程,可以直接阅读OPENGL CN
我自己大概总结了一下就是,OpenGL本身就是一个巨大的状态机,我们通过更改状态变量(上下文)来告诉OpenGL如何去绘制图像。一般通过设置选项,修改缓冲来更改OpenGL状态。OpenGL的库是C语言写的,为了方便,OPenGL为我们抽象了一个层,这个层就是把一个状态或者选项包装为一个“对象”,一个对象就是一些选项或者数据的集合,它代表OpenGL状态的一个子集。
VBO
英文全名是Vertex Buffer Object,中文翻译过来是顶点缓冲对象
VBO就是OpenGL的一个“对象”,我们把大批顶点数据封装成一个VBO对象,然后通过CPU将这个VBO对象发送到显存上,方便后期GPU的读取。
例如buffer1数组和buffer2就分别为一个VBO对象
buffer1里面就是最普通的封装的顶点的三个坐标值
buffer2里面就包含顶点坐标的坐标值以及顶点颜色和纹理坐标
float buffer1= {
//顶点坐标(3个一组)
0.5f, 0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
-0.5f, 0.5f, 0.0f, };
float buffer1 = {
//顶点坐标(3个一组) //顶点颜色(3个一组) //纹理坐标(2个一组)
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
*VAO
英文全名 Vertex Array Object ,中文翻译过来是顶点数组对象,存储着设置的顶点属性指针。
VAO就像是为了向GPU解释顶点数据,指定VBO数据中的各个属性字段用途,我们把顶点相关数据打包成一个VBO传入显存,供GPU读取,GPU读取到这个数组数据以后,顶点着色器并不知道要如何去使用这些数据,这个时候就需要VAO来解释这些数据,通过指针告诉顶点着色器,第一组数据去哪里取,第二组数据从哪里开始取。
GLuint VAO;
glBindVertexArray(VAO);
// 2. 把顶点数组复制到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//vertex coord
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// color attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// texture coord attribute
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
glVertexAttribPointer函数的参数非常多,所以我会逐一介绍它们:
第一个参数指定我们要配置的顶点属性。我们会在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location),它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。
第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在8个GLfloat之后,我们把步长设置为8 * sizeof(GLfloat)。
最后一个参数的类型是GLvoid*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。
*EBO
英文Element Buffer Object ,中文翻译为索引缓冲对象
当我们绘制一个矩形,由两个三角形组成,分别为顶点ABC,BCD构成的三角形组成,如果我们按照将组成每个三角形的单个顶点都存入到数组中,那BC这两个顶点就被存储了2次,就会产生额外的开销。当顶点的数量特别多时,开销就会特别巨大。因此为了优化这个问题,就有一个方案,全部的顶点只存储一次,但是额外存储这些顶点的绘制顺序。也就是我们这里存储ABCD四个顶点到VBO中,在EBO中存储绘制顺序,012,123。
耗费资源方式
GLfloat 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, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
索引绘制方式
GLfloat 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 // 左上角
};
GLuint indices[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
绘制过程如下
1、使用各自唯一的ID生成VAO,VBO,EBO对象
2、新创建的VBO缓冲绑定到GL_ARRAY_BUFFER类型目标上,从绑定这一刻起,我们使用的任何(在GL_ARRAY_BUFFER类型目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)。并把定义的顶点数据复制到缓冲的内存中
3、新创建的EBO缓冲绑定到GL_ELEMENT_ARRAY_BUFFER类型目标上,并把索引数据复制到缓冲内存中
4、设定顶点属性指针
GLuint VBO, VAO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
// Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointer(s).
glBindVertexArray(VAO);
//新创建的VBO缓冲绑定到GL_ARRAY_BUFFER目标上
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//把之前定义的顶点数据复制到缓冲的内存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 新创建的EBO缓冲绑定到GL_ELEMENT_ARRAY_BUFFER目标上
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
//并把索引数据复制到缓冲内存中
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//设定顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
参考链接:https://www.cnblogs.com/zhoug2020/p/16465692.html
参考链接:https://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/04%20Hello%20Triangle/
参考链接:https://zhuanlan.zhihu.com/p/150906665