GPUImage(二)自定义滤镜

自定义滤镜

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

先看.h 文件:

1
2
3
4
5
6
#import “GPUImageFilter.h"

@interface GPUImageColorInvertFilter : GPUImageFilter
{
}
@end

很简单,可以看出 GPUImageColorInvertFilter 是继承了 GPUImageFilter
然后看 .m 文件 中 @implementation 之前的一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14

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

highp 声明 textureCoordinate 精度(相应的还有mediump和lowp)。

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

uniform 声明 inputImageTexture 是外部程序传递给 Shader 的变量, Shader 程序内部只能用,不能改。

sampler2D 声明变量是一个2D纹理。

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14

@implementation GPUImageColorInvertFilter

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

return self;
}

@end

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

也可以从bundle里面加载OpenGL Shading Language编写的 fragment shader文件, 后缀是 .fsh 或者从字符串里面加载.

1
2
3
4
5
6
1. GPUImageFilter *customFilter = [[GPUImageFilter alloc] initWithFragmentShaderFromFile:@"CustomShader"];

1. NSString *const kfilterShader = SHADER_STRING
2. (
3. ...
4. );

一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
1. varying highp vec2 textureCoordinate;
2. uniform sampler2D inputImageTexture;
3. void main()
4. {
5. lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
6. lowp vec4 outputColor;
7. outputColor.r = (textureColor.r * 0.393) + (textureColor.g * 0.769) + (textureColor.b * 0.189);
8. outputColor.g = (textureColor.r * 0.349) + (textureColor.g * 0.686) + (textureColor.b * 0.168);
9. outputColor.b = (textureColor.r * 0.272) + (textureColor.g * 0.534) + (textureColor.b * 0.131);
10. outputColor.a = 1.0;
11. gl_FragColor = outputColor;
12. }

这段脚本有一个varing, 表示的是材质的2D坐标, 一个unifrom是材质的图片.
main函数里面把这个坐标的颜色取出来处理后生成新的颜色, 交给gl_FragColor传递个pipeline的下一个单元. 在这里也就是最终输出.

但是自己没有openGL基础,如果想要开发滤镜也会非常困难。下一篇就讲一下如何快速开发滤镜。