学习OpenGL ES之VBO&VAO

本系列所有文章目录

获取示例代码


本文将要介绍OpenGL ES的一个优化技巧,使用VBO和VAO减少CPU和GPU之间的数据传递,提高绘制速度。我们先来回顾一下之前绘制图形用到的代码。

// 启用Shader中的两个属性
// attribute vec4 position;
// attribute vec4 color;
GLuint positionAttribLocation = glGetAttribLocation(program, "position");
glEnableVertexAttribArray(positionAttribLocation);
GLuint colorAttribLocation = glGetAttribLocation(program, "normal");
glEnableVertexAttribArray(colorAttribLocation);
GLuint uvAttribLocation = glGetAttribLocation(program, "uv");
glEnableVertexAttribArray(uvAttribLocation);

// 为shader中的position和color赋值
// glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
// indx: 上面Get到的Location
// size: 有几个类型为type的数据,比如位置有x,y,z三个GLfloat元素,值就为3
// type: 一般就是数组里元素数据的类型
// normalized: 暂时用不上
// stride: 每一个点包含几个byte,本例中就是6个GLfloat,x,y,z,r,g,b
// ptr: 数据开始的指针,位置就是从头开始,颜色则跳过3个GLFloat的大小
glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (char *)triangleData);
glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (char *)triangleData + 3 * sizeof(GLfloat));
glVertexAttribPointer(uvAttribLocation, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (char *)triangleData + 6 * sizeof(GLfloat));
glDrawArrays(GL_TRIANGLES, 0, vertexCount);

首先激活Vertex Shader中的属性,然后绑定数据到属性上,最后使用glDrawArrays�绘制图形。在这个过程中,triangleData会从系统内存传递到GPU的内存,也就是说每次渲染都会传递一次,如果数据量过多,可能会导致数据传递时间过长,严重影响帧率。

帧率是指每秒绘制的次数,每次绘制消耗的时间越多,帧率也就越低。为了让动画看起来很平滑,帧率越高越好。不过因为屏幕的刷新频率一般都是60Hz,为了保持垂直同步,画面不容易撕裂,会限制帧率小于等于屏幕刷新频率。

VBO

如果我们可以把数据提前传递到GPU,CPU每次绘制的时候只需要告诉GPU自己引用了显存里面的哪一块数据就可以减少数据的传递了。这就是VBO做的事情,VBO全称Vertex Buffer Object,它就是把系统内存中的顶点数据传递给GPU后返回的引用凭证。创建的代码如下。

- (GLfloat *)cubeData {
    static GLfloat cubeData[] = {
        // X轴0.5处的平面
        0.5,  -0.5,    0.5f, 1,  0,  0, 0, 0,
        0.5,  -0.5f,  -0.5f, 1,  0,  0, 0, 1,
        0.5,  0.5f,   -0.5f, 1,  0,  0, 1, 1,
        0.5,  0.5,    -0.5f, 1,  0,  0, 1, 1,
        0.5,  0.5f,    0.5f, 1,  0,  0, 1, 0,
        0.5,  -0.5f,   0.5f, 1,  0,  0, 0, 0,
        // X轴-0.5处的平面
        -0.5,  -0.5,    0.5f, -1,  0,  0, 0, 0,
        -0.5,  -0.5f,  -0.5f, -1,  0,  0, 0, 1,
        -0.5,  0.5f,   -0.5f, -1,  0,  0, 1, 1,
        -0.5,  0.5,    -0.5f, -1,  0,  0, 1, 1,
        -0.5,  0.5f,    0.5f, -1,  0,  0, 1, 0,
        -0.5,  -0.5f,   0.5f, -1,  0,  0, 0, 0,
        
        -0.5,  0.5,  0.5f, 0,  1,  0, 0, 0,
        -0.5f, 0.5, -0.5f, 0,  1,  0, 0, 1,
        0.5f, 0.5,  -0.5f, 0,  1,  0, 1, 1,
        0.5,  0.5,  -0.5f, 0,  1,  0, 1, 1,
        0.5f, 0.5,   0.5f, 0,  1,  0, 1, 0,
        -0.5f, 0.5,  0.5f, 0,  1,  0, 0, 0,
        -0.5, -0.5,   0.5f, 0,  -1,  0, 0, 0,
        -0.5f, -0.5, -0.5f, 0,  -1,  0, 0, 1,
        0.5f, -0.5,  -0.5f, 0,  -1,  0, 1, 1,
        0.5,  -0.5,  -0.5f, 0,  -1,  0, 1, 1,
        0.5f, -0.5,   0.5f, 0,  -1,  0, 1, 0,
        -0.5f, -0.5,  0.5f, 0,  -1,  0, 0, 0,
        
        -0.5,   0.5f,  0.5,   0,  0,  1, 0, 0,
        -0.5f,  -0.5f,  0.5,  0,  0,  1, 0, 1,
        0.5f,   -0.5f,  0.5,  0,  0,  1, 1, 1,
        0.5,    -0.5f, 0.5,   0,  0,  1, 1, 1,
        0.5f,  0.5f,  0.5,    0,  0,  1, 1, 0,
        -0.5f,   0.5f,  0.5,  0,  0,  1, 0, 0,
        -0.5,   0.5f,  -0.5,   0,  0,  -1, 0, 0,
        -0.5f,  -0.5f,  -0.5,  0,  0,  -1, 0, 1,
        0.5f,   -0.5f,  -0.5,  0,  0,  -1, 1, 1,
        0.5,    -0.5f, -0.5,   0,  0,  -1, 1, 1,
        0.5f,  0.5f,  -0.5,    0,  0,  -1, 1, 0,
        -0.5f,   0.5f,  -0.5,  0,  0,  -1, 0, 0,
    };
    return cubeData;
}
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, 36 * 8 * sizeof(GLfloat), [self cubeData], GL_STATIC_DRAW);

