一、前言

本篇主要讲解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/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必须使用这个图层
  1. #pragma mark - 1.自定义图层类型
  2. + (Class)layerClass
  3. {
  4. return [CAEAGLLayer class];
  5. }

02-初始化CAEAGLLayer图层属性

  • 1.不透明度(opaque)=YES,CALayer默认是透明的,透明性能不好,最好设置为不透明.

  • 2.设置绘图属性

    • kEAGLDrawablePropertyRetainedBacking :NO (告诉CoreAnimation不要试图保留任何以前绘制的图像留作以后重用)
    • kEAGLDrawablePropertyColorFormat :kEAGLColorFormatRGBA8 (告诉CoreAnimation用8位来保存RGBA的值)
  • 其实设置不设置都无所谓,默认也是这个值,只不过GPUImage设置了

  1. #pragma mark - 2.初始化图层
  2. - (void)setupLayer
  3. {
  4. CAEAGLLayer *openGLLayer = (CAEAGLLayer *)self.layer;
  5. _openGLLayer = openGLLayer;
  6. // 设置不透明,CALayer 默认是透明的,透明性能不好,最好设置为不透明.
  7. openGLLayer.opaque = YES;
  8. // 设置绘图属性drawableProperties
  9. // kEAGLColorFormatRGBA8 : red、green、blue、alpha共8位
  10. openGLLayer.drawableProperties = @{
  11. kEAGLDrawablePropertyRetainedBacking :[NSNumber numberWithBool:NO],
  12. kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8
  13. };
  14. }

03-创建EAGLContext

  • 需要将它设置为当前context,所有的OpenGL ES渲染默认渲染到当前上下文
  • EAGLContext管理所有使用OpenGL ES进行描绘的状态,命令以及资源信息,要绘制东西,必须要有上下文,跟图形上下文类似。
  • 当你创建一个EAGLContext,你要声明你要用哪个version的API。这里,我们选择OpenGL ES 2.0
  1. #pragma mark - 3、创建OpenGL上下文,并且设置上下文
  2. - (void)setupContext
  3. {
  4. // 指定OpenGL 渲染 API 的版本,目前都使用 OpenGL ES 2.0
  5. EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
  6. // 创建EAGLContext上下文
  7. _context = [[EAGLContext alloc] initWithAPI:api];
  8. // 设置为当前上下文,所有的渲染默认渲染到当前上下文
  9. [EAGLContext setCurrentContext:_context];
  10. }

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. width height:像素单位的宽和高,默认值为0
  2. internal format:内部格式,三大 buffer 格式之一 -- colordepth or stencil
  3. Color bit-depth:仅当内部格式为 color 时,设置颜色的 bit-depth,默认值为0
  4. Depth bit-depth:仅当内部格式为 depth时,默认值为0
  5. Stencil bit-depth: 仅当内部格式为 stencil,默认值为0
函数renderbufferStorage
  1. EAGLContext方法 - (BOOL)renderbufferStorage:(NSUInteger)target fromDrawable:(id<EAGLDrawable>)drawable
  • 把渲染缓存(renderbuffer)绑定到渲染图层(CAEAGLLayer)上,并为它分配一个共享内存。
  • 参数target,为哪个renderbuffer分配存储空间
  • 参数drawable,绑定在哪个渲染图层,会根据渲染图层里的绘图属性生成共享内存。
  1. // 底层调用这个分配内存
  2. glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, _openGLLayer.bounds.size.width, _openGLLayer.bounds.size.height);

实战代码

  1. #pragma mark - 4、创建渲染缓存
  2. - (void)setupRenderBuffer
  3. {
  4. glGenRenderbuffers(1, &_colorRenderBuffer);
  5. glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
  6. // 把渲染缓存绑定到渲染图层上CAEAGLLayer,并为它分配一个共享内存。
  7. // 并且会设置渲染缓存的格式,和宽度
  8. [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_openGLLayer];
  9. }

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. #pragma mark - 5、创建帧缓冲区
  2. - (void)setupFrameBuffer
  3. {
  4. glGenFramebuffers(1, &_framebuffers);
  5. glBindFramebuffer(GL_FRAMEBUFFER, _framebuffers);
  6. // 把颜色渲染缓存 添加到 帧缓存的GL_COLOR_ATTACHMENT0上,就会自动把渲染缓存的内容填充到帧缓存,在由帧缓存渲染到屏幕
  7. glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer);
  8. }

