Android OpenGL 开发详解 (渲染Camera数据到GLSurfaceView)

只愿长相守 提交于 2020-01-13 04:03:24

OpenGL 作为跨平台的开放式图形库,在我们android平台自然也是有很大用处的。

这篇文章是我自己学习OpenGL的一个记录总结,同时写下我的理解,希望可以对你有帮助。

我们就使用OpenGL+GLSurfaceView+Camera 来实现使用Camera采集数据,通过OpenGL渲染到GLSurfaceView显示。

首先我们先在xml中写一个GLSurfaceView控件,获取到它的实例。那为什么我们要使用GLSurfaceView而不是用SurfaceView,因为我们渲染的过程需要和OpenGL建立联系才可以进行绘制的,而GLSurfaceView本身提供给我们使用就已经通过特殊的EGL环境与OpenGL建立了联系,而SurfaceView的话我们如果要使用需要自己配置EGL环境。

对GLSurfaceView进行一些配置:

GLSurfaceView glSurfaceview = findViewById(R.id.glsurfaceview);
glSurfaceview.setEGLContextClientVersion(2); //设置所使用的EGLContext版本号。
glSurfaceview.setRenderer(new GLSurfaceView.Renderer() { //设置渲染器 
            @Override
            public void onSurfaceCreated(GL10 gl, EGLConfig config) {

            }
            @Override
            public void onSurfaceChanged(GL10 gl, int width, int height) {

            }
            @Override
            public void onDrawFrame(GL10 gl) {

            }
        });
glSurfaceview.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);//设置渲染方式按需渲染

渲染器回调:

onSurfaceCreated  在创建GLSurfaceView的时候会回调一次,我们可以做一些初始化操作。                      

onSurfaceChanged  在GLSurfaceView 大小发生改变的时候回调,包括屏幕方向等等,我们可以做一些更改设置的一些操作。

onDrawFrame   进行渲染的回调,这个里面我们进行绘制

设置渲染方式: 分为两种(一种按需渲染,一种连续渲染),这里我们使用按需渲染。

 

接下来我们要我们要从camera中获取采集到的图像数据:

我们在设置camera预览画面的时候进行离屏渲染,给他一个SurfaceTexture,传入一个纹理id,这样camera采集到的数据就绑定到那个纹理id上面,纹理泛指物体面上的线条或者花纹,这里就是采集到构成这个画面的元素。

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    mTexture = new int[1];
    GLES20.glGenTextures(1, mTexture, 0); // 创建一个纹理id
    //将纹理id传入成功创建SurfaceTexture
    mSurfaceTexture = new SurfaceTexture(mTexture[0]);
    mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
                    @Override
                    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                        glSurfaceview.requestRender();
                    }
                });
     //这个是我的camera工具类
    mCameraHelper = new CameraHelper(Camera.CameraInfo.CAMERA_FACING_FRONT);
    mCameraHelper.startPreview(mSurfaceTexture); //这个是设置相机的预览画面

    //创建着色器程序 并且获取着色器程序中的部分属性
    mProgramId = creatProgram(vertex, frag);
    vPosition = GLES20.glGetAttribLocation(mProgramId, "vPosition");
    vCoord = GLES20.glGetAttribLocation(mProgramId, "vCoord");
    vMatrix = GLES20.glGetUniformLocation(mProgramId, "vMatrix");
    vTexture = GLES20.glGetUniformLocation(mProgramId, "vTexture");
}

  可以看到我们设置了SurfaceTexture的新数据监听,我们在回调中调用了GLSurfaceView的 requestRender方法。这个是请求渲染器渲染帧,因为我们设置的渲染方式是按需渲染,这里我们在有了新数据的时候再去主动要求渲染,就会回调onDrawFrame 这个监听。

创建一个着色器程序,并且获取着色器程序中的部分属性,下面是创建着色器程序的代码。

//顶点着色器代码
private String vertex = "attribute vec4 vPosition;\n" +
            "    attribute vec4 vCoord;\n" +
            "    uniform mat4 vMatrix;\n" +
            "    varying vec2 aCoord;\n" +
            "    void main(){\n" +
            "        gl_Position = vPosition; \n" +
            "        aCoord = (vMatrix * vCoord).xy;\n" +
            "    }";
//片元着色器代码
private String frag = "#extension GL_OES_EGL_image_external:require\n" +
            "    precision mediump float;\n" +
            "    varying vec2 aCoord;\n" +
            "    uniform samplerExternalOES vTexture;\n" +
            "    void main() {\n" +
            "        gl_FragColor = texture2D(vTexture,aCoord);\n" +
            "    }";
