基于iOS的OpenGL ES入門


學(xué)習(xí)動(dòng)機(jī):

  • 問(wèn)題:最近在做直播項(xiàng)目禮物動(dòng)畫,要求mp4格式文件進(jìn)行播放,由于MP4視頻幀只有RGB信息,沒(méi)有透明度信息,所以不能實(shí)現(xiàn)在直播間內(nèi)透明播放,導(dǎo)致播放時(shí)后面直播內(nèi)容被遮擋。

  • 思路:最后想到可以通過(guò)合成視頻畫面的方式達(dá)到視頻背景透明的效果,原理就是視頻文件包含兩部分,一部分是原視頻內(nèi)容,一部分是原視頻的黑白內(nèi)容,使用黑白部分內(nèi)容(0xFFFFFF表示alpha 1.0,0x000000表示alpha 0)對(duì)原視頻部分附加透明度,進(jìn)而實(shí)現(xiàn)視頻播放的透明背景效果。


    image.png
  • 實(shí)現(xiàn)過(guò)程
    1,捕獲到原視頻的每幀畫面CVPixelBufferRef,內(nèi)容及上圖。
    2,使用OpenGL進(jìn)行處理,為每個(gè)畫面附加透明度,最后渲染到屏幕上。


一、OpenGL是什么

全名是open graphics library , 用于渲染2d,3d圖像的跨平臺(tái),跨語(yǔ)言的應(yīng)用程序編程接口

二、OpenGL 能做什么?

可以對(duì)圖像進(jìn)行各種美顏,濾鏡,裁剪,貼紙等處理,源圖像數(shù)據(jù)可以是來(lái)自相機(jī),文件,圖片等。GPUImage框架底層就是用opengl實(shí)現(xiàn)的

三、利用OpenGL渲染幀數(shù)據(jù)并顯示

  • 導(dǎo)入頭文件#import <GLKit/GLKit.h>,GLKit.h底層使用了OpenGLES,導(dǎo)入它,相當(dāng)于自動(dòng)導(dǎo)入了OpenGLES

  • 步驟
    01-自定義圖層類型
    02-初始化CAEAGLLayer圖層屬性
    03-創(chuàng)建EAGLContext
    04-創(chuàng)建渲染緩沖區(qū)
    05-創(chuàng)建幀緩沖區(qū)
    06-創(chuàng)建著色器
    07-創(chuàng)建著色器程序
    08-創(chuàng)建紋理對(duì)象
    09-YUV轉(zhuǎn)RGB繪制紋理
    10-渲染緩沖區(qū)到屏幕
    11-清理內(nèi)存

01自定義圖層類型

//CAEAGLLayer是OpenGL專門用來(lái)渲染的圖層,
//使用OpenGL必須使用這個(gè)圖層
+ (Class)layerClass {
    return [CAEAGLLayer class];
}

02初始化CAEAGLLayer圖層屬性

 //設(shè)置圖層
    CAEAGLLayer *eaglLayer       = (CAEAGLLayer *)self.layer;
    eaglLayer.opaque = NO; //這個(gè)一定要設(shè)置  不然無(wú)法透明
    eaglLayer.backgroundColor = [UIColor clearColor].CGColor;
    /*kEAGLDrawablePropertyRetainedBacking  是否需要保留已經(jīng)繪制到圖層上面的內(nèi)容
     kEAGLDrawablePropertyColorFormat 繪制對(duì)象內(nèi)部的顏色緩沖區(qū)的格式 kEAGLColorFormatRGBA8 4*8 = 32*/
    eaglLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : [NSNumber numberWithBool:NO],
                                     kEAGLDrawablePropertyColorFormat     : kEAGLColorFormatRGBA8};

03-創(chuàng)建EAGLContext

//設(shè)置上下文
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    [EAGLContext setCurrentContext:context];
    

04-創(chuàng)建渲染緩沖區(qū)

 //創(chuàng)建渲染緩存
    glGenRenderbuffers(1, colorBufferHandle);
    glBindRenderbuffer(GL_RENDERBUFFER, *colorBufferHandle);
    
    // 把渲染緩存綁定到渲染圖層上CAEAGLLayer,并為它分配一個(gè)共享內(nèi)存。
    // 并且會(huì)設(shè)置渲染緩存的格式,和寬度
    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];

05-創(chuàng)建幀緩沖區(qū)

//創(chuàng)建幀緩存
    glGenFramebuffers(1, frameBufferHandle);
    glBindFramebuffer(GL_FRAMEBUFFER, *frameBufferHandle);
    // 把顏色渲染緩存 添加到 幀緩存的GL_COLOR_ATTACHMENT0上,就會(huì)自動(dòng)把渲染緩存的內(nèi)容填充到幀緩存,在由幀緩存渲染到屏幕
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBufferHandle);

06-創(chuàng)建著色器

  • 什么是著色器?
    通常用來(lái)處理紋理對(duì)象,并且把處理好的紋理對(duì)象渲染到幀緩存上,從而顯示到屏幕上。
    提取紋理信息,可以處理頂點(diǎn)坐標(biāo)空間轉(zhuǎn)換,紋理色彩度調(diào)整(濾鏡效果)等操作。

  • 著色器分為頂點(diǎn)著色器,片段著色器
    頂點(diǎn)著色器用來(lái)確定圖形形狀。
    片段著色器用來(lái)確定圖形渲染顏色。

  • 步驟: 1.編輯著色器代碼 2.創(chuàng)建著色器 3.編譯著色器
    只要?jiǎng)?chuàng)建一次,可以在一開(kāi)始的時(shí)候創(chuàng)建

  • 編輯著色器代碼 :glsl語(yǔ)言,應(yīng)該屬于gpu編程,如下:

//頂點(diǎn)著色器代碼
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);
}

  • 創(chuàng)建 +編譯:屬于動(dòng)態(tài)編譯
 GLint status;

//sourceString 是頂點(diǎn)or片段著色器 的代碼文本
    const GLchar *source;
    source = (GLchar *)[sourceString UTF8String];
    
//type 頂點(diǎn)or片段著色器
    *shader = glCreateShader(type); // 創(chuàng)建著色器
    glShaderSource(*shader, 1, &source, NULL);//加載著色器源代碼
    glCompileShader(*shader); //編譯著色器
    
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);//獲取完成狀態(tài)
    if (status == 0) {
        // 沒(méi)有完成就直接刪除著色器
        glDeleteShader(*shader);
        return NO;
    }

07-創(chuàng)建著色器程序

 //創(chuàng)建一個(gè)著色器程序?qū)ο?    GLuint program = glCreateProgram();
    _rgbProgram = program;
    
    //關(guān)聯(lián)著色器對(duì)象到著色器程序?qū)ο?    //綁定頂點(diǎn)著色器
    glAttachShader(program, vertShader);
    //綁定片元著色器
    glAttachShader(program, fragShader);
    
    // 綁定著色器屬性,方便以后獲取,以后根據(jù)角標(biāo)獲取
    // 一定要在鏈接程序之前綁定屬性,否則拿不到
    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;
    }
    
    /// 獲取全局參數(shù),注意 一定要在連接完成后才行,否則拿不到
    _displayInputTextureUniform = glGetUniformLocation(program, "Texture");
    
    //釋放已經(jīng)使用完畢的verShader\fragShader
    if (vertShader) {
        glDetachShader(program, vertShader);
        glDeleteShader(vertShader);
    }
    if (fragShader) {
        glDetachShader(program, fragShader);
        glDeleteShader(fragShader);
    }

//啟動(dòng)程序
    glUseProgram(rgbProgram);

08-創(chuàng)建紋理對(duì)象

通過(guò)獲取的一張一張的圖片(CVPixelBufferRef pixelBuffer),可以把圖片轉(zhuǎn)換為OpenGL中的紋理, 然后再把紋理畫到OpenGL的上下文中

  • 什么是紋理?一個(gè)紋理其實(shí)就是一幅圖像。

  • 紋理映射,我們可以把這幅圖像的整體或部分貼到我們先前用頂點(diǎn)勾畫出的物體上去.

比如繪制一面磚墻,就可以用一幅真實(shí)的磚墻圖像或照片作為紋理貼到一個(gè)矩形上,這樣,一面逼真的磚墻就畫好了。如果不用紋理映射的方法,則墻上的每一塊磚都必須作為一個(gè)獨(dú)立的多邊形來(lái)畫。另外,紋理映射能夠保證在變換多邊形時(shí),多邊形上的紋理圖案也隨之變化。

