美文网首页
OpenGLES2.0(四)OpenGLES做Camera预览

OpenGLES2.0(四)OpenGLES做Camera预览

作者: 张小潇 | 来源:发表于2020-03-16 00:10 被阅读0次

OpenGLES在Android上除了可以用来做游戏、处理图片也可以用来处理视频图像、做相机预览美颜等等。本篇博客将介绍利用OpenGLES做相机预览的基本实现。

一、流程

  1. 理解OpenGL坐标系

  2. camera相机预览和opengl关联

  3. 布局中使用 GLSurfacView 作为预览窗口。

  4. 准备相关的顶点属性数据和着色器文件。

  5. 实现 GLSurfaceView.Render 接口,编写具体的渲染代码。

二、具体实现

1、理解OpenGL坐标系

坐标系

2、camera相机预览和opengl关联

Android 相机的预览数据可以输出到 SurfaceTexture 上,所以用 opengl 做相机预览的主要思路是

  1. 在 GLSurfaceView.Render 中创建一个纹理,再使用该纹理创建一个 SurfaceTexture
  2. 使用该 SurfaceTexture 创建一个 Surface 传给相机,相机预览数据就输出到一个纹理上了
  3. 使用 GLSurfaceView.Render 将该纹理渲染到 GLSurfaceView 窗口上
  4. 使用 SurfaceTexture 的 setOnFrameAvailableListener 方法 给 SurfaceTexture 添加一个数据帧数据可用的监听器,在监听器中 调用 GLSurfaceView 的 requestRender 方法渲染该帧数据,这样相机每次输出一帧数据就可以渲染一次,在GLSurfaceView窗口中就可以看到相机的预览数据了
 @Override
    public void onSurfaceChanged(GL10 gl10, int i, int i1) {
        //开启预览、传人创建好的mSurfaceTexture
        mCameraHelper.startPreview(mSurfaceTexture);
    }

@Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        mFunCameraView.requestRender();
    }

3、布局中使用 GLSurfacView 作为预览窗口。

public class FunCameraView extends GLSurfaceView {

    public FunCameraView(Context context) {
        this(context,null);
    }

    public FunCameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //设置opengl版本
        setEGLContextClientVersion(2);
        //自定义渲染器
        setRenderer(new FunCameraRender(this));
        //按需渲染
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }
}

4、准备相关的顶点属性数据和着色器文件。

提醒:如果想编写着色器文件时候有高亮,可以安装“GLSL Support"插件,或者你可以直接写成String

顶点着色器

// 把顶点坐标给这个变量, 确定要画画的形状
attribute vec4 vPosition;
//接收纹理坐标,接收采样器采样图片的坐标
attribute vec4 vCoord;
//变换矩阵, 需要将原本的vCoord(01,11,00,10) 与矩阵相乘 才能够得到 surfacetexure(特殊)的正确的采样坐标
uniform mat4 vMatrix;
//传给片元着色器 像素点
varying vec2 aCoord;
void main(){
    //内置变量 gl_Position ,我们把顶点数据赋值给这个变量 opengl就知道它要画什么形状了
    gl_Position = vPosition;
    // 进过测试 和设备有关
    aCoord = (vMatrix * vCoord).xy;
    //aCoord =  vec2((vCoord*vMatrix).x,(vCoord*vMatrix).y);
}

片元着色器

#extension GL_OES_EGL_image_external : require
//SurfaceTexture比较特殊
//float数据是什么精度的
precision mediump float;

//采样点的坐标
varying vec2 aCoord;

//采样器
uniform samplerExternalOES vTexture;

void main(){
    //变量 接收像素值
    // texture2D:采样器 采集 aCoord的像素
    //赋值给 gl_FragColor 就可以了
    gl_FragColor = texture2D(vTexture,aCoord);
}

5、实现 GLSurfaceView.Render 接口,编写具体的渲染代码。

