【HarmonyOS实战开发】鸿蒙OpenGL使用VBO、EBO、VAO提升渲染性能

30 篇文章 0 订阅
30 篇文章 0 订阅

一、VBO

VBO是顶点缓冲对象,顶点缓冲对象在GPU中存储数据,加速渲染过程。

1、1 VBO优点
  • 减少数据从CPU到GPU的传输,提升渲染效率。在鸿蒙OpenGL入门,绘制三角形中介绍了绘制三角形,其实当时绘制的三角形性能并不高。三角形的顶点数据和颜色数据都是从CPU获取,如果图形复杂,数据每次都从CPU拷贝到GPU,性能就太低。更好的方式是一次性将全部数据从CPU拷贝到GPU,之后需要数据的时候,直接从GPU中获取数据。在GPU中设置一个对象,这个对象就是VBO,一次性将全部数据拷贝到VBO中。VBO在GPU中,从VBO中获取数据就是从GPU中获取数据,大大的提升了渲染性能。
  • 数据保存在GPU,可重复使用。
  • 可以在GPU中转换数据,减少CPU负载。
1、2 使用VBO基本步骤

需要注意的是,下面的操作需要在链接完程序后调用,也就是在调用完glLinkProgram函数后。

  • 调用glGenBuffers函数生成VBO对象。

void GL_APIENTRY glGenBuffers (GLsizei n, // 一次生成多少个VBO
GLuint *buffers); // VBO对象的索引值
  • 调用glBindBuffer进行绑定。
void GL_APIENTRY glBindBuffer (GLenum target, // 目标数据类型,它是个常量,GL_ARRAY_BUFFER表示用于存储顶点数据
GLuint buffer); // VBO对象的id,解绑时需要将该参数设置为0
  • 调用glBufferData将顶点数据复制到VBO对象中。
void GL_APIENTRY glBufferData (GLenum target, // 目标数据类型,它是个常量,GL_ARRAY_BUFFER表示用于存储顶点数据
GLsizeiptr size, // 拷贝数据的大小 
const void *data, // 从哪拷贝数据
GLenum usage); // 常量,指定拷贝数据的方式,一般设置为GL_STATIC_DRAW,表明是一次性拷贝
  • 调用glVertexAttribPointer从VBO中读取数据,glVertexAttribPointer函数之前文章介绍过,当使用VBO的时候,需要将glVertexAttribPointer函数最后一个参数设置为0。
  • 解绑VBO对象,VBO是在GPU分配空间,当不需要时,主动释放空间。
glDeleteBuffers(VBO_COUNT, vboIds);

如下代码是使用VBO的流程。

bool VBOTriangleExample::init() {
 if (TriangleExample::init()) {
 // 生成vbo对象
 glGenBuffers(1, vboIds);
 // 绑定vbo
 glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
 // 将顶点数据复制到VBO对象
 glBufferData(GL_ARRAY_BUFFER, sizeof(triangleVertices), triangleVertices, GL_STATIC_DRAW);
 // 解绑vbo
 glBindBuffer(GL_ARRAY_BUFFER, 0);
 return true;
 }
 return false;
}
1、3修改绘制代码

有三处改动

  • 调用glBindBuffer函数告知着色器从哪个vbo中读取数据。
  • 调用glVertexAttribPointer的时候,需要将最后一个参数设置为0,表示从GPU读取数据。
  • 调用glBindBuffer函数,最后一个参数设置为0,表示解绑vbo。

