使用C++实现基于OpenGLES的滤镜功能

本文主要阐述使用C++实现基于OpenGLES的滤镜功能的思路,可用于Android和iOS平台上,建议配合本博客上一篇文章《解读GPUImage框架滤镜功能》一起阅读。

着色器管理组件的实现

GPUImage中实现的着色器管理组件对于顶点着色器和片段着色器的编译、链接和使用进行了封装,这部分的设计思想可以参考。但是针对着色器参数的设置,是用dictionary存储参数设置的代码block然后在渲染时执行block来实现的,该部分在C++中的实现有所不同。
在C++中,着色器参数的设置操作放在着色器管理组件中实现,提供设置参数的函数供外部调用。滤镜对象使用外部注入的着色器管理组件,调用接口设置参数即可,不需要在滤镜类的函数实现中耦合着色器参数设置相关的代码。

帧缓存管理组件的实现

帧缓存管理组件涉及纹理、帧缓存的创建、绑定、获取属性值等操作。GPUImage中比较有亮点的就是帧缓存池的实现,在C++中可以使用STL标准库中的Map实现该功能。
同时参考GPUImage,在帧缓存管理组件中加入引用计数和纹理属性:通过引用计数,可以更好地掌握当前帧缓存对象的使用情况;使用帧缓存对象的纹理属性来缓存帧缓存对象,能够更高效地进行帧缓存对象的复用。

缓冲池组件的实现

缓冲池组件以单例形式面向滤镜对象和帧缓存管理组件提供功能,其内部管理了具体的帧缓存对象,实现逻辑如下:
1、当缓冲池接收到外部申请帧缓存对象的请求时,会根据传入的纹理属性,生成一个特定的字符串,然后根据该字符串先查找同类型的帧缓存对象的个数。若个数大于0,则在字符串后面附加数量的index,作为key去实际的缓存中进行读取,避免了遍历不同类型字符串的情况;若个数等于0,则根据纹理属性创建一个帧缓存对象,并提供给外部。
2、当外部释放一个帧缓存对象,且帧缓存对象的引用计数为0时,该帧缓存对象会自动回归缓冲池。缓冲池内部会根据其纹理属性生成特定的字符串,将同类型帧缓存对象的个数+1,同时在字符串后附加目前的数量index,以该字符串作为key来存储该帧缓存对象。

滤镜基类的实现

1、输入输出基类

在GPUImage框架中,使用的是Apple生态专属的Objective-C语言,GPUImageOutput以基类的形式提供,GPUImageInput以协议的形式提供。因C++语言的特性,使用C++实现时均以基类的形式提供,涉及具体功能的函数以虚函数的形式提供给子类进行重写。

2、滤镜基类优化

在研究GPUImage代码的时候,发现其在多输入滤镜和多着色器滤镜的实现上拓展性很差。比如双输入滤镜,仅仅是在单输入滤镜的基础上,增加几个命名由Second开头的成员变量来记录相关数据,如果需要三输入滤镜,则还需要在双输入滤镜的基础上再写一个子类进行实现;再比如双着色器滤镜,仅仅是在具体执行渲染操作的代码中,将渲染相关操作又重复了一遍,区别只在于用了不同的着色器。因此在用C++实现滤镜功能时,为了实现滤镜的拓展性,我整合了多输入和多着色器滤镜的特点,改造出了既支持多输入又支持多着色器的滤镜基类。具体的内部流程如下图所示: