iOS开发技巧iOSiOS Collection

教你实现GPUImage【OpenGL渲染原理】

2017-01-06  本文已影响7546人  袁峥

一、前言

本篇主要讲解GPUImage底层是如何渲染的,GPUImage底层使用的是OPENGL,操控GPU来实现屏幕展示

由于网上OpenGL实战资料特别少,官方文档对一些方法也是解释不清楚,避免广大同学再次爬坑,本篇讲解了不少OpenGL的知识,并且还讲解了花了大量时间解决bug的注意点,曾经因为对glDrawArrays这个方法不熟悉,遇上Bug,晚上熬到凌晨四点都没解决,还是第二天中午解决的。

如果喜欢我的文章,可以关注我微博:袁峥Seemygo

二、GPUImageVideoCamera

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
[videoConnection setVideoOrientation:AVCaptureVideoOrientationPortraitUpsideDown];

三、自定义OpenGLView渲染视频

- (void)displayFramebuffer:(CMSampleBufferRef)sampleBuffer;

四、利用OpenGL渲染帧数据并显示

01-自定义图层类型

#pragma mark - 1.自定义图层类型
+ (Class)layerClass
{
    return [CAEAGLLayer class];
}

02-初始化CAEAGLLayer图层属性

#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

#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-创建渲染缓冲区

函数glGenRenderbuffers
函数 void glGenRenderbuffers (GLsizei n, GLuint* renderbuffers)
函数glBindRenderbuffer
 void glBindRenderbuffer (GLenum target, GLuint renderbuffer)
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
EAGLContext方法 - (BOOL)renderbufferStorage:(NSUInteger)target fromDrawable:(id<EAGLDrawable>)drawable
    //  底层调用这个分配内存
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, _openGLLayer.bounds.size.width, _openGLLayer.bounds.size.height);

实战代码

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

}

05-创建帧缓冲区

函数glFramebufferRenderbuffer
void glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
#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-创建着色器

着色器
// shader: 指向着色器对象的句柄
// count: 着色器源代码字符串的数量。着色器可以由多个源字符串组成,但是每个着色器只能有一个main函数
// string: 指向着色器源代码的字符串指针
// length: 指向保存着多个(如果有多个)源代码字符串大小的整型数组指针
void glShaderSource (GLuint shader, 
                           GLsizei count, 
                           const GLchar *const string,
                           const GLint *length );
着色器代码
// 顶点着色器代码
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);
 }
);
实战代码
#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-创建着色器程序

#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-创建纹理对象

纹理

函数glTexParameter
void glTexParameter{if}[v](GLenum target,GLenum pname,TYPE param);
{if}:表示可能是否i,f
[v]:表示v可有可无
/* 控制滤波 */  
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);  
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);  
函数glPixelStorei
 void glPixelStorei(GLenum pname, GLint param);  
函数CVOpenGLESTextureCacheCreateTextureFromImage
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)
实战代码
#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绘制纹理

函数glUniform1i
glUniform1i(GLint location, GLint x)
函数glEnableVertexAttribArray
glEnableVertexAttribArray(GLuint index)
函数glVertexAttribPointer
glVertexAttribPointer(GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *ptr)
函数glBindAttribLocation
glBindAttribLocation(GLuint program, GLuint index, const GLchar *name)
函数glDrawArrays
glDrawArrays(GLenum mode, GLint first, GLsizei count)
实战代码
// 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
glViewport(GLint x, GLint y, GLsizei width, GLsizei height)
方法presentRenderbuffer
- (BOOL)presentRenderbuffer:(NSUInteger)target
实战代码
#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-清理内存

函数glClearColor
glClearColor (GLclampf red, GLclampf green, GLclampf blue, GLclampfalpha)
函数glClear
glClear (GLbitfieldmask)
#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工作原理

上一篇 下一篇

猜你喜欢

热点阅读