基于iOS的OpenGL ES入门


学习动机:

  • 问题:最近在做直播项目礼物动画,要求mp4格式文件进行播放,由于MP4视频帧只有RGB信息,没有透明度信息,所以不能实现在直播间内透明播放,导致播放时后面直播内容被遮挡。

  • 思路:最后想到可以通过合成视频画面的方式达到视频背景透明的效果,原理就是视频文件包含两部分,一部分是原视频内容,一部分是原视频的黑白内容,使用黑白部分内容(0xFFFFFF表示alpha 1.0,0x000000表示alpha 0)对原视频部分附加透明度,进而实现视频播放的透明背景效果。


    image.png
  • 实现过程
    1,捕获到原视频的每帧画面CVPixelBufferRef,内容及上图。
    2,使用OpenGL进行处理,为每个画面附加透明度,最后渲染到屏幕上。


一、OpenGL是什么

全名是open graphics library , 用于渲染2d,3d图像的跨平台,跨语言的应用程序编程接口

二、OpenGL 能做什么?

可以对图像进行各种美颜,滤镜,裁剪,贴纸等处理,源图像数据可以是来自相机,文件,图片等。GPUImage框架底层就是用opengl实现的

三、利用OpenGL渲染帧数据并显示

  • 导入头文件#import <GLKit/GLKit.h>,GLKit.h底层使用了OpenGLES,导入它,相当于自动导入了OpenGLES

  • 步骤
    01-自定义图层类型
    02-初始化CAEAGLLayer图层属性
    03-创建EAGLContext
    04-创建渲染缓冲区
    05-创建帧缓冲区
    06-创建着色器
    07-创建着色器程序
    08-创建纹理对象
    09-YUV转RGB绘制纹理
    10-渲染缓冲区到屏幕
    11-清理内存

01自定义图层类型

//CAEAGLLayer是OpenGL专门用来渲染的图层,
//使用OpenGL必须使用这个图层
+ (Class)layerClass {
    return [CAEAGLLayer class];
}

02初始化CAEAGLLayer图层属性

 //设置图层
    CAEAGLLayer *eaglLayer       = (CAEAGLLayer *)self.layer;
    eaglLayer.opaque = NO; //这个一定要设置  不然无法透明
    eaglLayer.backgroundColor = [UIColor clearColor].CGColor;
    /*kEAGLDrawablePropertyRetainedBacking  是否需要保留已经绘制到图层上面的内容
     kEAGLDrawablePropertyColorFormat 绘制对象内部的颜色缓冲区的格式 kEAGLColorFormatRGBA8 4*8 = 32*/
    eaglLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : [NSNumber numberWithBool:NO],
                                     kEAGLDrawablePropertyColorFormat     : kEAGLColorFormatRGBA8};

03-创建EAGLContext

//设置上下文
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    [EAGLContext setCurrentContext:context];
    

04-创建渲染缓冲区

 //创建渲染缓存
    glGenRenderbuffers(1, colorBufferHandle);
    glBindRenderbuffer(GL_RENDERBUFFER, *colorBufferHandle);
    
    // 把渲染缓存绑定到渲染图层上CAEAGLLayer,并为它分配一个共享内存。
    // 并且会设置渲染缓存的格式,和宽度
    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];

05-创建帧缓冲区

//创建帧缓存
    glGenFramebuffers(1, frameBufferHandle);
    glBindFramebuffer(GL_FRAMEBUFFER, *frameBufferHandle);
    // 把颜色渲染缓存 添加到 帧缓存的GL_COLOR_ATTACHMENT0上,就会自动把渲染缓存的内容填充到帧缓存,在由帧缓存渲染到屏幕
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBufferHandle);

06-创建着色器

  • 什么是着色器?
    通常用来处理纹理对象,并且把处理好的纹理对象渲染到帧缓存上,从而显示到屏幕上。
    提取纹理信息,可以处理顶点坐标空间转换,纹理色彩度调整(滤镜效果)等操作。

  • 着色器分为顶点着色器,片段着色器
    顶点着色器用来确定图形形状。
    片段着色器用来确定图形渲染颜色。

  • 步骤: 1.编辑着色器代码 2.创建着色器 3.编译着色器
    只要创建一次,可以在一开始的时候创建

  • 编辑着色器代码 :glsl语言,应该属于gpu编程,如下:

//顶点着色器代码
attribute vec4 Position;
attribute vec4 TextureCoords;
varying vec2 TextureCoordsVarying;

void main (void) {
    gl_Position = Position;
    TextureCoordsVarying = TextureCoords.xy;
}


//片段着色器代码
precision mediump float;

uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;

void main (void) {
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    vec4 alpha = texture2D(Texture, TextureCoordsVarying + vec2(-0.5, 0.0));
    gl_FragColor = vec4(mask.rgb, alpha.r);
}

  • 创建 +编译:属于动态编译
 GLint status;

//sourceString 是顶点or片段着色器 的代码文本
    const GLchar *source;
    source = (GLchar *)[sourceString UTF8String];
    
//type 顶点or片段着色器
    *shader = glCreateShader(type); // 创建着色器
    glShaderSource(*shader, 1, &source, NULL);//加载着色器源代码
    glCompileShader(*shader); //编译着色器
    
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);//获取完成状态
    if (status == 0) {
        // 没有完成就直接删除着色器
        glDeleteShader(*shader);
        return NO;
    }

07-创建着色器程序

 //创建一个着色器程序对象
    GLuint program = glCreateProgram();
    _rgbProgram = program;
    
    //关联着色器对象到着色器程序对象
    //绑定顶点着色器
    glAttachShader(program, vertShader);
    //绑定片元着色器
    glAttachShader(program, fragShader);
    
    // 绑定着色器属性,方便以后获取,以后根据角标获取
    // 一定要在链接程序之前绑定属性,否则拿不到
    glBindAttribLocation(program, ATTRIB_VERTEX  , "Position");
    glBindAttribLocation(program, ATTRIB_TEXCOORD, "TextureCoords");
    
    //链接程序
    if (![self linkProgram:program]) {
        //链接失败释放vertShader\fragShader\program
        if (vertShader) {
            glDeleteShader(vertShader);
            vertShader = 0;
        }
        if (fragShader) {
            glDeleteShader(fragShader);
            fragShader = 0;
        }
        if (program) {
            glDeleteProgram(program);
            program = 0;
        }
        return;
    }
    
    /// 获取全局参数,注意 一定要在连接完成后才行,否则拿不到
    _displayInputTextureUniform = glGetUniformLocation(program, "Texture");
    
    //释放已经使用完毕的verShader\fragShader
    if (vertShader) {
        glDetachShader(program, vertShader);
        glDeleteShader(vertShader);
    }
    if (fragShader) {
        glDetachShader(program, fragShader);
        glDeleteShader(fragShader);
    }

//启动程序
    glUseProgram(rgbProgram);

08-创建纹理对象

通过获取的一张一张的图片(CVPixelBufferRef pixelBuffer),可以把图片转换为OpenGL中的纹理, 然后再把纹理画到OpenGL的上下文中

  • 什么是纹理?一个纹理其实就是一幅图像。

  • 纹理映射,我们可以把这幅图像的整体或部分贴到我们先前用顶点勾画出的物体上去.

比如绘制一面砖墙,就可以用一幅真实的砖墙图像或照片作为纹理贴到一个矩形上,这样,一面逼真的砖墙就画好了。如果不用纹理映射的方法,则墙上的每一块砖都必须作为一个独立的多边形来画。另外,纹理映射能够保证在变换多边形时,多边形上的纹理图案也随之变化。

纹理映射是一个相当复杂的过程,基本步骤如下:

1)激活纹理单元、2)创建纹理 、3)绑定纹理 、4)设置滤波
注意:纹理映射只能在RGBA方式下执行

