上一章我们初步实现了 用GLSurfaceView展示相机预览功能,为了方便大家理解,主要代码都写在了一个类里,这一章我们来优化一下代码和项目结构,为以后的拓展实现各种效果打下基础。
Render 和 View类基本不需要改变,主要来提取一下Filter类中的方法。
我们先来回忆一下 ScreenFilter:
import android.content.Context;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import com.xopengl.xopenglcamera.R;
import com.xopengl.xopenglcamera.camera.util.OpenGLUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* @author Lixingxing
*/
public class ScreenFilter {
protected int mProgram;
protected final int vPosition,vCoord,vMatrix,vTexture;
protected int mWidth, mHeight;
protected FloatBuffer vPostionBuffer;
protected float[] POSITION = new float[]{-1f,-1f,
1f,-1f,
-1f,1f,
1f,1f};
protected FloatBuffer vCoordBuffer;
protected float[] TEXTURE = new float[]{1f,0f,
1f,1f,
0f,0f,
0f,1f};
public ScreenFilter(Context context){
//1.生成顶点着色器并编译顶点着色器代码
String vertexSource = OpenGLUtils.readRawFileContent(context, R.raw.screen_vert);
// 1.1 生成顶点着色器id
int vShaderVextId = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
// 1.2 绑定代码到着色器中
GLES20.glShaderSource(vShaderVextId, vertexSource);
// 1.3 编译着色器代码
GLES20.glCompileShader(vShaderVextId);
// 1.4 主动获取成功 失败状态
int[] status = new int[1];
GLES20.glGetShaderiv(vShaderVextId, GLES20.GL_COMPILE_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
// 如果没有成功,抛出异常 如果不做处理,log会输出一条GLERROR的日志
throw new IllegalStateException(" 顶点着色器配置失败");
}
// 2.创建片元着色器并编译顶点着色器代码
// 2.0 获取片元着色器代码
String fragSource = OpenGLUtils.readRawFileContent(context, R.raw.screen_frag);
// 2.1 生成片元着色器id
int vShaderFragId = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
// 2.2 绑定代码到着色器中
GLES20.glShaderSource(vShaderFragId, fragSource);
// 2.3 编译着色器代码
GLES20.glCompileShader(vShaderFragId);
// 2.4 主动获取成功 失败状态
GLES20.glGetShaderiv(vShaderFragId, GLES20.GL_COMPILE_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
// 如果没有成功,抛出异常 如果不做处理,log会输出一条GLERROR的日志
throw new IllegalStateException(" 片元着色器配置失败");
}
// 3.创建着色器程序并链接着色器
mProgram = GLES20.glCreateProgram();
// 把着色器塞入 程序当中
GLES20.glAttachShader(mProgram, vShaderVextId);
GLES20.glAttachShader(mProgram, vShaderFragId);
// 链接着色器
GLES20.glLinkProgram(mProgram);
// 主动获取成功 失败状态
GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
// 如果没有成功,抛出异常 如果不做处理,log会输出一条GLERROR的日志
throw new IllegalStateException(" 创建着色器程序失败");
}
// 4. 释放资源
GLES20.glDeleteShader(vShaderVextId);
GLES20.glDeleteShader(vShaderFragId);
//5. 获得着色器中的参数变量的索引,后面通过这个索引给这个变量赋值,索引都是int类型的
vPosition = GLES20.glGetAttribLocation(mProgram,"vPosition");
vCoord = GLES20.glGetAttribLocation(mProgram,"vCoord");
vMatrix = GLES20.glGetUniformLocation(mProgram,"vMatrix");
vTexture = GLES20.glGetUniformLocation(mProgram,"vTexture");
//6. 创建顶点坐标和纹理坐标
// 顶点坐标
vPostionBuffer = ByteBuffer.allocateDirect(4*2*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vPostionBuffer.clear();
vPostionBuffer.put(POSITION);
// 纹理坐标
vCoordBuffer = ByteBuffer.allocateDirect(4*2*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vCoordBuffer.clear();
vCoordBuffer.put(TEXTURE);
}
public void onReady(int width,int height){
this.mWidth = width;
this.mHeight = height;
}
public void onDrawFrame(int mTexture, float[] mtx) {
// 1.设置窗口大小
GLES20.glViewport(0,0, mWidth,mHeight);
// 2.使用着色器程序
GLES20.glUseProgram(mProgram);
// 3.给着色器程序中传值
// 3.1 给顶点坐标数据传值
vPostionBuffer.position(0);
GLES20.glVertexAttribPointer(vPosition,2,GLES20.GL_FLOAT,false,0,vPostionBuffer);
// 激活
GLES20.glEnableVertexAttribArray(vPosition);
// 3.2 给纹理坐标数据传值
vCoordBuffer.position(0);
GLES20.glVertexAttribPointer(vCoord,2,GLES20.GL_FLOAT,false,0,vCoordBuffer);
GLES20.glEnableVertexAttribArray(vCoord);
// 3.3 变化矩阵传值
GLES20.glUniformMatrix4fv(vMatrix,1,false,mtx,0);
// 3.4 给片元着色器中的 采样器绑定
// 激活图层
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// 图像数据
GLES20.glBindTexture(GLES11Ext.GL_SAMPLER_EXTERNAL_OES,mTexture);
// 传递参数
GLES20.glUniform1i(vTexture,0);
//参数传递完毕,通知 opengl开始画画
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);
// 解绑
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);
}
}
1. 新建一个抽象类, BaseFilter
import android.content.Context;
/**
* @author Lixingxing
*/
public abstract class BaseFilter {
protected float[] mtx = new float[16];
// 初始化
public BaseFilter(Context context,int vertRawId,int fragRawId){
}
// 设置窗口大小
public void onReady(int width,int height){
}
public void setMatrix(float[] mtx){
this.mtx = mtx;
}
// 绘制方法(绘制方法做了一下改变,不再直接传入矩阵数据,而是先设置矩阵数据,绘制的时候直接使用设置的矩阵数据。并且会返回纹理的id,为什么这样做我们接下来的内容里会说到)
public int onDrawFrame(int textureId){
}
}
2. 将 ScreenFilter中的参数都提取到 BaseFilter中,设置成 protected,这样每个子类都可以使用。
protected int mProgram;
protected int vPosition,vCoord,vMatrix,vTexture;
protected int mWidth, mHeight;
protected FloatBuffer vPostionBuffer;
protected float[] POSITION = new float[]{-1f,-1f,
1f,-1f,
-1f,1f,
1f,1f};
protected FloatBuffer vCoordBuffer;
protected float[] TEXTURE = new float[]{1f,0f,
1f,1f,
0f,0f,
0f,1f};
3.将创建着色器程序的代码 提取到 OpenGLUtils中,并在BaseFilter初始化方法中调用
将顶点着色器代码文件id 和 片元着色器代码文件id 动态传入,这样我们就可以调用这个方法生成不同的着色器程序。
public static int createProgram(Context context, int vertRawId,int vertFragId){
//1.生成顶点着色器并编译顶点着色器代码
String vertexSource = OpenGLUtils.readRawFileContent(context,vertRawId);
// 1.1 生成顶点着色器id
int vShaderVextId = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
// 1.2 绑定代码到着色器中
GLES20.glShaderSource(vShaderVextId, vertexSource);
// 1.3 编译着色器代码
GLES20.glCompileShader(vShaderVextId);
// 1.4 主动获取成功 失败状态
int[] status = new int[1];
GLES20.glGetShaderiv(vShaderVextId, GLES20.GL_COMPILE_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
// 如果没有成功,抛出异常 如果不做处理,log会输出一条GLERROR的日志
throw new IllegalStateException(" 顶点着色器配置失败");
}
// 2.创建片元着色器并编译顶点着色器代码
// 2.0 获取片元着色器代码
String fragSource = OpenGLUtils.readRawFileContent(context, vertFragId);
// 2.1 生成片元着色器id
int vShaderFragId = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
// 2.2 绑定代码到着色器中
GLES20.glShaderSource(vShaderFragId, fragSource);
// 2.3 编译着色器代码
GLES20.glCompileShader(vShaderFragId);
// 2.4 主动获取成功 失败状态
GLES20.glGetShaderiv(vShaderFragId, GLES20.GL_COMPILE_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
// 如果没有成功,抛出异常 如果不做处理,log会输出一条GLERROR的日志
throw new IllegalStateException(" 片元着色器配置失败");
}
// 3.创建着色器程序并链接着色器
int mProgram = GLES20.glCreateProgram();
// 把着色器塞入 程序当中
GLES20.glAttachShader(mProgram, vShaderVextId);
GLES20.glAttachShader(mProgram, vShaderFragId);
// 链接着色器
GLES20.glLinkProgram(mProgram);
// 主动获取成功 失败状态
GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
// 如果没有成功,抛出异常 如果不做处理,log会输出一条GLERROR的日志
throw new IllegalStateException(" 创建着色器程序失败");
}
// 4. 释放资源
GLES20.glDeleteShader(vShaderVextId);
GLES20.glDeleteShader(vShaderFragId);
return mProgram;
}
4. 将 BaseFilter的初始化方法设置成需要传入 上下文+顶点着色器文件id+片元着色器id,并分割成两个方法,每个方法里给出默认的实现,子类可以根据自己的需求再进行重写。
在BaseFilter中创建着色器程序,着色器代码文件id由具体实现的子类传入
protected int mProgram;
protected int vPosition,vCoord,vMatrix,vTexture;
protected int mWidth, mHeight;
protected FloatBuffer vPostionBuffer;
protected float[] POSITION = new float[]{-1f,-1f,
1f,-1f,
-1f,1f,
1f,1f};
protected FloatBuffer vCoordBuffer;
// 已经转过了坐标,所以这里不需要再变了。
protected float[] TEXTURE = new float[]{1f,0f,
1f,1f,
0f,0f,
0f,1f};
...
public BaseFilter(Context context,int vertRawId,int fragRawId){
initilize(context,vertRawId,fragRawId);
initCoord();
}
protected void initilize(Context context, int mVershaderId, int mFragShaderId){
mProgram = OpenGLUtils.createProgram(context,mVershaderId,mFragShaderId);
// 获得着色器中的变量的索引,通过这个索引给这个变量赋值
vPosition = GLES20.glGetAttribLocation(mProgram,"vPosition");
vCoord = GLES20.glGetAttribLocation(mProgram,"vCoord");
vMatrix = GLES20.glGetUniformLocation(mProgram,"vMatrix");
vTexture = GLES20.glGetUniformLocation(mProgram,"vTexture");
}
protected void initCoord(){
// 顶点坐标
vPostionBuffer = ByteBuffer.allocateDirect(4*2*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vPostionBuffer.clear();
vPostionBuffer.put(POSITION);
// 纹理坐标
vCoordBuffer = ByteBuffer.allocateDirect(4*2*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vCoordBuffer.clear();
vCoordBuffer.put(TEXTURE);
}
...
5. 将绘制方法提取到 BaseFilter中
// 绘制方法
public int onDrawFrame(int textureId){
// 1.设置窗口大小
GLES20.glViewport(0,0, mWidth,mHeight);
// 2.使用着色器程序
GLES20.glUseProgram(mProgram);
// 3.给着色器程序中传值
// 3.1 给顶点坐标数据传值
vPostionBuffer.position(0);
GLES20.glVertexAttribPointer(vPosition,2,GLES20.GL_FLOAT,false,0,vPostionBuffer);
// 激活
GLES20.glEnableVertexAttribArray(vPosition);
// 3.2 给纹理坐标数据传值
vCoordBuffer.position(0);
GLES20.glVertexAttribPointer(vCoord,2,GLES20.GL_FLOAT,false,0,vCoordBuffer);
GLES20.glEnableVertexAttribArray(vCoord);
// 3.3 变化矩阵传值
GLES20.glUniformMatrix4fv(vMatrix,1,false,mtx,0);
// 3.4 给片元着色器中的 采样器绑定
// 激活图层
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// 图像数据
GLES20.glBindTexture(GLES11Ext.GL_SAMPLER_EXTERNAL_OES,textureId);
// 传递参数
GLES20.glUniform1i(vTexture,0);
//参数传递完毕,通知 opengl开始画画
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);
// 解绑
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);
return textureId;
}
自此,整个BaseFilter就已经优化完成,贴上全篇代码:
import android.content.Context;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import com.xopengl.xopenglcamera.camera.util.OpenGLUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* @author Lixingxing
*/
public abstract class BaseFilter {
protected int mProgram;
protected int vPosition,vCoord,vMatrix,vTexture;
protected int mWidth, mHeight;
protected FloatBuffer vPostionBuffer;
protected float[] POSITION = new float[]{-1f,-1f,
1f,-1f,
-1f,1f,
1f,1f};
protected FloatBuffer vCoordBuffer;
protected float[] TEXTURE = new float[]{1f,0f,
1f,1f,
0f,0f,
0f,1f};
protected float[] mtx = new float[16];
// 初始化
public BaseFilter(Context context,int vertRawId,int fragRawId){
initilize(context,vertRawId,fragRawId);
initCoord();
}
protected void initilize(Context context, int mVershaderId, int mFragShaderId){
mProgram = OpenGLUtils.createProgram(context,mVershaderId,mFragShaderId);
// 获得着色器中的变量的索引,通过这个索引给这个变量赋值
vPosition = GLES20.glGetAttribLocation(mProgram,"vPosition");
vCoord = GLES20.glGetAttribLocation(mProgram,"vCoord");
vMatrix = GLES20.glGetUniformLocation(mProgram,"vMatrix");
vTexture = GLES20.glGetUniformLocation(mProgram,"vTexture");
}
protected void initCoord(){
// 顶点坐标
vPostionBuffer = ByteBuffer.allocateDirect(4*2*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vPostionBuffer.clear();
vPostionBuffer.put(POSITION);
// 纹理坐标
vCoordBuffer = ByteBuffer.allocateDirect(4*2*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vCoordBuffer.clear();
vCoordBuffer.put(TEXTURE);
}
// 设置窗口大小
public void onReady(int width,int height){
this.mWidth = width;
this.mHeight = height;
}
public void setMatrix(float[] mtx){
this.mtx = mtx;
}
// 绘制方法
public int onDrawFrame(int textureId){
// 1.设置窗口大小
GLES20.glViewport(0,0, mWidth,mHeight);
// 2.使用着色器程序
GLES20.glUseProgram(mProgram);
// 3.给着色器程序中传值
// 3.1 给顶点坐标数据传值
vPostionBuffer.position(0);
GLES20.glVertexAttribPointer(vPosition,2,GLES20.GL_FLOAT,false,0,vPostionBuffer);
// 激活
GLES20.glEnableVertexAttribArray(vPosition);
// 3.2 给纹理坐标数据传值
vCoordBuffer.position(0);
GLES20.glVertexAttribPointer(vCoord,2,GLES20.GL_FLOAT,false,0,vCoordBuffer);
GLES20.glEnableVertexAttribArray(vCoord);
// 3.3 变化矩阵传值
GLES20.glUniformMatrix4fv(vMatrix,1,false,mtx,0);
// 3.4 给片元着色器中的 采样器绑定
// 激活图层
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// 图像数据
GLES20.glBindTexture(GLES11Ext.GL_SAMPLER_EXTERNAL_OES,textureId);
// 传递参数
GLES20.glUniform1i(vTexture,0);
//参数传递完毕,通知 opengl开始画画
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);
// 解绑
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);
return textureId;
}
}
6. 修改一下之前的 ScreenFilter 和 Render中的调用
import android.content.Context;
import com.xopengl.xopenglcamera.R;
/**
* @author Lixingxing
*/
public class ScreenFilter extends BaseFilter{
public ScreenFilter(Context context) {
super(context, R.raw.screen_vert,R.raw.screen_frag);
}
}
是不是很简洁。
再修改一下render中的调用方式:
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import com.xopengl.xopenglcamera.camera.filter.BaseFilter;
import com.xopengl.xopenglcamera.camera.filter.ScreenFilter;
import com.xopengl.xopenglcamera.camera.util.CameraHelper;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* @author Lixingxing
*/
public class MyCameraRenderer implements GLSurfaceView.Renderer {
private GLSurfaceView glSurfaceView;
private CameraHelper cameraHelper;
private SurfaceTexture surfaceTexture;
private int[] mTextures;
// 这里用 BaseFilter
private BaseFilter baseFilter;
private float[] mtx = new float[16];
public MyCameraRenderer(GLSurfaceView glSurfaceView){
this.glSurfaceView = glSurfaceView;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 打开前置摄像头
cameraHelper = new CameraHelper(Camera.CameraInfo.CAMERA_FACING_FRONT);
mTextures = new int[1];
GLES20.glGenTextures(mTextures.length,mTextures,0);
surfaceTexture = new SurfaceTexture(mTextures[0]);
surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
glSurfaceView.requestRender();
}
});
// 生成相机数据处理类
baseFilter = new ScreenFilter(glSurfaceView.getContext());
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 开启预览
cameraHelper.WIDTH = width;
cameraHelper.HEIGHT = height;
cameraHelper.startPreview(surfaceTexture);
baseFilter.onReady(width,height);
}
@Override
public void onDrawFrame(GL10 gl) {
// 1. 清理屏幕 设置屏幕颜色为 glClearColor中设置的颜色
GLES20.glClearColor(0,0,0,0);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 2. 把摄像头数据从 SurfaceTexture 中取出来
// 2.1 更新纹理,然后才能使用openGl从SurfaceTexture中获取数据
surfaceTexture.updateTexImage();
// 2.2 取得变换矩阵
// SurfaceTexture 在opengl中使用的是特殊的采样器“samplerExternalOES”,必须要通过变换矩阵才能获得 Simple2D的采样器
// mtx 代表一个4*4的矩阵数据,所以要用 float[] mtx = new float[16]来声明
surfaceTexture.getTransformMatrix(mtx);
// 3.把数据绘制到屏幕上显示
baseFilter.setMatrix(mtx);
baseFilter.onDrawFrame(mTextures[0]);
}
public void surfaceDestroyed(){
cameraHelper.stopPreview();
}
}
这样优化了以后,如果我们需要用不同的filter实现不同的效果(比如镜像翻转等),在BaseFilter具体的子类中做一些坐标或者绘制工作的改动,,然后在这里 new 不同的filter出来就行了。
比如 实现 画面翻转的效果:
只需要实现一个 FlipFilter。
import com.xopengl.xopenglcamera.R;
/**
* 翻转
* @author Lixingxing
*/
public class FlipFilter extends BaseFilter{
public FlipFilter(Context context) {
super(context, R.raw.screen_vert, R.raw.screen_frag);
}
@Override
protected void initCoord() {
TEXTURE = new float[]{ 0f,0f,
0f,1f,
1f,0f,
1f,1f,
};
super.initCoord();
}
}
然后在 MyCameraRenderer中把之前的 ScreenFilter 替换成 FlipFilter就行
// 生成相机数据处理类
// baseFilter = new ScreenFilter(glSurfaceView.getContext());
baseFilter = new FlipFilter(glSurfaceView.getContext());
联想到美颜相机里实现的各种效果,是不是突然就有了思路?
之后我们还会在优化后的项目基础上继续进行拓展。
github demo地址