void VBOTriangleExample::draw() {
 glUseProgram(program);
 // 告知着色器从哪个vbo中读取数据
 glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
 // 获取顶点着色器中定义的属性
 GLint positionHandler = glGetAttribLocation(program, "vPosition");
 // 启用顶点数组
 glEnableVertexAttribArray(positionHandler);
 /*
 * 顶点数据已经拷贝到GPU,直接从GPU读取顶点数据
 * 第一个参数是属性变量的下标
 * 第二个参数是顶点坐标的个数,我们在定义顶点坐标的时候,使用了空间坐标系,每个坐标使用x,y,z,所以第二个参数为3
 * 第三个参数是数据的类型
 * 第四个参数是否进行了归一化处理,这里写false
 * 第五个参数是跨度,这里是0,没有跨度
 * 第六个参数为0,表示从GPU读取数据
 */
 glVertexAttribPointer(positionHandler, 3, GL_FLOAT, false, 0, 0);
 // 获取片元着色器中定义的变量
 GLint colorHandler = glGetUniformLocation(program, "vColor");
 /*
 * 向片元着色器传递颜色
 * 第一个参数是变量的下标
 * 第二个参数是数据的数量,由于将所有的像素都设置成一样的颜色,所以第二个参数是1
 * 第三个参数是颜色
 */
 // 红色
 const GLfloat DRAW_COLOR[] = { 255, 0, 0, 1.0f };
 glUniform4fv(colorHandler, 1, DRAW_COLOR);
 // 绘制三角形
 GLsizei count = sizeof(triangleVertices) / sizeof(triangleVertices[0]) / 3;
 /*
 * 绘制三角形
 * 第一个参数是绘制的图形
 * 第二个参数是从哪里开始读取,这里从0开始读取
 * 第三个参数是顶点的数量,三角形有三个顶点,第三个参数就是3
 */
 glDrawArrays(GL_TRIANGLES, 0, count);
 // 释放属性变量
 glDisableVertexAttribArray(positionHandler);
 // 解绑vbo
 glBindBuffer(GL_ARRAY_BUFFER, 0);
}

看下绘制结果,仍然是绘制三角形,但是使用了VBO加速了渲染速度,再也不需要每次从CPU拷贝数据,直接从GPU读取数据。

1725189656504.jpg

二、EBO

EBO(Element Buffer Object)是元素缓冲对象,也叫索引缓冲对象(Index Buffer Object,IBO)。用于存储索引数据,减少顶点数组中的重复点,提高渲染效率。
  现在需要绘制一个矩形,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个顶点。当有上千个三角形的模型之后就产生一大堆浪费。更好的解决方案是只储存不同的顶点,并设定绘制顶点的顺序。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,2,3)就是顶点数组vertices的下标,
 // 这样可以由下标代表顶点组合成矩形
 0, 1, 3, // 第一个三角形
 1, 2, 3  // 第二个三角形
};
2、1使用EBO基本步骤
  • 调用glGenBuffers函数生成EBO对象。

void GL_APIENTRY glGenBuffers (GLsizei n, // 一次生成多少个EBO
GLuint *buffers); // EBO对象的索引值
  • 绑定EBO

void GL_APIENTRY glBindBuffer (GLenum target, // 目标数据类型,它是个常量,
GL_ELEMENT_ARRAY_BUFFER表示绑定EBO
GLuint buffer); // EBO对象的id,解绑时需要将该参数设置为0
  • 调用glBufferData将顶点数据复制到EBO对象中。

void GL_APIENTRY glBufferData (GLenum target, // 目标数据类型,它是个常量,GL_ELEMENT_ARRAY_BUFFER表示绑定EBO
GLsizeiptr size, // 拷贝数据的大小 
const void *data, // 从哪拷贝数据
GLenum usage); // 常量,指定拷贝数据的方式,一般设置为GL_STATIC_DRAW,表明是一次性拷贝
  • 调用glDrawElements绘制
void GL_APIENTRY glDrawElements (GLenum mode, // 制定要绘制的图形
GLsizei count, // 指定顶点的个数
GLenum type, // 指定数据类型,三角形顶点个数是3,矩形由两个三角形组成,顶点个数是6
const void *indices); // 指定偏移量,一般情况下为0
  • 解绑EBO
glDeleteBuffers(EBO_COUNT, eboIds);

如下代码是EBO的使用流程,使用EBO的前提是使用VBO。

bool EBORectangleExample::init() {
 program = GLUtil::createProgram(vertexShader, fragmentShader);
 if (program == PROGRAM_ERROR) {
 LOGE("链接程序失败");
 return false;
 }
 // 使用EBO的前提是绑定VBO
 glGenBuffers(VBO_COUNT, vboIds);
 glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
 // 将顶点数据复制到VBO对象
 glBufferData(GL_ARRAY_BUFFER, sizeof(rectangleVertices), rectangleVertices, GL_STATIC_DRAW);
 // 解绑vbo
 glBindBuffer(GL_ARRAY_BUFFER, 0);

 glGenBuffers(EBO_COUNT, eboIds);
 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboIds[0]);
 // 将索引数据复制到EBO对象,也就是将数据从CPU拷贝到GPU
 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
 // 解绑ebo
 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
 return true;
}
2、2绘制矩形
void EBORectangleExample::draw() {
 glUseProgram(program);
 // 告知着色器从哪个vbo中读取数据
 glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
 // 绑定ebo
 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboIds[0]);
 // 获取顶点着色器中定义的属性
 GLint positionHandler = glGetAttribLocation(program, "vPosition");
 // 启用顶点数组
 glEnableVertexAttribArray(positionHandler);
 /*
 * 顶点数据已经拷贝到GPU,直接从GPU读取顶点数据
 * 第一个参数是属性变量的下标
 * 第二个参数是顶点坐标的个数,我们在定义顶点坐标的时候,使用了空间坐标系,每个坐标使用x,y,z,所以第二个参数为3
 * 第三个参数是数据的类型
 * 第四个参数是否进行了归一化处理,这里写false
 * 第五个参数是跨度,这里是0,没有跨度
 * 第六个参数为0,表示从GPU读取数据
 */
 glVertexAttribPointer(positionHandler, 3, GL_FLOAT, false, 0, 0);
 // 获取片元着色器中定义的变量
 GLint colorHandler = glGetUniformLocation(program, "vColor");
 /*
 * 向片元着色器传递颜色
 * 第一个参数是变量的下标
 * 第二个参数是数据的数量,由于将所有的像素都设置成一样的颜色,所以第二个参数是1
 * 第三个参数是颜色
 */
 // 绿色
 const GLfloat DRAW_COLOR[] = { 0, 255, 0, 1.0f };
 glUniform4fv(colorHandler, 1, DRAW_COLOR);
 // 绘制三角形
 GLsizei count = sizeof(indices) / sizeof(indices[0]);
 /*
 * 绘制矩形
 * 第一个参数是绘制的图形
 * 第二个参数是顶点的个数,三角形顶点个数是3,矩形由两个三角形组成,顶点个数是6
 * 第三个数数指定数据类型
 * 第四个参数是从哪里开始读取,这里从0开始读取
 */
 glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, (const void *)0);
 // 解绑vbo
 glBindBuffer(GL_ARRAY_BUFFER, 0);
 // 解绑ebo
 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

看下绘制结果,使用EBO绘制矩形,减少顶点数组中的重复点,提高渲染效率。

image.png

三、VAO

VAO(Vertex Array Object)是顶点数组对象,是VBO的管理者,用于存储VBO对象的id。使用VBO需要在绘制的时候从GPU读取数据。有了VAO,只需在初始化的时候读取一次数据,也就是只需要执行一次glBindBuffer、glEnableVertexAttribArray、glVertexAttribPointer这些操作,这些操作会被保存到VAO中,后续绘制的时候,只需绑定VAO即可。

3、1 VAO使用流程
  • 在初始化的时候,生成并绑定VAO。
  • 绑定VBO,将顶点数据从CPU中拷贝到GPU。
  • 直接从VBO中读取数据,VBO的数据会记录到VAO中,最后解绑VBO和VAO。

