教你實現(xiàn)GPUImage【OpenGL渲染原理】

一、前言

本篇主要講解GPUImage底層是如何渲染的,GPUImage底層使用的是OPENGL,操控GPU來實現(xiàn)屏幕展示

由于網(wǎng)上OpenGL實戰(zhàn)資料特別少,官方文檔對一些方法也是解釋不清楚,避免廣大同學再次爬坑,本篇講解了不少OpenGL的知識,并且還講解了花了大量時間解決bug的注意點,曾經(jīng)因為對glDrawArrays這個方法不熟悉,遇上Bug,晚上熬到凌晨四點都沒解決,還是第二天中午解決的。

如果喜歡我的文章,可以關注我微博:袁崢Seemygo

二、GPUImageVideoCamera

  • 可以捕獲采集的視頻數(shù)據(jù)

  • 關鍵是捕獲到一幀一幀視頻數(shù)據(jù)如何展示?

  • 通過這個方法可以獲取采集的視頻數(shù)據(jù)

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
  • 采集視頻注意點:要設置采集豎屏,否則獲取的數(shù)據(jù)是橫屏
  • 通過AVCaptureConnection就可以設置
[videoConnection setVideoOrientation:AVCaptureVideoOrientationPortraitUpsideDown];

三、自定義OpenGLView渲染視頻

  • 暴露一個接口,獲取采集到的幀數(shù)據(jù),然后把幀數(shù)據(jù)傳遞給渲染View,展示出來
- (void)displayFramebuffer:(CMSampleBufferRef)sampleBuffer;

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

  • 導入頭文件#import <GLKit/GLKit.h>,GLKit.h底層使用了OpenGLES,導入它,相當于自動導入了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)建紋理對象
    • 09-YUV轉RGB繪制紋理
    • 10-渲染緩沖區(qū)到屏幕
    • 11-清理內存

01-自定義圖層類型

  • 為什么要自定義圖層類型CAEAGLLayer? CAEAGLLayer是OpenGL專門用來渲染的圖層,使用OpenGL必須使用這個圖層
#pragma mark - 1.自定義圖層類型
+ (Class)layerClass
{
    return [CAEAGLLayer class];
}

02-初始化CAEAGLLayer圖層屬性

  • 1.不透明度(opaque)=YES,CALayer默認是透明的,透明性能不好,最好設置為不透明.

  • 2.設置繪圖屬性

    • kEAGLDrawablePropertyRetainedBacking :NO (告訴CoreAnimation不要試圖保留任何以前繪制的圖像留作以后重用)
    • kEAGLDrawablePropertyColorFormat :kEAGLColorFormatRGBA8 (告訴CoreAnimation用8位來保存RGBA的值)
  • 其實設置不設置都無所謂,默認也是這個值,只不過GPUImage設置了

#pragma mark - 2.初始化圖層
- (void)setupLayer
{
    CAEAGLLayer *openGLLayer = (CAEAGLLayer *)self.layer;
    _openGLLayer = openGLLayer;
    
    // 設置不透明,CALayer 默認是透明的,透明性能不好,最好設置為不透明.
    openGLLayer.opaque = YES;
    
    // 設置繪圖屬性drawableProperties
    // kEAGLColorFormatRGBA8 : red、green、blue、alpha共8位
    openGLLayer.drawableProperties = @{
                                       kEAGLDrawablePropertyRetainedBacking :[NSNumber numberWithBool:NO],
                                      kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8
                                       };
}

03-創(chuàng)建EAGLContext

  • 需要將它設置為當前context,所有的OpenGL ES渲染默認渲染到當前上下文
  • EAGLContext管理所有使用OpenGL ES進行描繪的狀態(tài),命令以及資源信息,要繪制東西,必須要有上下文,跟圖形上下文類似。
  • 當你創(chuàng)建一個EAGLContext,你要聲明你要用哪個version的API。這里,我們選擇OpenGL ES 2.0
