OpenGL 学习教程
Android OpenGL ES 学习(一) – 基本概念
Android OpenGL ES 学习(二) – 图形渲染管线和GLSL
Android OpenGL ES 学习(三) – 绘制平面图形
Android OpenGL ES 学习(四) – 正交投屏
Android OpenGL ES 学习(五) – 渐变色
Android OpenGL ES 学习(六) – 使用 VBO、VAO 和 EBO/IBO 优化程序
Android OpenGL ES 学习(七) – 纹理
代码工程地址: https://github.com/LillteZheng/OpenGLDemo.git

上一篇 Android OpenGL ES 学习(五) – 渐变色 ,我们已经完成了 三角形的渐变色处理。

这一章,我们学习 GL3.0 特有的 OpenGL 缓存对象,VBO ,VAO 和 EBO。这一章稍微有点吃理解,建议多看多想。

一. VBO

顶点缓冲对象:Vertex Buffer Object,VBO
为什么有这个对象?从Android OpenGL ES 学习(二) – 图形渲染管线和GLSL 这章知道,图形渲染管线的顶点数据是在 CPU 上的,我们需要调用 GL 的指令,把这些数据加载到 GPU 中,每绘制一个顶点,都需要加载一次。
当数据小的时候可以忽略不计,但如果有非常大的数据,频繁的在 CPU 和 GPU 之间传递呢,比如渲染图片,视频。

再比如,我们的渐变色三角形,就18个顶点数据,我们会把它作为输入发送给图形渲染管线的顶点着色器,它会在 CPU 上创建内存,并存储这些顶点数据,然后再把它传给 CPU 。
但如果不止 18 点呢,这里有上万点呢,这无疑是很耗性能的。所以,GL在 3.0 版本,引入 VBO 这个顶点缓存对象,它会在 GPU 内存(显存) 中存储大量顶点,然后我们可以一次性把大批数据发送给显存,而不是每个顶点发送一次;

从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。

了解原理,看看怎么使用。

1.1 使用 VBO

VBO 的使用非常简单,步骤如下:

  1. 创建缓存区:glGenBuffers
  2. 绑定缓存区到上下文: glBindBuffer
  3. 将顶点数据存在缓冲区: glBindData
  4. 指定如何解析顶点属性数组:glVertexAttribPointer
  5. 绘制:glDrawArrays

创建:

//创建缓存区
val vbo = IntArray(1)
GLES30.glGenBuffers(1,vbo,0)
//绑定缓存区到上下文
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[0])
//将顶点数据存在缓冲区
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,vertexData.capacity() * 4,vertexData,GLES30.GL_STATIC_DRAW)

主要看 glBufferData 这个方法,它是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数,参数如下:

  1. buffer 对象类型: 有GL_ARRAY_BUFFER,GL_ELEMENT_ARRAY_BUFFER,GL_SHADER_STORAGE_BUFFER 等等。
  2. 传输数据大小((以字节为单位)): 填写 buffer 大小,由于是 float类型,乘以4
  3. 实际数据
  4. 告诉显卡如果管理给定的数据,它有三种形式
    • GL_STATIC_DRAW :数据不会或几乎不会改变
    • GL_DYNAMIC_DRAW:数据会被改变很多。
    • GL_STREAM_DRAW :数据每次绘制时都会改变。

因为三角形的数据不会改变,每次都一样,所以使用 GL_STATIC_DRAW。

使用数据

//绘制位置,注意这里,我们不再填入 vertexData,而是填入数据偏移地址
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT,false, 24, 0
)
GLES30.glEnableVertexAttribArray(0)//绘制颜色,颜色地址偏移量从3开始,前面3个为位置
vertexData.position(3)
GLES30.glVertexAttribPointer(1, 3, GLES30.GL_FLOAT,false, 24, 12 //需要指定颜色的地址 3 * 4
)
GLES30.glEnableVertexAttribArray(1)//解绑数据,因为我们不需要动态更新
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)

注意绘制索引时 glVertexAttribPointer,我们不再填入顶点内存数据,因为我们已经把数据关联到 VBO 上了,所以填入数据偏移地址。
由于位置是首位,所以偏移地址是0,颜色是 3 * 4.

绘制
绘制也比较简单,直接使用 vbo 就可以了。

GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[0])
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP,0,3)

效果如下,熟悉的三角形:

二. VAO

顶点数组对象:Vertex Array Object,VAO
前面说道 VBO 会把顶点数据存到 GPU 显存中,但怎么使用呢?并没有人管理,GL 并不知道怎么去拿这些数据。
比如,我们有这个一个场景,要画两个三角形,构成一个矩形,按照我们上面的做法,也需要两个 VBO 。

顶点数据:

//第一个三角形private val POINT_COLOR_DATA = floatArrayOf(// positions         // colors0.5f, 0.5f, 0.0f,   1.0f, 0.5f, 0.5f,// 右上角0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 1.0f,// 右下角-0.5f, 0.5f, 0.0f,  0.0f, 0.5f, 1.0f,// 左上角)//第二个三角形private val POINT_COLOR_DATA2 = floatArrayOf(// positions         // colors0.5f, -0.5f, 0.0f,  1.0f, 0.5f, 0.5f,// 右下角-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f,// 左下角-0.5f, 0.5f, 0.0f,   0.0f, 0.5f, 1.0f,// 左上角)

创建两个vbo

  val vbo = IntArray(2)
private fun useVaoVbo(){//绑定第一个 vboval vertexData = BufferUtil.createFloatBuffer(POINT_COLOR_DATA)GLES30.glGenBuffers(2,vbo,0)GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[0])GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,vertexData.capacity() * 4,vertexData,GLES30.GL_STATIC_DRAW)//绘制位置GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT,false, 24, 0)GLES30.glEnableVertexAttribArray(0)//绘制颜色,颜色地址偏移量从3开始,前面3个为位置vertexData.position(3)GLES30.glVertexAttribPointer(1, 3, GLES30.GL_FLOAT,false, 24, 12 //需要指定颜色的地址 3 * 4)GLES30.glEnableVertexAttribArray(1)//解绑数据GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)//绑定第二个 vboval vertexData2 = BufferUtil.createFloatBuffer(POINT_COLOR_DATA2)GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[1])GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,vertexData2.capacity() * 4,vertexData2,GLES30.GL_STATIC_DRAW)//绘制位置GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT,false, 24, 0)GLES30.glEnableVertexAttribArray(0)//绘制颜色,颜色地址偏移量从3开始,前面3个为位置vertexData2.position(3)GLES30.glVertexAttribPointer(1, 3, GLES30.GL_FLOAT,false, 24, 12 //需要指定颜色的地址 3 * 4)GLES30.glEnableVertexAttribArray(1)//解绑数据GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)}

绘制:

//绘制第一个三角形,右上角
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[0])
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP,0,3)


咦,我们的第一个顶点数据的数据为:

//第一个三角形
private val POINT_COLOR_DATA = floatArrayOf(// positions         // colors0.5f, 0.5f, 0.0f,   1.0f, 0.5f, 0.5f,// 右上角0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 1.0f,// 右下角-0.5f, 0.5f, 0.0f,  0.0f, 0.5f, 1.0f,// 左上角
)

应该是下图才对,

怎么变成第二个顶点的三角形形状了呢?
因为前面我们创建了两个 VBO ,使用 glBufferData 后,VBO[0] 就发送给显卡内存了,同理 VBO[1] 也发给过去,但是GPU 在拿这些数据的时候,并不知道这些数据的地址,就拿了最新的,所以才会显示 VBO[1] 的数据。
怎么处理呢?

回到刚才的 VAO ,它会记录 Buffer Object 中缓存顶点属性的缓存对象的配置信息,相当于我们可以通过 VAO 准确告诉 GPU 缓存对象的地址。如下图:

它的创建跟 VBO 差不多,只不过方法不一样:

  1. 创建 VAO: glGenVertexArrays
  2. 绑定 VAO : glBindVertexArray ,需要注意,这里绑定了 VAO 后,再绑定 VBO,这样他们才能关联起来。
  3. 解绑数据:GLES30.glBindVertexArray(0)

这样,我们修改一下上面的代码:

private val vao = IntArray(2)
private fun useVaoVbo(){val vbo = IntArray(2)val vertexData = BufferUtil.createFloatBuffer(POINT_COLOR_DATA)//创建 VAOGLES30.glGenVertexArrays(2,vao,0)// //创建 VBOGLES30.glGenBuffers(2,vbo,0)//绑定 VAO ,之后再绑定 VBOGLES30.glBindVertexArray(vao[0])//绑定VBOGLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[0])GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,vertexData.capacity() * 4,vertexData,GLES30.GL_STATIC_DRAW)//绘制位置GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT,false, 24, 0)GLES30.glEnableVertexAttribArray(0)//绘制颜色,颜色地址偏移量从3开始,前面3个为位置vertexData.position(3)GLES30.glVertexAttribPointer(1, 3, GLES30.GL_FLOAT,false, 24, 12 //需要指定颜色的地址 3 * 4)GLES30.glEnableVertexAttribArray(1)//解绑数据GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)GLES30.glBindVertexArray(0)//绑定第二个 vboval vertexData2 = BufferUtil.createFloatBuffer(POINT_COLOR_DATA2)// GLES30.glBindVertexArray(vao[1])GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[1])GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,vertexData2.capacity() * 4,vertexData2,GLES30.GL_STATIC_DRAW)//绘制位置GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT,false, 24, 0)GLES30.glEnableVertexAttribArray(0)//绘制颜色,颜色地址偏移量从3开始,前面3个为位置vertexData2.position(3)GLES30.glVertexAttribPointer(1, 3, GLES30.GL_FLOAT,false, 24, 12 //需要指定颜色的地址 3 * 4)GLES30.glEnableVertexAttribArray(1)//解绑数据GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)GLES30.glBindVertexArray(0)
}

