GPUImage(二):视频滤镜GPUImageFilter基础篇

按照GPUImage(一):视频采集GPUImageVideoCamera提到的示例demo FilterShowcase的路径,初始化videoCamera后设置GPUImageFilter。

GPUImageFilter

GPUImageFilter继承自GPUImageOutput,遵循GPUImageInput协议,遵循这个协议的对象都可以从响应链的上游接收处理过的纹理并继续处理,下游的处理对象称为上一步的target,响应链的下游可以有多个target(或分支)。
所以,GPUImageFilter就是用来接收源图像,通过自定义的顶点、片元着色器来渲染新的图像,并在绘制完成后通知响应链的下一个对象,既可以流入数据,也可以流出数据,GPUImageView,GPUImageMovieWriter是最终输出target,来显示图片或者视频。

GPUImage(二):视频滤镜GPUImageFilter基础篇_第1张图片
GPUImageFilter.png

GPUImageSaturationFilter

直接看GPUImageFilter代码有些抽象,从GPUImageSaturationFilter入手更容易理解。当从FilterShowcase demo列表中选中Saturation时,filter操作——

filter = [[GPUImageSaturationFilter alloc] init];//filter初始化
.
.
[videoCamera addTarget:filter];//GPUImageVideoCamera添加filter作为target
.
.
GPUImageView *filterView = (GPUImageView *)self.view;//设置当前view为预览view
.
.
[filter addTarget:filterView];//filter添加预览view为最终target
.
.
[videoCamera startCameraCapture];//相机开始捕获

如果需要更改强度
[(GPUImageSaturationFilter *)filter setSaturation:[(UISlider *)sender value]]; break;
简单几步,即完成了调用和强度设置。
GPUImageSaturationFilter的.h文件只有一个saturation属性用于修改,.m文件分为两个部分,第一部分是一个做饱和度调节的片段着色器,这个着色器出自《图形着色器:理论和实践》一书;第二部分是滤镜的初始化方法和饱和度强度设置方法。分别来说。

饱和度片段着色器

饱和度是用来表示颜色的亮度强度的术语。一件亮红色的毛衣的饱和度要远比北京雾霾时灰色的天空的饱和度高得多。
在这个着色器上,参照人类对颜色和亮度的感知过程,有一些优化可以使用。一般而言,人类对亮度要比对颜色敏感的多。这么多年来,压缩软件体积的一个优化方式就是减少存储颜色所用的内存。
人类不仅对亮度比颜色要敏感,同样亮度下,我们对某些特定的颜色反应也更加灵敏,尤其是绿色。这意味着,当你寻找压缩图片的方式,或者以某种方式改变它们的亮度和颜色的时候,多放一些注意力在绿色光谱上是很重要的,因为我们对它最为敏感。

//饱和度片元着色器
NSString *const kGPUImageSaturationFragmentShaderString = SHADER_STRING
(
 varying highp vec2 textureCoordinate;
 
 uniform sampler2D inputImageTexture;
 uniform lowp float saturation;
 
 // Values from "Graphics Shaders: Theory and Practice" by Bailey and Cunningham
 const mediump vec3 luminanceWeighting = vec3(0.2125, 0.7154, 0.0721);
 
 void main()
 {
    lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
    lowp float luminance = dot(textureColor.rgb, luminanceWeighting);
    lowp vec3 greyScaleColor = vec3(luminance);
    
    gl_FragColor = vec4(mix(greyScaleColor, textureColor.rgb, saturation), textureColor.w);
     
 }
);

在 GLSL 中,有三种标签可以赋值给我们的变量:

  • Uniforms
  • Attributes
  • Varyings

Uniforms 是一种外界和你的着色器交流的方式,在顶点着色器和片段着色器里都可以被访问到。Uniforms 在一个渲染循环里作为不变的输入值。如果你正在应用茶色滤镜,并且你已经指定了滤镜的强度,那么这些就是在渲染过程中不需要改变的事情,你可以把它作为 Uniform 输入。
Attributes是随顶点位置不同会变的输入值
Varying 是用来在顶点着色器和片段着色器传递信息的,在顶点着色器和片段着色器都会出现,并且在顶点着色器和片段着色器中必须有匹配的名字。数值在顶点着色器被写入到 varying ,然后在片段着色器被读出。被写入 varying 中的值,在片段着色器中会被以插值的形式插入到两个顶点之间的各个像素中去。
这里出现了两种,逐行来看

varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform lowp float saturation;
  • textureCoordinate:因为顶点着色器负责和片段着色器交流,所以需要创建一个变量和它共享相关的信息。在图像处理中,片段着色器需要的唯一相关信息就是顶点着色器现在正在处理哪个像素,它需要存储像素的 X 和 Y 坐标。片段着色器接收到的正是当前在顶点着色器被设置好的纹理坐标。所以为输入纹理坐标和输入图片纹理声明一个varyings变量。
  • inputImageTexture:为了处理图像,从应用中接收一个图片的引用,把它当做一个 2D 的纹理。这个数据类型被叫做 sampler2D ,这是因为要从这个 2D 纹理中采样出一个点来进行处理。
  • saturation :饱和度的数值是一个我们从用户界面设置的参数。我们需要知道用户需要多少饱和度,从而展示正确的颜色数量。
const mediump vec3 luminanceWeighting = vec3(0.2125, 0.7154, 0.0721);

这就是设置三个元素的向量,为亮度来保存颜色比重的地方。这三个值加起来要为 1,这样我们才能把亮度计算为 0.0 - 1.0 之间的值。注意中间的值,就是表示绿色的值,用了 70% 的颜色比重,而蓝色只用了它的 10%。蓝色对我们的展示不是很好,把更多权重放在绿色上是很有意义的。

接下来是main()函数中的代码:

lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
lowp float luminance = dot(textureColor.rgb, luminanceWeighting);
lowp vec3 greyScaleColor = vec3(luminance);

  • textureColor:texture2D()是GLSL 特有的方法,顾名思义,创建一个 2D 的纹理。它采用之前声明过的属性作为参数来决定被处理的像素的颜色,取样特定像素在图片/纹理中的具体坐标来获取颜色信息。
  • luminance:dot()这是在使用 GLSL 中的点乘运算。点乘计算以包含纹理颜色信息的 vec4 为参数,舍弃 vec4 的最后一个不需要的元素,将它和相对应的亮度权重相乘。然后取出所有的三个值把它们加在一起,计算出这个像素综合的亮度值。
  • greyScaleColor:创建一个三个值都是亮度信息的 vec3。如果只指定一个值,编译器会帮你把该将向量中的每个分量都设成这个值。
gl_FragColor = vec4(mix(greyScaleColor, textureColor.rgb, saturation), textureColor.w);

把所有的片段组合起来。为了确定每个新的颜色是什么,使用mix() 函数。mix 函数会把刚刚计算的灰度值初始的纹理颜色以及得到的饱和度信息相结合。因为片段着色器的唯一目的就是确定一个像素的颜色,gl_FragColor 本质上就是片段着色器的返回语句。一旦这个片段的颜色被设置,接下来片段着色器就不需要再做其他任何事情了,所以在这之后写任何的语句,都不会被执行。

几个函数:

mix(): mix 函数将两个值 (例如颜色值) 混合为一个变量。如果我们有红和绿两个颜色,我们可以用 mix() 函数线性插值。这在图像处理中很常用,比如在应用程序中通过一组独特的设定来控制效果的强度等。

GPUImageSaturationFilter: @implementation

GPUImageSaturationFilter只有两个方法,一个init方法和一个- (void)setSaturation:(CGFloat)newValue;方法。

- (id)init {
    if (!(self = [super initWithFragmentShaderFromString:kGPUImageSaturationFragmentShaderString])) {
        return nil;
    }
    saturationUniform = [filterProgram uniformIndex:@"saturation"];
    self.saturation = 1.0;

    return self;
}
- (void)setSaturation:(CGFloat)newValue {
    _saturation = newValue;
    [self setFloat:_saturation forUniform:saturationUniform program:filterProgram];
}
  • init方法:把创建的片段着色器kGPUImageSaturationFragmentShaderString通过父类GPUImageFilter初始化
    saturationUniform = [filterProgram uniformIndex:@"saturation"]// This does assume a name of "saturation" for the fragment shader//得到名字叫“ saturation”的片元着色器常量

    • (void)setSaturation:(CGFloat)newValue:
      [self setFloat:_saturation forUniform:saturationUniform program:filterProgram]
      调用父类GPUImageFilter的方法,saturationUniform即为init方法中得到的常量,filterProgram是父类GPUImageFilter中初始化的GLProgram对象示例,GLProgram在后续文章中再提。
      如果实现更复杂的滤镜效果,可以参考基于GPUImage的实时美颜滤镜
      GPUImage滤镜中的shader代码分析,及自定义滤镜
      用GPUImage做难一点点的效果

你可能感兴趣的:(GPUImage(二):视频滤镜GPUImageFilter基础篇)