教你實現GPUImage【OpenGL渲染原理】

發表於2017-01-08

一、前言

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

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

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

二、GPUImageVideoCamera

  • 可以捕獲採集的視訊資料
  • 關鍵是捕獲到一幀一幀視訊資料如何展示?
  • 通過這個方法可以獲取採集的視訊資料

  • 採集視訊注意點:要設定採集豎屏,否則獲取的資料是橫屏
  • 通過AVCaptureConnection就可以設定

三、自定義OpenGLView渲染視訊

  • 暴露一個介面,獲取採集到的幀資料,然後把幀資料傳遞給渲染View,展示出來

四、利用OpenGL渲染幀資料並顯示

  • 匯入標頭檔案#import ,GLKit.h底層使用了OpenGLES,匯入它,相當於自動匯入了OpenGLES
  • 步驟
    • 01-自定義圖層型別
    • 02-初始化CAEAGLLayer圖層屬性
    • 03-建立EAGLContext
    • 04-建立渲染緩衝區
    • 05-建立幀緩衝區
    • 06-建立著色器
    • 07-建立著色器程式
    • 08-建立紋理物件
    • 09-YUV轉RGB繪製紋理
    • 10-渲染緩衝區到螢幕
    • 11-清理記憶體

01-自定義圖層型別

  • 為什麼要自定義圖層型別CAEAGLLayer? CAEAGLLayer是OpenGL專門用來渲染的圖層,使用OpenGL必須使用這個圖層

02-初始化CAEAGLLayer圖層屬性

  • 1.不透明度(opaque)=YES,CALayer預設是透明的,透明效能不好,最好設定為不透明.
  • 2.設定繪圖屬性
    • kEAGLDrawablePropertyRetainedBacking :NO (告訴CoreAnimation不要試圖保留任何以前繪製的影像留作以後重用)
    • kEAGLDrawablePropertyColorFormat :kEAGLColorFormatRGBA8 (告訴CoreAnimation用8位來儲存RGBA的值)
  • 其實設定不設定都無所謂,預設也是這個值,只不過GPUImage設定了

03-建立EAGLContext

  • 需要將它設定為當前context,所有的OpenGL ES渲染預設渲染到當前上下文
  • EAGLContext管理所有使用OpenGL ES進行描繪的狀態,命令以及資源資訊,要繪製東西,必須要有上下文,跟圖形上下文類似。
  • 當你建立一個EAGLContext,你要宣告你要用哪個version的API。這裡,我們選擇OpenGL ES 2.0

04-建立渲染緩衝區

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

  • 它是為renderbuffer(渲染快取)申請一個id(名字),建立渲染快取
  • 引數n表示申請生成renderbuffer的個數
  • 引數renderbuffers返回分配給renderbuffer(渲染快取)的id
    。 注意:返回的id不會為0,id 0 是OpenGL ES保留的,我們也不能使用id 為0的renderbuffer(渲染快取)。
函式glBindRenderbuffer

  • 告訴OpenGL:我在後面引用GL_RENDERBUFFER的地方,其實是引用_colorRenderBuffer
  • 引數target必須為GL_RENDERBUFFER
  • 引數renderbuffer就是使用glGenRenderbuffers生成的id
    。 當指定id的renderbuffer第一次被設定為當前renderbuffer時,會初始化該 renderbuffer物件,其初始值為:

函式renderbufferStorage

  • 把渲染快取(renderbuffer)繫結到渲染圖層(CAEAGLLayer)上,併為它分配一個共享記憶體。
  • 引數target,為哪個renderbuffer分配儲存空間
  • 引數drawable,繫結在哪個渲染圖層,會根據渲染圖層裡的繪圖屬性生成共享記憶體。

實戰程式碼

05-建立幀緩衝區

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

  • 該函式是將相關buffer()三大buffer之一)attach到framebuffer上,就會自動把渲染快取的內容填充到幀快取,在由幀快取渲染到螢幕
  • 引數target,哪個幀快取
  • 引數attachment是指定renderbuffer被裝配到那個裝配點上,其值是GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT中的一個,分別對應 color,depth和 stencil三大buffer。
  • renderbuffertarget:哪個渲染快取
  • renderbuffer渲染快取id

06-建立著色器

著色器
  • 什麼是著色器? 通常用來處理紋理物件,並且把處理好的紋理物件渲染到幀快取上,從而顯示到螢幕上。
  • 提取紋理資訊,可以處理頂點座標空間轉換,紋理色彩度調整(濾鏡效果)等操作。
  • 著色器分為頂點著色器,片段著色器
    • 頂點著色器用來確定圖形形狀
    • 片段著色器用來確定圖形渲染顏色
  • 步驟: 1.編輯著色器程式碼 2.建立著色器 3.編譯著色器
  • 只要建立一次,可以在一開始的時候建立
著色器程式碼

實戰程式碼

07-建立著色器程式

  • 步驟: 1.建立程式 2.貼上頂點和片段著色器 3.繫結attribute屬性 4.連線程式 5.繫結uniform屬性 6.執行程式
  • 注意點:第3步和第5步,繫結屬性,必須有順序,否則繫結不成功,造成黑屏

08-建立紋理物件

紋理

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

  • 控制濾波,濾波就是去除沒用的資訊,保留有用的資訊
  • 一般來說,紋理影像為正方形或長方形。但當它對映到一個多邊形或曲面上並變換到螢幕座標時,紋理的單個紋素很少對應於螢幕影像上的畫素。根據所用變換和所用紋理對映,螢幕上單個象素可以對應於一個紋素的一小部分(即放大)或一大批紋素(即縮小)
  • 固定寫法

函式glPixelStorei

  • 設定畫素儲存方式
  • pname:畫素儲存方式名
  • 一種是GL_PACK_ALIGNMENT,用於將畫素資料打包,一般用於壓縮。
  • 另一種是GL_UNPACK_ALIGNMENT,用於將畫素資料解包,一般生成紋理物件,就需要用到解包.
  • param:用於指定儲存器中每個畫素行有多少個位元組對齊。這個數值一般是1、2、4或8,
    一般填1,一個畫素對應一個位元組;
函式CVOpenGLESTextureCacheCreateTextureFromImage

  • 根據圖片生成紋理
  • 引數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 輸出的紋理物件
實戰程式碼

09-YUV轉RGB繪製紋理

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

  • 指定著色器中亮度紋理對應哪一層紋理單元
  • 引數location:著色器中紋理座標
  • 引數x:指定那一層紋理
函式glEnableVertexAttribArray

  • 開啟頂點屬性陣列,只有開啟頂點屬性,才能給頂點屬性資訊賦值
函式glVertexAttribPointer

  • 設定頂點著色器屬性,描述屬性的基本資訊
  • 引數indx:屬性ID,給哪個屬性描述資訊
  • 引數size:頂點屬性由幾個值組成,這個值必須位1,2,3或4;
  • 引數type:表示屬性的資料型別
  • 引數normalized:GL_FALSE表示不要將資料型別標準化
  • 引數stride 表示陣列中每個元素的長度;
  • 引數ptr 表示陣列的首地址
函式glBindAttribLocation

  • 給屬性繫結ID,通過ID獲取屬性,方便以後使用
  • 引數program 程式
  • 引數index 屬性ID
  • 引數name 屬性名稱
函式glDrawArrays

  • 作用:使用當前啟用的頂點著色器的頂點資料和片段著色器資料來繪製基本圖形
  • mode:繪製方式 一般使用GL_TRIANGLE_STRIP,三角形繪製法
  • first:從陣列中哪個頂點開始繪製,一般為0
  • count:陣列中頂點數量,在定義頂點著色器的時候,就定義過了,比如vec4,表示4個頂點
  • 注意點,如果要繪製著色器上的點和片段,必須和著色器賦值程式碼放在一個程式碼塊中,否則找不到繪製的資訊,就繪製不上去,造成螢幕黑屏。
實戰程式碼

10-渲染緩衝區到螢幕

  • 注意點:必須設定視窗尺寸glViewport
  • 注意點:渲染程式碼必須呼叫[EAGLContext setCurrentContext:_context]
  • 原因:因為是多執行緒,每一個執行緒都有一個上下文,只要在一個上下文繪製就好,設定執行緒的上下文為我們自己的上下文,就能繪製在一起了,否則會黑屏.
  • 注意點:每次建立紋理前,先把之前的紋理引用清空[self cleanUpTextures],否則卡頓
    函式glViewport

  • 設定OpenGL渲染視窗的尺寸大小,一般跟圖層尺寸一樣.
  • 注意:在我們繪製之前還有一件重要的事情要做,我們必須告訴OpenGL渲染視窗的尺寸大小
方法presentRenderbuffer

  • 是將指定renderbuffer呈現在螢幕上
實戰程式碼

11-清理記憶體

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

  • 設定一個RGB顏色和透明度,接下來會用這個顏色塗滿全屏.
函式glClear

  • 用來指定要用清屏顏色來清除由mask指定的buffer,mask可以是 GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT的自由組合。
  • 在這裡我們只使用到 color buffer,所以清除的就是 clolor buffer。

GPUImage工作原理

  • GPUImage最關鍵在於GPUImageFramebuffer這個類,這個類會儲存當前處理好的圖片資訊。
  • GPUImage是通過一個鏈條處理圖片,每個鏈條通過target連線,每個target處理完圖片後,會生成一個GPUImageFramebuffer物件,並且把圖片資訊儲存到GPUImageFramebuffer。
  • 這樣比如targetA處理好,要處理targetB,就會先取出targetA的圖片,然後targetB在targetA的圖片基礎上在進行處理.

相關文章