bool VAOTriangleExample::init() {
 program = GLUtil::createProgram(vertexShader, fragmentShader);
 if (program == PROGRAM_ERROR) {
 LOGE("链接程序失败");
 return false;
 }
 // 生成vao对象
 glGenVertexArrays(VAO_COUNT, vaoIds);
 // 绑定vao对象
 glBindVertexArray(vaoIds[0]);

 // 生成vbo对象
 glGenBuffers(VBO_COUNT, vboIds);
 // 绑定vbo
 glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
 // 将顶点数据复制到VBO对象
 glBufferData(GL_ARRAY_BUFFER, sizeof(triangleVertices), triangleVertices, GL_STATIC_DRAW);
 /*
 * 直接从VBO中读取数据,VBO里面的数据会记录到VAO里面,绘制的时候只需要拿到VAO,进而拿到VBO的数据进行绘制
 */
 // 获取顶点着色器中定义的属性
 GLint positionHandler = glGetAttribLocation(program, "vPosition");
 // 启用顶点数组
 glEnableVertexAttribArray(positionHandler);
 /*
 * 顶点数据已经拷贝到GPU,直接从GPU读取顶点数据
 * 第一个参数是属性变量的下标
 * 第二个参数是顶点坐标的个数,我们在定义顶点坐标的时候,使用了空间坐标系,每个坐标使用x,y,z,所以第二个参数为3
 * 第三个参数是数据的类型
 * 第四个参数是否进行了归一化处理,这里写false
 * 第五个参数是跨度,这里是0,没有跨度
 * 第六个参数为0,表示从GPU读取数据
 */
 glVertexAttribPointer(positionHandler, 3, GL_FLOAT, false, 0, 0);

 // 解绑vbo
 glBindBuffer(GL_ARRAY_BUFFER, 0);
 // 解绑vao对象
 glBindVertexArray(0);
 return true;
}
3、2 VAO绘制
  • 绑定VAO。
  • 由于顶点着色器的数据在初始化的时候已经保存VAO,绘制的时候就不再需要调用glBindBuffer、glEnableVertexAttribArray、glVertexAttribPointer等函数了。
  • 向片元着色器传参。
  • 执行绘制。
  • 解绑VAO。
void VAOTriangleExample::draw() {
 glUseProgram(program);
 // 绑定vao,减少glBindBuffer、glEnableVertexAttribArray、glVertexAttribPointer这些调用操作
 glBindVertexArray(vaoIds[0]);
 // 获取片元着色器中定义的变量
 GLint colorHandler = glGetUniformLocation(program, "vColor");
 /*
 * 向片元着色器传递颜色
 * 第一个参数是变量的下标
 * 第二个参数是数据的数量,由于将所有的像素都设置成一样的颜色,所以第二个参数是1
 * 第三个参数是颜色
 */
 // 蓝色
 const GLfloat DRAW_COLOR[] = { 0, 0, 255, 1.0f };
 glUniform4fv(colorHandler, 1, DRAW_COLOR);
 // 绘制三角形
 GLsizei count = sizeof(triangleVertices) / sizeof(triangleVertices[0]) / 3;
 /*
 * 绘制三角形
 * 第一个参数是绘制的图形
 * 第二个参数是从哪里开始读取,这里从0开始读取
 * 第三个参数是顶点的数量,三角形有三个顶点,第三个参数就是3
 */
 glDrawArrays(GL_TRIANGLES, 0, count);
 // 解绑vao
 glBindVertexArray(0);
}

看下绘制效果,仍然是绘制三角形,但是使用了VAO,初始化的时候就直接从GPU中读取顶点数据,绘制的时候就不再需要调用glBindBuffer、glEnableVertexAttribArray、glVertexAttribPointer等函数了。
1725189961821.jpg

3、3 VAO使用注意事项
  • 如果只绘制一个图形,那就不要使用VAO,直接使用VBO即可。
  • 同一模型的多个VBO,可以共用一个VAO。比如,位置VBO和颜色VBO可以共用一个VAO。
  • 多个模型下,每个模型都需要使用一个VAO,多个模型不能共用一个VAO。比如,要绘制两个三角形,那就要使用两个VAO和两个VBO。

四、总结

本文介绍了VBO、EBO、VAO,在复杂情况下,VBO、EBO、VAO可以大大提升渲染性能,后续我会持续的分享OpenGL,完整代码请下载源码