#pragma mark - 3、創(chuàng)建OpenGL上下文,并且設置上下文
- (void)setupContext
{
    // 指定OpenGL 渲染 API 的版本,目前都使用 OpenGL ES 2.0
    EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
    
    // 創(chuàng)建EAGLContext上下文
    _context = [[EAGLContext alloc] initWithAPI:api];
    
    // 設置為當前上下文,所有的渲染默認渲染到當前上下文
    [EAGLContext setCurrentContext:_context];
}

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

  • 有了上下文,openGL還需要在一塊buffer進行描繪,這塊buffer就是RenderBuffer

  • OpenGLES 總共有三大不同用途的color buffer,depth buffer 和 stencil buffer.

  • 最基本的是color buffer,創(chuàng)建它就好了

函數(shù)glGenRenderbuffers
函數(shù) void glGenRenderbuffers (GLsizei n, GLuint* renderbuffers)
  • 它是為renderbuffer(渲染緩存)申請一個id(名字),創(chuàng)建渲染緩存
  • 參數(shù)n表示申請生成renderbuffer的個數(shù)
  • 參數(shù)renderbuffers返回分配給renderbuffer(渲染緩存)的id
    。 注意:返回的id不會為0,id 0 是OpenGL ES保留的,我們也不能使用id 為0的renderbuffer(渲染緩存)。
函數(shù)glBindRenderbuffer
 void glBindRenderbuffer (GLenum target, GLuint renderbuffer)
  • 告訴OpenGL:我在后面引用GL_RENDERBUFFER的地方,其實是引用_colorRenderBuffer
  • 參數(shù)target必須為GL_RENDERBUFFER
  • 參數(shù)renderbuffer就是使用glGenRenderbuffers生成的id
    。 當指定id的renderbuffer第一次被設置為當前renderbuffer時,會初始化該 renderbuffer對象,其初始值為:
width 和 height:像素單位的寬和高,默認值為0;

internal format:內部格式,三大 buffer 格式之一 -- color,depth or stencil;

Color bit-depth:僅當內部格式為 color 時,設置顏色的 bit-depth,默認值為0;

Depth bit-depth:僅當內部格式為 depth時,默認值為0;

Stencil bit-depth: 僅當內部格式為 stencil,默認值為0
函數(shù)renderbufferStorage
EAGLContext方法 - (BOOL)renderbufferStorage:(NSUInteger)target fromDrawable:(id<EAGLDrawable>)drawable
  • 把渲染緩存(renderbuffer)綁定到渲染圖層(CAEAGLLayer)上,并為它分配一個共享內存。
  • 參數(shù)target,為哪個renderbuffer分配存儲空間
  • 參數(shù)drawable,綁定在哪個渲染圖層,會根據(jù)渲染圖層里的繪圖屬性生成共享內存。
    //  底層調用這個分配內存
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, _openGLLayer.bounds.size.width, _openGLLayer.bounds.size.height);

實戰(zhàn)代碼

#pragma mark - 4、創(chuàng)建渲染緩存
- (void)setupRenderBuffer
{
    glGenRenderbuffers(1, &_colorRenderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
    
    // 把渲染緩存綁定到渲染圖層上CAEAGLLayer,并為它分配一個共享內存。
    // 并且會設置渲染緩存的格式,和寬度
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_openGLLayer];

}

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

  • 它相當于buffer(color, depth, stencil)的管理者,三大buffer可以附加到一個framebuffer上

  • 本質是把framebuffer內容渲染到屏幕

函數(shù)glFramebufferRenderbuffer
void glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
  • 該函數(shù)是將相關buffer()三大buffer之一)attach到framebuffer上,就會自動把渲染緩存的內容填充到幀緩存,在由幀緩存渲染到屏幕
  • 參數(shù)target,哪個幀緩存
  • 參數(shù)attachment是指定renderbuffer被裝配到那個裝配點上,其值是GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT中的一個,分別對應 color,depth和 stencil三大buffer。
  • renderbuffertarget:哪個渲染緩存
  • renderbuffer渲染緩存id
#pragma mark - 5、創(chuàng)建幀緩沖區(qū)
- (void)setupFrameBuffer
{
    glGenFramebuffers(1, &_framebuffers);
    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffers);
    // 把顏色渲染緩存 添加到 幀緩存的GL_COLOR_ATTACHMENT0上,就會自動把渲染緩存的內容填充到幀緩存,在由幀緩存渲染到屏幕
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer);
}

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

