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

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

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

抖音滤镜

可以看到,抖音提供了很多炫酷滤镜。
无意间看到这篇文章
当一个 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 ?


实战

一、修改着色器

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

image.png

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

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<Param> 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

/**
 * 滤镜的基类
 * <p>
 * 加载不同的着色器就有不同滤镜效果
 */
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项目学习。
源码仅供参考

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容