美文网首页
OpenGL 入门到放弃3-- 用openGL展示相机预览 项

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

作者: 李星星星星星 | 来源:发表于2019-10-25 15:32 被阅读0次

    上一章我们初步实现了 用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展示相机预览 项

          本文链接:https://www.haomeiwen.com/subject/erbsvctx.html