iOS开发之OpenGL ES(二)—GLKit

前言:

这篇文章是作者iOS开发之OpenGL ES系列文章的第二篇,将结合一个使用OpenGL ES渲染一个三角形的Demo来讲解GLKit,以及提供给GPU的数据都应该放入缓存中,为缓存提供数据的7个步骤,虽然只有一个三角形,但是执行OpenGL ES渲染的步骤与更复杂场景的步骤是一致的。
注:(如果你觉得我的文章有所帮助,点个喜欢或关注。您的每一份鼓励都是我前进的动力😝)

本文系列第一篇:初见篇已经完结,感兴趣可以看看:
iOS开发之OpenGL ES—初见

正文:

GLKit是苹果iOS 5引入的一个为简化OpenGL ES的使用的框架,它为OpenGL ES的使用提供了相关的类和函数,GLKit是Cocoa Touch以及多个其他的框架(包含UIKit)的一部分。而GLKView和GLKViewController类名字中的GLK前缀表明这些类是GLKit框架的一部分。
GLKViewController类是支持OpenGL ES特有的行为和动画时的UIViewController的内建子类。
GLKView是Cocoa Touch UIView类的内建子类。GLKView简化了通过用Core Animation层来自动创建并管理帧缓存和渲染缓存共享内存所需要做的工作。GLKView相关的GLKViewController实例是视图的委托并接收当视图需要重绘时的消息。

OpenGL ES 01-GLKit示例:
ViewController类的interface
  • 通过#import编译指令导入GLKit框架
  • 将ViewController继承的UIViewController改为GLKViewController,使其继承GLKViewController的基本功能(如:接收当视图需要重绘时的消息),GLKViewController又从它的超类继承了很多功能。
  • vertexBufferID变量保存了用于盛放本例中用到的顶点数据的缓存的OpenGL ES标识符。
  • baseEffect属性声明了一个GLKBaseEffect实例的指针。GLKBaseEffect是GLKit提供的另一个内建类。GLKBaseEffect的存在是为了简化OpenGL ES的很多常用的操作。GLKBaseEffect隐藏了iOS设备支持的多了OPenGL ES版本间的差异。在应用中使用GLKBaseEffect能减少需要编写的代码量。
#import <GLKit/GLKit.h>
@interface ViewController : GLKViewController
{
    GLuint vertexBufferID;
}
@property (nonatomic,strong) GLKBaseEffect *baseEffect;
@end
ViewController类的实现
  • SceneVertex是一个C结构体,用来保存一个GLKVector3类型的成员positionCoords。GLKit的GLKVector3类型保存了3个坐标:X、Y和Z。
  • vertices变量是一个用顶点数据初始化的普通C数组,这个变量用来定义一个三角形。这个例子的顶点坐标是挑选出来的,因为默认的用于OpenGL上下文的可见坐标系是分别沿着X、Y、Z轴从-1.0延伸到1.0的。