### 回答1: OpenGL中的VAOVBOEBO是三种重要的缓存对象,用于存储和管理顶点数据和索引数据。 VAO(Vertex Array Object)是顶点数组对象,用于存储顶点数据的属性信息,如顶点坐标、颜色、法向量等。VAO可以将多个VBO绑定到同一个VAO中,方便管理和使用VBO(Vertex Buffer Object)是顶点缓存对象,用于存储顶点数据的实际数值,如顶点坐标、颜色、法向量等。使用VBO可以提高渲染效率,减少数据传输次数。 EBO(Element Buffer Object)是索引缓存对象,用于存储顶点索引数据,可以减少重复顶点的存储,提高渲染效率。 总之,VAOVBOEBOOpenGL中非常重要的缓存对象,可以提高渲染效率,减少数据传输次数,方便管理和使用。 ### 回答2: OpenGL是一款广泛应用于图形渲染的开源3D图形库。VAOVBOEBO这三个单词是OpenGL中的三种不同类型的缓冲区对象。 VAO,即顶点属性对象,是一个OpenGL缓冲区对象,用于存储和管理顶点属性,即描述一个图元的顶点的各种属性,如顶点坐标、颜色、法线等。VAO可以容纳多个VBO,而且可以把VBO的数据分组合起来,VBO的顶点属性索引可以绑定到VAO中,这样一来,当我们要渲染一个物体时,只需绑定一个VAO对象,就可以一次性调用所有需要的VBOVBO,即顶点缓冲区对象,是一个OpenGL缓冲区对象,用于高效存储顶点数据,如顶点坐标、顶点颜色等。当需要渲染一个物体时,我们可以从VBO中读取需要的顶点信息,然后传递给渲染管线进行处理和渲染使用VBO有一些显著的优势,比如,VBO能够提升渲染性能,减少CPU和GPU之间的数据传输,同时还可以减少内存占用。 EBO,即索引缓冲区对象,是一种存储一系列索引的OpenGL缓冲区对象。EBO中的索引是用于指定顶点顺序的,即用于描述三角形顶点之间的关系。使用EBO可以大大减少需要传输给GPU的数据量,并且可以避免重复的顶点数据,提升渲染性能。 一般情况下,我们通过VAOVBOEBO来实现复杂图形的绘制。在渲染一个物体时,首先需要创建一个VAO对象,并绑定所有需要使用VBOEBO。然后将这些缓冲区对象中的数据存储到GPU中,并在渲染时调用这些数据。最后,将VAO对象绑定到渲染管道中,执行图形渲染操作。 ### 回答3: OpenGL是一种图形库,它可以用来渲染三维场景或者二维绘图。VAOVBOEBO都是OpenGL中的重要概念,用于优化渲染性能和提高代码可读性。 VAO(Vertex Array Object)是一个顶点数组对象,它保存了一组顶点数据的状态。每个顶点包含诸如位置、法向量、纹理坐标等属性。VAO可以将这些属性打包成一个容器,使得OpenGL可以更快地访问和处理数据。VAO通常包括各种属性缓冲对象(VBO),它们保存了不同类型的属性数据。 VBO(Vertex Buffer Object)是OpenGL中的缓冲对象,用于存储顶点数据。VBO可以存储各种顶点属性,如位置、法向量、颜色等。VBO可以在GPU内存中创建,并且可以被透明地映射到CPU内存。这意味着我们可以直接在CPU内存中修改VBO的数据,而不必将其复制到GPU中。 EBO(Element Buffer Object)是一个索引缓冲对象,它充当索引数组的角色。EBO中存储的是顶点的索引,这些索引对应于VBO中存储的实际顶点数据。使用EBO可以减少顶点数据的冗余性。例如,在多个三角形之间共享相同的顶点数据时,可以使用EBO来避免重复数据的存储。 总之,VAOVBOEBOOpenGL中的重要工具,它们可以大大提高渲染性能和代码可读性。VAO提供了一种整理顶点属性的方法,VBO可以高效地管理顶点数据,而EBO可以减少数据冗余。对于OpenGL编程来说,深入了解VAOVBOEBO是非常重要的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值