绘制,使用 vao

//绘制第一个三角形
GLES30.glBindVertexArray(vao[0])
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP,0,3)

可以看到,我们第一个三角形可以被正确识别了

三. EBO / IBO

元素缓冲对象:Element Buffer Object,EBO 或 索引缓冲对象 Index Buffer Object,IBO

什么叫元素缓冲对象呢,你可以简单理解成 顶点数据复用,举个栗子:
上面我们要画一个矩形,需要用到两个三角形,顶点如下:

//第一个三角形
private val POINT_COLOR_DATA = floatArrayOf(// positions         // colors0.5f, 0.5f, 0.0f,   1.0f, 0.5f, 0.5f,// 右上角0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 1.0f,// 右下角-0.5f, 0.5f, 0.0f,  0.0f, 0.5f, 1.0f,// 左上角
)
//第二个三角形
private val POINT_COLOR_DATA2 = floatArrayOf(// positions         // colors0.5f, -0.5f, 0.0f,  1.0f, 0.5f, 0.5f,// 右下角-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f,// 左下角-0.5f, 0.5f, 0.0f,   0.0f, 0.5f, 1.0f,// 左上角
)

但注意到,对角线的数据,是相同的,有几个顶点叠加了,一个矩形只有4个角而不是6个,这样就产生50%的额外开销。

当有成千上百个三角形时,就会产生一大堆浪费。

一个比较好的解决方案,就是存储不同的顶点,并按照顺序去绘制,这样,我们只需要4个顶点,就能绘制矩形了。而 EBO 就是这样一个缓冲对象,它会存储 GL 用来决定要绘制哪些顶点的索引。
所以,我们先定义不重复的顶点:

private val POINT_RECT_DATA2 = floatArrayOf(// 矩形4个顶点0.5f, 0.5f, 0.0f,   1.0f, 0.5f, 0.5f,// 右上角0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 1.0f,// 右下角-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f,// 左下角-0.5f, 0.5f, 0.0f,   0.0f, 0.5f, 1.0f,// 左上角)

需要按顺序的数据:

private val indeices = intArrayOf(// 注意索引从0开始!// 此例的索引(0,1,2,3)就是顶点数组vertices的下标,// 这样可以由下标代表顶点组合成矩形0, 1, 3, // 第一个三角形1, 2, 3  // 第二个三角形
)

它的创建也跟 VAO 特别像

  1. 创建对象:glGenBuffers
  2. 绑定对象到上下文:glBindBuffer
  3. 绑定顺序的数据:glBufferData ,根据前面的解释,这个对象参数,选择 GL_ELEMENT_ARRAY_BUFFER

代码如下:

//创建 ebo
GLES30.glGenBuffers(1,ebo,0)
//绑定 ebo 到上下文
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER,ebo[0])
//昂丁
GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER,indexData.capacity() * 4,indexData,GLES30.GL_STATIC_DRAW
)

跟 VBO 和 VAO 结合后的代码:

private fun useVaoVboAndEbo(){val vertexData = BufferUtil.createFloatBuffer(POINT_RECT_DATA2)
val indexData = BufferUtil.createIntBuffer(indeices)//使用 vbo,vao 优化数据传递
//创建 VAO
GLES30.glGenVertexArrays(1,vao,0)
// //创建 VBO
GLES30.glGenBuffers(1,vbo,0)
//绑定 VAO ,之后再绑定 VBO
GLES30.glBindVertexArray(vao[0])
//绑定VBO
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[0])
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,vertexData.capacity() * 4,vertexData,GLES30.GL_STATIC_DRAW)
//创建 ebo
GLES30.glGenBuffers(1,ebo,0)
//绑定 ebo 到上下文
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER,ebo[0])
//昂丁
GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER,indexData.capacity() * 4,indexData,GLES30.GL_STATIC_DRAW
)//绘制位置
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT,false, 24, 0
)
GLES30.glEnableVertexAttribArray(0)//绘制颜色,颜色地址偏移量从3开始,前面3个为位置
vertexData.position(3)
GLES30.glVertexAttribPointer(1, 3, GLES30.GL_FLOAT,false, 24, 12 //需要指定颜色的地址 3 * 4
)
GLES30.glEnableVertexAttribArray(1)GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)
GLES30.glBindVertexArray(0)
//注意顺序,ebo 要在 vao 之后
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER,0)
}

关于 EBO 为啥要再 VAO 之后,可以参考 https://www.zhihu.com/question/39082624