typedef struct{
    GLKVector3 positionCoords;
}SceneVertex;
static const SceneVertex vertices[] = {
    {{-0.5f,-0.5f,0.0}},//lower left corner
    {{0.5f,-0.5f,0.0}},//lower right corner
    {{-0.5f,0.5f,0.0}}//upper left corner
};
  • viewDidLoad方法为OpenGL ES提供三角形的顶点数据。此方法会将它继承的view属性的值转换成GLKView类型。类似ViewController的GLKViewController的子类只能与GLKView实例或者GLKView的子类的实例一起工作。
  • OpenGL ES的上下文不仅会保存OpenGL ES的状态,还会控制GPU去执行渲染运算。在这里分配并初始化一个内建的EAGLContext类的实例,这个实例会封装夜歌特定于某个平台的OpenGL ES上下文,在任何其他的OpenGL ES配置或者渲染发生之前,应用的GLKView实例的上下文属性都需要设置为当前。EAGLContext实例既支持OpenGL ES 1.1又支持OpenGL ES 2.0和OpenGL ES 3.0。本例使用的是2.0版本。EAGLContext的方法“+setCurrentContext:”会为接下来的OpenGL ES运算设置将会用到的上下文,而“-initWithAPI:”方法传入一个kEAGLRenderingAPIOpenGLES2常量将它初始化为OpenGL ES 2.0。
  • baseEffect属性为一个新分配并初始化的GLKBaseEffect类型的实例,useConstantColor属性说明使用恒定不变的颜色,而constantColor定义了这种恒定颜色的颜色值。GLKVector4Make是GLKit中定义的用于保存4个颜色值的C数据结构体。
  • glClearColor()函数设置当前OpenGL ES的上下文的“清除颜色”为不透明白色,清除颜色由RGBA颜色元素值组成,用于在上下文的帧缓存被清除时初始化每个像素的颜色值。
  • 上一篇文章介绍了用于CPU和GPU控制的内存之间交换数据的缓存的概念。用于定义要缓存的三角形的顶点位置数据必须要发送到GPU来渲染。创建并使用一个用于保存顶点数据的顶点数组属性数组缓存。前三步骤如下:
  1. 为缓存生成一个独一无二的标识符。
    glGenBuffers(1, &vertexBufferID)
    参数1:要生成缓存标识符的数量
    参数2:指向生成的标识符的内存保存位置的指针
  2. 为接下来的运算绑定缓存。
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID)
    参数1:用于指定要绑定那一种类型的缓存,OPenGL ES2.0对于glbindBuffer()的实现只支持两种类型的缓存
    GL_ARRAY_BUFFER:顶点缓冲区对象
    GL_ELEMENT_ARRAY_BUFFER:顶点索引缓冲区对象
    参数2:要绑定缓存的标识符
  3. 复制数据到缓存中。
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    参数1:指定要更新当前上下文中所绑定的是哪一种缓存
    参数2:指定要复制这个缓存字节的数量
    参数3:复制的字节的地址
    参数4:缓存在未来的运算中可能将会被怎样使用
    GL_STATIC_DRAW提示会告诉上下文,缓存中的内容适合复制到GPU控制的内存,因为很少对其修改
    GL_DYNAMIC_DRAW会告诉上下文,缓存内的数据会频繁改变,同时提示OpenGL ES以不同的方式来处理缓存的存储
    viewDidLoad {}中的整体代码如下:
- (void)viewDidLoad {
    [super viewDidLoad];

    GLKView *view = (GLKView *)self.view;
    //使用NSAssert()函数的一个运行时检查会验证self.view是否为正确的类型
    NSAssert([view isKindOfClass:[GLKView class]], @"viewcontroller’s view is not a GLKView");
    
    view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];//分配一个新的EAGLContext的实例,并将它初始化为OpenGL ES 2.0
    [EAGLContext setCurrentContext:view.context];//在任何其他的OpenGL ES配置或者渲染之前,应用的GLKview实例的上下文属性都要设置为当前

    self.baseEffect = [[GLKBaseEffect alloc] init];
    self.baseEffect.useConstantColor = GL_TRUE;
    self.baseEffect.constantColor = GLKVector4Make(0.4f,//red
                                                   0.6f,//green
                                                   0.2f,//blue
                                                   1.0f);//alpha
    
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);//设置当前OpenGL ES的上下文的“清除颜色”为不透明白色
    /*
     *参数一:要生成缓存标识符的数量
     *参数二:指向生成的标识符的内存保存位置的指针
     */
    glGenBuffers(1, &vertexBufferID);//为缓存生成一个独一无二的标识符
    
    /*
     *参数一:用于指定要绑定那一种类型的缓存,OPenGL ES2.0对于glbindBuffer()的实现只支持两种类型的缓存
     GL_ARRAY_BUFFER:顶点缓冲区对象
     GL_ELEMENT_ARRAY_BUFFER 顶点索引缓冲区对象
     参数二:要绑定缓存的标识符
     */
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);//为接下来的应用绑定缓存
    
    /*
     *参数1:指定要更新当前上下文中所绑定的是哪一种缓存
     *参数2:指定要复制这个缓存字节的数量
     *参数3:复制的字节的地址
     *参数4:缓存在未来的运算中可能将会被怎样使用
     GL_STATIC_DRAW提示会告诉上下文,缓存中的内容适合复制到GPU控制的内存,因为很少对其修改
     GL_DYNAMIC_DRAW会告诉上下文,缓存内的数据会频繁改变,同时提示OpenGL ES以不同的方式来处理缓存的存储
     */
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
}
  • 每当一个GLKView实例需要被重绘时,他都会让保存在视图的上下文属性中的OpenGL ES上下文成为当前上下文。如果需要的话,GLKView实例会绑定一个与与一个Core Animation层分享的帧缓存,执行其他的标准的OpenGL ES配置,并发送一个消息来调用ViewController的-glkView: drawInRect:方法,-glkView: drawInRect:是GLKView的委托方法。
  • 委托方法的实现[self.baseEffect prepareToDraw],告诉baseEffect准备好当前的OpenGL ES的上下文,以便为使用baseEffect生成的属性和Shading Language程序的绘图做好准备。
  • glClear()来设置当前绑定的帧缓存的像素颜色渲染缓存中的每一个像素的颜色为前面使用glClearColor()函数设定的值。glClear()函数会有效的设置帧缓存中的每一个像素的颜色为背景色。
  • 使用缓存的前三步已经在- viewDidLoad方法中被执行了。-glkView: drawInRect:方法会执行剩下的几个步骤:
  • 启动。
    通过glEnableVertexAttribArray()来启动顶点缓存渲染操作。OpenGL ES 所支持的每一个渲染操作都可以单独的使用保存在当前OpenGL ES上下文中的设置来开启或关闭。
  • 设置指针。
    glVertexAttribPointer()函数会告诉OpenGL ES顶点数据在哪里,以及怎么解释为每个顶点保存的数据。
    glVertexAttribPointer(GLKVertexAttribPosition, 3
    , GL_FLOAT, GL_FALSE, sizeof(SceneVertex), NULL);
    参数1:指示当前绑定的缓存包含每个顶点的位置信息
    参数2:指示每个位置有三个部分
    参数3:告诉OpenGL ES每个部分都保存为一个浮点类型的值
    参数4:告诉OPenGL ES小数点固定数据是否可以被改变
    参数5:叫做步幅,他指定了每个顶点的保存需要多少个字节
    参数6:告诉OpenGL ES可以从当前绑定的顶点缓存的位置访问顶点数据
  • 绘图。
    通过调用glDrawArrays()来执行绘图。
    glDrawArrays(GL_TRIANGLES, 0, 3);
    参数1:告诉GPU怎么处理在绑定的顶点缓存内的顶点数据
    GL_POINTS //把每一个顶点作为一个点进行处理,顶点n即定义了点n,共绘制N个点
    GL_LINES //把每一个顶点作为一个独立的线段,顶点2n-1和2n之间共定义了n条线段,总共绘制N/2条线段
    GL_LINE_LOOP //绘制从第一个顶点到最后一个顶点依次相连的一组线段,然后最后一个顶点和第一个顶点相连,第n和n+1个顶点定义了线段n,总共绘制n条线段
    GL_LINE_STRIP //绘制从第一个顶点到最后一个顶点依次相连的一组线段,第n和n+1个顶点定义了线段n,总共绘制n-1条线段
    GL_TRIANGLES //把每个顶点作为一个独立的三角形,顶点3n-2、3n-1和3n定义了第n个三角形,总共绘制N/3个三角形
    GL_TRIANGLE_STRIP //绘制一组相连的四边形。每个四边形是由一对顶点及其后给定的一对顶点共同确定的。顶点2n-1、2n、2n+2和2n+1定义了第n个四边形,总共绘制N/2-1个四边形
    GL_TRIANGLE_FAN //绘制一组相连的三角形,三角形是由第一个顶点及其后给定的顶点确定,顶点1、n+1和n+2定义了第n个三角形,总共绘制N-2个三角形
    参数2:需要渲染的第一个顶点
    参数3:需要渲染的顶点的个数
    -glkView: drawInRect:中的整体代码如下:
//这个方法每帧都执行一次(循环执行),执行频率与屏幕刷新率相同,iPhone屏幕的刷新频率是60Hz
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
    [self.baseEffect prepareToDraw];
    glClear(GL_COLOR_BUFFER_BIT);
    
    //启动顶点缓存渲染操作
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    
    /*告诉OpenGL ES顶点数据在哪里,以及解释为每个顶点保存的数据
     *参数1:指示当前绑定的缓存包含每个顶点的位置信息
     *参数2:指示每个位置有三个部分
     *参数3:告诉OpenGL ES每个部分都保存为一个浮点类型的值
     *参数4:告诉OPenGL ES小数点固定数据是否可以被改变
     *参数5:叫做步幅,他指定了每个顶点的保存需要多少个字节
     *参数6:告诉OpenGL ES可以从当前绑定的顶点缓存的位置访问顶点数据
     */
    glVertexAttribPointer(GLKVertexAttribPosition, 3
                          , GL_FLOAT, GL_FALSE, sizeof(SceneVertex), NULL);
    /*绘图
     *参数1:告诉GPU怎么处理在绑定的顶点缓存内的顶点数据
     GL_POINTS //把每一个顶点作为一个点进行处理,顶点n即定义了点n,共绘制N个点
     GL_LINES //把每一个顶点作为一个独立的线段,顶点2n-1和2n之间共定义了n条线段,总共绘制N/2条线段
     GL_LINE_LOOP //绘制从第一个顶点到最后一个顶点依次相连的一组线段,然后最后一个顶点和第一个顶点相连,第n和n+1个顶点定义了线段n,总共绘制n条线段
     GL_LINE_STRIP //绘制从第一个顶点到最后一个顶点依次相连的一组线段,第n和n+1个顶点定义了线段n,总共绘制n-1条线段
     GL_TRIANGLES //把每个顶点作为一个独立的三角形,顶点3n-2、3n-1和3n定义了第n个三角形,总共绘制N/3个三角形
     GL_TRIANGLE_STRIP //绘制一组相连的四边形。每个四边形是由一对顶点及其后给定的一对顶点共同确定的。顶点2n-1、2n、2n+2和2n+1定义了第n个四边形,总共绘制N/2-1个四边形
     GL_TRIANGLE_FAN //绘制一组相连的三角形,三角形是由第一个顶点及其后给定的顶点确定,顶点1、n+1和n+2定义了第n个三角形,总共绘制N-2个三角形
     *参数2:需要渲染的第一个顶点
     *参数3:需要渲染的顶点的个数
     */
    glDrawArrays(GL_TRIANGLES, 0, 3);
  • ViewController实现的最后一个方法是-(void)dealloc{},在这个函数里删除不再需要的顶点缓存和上下文。设置vertexBufferID为0避免了在对应的缓存被删除后还使用其无效的标识符。设置视图的上下文属性为nil并设置当前的上下文为nil,以便让Cocoa Touch收回所有上下文使用的内存和其他资源。
-(void)dealloc{
    GLKView *view = (GLKView *)self.view;
    [EAGLContext setCurrentContext:view.context];
    
    if (0!=vertexBufferID) {
        glDeleteBuffers(1, &vertexBufferID);
        //设置vertexBufferID为0避免了在对应的缓存被删除后还使用其无效的标识符。
        vertexBufferID = 0;
    }
    
    //设置视图的上下文属性为nil并设置当前的上下文为nil,以便让Cocoa Touch收回所有上下文使用的内存和其他资源
    ((GLKView *)self.view).context = nil;
    [EAGLContext setCurrentContext:nil];
}

源码已上传至fenglinyunshi-git,欢迎下载,并提出宝贵意见。

结语:

本文是iOS开发之OpenGL ES的第二篇文章,主要关于GLKit以及提供给GPU的数据都应该放入缓存中,为缓存提供数据的7个步骤。如果看后你有所收获,那么我会非常高兴,如果文中有不准确的地方还望提出指证,笔者将非常感谢!
未完待续 ...

业精于勤,荒于嬉;行成于思,毁于随。

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

推荐阅读更多精彩内容