首先使用glGenBuffers生成Buffer,然后绑定缓存到GL_ARRAY_BUFFER,然后向GL_ARRAY_BUFFER中写数据。这样vbo所对应的GPU显存中就有cubeData的数据了。glBufferData最后一个参数GL_STATIC_DRAW表示这块数据不会改变。如果你想在后面改变vbo对应的数据的话,可以改为GL_DYNAMIC_DRAW或者GL_STREAM_DRAW,前者适合低频的修改,后者适合高频修改。

只要再次调用glBindBufferglBufferData就可以修改vbo对应的数据了。

有了VBO之后,绘制代码可以修改成。

glBindBuffer(GL_ARRAY_BUFFER, vbo);

// 启用Shader中的两个属性
// attribute vec4 position;
// attribute vec4 color;
GLuint positionAttribLocation = glGetAttribLocation(program, "position");
glEnableVertexAttribArray(positionAttribLocation);
GLuint colorAttribLocation = glGetAttribLocation(program, "normal");
glEnableVertexAttribArray(colorAttribLocation);
GLuint uvAttribLocation = glGetAttribLocation(program, "uv");
glEnableVertexAttribArray(uvAttribLocation);

// 为shader中的position和color赋值
// glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
// indx: 上面Get到的Location
// size: 有几个类型为type的数据,比如位置有x,y,z三个GLfloat元素,值就为3
// type: 一般就是数组里元素数据的类型
// normalized: 暂时用不上
// stride: 每一个点包含几个byte,本例中就是6个GLfloat,x,y,z,r,g,b
// ptr: 数据开始的指针,位置就是从头开始,颜色则跳过3个GLFloat的大小
glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (char *)NULL);
glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (char *)NULL + 3 * sizeof(GLfloat));
glVertexAttribPointer(uvAttribLocation, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (char *)NULL + 6 * sizeof(GLfloat));

首先要绑定你的VBOglBindBuffer(GL_ARRAY_BUFFER, vbo);,这样在绑定属性数据时就不再需要传递顶点数据triangleData了,直接改为NULL即可。现在数据传递已经被优化了,那么绑定属性数据这个步骤是不是也可以被优化呢?当然可以,这就是VAO做的事情。

VAO

VAO全称Vertex Array Object,无格式的顶点数据VBO转化为固定顶点格式的数组VAO,就可以被Vertex Shader直接使用了。创建过程如下。

glGenVertexArraysOES(1, &vao);
glBindVertexArrayOES(vao);

glBindBuffer(GL_ARRAY_BUFFER, vbo);
[self.context bindAttribs:NULL];

glBindVertexArrayOES(0);

bindAttribs实现如下。

- (void)bindAttribs:(GLfloat *)triangleData {
    // 启用Shader中的两个属性
    // attribute vec4 position;
    // attribute vec4 color;
    GLuint positionAttribLocation = glGetAttribLocation(program, "position");
    glEnableVertexAttribArray(positionAttribLocation);
    GLuint colorAttribLocation = glGetAttribLocation(program, "normal");
    glEnableVertexAttribArray(colorAttribLocation);
    GLuint uvAttribLocation = glGetAttribLocation(program, "uv");
    glEnableVertexAttribArray(uvAttribLocation);
    
    // 为shader中的position和color赋值
    // glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
    // indx: 上面Get到的Location
    // size: 有几个类型为type的数据,比如位置有x,y,z三个GLfloat元素,值就为3
    // type: 一般就是数组里元素数据的类型
    // normalized: 暂时用不上
    // stride: 每一个点包含几个byte,本例中就是6个GLfloat,x,y,z,r,g,b
    // ptr: 数据开始的指针,位置就是从头开始,颜色则跳过3个GLFloat的大小
    glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (char *)triangleData);
    glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (char *)triangleData + 3 * sizeof(GLfloat));
    glVertexAttribPointer(uvAttribLocation, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (char *)triangleData + 6 * sizeof(GLfloat));
}

glGenVertexArraysOES等OES结尾的方法都是苹果自己扩展的,在其他平台上也会有VAO相关的方法集合,但名字会有所不同。VAO的创建过程是生成VAO Buffer,绑定VAO Buffer,执行属性绑定的操作,最后解绑VAO Buffer。这样VBO就转化成了VAO。绘制的代码变成如下。

glBindVertexArrayOES(vao);
glDrawArrays(GL_TRIANGLES, 0, vertexCount);

这样一来,顶点数据经过VBO和VAO的优化,就不需要每次绘制前进行数据传递和属性绑定了,大大提高了绘制速度。

重构

本文的例子做了一些重构,创建了可渲染物体的基类GLObject,方便重用。Cube类继承GLObject,使用VAO绘制带有纹理的正方体。GLContext中增加了使用VBO和VAO绘制三角形的方法。对重构部分有兴趣的可以自行阅读源码。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容