紋理映射是一個(gè)相當(dāng)復(fù)雜的過(guò)程,基本步驟如下:

1)激活紋理單元、2)創(chuàng)建紋理 、3)綁定紋理 、4)設(shè)置濾波
注意:紋理映射只能在RGBA方式下執(zhí)行

// 創(chuàng)建亮度紋理
    // 激活紋理單元0, 不激活,創(chuàng)建紋理會(huì)失敗
    glActiveTexture(GL_TEXTURE0);
    // 創(chuàng)建紋理對(duì)象
    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;
    }
    //獲取紋理對(duì)象  CVOpenGLESTextureGetName(renderTexture)
    //綁定紋理
    glBindTexture(CVOpenGLESTextureGetTarget(renderTexture), CVOpenGLESTextureGetName(renderTexture));
    
    //設(shè)置紋理濾波
    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格式則需要轉(zhuǎn)為grb格式,一般直播推流時(shí)使用的是yuv格式,此處我是播放的靜態(tài)mp4文件,所以略過(guò)格式轉(zhuǎn)換

// 在創(chuàng)建紋理之前,有激活過(guò)紋理單元,glActiveTexture(GL_TEXTURE0)
    // 指定著色器中亮度紋理對(duì)應(yīng)哪一層紋理單元
    // 這樣就會(huì)把亮度紋理,往著色器上貼
    glUniform1i(displayInputTextureUniform, 0);
    
    if (self.pixelbufferWidth != frameWidth || self.pixelbufferHeight != frameHeight) {
        CGSize normalizedSamplingSize = CGSizeMake(1.0, 1.0);
        self.pixelbufferWidth = frameWidth;
        self.pixelbufferHeight = frameHeight;
 /*       
//紋理坐標(biāo)
GLfloat quadTextureData[] = {
    0.5f, 1.0f,
    0.5f, 0.0f,
    1.0f, 1.0f,
    1.0f, 0.0f,
};

//頂點(diǎn)坐標(biāo)
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;
    }
    
    //激活A(yù)TTRIB_VERTEX頂點(diǎn)數(shù)組
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    //給ATTRIB_VERTEX頂點(diǎn)數(shù)組賦值
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, quadVertexData);
    
    //激活A(yù)TTRIB_TEXCOORD頂點(diǎn)數(shù)組
    glEnableVertexAttribArray(ATTRIB_TEXCOORD);
    //給ATTRIB_TEXCOORD頂點(diǎn)數(shù)組賦值
    glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, 0, 0, quadTextureData);
    
    //渲染紋理數(shù)據(jù)
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

10-渲染緩沖區(qū)到屏幕

- (void)displayFramebuffer:(CMSampleBufferRef)sampleBuffer{

// 因?yàn)槭嵌嗑€程,每一個(gè)線程都有一個(gè)上下文,只要在一個(gè)上下文繪制就好,設(shè)置線程的上下文為我們自己的上下文,就能繪制在一起了,否則會(huì)黑屏.
    if ([EAGLContext currentContext] != _context) {
        [EAGLContext setCurrentContext:_context];
    }
    
    // 清空之前的紋理,要不然每次都創(chuàng)建新的紋理,耗費(fèi)資源,造成界面卡頓
    [self cleanUpTextures];

    //執(zhí)行第8步 創(chuàng)建紋理對(duì)象

//設(shè)置視口大小
    glViewport(0, 0, backingWidth, backingHeight);
    //設(shè)置一個(gè)RGB顏色和透明度,接下來(lái)會(huì)用這個(gè)顏色涂滿全屏
    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
    //清除顏色緩沖區(qū)
    glClear(GL_COLOR_BUFFER_BIT);

    //執(zhí)行第9步 繪制紋理,也就是著色器提取并處理紋理

    

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

}


清理內(nèi)存

- (void)cleanUpTextures {
    if (_renderTexture) {
        CFRelease(_renderTexture);
        _renderTexture = NULL;
    }
    // 清空紋理緩存
    CVOpenGLESTextureCacheFlush(_videoTextureCache, 0);
}
- (void)dealloc {
    [self cleanUpTextures];
    
    if(_videoTextureCache) {
        CFRelease(_videoTextureCache);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容