OpenGL ES在iOS中的上下文环境搭建

OpenGL ES是什么

OpenGL ES(Open Graphics Library Embedded Systems):一种跨平台的渲染技术,定义了一个跨编程语言、跨平台编程的专业图形程序接口,可用于二维或三维图像的处理与渲染。由于跨平台设计,所以在每个平台上都要有它的具体实现,即要提供OpenGL ES的上下文环境以及窗口的管理。在OpenGL的设计中,OpenGL是不负责管理窗口的,窗口的管理将交由各个设备自己来完成,上下文环境也是一样的。所以,为了在OpenGL的渲染输出和设备的屏幕之间架起一个桥梁,Khronos group创建了EGL的API。值得注意的是,不同的平台会为了适应自己的设备和窗口系统而修改EGL的API,所以要根据不同平台的需求查看文档。

OpenGL ES可以做什么

OpenGL主要是做图形图像处理的库,尤其是在移动设备上进行图形图像处理,它的性能优势更能体现出来。若要使用OpenGL ES2.0就要使用GLSL(OpenGL Shading Language)这个OpenGL的着色器语言,开发人员利用这种语言编写程序运行在GPU(Graphic Processor Unit ,图形图像处单元,可以理解为是一种高并发的运算器)上以进行图像的处理或渲染。GLSL着色器代码分为两个部分,即Vertex Shader(顶点着色器)与Fragment Shader(片元着色器)两部分,分别完成各自在OpenGL渲染管线中的功能。在知名开源库GPUImage中,可以找到大部分图形图像处理Shader的实现:亮度、对比度、饱和度、色调曲线、白平衡、灰度等颜色处理,还包括锐化、高斯模糊等图像像素处理,还有素描、卡通效果、浮雕效果等视觉效果实现,开发者也可以自己实现一些有意思的Shader,如美颜滤镜效果、瘦脸效果及粒子效果等。

iOS上的视频渲染

即接收到视频的原始数据后,利用iOS平台提供的API构造出OpenGL ES环境,然后利用统一的OpenGL程序(program)将一张图片渲染到屏幕(view)上。具体来讲,在iOS平台上使用EAGL(Embedded Apple Graphics Library)提供本地平台对OpenGL ES的实现,那么需要了解一下EAGL是什么。

EGL与EAGL

先看看EGL的工作模式,iOS开发可以大概了解一下

  • EGL最先需要知道的是在什么地方显示内容,一般通过eglGetDisplay方法返回的EGLDisplay数据结构得以知晓,而平台实现的常量EGLDEFAULTDISPLAY可以返回默认的显示位置。然后通过eglInitialize方法初始化EGL API。

    EGLDisplay display;
    display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (eglInitialize(display, NULL, NULL))  {
        // Proceed with your code.
    }
    
  • 接下来是配置。EGL知道屏幕所在后,通过eglGetConfigs或eglChooseConfigs方法配置一些包括color format、大小、透明度、pixel format等参数,EGL就可以桥接OpenGL的渲染输出和设备屏幕了。

  • 配置完后,即可以命令EGL API创建一个渲染surface,这个surface就是OpenGL渲染输出的位置。选择屏上渲染,就可以把渲染结果直接展示到屏幕上。选择离屏渲染,则可以把渲染结果输出到一个buffer或一个图片文件、一个快照或其他等。可用的方法有eglCreateWindowSurface和eglCreatePbufferSurface。

桥有两头,EGL已经知道了设备窗口系统的一侧,接下来EGL需要知道OpenGL的渲染buffer在哪。
很简单。
  • 创建一个EGL context并且设为当前context(道理上是可以创建很多context的),只要创建好context,接下来使用的Frame Buffer就可以在EGL context中使用了。
  • EGL是双缓冲的工作模式,即有一个Back Frame Buffer和一个Front Frame Buffer,正常绘制操作的目标都是Back Frame Buffer,操作完毕后,调用eglSwapBuffer这个API,将绘制完毕的FrameBuffer交换到Front Frame Buffer并显示出来。
    swap_buffers_example

重要的是EAGL,即iOS平台上搭建上下文环境

安卓平台上,使用的是EGL这一套机制。
iOS平台为OpenGL提供的实现是EAGL,OpenGL ES系统与本地窗口(UIKit)桥接由EAGL上下文系统实现。
与EGL不用的是,iOS不会让应用直接向Back Frame Buffer和Front Frame Buffer绘图,也不会让应用直接控制Back Frame Buffer和Front Frame Buffer的swap。操作系统为自己保留了这些操作,以便它可以随时使用Core Animation合成器来控制显示的最终外观
Core Animation是iOS上图形渲染和动画的核心基础架构。可以使用托管不同iOS系统(UIKit,Quartz 2D和OpenGL ES)内容的图层来合成应用的用户界面或者其他视觉显示。OpenGL ES通过CAEAGLLayer与Core Animation连接,CAEAGLLayer是一种特殊类型的Core Animation图层,它的内容来自OpenGL ES的renderbuffer,Core Animation将renderbuffer的内容与其他图层合成,并在屏幕上显示生成的图像。所以同一时刻可以有任意数量的层。Core Animation合成器会联合这些层并在后帧缓存中产生最终的像素颜色,然后切换缓存。一个应用提供的层与操作系统提供的层混合起来可以产生最终的显示外观。如下图所示,OpenGL ES层显示了一个应用生成的旋转立方体,但是在显示器顶部的显示状态栏层是由操作系统生成和控制的,此图显示的是合并两个层来产生后帧缓存中的颜色数据的过程,交换后,我们看到的就是前帧缓存上的内容。

