OpenGL ES入门04-OpenGL ES VAO VBO缓存技术

前言

本文是关于OpenGL ES的系统性学习过程,记录了自己在学习OpenGL ES时的收获。
这篇文章的目标是通过OpenGL ES 3.0的VAO,VBO等缓存技术进行贝塞尔曲线的绘制。如果不明白贝塞尔曲线原理,请参考之前的博客 理解与运用贝塞尔曲线
环境是Xcode8.1+OpenGL ES 3.0
目前代码已经放到github上面,OpenGL ES入门04-OpenGL ES VAO VBO FBO缓存

欢迎关注我的 OpenGL ES入门专题

实现效果

贝塞尔曲线

顶点缓存VBO

普通的顶点数组的传输,需要在绘制的时候频繁地从CPU到GPU传输顶点数据,这种做法效率低下,为了加快显示速度,显卡增加了一个扩展 VBO (Vertex Buffer object),即顶点缓存。它直接在 GPU 中开辟一个缓存区域来存储顶点数据,因为它是用来缓存储顶点数据,因此被称之为顶点缓存。使用顶点缓存能够大大较少了CPU到GPU 之间的数据拷贝开销,因此显著地提升了程序运行的效率。

  • 创建顶点缓存对象
void glGenBuffers (GLsizei n, GLuint* buffers);

参数 n : 表示需要创建顶点缓存对象的个数
参数 buffers :用于存储创建好的顶点缓存对象句柄

  • 将顶点缓存对象设置为当前数组缓存对象
void glBindBuffer (GLenum target, GLuint buffer);

参数 target :指定绑定的目标,取值为 GL_ARRAY_BUFFER(用于顶点数据) 或 GL_ELEMENT_ARRAY_BUFFER(用于索引数据)
参数 buffer :顶点缓存对象句柄

  • 为顶点缓存对象分配空间
void glBufferData (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);

参数 target:与 glBindBuffer 中的参数 target 相同;
参数 size :指定顶点缓存区的大小,以字节为单位计数;
参数 data :用于初始化顶点缓存区的数据,可以为 NULL,表示只分配空间,之后再由 glBufferSubData 进行初始化;
参数 usage :表示该缓存区域将会被如何使用,它的主要目的是用于对该缓存区域做何种程度的优化,比如经常修改的数据可能就会放在GPU缓存中达到快速操作的目的。

|参数|解释|
|:---:|:---:|
|GL_STATIC_DRAW|表示该缓存区不会被修改|
|GL_DYNAMIC_DRAW|表示该缓存区会被周期性更改|
|GL_STREAM_DRAW|表示该缓存区会被频繁更改|

  • 更新顶点缓冲区数据
void glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);

参数 offset: 表示需要更新的数据的起始偏移量;
参数 size: 表示需要更新的数据的个数,也是以字节为计数单位;
参数 data: 用于更新的数据;

  • 释放顶点缓存
void glDeleteBuffers (GLsizei n, const GLuint* buffers);

参数 n : 表示顶点缓存对象的个数
参数 buffers :顶点缓存对象句柄

顶点数组对象VAO

VAO的全名是Vertex ArrayObject。它不用作存储数据,但它与顶点绘制相关。
它的定位是状态对象,记录存储状态信息。VAO记录的是一次绘制中做需要的信息,这包括数据在哪里、数据格式是什么等信息。VAO其实可以看成一个容器,可以包括多个VBO。 由于它进一步将VBO容于其中,所以绘制效率将在VBO的基础上更进一步。目前OpenGL ES3.0及以上才支持顶点数组对象。

  • 创建顶点数组对象
 glGenVertexArrays (GLsizei n, GLuint* arrays) ;

参数 n : 表示顶点数组对象的个数
参数 arrays :顶点数组对象句柄

  • 将顶点数组对象设置为当前顶点数组对象
glBindVertexArray (GLuint array) ;

参数 arrays :顶点数组对象句柄

  • 释放顶点数组对象
 glDeleteVertexArrays (GLsizei n, const GLuint* arrays);

参数 n : 表示顶点数组对象的个数
参数 arrays :顶点数组对象句柄