public class FunCameraRenderer implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
    private FunCameraView mView;
    private CameraHelper mCameraHelper;
    private SurfaceTexture mSurfaceTexture;
    private float[] mtx = new float[16];
    private int[] mTextures;
    private FloatBuffer mTextureBuffer;
    private FloatBuffer mVertexBuffer;
    private int vTexture;
    private int vMatrix;
    private int vCoord;
    private int vPosition;
    private int mProgram;
    private int mWidth;
    private int mHeight;

    public FunCameraRenderer(FunCameraView funCameraView) {
        mView = funCameraView;
    }

    /**
     * 画布创建好啦
     */
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //初始化的操作
        mCameraHelper = new CameraHelper(Camera.CameraInfo.CAMERA_FACING_BACK);
        //准备好摄像头绘制的画布
        //通过opengl创建一个纹理id
        mTextures = new int[1];
        GLES20.glGenTextures(mTextures.length, mTextures, 0);
        mSurfaceTexture = new SurfaceTexture(mTextures[0]);
        //
        mSurfaceTexture.setOnFrameAvailableListener(this);
       //camera_vertex 中的内容读出字符串
        String vertexSource = OpenUtils.readRawTextFile(context, R.raw.camera_vertex);
        String fragSource = OpenUtils.readRawTextFile(context, R.raw.camera_frag);

        //通过字符串(代码)创建着色器程序
        //使用opengl
        //1、创建顶点着色器
        // 1.1获取顶点着色器
        int vShaderId = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
        // 1.2 绑定代码到着色器中去
        GLES20.glShaderSource(vShaderId, vertexSource);
        // 1.3 编译着色器代码
        GLES20.glCompileShader(vShaderId);
        //主动获取成功、失败 (如果不主动查询,只输出 一条 GLERROR之类的日志,很难定位到到底是那里出错)
        int[] status = new int[1];
        GLES20.glGetShaderiv(vShaderId, GLES20.GL_COMPILE_STATUS, status, 0);
        if (status[0] != GLES20.GL_TRUE) {
            throw new IllegalStateException("ScreenFilter 顶点着色器配置失败!");
        }
        //2、创建片元着色器
        int fShaderId = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
        GLES20.glShaderSource(fShaderId, fragSource);
        GLES20.glCompileShader(fShaderId);
        GLES20.glGetShaderiv(fShaderId, GLES20.GL_COMPILE_STATUS, status, 0);
        if (status[0] != GLES20.GL_TRUE) {
            throw new IllegalStateException("ScreenFilter 片元着色器配置失败!");
        }
        //3、创建着色器程序 (GPU上的小程序)
        mProgram = GLES20.glCreateProgram();
        //把着色器塞到程序当中
        GLES20.glAttachShader(mProgram, vShaderId);
        GLES20.glAttachShader(mProgram, fShaderId);

        //链接着色器
        GLES20.glLinkProgram(mProgram);

        //获得程序是否配置成功
        GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, status, 0);
        if (status[0] != GLES20.GL_TRUE) {
            throw new IllegalStateException("ScreenFilter 着色器程序配置失败!");
        }

        //因为已经塞到着色器程序中了,所以删了没关系
        GLES20.glDeleteShader(vShaderId);
        GLES20.glDeleteShader(fShaderId);

        //获得着色器程序中的变量的索引, 通过这个索引来给着色器中的变量赋值
        /**
         * 顶点
         */
        vPosition = GLES20.glGetAttribLocation(mProgram, "vPosition");
        vCoord = GLES20.glGetAttribLocation(mProgram, "vCoord");
        vMatrix = GLES20.glGetUniformLocation(mProgram, "vMatrix");
        //片元
        vTexture = GLES20.glGetUniformLocation(mProgram, "vTexture");


        //创建一个数据缓冲区
        //4个点 每个点两个数据(x,y) 数据类型float
        //顶点坐标
        mVertexBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        mVertexBuffer.clear();
        float[] v = {-1.0f, -1.0f,
                1.0f, -1.0f,
                -1.0f, 1.0f,
                1.0f, 1.0f};
        mVertexBuffer.put(v);

        mTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        mTextureBuffer.clear();
        //镜像
        float[] t = {1.0f, 0.0f,
                1.0f, 1.0f,
                0.0f, 0.0f,
                0.0f, 1.0f
                };
        mTextureBuffer.put(t);
    }

    /**
     * 画布发生了改变
     *
     * @param gl
     * @param width
     * @param height
     */
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //开启预览
        mCameraHelper.startPreview(mSurfaceTexture);
        mWidth = width;
        mHeight = height;
    }

    /**
     * 开始画画吧
     *
     * @param gl
     */
    @Override
    public void onDrawFrame(GL10 gl) {
        // 配置屏幕
        //清理屏幕 :告诉opengl 需要把屏幕清理成什么颜色
        GLES20.glClearColor(0, 0, 0, 0);
        //执行上一个:glClearColor配置的屏幕颜色
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        // 把摄像头的数据先输出来
        // 更新纹理,然后我们才能够使用opengl从SurfaceTexure当中获得数据 进行渲染
        mSurfaceTexture.updateTexImage();
        //surfaceTexture 比较特殊,在opengl当中 使用的是特殊的采样器 samplerExternalOES (不是sampler2D)
        //获得变换矩阵
        mSurfaceTexture.getTransformMatrix(mtx);
                 //1、设置窗口大小
        //画画的时候 你的画布可以看成 10x10,也可以看成5x5 等等
        //设置画布的大小,然后画画的时候, 画布越大,你画上去的图像就会显得越小
        // x与y 就是从画布的哪个位置开始画
        GLES20.glViewport(0, 0, mWidth, mHeight);

        //使用着色器程序
        GLES20.glUseProgram(mProgram);

        // 怎么画? 其实就是传值
        //2:xy两个数据 float的类型
        //1、将顶点数据传入,确定形状
        mVertexBuffer.position(0);
        GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, mVertexBuffer);
        //传了数据之后 激活
        GLES20.glEnableVertexAttribArray(vPosition);

        //2、将纹理坐标传入,采样坐标
        mTextureBuffer.position(0);
        GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, mTextureBuffer);
        GLES20.glEnableVertexAttribArray(vCoord);

        //3、变换矩阵
        GLES20.glUniformMatrix4fv(vMatrix,1,false,mtx,0);

        //片元 vTexture 绑定图像数据到采样器
        //激活图层
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        // 图像数据
        // 正常:GLES20.GL_TEXTURE_2D
        // surfaceTexure的纹理需要
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,texture);
        //传递参数 0:需要和纹理层GL_TEXTURE0对应
        GLES20.glUniform1i(vTexture,0);

        //参数传完了 通知opengl 画画 从第0点开始 共4个点
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);
    }

    /**
     * surfaceTexture 有一个有效的新数据的时候回调
     *
     * @param surfaceTexture
     */
    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        mView.requestRender();
    }
}

三、总结

本文梳理了使用 opengl 将相机预览数据渲染到 GLSurfaceView 的基本流程,后续以此为基础,结合 opengl 的离屏渲染机制实现实时滤镜功能,现在渲染器代码太长,后面会对代码进行抽取。

相关文章

网友评论

      本文标题:OpenGLES2.0(四)OpenGLES做Camera预览

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