// 创建亮度纹理
    // 激活纹理单元0, 不激活,创建纹理会失败
    glActiveTexture(GL_TEXTURE0);
    // 创建纹理对象
    CVReturn error;
    error = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                         videoTextureCache,
                                                         pixelBuffer,
                                                         NULL,
                                                         GL_TEXTURE_2D,
                                                         GL_RGBA,
                                                         frameWidth,
                                                         frameHeight,
                                                         GL_BGRA,
                                                         GL_UNSIGNED_BYTE,
                                                         0,
                                                         &renderTexture);
    if (error) {
        NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", error);
    }else {
        _renderTexture = renderTexture;
    }
    //获取纹理对象  CVOpenGLESTextureGetName(renderTexture)
    //绑定纹理
    glBindTexture(CVOpenGLESTextureGetTarget(renderTexture), CVOpenGLESTextureGetName(renderTexture));
    
    //设置纹理滤波
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

09-绘制纹理

如果源视频色彩空间是yuv格式则需要转为grb格式,一般直播推流时使用的是yuv格式,此处我是播放的静态mp4文件,所以略过格式转换

// 在创建纹理之前,有激活过纹理单元,glActiveTexture(GL_TEXTURE0)
    // 指定着色器中亮度纹理对应哪一层纹理单元
    // 这样就会把亮度纹理,往着色器上贴
    glUniform1i(displayInputTextureUniform, 0);
    
    if (self.pixelbufferWidth != frameWidth || self.pixelbufferHeight != frameHeight) {
        CGSize normalizedSamplingSize = CGSizeMake(1.0, 1.0);
        self.pixelbufferWidth = frameWidth;
        self.pixelbufferHeight = frameHeight;
 /*       
//纹理坐标
GLfloat quadTextureData[] = {
    0.5f, 1.0f,
    0.5f, 0.0f,
    1.0f, 1.0f,
    1.0f, 0.0f,
};

//顶点坐标
GLfloat quadVertexData[] = {
    -1.0f, 1.0f,
    -1.0f, -1.0f,
    1.0f, 1.0f,
    1.0f, -1.0f,
};
*/
        // 左下角
        quadVertexData[0] = -1 * normalizedSamplingSize.width;
        quadVertexData[1] = -1 * normalizedSamplingSize.height;
        // 左上角
        quadVertexData[2] = -1 * normalizedSamplingSize.width;
        quadVertexData[3] = normalizedSamplingSize.height;
        // 右下角
        quadVertexData[4] = normalizedSamplingSize.width;
        quadVertexData[5] = -1 * normalizedSamplingSize.height;
        // 右上角
        quadVertexData[6] = normalizedSamplingSize.width;
        quadVertexData[7] = normalizedSamplingSize.height;
    }
    
    //激活ATTRIB_VERTEX顶点数组
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    //给ATTRIB_VERTEX顶点数组赋值
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, quadVertexData);
    
    //激活ATTRIB_TEXCOORD顶点数组
    glEnableVertexAttribArray(ATTRIB_TEXCOORD);
    //给ATTRIB_TEXCOORD顶点数组赋值
    glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, 0, 0, quadTextureData);
    
    //渲染纹理数据
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

10-渲染缓冲区到屏幕

- (void)displayFramebuffer:(CMSampleBufferRef)sampleBuffer{

// 因为是多线程,每一个线程都有一个上下文,只要在一个上下文绘制就好,设置线程的上下文为我们自己的上下文,就能绘制在一起了,否则会黑屏.
    if ([EAGLContext currentContext] != _context) {
        [EAGLContext setCurrentContext:_context];
    }
    
    // 清空之前的纹理,要不然每次都创建新的纹理,耗费资源,造成界面卡顿
    [self cleanUpTextures];

    //执行第8步 创建纹理对象

//设置视口大小
    glViewport(0, 0, backingWidth, backingHeight);
    //设置一个RGB颜色和透明度,接下来会用这个颜色涂满全屏
    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
    //清除颜色缓冲区
    glClear(GL_COLOR_BUFFER_BIT);

    //执行第9步 绘制纹理,也就是着色器提取并处理纹理

    

 /// 把上下文的东西渲染到屏幕上
    if ([EAGLContext currentContext] == context) {
        [context presentRenderbuffer:GL_RENDERBUFFER];
    }

}


清理内存

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

推荐阅读更多精彩内容