一. GPUImage
框架的介绍及基本使用
1.GPUImage
的介绍
GPUImage是基于OpenGL ES
的一套图像、视频处理开源框架,它里面提供了大量的滤镜,使用者可以通过这些滤镜的组合实现很好的效果,同时也很方便在原有基础上实现自定义的滤镜。对于大规模并行操作(如处理图像或实时视频帧),GPU具有比CPU更显着的性能优势。而 GPUImage 所有滤镜是基于OpenGL Shader
实现的,所以滤镜效果、图像处理是在GPU上执行的,处理效率比较高,在iPhone4及其以上手机,可以做到实时流畅的效果。而且它隐藏了Objective-C
与OpenGL 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
是所有滤镜输入源的基类,也就是滤镜链的起点,先看下他的继承关系:
分别解释一下这几种类型:
-
GPUImagePicture
通过图片来初始化,本质上是先将图片转化为 CGImageRef,然后将 CGImageRef 转化为纹理。
-
GPUImageVideoCamera
:通过相机来初始化,本质是封装了AVCaptureVideoDataOutput来获取持续的视频流数据输出,在代理方法captureOutput:didOutputSampleBuffer:fromConnection:
拿到 CMSampleBufferRef,将其转化为纹理的过程。GPUImageStillCamera
是 GPUImageVideoCamera 的子类,可以用它来实现拍照功能。
-
GPUImageUIElement
:可以通过 UIView 或者 CALayer 来初始化。这个类可以用来实现在视频上添加文字水印的功能。
-
GPUImageTextureInput
:通过已经存在的纹理来初始化.
-
GPUImageRawDataInput
:通过二进制数据初始化,然后将二进制数据转化为纹理.
-
GPUImageMovie
:通过本地的视频来初始化。首先通过 AVAssetReader 来逐帧读取视频,然后将帧数据转化为纹理。 -
GPUImageFilter
:比较特殊,它既继承自 GPUImageOutput,又遵守协议 GPUImageInput 协议,所以它既可以作为滤镜链的源头,又可以把渲染的纹理输出给遵守 GPUImageInput 协议的类。是滤镜的核心,后面会单独介绍。
核心功能与方法:
想象一下,一个滤镜链的源头能做什么呢:
- 需要产出一个渲染对象,这个需要渲染的对象就是
GPUImageFrameBuffer
.几个关于frameBuffer的方法:
- (GPUImageFramebuffer *)framebufferForOutput;
这个方法可以获得当前正在渲染的frameBuffer
- (void)removeOutputFramebuffer;
这个方法用来移除当前渲染的frameBuffer
- (void)setInputFramebufferForTarget:(id<GPUImageInput>)target atIndex:(NSInteger)inputTextureIndex;
这个方法的调用发生在当前output渲染完毕后,需要通知下一个receiver可以开始渲染的时候,把当前Output的FrameBuffer传递给下一个Input,让它可以使用这个FrameBuffer的结果进行渲染。
- 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渲染完成的通知。
- 获取当前的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 接口的类:
对这几个类进行解释:
-
GPUImageMovieWriter
:封装了 AVAssetWriter,可以逐帧从帧缓存的渲染结果中读取数据,最后通过 AVAssetWriter 将视频文件保存到指定的路径。
-
GPUImageView
:继承自 UIView,通过输入的纹理,执行一遍渲染流程。我们一般使用它来呈现渲染结果。
-
GPUImageTextureOutput
:它可以获取到输入的Framebuffer中的纹理对象.
-
GPUImageRawDataOutput
:通过 rawBytesForImage 属性,可以获取到当前输入纹理的二进制数据。
核心功能与方法:
可以作为滤镜链的终点。基本功能主要包括:
- 接收 GPUmageOutput 的输出信息;
- 接收上一个GPUImageOutput渲染完成的通知,并且完成自己的处理;
- 接收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准备的。
- 接收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
: 链式组合滤镜
...
核心功能与方法:
GPUImageFilter是GPUImageOutput的子类,但是同时它也实现了GPUImageInput协议。因此,它包含了一个Input和Output的所有功能。既它可以接受一个待渲染对象,渲染完成后继续传递给下一个实现GPUImageInput协议的接受者。具体的方法调用我们在下一小节的 滤镜底层源码分析中讲解。
提供根据不同的顶点着色器(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的方法。
- 作为基类提供给子类可以进行覆盖的方法。
用一句话来总结GPUImageFilter的作用:就是用来接收源图像(FrameBuffer),通过自定义的顶点、片元着色器来渲染新的图像,并在绘制完成后通知响应链的下一个对象。
3.GPUImage滤镜的使用
我们先来看它的应用效果
(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
,它需要有两个输入源 - 初始化两个输入源
GPUImageVideoCamera
跟GPUImageMovie
- 添加输入源到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
,它需要有两个输入源 - 初始化两个输入源
GPUImageVideoCamera
跟GPUImageUIElement
- 其他同上
(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例子我们能够分析滤镜链的使用流程:
接下来我们以图片添加滤镜的例子分析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
视频编辑模块,滤镜的自定义流程等,需要感兴趣的同学自己探究。
- 进一步学习需要掌握的内容
The OpenGL Shading Language
GLSL内建的函数介绍