前言
GPUImage是一个很好的可以供我们来学习使用的图像处理框架,虽然学习整个框架对于只是学习了OpenGL ES的一些基础知识来说还有些困难,但是我认为学习一个框架我们需要先从整体上去了解这个框架实现的思路,运用了哪些知识点,只要我们把握了整个框架的结构,再慢慢去掌握一些小的知识点,不断去实践,也能够慢慢理解掌握。
整体结构
这里只是从我的理解来介绍,如果有哪些理解不到位的,还望指出理解。
对于图像处理,从学习OpenGL 开始,书上就介绍,图像处理就像管道一样,也可以理解为像流水线,它有着非常明显的时间顺序,这些顺序不能颠倒,这就像OpenGL的接口一样,非常要注意绑定,解绑,当前上下文这些东西,不然你写的东西可能就会出现异常。
而对于GPUImage的实现也同样如此,我学习的思路是 :
Sources(也就是提供数据的地方,原始图像)-> Filters (过滤器,相当于流水线上的加工点) ->Output (最后输出的地方,产品输出)
一些工具类
GLProgram :封装program相关接口
GPUImageContext:封装了Context相关接口,使用单例,保证操作的是同一个Context
GPUImageFramebuffer:封装fbo和texture相关的一些接口,这里需要注意到底使用的是fbo还是直接操作的纹理数据
GPUImageFramebufferCache :对使用的GPUImageFramebuffer做了一些缓存
Sources (提供数据的类,原产品)
打开Sources目录,我们可以看到目录结构
首先我们看到有一个GPUImageOutput的类,这是所有数据源的基础类,这个类是用来管理和它关联的实现了 GPUImageInput 协议的对象,相当于管理图像需要到达的下一个输出节点(filter)或者输出点(Output),那么这个类和它关联的实现GPUImageInput的对象如何联系在一起呢,就是下面这段代码:
/*
outputFramebuffer 返回出去的这个封装了fbo或者texture的对象,起到了连接 Target和 GPUImageOutput的作用
*/
- (GPUImageFramebuffer *)framebufferForOutput;
{
return outputFramebuffer;
}
看下 GPUImagePicture 如何提供数据的,将一个图片数据生成一个数据源,转换过程
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
// 生成一个texture,关注参数onlyTexture为yes
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:pixelSizeToUseForTexture onlyTexture:YES];
[outputFramebuffer disableReferenceCounting];
// 绑定纹理
glBindTexture(GL_TEXTURE_2D, [outputFramebuffer texture]);
if (self.shouldSmoothlyScaleOutput)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
}
// 将图像数据写入
// no need to use self.outputTextureOptions here since pictures need this texture formats and type
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)pixelSizeToUseForTexture.width, (int)pixelSizeToUseForTexture.height, 0, format, GL_UNSIGNED_BYTE, imageData);
if (self.shouldSmoothlyScaleOutput)
{
glGenerateMipmap(GL_TEXTURE_2D);
}
// 解绑纹理
glBindTexture(GL_TEXTURE_2D, 0);
});
同时关注如何将数据传递到和它关联的 GUPImageInput
// 将 outputFrameBuffer传递下去
[currentTarget setInputFramebuffer:outputFramebuffer atIndex:textureIndexOfTarget];
// 下一个节点开始处理数据
[currentTarget newFrameReadyAtTime:kCMTimeIndefinite atIndex:textureIndexOfTarget];
Filters (相当于生产线上的一道工序)
因为filters只是其中的一道加工工序,因此它既需要传入数据(实现了GPUImageInput协议)又需要向下一个节点提供数据(继承了GPUImageOutput)
关键代码:
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
{
static const GLfloat imageVertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
// 将上个节点传递过来的纹理数据做加工处理
[self renderToTextureWithVertices:imageVertices textureCoordinates:[[self class] textureCoordinatesForRotation:inputRotation]];
// 将处理后的纹理传递到后面节点
[self informTargetsAboutNewFrameAtTime:frameTime];
}
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
{
// 接收上个节点传递过来的纹理数据
firstInputFramebuffer = newInputFramebuffer;
[firstInputFramebuffer lock];
}
重点关注代码:
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
{
if (self.preventRendering)
{
[firstInputFramebuffer unlock];
return;
}
[GPUImageContext setActiveShaderProgram:filterProgram];
// 生成的是一个将图像输出到texture的fbo,也就是将 firstInputFramebuffer的texture输出到 outputFramebuffer关联到texture上
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO];
[outputFramebuffer activateFramebuffer];
if (usingNextFrameForImageCapture)
{
[outputFramebuffer lock];
}
[self setUniformsForProgramAtIndex:0];
glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]);
glUniform1i(filterInputTextureUniform, 2);
glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices);
glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[firstInputFramebuffer unlock];
if (usingNextFrameForImageCapture)
{
dispatch_semaphore_signal(imageCaptureSemaphore);
}
}
Output (图像输出,产品展示的地方)
这里是图像处理到达的地方,所以这里只需要接收前面节点处理过的数据,也就是实现 GPUImageInput接口。
关键代码:
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
{
// 接收上个节点处理的数据
inputFramebufferForDisplay = newInputFramebuffer;
[inputFramebufferForDisplay lock];
}
#pragma mark -
#pragma mark GPUInput protocol
// 处理传递过来的纹理数据
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
{
// 比如如果是 GPUImageView,会把数据渲染到View上
}
总结
以上这些只是简单到从整个框架结构的一点分析,当然具体还有很多小的细节需要去学习,但是只要我们把握了整体的组织结构,不管是看某个类的具体实现,还是仿照着去扩展实现一些功能,比如一个filter,一个Sources,我想都还是有据可依,有章可循的,这里面我感触比较深的是对于fbo的理解,这个东西也是贯穿整个框架的,所以要看懂GPUImage,必须要理解透彻fbo,这里有我看过的一篇关于fbo的博客