本文所介绍的OpenGL ES 优化技巧都是以减少CPU和GPU之间的数据传递,提高绘制速度
试想一下,顶点数据是通过 CPU 传递到 GPU,但是如果每次渲染都需要传递一次,如果数据量过多,可能会导致数据传递时间过长,从而严重地影响帧率
例
有一个三角锥

实际应用到 5 个顶点数据为:
GLfloat vertexArr[] =
{
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 1.0f,//左上
0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 1.0f, 1.0f,//右上
-0.5f, -0.5f, 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,//左下
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.0f,//右下
0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.5f, 0.5f,//顶点
};
使用VBO&VAO渲染
VBO
顶点缓冲对象(Vertex Buffer Objects, VBO),它就是把系统内存中的顶点数据传递给GPU后返回的引用凭证。
我们可以提前将顶点数据传递到 GPU 内存中,CPU 每次绘制的时候只需要告诉 GPU 自己引用看显存里的那一块数据,从而就可以减少数据的传递。
VBO 的使用,我们在之前已经用过很多次了
具体过程
glGenBuffers生成 Buffer- 绑定缓存到
GL_ARRAY_BUFFER - 向
GL_ARRAY_BUFFER写入数据
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexArr), vertexArr, GL_STATIC_DRAW);
这样 VBO 所对应的 GPU 显存中就有 vertexArr 数据。
最后一个参数指定了希望 GPU 如何管理给定的数据
GL_STREAM_DRAW:数据不会或几乎不会改变。GL_DYNAMIC_DRAW: 数据会被改变很多。GL_STREAM_DRAW:数据每次绘制时都会改变。
VAO
顶点数组对象(Vertex Array Object, VAO),可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。
这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。
这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。
VAO 的使用和 VBO 特别的相似。
使用过程
glGenVertexArraysOES生成 VAO Buffer- 绑定VAO Buffer
- 执行属性绑定操作
- 解绑 VAO Buffer
GLuint vao;
glGenVertexArraysOES(1, &vao);
glBindVertexArrayOES(vao);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// 向顶点属性传递数据
// GLuint positionAttribLocation = glGetAttribLocation(program, "position");
// glEnableVertexAttribArray(positionAttribLocation);
// glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT) * 8, (GLfloat *)NULL);
// 解绑
glBindVertexArrayOES(0);
glGenVertexArraysOES等 OES 结尾的方法是苹果自己扩展的,其他平台上也会有VAO相关的方法集合,但命名会有所不同
总结
顶点数据经过 VBO 和 VAO 的优化,就不需要每次绘制前进行数据传递和属性绑定了,大大提高绘制速度
// 绘制 glBindVertexArrayOES(vao); glDrawArrays(GL_TRIANGLES, 0, vertexCount);
使用EBO渲染
在 OpenGL 系列中有简单介绍过顶点索引缓冲对象(Element Buffer Object,EBO)。
以上面的三角锥例子来说,我们可以发现真正使用到的顶点坐标,只有 5 个点,如果我们不使用 EBO,需要使用到 18 个顶点坐标数据,这其实有几个顶点叠加了,比如:底面的矩阵,渲染需要用到 6 个点的,而不是 4 个顶点,这样就产生 50% 的额外开销。
EBO 的工作方式:存储真正使用的顶点数据,再给予指定的绘制顺序。
通过 EBO 渲染,大大减少了需要传递给 GPU 的数据量,提高了渲染的性能。
GLfloat vertexArr[] =
{
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 1.0f,//左上
0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 1.0f, 1.0f,//右上
-0.5f, -0.5f, 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,//左下
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.0f,//右下
0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.5f, 0.5f,//顶点
};
// 顶点索引
GLuint indices[] =
{
0, 3, 2,
0, 1, 3,
0, 2, 4,
0, 4, 1,
2, 3, 4,
1, 4, 3,
};
使用过程
- 为索引数据创建索引缓冲 (Index Buffer Object,IBO)
glBindBuffer绑定 IBO,需要传递GL_ELEMENT_ARRAY_BUFFER当作缓冲目标glBufferData把索引复制到缓冲里glDrawElements来替换glDrawArrays函数进行绘制
// 创建
GLuint index;
glGenBuffers(1, &index);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 绘制
glDrawElements(GL_TRIANGLES, sizeof(indices) / sizeof(GLuint), GL_UNSIGNED_INT, 0);
glDrawElements函数文档

注意点
当用 EBO 的方式去渲染一个立方体,并为之贴上 2D 纹理时,会出现一个奇怪的现象。
这是因为这里的纹理方式采用的是2D 纹理贴图GLKTextureTarget2D,而给一个 3D 物体渲染一张 2D 纹理,显示是不合理的。
立方体具体该怎么正确的贴图?
使用立方体贴图
GLKTextureTargetCubeMap,会在介绍天空盒的时候进行分析