一、前言
本篇主要讲解GPUImage底层是如何渲染的,GPUImage底层使用的是OPENGL,操控GPU来实现屏幕展示
由于网上OpenGL实战资料特别少,官方文档对一些方法也是解释不清楚,避免广大同学再次爬坑,本篇讲解了不少OpenGL的知识,并且还讲解了花了大量时间解决bug的注意点,曾经因为对glDrawArrays这个方法不熟悉,遇上Bug,晚上熬到凌晨四点都没解决,还是第二天中午解决的。
如果喜欢我的文章,可以关注我微博:袁峥Seemygo
1 - ( void ) captureOutput : ( AVCaptureOutput * ) captureOutput didOutputSampleBuffer : ( CMSampleBufferRef ) sampleBuffer fromConnection : ( AVCaptureConnection * ) connection 采集视频注意点:要设置采集竖屏,否则获取的数据是横屏通过AVCaptureConnection就可以设置 1 [ videoConnection setVideoOrientation : AVCaptureVideoOrientationPortraitUpsideDown ] ;
1 - ( void ) displayFramebuffer : ( CMSampleBufferRef ) sampleBuffer ;
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 } ; }
1 函数 void glGenRenderbuffers ( GLsizei n , GLuint* renderbuffers ) 它是为renderbuffer(渲染缓存)申请一个id(名字),创建渲染缓存参数n表示申请生成renderbuffer的个数参数renderbuffers返回分配给renderbuffer(渲染缓存)的id。 注意:返回的id不会为0,id 0 是OpenGL ES保留的,我们也不能使用id 为0的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
把渲染缓存(renderbuffer)绑定到渲染图层(CAEAGLLayer)上,并为它分配一个共享内存。 参数target,为哪个renderbuffer分配存储空间 参数drawable,绑定在哪个渲染图层,会根据渲染图层里的绘图属性生成共享内存。
1 2 // 底层调用这个分配内存 glRenderbufferStorage ( GL_RENDERBUFFER , GL_RGBA , _openGLLayer . bounds . size . width , _openGLLayer . bounds . size . height ) ;
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 ) ; }
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 #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 ) ; }
控制滤波,滤波就是去除没用的信息,保留有用的信息 一般来说,纹理图像为正方形或长方形。但当它映射到一个多边形或曲面上并变换到屏幕坐标时,纹理的单个纹素很少对应于屏幕图像上的像素。根据所用变换和所用纹理映射,屏幕上单个象素可以对应于一个纹素的一小部分(即放大)或一大批纹素(即缩小) 固定写法
1 2 3 /* 控制滤波 */ glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , GL_CLAMP ) ; glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , GL_CLAMP ) ;
设置像素存储方式 pname:像素存储方式名 一种是GL_PACK_ALIGNMENT,用于将像素数据打包,一般用于压缩。 另一种是GL_UNPACK_ALIGNMENT,用于将像素数据解包,一般生成纹理对象,就需要用到解包. param:用于指定存储器中每个像素行有多少个字节对齐。这个数值一般是1、2、4或8, 一般填1,一个像素对应一个字节;
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 输出的纹理对象 1 2 3 4 5 6 fotmat格式 描述 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 ) ; }
指定着色器中亮度纹理对应哪一层纹理单元 参数location:着色器中纹理坐标 参数x:指定那一层纹理
1 glEnableVertexAttribArray ( GLuint index ) 开启顶点属性数组,只有开启顶点属性,才能给顶点属性信息赋值
设置顶点着色器属性,描述属性的基本信息 参数indx:属性ID,给哪个属性描述信息 参数size:顶点属性由几个值组成,这个值必须位1,2,3或4; 参数type:表示属性的数据类型 参数normalized:GL_FALSE表示不要将数据类型标准化 参数stride 表示数组中每个元素的长度; 参数ptr 表示数组的首地址
1 glBindAttribLocation ( GLuint program , GLuint index , const GLchar * name ) 给属性绑定ID,通过ID获取属性,方便以后使用参数program 程序参数index 属性ID参数name 属性名称
作用:使用当前激活的顶点着色器的顶点数据和片段着色器数据来绘制基本图形 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 ) ; }
设置OpenGL渲染窗口的尺寸大小,一般跟图层尺寸一样. 注意:在我们绘制之前还有一件重要的事情要做,我们必须告诉OpenGL渲染窗口的尺寸大小
1 - ( BOOL ) presentRenderbuffer : ( NSUInteger ) target 是将指定renderbuffer呈现在屏幕上
1 glClearColor ( GLclampf red , GLclampf green , GLclampf blue , GLclampfalpha ) 设置一个RGB颜色和透明度,接下来会用这个颜色涂满全屏.
用来指定要用清屏颜色来清除由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 ) ; }
