OpenGL 入门到放弃3-- 用openGL展示相机预览 项目优化

上一章我们初步实现了 用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地址

你可能感兴趣的:(OpenGL 入门到放弃3-- 用openGL展示相机预览 项目优化)