著色器
  • 什么是著色器? 通常用來處理紋理對象,并且把處理好的紋理對象渲染到幀緩存上,從而顯示到屏幕上。
  • 提取紋理信息,可以處理頂點坐標空間轉換,紋理色彩度調整(濾鏡效果)等操作。
  • 著色器分為頂點著色器,片段著色器
    • 頂點著色器用來確定圖形形狀
    • 片段著色器用來確定圖形渲染顏色
  • 步驟: 1.編輯著色器代碼 2.創(chuàng)建著色器 3.編譯著色器
  • 只要創(chuàng)建一次,可以在一開始的時候創(chuàng)建
// shader: 指向著色器對象的句柄
// count: 著色器源代碼字符串的數(shù)量。著色器可以由多個源字符串組成,但是每個著色器只能有一個main函數(shù)
// string: 指向著色器源代碼的字符串指針
// length: 指向保存著多個(如果有多個)源代碼字符串大小的整型數(shù)組指針
void glShaderSource (GLuint shader, 
                           GLsizei count, 
                           const GLchar *const string,
                           const GLint *length );
著色器代碼
// 頂點著色器代碼
NSString *const kVertexShaderString = SHADER_STRING
(
 attribute vec4 position;
 attribute vec2 inputTextureCoordinate;
 
 varying vec2 textureCoordinate;
 
 void main()
 {
     gl_Position = position;
     textureCoordinate = inputTextureCoordinate;
 }
);

// 片段著色器代碼
NSString *const kYUVFullRangeConversionForLAFragmentShaderString = SHADER_STRING
(
 varying highp vec2 textureCoordinate;
 
 precision mediump float;
 
 uniform sampler2D luminanceTexture;
 uniform sampler2D chrominanceTexture;
 uniform mediump mat3 colorConversionMatrix;
 
 void main()
 {
     mediump vec3 yuv;
     lowp vec3 rgb;
     
     yuv.x = texture2D(luminanceTexture, textureCoordinate).r;
     yuv.yz = texture2D(chrominanceTexture, textureCoordinate).ra - vec2(0.5, 0.5);
     rgb = colorConversionMatrix * yuv;
     
     gl_FragColor = vec4(rgb, 1);
 }
);
實戰(zhàn)代碼
#pragma mark - 06、創(chuàng)建著色器
- (void)setupShader
{
    // 創(chuàng)建頂點著色器
    _vertShader = [self loadShader:GL_VERTEX_SHADER withString:kVertexShaderString];
    
    // 創(chuàng)建片段著色器
    _fragShader = [self loadShader:GL_FRAGMENT_SHADER withString:kYUVFullRangeConversionForLAFragmentShaderString];
    
}

// 加載著色器
- (GLuint)loadShader:(GLenum)type withString:(NSString *)shaderString
{
    // 創(chuàng)建著色器
    GLuint shader = glCreateShader(type);
    if (shader == 0) {
        NSLog(@"Error: failed to create shader.");
        return 0;
    }
    
    // 加載著色器源代碼
    const char * shaderStringUTF8 = [shaderString UTF8String];
    glShaderSource(shader, 1, &shaderStringUTF8, NULL);
    
    // 編譯著色器
    glCompileShader(shader);
    
    // 檢查是否完成
    GLint compiled = 0;
    
    // 獲取完成狀態(tài)
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    
    if (compiled == 0) {
        
        // 沒有完成就直接刪除著色器
        glDeleteShader(shader);
        return 0;
    }
    
    return shader;
}

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

  • 步驟: 1.創(chuàng)建程序 2.貼上頂點和片段著色器 3.綁定attribute屬性 4.連接程序 5.綁定uniform屬性 6.運行程序
  • 注意點:第3步和第5步,綁定屬性,必須有順序,否則綁定不成功,造成黑屏
