探索 GPUImage 音视频技术(14):创建自定义过滤器

这个系列文章我们来介绍一位海外工程师如何探索 GPUImage 音视频技术,对于想要开始学习音视频技术的朋友,这些文章是份不错的入门资料,本篇介绍 GPUImage 创建自定义过滤器。

——来自公众号“关键帧Keyframe”的分享

GPUImage 最强大的特性之一,就是能够使用 OpenGL ES 2.0 着色器程序创建你自己的图像处理滤镜。本指南将带你从零开始,逐步完成从简单颜色调整到复杂多输入特效的全部流程。

1、核心架构

GPUImage 采用管线(pipeline)架构:

  1. 源对象(如 GPUImageVideoCameraGPUImagePicture)负责捕获或加载图像
  2. 滤镜对象GPUImageFilter 的子类)通过 OpenGL 着色器处理图像
  3. 输出对象(如 GPUImageViewGPUImageMovieWriter)将结果展示或保存

所有滤镜都遵循 GPUImageInput 协议,可接收上游的纹理输入,同时也能拥有多个下游目标,形成分支处理路径。

2、创建第一个自定义滤镜

2.1、基本结构

写一个片段着色器(GLSL)并初始化 GPUImageFilter

// 从文件加载着色器
GPUImageFilter *customFilter = [[GPUImageFilter alloc]
    initWithFragmentShaderFromFile:@"MyCustomShader"];

// 或者直接使用字符串
NSString *shaderString = @"...你的 GLSL 代码...";
GPUImageFilter *customFilter = [[GPUImageFilter alloc]
    initWithFragmentShaderFromString:shaderString];

2.2、片段着色器模板

片段着色器必须包含以下固定内容:

// MyCustomShader.fsh
varying highp vec2 textureCoordinate;      // 当前像素的纹理坐标(0.0–1.0)
uniform sampler2D inputImageTexture;       // 输入图像纹理

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

    // ↓↓↓ 在这里编写你的处理逻辑 ↓↓↓
    lowp vec4 outputColor = textureColor;

    gl_FragColor = outputColor;
}

2.3、示例:颜色反转

varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;

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

    gl_FragColor = vec4(1.0 - c.rgb, c.a); // 仅反转 RGB,保持 Alpha
}

3、带可调参数的滤镜

多数滤镜需要可调参数(如强度、程度)。做法:

  1. 新建 GPUImageFilter 子类
  2. 定义公开属性
  3. 在 shader 中用 uniform 接收参数

3.1、示例:可调对比度滤镜

3.1.1、着色器 ContrastFilter.fsh

varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform lowp float contrast;   // ← 可调参数

void main()
{
    lowp vec4 c = texture2D(inputImageTexture, textureCoordinate);
    c.rgb = ((c.rgb - 0.5) * contrast) + 0.5; // 对比度公式
    gl_FragColor = c;
}

3.1.2、Objective-C 封装

// GPUImageContrastFilter.h
@interface GPUImageContrastFilter : GPUImageFilter
@property (nonatomic) CGFloat contrast;
@end

// GPUImageContrastFilter.m
@implementation GPUImageContrastFilter {
    GLint contrastUniform;
}
@synthesize contrast = _contrast;

- (instancetype)init {
    if (self = [super initWithFragmentShaderFromFile:@"ContrastFilter"]) {
        contrastUniform = [filterProgram uniformIndex:@"contrast"];
        self.contrast = 1.0;
    }
    returnself;
}

- (void)setContrast:(CGFloat)value {
    _contrast = value;
    [self setFloat:value forUniform:contrastUniform program:filterProgram];
}
@end

使用:

GPUImageContrastFilter *f = [[GPUImageContrastFilter alloc] init];
f.contrast = 1.5; // 提升 50% 对比度

4、进阶技巧

4.1、多输入滤镜

GPUImage 已内置:

  • GPUImageTwoInputFilter
  • GPUImageThreeInputFilter
  • GPUImageFourInputFilter

示例:两张图的混合

