0%

OpenGL性能优化

课程中我已经将Shader的一些基本语法都向你介绍清楚了,这篇文章我们来聊聊OpenGL的性能优化。

我们使用GPU时,一个非常重要的优化点是CPU与GPU之间的数据传输。一个重要原则是:使用Shader时尽量不要在CPU与GPU之间频繁的传输数据。在CPU与GPU之间频繁的传输数据会大大降低程序的执行效率,严重时甚至不如直接使用CPU进行渲染。

为了解决这个问题,OpenGL为我们提供了几种优化方法,如VBO、EBO与VAO。每种方法都有其特定的应用场景,下面我们就分别讲解一下这几种方法该如何使用。

VBO

首先我们来看一下VBO(Vertex Buffer Object),其作用是在GPU中存放顶点数据。利用VBO技术,我们可以将主内存中的顶点数据一次性拷贝到GPU中,来提高处理效率。

具体操作步骤如下:

  • 创建VBO ID,并将其绑定到GPU中
  • 将主内存中的顶点数据复制到VBO中
  • 从VBO中读取顶点数据绘制模型
  • 解绑VBO对象

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
//创建VBO对象
GLES30.glGenBuffers(1, vboId, 0)
//与GPU绑定到一起
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboId[0])
//拷贝数据到GPU中
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,
bb.capacity()/*数据的长度*/,
bb/*数据起始位置*/,
GLES30.GL_STATIC_DRAW)
...
//如何读取数据
GLES30.glVertexAttribPointer(mPositionHandle/*Shader程序数据接收点*/,
3/*每次读取的个数*/,
GLES30.GL_FLOAT/*数据的类型*/,
false,
5*Float.SIZE_BYTES/*数据间隔*/,
0/*从GPU读数据*/)
//绘制矩型
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4)
//解绑VBO
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)

上面的代码很简单,就是按照创建VBO对象、与GPU绑定、拷贝数据到GPU……这样一个步骤来实现的。代码中每一行的作用我都做了注释,通过注释相信你很容易理解每一行的作用了。

EBO

了解了VBO之后,接下来咱们再来看看EBO的作用。

EBO(Element Buffer Object)也称为IBO(Index Buffer Object),其作用是缓存顶点索引值。在OpenGL中,每个顶点对应一个索引值,而通过索引仠我们就可以找到某个顶点。

需要注意的是,使用EBO时,我们必须先使用VBO将顶点坐标保存到GPU中,这样索引值才能与顶点坐标一一对应。

EBO的使用与VBO是类似的,其使用步骤如下:

  • 生成 EBO ID
  • 将 EBO ID与GPU绑定
  • 从主内存中拷贝数据到GPU
  • 使用glDrawElement绘制模型
  • 解绑EBO

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
...
//VBO相关代码
...
//创建EBO ID
GLES30.glGenBuffers(1, eboId, 0)
//将EBO ID与GPU绑定
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, eboId[0])
//将数据拷贝到GPU中
GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER,
idxBuffer.capacity()*4, //数据的长度
idxBuffer, //存放数据的地址
GLES30.GL_STATIC_DRAW)
...
GLES30.glVertexAttribPointer(mIndexHandle,
1, /*每次读取数据的个数*/
GLES30.GL_FLOAT, /*数据类型 */
false,
16 /*数据间隔 */,
12 /*从GPU读取数据的起始位置*/)

//绘制三角形
GLES30.glDrawElements(GLES30.GL_TRIANGLES,
indics.size,
GLES30.GL_UNSIGNED_INT,
0)

//解绑VBO
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)
//解绑EBO
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER,0)
...

使用EBO与使用VBO非常类似,都是先创建EBO ID,之后绑定到GPU,再将顶点Index拷贝到GPU。最大的区别是绘制模型的触发函数,VBO使用的是glDrawArrays方法,而EBO使用的是glDrawElements方法。

VAO

最后我们来看一下VAO。VAO(Vertex Array Object)的作用是存放VBO ID,我们将多个VBO ID存入在VAO中。换句话说,你可以认为VAO是VBO的管理者。如果每个VBO是一个模型的话,那么VAO可以同时管理多个模型。

通过上面的描述我们可以知道,使用VAO的前提是先构建VBO,尤其是有多个VBO时使用VAO才有意义,否则没有必要使用VAO。

如何使用VAO呢?使用步骤如下:

  • 创建 VAO ID
  • 将 VAO ID 与GPU绑定
  • 当创建并绑定VBO时,它会被自动保存到VAO中
  • 绘制模型
  • 解绑VAO

来看一下具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
//创建VAO ID
GLES30.glGenVertexArrays(1, vaoId, 0)
//与GPU进行绑定
GLES30.glBindVertexArray(vaoId[0])

//创建VBO相关的代码
...

GLES30.glVertexAttribPointer(mPositionHandle, //Shader接入点
3, /*每次读取的个数*/
GLES30.GL_FLOAT/*数据的类型*/,
false,
0/*数据间隔*/,
0/*从GPU读数据的起始位置*/)

//绘制三角型
GLES30.glDrawArrays(GLES30.GL_TRIANGLES,
0,
triangleCoords.size / 3)

//解绑VAO
GLES30.glBindVertexArray(0)
...

通过上面的代码我们可以知道,使用VAO与直接使用VBO差别不大,只是在一开始创建了VAO ID,并将它绑定到了GPU上,之后触发模型绘制之后解绑VAO就完事儿了。正如我们前面所介绍的,VAO就是用于存放VBO的,有了该对象可以让我们同时处理多个VBO,否则的话每次只能处理一个,就会很麻烦。

小结

上面我就将使用VBO、EBO和VAO提高Shader工作效率的方法向你介绍清楚了。通过这篇文章我们可以知道,EBO和VAO都是以VBO为基础的,如果不有VBO我们就使用不了EBO和VAO。

另外我们还要清楚,之所以使用VBO、EBO、VAO可以提高Shader的工作效率是因为它们会先将顶点数据拷贝到GPU中,这样以后每次用到顶点数据时可以直接从GPU获取,而不必频繁的在CPU与GPU之间传输数据,这才是VBO、EBO、VAO可以提升效率的原因。

参考资料

系统玩转OpenGL+AI,实现各种酷炫视频特效

欢迎关注我的其它发布渠道