VBO会在GPU内存(显存)中储存大量顶点,使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。
//顶点着色器
#version 300 es
layout(location = 0) in vec4 in_position;
layout(location = 1) in vec3 in_color;
out vec3 frag_color;
void main() {
frag_color = in_color;
gl_Position = in_position;
}
//片段着色器
#version 300 es
precision mediump float;
in vec3 frag_color;
out vec4 out_frag_color;
void main() {
out_frag_color = vec4(frag_color, 1.0);
}
VBO的使用:
void Part3NativeRender::VBO() {
//创建VBO,参数1为VBO个数,参数2为 VBO 指针
glGenBuffers(1, &vbo);
//将新建的 Buffer ,绑定到 GL_ARRAY_BUFFER(目标缓冲类型) 上,OpenGL允许同时绑定多个缓冲,只要它们是不同的缓冲类型
//绑定之后使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲
glBindBuffer(GL_ARRAY_BUFFER, vbo);
/**
* 顶点数据,glsl/3/vertex.glsl 中的数据如下
* | vertex1 | vertex2 | vertex3 |
* |X|Y|Z|R|G|B|X|Y|Z|R|G|B|X|Y|Z|R|G|B|
* | 0-24 | 24-48 | 48-72 |
*/
float vertexArray[] = {0.0f, 0.5f, 0.0f, // top
0.0f, 0.0f, 0.0f, // RGB
-0.5f, -0.5f, 0.0f, // bottom left
0.0f, 0.5f, 0.5f, // RGB
0.5f, -0.5f, 0.0f, // bottom right
1.0f, 0.25f, 0.25f, // RGB
};
//将顶点数据复制到 Buffer 中
//参数1:目标缓冲类型
//参数2:传输数据的大小(单位:字节)
//参数3:实际发送的数据
//参数4:希望显卡如何管理数据GL_STATIC_DRAW(数据不会变)、GL_DYNAMIC_DRAW(数据经常改变)、GL_STREAM_DRAW(每次绘制时都会变化)
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexArray), vertexArray, GL_STATIC_DRAW);
//启用顶点属性,顶点属性默认是禁用的
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
//告诉OpenGL如何解析数据
//参数一:顶点属性位置
//参数二:每个顶点的数据个数
//参数三:数据类型
//参数四:是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间
//参数五:步长,连续的顶点属性组之间的间隔(当数值是紧密排列时,可以设置0来让OpenGL决定具体步长是多少)
//参数六:位置数据在缓冲中起始位置的偏移量
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GL_FLOAT), (void *) 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GL_FLOAT),
(void *) (3 * sizeof(GL_FLOAT)));
}
VBO缓存了数据,而数据的使用方式(glVertexAttribPointer)并没有缓存。VAO不存储数据,它存储的是一次绘制中所需要的信息,这包括数据在哪里 glBindBuffer
、数据的格式是怎么样的 glVertexAttribPointer
。它可以像VBO那样被绑定,任何随后的顶点属性调用都会储存在这个 VAO 中。这样的好处就是,当配置顶点属性
指针时,你只需要将那些调用执行一次
,之后再绘制物体的时候只需要绑定相应的VAO就行了。
void Part3NativeRender::VAO() {
//创建VAO,参数1为VBO个数,参数2为 VAO 指针
glGenVertexArrays(1, &vao);
//绑定VAO,绑定之后使用任何缓冲调用都会保存再当前VAO上
glBindVertexArray(vao);
//使用VBO的实例
VBO();
}
假设需要绘制一个矩形(OpenGL主要处理三角形,矩形由两个三角形组成),这会生成下面的顶点的集合:
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, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
这样会导致有两个顶点叠加了,一个矩形只有4个而不是6个顶点,这样就产生50%的额外开销。使用索引缓冲对象(EBO)之后的顶点就为如下方式:
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 // 第二个三角形
};
EBO的使用
unsigned int ebo;
unsigned int indices[] = {
0, 1, 2, // 第一个三角形
0, 2, 3 // 第二个三角形
};
//创建EBO
glGenBuffers(1, &ebo);
//绑定EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
//设置EBO数据,EBO同VBO一样,也可以存放再VAO中
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//绘制操作使用glDrawElements,而不是glDrawArrays
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
// 1. 绑定顶点数组对象
glBindVertexArray(VAO);
// 2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 复制我们的索引数组到一个索引缓冲中,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 设定顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//绘制代码
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);