06-创建着色器

着色器
  • 什么是着色器? 通常用来处理纹理对象,并且把处理好的纹理对象渲染到帧缓存上,从而显示到屏幕上。
  • 提取纹理信息,可以处理顶点坐标空间转换,纹理色彩度调整(滤镜效果)等操作。
  • 着色器分为顶点着色器,片段着色器
    • 顶点着色器用来确定图形形状
    • 片段着色器用来确定图形渲染颜色
  • 步骤: 1.编辑着色器代码 2.创建着色器 3.编译着色器
  • 只要创建一次,可以在一开始的时候创建
  1. // shader: 指向着色器对象的句柄
  2. // count: 着色器源代码字符串的数量。着色器可以由多个源字符串组成,但是每个着色器只能有一个main函数
  3. // string: 指向着色器源代码的字符串指针
  4. // length: 指向保存着多个(如果有多个)源代码字符串大小的整型数组指针
  5. void glShaderSource (GLuint shader,
  6. GLsizei count,
  7. const GLchar *const string,
  8. const GLint *length );
着色器代码
  1. // 顶点着色器代码
  2. NSString *const kVertexShaderString = SHADER_STRING
  3. (
  4. attribute vec4 position;
  5. attribute vec2 inputTextureCoordinate;
  6. varying vec2 textureCoordinate;
  7. void main()
  8. {
  9. gl_Position = position;
  10. textureCoordinate = inputTextureCoordinate;
  11. }
  12. );
  13. // 片段着色器代码
  14. NSString *const kYUVFullRangeConversionForLAFragmentShaderString = SHADER_STRING
  15. (
  16. varying highp vec2 textureCoordinate;
  17. precision mediump float;
  18. uniform sampler2D luminanceTexture;
  19. uniform sampler2D chrominanceTexture;
  20. uniform mediump mat3 colorConversionMatrix;
  21. void main()
  22. {
  23. mediump vec3 yuv;
  24. lowp vec3 rgb;
  25. yuv.x = texture2D(luminanceTexture, textureCoordinate).r;
  26. yuv.yz = texture2D(chrominanceTexture, textureCoordinate).ra - vec2(0.5, 0.5);
  27. rgb = colorConversionMatrix * yuv;
  28. gl_FragColor = vec4(rgb, 1);
  29. }
  30. );
实战代码
  1. #pragma mark - 06、创建着色器
  2. - (void)setupShader
  3. {
  4. // 创建顶点着色器
  5. _vertShader = [self loadShader:GL_VERTEX_SHADER withString:kVertexShaderString];
  6. // 创建片段着色器
  7. _fragShader = [self loadShader:GL_FRAGMENT_SHADER withString:kYUVFullRangeConversionForLAFragmentShaderString];
  8. }
  9. // 加载着色器
  10. - (GLuint)loadShader:(GLenum)type withString:(NSString *)shaderString
  11. {
  12. // 创建着色器
  13. GLuint shader = glCreateShader(type);
  14. if (shader == 0) {
  15. NSLog(@"Error: failed to create shader.");
  16. return 0;
  17. }
  18. // 加载着色器源代码
  19. const char * shaderStringUTF8 = [shaderString UTF8String];
  20. glShaderSource(shader, 1, &shaderStringUTF8, NULL);
  21. // 编译着色器
  22. glCompileShader(shader);
  23. // 检查是否完成
  24. GLint compiled = 0;
  25. // 获取完成状态
  26. glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
  27. if (compiled == 0) {
  28. // 没有完成就直接删除着色器
  29. glDeleteShader(shader);
  30. return 0;
  31. }
  32. return shader;
  33. }

07-创建着色器程序

  • 步骤: 1.创建程序 2.贴上顶点和片段着色器 3.绑定attribute属性 4.连接程序 5.绑定uniform属性 6.运行程序
  • 注意点:第3步和第5步,绑定属性,必须有顺序,否则绑定不成功,造成黑屏
  1. #pragma mark - 7、创建着色器程序
  2. - (void)setupProgram
  3. {
  4. // 创建着色器程序
  5. _program = glCreateProgram();
  6. // 绑定着色器
  7. // 绑定顶点着色器
  8. glAttachShader(_program, _vertShader);
  9. // 绑定片段着色器
  10. glAttachShader(_program, _fragShader);
  11. // 绑定着色器属性,方便以后获取,以后根据角标获取
  12. // 一定要在链接程序之前绑定属性,否则拿不到
  13. glBindAttribLocation(_program, ATTRIB_POSITION, "position");
  14. glBindAttribLocation(_program, ATTRIB_TEXCOORD, "inputTextureCoordinate");
  15. // 链接程序
  16. glLinkProgram(_program);
  17. // 获取全局参数,注意 一定要在连接完成后才行,否则拿不到
  18. _luminanceTextureAtt = glGetUniformLocation(_program, "luminanceTexture");
  19. _chrominanceTextureAtt = glGetUniformLocation(_program, "chrominanceTexture");
  20. _colorConversionMatrixAtt = glGetUniformLocation(_program, "colorConversionMatrix");
  21. // 启动程序
  22. glUseProgram(_program);
  23. }

