一、前言
本篇主要講解GPUImage底層是如何渲染的,GPUImage底層使用的是OPENGL,操控GPU來實現屏幕展示
由於網上OpenGL實戰資料特別少,官方文檔對一些方法也是解釋不清楚,避免廣大同學再次爬坑,本篇講解了不少OpenGL的知識,並且還講解了花了大量時間解決bug的注意點,曾經因爲對glDrawArrays這個方法不熟悉,遇上Bug,晚上熬到凌晨四點都沒解決,還是第二天中午解決的。
如果喜歡我的文章,可以關注我微博:袁崢Seemygo,也可以來小碼哥,瞭解下我們的iOS培訓課程。後續還會更新更多內容,有任何問題,歡迎關注微,信號“小碼哥訂閱號”。
二、GPUImageVideoCamera
可以捕獲採集的視頻數據
關鍵是捕獲到一幀一幀視頻數據如何展示?
通過這個方法可以獲取採集的視頻數據
採集視頻注意點:要設置採集豎屏,否則獲取的數據是橫屏
通過AVCaptureConnection就可以設置
三、自定義OpenGLView渲染視頻
暴露一個接口,獲取採集到的幀數據,然後把幀數據傳遞給渲染View,展示出來
四、利用OpenGL渲染幀數據並顯示
導入頭文件#import <GLKit/GLKit.h>,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,哪個幀緩存
參數p_w_upload是指定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的圖片基礎上在進行處理.