android平台下OpenGL ES 3.0给图片添加黑白滤镜

OpenGL ES 3.0学习实践

  • android平台下OpenGL ES 3.0从零开始
  • android平台下OpenGL ES 3.0绘制纯色背景
  • android平台下OpenGL ES 3.0绘制圆点、直线和三角形
  • android平台下OpenGL ES 3.0绘制彩色三角形
  • android平台下OpenGL ES 3.0从矩形中看矩阵和正交投影
  • android平台下OpenGL ES 3.0着色语言基础知识(上)
  • android平台下OpenGL ES 3.0着色语言基础知识(下)
  • android平台下OpenGL ES 3.0实例详解顶点属性、顶点数组
  • android平台下OpenGL ES 3.0实例详解顶点缓冲区对象(VBO)和顶点数组对象(VAO)
  • android平台下OpenGL ES 3.0绘制立方体的几种方式
  • android平台下OpenGL ES 3.0实现2D纹理贴图显示bitmap
  • android平台下OpenGL ES 3.0基于GLSurfaceView对相机Camera预览实时处理
  • android平台下OpenGL ES 3.0基于TextureView对相机Camera预览实时处理
  • android平台下基于ANativeWindow实现渲染bitmap图像
  • android平台下OpenGL ES 3.0给图片添加黑白滤镜

概述

之前了解的一些第三方库,可以对视频预览页面进行各种滤镜切换,比较神奇,于是自己研究了一番,基础原理倒也不是很难,我们先从最简单的图片开始处理,下面记录一下过程。

配置环境

操作系统:ubuntu 16.05

效果预览

android平台下OpenGL ES 3.0给图片添加黑白滤镜_第1张图片 android平台下OpenGL ES 3.0给图片添加黑白滤镜_第2张图片

实现方案

笔者这里封装了一个渲染控件OpenGLView,继承自GLSurfaceView

/**
 * @anchor: andy
 * @date: 2019-03-27
 * @description:
 */
public class OpenGLView extends GLSurfaceView {

    private FilterRenderer mGLRender;

    public OpenGLView(Context context) {
        this(context, null);
    }

