一、前言
本篇主要講解GPUImage底層是如何渲染的,GPUImage底層使用的是OPENGL,操控GPU來實現螢幕展示
由於網上OpenGL實戰資料特別少,官方文件對一些方法也是解釋不清楚,避免廣大同學再次爬坑,本篇講解了不少OpenGL的知識,並且還講解了花了大量時間解決bug的注意點,曾經因為對glDrawArrays這個方法不熟悉,遇上Bug,晚上熬到凌晨四點都沒解決,還是第二天中午解決的。
如果喜歡我的文章,可以關注我微博:袁崢Seemygo
二、GPUImageVideoCamera
- 可以捕獲採集的視訊資料
- 關鍵是捕獲到一幀一幀視訊資料如何展示?
- 通過這個方法可以獲取採集的視訊資料
1 |
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection |
- 採集視訊注意點:要設定採集豎屏,否則獲取的資料是橫屏
- 通過AVCaptureConnection就可以設定
1 |
[videoConnection setVideoOrientation:AVCaptureVideoOrientationPortraitUpsideDown]; |
三、自定義OpenGLView渲染視訊
- 暴露一個介面,獲取採集到的幀資料,然後把幀資料傳遞給渲染View,展示出來
1 |
- (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-清理記憶體
01-自定義圖層型別
- 為什麼要自定義圖層型別CAEAGLLayer? CAEAGLLayer是OpenGL專門用來渲染的圖層,使用OpenGL必須使用這個圖層
1 2 3 4 5 |
#pragma mark - 1.自定義圖層型別 + (Class)layerClass { return [CAEAGLLayer class]; } |
02-初始化CAEAGLLayer圖層屬性
- 1.不透明度(opaque)=YES,CALayer預設是透明的,透明效能不好,最好設定為不透明.
- 2.設定繪圖屬性
- kEAGLDrawablePropertyRetainedBacking :NO (告訴CoreAnimation不要試圖保留任何以前繪製的影像留作以後重用)
- kEAGLDrawablePropertyColorFormat :kEAGLColorFormatRGBA8 (告訴CoreAnimation用8位來儲存RGBA的值)
- 其實設定不設定都無所謂,預設也是這個值,只不過GPUImage設定了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#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-建立EAGLContext
- 需要將它設定為當前context,所有的OpenGL ES渲染預設渲染到當前上下文
- EAGLContext管理所有使用OpenGL ES進行描繪的狀態,命令以及資源資訊,要繪製東西,必須要有上下文,跟圖形上下文類似。
- 當你建立一個EAGLContext,你要宣告你要用哪個version的API。這裡,我們選擇OpenGL ES 2.0
1 2 3 4 5 6 7 8 9 10 11 12 |
#pragma mark - 3、建立OpenGL上下文,並且設定上下文 - (void)setupContext { // 指定OpenGL 渲染 API 的版本,目前都使用 OpenGL ES 2.0 EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2; // 建立EAGLContext上下文 _context = [[EAGLContext alloc] initWithAPI:api]; // 設定為當前上下文,所有的渲染預設渲染到當前上下文 [EAGLContext setCurrentContext:_context]; } |
04-建立渲染緩衝區
- 有了上下文,openGL還需要在一塊buffer進行描繪,這塊buffer就是RenderBuffer
- OpenGLES 總共有三大不同用途的color buffer,depth buffer 和 stencil buffer.
- 最基本的是color buffer,建立它就好了
函式glGenRenderbuffers
1 |
函式 void glGenRenderbuffers (GLsizei n, GLuint* renderbuffers) |
- 它是為renderbuffer(渲染快取)申請一個id(名字),建立渲染快取
- 引數n表示申請生成renderbuffer的個數
- 引數renderbuffers返回分配給renderbuffer(渲染快取)的id
。 注意:返回的id不會為0,id 0 是OpenGL ES保留的,我們也不能使用id 為0的renderbuffer(渲染快取)。
函式glBindRenderbuffer
1 |
void glBindRenderbuffer (GLenum target, GLuint renderbuffer) |
- 告訴OpenGL:我在後面引用GL_RENDERBUFFER的地方,其實是引用_colorRenderBuffer
- 引數target必須為GL_RENDERBUFFER
- 引數renderbuffer就是使用glGenRenderbuffers生成的id
。 當指定id的renderbuffer第一次被設定為當前renderbuffer時,會初始化該 renderbuffer物件,其初始值為:
1 2 3 4 5 6 7 8 9 |
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 |
函式renderbufferStorage
1 |
EAGLContext方法 - (BOOL)renderbufferStorage:(NSUInteger)target fromDrawable:(id)drawable |
- 把渲染快取(renderbuffer)繫結到渲染圖層(CAEAGLLayer)上,併為它分配一個共享記憶體。
- 引數target,為哪個renderbuffer分配儲存空間
- 引數drawable,繫結在哪個渲染圖層,會根據渲染圖層裡的繪圖屬性生成共享記憶體。
1 2 |
// 底層呼叫這個分配記憶體 glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, _openGLLayer.bounds.size.width, _openGLLayer.bounds.size.height); |
實戰程式碼
1 2 3 4 5 6 7 8 9 10 11 |
#pragma mark - 4、建立渲染快取 - (void)setupRenderBuffer { glGenRenderbuffers(1, &_colorRenderBuffer); glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer); // 把渲染快取繫結到渲染圖層上CAEAGLLayer,併為它分配一個共享記憶體。 // 並且會設定渲染快取的格式,和寬度 [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_openGLLayer]; } |
05-建立幀緩衝區
- 它相當於buffer(color, depth, stencil)的管理者,三大buffer可以附加到一個framebuffer上
- 本質是把framebuffer內容渲染到螢幕
函式glFramebufferRenderbuffer
1 |
void glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) |
- 該函式是將相關buffer()三大buffer之一)attach到framebuffer上,就會自動把渲染快取的內容填充到幀快取,在由幀快取渲染到螢幕
- 引數target,哪個幀快取
- 引數attachment是指定renderbuffer被裝配到那個裝配點上,其值是GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT中的一個,分別對應 color,depth和 stencil三大buffer。
- renderbuffertarget:哪個渲染快取
- renderbuffer渲染快取id
1 2 3 4 5 6 7 8 |
#pragma mark - 5、建立幀緩衝區 - (void)setupFrameBuffer { glGenFramebuffers(1, &_framebuffers); glBindFramebuffer(GL_FRAMEBUFFER, _framebuffers); // 把顏色渲染快取 新增到 幀快取的GL_COLOR_ATTACHMENT0上,就會自動把渲染快取的內容填充到幀快取,在由幀快取渲染到螢幕 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer); } |
06-建立著色器
著色器
- 什麼是著色器? 通常用來處理紋理物件,並且把處理好的紋理物件渲染到幀快取上,從而顯示到螢幕上。
- 提取紋理資訊,可以處理頂點座標空間轉換,紋理色彩度調整(濾鏡效果)等操作。
- 著色器分為頂點著色器,片段著色器
- 頂點著色器用來確定圖形形狀
- 片段著色器用來確定圖形渲染顏色
- 步驟: 1.編輯著色器程式碼 2.建立著色器 3.編譯著色器
- 只要建立一次,可以在一開始的時候建立
著色器程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
// 頂點著色器程式碼 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); } ); |
實戰程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#pragma mark - 06、建立著色器 - (void)setupShader { // 建立頂點著色器 _vertShader = [self loadShader:GL_VERTEX_SHADER withString:kVertexShaderString]; // 建立片段著色器 _fragShader = [self loadShader:GL_FRAGMENT_SHADER withString:kYUVFullRangeConversionForLAFragmentShaderString]; } // 載入著色器 - (GLuint)loadShader:(GLenum)type withString:(NSString *)shaderString { // 建立著色器 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; // 獲取完成狀態 glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); if (compiled == 0) { // 沒有完成就直接刪除著色器 glDeleteShader(shader); return 0; } return shader; } |
07-建立著色器程式
- 步驟: 1.建立程式 2.貼上頂點和片段著色器 3.繫結attribute屬性 4.連線程式 5.繫結uniform屬性 6.執行程式
- 注意點:
第3步和第5步,繫結屬性,必須有順序,否則繫結不成功,造成黑屏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#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); } |
08-建立紋理物件
紋理
- 採集的是一張一張的圖片,可以把圖片轉換為OpenGL中的紋理, 然後再把紋理畫到OpenGL的上下文中
- 什麼是紋理?一個紋理其實就是一幅影像。
- 紋理對映,我們可以把這幅影像的整體或部分貼到我們先前用頂點勾畫出的物體上去.
- 比如繪製一面磚牆,就可以用一幅真實的磚牆影像或照片作為紋理貼到一個矩形上,這樣,一面逼真的磚牆就畫好了。如果不用紋理對映的方法,則牆上的每一塊磚都必須作為一個獨立的多邊形來畫。另外,紋理對映能夠保證在變換多邊形時,多邊形上的紋理圖案也隨之變化。
- 紋理對映是一個相當複雜的過程,基本步驟如下:
- 1)啟用紋理單元、2)建立紋理 、3)繫結紋理 、4)設定濾波
- 注意:紋理對映只能在RGBA方式下執行
函式glTexParameter
1 2 3 |
void glTexParameter{if}[v](GLenum target,GLenum pname,TYPE param); {if}:表示可能是否i,f [v]:表示v可有可無 |
- 控制濾波,濾波就是去除沒用的資訊,保留有用的資訊
- 一般來說,紋理影像為正方形或長方形。但當它對映到一個多邊形或曲面上並變換到螢幕座標時,紋理的單個紋素很少對應於螢幕影像上的畫素。根據所用變換和所用紋理對映,螢幕上單個象素可以對應於一個紋素的一小部分(即放大)或一大批紋素(即縮小)
- 固定寫法
1 2 3 |
/* 控制濾波 */ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); |
函式glPixelStorei
1 |
void glPixelStorei(GLenum pname, GLint param); |
- 設定畫素儲存方式
- pname:畫素儲存方式名
- 一種是
GL_PACK_ALIGNMENT
,用於將畫素資料打包,一般用於壓縮。 - 另一種是
GL_UNPACK_ALIGNMENT
,用於將畫素資料解包,一般生成紋理物件,就需要用到解包. - param:用於指定儲存器中每個畫素行有多少個位元組對齊。這個數值一般是1、2、4或8,
一般填1,一個畫素對應一個位元組;
函式CVOpenGLESTextureCacheCreateTextureFromImage
1 |
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) |
- 根據圖片生成紋理
- 引數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 輸出的紋理物件
123456fotmat格式 描述GL_ALPHA 按照ALPHA值儲存紋理單元GL_LUMINANCE 按照亮度值儲存紋理單元GL_LUMINANCE_ALPHA 按照亮度和alpha值儲存紋理單元GL_RGB 按照RGB成分儲存紋理單元GL_RGBA 按照RGBA成分儲存紋理單元
實戰程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#pragma mark - 7、建立紋理物件,渲染採集圖片到螢幕 - (void)setupTexture:(CMSampleBufferRef)sampleBuffer { // 獲取圖片資訊 CVImageBufferRef imageBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer); // 獲取圖片寬度 GLsizei bufferWidth = (GLsizei)CVPixelBufferGetWidth(imageBufferRef); _bufferWidth = bufferWidth; GLsizei bufferHeight = (GLsizei)CVPixelBufferGetHeight(imageBufferRef); _bufferHeight = bufferHeight; // 建立亮度紋理 // 啟用紋理單元0, 不啟用,建立紋理會失敗 glActiveTexture(GL_TEXTURE0); // 建立紋理物件 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); // 建立色度紋理 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方式下執行
- 而採集的是YUV,所以需要把YUV 轉換 為 RGBA,
- 本質其實就是改下矩陣結構
- 注意點(熬夜凌晨的bug):
glDrawArrays如果要繪製著色器上的點和片段,必須和著色器賦值程式碼放在一個程式碼塊中,否則找不到繪製的資訊,就繪製不上去,造成螢幕黑屏
- 之前是把glDrawArrays和YUV轉RGB方法分開,就一直黑屏.
函式glUniform1i
1 |
glUniform1i(GLint location, GLint x) |
- 指定著色器中亮度紋理對應哪一層紋理單元
- 引數location:著色器中紋理座標
- 引數x:指定那一層紋理
函式glEnableVertexAttribArray
1 |
glEnableVertexAttribArray(GLuint index) |
- 開啟頂點屬性陣列,只有開啟頂點屬性,才能給頂點屬性資訊賦值
函式glVertexAttribPointer
1 |
glVertexAttribPointer(GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *ptr) |
- 設定頂點著色器屬性,描述屬性的基本資訊
- 引數indx:屬性ID,給哪個屬性描述資訊
- 引數size:頂點屬性由幾個值組成,這個值必須位1,2,3或4;
- 引數type:表示屬性的資料型別
- 引數normalized:GL_FALSE表示不要將資料型別標準化
- 引數stride 表示陣列中每個元素的長度;
- 引數ptr 表示陣列的首地址
函式glBindAttribLocation
1 |
glBindAttribLocation(GLuint program, GLuint index, const GLchar *name) |
- 給屬性繫結ID,通過ID獲取屬性,方便以後使用
- 引數program 程式
- 引數index 屬性ID
- 引數name 屬性名稱
函式glDrawArrays
1 |
glDrawArrays(GLenum mode, GLint first, GLsizei count) |
- 作用:使用當前啟用的頂點著色器的頂點資料和片段著色器資料來繪製基本圖形
- mode:繪製方式 一般使用GL_TRIANGLE_STRIP,三角形繪製法
- first:從陣列中哪個頂點開始繪製,一般為0
- count:陣列中頂點數量,在定義頂點著色器的時候,就定義過了,比如vec4,表示4個頂點
- 注意點,如果要繪製著色器上的點和片段,必須和著色器賦值程式碼放在一個程式碼塊中,否則找不到繪製的資訊,就繪製不上去,造成螢幕黑屏。
實戰程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
// YUV 轉 RGB,裡面的頂點和片段都要轉換 - (void)convertYUVToRGBOutput { // 在建立紋理之前,有啟用過紋理單元,就是那個數字.GL_TEXTURE0,GL_TEXTURE1 // 指定著色器中亮度紋理對應哪一層紋理單元 // 這樣就會把亮度紋理,往著色器上貼 glUniform1i(_luminanceTextureAtt, 0); // 指定著色器中色度紋理對應哪一層紋理單元 glUniform1i(_chrominanceTextureAtt, 1); // YUV轉RGB矩陣 glUniformMatrix3fv(_colorConversionMatrixAtt, 1, GL_FALSE, _preferredConversion); // 計算頂點資料結構 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; } // 確定頂點資料結構 GLfloat quadVertexData [] = { -1 * normalizedSamplingSize.width, -1 * normalizedSamplingSize.height, normalizedSamplingSize.width, -1 * normalizedSamplingSize.height, -1 * normalizedSamplingSize.width, normalizedSamplingSize.height, normalizedSamplingSize.width, normalizedSamplingSize.height, }; // 確定紋理資料結構 GLfloat quadTextureData[] = { // 正常座標 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); } |
10-渲染緩衝區到螢幕
- 注意點:
必須設定視窗尺寸glViewport
- 注意點:
渲染程式碼必須呼叫[EAGLContext setCurrentContext:_context]
- 原因:因為是多執行緒,每一個執行緒都有一個上下文,只要在一個上下文繪製就好,設定執行緒的上下文為我們自己的上下文,就能繪製在一起了,否則會黑屏.
- 注意點:
每次建立紋理前,先把之前的紋理引用清空[self cleanUpTextures],否則卡頓
函式glViewport
1glViewport(GLint x, GLint y, GLsizei width, GLsizei height) - 設定OpenGL渲染視窗的尺寸大小,一般跟圖層尺寸一樣.
- 注意:在我們繪製之前還有一件重要的事情要做,我們必須告訴OpenGL渲染視窗的尺寸大小
方法presentRenderbuffer
1 |
- (BOOL)presentRenderbuffer:(NSUInteger)target |
- 是將指定renderbuffer呈現在螢幕上
實戰程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#pragma mark - 10.渲染幀快取 - (void)displayFramebuffer:(CMSampleBufferRef)sampleBuffer { // 因為是多執行緒,每一個執行緒都有一個上下文,只要在一個上下文繪製就好,設定執行緒的上下文為我們自己的上下文,就能繪製在一起了,否則會黑屏. if ([EAGLContext currentContext] != _context) { [EAGLContext setCurrentContext:_context]; } // 清空之前的紋理,要不然每次都建立新的紋理,耗費資源,造成介面卡頓 [self cleanUpTextures]; // 建立紋理物件 [self setupTexture:sampleBuffer]; // YUV 轉 RGB [self convertYUVToRGBOutput]; // 設定視窗尺寸 glViewport(0, 0, self.bounds.size.width, self.bounds.size.height); // 把上下文的東西渲染到螢幕上 [_context presentRenderbuffer:GL_RENDERBUFFER]; } |
11-清理記憶體
- 注意:
只要有Ref結尾的,都需要自己手動管理,清空
函式glClearColor
1 |
glClearColor (GLclampf red, GLclampf green, GLclampf blue, GLclampfalpha) |
- 設定一個RGB顏色和透明度,接下來會用這個顏色塗滿全屏.
函式glClear
1 |
glClear (GLbitfieldmask) |
- 用來指定要用清屏顏色來清除由mask指定的buffer,mask可以是 GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT的自由組合。
- 在這裡我們只使用到 color buffer,所以清除的就是 clolor buffer。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#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的圖片基礎上在進行處理.