合并Core Animation层来产生后帧缓存中的颜色数据

由于iOS平台不允许直接渲染到屏幕上,所以通过FrameBuffer和RenderBuffer来进行渲染——先创建一个RenderBuffer,然后让OpenGL ES渲染到该RenderBuffer上去。而该RenderBuffer则需要绑定到一个CAEAGLLayer上面去,这样开发者最后调用EAGLContext的presentRenderBuffer方法,就可以将那个渲染结果输出到屏幕上去了。实际上,EAGL也会执行类似EGL的swapBuffer过程,将OpenGL ES的渲染结果绘制到物理屏幕上去(view的Layer),具体步骤如下:

Core Animation 与 OpenGL ES共享renderbuffer
  • 写一个View类,继承自UIView,然后重写父类UIView的一个方法layerClass,并且返回CAEAGLLayer类型:

    + (Class)layerClass {
        return [CAEAGLLayer class];
    }
    
  • 在该View的initWithFrame方法中,获得layer并且强制类型转换为CAEAGLLayer类型的变量,同时为layer设置参数,其中包括色彩模式等属性:

    - (id)initWithFrame:(CGRect)frame {
        if (self = [super initWithFrame:frame]) {
            CAEAGLLayer *eaglLayer = (CAEAGLLayer *)[self layer];
            NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGB565,kEAGLDrawablePropertyColorFormat, nil];
            [eaglLayer setOpaque:YES];
            [eaglLayer setDrawableProperties:dict];
        }
        return self;
    }
    
  • 接下来构造 EAGLContext与RenderBuffer并且绑定到Layer上。必须为每一个线程绑定OpenGL ES上下文。所以首先必须开辟一个线程,可以使用dispatch_queue,也可以使用NSOperationQueue,设置pthread也可以,反正必须在一个线程中执行一下操作:

    • 创建OpenGL ES的上下文:
      EAGLContext *_context;
       _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
      
    • 然后实施绑定操作,设置当前上下文环境并告知EAGLContext,即该线程中的后续OpenGL ES调用将与该上下文环境绑定,若不绑定,则下面的GL调用都无返回值:
      [EAGLContext setCurrentContext:_context];
      
    此时已经为该线程绑定好了刚刚创建好的上下文环境了,也就是说已经建立好了iOS的EAGL与OpenGL ES的连接,接下来建立另一端的连接(OpenGL ES与RenderBuffer的连接,误?)
    • 创建帧缓冲区和绘制缓冲区(有说渲染缓冲区,一个意思):
      glGenFramebuffers(1, &_FrameBuffer);
      glGenRenderbuffers(1, &renderbuffer);
      
    • 绑定帧缓冲区到渲染管线 | 绑定绘制缓存区到渲染管线:
      glBindFramebuffer(GL_FRAMEBUFFER, _FrameBuffer);
      glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
      
    • 为绘制缓冲区分配存储区,此处将CAEAGLLayer的绘制存储区作为绘制缓冲区的存储区:
      [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
      
    • 获取绘制缓冲区的像素宽度和高度:
      glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
      glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
      
    • 将绘制缓冲区绑定到帧缓冲区:
      glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderbuffer);
      
    • 检查FrameBuffer的status:
      GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
      if (status != GL_FRAMEBUFFER_COMPLETE) {
          NSLog(@"failed to make complete framebuffer object %x", status);
          return FALSE;
      }
      

至此EAGL与Layer连接起来了,绘制完一帧之后(绘制过程也必须在这个线程之中),调用一下代码:[_context presentRenderbuffer:GL_RENDERBUFFER];,这样就可以将绘制的结果显示到屏幕上了。至此就搭建好了iOS平台的OpenGL ES的上下文环境,以便在此基础上进行业务开发。

帧缓存可以与层分享像素存储器

引用文章:
音视频开发进阶指南
Khronos EGL and Apple EAGL
Apple OpenGL ES Programming Guide____Drawing to Other Rendering Destinations
GPUImage
OpenGL ES 3.0 编程指南
OpenGL ES 数据可视化
OpenGL ES应用开发实践指南

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

推荐阅读更多精彩内容