《OpenGL从入门到放弃07》滤镜,一定要看,不亏

通过本文你将学习到滤镜的原理,以及一些常用滤镜的实现方式。

关于滤镜大家应该都不陌生,滤镜功能广泛应用
于相机、图库、短视频等应用,如抖音,看一下抖音的滤镜界面

《OpenGL从入门到放弃07》滤镜,一定要看,不亏_第1张图片
抖音滤镜

可以看到,抖音提供了很多炫酷滤镜。
无意间看到这篇文章
当一个 Android 开发玩抖音玩疯了之后(二)
里面介绍了5种抖音滤镜实现,不过因为还没涉及动画和相机预览,所以这一节我们就拿一张静态图片来实现滤镜功能。

我们最终要实现的滤镜效果如下:

滤镜.gif

继续阅读本文,需要你对OpenGL有一定了解,
如果对OpenGL不熟悉,请先阅读以下文章:
《OpenGL从入门到放弃01 》一些基本概念
《OpenGL从入门到放弃02 》GLSurfaceView和Renderer
《OpenGL从入门到放弃03 》相机和视图
《OpenGL从入门到放弃04 》画一个长方形
《OpenGL从入门到放弃05 》着色器语言
《OpenGL从入门到放弃06 》纹理图片显示


进入正题

一、原理

基本色调处理

在OpenGL中,颜色是用包含四个浮点的向量vec4表示,四个浮点分别表示RGBA四个通道,取值范围为0.0-1.0。
我们先读取图片每个像素的色彩值,再对读取到的色彩值进行调整,这样就可以完成对图片的色彩处理了。

1. 黑白图片
我们应该都知道,黑白图片上,每个像素点的RGB三个通道值应该是相等的。知道了这个,将彩色图片处理成黑白图片就非常简单了。

我们直接取出像素点的RGB三个通道,相加然后除以3作为处理后每个通道的值就可以得到一个黑白图片了。这是常见的黑白图片处理的一种方法。
类似的还有权值方法(给予RGB三个通道不同的比例)、只取绿色通道等方式。

与之类似的,
2. 冷色调 的处理就是单一增加蓝色通道的值,
3. 暖色调 的处理是增加红绿通道的值。

还有其他复古、浮雕等处理也都差不多,大家可以自行百度。

图片模糊处理

图片模糊处理相对上面的色调处理稍微复杂一点,通常图片模糊处理是采集周边多个点,然后利用这些点的色彩和这个点自身的色彩进行计算(取平均值),得到一个新的色彩值作为目标色彩。当然,模糊处理有很多算法,类似高斯模糊、径向模糊等等,大家可以自行百度。

放大镜效果

放大镜效果相对模糊处理来说,处理过程也会相对简单一些。我们只需要将制定区域的像素点,都以需要放大的区域中心点为中心,
向外延伸其到这个中心的距离即可实现放大效果。

四分镜效果

把整张图片缩成四份,然后分别放在左上角、右上角、左下角、右下角等地方。我们可以通过改变纹理坐标实现


啰嗦那么多,show me the code ?


《OpenGL从入门到放弃07》滤镜,一定要看,不亏_第2张图片

实战

一、修改着色器

由于这次要大改着色器,所以将着色器代码分别放到一个文件,放在assets目录下,

《OpenGL从入门到放弃07》滤镜,一定要看,不亏_第3张图片
image.png

后缀改为glsl,然后安装as插件GLSL SUPPORT,打开即可看到语法高亮,还可以跳转方法。

《OpenGL从入门到放弃07》滤镜,一定要看,不亏_第4张图片

1、顶点着色器

顶点着色器(公用):shader/image/filter/filter_vertex_base.glsl