绘制:

GLES30.glBindVertexArray(vao[0])
GLES30.glDrawElements(GLES30.GL_TRIANGLE_STRIP,6,GLES30.GL_UNSIGNED_INT,0)

注意这里把 glDrawArrays 替换成了 glDrawElements ,表示我们要从索引缓存中渲染三角形。它的参数如下:

  1. 绘制类型,这里也是三角形 GLES30.GL_TRIANGLE_STRIP
  2. 顶点个数:6 个,实际是两个三角形
  3. 索引的类型,这里是GL_UNSIGNED_INT
  4. 最后是偏移量,这里填0即可

glDrawElements函数从当前绑定到GL_ELEMENT_ARRAY_BUFFER目标的EBO中获取其索引。但前面说道,VAO 可以关联 buffer object 的索引,所以,统一使用 VAO 去关联即可。
如下图:

可以看到,VAO 实际上关联了 VAO 和 EBO 。
效果:

参考:
https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
https://www.zhihu.com/question/39082624
https://juejin.cn/post/7149775557398364167

Android OpenGL ES 学习(六) – 使用 VBO、VAO 和 EBO/IBO 优化程序相关推荐

  1. Android OpenGL ES 学习(十一) –渲染YUV视频以及视频抖音特效

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  2. Android OpenGL ES 学习(二) -- 图形渲染管线和GLSL

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  3. Android OpenGL ES 学习(十) – GLSurfaceView 源码解析GL线程以及自定义 EGL

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  4. Android OpenGL ES 学习(九) – 坐标系统和实现3D效果

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  5. Android OpenGL ES 学习(五) -- 渐变色

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  6. Android OpenGL ES 学习(十二) - MediaCodec + OpenGL 解析H264视频+滤镜

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  7. Android OpenGl Es 学习(二):定义顶点和着色器

    概述 这是一个新的系列,学习OpengGl Es,其实是<OpenGl Es 应用开发实践指南 Android卷>的学习笔记,感兴趣的可以直接看这本书,当然这个会记录自己的理解,以下只作为 ...

  8. Android OpenGL ES(六)创建实例应用OpenGLDemos程序框架 .

    有了前面关于Android OpenGL ES的介绍,可以开始创建示例程序OpenGLDemos. 使用Eclipse 创建一个Android项目 Project Name: OpenGLDemos ...

  9. Android OpenGL ES 从入门到精通系统性学习教程

    1 为什么要写这个教程 目前这个 OpenGL ES 极简教程的更新暂时告一段落,在此之前,很荣幸获得了阮一峰老师的推荐. 因为在工作中频繁使用 OpenGL ES 做一些特效.滤镜之类的效果,加上平 ...

最新文章

  1. 5G NGC — UDM 统一数据管理
  2. 一些Java开发人员在编程中常见的雷!
  3. 蜗轮蜗杆计算软件_齿轮传动计算软件
  4. Java隐含对象实验报告,JSP隐含对象response实现文件下载
  5. Quick, Draw! Kaggle挑战赛丨现在,轮到你来猜了
  6. c#解析json字符串数组_在C#中解析Json字符串
  7. c语言优先级详解pdf,C语言符号优先级.pdf
  8. 多后端深度学习开发框架TensorlayerX发布
  9. MOS管和IGBT有什么区别?别傻傻分不清了
  10. 一年之计在于春,一日之计在于晨, 一生之计在于勤,清晨起来修手机
  11. python图像识别植物识别_python 植物识别 error_code
  12. gcc -fpic 和 -fPIC 参数问题
  13. 给HTML页面设置背景音乐
  14. 自定义ro.build.fingerprint
  15. R 语言 state 数据集的可视化
  16. 2015年《大数据》高被引论文Top10文章No.6——医疗健康大数据:应用实例与系统分析...
  17. 郑大计算机应用基础试题及答案,郑州大学远程教育计算机应用基础在线测试题库.doc...
  18. 三十天学会绘画pdf_【推荐】三十天学会绘画pdf|30天学会绘画pdf下载!
  19. 在tinycolinux上安装sandstorm davros
  20. SSD固态硬盘接口 M2

热门文章

  1. 中兴zxr10路由器重启命令_中兴路由器ZXR10 版本恢复纪要
  2. dN/dS与分子进化常用软件
  3. 美国空军如何在45天内在F-16战斗机上部署Kubernetes与Istio
  4. 外贸常用结算方式(信用证)
  5. html实现网格布局排版整齐的表格,CSS Grid 网格布局全攻略
  6. 快快快收藏!!Google内部Python代码风格指南(中文版)
  7. 享元模式(对象共享)
  8. ps神经网络滤镜安装包,ps神经网络滤镜用不了
  9. IPSEC 面试的几个小问题
  10. 嗨!摩尔信使(MThings)焕新升级