08-创建纹理对象

纹理

  • 采集的是一张一张的图片,可以把图片转换为OpenGL中的纹理, 然后再把纹理画到OpenGL的上下文中

  • 什么是纹理?一个纹理其实就是一幅图像。

  • 纹理映射,我们可以把这幅图像的整体或部分贴到我们先前用顶点勾画出的物体上去.

  • 比如绘制一面砖墙,就可以用一幅真实的砖墙图像或照片作为纹理贴到一个矩形上,这样,一面逼真的砖墙就画好了。如果不用纹理映射的方法,则墙上的每一块砖都必须作为一个独立的多边形来画。另外,纹理映射能够保证在变换多边形时,多边形上的纹理图案也随之变化。

  • 纹理映射是一个相当复杂的过程,基本步骤如下:

    • 1)激活纹理单元、2)创建纹理 、3)绑定纹理 、4)设置滤波
  • 注意:纹理映射只能在RGBA方式下执行

函数glTexParameter
  1. void glTexParameter{if}[v](GLenum target,GLenum pname,TYPE param);
  2. {if}:表示可能是否i,f
  3. [v]:表示v可有可无
  • 控制滤波,滤波就是去除没用的信息,保留有用的信息
  • 一般来说,纹理图像为正方形或长方形。但当它映射到一个多边形或曲面上并变换到屏幕坐标时,纹理的单个纹素很少对应于屏幕图像上的像素。根据所用变换和所用纹理映射,屏幕上单个象素可以对应于一个纹素的一小部分(即放大)或一大批纹素(即缩小)
  • 固定写法
  1. /* 控制滤波 */
  2.   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
  3.   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 输出的纹理对象

    1. fotmat格式 描述
    2. GL_ALPHA 按照ALPHA值存储纹理单元
    3. GL_LUMINANCE 按照亮度值存储纹理单元
    4. GL_LUMINANCE_ALPHA 按照亮度和alpha值存储纹理单元
    5. GL_RGB 按照RGB成分存储纹理单元
    6. GL_RGBA 按照RGBA成分存储纹理单元