#pragma mark - 7、創(chuàng)建著色器程序
- (void)setupProgram
{
    // 創(chuàng)建著色器程序
    _program = glCreateProgram();
    
    // 綁定著色器
    // 綁定頂點著色器
    glAttachShader(_program, _vertShader);
    
    // 綁定片段著色器
    glAttachShader(_program, _fragShader);
    
    // 綁定著色器屬性,方便以后獲取,以后根據(jù)角標獲取
    // 一定要在鏈接程序之前綁定屬性,否則拿不到
    glBindAttribLocation(_program, ATTRIB_POSITION, "position");
    glBindAttribLocation(_program, ATTRIB_TEXCOORD, "inputTextureCoordinate");
    
    // 鏈接程序
    glLinkProgram(_program);
    
    // 獲取全局參數(shù),注意 一定要在連接完成后才行,否則拿不到
    _luminanceTextureAtt = glGetUniformLocation(_program, "luminanceTexture");
    _chrominanceTextureAtt = glGetUniformLocation(_program, "chrominanceTexture");
    _colorConversionMatrixAtt = glGetUniformLocation(_program, "colorConversionMatrix");
    
    // 啟動程序
    glUseProgram(_program);
}

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

紋理

  • 采集的是一張一張的圖片,可以把圖片轉換為OpenGL中的紋理, 然后再把紋理畫到OpenGL的上下文中

  • 什么是紋理?一個紋理其實就是一幅圖像。

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

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

  • 紋理映射是一個相當復雜的過程,基本步驟如下:

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

函數(shù)glTexParameter
void glTexParameter{if}[v](GLenum target,GLenum pname,TYPE param);
{if}:表示可能是否i,f
[v]:表示v可有可無
  • 控制濾波,濾波就是去除沒用的信息,保留有用的信息
  • 一般來說,紋理圖像為正方形或長方形。但當它映射到一個多邊形或曲面上并變換到屏幕坐標時,紋理的單個紋素很少對應于屏幕圖像上的像素。根據(jù)所用變換和所用紋理映射,屏幕上單個象素可以對應于一個紋素的一小部分(即放大)或一大批紋素(即縮?。?/li>
  • 固定寫法
/* 控制濾波 */  
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);  
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);  
函數(shù)glPixelStorei
 void glPixelStorei(GLenum pname, GLint param);  
  • 設置像素存儲方式
  • pname:像素存儲方式名
  • 一種是GL_PACK_ALIGNMENT,用于將像素數(shù)據(jù)打包,一般用于壓縮。
  • 另一種是GL_UNPACK_ALIGNMENT,用于將像素數(shù)據(jù)解包,一般生成紋理對象,就需要用到解包.
  • param:用于指定存儲器中每個像素行有多少個字節(jié)對齊。這個數(shù)值一般是1、2、4或8,
    一般填1,一個像素對應一個字節(jié);
函數(shù)CVOpenGLESTextureCacheCreateTextureFromImage
CVOpenGLESTextureCacheCreateTextureFromImage(CFAllocatorRef  _Nullable allocator, CVOpenGLESTextureCacheRef  _Nonnull textureCache, CVImageBufferRef  _Nonnull sourceImage, CFDictionaryRef  _Nullable textureAttributes, GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, size_t planeIndex, CVOpenGLESTextureRef  _Nullable * _Nonnull textureOut)
  • 根據(jù)圖片生成紋理

  • 參數(shù)allocator kCFAllocatorDefault,默認分配內存

  • 參數(shù)textureCache 紋理緩存

  • 參數(shù)sourceImage 圖片

  • 參數(shù)textureAttributes NULL

  • 參數(shù)target , GL_TEXTURE_2D(創(chuàng)建2維紋理對象)

  • 參數(shù)internalFormat GL_LUMINANCE,亮度格式

  • 參數(shù)width 圖片寬

  • 參數(shù)height 圖片高

  • 參數(shù)format GL_LUMINANCE 亮度格式

  • 參數(shù)type 圖片類型 GL_UNSIGNED_BYTE

  • 參數(shù)planeIndex 0,切面角標,表示第0個切面

  • 參數(shù)textureOut 輸出的紋理對象

    fotmat格式                    描述
    GL_ALPHA            按照ALPHA值存儲紋理單元
    GL_LUMINANCE        按照亮度值存儲紋理單元
    GL_LUMINANCE_ALPHA  按照亮度和alpha值存儲紋理單元
    GL_RGB              按照RGB成分存儲紋理單元
    GL_RGBA             按照RGBA成分存儲紋理單元
    
    
