教你實現GPUImage - OpenGL渲染原理

anyone1985 7年前發布 | 22K 次閱讀 OpenGL GPUImage iOS開發 移動開發

一、前言

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

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

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

二、GPUImageVideoCamera

  • 可以捕獲采集的視頻數據
  • 關鍵是捕獲到一幀一幀視頻數據如何展示?
  • 通過這個方法可以獲取采集的視頻數據
- (void)captureOutput:(AVCaptureOutput *)captureOutputdidOutputSampleBuffer:(CMSampleBufferRef)sampleBufferfromConnection:(AVCaptureConnection *)connection
  • 采集視頻注意點:要設置采集豎屏,否則獲取的數據是橫屏
  • 通過AVCaptureConnection就可以設置
[videoConnectionsetVideoOrientation:AVCaptureVideoOrientationPortraitUpsideDown];

三、自定義OpenGLView渲染視頻

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

四、利用OpenGL渲染幀數據并顯示

  • 導入頭文件#import ,GLKit.h底層使用了OpenGLES,導入它,相當于自動導入了OpenGLES
  • 步驟
    • 01-自定義圖層類型
    • 02-初始化CAEAGLLayer圖層屬性
    • 03-創建EAGLContext
    • 04-創建渲染緩沖區
    • 05-創建幀緩沖區
    • 06-創建著色器
    • 07-創建著色器程序
    • 08-創建紋理對象
    • 09-YUV轉RGB繪制紋理
    • 10-渲染緩沖區到屏幕
    • 11-清理內存
    </li> </ul>

    01-自定義圖層類型

    • 為什么要自定義圖層類型CAEAGLLayer? CAEAGLLayer是OpenGL專門用來渲染的圖層,使用OpenGL必須使用這個圖層
    #pragma mark - 1.自定義圖層類型

    • (Class)layerClass {     return [CAEAGLLayerclass]; } </code></pre>

      02-初始化CAEAGLLayer圖層屬性

      • 1.不透明度(opaque)=YES,CALayer默認是透明的,透明性能不好,最好設置為不透明.
      • 2.設置繪圖屬性
        • kEAGLDrawablePropertyRetainedBacking :NO (告訴CoreAnimation不要試圖保留任何以前繪制的圖像留作以后重用)
        • kEAGLDrawablePropertyColorFormat :kEAGLColorFormatRGBA8 (告訴CoreAnimation用8位來保存RGBA的值)
        </li>
      • 其實設置不設置都無所謂,默認也是這個值,只不過GPUImage設置了
      • </ul>
        #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 :[NSNumbernumberWithBool:NO],                                       kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8                                       }; } </code></pre>

        03-創建EAGLContext

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

        04-創建渲染緩沖區

        • 有了上下文,openGL還需要在一塊buffer進行描繪,這塊buffer就是RenderBuffer
        • OpenGLES 總共有三大不同用途的color buffer,depth buffer 和 stencil buffer.
        • 最基本的是color buffer,創建它就好了

        函數glGenRenderbuffers

        函數 void glGenRenderbuffers (GLsizei n, GLuint* renderbuffers)
        
        • 它是為renderbuffer(渲染緩存)申請一個id(名字),創建渲染緩存
        • 參數n表示申請生成renderbuffer的個數
        • 參數renderbuffers返回分配給renderbuffer(渲染緩存)的id
          。 注意:返回的id不會為0,id 0 是OpenGL ES保留的,我們也不能使用id 為0的renderbuffer(渲染緩存)。

        函數glBindRenderbuffer

         void glBindRenderbuffer (GLenumtarget, GLuintrenderbuffer)
        
        • 告訴OpenGL:我在后面引用GL_RENDERBUFFER的地方,其實是引用_colorRenderBuffer
        • 參數target必須為GL_RENDERBUFFER
        • 參數renderbuffer就是使用glGenRenderbuffers生成的id
          。 當指定id的renderbuffer第一次被設置為當前renderbuffer時,會初始化該 renderbuffer對象,其初始值為:
        width 和 height:像素單位的寬和高,默認值為0;
         
        internalformat:內部格式,三大 buffer 格式之一 -- color,depthor stencil;
         
        Colorbit-depth:僅當內部格式為 color 時,設置顏色的 bit-depth,默認值為0;
         
        Depthbit-depth:僅當內部格式為 depth時,默認值為0;
         
        Stencilbit-depth: 僅當內部格式為 stencil,默認值為0
        

        函數renderbufferStorage

        EAGLContext方法 - (BOOL)renderbufferStorage:(NSUInteger)targetfromDrawable:(id)drawable
        
        • 把渲染緩存(renderbuffer)綁定到渲染圖層(CAEAGLLayer)上,并為它分配一個共享內存。
        • 參數target,為哪個renderbuffer分配存儲空間
        • 參數drawable,綁定在哪個渲染圖層,會根據渲染圖層里的繪圖屬性生成共享內存。
            //    底層調用這個分配內存
            glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, _openGLLayer.bounds.size.width, _openGLLayer.bounds.size.height);
        

        實戰代碼

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

        05-創建幀緩沖區

        • 它相當于buffer(color, depth, stencil)的管理者,三大buffer可以附加到一個framebuffer上
        • 本質是把framebuffer內容渲染到屏幕

        函數glFramebufferRenderbuffer

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

        06-創建著色器

        著色器

        • 什么是著色器? 通常用來處理紋理對象,并且把處理好的紋理對象渲染到幀緩存上,從而顯示到屏幕上。
        • 提取紋理信息,可以處理頂點坐標空間轉換,紋理色彩度調整(濾鏡效果)等操作。
        • 著色器分為頂點著色器,片段著色器
          • 頂點著色器用來確定圖形形狀
          • 片段著色器用來確定圖形渲染顏色
          </li>
        • 步驟: 1.編輯著色器代碼 2.創建著色器 3.編譯著色器
        • 只要創建一次,可以在一開始的時候創建
        • </ul>

          著色器代碼

          // 頂點著色器代碼
          NSString *const kVertexShaderString = SHADER_STRING
          (
          attributevec4position;
          attributevec2inputTextureCoordinate;
           
          varyingvec2textureCoordinate;
           
          void main()
          {
              gl_Position = position;
              textureCoordinate = inputTextureCoordinate;
          }
          );
           
          // 片段著色器代碼
          NSString *const kYUVFullRangeConversionForLAFragmentShaderString = SHADER_STRING
          (
          varyinghighpvec2textureCoordinate;
           
          precisionmediumpfloat;
           
          uniformsampler2DluminanceTexture;
          uniformsampler2DchrominanceTexture;
          uniformmediumpmat3colorConversionMatrix;
           
          void main()
          {
              mediumpvec3yuv;
              lowpvec3rgb;
           
              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);
          }
          );
          

          實戰代碼

          #pragma mark - 06、創建著色器
          
        • (void)setupShader {     // 創建頂點著色器     _vertShader = [self loadShader:GL_VERTEX_SHADERwithString:kVertexShaderString];       // 創建片段著色器     _fragShader = [self loadShader:GL_FRAGMENT_SHADERwithString:kYUVFullRangeConversionForLAFragmentShaderString];   }   // 加載著色器
        • (GLuint)loadShader:(GLenum)typewithString:(NSString )shaderString {     // 創建著色器     GLuintshader = glCreateShader(type);     if (shader == 0) {         NSLog(@"Error: failed to create shader.");         return 0;     }       // 加載著色器源代碼     const char shaderStringUTF8 = [shaderStringUTF8String];     glShaderSource(shader, 1, &shaderStringUTF8, NULL);       // 編譯著色器     glCompileShader(shader);       // 檢查是否完成     GLintcompiled = 0;       // 獲取完成狀態     glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);       if (compiled == 0) {           // 沒有完成就直接刪除著色器         glDeleteShader(shader);         return 0;     }       return shader; } </code></pre>

          07-創建著色器程序

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

          08-創建紋理對象

          紋理

          • 采集的是一張一張的圖片,可以把圖片轉換為OpenGL中的紋理, 然后再把紋理畫到OpenGL的上下文中
          • 什么是紋理?一個紋理其實就是一幅圖像。
          • 紋理映射,我們可以把這幅圖像的整體或部分貼到我們先前用頂點勾畫出的物體上去.
          • 比如繪制一面磚墻,就可以用一幅真實的磚墻圖像或照片作為紋理貼到一個矩形上,這樣,一面逼真的磚墻就畫好了。如果不用紋理映射的方法,則墻上的每一塊磚都必須作為一個獨立的多邊形來畫。另外,紋理映射能夠保證在變換多邊形時,多邊形上的紋理圖案也隨之變化。
          • 紋理映射是一個相當復雜的過程,基本步驟如下:
            • 1)激活紋理單元、2)創建紋理 、3)綁定紋理 、4)設置濾波
            </li>
          • 注意:紋理映射只能在RGBA方式下執行
          • </ul>

            函數glTexParameter

            void glTexParameter{if}v;
            {if}:表示可能是否i,f
            

          </code></pre>

          • 控制濾波,濾波就是去除沒用的信息,保留有用的信息
          • 一般來說,紋理圖像為正方形或長方形。但當它映射到一個多邊形或曲面上并變換到屏幕坐標時,紋理的單個紋素很少對應于屏幕圖像上的像素。根據所用變換和所用紋理映射,屏幕上單個象素可以對應于一個紋素的一小部分(即放大)或一大批紋素(即縮小)
          • 固定寫法
          /* 控制濾波 */  
                  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);  
                  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
          

          函數glPixelStorei

           void glPixelStorei(GLenumpname, GLintparam);
          
          • 設置像素存儲方式
          • pname:像素存儲方式名
          • 一種是 GL_PACK_ALIGNMENT ,用于將像素數據打包,一般用于壓縮。
          • 另一種是 GL_UNPACK_ALIGNMENT ,用于將像素數據解包,一般生成紋理對象,就需要用到解包.
          • param:用于指定存儲器中每個像素行有多少個字節對齊。這個數值一般是1、2、4或8,
            一般填1,一個像素對應一個字節;

          函數CVOpenGLESTextureCacheCreateTextureFromImage

          CVOpenGLESTextureCacheCreateTextureFromImage(CFAllocatorRef  _Nullableallocator, CVOpenGLESTextureCacheRef  _NonnulltextureCache, CVImageBufferRef  _NonnullsourceImage, CFDictionaryRef  _NullabletextureAttributes, GLenumtarget, GLintinternalFormat, GLsizeiwidth, GLsizeiheight, GLenumformat, GLenumtype, size_tplaneIndex, CVOpenGLESTextureRef  _Nullable * _NonnulltextureOut)
          
          • 根據圖片生成紋理
          • 參數allocator kCFAllocatorDefault,默認分配內存
          • 參數textureCache 紋理緩存
          • 參數sourceImage 圖片
          • 參數textureAttributes NULL
          • 參數target , GL_TEXTURE_2D(創建2維紋理對象)
          • 參數internalFormat GL_LUMINANCE,亮度格式
          • 參數width 圖片寬
          • 參數height 圖片高
          • 參數format GL_LUMINANCE 亮度格式
          • 參數type 圖片類型 GL_UNSIGNED_BYTE
          • 參數planeIndex 0,切面角標,表示第0個切面
          • 參數textureOut 輸出的紋理對象
            fotmat格式                    描述
            GL_ALPHA            按照ALPHA值存儲紋理單元
            GL_LUMINANCE        按照亮度值存儲紋理單元
            GL_LUMINANCE_ALPHA    按照亮度和alpha值存儲紋理單元
            GL_RGB                按照RGB成分存儲紋理單元
            GL_RGBA                按照RGBA成分存儲紋理單元
            

          實戰代碼

          #pragma mark - 7、創建紋理對象,渲染采集圖片到屏幕

          • (void)setupTexture:(CMSampleBufferRef)sampleBuffer {     // 獲取圖片信息     CVImageBufferRefimageBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer);       // 獲取圖片寬度     GLsizeibufferWidth = (GLsizei)CVPixelBufferGetWidth(imageBufferRef);     _bufferWidth = bufferWidth;     GLsizeibufferHeight = (GLsizei)CVPixelBufferGetHeight(imageBufferRef);     _bufferHeight = bufferHeight;       // 創建亮度紋理     // 激活紋理單元0, 不激活,創建紋理會失敗     glActiveTexture(GL_TEXTURE0);       // 創建紋理對象     CVReturnerr;     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);       // 創建色度紋理     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); } </code></pre>

            09-YUV轉RGB繪制紋理

            • 紋理映射只能在RGBA方式下執行
            • 而采集的是YUV,所以需要把YUV 轉換 為 RGBA,
            • 本質其實就是改下矩陣結構
            • 注意點(熬夜凌晨的bug): glDrawArrays如果要繪制著色器上的點和片段,必須和著色器賦值代碼放在一個代碼塊中,否則找不到繪制的信息,就繪制不上去,造成屏幕黑屏
            • 之前是把glDrawArrays和YUV轉RGB方法分開,就一直黑屏.

            函數glUniform1i

            glUniform1i(GLintlocation, GLint x)
            
            • 指定著色器中亮度紋理對應哪一層紋理單元
            • 參數location:著色器中紋理坐標
            • 參數x:指定那一層紋理

            函數glEnableVertexAttribArray

            glEnableVertexAttribArray(GLuintindex)
            
            • 開啟頂點屬性數組,只有開啟頂點屬性,才能給頂點屬性信息賦值

            函數glVertexAttribPointer

            glVertexAttribPointer(GLuintindx, GLintsize, GLenumtype, GLbooleannormalized, GLsizeistride, const GLvoid *ptr)
            
            • 設置頂點著色器屬性,描述屬性的基本信息
            • 參數indx:屬性ID,給哪個屬性描述信息
            • 參數size:頂點屬性由幾個值組成,這個值必須位1,2,3或4;
            • 參數type:表示屬性的數據類型
            • 參數normalized:GL_FALSE表示不要將數據類型標準化
            • 參數stride 表示數組中每個元素的長度;
            • 參數ptr 表示數組的首地址

            函數glBindAttribLocation

            glBindAttribLocation(GLuintprogram, GLuintindex, const GLchar *name)
            
            • 給屬性綁定ID,通過ID獲取屬性,方便以后使用
            • 參數program 程序
            • 參數index 屬性ID
            • 參數name 屬性名稱

            函數glDrawArrays

            glDrawArrays(GLenummode, GLintfirst, GLsizeicount)
            
            • 作用:使用當前激活的頂點著色器的頂點數據和片段著色器數據來繪制基本圖形
            • mode:繪制方式 一般使用GL_TRIANGLE_STRIP,三角形繪制法
            • first:從數組中哪個頂點開始繪制,一般為0
            • count:數組中頂點數量,在定義頂點著色器的時候,就定義過了,比如vec4,表示4個頂點
            • 注意點,如果要繪制著色器上的點和片段,必須和著色器賦值代碼放在一個代碼塊中,否則找不到繪制的信息,就繪制不上去,造成屏幕黑屏。

            實戰代碼

            // YUV 轉 RGB,里面的頂點和片段都要轉換
          • (void)convertYUVToRGBOutput {     // 在創建紋理之前,有激活過紋理單元,就是那個數字.GL_TEXTURE0,GL_TEXTURE1     // 指定著色器中亮度紋理對應哪一層紋理單元     // 這樣就會把亮度紋理,往著色器上貼     glUniform1i(_luminanceTextureAtt, 0);       // 指定著色器中色度紋理對應哪一層紋理單元     glUniform1i(_chrominanceTextureAtt, 1);       // YUV轉RGB矩陣     glUniformMatrix3fv(_colorConversionMatrixAtt, 1, GL_FALSE, _preferredConversion);       // 計算頂點數據結構     CGRectvertexSamplingRect = AVMakeRectWithAspectRatioInsideRect(CGSizeMake(self.bounds.size.width, self.bounds.size.height), self.layer.bounds);       CGSizenormalizedSamplingSize = CGSizeMake(0.0, 0.0);     CGSizecropScaleAmount = 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;     }       // 確定頂點數據結構     GLfloatquadVertexData [] = {         -1 normalizedSamplingSize.width, -1 normalizedSamplingSize.height,         normalizedSamplingSize.width, -1 normalizedSamplingSize.height,         -1 normalizedSamplingSize.width, normalizedSamplingSize.height,         normalizedSamplingSize.width, normalizedSamplingSize.height,     };       // 確定紋理數據結構     GLfloatquadTextureData[] =  { // 正常坐標         0, 0,         1, 0,         0, 1,         1, 1     };       // 激活ATTRIB_POSITION頂點數組     glEnableVertexAttribArray(ATTRIB_POSITION);     // 給ATTRIB_POSITION頂點數組賦值     glVertexAttribPointer(ATTRIB_POSITION, 2, GL_FLOAT, 0, 0, quadVertexData);       // 激活ATTRIB_TEXCOORD頂點數組     glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, 0, 0, quadTextureData);     // 給ATTRIB_TEXCOORD頂點數組賦值     glEnableVertexAttribArray(ATTRIB_TEXCOORD);       // 渲染紋理數據,注意一定要和紋理代碼放一起     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } </code></pre>

            10-渲染緩沖區到屏幕

            • 注意點: 必須設置窗口尺寸glViewport
            • 注意點: 渲染代碼必須調用[EAGLContext setCurrentContext:_context]
            • 原因:因為是多線程,每一個線程都有一個上下文,只要在一個上下文繪制就好,設置線程的上下文為我們自己的上下文,就能繪制在一起了,否則會黑屏.
            • 注意點: 每次創建紋理前,先把之前的紋理引用清空[self cleanUpTextures],否則卡頓

              函數glViewport

              glViewport(GLint x, GLint y, GLsizeiwidth, GLsizeiheight)
              
            • 設置OpenGL渲染窗口的尺寸大小,一般跟圖層尺寸一樣.
            • 注意:在我們繪制之前還有一件重要的事情要做,我們必須告訴OpenGL渲染窗口的尺寸大小

            方法presentRenderbuffer

            - (BOOL)presentRenderbuffer:(NSUInteger)target
            
            • 是將指定renderbuffer呈現在屏幕上

            實戰代碼

            #pragma mark - 10.渲染幀緩存
          • (void)displayFramebuffer:(CMSampleBufferRef)sampleBuffer {     // 因為是多線程,每一個線程都有一個上下文,只要在一個上下文繪制就好,設置線程的上下文為我們自己的上下文,就能繪制在一起了,否則會黑屏.     if ([EAGLContextcurrentContext] != _context) {         [EAGLContextsetCurrentContext:_context];     }       // 清空之前的紋理,要不然每次都創建新的紋理,耗費資源,造成界面卡頓     [self cleanUpTextures];       // 創建紋理對象     [self setupTexture:sampleBuffer];       // YUV 轉 RGB     [self convertYUVToRGBOutput];       // 設置窗口尺寸     glViewport(0, 0, self.bounds.size.width, self.bounds.size.height);       // 把上下文的東西渲染到屏幕上     [_contextpresentRenderbuffer:GL_RENDERBUFFER];   } </code></pre>

            11-清理內存

            • 注意: 只要有Ref結尾的,都需要自己手動管理,清空

            函數glClearColor

            glClearColor (GLclampfred, GLclampfgreen, GLclampfblue, GLclampfalpha)
            
            • 設置一個RGB顏色和透明度,接下來會用這個顏色涂滿全屏.

            函數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); } </code></pre>

            GPUImage工作原理

            • GPUImage最關鍵在于GPUImageFramebuffer這個類,這個類會保存當前處理好的圖片信息。
            • GPUImage是通過一個鏈條處理圖片,每個鏈條通過target連接,每個target處理完圖片后,會生成一個GPUImageFramebuffer對象,并且把圖片信息保存到GPUImageFramebuffer。
            • 這樣比如targetA處理好,要處理targetB,就會先取出targetA的圖片,然后targetB在targetA的圖片基礎上在進行處理.

             

            來自:http://ios.jobbole.com/92362/

             

 本文由用戶 anyone1985 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!