实战代码
  1. #pragma mark - 7、创建纹理对象,渲染采集图片到屏幕
  2. - (void)setupTexture:(CMSampleBufferRef)sampleBuffer
  3. {
  4. // 获取图片信息
  5. CVImageBufferRef imageBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer);
  6. // 获取图片宽度
  7. GLsizei bufferWidth = (GLsizei)CVPixelBufferGetWidth(imageBufferRef);
  8. _bufferWidth = bufferWidth;
  9. GLsizei bufferHeight = (GLsizei)CVPixelBufferGetHeight(imageBufferRef);
  10. _bufferHeight = bufferHeight;
  11. // 创建亮度纹理
  12. // 激活纹理单元0, 不激活,创建纹理会失败
  13. glActiveTexture(GL_TEXTURE0);
  14. // 创建纹理对象
  15. CVReturn err;
  16. err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _textureCacheRef, imageBufferRef, NULL, GL_TEXTURE_2D, GL_LUMINANCE, bufferWidth, bufferHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0, &_luminanceTextureRef);
  17. if (err) {
  18. NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
  19. }
  20. // 获取纹理对象
  21. _luminanceTexture = CVOpenGLESTextureGetName(_luminanceTextureRef);
  22. // 绑定纹理
  23. glBindTexture(GL_TEXTURE_2D, _luminanceTexture);
  24. // 设置纹理滤波
  25. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  26. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  27. // 激活单元1
  28. glActiveTexture(GL_TEXTURE1);
  29. // 创建色度纹理
  30. err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _textureCacheRef, imageBufferRef, NULL, GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, bufferWidth / 2, bufferHeight / 2, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, 1, &_chrominanceTextureRef);
  31. if (err) {
  32. NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
  33. }
  34. // 获取纹理对象
  35. _chrominanceTexture = CVOpenGLESTextureGetName(_chrominanceTextureRef);
  36. // 绑定纹理
  37. glBindTexture(GL_TEXTURE_2D, _chrominanceTexture);
  38. // 设置纹理滤波
  39. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  40. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  41. }

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. // YUV 转 RGB,里面的顶点和片段都要转换
  2. - (void)convertYUVToRGBOutput
  3. {
  4. // 在创建纹理之前,有激活过纹理单元,就是那个数字.GL_TEXTURE0,GL_TEXTURE1
  5. // 指定着色器中亮度纹理对应哪一层纹理单元
  6. // 这样就会把亮度纹理,往着色器上贴
  7. glUniform1i(_luminanceTextureAtt, 0);
  8. // 指定着色器中色度纹理对应哪一层纹理单元
  9. glUniform1i(_chrominanceTextureAtt, 1);
  10. // YUV转RGB矩阵
  11. glUniformMatrix3fv(_colorConversionMatrixAtt, 1, GL_FALSE, _preferredConversion);
  12. // 计算顶点数据结构
  13. CGRect vertexSamplingRect = AVMakeRectWithAspectRatioInsideRect(CGSizeMake(self.bounds.size.width, self.bounds.size.height), self.layer.bounds);
  14. CGSize normalizedSamplingSize = CGSizeMake(0.0, 0.0);
  15. CGSize cropScaleAmount = CGSizeMake(vertexSamplingRect.size.width/self.layer.bounds.size.width, vertexSamplingRect.size.height/self.layer.bounds.size.height);
  16. if (cropScaleAmount.width > cropScaleAmount.height) {
  17. normalizedSamplingSize.width = 1.0;
  18. normalizedSamplingSize.height = cropScaleAmount.height/cropScaleAmount.width;
  19. }
  20. else {
  21. normalizedSamplingSize.width = 1.0;
  22. normalizedSamplingSize.height = cropScaleAmount.width/cropScaleAmount.height;
  23. }
  24. // 确定顶点数据结构
  25. GLfloat quadVertexData [] = {
  26. -1 * normalizedSamplingSize.width, -1 * normalizedSamplingSize.height,
  27. normalizedSamplingSize.width, -1 * normalizedSamplingSize.height,
  28. -1 * normalizedSamplingSize.width, normalizedSamplingSize.height,
  29. normalizedSamplingSize.width, normalizedSamplingSize.height,
  30. };
  31. // 确定纹理数据结构
  32. GLfloat quadTextureData[] = { // 正常坐标
  33. 0, 0,
  34. 1, 0,
  35. 0, 1,
  36. 1, 1
  37. };
  38. // 激活ATTRIB_POSITION顶点数组
  39. glEnableVertexAttribArray(ATTRIB_POSITION);
  40. // 给ATTRIB_POSITION顶点数组赋值
  41. glVertexAttribPointer(ATTRIB_POSITION, 2, GL_FLOAT, 0, 0, quadVertexData);
  42. // 激活ATTRIB_TEXCOORD顶点数组
  43. glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, 0, 0, quadTextureData);
  44. // 给ATTRIB_TEXCOORD顶点数组赋值
  45. glEnableVertexAttribArray(ATTRIB_TEXCOORD);
  46. // 渲染纹理数据,注意一定要和纹理代码放一起
  47. glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
  48. }

10-渲染缓冲区到屏幕

  • 注意点:必须设置窗口尺寸glViewport
  • 注意点:渲染代码必须调用[EAGLContext setCurrentContext:_context]
  • 原因:因为是多线程,每一个线程都有一个上下文,只要在一个上下文绘制就好,设置线程的上下文为我们自己的上下文,就能绘制在一起了,否则会黑屏.
  • 注意点:每次创建纹理前,先把之前的纹理引用清空[self cleanUpTextures],否则卡顿
函数glViewport
  1. glViewport(GLint x, GLint y, GLsizei width, GLsizei height)
  • 设置OpenGL渲染窗口的尺寸大小,一般跟图层尺寸一样.
  • 注意:在我们绘制之前还有一件重要的事情要做,我们必须告诉OpenGL渲染窗口的尺寸大小
方法presentRenderbuffer
  1. - (BOOL)presentRenderbuffer:(NSUInteger)target
  • 是将指定renderbuffer呈现在屏幕上
实战代码
  1. #pragma mark - 10.渲染帧缓存
  2. - (void)displayFramebuffer:(CMSampleBufferRef)sampleBuffer
  3. {
  4. // 因为是多线程,每一个线程都有一个上下文,只要在一个上下文绘制就好,设置线程的上下文为我们自己的上下文,就能绘制在一起了,否则会黑屏.
  5. if ([EAGLContext currentContext] != _context) {
  6. [EAGLContext setCurrentContext:_context];
  7. }
  8. // 清空之前的纹理,要不然每次都创建新的纹理,耗费资源,造成界面卡顿
  9. [self cleanUpTextures];
  10. // 创建纹理对象
  11. [self setupTexture:sampleBuffer];
  12. // YUV 转 RGB
  13. [self convertYUVToRGBOutput];
  14. // 设置窗口尺寸
  15. glViewport(0, 0, self.bounds.size.width, self.bounds.size.height);
  16. // 把上下文的东西渲染到屏幕上
  17. [_context presentRenderbuffer:GL_RENDERBUFFER];
  18. }

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. #pragma mark - 11.清理内存
  2. - (void)dealloc
  3. {
  4. // 清空缓存
  5. [self destoryRenderAndFrameBuffer];
  6. // 清空纹理
  7. [self cleanUpTextures];
  8. }
  9. #pragma mark - 销毁渲染和帧缓存
  10. - (void)destoryRenderAndFrameBuffer
  11. {
  12. glDeleteRenderbuffers(1, &_colorRenderBuffer);
  13. _colorRenderBuffer = 0;
  14. glDeleteBuffers(1, &_framebuffers);
  15. _framebuffers = 0;
  16. }
  17. // 清空纹理
  18. - (void)cleanUpTextures
  19. {
  20. // 清空亮度引用
  21. if (_luminanceTextureRef) {
  22. CFRelease(_luminanceTextureRef);
  23. _luminanceTextureRef = NULL;
  24. }
  25. // 清空色度引用
  26. if (_chrominanceTextureRef) {
  27. CFRelease(_chrominanceTextureRef);
  28. _chrominanceTextureRef = NULL;
  29. }
  30. // 清空纹理缓存
  31. CVOpenGLESTextureCacheFlush(_textureCacheRef, 0);
  32. }

GPUImage工作原理

  • GPUImage最关键在于GPUImageFramebuffer这个类,这个类会保存当前处理好的图片信息。
  • GPUImage是通过一个链条处理图片,每个链条通过target连接,每个target处理完图片后,会生成一个GPUImageFramebuffer对象,并且把图片信息保存到GPUImageFramebuffer。
  • 这样比如targetA处理好,要处理targetB,就会先取出targetA的图片,然后targetB在targetA的图片基础上在进行处理.

教你实现GPUImage【OpenGL渲染原理】的更多相关文章

  1. 教你实现GPUImage - OpenGL渲染原理<转>

    http://www.open-open.com/lib/view/open1483943550976.html

  2. 通过OpenGL理解前端渲染原理(1)

    一.OpenGL OpenGL,是一套绘制3D图形的API,当然它也可以用来绘制2D的物体.OpenGL有一大套可以用来操作模型和图片的函数,通常编写OpenGL库的人是显卡的制造者.我们买的显卡都支 ...

  3. OpenGL渲染流程

    一.什么是openGL OpenGL被定义为“图形硬件的一种软件接口”.从本质上说,它是一个3D图形和模型库,具有高度的可移植性,具有非常快的速度. 二.管线 管线这个术语描述了opengl渲染的整个 ...

  4. iOS 图像渲染原理

    http://chuquan.me/2018/09/25/ios-graphics-render-principle/ 通过 图形渲染原理 一文,大致能够了解图形渲染过程中硬件相关的原理.本文将进一步 ...

  5. 【Web动画】CSS3 3D 行星运转 && 浏览器渲染原理

    承接上一篇:[CSS3进阶]酷炫的3D旋转透视 . 最近入坑 Web 动画,所以把自己的学习过程记录一下分享给大家. CSS3 3D 行星运转 demo 页面请戳:Demo.(建议使用Chrome打开 ...

  6. CSharpGL(31)[译]OpenGL渲染管道那些事

    CSharpGL(31)[译]OpenGL渲染管道那些事 +BIT祝威+悄悄在此留下版了个权的信息说: 开始 自认为对OpenGL的掌握到了一个小瓶颈,现在回头细细地捋一遍OpenGL渲染管道应当是一 ...

  7. 初始化glew,创建OpenGL渲染上下文

    void RegisterWinDowClass(HINSTANCE hInstance,std::string className,WNDPROC proc) { WNDCLASS wndClass ...

  8. 一个使用openGL渲染的炫丽Android动画库二(碎片化曲面动画)

    续一个使用openGL渲染的炫丽Android动画库 MagicSurfaceView v1.1.0发布, 新增碎片化曲面动画 地址:https://github.com/gplibs/android ...

  9. 关于QT Graphics View开启OpenGL渲染后复选框、微调框等无法正常显示的问题

    之前学习QT Graphics View框架,除了基本的图元外,还可以通过QGraphicsProxyWidget类添加QT的基本Widget(如按钮.复选框.单选框等),常使用的场景类接口如下: Q ...

  10. [转贴]Cocos2d-x3.2与OpenGL渲染总结(一)Cocos2d-x3.2的渲染流程

    看了opengles有一段时间了,算是了解了一下下.然后,就在基本要决定还是回归cocos2dx 3.2的,看了这篇好文章,欣喜转之~ 推荐看原帖: Cocos2d-x3.2与OpenGL渲染总结(一 ...

随机推荐

  1. 《使用Gin框架构建分布式应用》阅读笔记:p393-p437

    <用Gin框架构建分布式应用>学习第17天,p393-p437总结,总45页. 一.技术总结 1.Prometheus Prometheus放在代码里面使用,还是第一次见.在本人实际的工作 ...

  2. 3-3 C++ vector类型

    目录 3.3.0 模板(Template) vector说明 模板简介 3.3.1 vector的定义和初始化 初始化的方式 总结初始化 3.3.2 往vector中添加元素 3.3.3 vector ...

  3. 开源 - Ideal库 - 特殊时间扩展方法(三)

    书接上回,我们继续来分享一些关于特殊时间获取的常用扩展方法. 01.获取当天的开始时间 当天的开始时间指00:00:00时刻,因此只需要获取DateTime的Date属性只获取时间即可,具体代码如下: ...

  4. 利用sqlmapapi和google-hacking联动自动化sql注入探测

    利用inurl语法搜索+sqlmap梭哈挖到过一点sql注入,这不失为一种好方法. 但是现在的sql注入漏洞的网站是比较少的了,所以这样一个个手工测,不仅效率低,还不一定有什么收获.不妨写一个goog ...

  5. 曲线救国--访问dockerhub仓库

    前言 由于dockerhub也被墙了,导致基础镜像没法拉取.后面解封了,又被墙了... 在这次被墙之前,访问国外的速度也是堪忧,甚至访问不了k8s的镜像,基于此,分享一下笔者经验 使用Daocloud ...

  6. 使用MySQL Shell 8.4.1-LTS 直接将数据复制到 MySQL实例

    在之前的文章中,我谈到了如何使用 MySQL Shell 通过多线程过程来转储和加载数据,以及如何以不同格式导出表数据,然后可以将这些数据导入到新的 MySQL 实例中.这篇文章将讨论我们如何直接将数 ...

  7. 【java基础】-- java接口和抽象类的异同分析

    在java中,通常初学者搞不懂接口与抽象类,这也是面试比较容易问到的一个问题.下面我来谈谈自己的理解.如有不妥之处,还望批评指正,不胜感激. 目录 1.抽象类怎么定义和继承? 2.接口怎么定义和实现? ...

  8. vue 的provide 和 inject

    1.功能说明 在开发过程中,在子组件中如何获取父组件或者祖父级的数据.这个我们之前的做法是在子组件中找到父组件实例,然后使用父组件的数据.这样其实不是很自然. 在vue 中提供了 provide 和 ...

  9. 怎么实时更新echarts图标数据?

    function getData(){ var request . nem XPHLHttpRequest () ; request . open("get",'http://lo ...

  10. 张高兴的 Raspberry Pi AI 开发指南:(三)将自定义模型编译为 Hailo NPU 的 .hef 模型

    目录 Python 环境配置 转换 量化 编译 参考 在上一篇博客中,探讨了如何使用 Python 和 hailo_model_zoo 中预编译的模型来实现目标检测.本篇博客将深入介绍如何将用户自定义 ...