之前了解的一些第三方库,可以对视频预览页面进行各种滤镜切换,比较神奇,于是自己研究了一番,基础原理倒也不是很难,我们先从最简单的图片开始处理,下面记录一下过程。
操作系统:ubuntu 16.05
笔者这里封装了一个渲染控件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);
}
每次切换滤镜的时候,都会销毁当前的滤镜,删除相关的程序对象
,然后重新构建新的滤镜实例
,加载相关的着色器,链接到程序对象
中,完成滤镜的切换。
https://github.com/byhook/opengles4android
https://blog.csdn.net/xiaxl/article/details/72622236
https://github.com/wuhaoyu1990/MagicCamera