美文网首页
iOS滤镜那些事儿

iOS滤镜那些事儿

作者: 天明天 | 来源:发表于2021-04-27 18:27 被阅读0次

    一. GPUImage 框架的介绍及基本使用

    1.GPUImage 的介绍

    GPUImage是基于OpenGL ES的一套图像、视频处理开源框架,它里面提供了大量的滤镜,使用者可以通过这些滤镜的组合实现很好的效果,同时也很方便在原有基础上实现自定义的滤镜。对于大规模并行操作(如处理图像或实时视频帧),GPU具有比CPU更显着的性能优势。而 GPUImage 所有滤镜是基于OpenGL Shader实现的,所以滤镜效果、图像处理是在GPU上执行的,处理效率比较高,在iPhone4及其以上手机,可以做到实时流畅的效果。而且它隐藏了Objective-COpenGL ES API交互的复杂性。目前市面上的图像视频处理App,95%以上在使用GPUImage,所以学习它的使用及原理还是很有必要的。GPUImage 同时支持iOS跟Andorid平台,地址:iOS版本 Android版本 也支持 Swift版本,本文主要介绍它的 OC 版本,核心类的功能以及原理跟 Andorid 版本是相通的。
    iOS开发者使用方式:直接 CocaPods 集成:

    pod 'GPUImage'
    
    首先来看下它的基本结构图: 架构图

    从这张图中我们可以看到GPUImage的几个核心类:GPUImageOutput GPUImageFilter GPUImageInput 协议 GPUImageFrameBuffer,接下来我们重点讲解这几个类。

    2.核心功能类说明

    GPUImageOutput

    GPUImageOutput 是所有滤镜输入源的基类,也就是滤镜链的起点,先看下他的继承关系:

    GPUImageOutput

    分别解释一下这几种类型:
    • GPUImagePicture
      通过图片来初始化,本质上是先将图片转化为 CGImageRef,然后将 CGImageRef 转化为纹理。
    • GPUImageVideoCamera:通过相机来初始化,本质是封装了AVCaptureVideoDataOutput来获取持续的视频流数据输出,在代理方法captureOutput:didOutputSampleBuffer:fromConnection:拿到 CMSampleBufferRef,将其转化为纹理的过程。GPUImageStillCamera是 GPUImageVideoCamera 的子类,可以用它来实现拍照功能。
    • GPUImageUIElement:可以通过 UIView 或者 CALayer 来初始化。这个类可以用来实现在视频上添加文字水印的功能。
    • GPUImageTextureInput:通过已经存在的纹理来初始化.
    • GPUImageRawDataInput:通过二进制数据初始化,然后将二进制数据转化为纹理.
    • GPUImageMovie:通过本地的视频来初始化。首先通过 AVAssetReader 来逐帧读取视频,然后将帧数据转化为纹理。
    • GPUImageFilter:比较特殊,它既继承自 GPUImageOutput,又遵守协议 GPUImageInput 协议,所以它既可以作为滤镜链的源头,又可以把渲染的纹理输出给遵守 GPUImageInput 协议的类。是滤镜的核心,后面会单独介绍。
    核心功能与方法:

    想象一下,一个滤镜链的源头能做什么呢:

    1. 需要产出一个渲染对象,这个需要渲染的对象就是GPUImageFrameBuffer.几个关于frameBuffer的方法:
    - (GPUImageFramebuffer *)framebufferForOutput;
    

    这个方法可以获得当前正在渲染的frameBuffer

    - (void)removeOutputFramebuffer;
    

    这个方法用来移除当前渲染的frameBuffer

    - (void)setInputFramebufferForTarget:(id<GPUImageInput>)target atIndex:(NSInteger)inputTextureIndex;
    

    这个方法的调用发生在当前output渲染完毕后,需要通知下一个receiver可以开始渲染的时候,把当前Output的FrameBuffer传递给下一个Input,让它可以使用这个FrameBuffer的结果进行渲染。

    1. Target的添加以及管理,用来生成整个FilterChain.

      GPUImageOutput 既然作为一个滤镜的源头,相对应的就得有接受者接受它输出的 FrameBuffer ,这些接受者就是Target,而且有可能有多个接受者。管理这些target的主要方法:
    - (void)addTarget:(id<GPUImageInput>)newTarget;
    - (void)addTarget:(id<GPUImageInput>)newTarget atTextureLocation:(NSInteger)textureLocation;
    

    这两个addTarget方法的作用都是将下一个实现了GPUImageInput协议的对象添加到FilterChain当中来.一旦添加到滤镜链后,在当前Output渲染完成后就会收到通知,从而进行下一步的处理。

    - (NSArray*)targets;
    

    每个Output都可以添加多个target,这个方法可以获取到当前Output所有的target.

    - (void)removeTarget:(id<GPUImageInput>)targetToRemove;
    - (void)removeAllTargets;
    

    这两个方法的作用是将某一个或者所有的target都移出FilterChain。当一个target被移出FilterChain之后,它将不会再收到任何当前Output渲染完成的通知。

    1. 获取当前的GPUImageOutput对FrameBuffer的处理结果
    - (CGImageRef)newCGImageFromCurrentlyProcessedOutput;
    - (CGImageRef)newCGImageByFilteringCGImage:(CGImageRef)imageToFilter;
    - (UIImage *)imageFromCurrentFramebuffer;
    - (UIImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation;
    - (UIImage *)imageByFilteringImage:(UIImage *)imageToFilter;
    - (CGImageRef)newCGImageByFilteringImage:(UIImage *)imageToFilter;
    

    其中最核心的方法是newCGImageFromCurrentlyProcessedOutput,基本上所有的方法最终都调用了这个方法。但是GPUImageOutput并没有为这个方法提供默认的实现,而是提供了一个方法定义。具体的实现在它的两个重要的子类 GPUImageFilter 和 GPUImageFilterGroup 中。而实际上最终调用的方法都在 GPUImageFilter 中实现了.

    GPUImageInput协议

    GPUImageInput 是一个协议,它定义了一个能够接收 FrameBuffer 的 receiver 所必须实现的基本功能。实现这个协议的类可以作为渲染的终点使用。
    实现了 GPUImageInput 接口的类:

    GPUImageInput协议
    对这几个类进行解释:
    • GPUImageMovieWriter:封装了 AVAssetWriter,可以逐帧从帧缓存的渲染结果中读取数据,最后通过 AVAssetWriter 将视频文件保存到指定的路径。
    • GPUImageView:继承自 UIView,通过输入的纹理,执行一遍渲染流程。我们一般使用它来呈现渲染结果。
    • GPUImageTextureOutput:它可以获取到输入的Framebuffer中的纹理对象.
    • GPUImageRawDataOutput:通过 rawBytesForImage 属性,可以获取到当前输入纹理的二进制数据。
    核心功能与方法:

    可以作为滤镜链的终点。基本功能主要包括:

    • 接收 GPUmageOutput 的输出信息;
    • 接收上一个GPUImageOutput渲染完成的通知,并且完成自己的处理;
    1. 接收GPUmageOutput的输出信息对应方法:
    - (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
    - (NSInteger)nextAvailableTextureIndex;
    - (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex;
    - (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex;
    

    根据这些方法可以看到,GPUImageInput 可以接收的信息包括上一个Output输出的FrameBuffer,FrameBuffer的size以及rotation。这些 textureIndex 都是为了提供个需要多个input的Filter准备的。

    1. 接收GPUImageOutput渲染完成的通知对应方法:
    - (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
    

    上一个 GPUImageOutput 渲染完成后会通知它所有的 Target,可以参考下它在GPUImageFilter里面的实现。

    GPUImageFrameBuffer

    GPUImageFrameBuffer 提供了在 GPUImageOutput 和 GPUImageInput 进行数据传递的媒介。在整个渲染流程中,GPUImageFrameBuffer作为一个纽带,将各个不同的元素串联起来;每个GPUImageFrameBuffer 都有一个自己的OpenGL Texture,每个 GPUImageOutput 都会输出一个 GPUImageFrameBuffer 对象,而每个 GPUImageInput都实现了一个setInputFramebuffer:atIndex:方法,来接收上一个Output处理完的纹理.

    • GPUImageFrameBuffer 的获取逻辑,是由GPUImageFrameBufferCache 进行管理的,需要时从BufferCache中获取,使用完成后,被BufferCache回收。FrameBuffer 的创建跟存储是需要消耗资源的,所以 GPUImage 为了尽量减少资源的消耗,会将使用完成的 FrameBuffer 存储在缓存中,每次通过 输入的纹理size 跟 TextureOptions 作为 key 从hash map 中获取。
    GPUImageFilter

    GPUImageFilter 是整个GPUImage框架的核心,GPUImage所内置的100多种滤镜效果都继承于此类。例如我们经常用到的一些滤镜:

    • GPUImageBrightnessFilter:亮度调整滤镜
    • GPUImageExposureFilter:曝光调整滤镜
    • GPUImageContrastFilter:对比度调整滤镜
    • GPUImageSaturationFilter:饱和度调整滤镜
    • GPUImageWhiteBalanceFilter:白平衡调整滤镜
    • GPUImageColorInvertFilter:反转图像的颜色
    • GPUImageCropFilter:将图像裁剪到特定区域
    • GPUImageGaussianBlurFilter:可变半径高斯模糊
    • GPUImageSketchFilter:素描滤镜
    • GPUImageToonFilter:卡通效果
    • GPUImageDissolveBlendFilter:两个图像的混合
    • GPUImageFilterPipeline : 链式组合滤镜
      ...
    核心功能与方法:
    1. GPUImageFilter是GPUImageOutput的子类,但是同时它也实现了GPUImageInput协议。因此,它包含了一个Input和Output的所有功能。既它可以接受一个待渲染对象,渲染完成后继续传递给下一个实现GPUImageInput协议的接受者。具体的方法调用我们在下一小节的 滤镜底层源码分析中讲解。

    2. 提供根据不同的顶点着色器(VertexShader)与片元着色器(FragmentShader)来初始化渲染程序(GLProgram)的方法,但是整个渲染过程是一样的,因此这个过程都被封装到了基类中;

    - (id)initWithVertexShaderFromString:(NSString *)vertexShaderString fragmentShaderFromString:(NSString *)fragmentShaderString;
    - (id)initWithFragmentShaderFromString:(NSString *)fragmentShaderString;
    - (id)initWithFragmentShaderFromFile:(NSString *)fragmentShaderFilename;
    

    这里简单介绍一下这几个OPenGL的术语

    • VertexShader:顶点着色器,OPenGL 接收用户传递的几何数据(顶点信息和几何图元),这些数据经过顶点着色器后可以确定图形的形状以及位置。顶点着色器是 OPenGL 渲染过程的第一个着色器。
    • 光栅化:是将图形的立体位置转换成在屏幕上显示的像素片元的过程;
    • FragmentShader:对光栅化的像素点进行着色就要使用片元着色器。它是OPenGL渲染过程的最后一个着色器。
    • GLProgram: OpenGL ES的program的面向对象封装,包括了VertexShader,FragmentShader的加载,program的link以及对attribute和uniform的获取和管理.
      这里主要是一些根据不同的着色器进行创建Program的方法。
    1. 作为基类提供给子类可以进行覆盖的方法。

    用一句话来总结GPUImageFilter的作用:就是用来接收源图像(FrameBuffer),通过自定义的顶点、片元着色器来渲染新的图像,并在绘制完成后通知响应链的下一个对象。

    3.GPUImage滤镜的使用

    我们先来看它的应用效果

    效果 效果2
    (1) 为图片添加滤镜

    直接上代码:

       /**初始化滤镜源头*/
        GPUImagePicture *imagePic = [[GPUImagePicture alloc] initWithImage:[UIImage imageNamed:@"picOne.jpg"]];
        /**创建滤镜*/
        GPUImageGaussianBlurFilter *gaussianBlur = [[GPUImageGaussianBlurFilter alloc] init];
        gaussianBlur.blurRadiusInPixels = 10;
        /**添加接受者,即target*/
        [imagePic addTarget:gaussianBlur];
        /**增加frameBUffer 计数防止被移除*/
        [gaussianBlur useNextFrameForImageCapture];
        /**开始处理图片*/
        [imagePic processImage];
        /**根据frameBuffer 获取图片*/
        self.showImageView.image = [gaussianBlur imageFromCurrentFramebuffer];
    
    流程说明:
    • 使用图片初始化滤镜源头GPUImagePicture
    • 初始化滤镜效果GPUImageGaussianBlurFilter
    • 为当前滤镜源添加接收者Target addTarget
    • useNextFrameForImageCapture:方法是防止帧缓存被移除,如果不调用这个方法会导致Framebuffer被移除,从而导致Crash
    • 根据滤镜的渲染结果FrameBuffer导出图片[gaussianBlur imageFromCurrentFramebuffer]
    (2) 摄像头捕获视频流添加滤镜

    核心代码:

    - (void)setupCamera
    {
        //videoCamera
        self.gpuVideoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack];
        self.gpuVideoCamera.outputImageOrientation = [[UIApplication sharedApplication] statusBarOrientation];
        //GPUImageView填充模式
        self.gpuImageView.fillMode = kGPUImageFillModePreserveAspectRatioAndFill;
        //空白效果
        GPUImageFilter *clearFilter = [[GPUImageFilter alloc] init];
        [self.gpuVideoCamera addTarget:clearFilter];
        [clearFilter addTarget:self.gpuImageView];
        //Start camera capturing, 里面封装的是AVFoundation的session的startRunning
        [self.gpuVideoCamera startCameraCapture];
    }
    #pragma mark - Action && Notification
    - (IBAction)originalBtnDown:(id)sender {
        /**先移除target*/
        [self.gpuVideoCamera removeAllTargets];
        //空白效果
        GPUImageFilter *clearFilter = [[GPUImageFilter alloc] init];
        [self.gpuVideoCamera addTarget:clearFilter];
        [clearFilter addTarget:self.gpuImageView];
    }
    
    (3) 混合滤镜的使用

    核心代码:

        GPUImageView *filterView = [[GPUImageView alloc] initWithFrame:self.view.frame];
        filterView.center = self.view.center;
        filterView.fillMode = kGPUImageFillModePreserveAspectRatioAndFill;
        [self.view addSubview:filterView];
        /*初始化混合滤镜*/
        filter = [[GPUImageDissolveBlendFilter alloc] init];
        /*设置滤镜混合度*/
        [(GPUImageDissolveBlendFilter *)filter setMix:0.5];
        /*初始化视频输出源*/
        NSURL *sampleURL = [[NSBundle mainBundle] URLForResource:@"IMG_4278" withExtension:@"MOV"];
        movieFile = [[GPUImageMovie alloc] initWithURL:sampleURL];
        movieFile.runBenchmark = YES;
        movieFile.playAtActualSpeed = YES;
        /*初始化摄像头输出源*/
        videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack];
        videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
        NSString *pathToMovie = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/Movie.m4v"];
        unlink([pathToMovie UTF8String]);
        NSURL *movieURL = [NSURL fileURLWithPath:pathToMovie];
        //初始化接受者
        movieWriter = [[GPUImageMovieWriter alloc] initWithMovieURL:movieURL size:CGSizeMake(480.0, 640.0)];
        GPUImageFilter* progressFilter = [[GPUImageFilter alloc] init];
        [movieFile addTarget:progressFilter];
        //设置输出方向
        [progressFilter setInputRotation:kGPUImageRotateRight atIndex:0];
        // 响应链
        [progressFilter addTarget:filter];
        [videoCamera addTarget:filter];
        //设置音源
         movieWriter.shouldPassthroughAudio = YES;
         movieFile.audioEncodingTarget = movieWriter;
         [movieFile enableSynchronizedEncodingUsingMovieWriter:movieWriter];
         // 显示到界面
        [filter addTarget:filterView];
        //添加到接收者
        [filter addTarget:movieWriter];
        [videoCamera startCameraCapture];
        [movieWriter startRecording];
        [movieFile startProcessing];
          /*写入结束后保存视频*/
        __weak typeof(self) weakSelf = self;
        [movieWriter setCompletionBlock:^{
            __strong typeof(self) strongSelf = weakSelf;
            [strongSelf->filter removeTarget:strongSelf->movieWriter];
            [strongSelf->movieWriter finishRecording];
             /*根据movieURL保存视频到本地*/
             // ...
         }];
    
    
    流程说明:
    • 混合滤的核心是GPUImageDissolveBlendFilter的使用,它继承自GPUImageTwoInputFilter,它需要有两个输入源
    • 初始化两个输入源GPUImageVideoCameraGPUImageMovie
    • 添加输入源到DissolveBlendFilter
    • 添加filter到输出数据源GPUImageMovieWriter
    (4) 为视频添加水印

    核心代码:

        GPUImageView *filterView = [[GPUImageView alloc] initWithFrame:self.view.frame];
        self.view = filterView;
        // 混合滤镜初始化
        filter = [[GPUImageDissolveBlendFilter alloc] init];
        //混合度
        [(GPUImageDissolveBlendFilter *)filter setMix:0.5];
        // 本地视频播放源
        NSURL *sampleURL = [[NSBundle mainBundle] URLForResource:@"IMG_4278" withExtension:@"MOV"];
        AVAsset *asset = [AVAsset assetWithURL:sampleURL];
        CGSize size = self.view.bounds.size;
        //设置moive源头
        movieFile = [[GPUImageMovie alloc] initWithAsset:asset];
        movieFile.runBenchmark = YES;
        movieFile.playAtActualSpeed = YES;
        // 水印
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
        label.text = @"我是水印";
        label.font = [UIFont systemFontOfSize:30];
        label.textColor = [UIColor redColor];
        [label sizeToFit];
        UIImage *image = [UIImage imageNamed:@"watermark.png"];
        UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
        UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
        subView.backgroundColor = [UIColor clearColor];
        imageView.center = CGPointMake(subView.bounds.size.width / 2, subView.bounds.size.height / 2);
        [subView addSubview:imageView];
        [subView addSubview:label];
        //设置UI源头
        GPUImageUIElement *uielement = [[GPUImageUIElement alloc] initWithView:subView];
        //GPUImageTransformFilter 动画的filter
        NSString *pathToMovie = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/Movie.m4v"];
        unlink([pathToMovie UTF8String]);
        NSURL *movieURL = [NSURL fileURLWithPath:pathToMovie];
        //初始化接受者
        movieWriter = [[GPUImageMovieWriter alloc] initWithMovieURL:movieURL size:CGSizeMake(480.0, 640.0)];
        //为调整视频方向添加一个空白滤镜
        GPUImageFilter* progressFilter = [[GPUImageFilter alloc] init];
        [movieFile addTarget:progressFilter];
        //设置方向
        [progressFilter setInputRotation:kGPUImageRotateRight atIndex:0];
    
        [progressFilter addTarget:filter];
        [uielement addTarget:filter];
        movieWriter.shouldPassthroughAudio = YES;
        movieFile.audioEncodingTarget = movieWriter;
        [movieFile enableSynchronizedEncodingUsingMovieWriter:movieWriter];
         // 显示到界面
        [filter addTarget:filterView];
        [filter addTarget:movieWriter];
        //开始记录
        [movieWriter startRecording];
        [movieFile startProcessing];
        __weak typeof(self) weakSelf = self;
        //每一帧处理完成 大约30帧/秒
        [progressFilter setFrameProcessingCompletionBlock:^(GPUImageOutput *output, CMTime time){
            CGRect frame = imageView.frame;
            frame.origin.x += 1;
            frame.origin.y += 1;
            imageView.frame = frame;
            //更新UIElement
            [uielement updateWithTimestamp:time];
        }];
        [movieWriter setCompletionBlock:^{
            __strong typeof(self) strongSelf = weakSelf;
            [strongSelf->filter removeTarget:strongSelf->movieWriter];
            [strongSelf->movieWriter finishRecording];
              /*根据movieURL保存视频到本地*/
             // ... 
        }];
    
    流程说明:
    • 混合滤镜的核心是GPUImageDissolveBlendFilter的使用,它继承自GPUImageTwoInputFilter,它需要有两个输入源
    • 初始化两个输入源GPUImageVideoCameraGPUImageUIElement
    • 其他同上
    (5) 滤镜组的使用

    核心代码

        //创建摄像头视图
        GPUImageView *filterView = [[GPUImageView alloc]initWithFrame:self.view.bounds];
        //显示模式充满整个边框
        filterView.fillMode = kGPUImageFillModePreserveAspectRatioAndFill;
        [self.view addSubview:filterView];
        //初始化滤镜源
        self.stillCamera = [[GPUImageStillCamera alloc]initWithSessionPreset:AVCaptureSessionPresetPhoto cameraPosition:AVCaptureDevicePositionBack];
        //输出图像旋转方式
        self.stillCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
        //反色滤镜
        GPUImageColorInvertFilter *filter1 = [[GPUImageColorInvertFilter alloc]init];
        //浮雕滤镜
        GPUImageEmbossFilter *filter2 = [[GPUImageEmbossFilter alloc]init];
        //GPUImageToonFilter *filter3 = [[GPUImageToonFilter alloc] init];
        GPUImageFilterGroup *groupFilter = [[GPUImageFilterGroup alloc]init];
        [groupFilter addFilter:filter1];
        [groupFilter addFilter:filter2];
        //[groupFilter addFilter:filter3];
        [filter1 addTarget:filter2];
        //[filter2 addTarget:filter3];
        //定义了一个变量来保存filter-chain上的最后一个filter,后面保存图片时调用的方法里要用到。
        self.lastFilter = filter2;
        //设置第一个滤镜
        groupFilter.initialFilters = @[filter1];
        //设置最后一个滤镜
        groupFilter.terminalFilter = filter2;
        [self.stillCamera addTarget:groupFilter];
        [groupFilter addTarget:filterView];
        //解决第一帧黑屏,音频缓冲区是在视频缓冲区之前写入的。
        [self.stillCamera addAudioInputsAndOutputs];
        [self.view bringSubviewToFront:self.catchBtn];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            //开始捕捉
            [self.stillCamera startCameraCapture];
        });
    
    
    流程说明:
    • 混合滤的核心是GPUImageFilterGroup的使用
    • 初始化多个滤镜并且添加到滤镜组
    • 设置Group的第一个以及最后一个滤镜
    • 输出

    二. GPUImage 底层源码分析

    1.滤镜链加载流程分析

    通过上面的Demo例子我们能够分析滤镜链的使用流程:

    GPUImageFilter流

    接下来我们以图片添加滤镜的例子分析GPUImage的滤镜方法调用流程:

    • 使用图片初始化滤镜源头GPUImagePicture,调用方法:
    - (id)initWithImage:(UIImage *)newImageSource;
    

    这个方法里面又会调用

    outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:pixelSizeToUseForTexture onlyTexture:YES];
    

    这个方法最主要的作用是根据图片的大小去GPUImageFramebufferCache中去获取一块 FrameBuffer,也就是outputFramebuffer

    • 滤镜的初始化,根据当前自己的顶点着色器以及片元着色器初始化滤镜,以及创建OPenGL ES的渲染程序 GLProgram
    • 为滤镜源添加Target:- (void)addTarget:(id<GPUImageInput>)newTarget;. 在这个方法里面会调用
      [self setInputFramebufferForTarget:newTarget atIndex:textureLocation];
      最终会调用[target setInputFramebuffer:[self framebufferForOutput] atIndex:inputTextureIndex];方法.这个方法最主要的作用是把当前Output的输出 Framebuffer 传递给接受者.
    • - (void)useNextFrameForImageCapture;设置成员变量usingNextFrameForImageCapture = YES代表着输出的结果会被用于获取图像,所以在渲染的核心方法
    - (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
    

    outputFramebuffer加锁,因为默认情况下,当下一个input渲染完成之后,就会释放这个 FrameBuffer。如果你需要对当前的Filter的输出进行截图的话,则需要保留住这个 FrameBuffer。

    • 接下来调用方法[imagePic processImage];: 开始进入滤镜处理流程,接着调用方法-(BOOL)processImageWithCompletionHandler:(void (^)(void))completion;在这个方法内部调用了Target的两个方法,进行OutputFrameBuffer的渲染与向下传递.
     [currentTarget setInputFramebuffer:outputFramebuffer atIndex:textureIndexOfTarget];
     [currentTarget newFrameReadyAtTime:kCMTimeIndefinite atIndex:textureIndexOfTarget];
    

    第一个方法的作用是获取从上个Output传递过来的 Framebuffer,并进行加锁操作。

    第二个方法的作用是利用自身GLProgram进行渲染,并且调用- (void)informTargetsAboutNewFrameAtTime:(CMTime)frameTime;把渲染结果向下一个实现GPUImageInput协议的滤镜传递。

    • [gaussianBlur imageFromCurrentFramebuffer]; 方法:根据 Framebuffer 获取图片,里面调用- (CGImageRef)newCGImageFromCurrentlyProcessedOutput 方法,完成图片获取以及释放GCD信号量。
    if (dispatch_semaphore_wait(imageCaptureSemaphore, convertedTimeout) != 0)
      {
            return NULL;
      }
    

    这里信号量的作用是等待渲染完成。完成后走下面的获取图片流程。整个的方法调用流程可以参考下面的图片:

    方法调用栈

    2.滤镜渲染流程分析

    渲染是整个GPUImageFilter 的核心,在初始化方法中完成了OpenGL ES Program的创建好并且link成功了之后,我们就可以使用这个Program进行渲染了。整个渲染的过程发生在- (void)renderToTextureWithVertices:textureCoordinates:中。我们也借着解析这个方法来了解一下OpenGL ES的渲染过程:

    • [GPUImageContext setActiveShaderProgram:filterProgram];: 将初始化后得到Progrm 上下文设置为默认的context,并且激活。调用的GPUImageContext方法
    + (void)setActiveShaderProgram:(GLProgram *)shaderProgram;
    {
        GPUImageContext *sharedContext = [GPUImageContext sharedImageProcessingContext];
        [sharedContext setContextShaderProgram:shaderProgram];
    }
    
    • 获取一个待渲染的GPUImageFrameBuffer,这个FrameBuffer 会根据输入纹理的尺寸(inputTextureSize)以及纹理信息(outputTextureOptions) 去GPUImageFrameBufferCahe中获取。大致流程为:存在符合要求的Framebuffer就返回一个,没有就去创建。
    • 根据usingNextFrameForImageCapture判断当前Framebuffer是否用于获取图片,如果是则进行加锁。
     if (usingNextFrameForImageCapture)
        { //将这个outputFrameBuffer进行lock。
            [outputFramebuffer lock];
        }
    
    • 将整个FrameBuffer的数据使用backgroundColor进行清空:
    glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha);
    glClear(GL_COLOR_BUFFER_BIT);
    
    • 将上一个Output传递过来的FrameBuffer作为texture用来渲染:
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]);
    glUniform1i(filterInputTextureUniform, 2);
    
    • 将顶点的位置信息以及顶点的纹理坐标信息作为attribute传递给GPU:
    glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices);
    glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
    
    • 进行渲染:
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    
    • 最后将上一个GPUImageOutput传递过来的FrameBuffer使命已经完成,对其进行解锁释放:
    [firstInputFramebuffer unlock];
    

    整个渲染过程完成。

    三. 自定义滤镜

    1.如何加载一个自定义滤镜

    通过上面的学习我们知道,滤镜的效果实际是根据不同的顶点着色器以及片元着色器来实现的。是定义滤镜实际就是自定义这两种着色器。有两种方式来加载我们的自定义滤镜

    • 自定义滤镜类,继承自GPUImageFilter,然后用字符串常量形式加载我们的Shader代码例如:
    NSString *const kGPUImageBrightnessFragmentShaderString = SHADER_STRING
    (
     varying highp vec2 textureCoordinate;
     uniform sampler2D inputImageTexture;
     uniform lowp float brightness;
     
     void main()
     {
         lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
         gl_FragColor = vec4((textureColor.rgb + vec3(brightness)), textureColor.w);
     }
    );
    

    然后根据GPUImageFilter提供的初始化方法进行加载。

    - (id)initWithVertexShaderFromString:(NSString *)vertexShaderString fragmentShaderFromString:(NSString *)fragmentShaderString;
    - (id)initWithFragmentShaderFromString:(NSString *)fragmentShaderString;
    - (id)initWithFragmentShaderFromFile:(NSString *)fragmentShaderFilename;
    
    • 另一种方式:如果只是自定义FragmentShader,可以是将Shader语句封装为fsh结尾的文件,然后调用下面方法进行加载
    - (id)initWithFragmentShaderFromFile:(NSString *)fragmentShaderFilename;
    

    2. 一些特殊的自定义滤镜效果

    自定义滤镜

    一些特殊的滤镜效果,比如抖音的滤镜效果(闪白、灵魂出窍、抖动、缩放、毛刺、眩晕等)可以查看我的GitHub.
    关于自定义滤镜部分需要你对OPenGL ES、线性代数以及算法有基础的了解,并且熟悉GLSL着色语言,如果想进一步学习可以参考GLSL的官方快速入门指导OpenGL ES,我们这篇文章不在涉及。

    四. 总结

    这篇文章主要是介绍了GPUImage的使用、滤镜链加载流程、渲染逻辑,还有一些模块未涉及到,比如GLProgram的创建、link过程,GPUImageMovieComposition视频编辑模块,滤镜的自定义流程等,需要感兴趣的同学自己探究。

    相关文章

      网友评论

          本文标题:iOS滤镜那些事儿

          本文链接:https://www.haomeiwen.com/subject/ltsprltx.html