uniform mat4 uMVPMatrix;//接收传入的转换矩阵
attribute vec4 aPosition;//接收传入的顶点
attribute vec2 aTexCoord;//接收传入的纹理坐标
varying vec2 vTextureCoord;//增加用于传递给片元着色器的纹理坐标
varying vec4 vPosition;//传顶点坐标给片元着色器
void main() {
    gl_Position = uMVPMatrix * aPosition;//矩阵变换计算之后的位置
    vPosition = uMVPMatrix * aPosition;//矩阵变换计算之后的位置
    vTextureCoord = aTexCoord;
}

这里主要关注的是定义要传递给片元着色器的变量(其它的跟之前文章一样):
vTextureCoord(纹理坐标)
vPosition(顶点坐标)
varying 修饰的变量是传递给片元着色器

2、片元着色器

重点在片元着色器的处理,一个滤镜对应一个片元着色器,不同的滤镜效果在对应的着色器里面处理:

2.1 普通的片元着色器

shader/image/filter/filter_fragment_base.glsl

precision mediump float;// 声明float类型的精度为中等(精度越高越耗资源)
varying vec2 vTextureCoord;//顶点着色器传过来的纹理坐标向量
uniform sampler2D uTexture;//纹理采样器,代表一副纹理
varying vec4 vPosition; //顶点着色器把坐标传过来

void main() {
    vec4 color = texture2D(uTexture, vTextureCoord);//进行纹理采样,拿到当前颜色
    gl_FragColor = color;//不特殊处理
}
2.2 黑白滤镜

shader/image/filter/filter_fragment_gray.glsl

//黑白滤镜
precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D vTexture;
void main() {
    vec4 nColor = texture2D(vTexture, vTextureCoord);//进行纹理采样,拿到当前颜色
    float c=(nColor.r + nColor.g + nColor.b)/3.0; //黑白的处理就是将rgb通道的颜色相加再除以3,再作为rgb通道的值
    gl_FragColor=vec4(c, c, c, nColor.a);
}
2.3 暖色滤镜

shader/image/filter/filter_fragment_warm.glsl

//暖色滤镜
precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D vTexture;
void main() {
    vec4 nColor = texture2D(vTexture, vTextureCoord);//进行纹理采样,拿到当前颜色
    gl_FragColor=nColor + vec4(0.2, 0.2, 0.0, 0.0); //暖就是多加点红跟绿
}
2.4 冷色滤镜

shader/image/filter/filter_fragment_cool.glsl

//冷色滤镜
precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D vTexture;
void main() {
    vec4 nColor = texture2D(vTexture, vTextureCoord);//进行纹理采样,拿到当前颜色
    vec4 deltaColor=nColor + vec4(0.0, 0.0, 0.3, 0.0); //冷就是多加点蓝
    gl_FragColor=deltaColor;
}
2.5 模糊滤镜

shader/image/filter/filter_fragment_buzzy.glsl

//模糊滤镜
precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D vTexture;
void main() {
    vec4 nColor = texture2D(vTexture, vTextureCoord);//进行纹理采样,拿到当前颜色

    float dis = 0.01; //距离越大越模糊

    nColor+=texture2D(vTexture, vec2(vTextureCoord.x-dis, vTextureCoord.y-dis));
    nColor+=texture2D(vTexture, vec2(vTextureCoord.x-dis, vTextureCoord.y+dis));
    nColor+=texture2D(vTexture, vec2(vTextureCoord.x+dis, vTextureCoord.y-dis));
    nColor+=texture2D(vTexture, vec2(vTextureCoord.x+dis, vTextureCoord.y+dis));
    nColor+=texture2D(vTexture, vec2(vTextureCoord.x-dis, vTextureCoord.y-dis));
    nColor+=texture2D(vTexture, vec2(vTextureCoord.x-dis, vTextureCoord.y+dis));
    nColor+=texture2D(vTexture, vec2(vTextureCoord.x+dis, vTextureCoord.y-dis));
    nColor+=texture2D(vTexture, vec2(vTextureCoord.x+dis, vTextureCoord.y+dis));
    nColor+=texture2D(vTexture, vec2(vTextureCoord.x-dis, vTextureCoord.y-dis));
    nColor+=texture2D(vTexture, vec2(vTextureCoord.x-dis, vTextureCoord.y+dis));
    nColor+=texture2D(vTexture, vec2(vTextureCoord.x+dis, vTextureCoord.y-dis));
    nColor+=texture2D(vTexture, vec2(vTextureCoord.x+dis, vTextureCoord.y+dis));
    nColor/=13.0; //周边13个颜色相加,然后取平均,作为这个点的颜色
    gl_FragColor=nColor;
}
2.6 四分镜滤镜

shader/image/filter/filter_fragment_four.glsl

//四分镜
precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D vTexture;
void main() {
    //四分镜就是把整张图片缩成四份,然后分别放在左上角、右上角、左下角、右下角等地方。我们可以通过改变纹理坐标(x和y)得到
    //类似两分镜也是同理
    vec2 uv = vTextureCoord;
    if (uv.x <= 0.5) {
        uv.x = uv.x * 2.0;
    } else {
        uv.x = (uv.x - 0.5) * 2.0;
    }

    if (uv.y <= 0.5) {
        uv.y = uv.y * 2.0;
    } else {
        uv.y = (uv.y - 0.5) * 2.0;
    }
    gl_FragColor = texture2D(vTexture, uv);
}
2.7 发光滤镜

shader/image/filter/filter_fragment_light.glsl

//发光
precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D vTexture;
uniform float uTime; //应用传时间戳过来
void main() {
    float lightUpValue = abs(sin(uTime / 1000.0)) / 4.0;  //计算变化值,sin函数
    vec4 src = texture2D(vTexture, vTextureCoord);
    vec4 addColor = vec4(lightUpValue, lightUpValue, lightUpValue, 1.0);
    gl_FragColor = src + addColor;  //不断地添加一个颜色
}

发光滤镜利用sin函数(取值是-1到1),然后abs取正(0到1),然后除以4,最终lightUpValue 的值是0到0.25,然后给rgb通道都加上一个 lightUpValue 值,就能实现变亮和变暗的效果,像在发光。

以上是滤镜的片元着色器代码,参考Camera中的思想,新建一个着色器管理类
ShaderManager 来加载和缓存各种滤镜对应的OpenGL程序参数

3、 ShaderManager

/**
 * 着色器管理,初始化一次,以后都从缓存取
 */
public class ShaderManager {
    public static final int BASE_SHADER = 1;  //默认
    public static final int GRAY_SHADER = 2;  //黑白、灰色
    public static final int WARM_SHADER = 3;  //暖色
    public static final int COOL_SHADER = 4;  //冷色
    public static final int BUZZY_SHADER = 5;  //模糊
    public static final int FOUR_SHADER = 6;  //四分镜
    public static final int ZOOM_SHADER = 7;  //放大
    public static final int LIGHT_SHADER = 8;  //发光,比较复杂一点

    private static SparseArray mParamSparseArray;

