Android开发|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开发|android平台下OpenGL ES 3.0给图片添加黑白滤镜
文章图片
Android开发|android平台下OpenGL ES 3.0给图片添加黑白滤镜
文章图片
实现方案 【Android开发|android平台下OpenGL ES 3.0给图片添加黑白滤镜】笔者这里封装了一个渲染控件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 = https://www.it610.com/article/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

    推荐阅读