GPUImagePicture *bg  = [[GPUImagePicture alloc] initWithImage:bgImg];
GPUImagePicture *fg  = [[GPUImagePicture alloc] initWithImage:fgImg];
GPUImageAlphaBlendFilter *blend = [[GPUImageAlphaBlendFilter alloc] init];
blend.mix = 0.5;

[bg addTarget:blend];
[fg addTarget:blend]; // 顺序决定前后景
[bg processImage];
[fg processImage];

4.2、自定义双输入滤镜

着色器示例(两图混合):

varying highp vec2 textureCoordinate;
varying highp vec2 textureCoordinate2;   // 第二张图的坐标
uniform sampler2D inputImageTexture;     // 背景
uniform sampler2D inputImageTexture2;    // 前景
uniform lowp float mixValue;

void main() {
    lowp vec4 c1 = texture2D(inputImageTexture, textureCoordinate);
    lowp vec4 c2 = texture2D(inputImageTexture2, textureCoordinate2);
    gl_FragColor = mix(c1, c2, mixValue);
}

4.3、卷积与多点采样

边缘检测简化版:

varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform highp float texelWidth, texelHeight;

void main() {
    highp vec2 dx = vec2(texelWidth, 0.0);
    highp vec2 dy = vec2(0.0, texelHeight);
    lowp float s[9];
    // 3×3 邻域采样
    s[0] = texture2D(inputImageTexture, textureCoordinate - dx - dy).r;
    ...
    // Sobel 计算
    highp float gx = -s[0] + s[2] - 2.0*s[3] + 2.0*s[5] - s[6] + s[8];
    highp float gy = -s[0] - 2.0*s[1] - s[2] + s[6] + 2.0*s[7] + s[8];
    gl_FragColor = vec4(vec3(length(vec2(gx, gy))), 1.0);
}

5、滤镜链与分支

5.1、线性链

[videoCamera addTarget:brightnessFilter];
[brightnessFilter addTarget:contrastFilter];
[contrastFilter addTarget:sepiaFilter];
[sepiaFilter addTarget:gpuImageView];
[videoCamera startCameraCapture];

5.1.1、分支路径

任意滤镜都可同时输出到多个目标,实现分屏、并行处理等效果。

6、性能优化

  1. 减少纹理采样texture2D() 昂贵,能省则省
  2. 避免条件分支if 会显著拖慢 GPU
  3. 精度够用即可:颜色用 lowp,坐标用 mediump,仅必要时用 highp
  4. 合并运算:复杂公式尽量化简
  5. 链长度:每多一级滤镜就多一次渲染开销

6.1、内存管理

如需捕获某一帧:

[filter useNextFrameForImageCapture];
[source processImage];
UIImage *out = [filter imageFromCurrentFramebuffer];

7、实战示例

7.1、自定义暗角(Vignette)

varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform highp float vignetteStart, vignetteEnd;

void main() {
    lowp vec4 c = texture2D(inputImageTexture, textureCoordinate);
    highp float d = distance(vec2(0.5), textureCoordinate);
    highp float v = smoothstep(vignetteEnd, vignetteStart, d);
    gl_FragColor = vec4(c.rgb * v, c.a);
}

7.2、自定义 RGB 色相/饱和度调整

varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform highp float redAdj, greenAdj, blueAdj;

void main() {
    lowp vec4 c = texture2D(inputImageTexture, textureCoordinate);
    gl_FragColor = vec4(c.r*redAdj, c.g*greenAdj, c.b*blueAdj, c.a);
}

8、结语

掌握 GLSL 与 GPUImage 架构后,你就能为 iOS 应用打造独一无二的实时视觉效果。牢记:

  • 从模板开始,逐步叠加功能
  • 用 uniform 暴露参数
  • 继承 GPUImageFilter 或其变体
  • 用链式结构组合复杂特效
  • 时刻关注实时性能

学习和提升音视频开发技术,推荐你加入我们的知识星球:【关键帧的音视频开发圈】

探索 GPUImage 音视频技术(14):创建自定义过滤器

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论