    public static void init(Context context) {

        mParamSparseArray = new SparseArray<>();

        insertParam(BASE_SHADER, GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_vertex_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_fragment_base.glsl"));

        insertParam(GRAY_SHADER, GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_vertex_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_fragment_gray.glsl"));

        insertParam(WARM_SHADER, GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_vertex_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_fragment_warm.glsl"));

        insertParam(COOL_SHADER, GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_vertex_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_fragment_cool.glsl"));

        insertParam(BUZZY_SHADER, GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_vertex_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_fragment_buzzy.glsl"));
        insertParam(FOUR_SHADER, GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_vertex_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_fragment_four.glsl"));

        insertParam(ZOOM_SHADER, GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_vertex_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_fragment_zoom.glsl"));

        insertParam(LIGHT_SHADER, GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_vertex_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/image/filter/filter_fragment_light.glsl"));

    }


    public static void insertParam(int key, String vertexShaderCode, String fragmentShaderCode) {
        int program = GLUtil.createProgram(vertexShaderCode, fragmentShaderCode);
        // 获取顶点着色器的位置的句柄(这里可以理解为当前绘制的顶点位置)
        int positionHandle = GLES20.glGetAttribLocation(program, "aPosition");
        // 获取变换矩阵的句柄
        int mMVPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
        //纹理位置句柄
        int mTexCoordHandle = GLES20.glGetAttribLocation(program, "aTexCoord");

        //缓存OpenGL程序
        Param param = new Param(program, positionHandle, mMVPMatrixHandle, mTexCoordHandle);
        mParamSparseArray.append(key, param);
    }

    //通过key获取缓存中的OpenGL程序参数
    public static Param getParam(int key) {
        return mParamSparseArray.get(key);
    }

    /**
     * 定义一些要缓存的参数
     */
    public static class Param {
        public Param(int program, int positionHandle, int MVPMatrixHandle, int texCoordHandle) {
            this.program = program;
            this.positionHandle = positionHandle;
            mMVPMatrixHandle = MVPMatrixHandle;
            mTexCoordHandle = texCoordHandle;
        }

        public int program;
        //一些公用的句柄(顶点位置、矩阵、纹理坐标)
        public int positionHandle;
        public int mMVPMatrixHandle;
        public int mTexCoordHandle;
    }
}

在onSurfaceCreated中通过调用 init 方法,即可完成所有滤镜对应的着色器的加载,然后切换滤镜只需要获取对应的缓存的参数即可。

4、滤镜的基类 BaseFilter

不同的滤镜效果,只是在片元着色器上会有大的不同,其它基本都是一样的代码,所以我们将这些公有的代码抽取出一个类,BaseFilter

/**
 * 滤镜的基类
 * 

* 加载不同的着色器就有不同滤镜效果 */ public class BaseFilter { private static final String TAG = "BaseFilterView"; private FloatBuffer mVertexBuffer; //顶点坐标数据要转化成FloatBuffer格式 private FloatBuffer mTexCoordBuffer;//顶点纹理坐标缓存 //当前绘制的顶点位置句柄 protected int vPositionHandle; //变换矩阵句柄 protected int mMVPMatrixHandle; //这个可以理解为一个OpenGL程序句柄 protected int mProgram; //纹理坐标句柄 protected int mTexCoordHandle; //变换矩阵,提供set方法 private float[] mvpMatrix = new float[16]; //纹理id protected int mTextureId; public void setMvpMatrix(float[] mvpMatrix) { this.mvpMatrix = mvpMatrix; } private Context mContext; private Bitmap mBitmap; public BaseFilter(Context context, Bitmap bitmap) { mContext = context; this.mBitmap = bitmap; //初始化Buffer、Shader、纹理 initBuffer(); initShader(); initTexture(); } //数据转换成Buffer private void initBuffer() { float vertices[] = new float[]{ -1, 1, 0, -1, -1, 0, 1, 1, 0, 1, -1, 0, };//顶点位置 float[] colors = new float[]{ 0, 0, 0, 1, 1, 0, 1, 1, };//纹理顶点数组 mVertexBuffer = GLUtil.floatArray2FloatBuffer(vertices); mTexCoordBuffer = GLUtil.floatArray2FloatBuffer(colors); } /** * 着色器 */ private void initShader() { //获取程序,封装了加载、链接等操作 ShaderManager.Param param = getProgram(); mProgram = param.program; vPositionHandle = param.positionHandle; // 获取变换矩阵的句柄 mMVPMatrixHandle = param.mMVPMatrixHandle; //纹理位置句柄 mTexCoordHandle = param.mTexCoordHandle; } /** * 滤镜子类重写这个方法,加载不同的OpenGL程序 * * @return */ protected ShaderManager.Param getProgram() { return ShaderManager.getParam(ShaderManager.BASE_SHADER); } protected int initTexture() { Log.d(TAG, "initTexture: start"); int textures[] = new int[1]; //生成纹理id GLES20.glGenTextures( //创建纹理对象 1, //产生纹理id的数量 textures, //纹理id的数组 0 //偏移量 ); mTextureId = textures[0]; //绑定纹理id,将对象绑定到环境的纹理单元 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);//设置MIN 采样方式 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);//设置MAG采样方式 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);//设置S轴拉伸方式 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);//设置T轴拉伸方式 if (mBitmap == null) { Log.e(TAG, "initTexture: mBitmap == null"); return -1; } //加载图片 GLUtils.texImage2D( //实际加载纹理进显存 GLES20.GL_TEXTURE_2D, //纹理类型 0, //纹理的层次,0表示基本图像层,可以理解为直接贴图 mBitmap, //纹理图像 0 //纹理边框尺寸 ); Log.d(TAG, "initTexture: end,mTextureId=" + textures[0]); return textures[0]; } public void draw() { // 将程序添加到OpenGL ES环境 GLES20.glUseProgram(mProgram); /**设置数据*/ // 启用顶点属性,最后对应禁用 GLES20.glEnableVertexAttribArray(vPositionHandle); GLES20.glEnableVertexAttribArray(mTexCoordHandle); //设置三角形坐标数据(一个顶点三个坐标) GLES20.glVertexAttribPointer(vPositionHandle, 3, GLES20.GL_FLOAT, false, 3 * 4, mVertexBuffer); //设置纹理坐标数据 GLES20.glVertexAttribPointer(mTexCoordHandle, 2, GLES20.GL_FLOAT, false, 2 * 4, mTexCoordBuffer); // 将投影和视图转换传递给着色器,可以理解为给uMVPMatrix这个变量赋值为mvpMatrix GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0); //设置使用的纹理编号 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); //绑定指定的纹理id GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId); /** 绘制三角形,三个顶点*/ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); // 禁用顶点数组(好像不禁用也没啥问题) GLES20.glDisableVertexAttribArray(vPositionHandle); GLES20.glDisableVertexAttribArray(mTexCoordHandle); } public void onDestroy() { GLES20.glDeleteProgram(mProgram); mProgram = 0; } }

滤镜的基类代码还是比较清晰的,跟上一篇加载纹理图片基本一样,这里要注意的地方是
initShader方法里面通过调用getProgram方法,从缓存中获取OpenGL程序的参数,然后取出对应的句柄;

子类通过重写getProgram方法这个方法,返回不同的着色器参数,就实现不同滤镜效果

5、黑白滤镜 GrayFilter

/**
 * 黑白滤镜比较好处理
 */
public class GrayFilter extends BaseFilter {

    public GrayFilter(Context context, Bitmap bitmap) {
        super(context, bitmap);
    }

    @Override
    protected ShaderManager.Param getProgram(){
        return ShaderManager.getParam(ShaderManager.GRAY_SHADER);
    }
}

通过对基类的封装,子类要实现滤镜就相当清晰了,只要继承BaseFilter,重写
getProgram 方法,返回该滤镜对应的 Param
即可,冷速调、暖色调、模糊、四分镜都是类似处理,简单返回不同的Param即可,而对于发光滤镜,涉及到新的参数,看一下实现

6、发光滤镜 LightFilter

/**
 * 发光滤镜
 */
public class LightFilter extends BaseFilter {

    private int uTimeHandle = 0;
    private long startTime= 0;

    public LightFilter(Context context, Bitmap bitmap) {
        super(context, bitmap);

        startTime = System.currentTimeMillis();
        uTimeHandle = GLES20.glGetUniformLocation(mProgram,"uTime");
    }

    @Override
    protected ShaderManager.Param getProgram(){
        return ShaderManager.getParam(ShaderManager.LIGHT_SHADER);
    }

    @Override
    public void draw() {
        super.draw();

        //不断更新时间
        GLES20.glUniform1f(uTimeHandle, (System.currentTimeMillis() - startTime));
    }
}

其实也不难,在构造方法中获取uTime的句柄(因为uTime句柄不是共有的句柄),然后在draw方法中不断传新的值过去即可,然后着色器根据这个值的变化做处理,着色器的代码上面有,没印象可以往回翻一下。

7、切换模式

上面着色器代码和滤镜基类,子类都已经准备好了,接下来就差滤镜的切换显示了。
具体代码见 com.lanshifu.opengldemo.image.FilterRenderer
这个类,下面分成几个步骤说一下

7.1 onSurfaceCreated 初始化

在onSurfaceCreated 中

  1. 初始化ShaderManager,预加载各种着色器,缓存起来
  2. 创建一个Bitmap
  3. 创建BaseFilter,传bitmap过去
   public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //着色器初始化,缓存操作
        ShaderManager.init(mContext);

        try {
            mBitmap = BitmapFactory.decodeStream(mContext.getResources().getAssets().open("picture.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        //暂时传Bitmap过去,后面涉及到相机再修改一下
        mFilterView = new BaseFilter(mContext, mBitmap);

        // 设置默认背景颜色
        GLES20.glClearColor(1.0f, 0.0f, 0, 1.0f);
        GLES20.glEnable(GLES20.GL_TEXTURE_2D);

    }
7.2 onSurfaceChanged 计算变换矩阵,没变化
7.3 点击事件

点击了不同的滤镜按钮,调用
setType方法设置当前滤镜类型,并将mFilterChange标志位设置为true,这样在onDrawFrame中判断这个这个标志位true,调用updateFilterView方法更新当前的FilterView

    boolean mFilterChange = false;
    int mFilterType;
    public void setType(int filterType) {
        if (this.mFilterType == filterType) {
            Log.d(TAG, "setType: this.mFilterType == mFilterType");
            return;
        }
        this.mFilterType = filterType;
        mFilterChange = true;
    }
7.4 onDrawFrame

调用updateFilterView,然后调用 mFilterView.draw();

 public void onDrawFrame(GL10 gl) {
        if (mFilterChange) {
            updateFilterView();
        }
        // Redraw background color 重绘背景
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

        mFilterView.setMvpMatrix(mMVPMatrix);
        mFilterView.draw();
    }
7.5 updateFilterView
void updateFilterView() {
        mFilterView = null;
        switch (this.mFilterType) {
            case ShaderManager.GRAY_SHADER:
                mFilterView = new GrayFilter(mContext, mBitmap);
                break;
            case ShaderManager.BASE_SHADER:
                mFilterView = new BaseFilter(mContext, mBitmap);
                break;
            case ShaderManager.WARM_SHADER:
                mFilterView = new WarmFilter(mContext, mBitmap);
                break;
            case ShaderManager.COOL_SHADER:
                mFilterView = new CoolFilter(mContext, mBitmap);
                break;
            case ShaderManager.BUZZY_SHADER:
                mFilterView = new BuzzyFilter(mContext, mBitmap);
                break;
            case ShaderManager.FOUR_SHADER:
                mFilterView = new FourFilter(mContext, mBitmap);
                break;
            case ShaderManager.ZOOM_SHADER:
                mFilterView = new ZoomFilter(mContext, mBitmap);
                break;
            case ShaderManager.LIGHT_SHADER:
                mFilterView = new LightFilter(mContext, mBitmap);
                break;
            default:
                mFilterView = new BaseFilter(mContext, mBitmap);
                break;
        }
        mFilterChange = false;
    }

到此,整个滤镜功能的实现流程已经分析完了,总结一下:

  1. 修改着色器,增加不同的滤镜对应的片元着色器
  2. 用 ShaderManager 管理着色器跟OpenGL程序,缓存起来(提高切换滤镜的性能)
  3. 创建滤镜基类
  4. 不同滤镜类继承滤镜基类,加载各自的滤镜着色器
  5. 点击切换滤镜

如果这篇文章没看懂,那么需要先看之前的几篇文章,在文章开头有。

也可以点击源码clone项目学习。
源码仅供参考

下一篇将介绍动画实现,敬请期待。

你可能感兴趣的:(《OpenGL从入门到放弃07》滤镜,一定要看,不亏)