前言
本文次要介绍了什么是 VBO/VAO,为什么须要应用 VBO/VAO 以及如何应用 VBO 和 VAO。
VBO
什么是 VBO
VBO(vertex Buffer Object):顶点缓冲对象。是在显卡存储空间中开拓的一块区域,在显卡存储空间中开拓一块区域,用于寄存顶点的各类属性信息。如顶点坐标、纹理坐标、顶点色彩等数据。
在渲染时间接从显 VBO 去取数据而不用与 CPU 进行数据交换。
为什么须要应用 VBO
将顶点数据保留在内存中,在调用 glDrawArrays 或者 glDrawElements 等绘制办法前须要调用相应的办法将数据送入显存,I/ O 开销大,性能不够好。
若采纳顶点缓冲区对象寄存顶点数据,则不须要在每次绘制前都将顶点数据复制进显存,而是在初始化顶点缓冲区对象时一次性将顶点数据送入显存,
每次绘制时间接应用显存中的数据,能够大大提高渲染性能。
如何应用 VBO
-
应用函数
glGenBuffers
和一个缓冲 ID 生成一个 VBO 对象:unsigned int VBO; glGenBuffers(1, &VBO);
- 应用函数
glBindBuffer
绑定顶点缓冲对象的缓冲类型是 GL_ARRAY_BUFFER
OpenGL 有很多缓冲对象类型,顶点缓冲对象的缓冲类型是 GL_ARRAY_BUFFER。
glBindBuffer(GL_ARRAY_BUFFER, VBO);
-
应用函数
glBufferData
把定义好的顶点数据复制到缓冲的内存中:// vertices 示意顶点数组 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBufferData
是一个专门用来把用户定义的数据复制到以后绑定缓冲的函数。它的第一个参数是指标缓冲的类型,其中 VBO 代表是的 GL_ARRAY_BUFFER。第二个参数指定传输数据的大小 (以字节为单位),用一个简略的 sizeof 计算出顶点数据大小就行。
第三个参数是咱们心愿发送的理论数据。第四个参数指定了咱们心愿显卡如何治理给定的数据,它有三种模式:
GL_STATIC_DRAW:数据不会或简直不会扭转。
GL_DYNAMIC_DRAW:数据会被扭转很多。
GL_STREAM_DRAW:数据每次绘制时都会扭转。
个别状况下地位数据不会扭转,每次渲染调用时都放弃原样,所以它的应用类型个别是 GL_STATIC_DRAW。如果一个缓冲中的数据将频繁被扭转,那么应用的类型就是 GL_DYNAMIC_DRAW 或 GL_STREAM_DRAW,这样就能确保显卡把数据放在可能高速写入的内存局部。
- 用完后应用函数
glDeleteBuffers
删除缓冲区
明天咱们以之前绘制四边形的实际为例子,应用 VBO 的形式来实现四边形的绘制:Opengl ES 之四边形绘制
咱们的指标是灵便应用 Opengl 绘制一个蓝色的四边形 …
一般惯例的绘制这里就不多说了,后续能够看代码,或者回顾之前的四边形绘制的文章,这里次要介绍搭配 VBO 的两种绘制形式:
首先它们应用的顶点着色器和片段着色器都是一样的,都是:
// 顶点着色器
static const char *ver = "#version 300 es\n"
"in vec4 aColor;\n"
"in vec4 aPosition;\n"
"out vec4 vColor;\n"
"void main() {\n"
"vColor = aColor;\n"
"gl_Position = aPosition;\n"
"}";
// 片元着色器
static const char *fragment = "#version 300 es\n"
"precision mediump float;\n"
"in vec4 vColor;\n"
"out vec4 fragColor;\n"
"void main() {\n"
"fragColor = vColor;\n"
"}";
- 顶点坐标与色彩值坐标拆散的形式(数组构造)
先看顶点数据与色彩数据:
// 应用绘制两个三角形组成一个矩形的模式(三角形带)
// 第一第二第三个点组成一个三角形,第二第三第四个点组成一个三角形
const static GLfloat VERTICES[] = {
0.5f,-0.5f, // 右下
0.5f,0.5f, // 右上
-0.5f,-0.5f, // 左下
-0.5f,0.5f, // 左上
};
// vbo 色彩
const static GLfloat COLOR_ICES[] = {
0.0f,0.0f,1.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
};
VBO 数据与绑定:
// vbo
glGenBuffers(3,vbo);
glBindBuffer(GL_ARRAY_BUFFER,vbo[0]);
glBufferData(GL_ARRAY_BUFFER,sizeof(VERTICES),VERTICES,GL_STATIC_DRAW);
// 色彩
glBindBuffer(GL_ARRAY_BUFFER,vbo[1]);
glBufferData(GL_ARRAY_BUFFER,sizeof(COLOR_ICES),COLOR_ICES,GL_STATIC_DRAW);
次要绘制代码:
// 应用 VBO 的形式绘制,顶点与色彩离开
// glBindBuffer(GL_ARRAY_BUFFER,vbo[0]);
// glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,(void *)0);
// // 启用顶点数据
// glEnableVertexAttribArray(positionHandle);
//
// glBindBuffer(GL_ARRAY_BUFFER,vbo[1]);
// glVertexAttribPointer(colorHandle,4,GL_FLOAT,GL_FALSE,0,(void *)0);
// // 启用色彩顶点数据
// glEnableVertexAttribArray(colorHandle);
// // 解除绑定
// glBindBuffer(GL_ARRAY_BUFFER,0);
- 顶点坐标与色彩值坐标联合的形式(构造数组)
顶点数据与色彩数据混合打乱:
const static GLfloat VERTICES_AND_COLOR[] = {
0.5f,-0.5f, // 右下
// 色彩
0.0f,0.0f,1.0f,1.0f,
0.5f,0.5f, // 右上
// 色彩
0.0f,0.0f,1.0f,1.0f,
-0.5f,-0.5f, // 左下
// 色彩
0.0f,0.0f,1.0f,1.0f,
-0.5f,0.5f, // 左上
// 色彩
0.0f,0.0f,1.0f,1.0f,
};
VBO 数据绑定:
// vbo
glGenBuffers(3,vbo);
glBindBuffer(GL_ARRAY_BUFFER,vbo[0]);
glBufferData(GL_ARRAY_BUFFER,sizeof(VERTICES),VERTICES,GL_STATIC_DRAW);
// 色彩
glBindBuffer(GL_ARRAY_BUFFER,vbo[1]);
glBufferData(GL_ARRAY_BUFFER,sizeof(COLOR_ICES),COLOR_ICES,GL_STATIC_DRAW);
// 顶点与颜色混合,先顶点坐标,再色彩坐标
glBindBuffer(GL_ARRAY_BUFFER,vbo[2]);
glBufferData(GL_ARRAY_BUFFER,sizeof(VERTICES_AND_COLOR),VERTICES_AND_COLOR
,GL_STATIC_DRAW);
次要绘制代码:
// VBO 绘制 顶点坐标与色彩坐标一起
// glBindBuffer(GL_ARRAY_BUFFER,vbo[2]);
// // stride 步长 每个顶点坐标之间相隔 6 个数据点,数据类型是 float
// glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,6 * sizeof(float),(void *)0);
// // 启用顶点数据
// glEnableVertexAttribArray(positionHandle);
// // stride 步长 每个色彩坐标之间相隔 6 个数据点,数据类型是 float,色彩坐标索引从 2 开始
// glVertexAttribPointer(colorHandle,4,GL_FLOAT,GL_FALSE,6 * sizeof(float) ,(void *)(2 * sizeof(float)) );
// // 启用色彩顶点数据
// glEnableVertexAttribArray(colorHandle);
// // 解除绑定
// glBindBuffer(GL_ARRAY_BUFFER,0);
它们的运行后果都是胜利绘制了一个蓝色的四边形:
性能
在下面咱们应用了多种不同的组合形式进行了四边形的绘制,其中有将顶点坐标和色彩坐标拆散写在两个不同的数组的形式,也有将顶点坐标和色彩坐标组合写在同一个数组,而后应用步长 (stride) 和偏移量 (*pointer) 参数管制的形式进行绘制,那么这两种形式那种性能更佳呢?
其中将各种顶点坐标拆散在不同数组的写法又成为数组构造,而将各种顶点坐标合并成一个数组的写法又称为构造数组,在《OPENGL ES 3.0 编程指南》一书中作者指出,构造数组的写法性能更好。
在大部分状况下,答案是构造数组。
起因是,每个顶点的属性数据能够程序形式读取,这最有可能造成高效的内存拜访模式。
VAO
什么是 VAO
VAO(vertex Array Object):顶点数组对象。
留神:VAO 是 OpenGL ES 3.0 之后才推出的新个性,所以在应用 VAO 前须要确定 OpenGL ES 的版本是否是 3.0 之后的版本。
顶点数组对象能够像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会贮存在这个 VAO 中。这样的益处就是,当配置顶点属性指针时,你只须要将那些调用执行一次,之后再绘制物体的时候只须要绑定相应的 VAO 就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只须要绑定不同的 VAO 就行了。刚刚设置的所有状态都将存储在 VAO 中。
一个顶点数组对象会贮存以下这些内容:
- glEnableVertexAttribArray 和 glDisableVertexAttribArray 的调用。
- 通过 glVertexAttribPointer 设置的顶点属性配置。
- 通过 glVertexAttribPointer 调用与顶点属性关联的顶点缓冲对象。
在下面 VBO 的介绍中咱们晓得每次在绘制的时候都须要频繁地绑定与解绑 VBO,每次绘制还须要取出 VBO 中的数据进行赋值之后能力进行绘制渲染。当数据量大的时候,反复这些操作就会变得很繁琐。通过 VAO 就能够简化这个过程,因而 VAO 能够简略了解成 VBO 的管理者,防止在帧绘制时再去手动操纵 VBO,VAO 不能独自应用,
须要搭配 VBO 应用。
对于 GPU 来说 VBO 就是一堆数据,然而这堆数据怎么解析应用,须要 glEnableVertexAttribArray
等相干函数在每次绘制的时候通知 GPU,那么 VAO 的作用就是简化这个过程的,只须要在初始化的时候将这些解析逻辑与 VAO 绑定一次即可,
而后每次在绘制的时候只需绑定对应的 VAO,而不用每次再绑定 VBO,而后去通知 GPU 如何解析相干数据了,能够说是一个性能的优化了。
如何应用 VAO
- 首先调用函数
glGenVertexArrays
生成 VAO
unsigned int VAO;
glGenVertexArrays(1, &VAO);
- 调用函数
glBindVertexArray
绑定 VAO
glBindVertexArray(VAO);
// 治理 VBO,让 VAO 记住 VBO 的数据如何解析应用
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);
glBindVertexArray(0);
而后在绘制的时候应用函数 glBindVertexArray(VAO)
应用即可。
- 退出时通过函数
glDeleteVertexArrays
删除 VAO
glDeleteVertexArrays
次要绑定代码:
// VAO
glGenVertexArrays(1,&vao);
glBindVertexArray(vao);
// VAO 与 VBO 关联
glBindBuffer(GL_ARRAY_BUFFER,vbo[2]);
// stride 步长 每个顶点坐标之间相隔 6 个数据点,数据类型是 float
glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,6 * sizeof(float),(void *)0);
// 启用顶点数据
glEnableVertexAttribArray(positionHandle);
// stride 步长 每个色彩坐标之间相隔 6 个数据点,数据类型是 float,色彩坐标索引从 2 开始
glVertexAttribPointer(colorHandle,4,GL_FLOAT,GL_FALSE,6 * sizeof(float) ,(void *)(2 * sizeof(float)) );
// 启用色彩顶点数据
glEnableVertexAttribArray(colorHandle);
// 解除绑定
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindVertexArray(0);
次要应用绘制代码:
// VBO 与 VAO 配合绘制
// 应用 vao
glBindVertexArray(vao);
// 4 个顶点绘制两个三角形组成矩形
glDrawArrays(GL_TRIANGLE_STRIP,0,4);
glUseProgram(0);
// vao 解除绑定
glBindVertexArray(vao);
上面是残缺代码:
VBOVAOOpengl.h
static const int NUM_VBO = 3;
class VBOVAOOpengl:public BaseOpengl{
public:
VBOVAOOpengl();
virtual ~VBOVAOOpengl();
virtual void onDraw();
private:
GLint positionHandle{-1};
GLint colorHandle{-1};
GLuint vbo[NUM_VBO];
GLuint vao{0};
};
VBOVAOOpengl.cpp
// 顶点着色器
static const char *ver = "#version 300 es\n"
"in vec4 aColor;\n"
"in vec4 aPosition;\n"
"out vec4 vColor;\n"
"void main() {\n"
"vColor = aColor;\n"
"gl_Position = aPosition;\n"
"}";
// 片元着色器
static const char *fragment = "#version 300 es\n"
"precision mediump float;\n"
"in vec4 vColor;\n"
"out vec4 fragColor;\n"
"void main() {\n"
"fragColor = vColor;\n"
"}";
// 应用绘制两个三角形组成一个矩形的模式(三角形带)
// 第一第二第三个点组成一个三角形,第二第三第四个点组成一个三角形
const static GLfloat VERTICES[] = {
0.5f,-0.5f, // 右下
0.5f,0.5f, // 右上
-0.5f,-0.5f, // 左下
-0.5f,0.5f, // 左上
};
const static GLfloat VERTICES_AND_COLOR[] = {
0.5f,-0.5f, // 右下
// 色彩
0.0f,0.0f,1.0f,1.0f,
0.5f,0.5f, // 右上
// 色彩
0.0f,0.0f,1.0f,1.0f,
-0.5f,-0.5f, // 左下
// 色彩
0.0f,0.0f,1.0f,1.0f,
-0.5f,0.5f, // 左上
// 色彩
0.0f,0.0f,1.0f,1.0f,
};
// rgba
//const static GLfloat COLOR_ICES[] = {
// 0.0f,0.0f,1.0f,1.0f
//};
// vbo 色彩
const static GLfloat COLOR_ICES[] = {
0.0f,0.0f,1.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
};
VBOVAOOpengl::VBOVAOOpengl() {initGlProgram(ver,fragment);
positionHandle = glGetAttribLocation(program,"aPosition");
colorHandle = glGetAttribLocation(program,"aColor");
// vbo
glGenBuffers(3,vbo);
glBindBuffer(GL_ARRAY_BUFFER,vbo[0]);
glBufferData(GL_ARRAY_BUFFER,sizeof(VERTICES),VERTICES,GL_STATIC_DRAW);
// 色彩
glBindBuffer(GL_ARRAY_BUFFER,vbo[1]);
glBufferData(GL_ARRAY_BUFFER,sizeof(COLOR_ICES),COLOR_ICES,GL_STATIC_DRAW);
// 顶点与颜色混合,先顶点坐标,再色彩坐标
glBindBuffer(GL_ARRAY_BUFFER,vbo[2]);
glBufferData(GL_ARRAY_BUFFER,sizeof(VERTICES_AND_COLOR),VERTICES_AND_COLOR
,GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER,0);
// VAO
glGenVertexArrays(1,&vao);
glBindVertexArray(vao);
// VAO 与 VBO 关联
glBindBuffer(GL_ARRAY_BUFFER,vbo[2]);
// stride 步长 每个顶点坐标之间相隔 6 个数据点,数据类型是 float
glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,6 * sizeof(float),(void *)0);
// 启用顶点数据
glEnableVertexAttribArray(positionHandle);
// stride 步长 每个色彩坐标之间相隔 6 个数据点,数据类型是 float,色彩坐标索引从 2 开始
glVertexAttribPointer(colorHandle,4,GL_FLOAT,GL_FALSE,6 * sizeof(float) ,(void *)(2 * sizeof(float)) );
// 启用色彩顶点数据
glEnableVertexAttribArray(colorHandle);
// 解除绑定
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindVertexArray(0);
LOGD("program:%d",program);
LOGD("positionHandle:%d",positionHandle);
LOGD("colorHandle:%d",colorHandle);
}
void VBOVAOOpengl::onDraw() {
// 清屏
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(program);
// 一般形式绘制
// /**
// * size 几个数字示意一个点,显示是两个数字示意一个点
// * normalized 是否须要归一化,不必,这里曾经归一化了
// * stride 步长,间断顶点之间的距离,如果顶点间接是间断的,也可填 0
// */
// glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,VERTICES);
// // 启用顶点数据
// glEnableVertexAttribArray(positionHandle);
//
// // 这个不须要 glEnableVertexAttribArray
// glVertexAttrib4fv(colorHandle, COLOR_ICES);
// ############################################# 分割线 #################################
// 应用 VBO 的形式绘制,顶点与色彩离开
// /**
// glBindBuffer(GL_ARRAY_BUFFER,vbo[0]);
// glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,(void *)0);
// // 启用顶点数据
// glEnableVertexAttribArray(positionHandle);
//
// glBindBuffer(GL_ARRAY_BUFFER,vbo[1]);
// glVertexAttribPointer(colorHandle,4,GL_FLOAT,GL_FALSE,0,(void *)0);
// // 启用色彩顶点数据
// glEnableVertexAttribArray(colorHandle);
// // 解除绑定
// glBindBuffer(GL_ARRAY_BUFFER,0);
// ############################################# 分割线 #################################
// VBO 绘制 顶点坐标与色彩坐标一起
// glBindBuffer(GL_ARRAY_BUFFER,vbo[2]);
// // stride 步长 每个顶点坐标之间相隔 6 个数据点,数据类型是 float
// glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,6 * sizeof(float),(void *)0);
// // 启用顶点数据
// glEnableVertexAttribArray(positionHandle);
// // stride 步长 每个色彩坐标之间相隔 6 个数据点,数据类型是 float,色彩坐标索引从 2 开始
// glVertexAttribPointer(colorHandle,4,GL_FLOAT,GL_FALSE,6 * sizeof(float) ,(void *)(2 * sizeof(float)) );
// // 启用色彩顶点数据
// glEnableVertexAttribArray(colorHandle);
// // 解除绑定
// glBindBuffer(GL_ARRAY_BUFFER,0);
// ############################################# 分割线 #################################
// VBO 与 VAO 配合绘制
// 应用 vao
glBindVertexArray(vao);
// 4 个顶点绘制两个三角形组成矩形
glDrawArrays(GL_TRIANGLE_STRIP,0,4);
glUseProgram(0);
// vao 解除绑定
glBindVertexArray(vao);
// 禁用顶点
glDisableVertexAttribArray(positionHandle);
if(nullptr != eglHelper){eglHelper->swapBuffers();
}
}
VBOVAOOpengl::~VBOVAOOpengl() noexcept {glDeleteBuffers(NUM_VBO,vbo);
glDeleteVertexArrays(1,&vao);
}
往期笔记
Opengl ES 之 EGL 环境搭建
Opengl ES 之着色器
Opengl ES 之三角形绘制
Opengl ES 之四边形绘制
Opengl ES 之纹理贴图
关注我,一起提高,人生不止 coding!!!