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

一、前言

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

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

如果喜歡我的文章,可以關(guān)注我微博:袁崢Seemygo

二、GPUImageVideoCamera

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

  • 關(guān)鍵是捕獲到一幀一幀視頻數(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轉(zhuǎn)RGB繪制紋理
    • 10-渲染緩沖區(qū)到屏幕
    • 11-清理內(nèi)存

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:內(nèi)部格式,三大 buffer 格式之一 -- color,depth or stencil;

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

Depth bit-depth:僅當內(nèi)部格式為 depth時,默認值為0;

Stencil bit-depth: 僅當內(nèi)部格式為 stencil,默認值為0
函數(shù)renderbufferStorage
EAGLContext方法 - (BOOL)renderbufferStorage:(NSUInteger)target fromDrawable:(id<EAGLDrawable>)drawable
  • 把渲染緩存(renderbuffer)綁定到渲染圖層(CAEAGLLayer)上,并為它分配一個共享內(nèi)存。
  • 參數(shù)target,為哪個renderbuffer分配存儲空間
  • 參數(shù)drawable,綁定在哪個渲染圖層,會根據(jù)渲染圖層里的繪圖屬性生成共享內(nèi)存。
    //  底層調(diào)用這個分配內(nèi)存
    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,并為它分配一個共享內(nèi)存。
    // 并且會設置渲染緩存的格式,和寬度
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_openGLLayer];

}

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

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

  • 本質(zhì)是把framebuffer內(nèi)容渲染到屏幕

函數(shù)glFramebufferRenderbuffer
void glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
  • 該函數(shù)是將相關(guān)buffer()三大buffer之一)attach到framebuffer上,就會自動把渲染緩存的內(nèi)容填充到幀緩存,在由幀緩存渲染到屏幕
  • 參數(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上,就會自動把渲染緩存的內(nèi)容填充到幀緩存,在由幀緩存渲染到屏幕
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer);
}

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

著色器
  • 什么是著色器? 通常用來處理紋理對象,并且把處理好的紋理對象渲染到幀緩存上,從而顯示到屏幕上。
  • 提取紋理信息,可以處理頂點坐標空間轉(zhuǎn)換,紋理色彩度調(diào)整(濾鏡效果)等操作。
  • 著色器分為頂點著色器,片段著色器
    • 頂點著色器用來確定圖形形狀
    • 片段著色器用來確定圖形渲染顏色
  • 步驟: 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)建紋理對象

紋理

  • 采集的是一張一張的圖片,可以把圖片轉(zhuǎn)換為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ù)所用變換和所用紋理映射,屏幕上單個象素可以對應于一個紋素的一小部分(即放大)或一大批紋素(即縮小)
  • 固定寫法
/* 控制濾波 */  
  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,默認分配內(nèi)存

  • 參數(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轉(zhuǎn)RGB繪制紋理

  • 紋理映射只能在RGBA方式下執(zhí)行
  • 而采集的是YUV,所以需要把YUV 轉(zhuǎn)換 為 RGBA,
  • 本質(zhì)其實就是改下矩陣結(jié)構(gòu)
  • 注意點(熬夜凌晨的bug):glDrawArrays如果要繪制著色器上的點和片段,必須和著色器賦值代碼放在一個代碼塊中,否則找不到繪制的信息,就繪制不上去,造成屏幕黑屏
  • 之前是把glDrawArrays和YUV轉(zhuǎn)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 轉(zhuǎn) RGB,里面的頂點和片段都要轉(zhuǎn)換
- (void)convertYUVToRGBOutput
{
    // 在創(chuàng)建紋理之前,有激活過紋理單元,就是那個數(shù)字.GL_TEXTURE0,GL_TEXTURE1
    // 指定著色器中亮度紋理對應哪一層紋理單元
    // 這樣就會把亮度紋理,往著色器上貼
    glUniform1i(_luminanceTextureAtt, 0);
    
    // 指定著色器中色度紋理對應哪一層紋理單元
    glUniform1i(_chrominanceTextureAtt, 1);
    
    // YUV轉(zhuǎn)RGB矩陣
    glUniformMatrix3fv(_colorConversionMatrixAtt, 1, GL_FALSE, _preferredConversion);

    // 計算頂點數(shù)據(jù)結(jié)構(gòu)
    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ù)結(jié)構(gòu)
    GLfloat quadVertexData [] = {
        -1 * normalizedSamplingSize.width, -1 * normalizedSamplingSize.height,
        normalizedSamplingSize.width, -1 * normalizedSamplingSize.height,
        -1 * normalizedSamplingSize.width, normalizedSamplingSize.height,
        normalizedSamplingSize.width, normalizedSamplingSize.height,
    };
    
    // 確定紋理數(shù)據(jù)結(jié)構(gòu)
    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
  • 注意點:渲染代碼必須調(diào)用[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 轉(zhuǎn) RGB
    [self convertYUVToRGBOutput];
    
    // 設置窗口尺寸
    glViewport(0, 0, self.bounds.size.width, self.bounds.size.height);
    
    // 把上下文的東西渲染到屏幕上
    [_context presentRenderbuffer:GL_RENDERBUFFER];
    
}

11-清理內(nèi)存

  • 注意:只要有Ref結(jié)尾的,都需要自己手動管理,清空
函數(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.清理內(nèi)存
- (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最關(guān)鍵在于GPUImageFramebuffer這個類,這個類會保存當前處理好的圖片信息。
  • GPUImage是通過一個鏈條處理圖片,每個鏈條通過target連接,每個target處理完圖片后,會生成一個GPUImageFramebuffer對象,并且把圖片信息保存到GPUImageFramebuffer。
  • 這樣比如targetA處理好,要處理targetB,就會先取出targetA的圖片,然后targetB在targetA的圖片基礎上在進行處理.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內(nèi)容