//创建着色器程序 返回着色器id
private int creatProgram(String vsi, String fsi) {

        int vShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);//创建一个顶点着色器
        GLES20.glShaderSource(vShader, vsi); //加载顶点着色器代码
        GLES20.glCompileShader(vShader); //编译

        int[] status = new int[1];
        GLES20.glGetShaderiv(vShader, GLES20.GL_COMPILE_STATUS, status, 0);//获取状态
        if (status[0] != GLES20.GL_TRUE) { //判断是否创建成功
            throw new IllegalStateException("顶点着色器创建失败!");
        }

        int fShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);//创建一个顶点着色器
        GLES20.glShaderSource(fShader, fsi);//加载顶点着色器代码
        GLES20.glCompileShader(fShader);
        GLES20.glGetShaderiv(fShader, GLES20.GL_COMPILE_STATUS, status, 0);
        if (status[0] != GLES20.GL_TRUE) {
            throw new IllegalStateException("片元着色器创建失败");
        }
        
        //创建着色器程序
        int mProgram = GLES20.glCreateProgram(); 
        GLES20.glAttachShader(mProgram, vShader);//将着色器塞入程序中
        GLES20.glAttachShader(mProgram, fShader);
        GLES20.glLinkProgram(mProgram);//链接
        //获取状态,判断是否成功
        GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, status, 0);
        if (status[0] != GLES20.GL_TRUE) {
            throw new IllegalStateException("link program:" + GLES20.glGetProgramInfoLog(mProgram));
        }

        GLES20.glDeleteShader(vShader);
        GLES20.glDeleteShader(fShader);

        return mProgram;
    }

将两段String字符传入,这两段式glsl代码,是OpenGL用于编写采样渲染的程序语言。

第一段字符是顶点着色器获取到外部传入的两个坐标,一个是OpenGL世界坐标,用于确定要绘制的形状,另一个是要采样器用于采样的采样坐标,还有一个矩阵,需要和采样坐标相乘才能获取到surfacetexture正确的采样坐标。

第二段字符是片元着色器,接收到顶点着色器确定的采样坐标,然后使用采样器采样到纹理的像素点颜色值,许许多多像素点的颜色值就构成了图像。

获取到画布宽高。

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
                mWidth = width;
                mHeight = height;
}

做好准备工作,开始我们的渲染:

首先清理屏幕,将屏幕清理成指定的颜色,然后使用SurfaceTexture更新纹理数据,获取SurfaceTexture采样所需要的矩阵,确定要绘制屏幕的大小。接着使用着色器程序 将我们的OpenGL世界坐标,所要采样的目标坐标,和矩阵传入,激活图层,绑定纹理id,上面我们在创建SurfaceTexture时传入一个纹理id,这时我们需要将这个纹理id传入程序中,这样它在进行采样的时候就是在这个纹理id上面采样,然后开始渲染。


@Override
public void onDrawFrame(GL10 gl) {
                //清理屏幕:可以清理成指定的颜色
                GLES20.glClearColor(0, 0, 0, 0);
                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

                mSurfaceTexture.updateTexImage();//更新纹理成为最新的数据
                mSurfaceTexture.getTransformMatrix(mtx);

                GLES20.glViewport(0, 0, mWidth, mHeight);//确定渲染的起始坐标,和宽高

                GLES20.glUseProgram(mProgramId);//使用着色器程序 
        
                mGLVertexBuffer.position(0);
                GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, mGLVertexBuffer);//设置顶点数据
                GLES20.glEnableVertexAttribArray(vPosition);
                
                mGLTextureBuffer.position(0);
                GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, mGLTextureBuffer); //采样目标顶点坐标
                GLES20.glEnableVertexAttribArray(vCoord);

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

                GLES20.glActiveTexture(GLES20.GL_TEXTURE0); //激活图层
                GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTexture[0]);//绑定采样器采样的目标纹理
                GLES20.glUniform1i(vTexture, 0);
                GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); //开始渲染

                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); //解绑纹理
            }

这个是我们传入着色器中的两个顶点数据,可以在onCreate中初始化。

mGLVertexBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
        mGLVertexBuffer.clear();
        float[] VERTEX = { //OpenGL世界坐标
                -1.0f, -1.0f,
                1.0f, -1.0f,
                -1.0f, 1.0f,
                1.0f, 1.0f,
        };
        mGLVertexBuffer.put(VERTEX);
        mGLTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
        mGLTextureBuffer.clear();
        float[] TEXTURE = {   //目标采样的顶点坐标
                1.0f, 0.0f,
                1.0f, 1.0f,
                0.0f, 0.0f,
                0.0f, 1.0f,
        };
        mGLTextureBuffer.put(TEXTURE);

最后别忘了在AndroidManifest中加一个OpenGL版本号和相机权限,当然6.0以上需要动态申请。

<uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />
    <uses-permission android:name="android.permission.CAMERA" />

以上就是OpenGL在Android平台上渲染的整个流程,我们使用OpenGL可以做很多事情,比如加贴纸,加滤镜,美颜等特效,但是想要做这些的前提就是要对OpenGL渲染的整个流程熟悉,熟悉之后我们才可以随心所欲的添加特效,在上面的整个流程中需要主要理解着色器程序的原理,里面的代码做了一些什么操作,这些我会在后面将我的一些理解整理一些分享出来,今天就先到这里。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!