    public OpenGLView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setupSurfaceView();
    }

    private void setupSurfaceView() {
        //设置版本
        setEGLContextClientVersion(3);
        mGLRender = new FilterRenderer();
        setRenderer(mGLRender);

        try {
            requestRender();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 设置滤镜
     * 滤镜由于可能存在多种类型
     * 这里抽象了一个基础的滤镜类
     * queueEvent
     *
     * @param baseFilter
     */
    public void setFilter(final BaseFilter baseFilter) {
        queueEvent(new Runnable() {
            @Override
            public void run() {
                if (mGLRender != null) {
                    mGLRender.setFilter(baseFilter);
                }
            }
        });
        try {
            requestRender();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

FilterRenderer的实现:实现了GLSurfaceView.Renderer接口

/**
 * @anchor: andy
 * @date: 2018-11-09
 * @description: 
 */
public class FilterRenderer implements GLSurfaceView.Renderer {

    private static final String TAG = "FilterRenderer";

    private int mSurfaceWidth, mSurfaceHeight;

    private BaseFilter mTargetFilter = new OriginFilter();

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        mTargetFilter.onSurfaceCreated();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        mSurfaceWidth = width;
        mSurfaceHeight = height;
        mTargetFilter.onSurfaceChanged(width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        mTargetFilter.onDrawFrame();
    }

    public void setFilter(BaseFilter baseFilter) {
        if (mTargetFilter != null) {
            mTargetFilter.onDestroy();
        }
        mTargetFilter = baseFilter;
        if (mTargetFilter != null) {
            mTargetFilter.onSurfaceCreated();
            mTargetFilter.onSurfaceChanged(mSurfaceWidth, mSurfaceHeight);
        }
    }

}

由于目前的想法是实现一个简单的黑白滤镜,其实直接在片段着色器中硬编码,也可以可以实现的,但是后续的扩展性可能难以保证,笔者这里是抽象了一个基础的BaseFilter滤镜实现类,后续的滤镜都是基于这个扩展而来。

抽象类BaseFilter的实现:子类可以扩展不同的滤镜

/**
 * @anchor: andy
 * @date: 2019-03-27
 * @description:
 */
public abstract class BaseFilter implements RendererFilter {

    private static final String TAG = "RendererFilter";

    private FloatBuffer vertexBuffer, mTexVertexBuffer;

    private ShortBuffer mVertexIndexBuffer;

    protected int mProgram;

    private int textureId;

    /**
     * 顶点坐标
     * (x,y,z)
     */
    private float[] POSITION_VERTEX = new float[]{
            0f, 0f, 0f,     //顶点坐标V0
            1f, 1f, 0f,     //顶点坐标V1
            -1f, 1f, 0f,    //顶点坐标V2
            -1f, -1f, 0f,   //顶点坐标V3
            1f, -1f, 0f     //顶点坐标V4
    };

    /**
     * 纹理坐标
     * (s,t)
     */
    private static final float[] TEX_VERTEX = {
            0.5f, 0.5f, //纹理坐标V0
            1f, 0f,     //纹理坐标V1
            0f, 0f,     //纹理坐标V2
            0f, 1.0f,   //纹理坐标V3
            1f, 1.0f    //纹理坐标V4
    };

    /**
     * 索引
     */
    private static final short[] VERTEX_INDEX = {
            0, 1, 2,  //V0,V1,V2 三个顶点组成一个三角形
            0, 2, 3,  //V0,V2,V3 三个顶点组成一个三角形
            0, 3, 4,  //V0,V3,V4 三个顶点组成一个三角形
            0, 4, 1   //V0,V4,V1 三个顶点组成一个三角形
    };

    private int uMatrixLocation;

    /**
     * 矩阵
     */
    private float[] mMatrix = new float[16];

    /**
     * 顶点着色器
     */
    private String mVertexShader;

    /**
     * 片段着色器
     */
    private String mFragmentShader;

    /**
     * 加载默认的着色器
     */
    public BaseFilter() {
        this(ResReadUtils.readResource(R.raw.no_filter_vertex_shader), ResReadUtils.readResource(R.raw.no_filter_fragment_shader));
    }

    public BaseFilter(final String vertexShader, final String fragmentShader) {
        mVertexShader = vertexShader;
        mFragmentShader = fragmentShader;
        //初始化内存空间
        setupBuffer();
    }

    private void setupBuffer() {
        //分配内存空间,每个浮点型占4字节空间
        vertexBuffer = ByteBuffer.allocateDirect(POSITION_VERTEX.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        //传入指定的坐标数据
        vertexBuffer.put(POSITION_VERTEX);
        vertexBuffer.position(0);

        mTexVertexBuffer = ByteBuffer.allocateDirect(TEX_VERTEX.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(TEX_VERTEX);
        mTexVertexBuffer.position(0);

        mVertexIndexBuffer = ByteBuffer.allocateDirect(VERTEX_INDEX.length * 2)
                .order(ByteOrder.nativeOrder())
                .asShortBuffer()
                .put(VERTEX_INDEX);
        mVertexIndexBuffer.position(0);
    }

    public void setupProgram() {
        //编译着色器
        final int vertexShaderId = ShaderUtils.compileVertexShader(mVertexShader);
        final int fragmentShaderId = ShaderUtils.compileFragmentShader(mFragmentShader);
        //链接程序片段
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
        uMatrixLocation = GLES30.glGetUniformLocation(mProgram, "u_Matrix");
        //加载纹理
        textureId = TextureUtils.loadTexture(AppCore.getInstance().getContext(), R.drawable.main);

        LogUtils.d(TAG, "program=%d matrixLocation=%d textureId=%d", mProgram, uMatrixLocation, textureId);
    }

    @Override
    public final void onSurfaceCreated() {
        //设置背景颜色
        GLES30.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
        //初始化程序对象
        setupProgram();
    }

    @Override
    public final void onSurfaceChanged(int width, int height) {
        GLES30.glViewport(0, 0, width, height);

        final float aspectRatio = width > height ?
                (float) width / (float) height :
                (float) height / (float) width;
        if (width > height) {
            //横屏
            Matrix.orthoM(mMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f);
        } else {
            //竖屏
            Matrix.orthoM(mMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f);
        }
    }

    @Override
    public void onDrawFrame() {
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);

        //使用程序片段
        GLES30.glUseProgram(mProgram);

        //更新属性等信息
        onUpdateDrawFrame();

        GLES30.glUniformMatrix4fv(uMatrixLocation, 1, false, mMatrix, 0);

        GLES30.glEnableVertexAttribArray(0);
        GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);

        GLES30.glEnableVertexAttribArray(1);
        GLES30.glVertexAttribPointer(1, 2, GLES30.GL_FLOAT, false, 0, mTexVertexBuffer);

        GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
        //绑定纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId);

        // 绘制
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, VERTEX_INDEX.length, GLES20.GL_UNSIGNED_SHORT, mVertexIndexBuffer);
    }

    public void onUpdateDrawFrame() {
		//供子类实现
    }

    @Override
    public void onDestroy() {
        GLES30.glDeleteProgram(mProgram);
    }

}

笔者这里以黑白滤镜为例:

/**
 * @anchor: andy
 * @date: 2019-03-27
 * @description:
 */
public class GrayFilter extends BaseFilter {

    private int aFilterLocation;

    private float[] filterValue = new float[]{0.299f, 0.587f, 0.114f};

    public GrayFilter() {
        super(ResReadUtils.readResource(R.raw.gray_filter_vertex_shader), ResReadUtils.readResource(R.raw.gray_filter_fragment_shader));
    }

    @Override
    public void setupProgram() {
        super.setupProgram();
        //获取顶点着色器中滤镜统一变量的位置
        aFilterLocation = GLES30.glGetUniformLocation(mProgram, "a_Filter");
    }

    @Override
    public void onUpdateDrawFrame() {
        //更新参数
        GLES30.glUniform3fv(aFilterLocation, 1, filterValue, 0);
    }

}

黑白滤镜的着色器实现:

顶点着色器:

#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec2 aTextureCoord;

uniform mat4 u_Matrix;
uniform vec3 a_Filter;

out vec3 vFilter;
out vec2 vTexCoord;
void main() {
     gl_Position  = u_Matrix * vPosition;
     vTexCoord = aTextureCoord;
     
     vFilter = a_Filter;
}

片段着色器:

#version 300 es
precision mediump float;
uniform sampler2D uTextureUnit;

//传入滤镜数据
in vec3 vFilter;
in vec2 vTexCoord;

//输出
out vec4 vFragColor;
void main() {
     vec4 vTextureColor = texture(uTextureUnit,vTexCoord);
     //计算此颜色的灰度值
     float vFilterColor = (vFilter.x * vTextureColor.r + vFilter.y * vTextureColor.g + vFilter.z * vTextureColor.b);
     vFragColor = vec4(vFilterColor, vFilterColor, vFilterColor, 1.0);
}

灰度值与RGB的计算公式:

Y = 0.299R + 0.587G + 0.114*B

滤镜切换

在对应的菜单选择中,我们动态切换即可:

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int itemId = item.getItemId();
        if (itemId == R.id.filter_default) {
            mGlView.setFilter(new OriginFilter());
        } else if (itemId == R.id.filter_gray) {
            mGlView.setFilter(new GrayFilter());
        }
        return super.onOptionsItemSelected(item);
    }

小结

每次切换滤镜的时候,都会销毁当前的滤镜,删除相关的程序对象,然后重新构建新的滤镜实例,加载相关的着色器,链接到程序对象中,完成滤镜的切换。

项目地址:sample-filter

https://github.com/byhook/opengles4android

参考:

https://blog.csdn.net/xiaxl/article/details/72622236
https://github.com/wuhaoyu1990/MagicCamera

你可能感兴趣的:(Android开发,OpenGL,ES,OpenGL,ES,3.0实践)