實戰(zhàn)代碼
#pragma mark - 7、創(chuàng)建紋理對象,渲染采集圖片到屏幕
- (void)setupTexture:(CMSampleBufferRef)sampleBuffer
{
    // 獲取圖片信息
    CVImageBufferRef imageBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer);
    
    // 獲取圖片寬度
    GLsizei bufferWidth = (GLsizei)CVPixelBufferGetWidth(imageBufferRef);
    _bufferWidth = bufferWidth;
    GLsizei bufferHeight = (GLsizei)CVPixelBufferGetHeight(imageBufferRef);
    _bufferHeight = bufferHeight;
    
    // 創(chuàng)建亮度紋理
    // 激活紋理單元0, 不激活,創(chuàng)建紋理會失敗
    glActiveTexture(GL_TEXTURE0);
    
    // 創(chuàng)建紋理對象
    CVReturn err;
    err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _textureCacheRef, imageBufferRef, NULL, GL_TEXTURE_2D, GL_LUMINANCE, bufferWidth, bufferHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0, &_luminanceTextureRef);
    if (err) {
        NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
    }
    // 獲取紋理對象
    _luminanceTexture = CVOpenGLESTextureGetName(_luminanceTextureRef);
    
    // 綁定紋理
    glBindTexture(GL_TEXTURE_2D, _luminanceTexture);
    
    // 設置紋理濾波
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    // 激活單元1
    glActiveTexture(GL_TEXTURE1);
    
    // 創(chuàng)建色度紋理
    err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _textureCacheRef, imageBufferRef, NULL, GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, bufferWidth / 2, bufferHeight / 2, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, 1, &_chrominanceTextureRef);
    if (err) {
        NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
    }
    // 獲取紋理對象
    _chrominanceTexture = CVOpenGLESTextureGetName(_chrominanceTextureRef);
    
    // 綁定紋理
    glBindTexture(GL_TEXTURE_2D, _chrominanceTexture);
    
    // 設置紋理濾波
    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轉RGB繪制紋理

  • 紋理映射只能在RGBA方式下執(zhí)行
  • 而采集的是YUV,所以需要把YUV 轉換 為 RGBA,
  • 本質其實就是改下矩陣結構
  • 注意點(熬夜凌晨的bug):glDrawArrays如果要繪制著色器上的點和片段,必須和著色器賦值代碼放在一個代碼塊中,否則找不到繪制的信息,就繪制不上去,造成屏幕黑屏
  • 之前是把glDrawArrays和YUV轉RGB方法分開,就一直黑屏.
函數(shù)glUniform1i
glUniform1i(GLint location, GLint x)
  • 指定著色器中亮度紋理對應哪一層紋理單元
  • 參數(shù)location:著色器中紋理坐標
  • 參數(shù)x:指定那一層紋理
函數(shù)glEnableVertexAttribArray
glEnableVertexAttribArray(GLuint index)
  • 開啟頂點屬性數(shù)組,只有開啟頂點屬性,才能給頂點屬性信息賦值
函數(shù)glVertexAttribPointer
glVertexAttribPointer(GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *ptr)
  • 設置頂點著色器屬性,描述屬性的基本信息
  • 參數(shù)indx:屬性ID,給哪個屬性描述信息
  • 參數(shù)size:頂點屬性由幾個值組成,這個值必須位1,2,3或4;
  • 參數(shù)type:表示屬性的數(shù)據(jù)類型
  • 參數(shù)normalized:GL_FALSE表示不要將數(shù)據(jù)類型標準化
  • 參數(shù)stride 表示數(shù)組中每個元素的長度;
  • 參數(shù)ptr 表示數(shù)組的首地址