注意:如果需要在OpenGL ES2.0上使用VAO,可以使用苹果扩展的相关的API,具体扩展如下:

GLvoid glGenVertexArraysOES(GLsizei n, GLuint *arrays)
GLvoid glBindVertexArrayOES(GLuint array);
GLvoid glDeleteVertexArraysOES(GLsizei n, const GLuint *arrays);; 
GLboolean glIsVertexArrayOES(GLuint array);

实现贝塞尔曲线

1、创建OpenGL ES3.0上下文对象,由于Opengl ES3.0不是所有的设备和iOS版本都支持,因此查看支持情况请参考 OpenGL ES入门01-OpenGL ES概述 或者参见苹果官网 设备特性

// 引入OpenGL ES3.0头文件
#import <OpenGLES/ES3/gl.h> 

- (void)setupContext
{
    // 设置OpenGLES的版本为3.0
    _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    if (!_context) {
        NSLog(@"Failed to initialize OpenGLES 3.0 context");
        exit(1);
    }
    
    // 将当前上下文设置为我们创建的上下文
    if (![EAGLContext setCurrentContext:_context]) {
        NSLog(@"Failed to set current OpenGL context");
        exit(1);
    }
}

2、创建OpenGL ES3.0相关的着色器。要想使用OpenGL ES3.0的新特性,必须要指定着色器的版本 #version 300 es 。如果不指定,则会按照2.0的版本处理,因此3.0的新特性比如in、out、layout等关键字会报错。我们通过layout(location = i)指定属性的位置,这样我们就可以不用调用glGetAttribLocation获取属性的位置了。至于编译、链接、创建着色器程序和以前2.0一样,所以不再特别指明。

#version 300 es

layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;

out vec3 outColor;

void main()
{
    gl_Position = vec4(position, 1.0);
    outColor = color;
}
#version 300 es

precision mediump float;

in vec3 outColor;

out vec4 v_color;

void main()
{
    v_color = vec4(outColor, 1.0);
}

3、创建贝塞尔曲线定点。我们通过指定一个控制点和另外两个顶点便可以通过曲线方程创建贝塞尔曲线(曲线方程见之前的文章 理解与运用贝塞尔曲线 )。我们再次将t分为100份,就可以创建100个顶点数据,然后通过线段代表曲线的近似方式便可以画出贝塞尔曲线。

- (void)setupVertexData
{
    CGPoint p1 = CGPointMake(-0.8, 0);
    CGPoint p2 = CGPointMake(0.8, 0.2);
    CGPoint control = CGPointMake(0, -0.9);
    CGFloat deltaT = 0.01;
    
    _vertCount = 1.0/deltaT;
    _vertext = (Vertex *)malloc(sizeof(Vertex) * _vertCount);
    
    // t的范围[0,1]
    for (int i = 0; i < _vertCount; i++) {
        float t = i * deltaT;
        
        // 二次方计算公式
        float cx = (1-t)*(1-t)*p1.x + 2*t*(1-t)*control.x + t*t*p2.x;
        float cy = (1-t)*(1-t)*p1.y + 2*t*(1-t)*control.y + t*t*p2.y;
        _vertext[i] = (Vertex){cx, cy, 0.0, 1.0, 0.0, 0.0};
        
        printf("%f, %f\n",cx, cy);
    }
}

4、创建顶点缓存对象VBO,在此通过GLUtil封装了一下VBO的创建。

GLuint createVBO(GLenum target, int usage, int datSize, void *data)
{
    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(target, vbo);
    glBufferData(target, datSize, data, usage);
    return vbo;
}

5、创建顶点数组对象VAO。通过VAO容易我们包裹了VBO和数组传递等操作,使用的时候只要激活当前的VAO便可以完成绘制,加快了绘制效率。

- (void)setupVAO
{
    glGenVertexArrays(1, &_vao);
    glBindVertexArray(_vao);
    
    // VBO
    GLuint vbo = createVBO(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(Vertex) * (_vertCount + 1), _vertext);
    
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), NULL);
    
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), NULL+sizeof(GLfloat)*3);
    
    glBindVertexArray(0);
}

6、绘制。只需要通过 glBindVertexArray 启用创建VAO便可以完成绘制。

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

推荐阅读更多精彩内容