Tiger的小站

得失从缘,心无增减

GPUImage 自定义滤镜

GPUImage 自定义滤镜

GPUImage 是一个基于 GPU 图像和视频处理的开源 iOS 框架。由于使用 GPU 来处理图像和视频,所以速度非常快,它的作者 BradLarson 称在 iPhone4 上其处理速度是使用 CPU 来处理的 100 倍 (CoreImage 也能使用 GPU 来处理图像,但我觉得 CoreImage 还是慢)。除了速度上的优势,GPUImage 还提供了很多很棒的图像处理滤镜,但有时候这些基本功能仍然无法满足实际开发中的需求,不用担心 GPUImage 支持自定义滤镜。

GPUImage 自定义滤镜需要使用 OpenGL 着色语言( GLSL )编写 Fragment Shader(片段着色器),除此之外你可能还需要一点点图像处理相关的知识。下面我将尝试通过 GPUImage 中的 GPUImageColorInvertFilter(反色滤镜)来讲解一下它的运作过程。

先看.h 文件:

#import "GPUImageFilter.h"

@interface GPUImageColorInvertFilter : GPUImageFilter
{
}

@end

很简单,可以看出 GPUImageColorInvertFilter 是继承了 GPUImageFilter

然后看 .m 文件 中 @implementation 之前的一段代码

NSString *const kGPUImageInvertFragmentShaderString = SHADER_STRING
(
 varying highp vec2 textureCoordinate;

 uniform sampler2D inputImageTexture;

 void main()
 {
    lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);

    gl_FragColor = vec4((1.0 - textureColor.rgb), textureColor.a);
 }
);                                                                    

第 1 行,可以看到 SHADER_STRING 宏中包含着我们的 Shader (着色器)代码,我们的着色器字符串赋给一个 const NSString 对象(这个常量将在 GPUImageFilter 及其子类的初始化过程中用来设置 filter)。

第 2、3 行声明了两个变量。

varying 变量是Vertex 和 Fragment Shader(顶点着色器和片段着色器)之间做数据传递用的,一般 Vertex Shader(顶点着色器) 修改 varying 变量的值,然后 Fragment Shader(片段着色器)使用该varying变量的值。因此varying 变量在 Vertex 和 Fragment Shader 中声明必须一致。放到这里,也就是说 textureCoordinate 必须叫这个名字不能改。

highp 声明 textureCoordinate 精度(相应的还有mediumplowp)。

vec2 声明textureCoordinate 是一个二维向量。

uniform 声明 inputImageTexture 是外部程序传递给 Shader 的变量, Shader 程序内部只能用,不能改。 sampler2D 声明变量是一个2D纹理。

第 4 行,相信你并不陌生,没错儿 Shader 也是从 main() 函数开始执行的。

第 5 行,texture2D 纹理取样器,根据纹理坐标返回纹理单元的值。

第 6 行,(1.0 - textureColor.rgb)textureColor也就是原图的 RGB 值,做了一个向量的减法,这是图像的反色算法,然后把经过反色的 RGB 值和原图的 Alpha 值组成一个新的 vec4(四维向量)值赋给 gl_FragColorgl_FragColor 是 Fragment Shader 预先定义的变量,赋给它的值就是该片段最终的颜色值。

Shader 到这里已经解释完了,可能你依然云里雾里,我来说一下我对这部分功能的理解,可能不对,但目前是管用的,方便你理解:GPUImage 中应该有一个 Vertex Shader,它对图像逐个像素扫描,通过 textureCoordinate 变量将当前的扫描坐标传递给我们的 Fragment Shader,inputImageTexture 包含我们要处理的图像的全部信息,在 Shader 程序内部通过 texture2D 得到 inputImageTexture 在当前位置 textureCoordinate 的 RGBA 值,运用图像处理知识,算出想要的新的 RGBA 值,把结果值赋给 gl_FragColor就算完成了。

现在我们继续看代码,在 Shader 之后是 GPUImageColorInvertFilter 的实现:

@implementation GPUImageColorInvertFilter

- (id)init;
{
    if (!(self = [super initWithFragmentShaderFromString:kGPUImageInvertFragmentShaderString]))
    {
        return nil;
    }

    return self;
}

@end     

很简单,就是使用刚才的着色器代码来设置 filter。这样一个新的滤镜就诞生了~

网路上关于 GPUImage 自定义滤镜 和 GLSL 的资料不是特别多,我斗胆把自己摸索到的理解在这里和你分享,希望对你有所帮助,如有错误指出欢迎指出,另外,可以去这里查阅 OpenGL GLSL 文档,玩的愉快 (●°u°●)​ 」

, , , , , , , , , , ,

  • newapp

    你学的东西总是快我一拍啊。这些个开发滤镜的工程师真不是一般的人,总是能把硬件的优势发挥得淋漓尽致。

    我希望有机会和同好一起讨论iOS 开发相关话题,比如最好搞个讨论小组之类的,我也在上海,最近在自学Swift。

    • ʕ •ᴥ•ʔ 其实不是快一拍,只是开始学习的顺序不同啦~

  • zhangsir

    对于textureCoordinate,我的理解是在默认的顶点着色器中已经命名过了,所以在片段着色器中才使用这个。它表示纹理尺寸,用来计算顶点坐标的,并传递给片段着色器使用。不知对不对。

    • 嗯嗯,我也觉得是这样,投赞成票~

  • Leo Lee

    问一个问题,`varying vec2 textureCoordinate;`里面的 textureCoordinate是属于保留字么?还是这是一个传进来的参数?。。。
    在读GPUImage 的代码的时候看到了还有`leftTextureCoordinate`等,一下子就晕了。。。

    • 我觉得楼下 @zhangsir 的理解应该是对的,可以理解成是把一张图片想成一个 X * Y 的点阵,每个点存着 RGBA 的颜色值,顶点着色器逐个扫描这些点(这一步 GPUImage 做的),通过把 textureCoordinate 把当前扫描到的点的坐标传给片段着色器,也就是我们要写的这个 Shader。textureCoordinate 不是保留字,它是顶点着色器和片段着色器之间做数据传递用的,只要它们之间保持一致就可以了,而由于顶点着色器 GPUImage 已经订好了,所以我们这个应该直接把 textureCoordinate 这个变量名拿来用。

      至于 leftTextureCoordinate。。 还没有研究过。。ಠ_ಠ