函數(shù)glBindAttribLocation
glBindAttribLocation(GLuint program, GLuint index, const GLchar *name)
  • 給屬性綁定ID,通過ID獲取屬性,方便以后使用
  • 參數(shù)program 程序
  • 參數(shù)index 屬性ID
  • 參數(shù)name 屬性名稱
函數(shù)glDrawArrays
glDrawArrays(GLenum mode, GLint first, GLsizei count)
  • 作用:使用當前激活的頂點著色器的頂點數(shù)據(jù)和片段著色器數(shù)據(jù)來繪制基本圖形
  • mode:繪制方式 一般使用GL_TRIANGLE_STRIP,三角形繪制法
  • first:從數(shù)組中哪個頂點開始繪制,一般為0
  • count:數(shù)組中頂點數(shù)量,在定義頂點著色器的時候,就定義過了,比如vec4,表示4個頂點
  • 注意點,如果要繪制著色器上的點和片段,必須和著色器賦值代碼放在一個代碼塊中,否則找不到繪制的信息,就繪制不上去,造成屏幕黑屏。
實戰(zhàn)代碼
// YUV 轉 RGB,里面的頂點和片段都要轉換
- (void)convertYUVToRGBOutput
{
    // 在創(chuàng)建紋理之前,有激活過紋理單元,就是那個數(shù)字.GL_TEXTURE0,GL_TEXTURE1
    // 指定著色器中亮度紋理對應哪一層紋理單元
    // 這樣就會把亮度紋理,往著色器上貼
    glUniform1i(_luminanceTextureAtt, 0);
    
    // 指定著色器中色度紋理對應哪一層紋理單元
    glUniform1i(_chrominanceTextureAtt, 1);
    
    // YUV轉RGB矩陣
    glUniformMatrix3fv(_colorConversionMatrixAtt, 1, GL_FALSE, _preferredConversion);

    // 計算頂點數(shù)據(jù)結構
    CGRect vertexSamplingRect = AVMakeRectWithAspectRatioInsideRect(CGSizeMake(self.bounds.size.width, self.bounds.size.height), self.layer.bounds);
    
    CGSize normalizedSamplingSize = CGSizeMake(0.0, 0.0);
    CGSize cropScaleAmount = CGSizeMake(vertexSamplingRect.size.width/self.layer.bounds.size.width, vertexSamplingRect.size.height/self.layer.bounds.size.height);
    
    if (cropScaleAmount.width > cropScaleAmount.height) {
        normalizedSamplingSize.width = 1.0;
        normalizedSamplingSize.height = cropScaleAmount.height/cropScaleAmount.width;
    }
    else {
        normalizedSamplingSize.width = 1.0;
        normalizedSamplingSize.height = cropScaleAmount.width/cropScaleAmount.height;
    }
    
    // 確定頂點數(shù)據(jù)結構
    GLfloat quadVertexData [] = {
        -1 * normalizedSamplingSize.width, -1 * normalizedSamplingSize.height,
        normalizedSamplingSize.width, -1 * normalizedSamplingSize.height,
        -1 * normalizedSamplingSize.width, normalizedSamplingSize.height,
        normalizedSamplingSize.width, normalizedSamplingSize.height,
    };
    
    // 確定紋理數(shù)據(jù)結構
    GLfloat quadTextureData[] =  { // 正常坐標
        0, 0,
        1, 0,
        0, 1,
        1, 1
    };
    
    // 激活ATTRIB_POSITION頂點數(shù)組
    glEnableVertexAttribArray(ATTRIB_POSITION);
    // 給ATTRIB_POSITION頂點數(shù)組賦值
    glVertexAttribPointer(ATTRIB_POSITION, 2, GL_FLOAT, 0, 0, quadVertexData);
    
    // 激活ATTRIB_TEXCOORD頂點數(shù)組
    glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, 0, 0, quadTextureData);
    // 給ATTRIB_TEXCOORD頂點數(shù)組賦值
    glEnableVertexAttribArray(ATTRIB_TEXCOORD);
    
    // 渲染紋理數(shù)據(jù),注意一定要和紋理代碼放一起
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

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

  • 注意點:必須設置窗口尺寸glViewport
  • 注意點:渲染代碼必須調用[EAGLContext setCurrentContext:_context]
  • 原因:因為是多線程,每一個線程都有一個上下文,只要在一個上下文繪制就好,設置線程的上下文為我們自己的上下文,就能繪制在一起了,否則會黑屏.
  • 注意點:每次創(chuàng)建紋理前,先把之前的紋理引用清空[self cleanUpTextures],否則卡頓
函數(shù)glViewport
glViewport(GLint x, GLint y, GLsizei width, GLsizei height)
  • 設置OpenGL渲染窗口的尺寸大小,一般跟圖層尺寸一樣.
  • 注意:在我們繪制之前還有一件重要的事情要做,我們必須告訴OpenGL渲染窗口的尺寸大小
方法presentRenderbuffer
- (BOOL)presentRenderbuffer:(NSUInteger)target
  • 是將指定renderbuffer呈現(xiàn)在屏幕上
實戰(zhàn)代碼
#pragma mark - 10.渲染幀緩存
- (void)displayFramebuffer:(CMSampleBufferRef)sampleBuffer
{
    // 因為是多線程,每一個線程都有一個上下文,只要在一個上下文繪制就好,設置線程的上下文為我們自己的上下文,就能繪制在一起了,否則會黑屏.
    if ([EAGLContext currentContext] != _context) {
        [EAGLContext setCurrentContext:_context];
    }
    
    // 清空之前的紋理,要不然每次都創(chuàng)建新的紋理,耗費資源,造成界面卡頓
    [self cleanUpTextures];
    
    // 創(chuàng)建紋理對象
    [self setupTexture:sampleBuffer];
    
    // YUV 轉 RGB
    [self convertYUVToRGBOutput];
    
    // 設置窗口尺寸
    glViewport(0, 0, self.bounds.size.width, self.bounds.size.height);
    
    // 把上下文的東西渲染到屏幕上
    [_context presentRenderbuffer:GL_RENDERBUFFER];
    
}

11-清理內存

  • 注意:只要有Ref結尾的,都需要自己手動管理,清空
函數(shù)glClearColor
glClearColor (GLclampf red, GLclampf green, GLclampf blue, GLclampfalpha)
  • 設置一個RGB顏色和透明度,接下來會用這個顏色涂滿全屏.
函數(shù)glClear
glClear (GLbitfieldmask)
  • 用來指定要用清屏顏色來清除由mask指定的buffer,mask可以是 GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT的自由組合。
  • 在這里我們只使用到 color buffer,所以清除的就是 clolor buffer。
#pragma mark - 11.清理內存
- (void)dealloc
{
    // 清空緩存
    [self destoryRenderAndFrameBuffer];
    
    // 清空紋理
    [self cleanUpTextures];
}

#pragma mark - 銷毀渲染和幀緩存
- (void)destoryRenderAndFrameBuffer
{
    glDeleteRenderbuffers(1, &_colorRenderBuffer);
    _colorRenderBuffer = 0;
    
    glDeleteBuffers(1, &_framebuffers);
    _framebuffers = 0;
}

// 清空紋理
- (void)cleanUpTextures
{
    // 清空亮度引用
    if (_luminanceTextureRef) {
        CFRelease(_luminanceTextureRef);
        _luminanceTextureRef = NULL;
    }
    
    // 清空色度引用
    if (_chrominanceTextureRef) {
        CFRelease(_chrominanceTextureRef);
        _chrominanceTextureRef = NULL;
    }
    
    // 清空紋理緩存
    CVOpenGLESTextureCacheFlush(_textureCacheRef, 0);
}

GPUImage工作原理

  • GPUImage最關鍵在于GPUImageFramebuffer這個類,這個類會保存當前處理好的圖片信息。
  • GPUImage是通過一個鏈條處理圖片,每個鏈條通過target連接,每個target處理完圖片后,會生成一個GPUImageFramebuffer對象,并且把圖片信息保存到GPUImageFramebuffer。
  • 這樣比如targetA處理好,要處理targetB,就會先取出targetA的圖片,然后targetB在targetA的圖片基礎上在進行處理.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容