美文网首页SurfaceView
Android高级 GLSurfaceView用法解析

Android高级 GLSurfaceView用法解析

作者: LiuJP | 来源:发表于2019-07-25 18:38 被阅读0次

    OpenGL和OpenGLES简介

    这里先简单介绍下OpenGL和OpenGLES。OpenGL(Open Graphics Library)意为开放图形库,是一个跨平台的图形API,用于指定3D图形处理硬件中的软硬件编程接口。OpenGL一般用于图形工作站,PC端使用。由于性能和可移植性等各方面原因,在移动端使用起来相对比较麻烦。为此,Khronos公司就为OpenGL提供一个子集,OpenGL ES(OpenGL for Embedded System)。OpenGL ES是免费的跨平台且功能完善的2D/3D图形库接口API,是OpenGL的一个子集。

    在Android端使用OpenGL ES的过程中,GLSurfaceView就扮演者一个重要的角色。本篇博文将结合几个简单的案例分别介绍GLSurfaceView的基本使用以及如何使用GLSurfaceView.Renderer所提供的接口将图形绘制到SurfaceView中

    GLSurfaceView的特点

    GLSurfaceView继承SurfaceView,并实现了SurfaceHolder.Callback2接口。既然继承于SurfaceView,就拥有SurfaceView的特性。如能在View的基础上创建独立的Surface,拥有SurfaceHolder来管理Surface,并且可以在子线程中进行View的渲染操作。通过SurfaceHolder得到Canvas,然后在单独的线程中利用Canvas绘制内容,然后更新到Surface。
    GLSurfaceView在SurfaceView的基础上实现GLThread(EGLContext创建GL环境所在线程),可通过OpenGL进行绘制,可在SurfaceView所提供的Surface进行绘制
    GLSurfaceView的使用步骤

    1.在使用OpenGL ES前,需要在AndroidManifest.xml中设置OpenGL ES的版本,这里我们使用的是OpenGL ES2.0,因此可进行如下声明。
       <uses-feature android:glEsVersion="0x00020000" android:required="true" /> 
    
    2.在Activity的onCreate方法中对GLSurfaceView进行初始化,设置相关配置信息
            glSurfaceView = (GLSurfaceView) findViewById(R.id.glSurfaceView);
            //GLContext设置OpenGLES2.0
            glSurfaceView.setEGLContextClientVersion(2);
            // 在setRenderer之前,可以调用以下方法进行EGL设置
        //        glSurfaceView.setEGLConfigChooser(true);//颜色,深度,模板等等设置
        //        glSurfaceView.setEGLWindowSurfaceFactory(new GLSurfaceView.EGLWindowSurfaceFactory() {           //窗口设置
        //            @Override
        //            public EGLSurface createWindowSurface(EGL10 egl10, EGLDisplay eglDisplay, EGLConfig     eglConfig, Object o) {
        //                return null;
        //            }
        //
        //            @Override
        //            public void destroySurface(EGL10 egl10, EGLDisplay eglDisplay, EGLSurface eglSurface) {
        //
        //            }
        //        });
            glSurfaceView.setRenderer(new TriangleRender());
            /*渲染方式,RENDERMODE_WHEN_DIRTY表示被动渲染,只有在调用requestRender或者onResume等方法时才会进行渲染。RENDERMODE_CONTINUOUSLY表示持续渲染*/
            glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    

    在Activity或者Fragment的onResume方法和onPause方法中,分别调用GLSurfaceView的onResume和onPause方法。

    @Override
    protected void onResume() {
        super.onResume();
        glSurfaceView.onResume();
    }
    @Override
    protected void onPause() {
        super.onPause();
        glSurfaceView.onPause();
    }
    

    编写渲染器定义,实体类实现GLSurfaceView.Renderer接口,
    并实现onSurfaceCreated ,onDrawFrame,onSurfaceChanged方法,GLSurfaceView的渲染工作主要由GLSurfaceView.Renderer负责渲染。

    public class BackgroundRender extends BaseRenderer implements GLSurfaceView.Renderer {
    
    private String TAG = BackgroundRender.class.getSimpleName();
    
    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        //surface被创建后需要做的处理
        //Set the background frame color
        GLES20.glClearColor(0.0f,0.0f,0.0f,1.0f);
    }
    
    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        // 渲染窗口大小发生改变或者屏幕方法发生变化时候回调
        GLES20.glViewport(0,0,width,height);
    }
    
    @Override
    public void onDrawFrame(GL10 gl10) {
        //执行渲染工作
        //Redraw background color
        GLES20.glClearColor(GLES20.GL_COLOR_BUFFER_BIT,0f,0f,0f);
    }
    }
    

    效果如下:给GLSurfaceView渲染一层黑色背景

    用OpenGL在GLSurfaceView上绘制几何图形

    绘制几何图形,需要将顶点坐标转为ByteBuffer,这样OpenGL才能进行图形处理。绘制三角形和正方形都需要加载顶点着色器和片元着色器的相关配置,关于着色器后面的博文会有相应介绍。这里我们定义BaseRenderer,在里面定义loadShader
    方法用于创建,加载和编译对应的着色器配置信息。绘制其他形状的渲染器都继承BaseRenderer。

    三角形图形绘制

        public class TriangleRender extends BaseRenderer implements GLSurfaceView.Renderer {
    
        private int mProgram;
        private FloatBuffer vertexBuffer;
    
        private final String vertexShaderCode =
                "attribute vec4 vPosition;" +
                        "void main() {" +
                        "  gl_Position = vPosition;" +
                        "}";
    
        private final String fragmentShaderCode =
                "precision mediump float;" +
                        "uniform vec4 vColor;" +
                        "void main() {" +
                        "  gl_FragColor = vColor;" +
                        "}";
    
        static float triangleCoords[] = {
                0.5f,  0.5f, 0.0f, // top
                -0.5f, -0.5f, 0.0f, // bottom left
                0.5f, -0.5f, 0.0f  // bottom right
        };
    
        //设置颜色,依次为红绿蓝和透明通道
        float color[] = { 1.0f, 0f, 0f, 1.0f };
        static final int COORDS_PER_VERTEX = 3;
    
        private int mPositionHandle;
        private int mColorHandle;
    
        //顶点个数
        private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
        //顶点之间的偏移量
        private final int vertexStride = COORDS_PER_VERTEX * 4; // 每个顶点四个字节
    
    
        @Override
        public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
            //将背景设置为灰色
            GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);
            //申请底层空间
            ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length * 4);
            bb.order(ByteOrder.nativeOrder());
            //将坐标数据转换为FloatBuffer,用以传入OpenGL ES程序
            vertexBuffer = bb.asFloatBuffer();
            vertexBuffer.put(triangleCoords);
            vertexBuffer.position(0);
    
            int vertexShader =  loadShader(GLES20.GL_VERTEX_SHADER,vertexShaderCode);
            int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,fragmentShaderCode);
    
            //创建一个空的OpenGLES程序
            mProgram = GLES20.glCreateProgram();
            //将顶点着色器加入到程序
            GLES20.glAttachShader(mProgram,vertexShader);
            //将片元着色器加入到程序中
            GLES20.glAttachShader(mProgram,fragmentShader);
            //连接到着色器程序
            GLES20.glLinkProgram(mProgram);
        }
    
        @Override
        public void onSurfaceChanged(GL10 gl10, int width, int height) {
            GLES20.glViewport(0,0,width,height);
        }
    
        @Override
        public void onDrawFrame(GL10 gl10) {
            //将程序加入到OpenGLES2.0环境
            GLES20.glUseProgram(mProgram);
    
            //获取顶点着色器的vPosition成员句柄
            mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
            //启用三角形顶点的句柄
            GLES20.glEnableVertexAttribArray(mPositionHandle);
            //准备三角形的坐标数据
            GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                    GLES20.GL_FLOAT, false,
                    vertexStride, vertexBuffer);
            //获取片元着色器的vColor成员的句柄
            mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
            //设置绘制三角形的颜色
            GLES20.glUniform4fv(mColorHandle, 1, color, 0);
            //绘制三角形
            GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
            //禁止顶点数组的句柄
            GLES20.glDisableVertexAttribArray(mPositionHandle);
        }
    }
    
    

    这是定义TriangleRender继承BaseRenderer并实现GLSurfaceView.Renderer接口在GLSurfaceView上进行绘制三角形操作,只需将GLSurfaceView的Renderer设置为TriangleRender即可

    glSurfaceView.setRenderer(new TriangleRender());

    运行效果如下

    绘制正方形

        public class Square extends BaseRenderer implements GLSurfaceView.Renderer {
        private FloatBuffer vertexBuffer;
        private ShortBuffer indexBuffer;
        private final String vertexShaderCode =
                "attribute vec4 vPosition;" +
                        "uniform mat4 vMatrix;"+
                        "void main() {" +
                        "  gl_Position = vMatrix*vPosition;" +
                        "}";
    
        private final String fragmentShaderCode =
                "precision mediump float;" +
                        "uniform vec4 vColor;" +
                        "void main() {" +
                        "  gl_FragColor = vColor;" +
                        "}";
    
        private int mProgram;
    
        static final int COORDS_PER_VERTEX = 3;
        static float triangleCoords[] = {
                -0.5f,  0.5f, 0.0f, // top left
                -0.5f, -0.5f, 0.0f, // bottom left
                0.5f, -0.5f, 0.0f, // bottom right
                0.5f,  0.5f, 0.0f  // top right
        };
    
        static short index[]={
                0,1,2,0,2,3
        };
    
        private int mPositionHandle;
        private int mColorHandle;
    
        private float[] mViewMatrix=new float[16];
        private float[] mProjectMatrix=new float[16];
        private float[] mMVPMatrix=new float[16];
    
        //顶点个数
        private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
        //顶点之间的偏移量
        private final int vertexStride = COORDS_PER_VERTEX * 4; // 每个顶点四个字节
    
        private int mMatrixHandler;
    
        //设置颜色,依次为红绿蓝和透明通道
        float color[] = { 1.0f, 0f, 0f, 1.0f };
    
    
        @Override
        public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
            ByteBuffer bb = ByteBuffer.allocateDirect(
                    triangleCoords.length * 4);
            bb.order(ByteOrder.nativeOrder());
            vertexBuffer = bb.asFloatBuffer();
            vertexBuffer.put(triangleCoords);
            vertexBuffer.position(0);
    
            ByteBuffer cc= ByteBuffer.allocateDirect(index.length*2);
            cc.order(ByteOrder.nativeOrder());
            indexBuffer=cc.asShortBuffer();
            indexBuffer.put(index);
            indexBuffer.position(0);
    
            int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
                    vertexShaderCode);
            int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
                    fragmentShaderCode);
    
            //创建一个空的OpenGLES程序
            mProgram = GLES20.glCreateProgram();
            //将顶点着色器加入到程序
            GLES20.glAttachShader(mProgram, vertexShader);
            //将片元着色器加入到程序中
            GLES20.glAttachShader(mProgram, fragmentShader);
            //连接到着色器程序
            GLES20.glLinkProgram(mProgram);
        }
    
        @Override
        public void onSurfaceChanged(GL10 gl10, int width, int height) {
            //计算宽高比
            float ratio=(float)width/height;
            //设置透视投影
            Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
            //设置相机位置
            Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7.0f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
            //计算变换矩阵
            Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
        }
    
        @Override
        public void onDrawFrame(GL10 gl10) {
            //将程序加入到OpenGLES2.0环境
            GLES20.glUseProgram(mProgram);
            //获取变换矩阵vMatrix成员句柄
            mMatrixHandler= GLES20.glGetUniformLocation(mProgram,"vMatrix");
            //指定vMatrix的值
            GLES20.glUniformMatrix4fv(mMatrixHandler,1,false,mMVPMatrix,0);
            //获取顶点着色器的vPosition成员句柄
            mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
            //启用三角形顶点的句柄
            GLES20.glEnableVertexAttribArray(mPositionHandle);
            //准备三角形的坐标数据
            GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                    GLES20.GL_FLOAT, false,
                    vertexStride, vertexBuffer);
            //获取片元着色器的vColor成员的句柄
            mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
            //设置绘制三角形的颜色
            GLES20.glUniform4fv(mColorHandle, 1, color, 0);
            //绘制三角形
        //        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount);
            //索引法绘制正方形
            GLES20.glDrawElements(GLES20.GL_TRIANGLES,index.length, GLES20.GL_UNSIGNED_SHORT,indexBuffer);
            //禁止顶点数组的句柄
            GLES20.glDisableVertexAttribArray(mPositionHandle);
        }
      }
    

    将Square作为渲染器设置到GLSurfaceView中

    glSurfaceView.setRenderer(new Square());

    运行效果如下

    三角形图形绘制
    一个颜色是不是太单调了些呢?接下来我们绘制一个彩色三角形。基于上面的代码,我们只需做如下调整
    1.修改着色器代码
    2.将颜色值修改为float数组并转为floatBuffer
    3.将获取的floatBuffer传递给顶点着色器
    修改着色器代码如下:

    private final String vertexShaderCode =
            "attribute vec4 vPosition;" +
                    "uniform mat4 vMatrix;"+
                    "varying  vec4 vColor;"+
                    "attribute vec4 aColor;"+
                    "void main() {" +
                    "  gl_Position = vMatrix*vPosition;" +
                    "  vColor=aColor;"+
                    "}";
    
    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "varying vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";
    

    接下来我们进行数据转换:

    //设置颜色
    float color[] = {
            0.0f, 1.0f, 0.0f, 1.0f ,
            1.0f, 0.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 1.0f, 1.0f
    };
    ByteBuffer bb = ByteBuffer.allocateDirect(
                triangleCoords.length * 4);
    bb.order(ByteOrder.nativeOrder());
    
    vertexBuffer = bb.asFloatBuffer();
    vertexBuffer.put(triangleCoords);
    vertexBuffer.position(0);
    

    最后我们需要获取着色器的句柄并设置着色器的颜色

    @Override
    public void onDrawFrame(GL10 gl10) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT| GLES20.GL_DEPTH_BUFFER_BIT);
        //将程序加入到OpenGLES2.0环境
        GLES20.glUseProgram(mProgram);
        //获取变换矩阵vMatrix成员句柄
        mMatrixHandler= GLES20.glGetUniformLocation(mProgram,"vMatrix");
        //指定vMatrix的值
        GLES20.glUniformMatrix4fv(mMatrixHandler,1,false,mMVPMatrix,0);
        //获取顶点着色器的vPosition成员句柄
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        //启用三角形顶点的句柄
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        //准备三角形的坐标数据
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);
        //获取片元着色器的vColor成员的句柄
        mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
        //设置绘制三角形的颜色
        GLES20.glEnableVertexAttribArray(mColorHandle);
        GLES20.glVertexAttribPointer(mColorHandle,4,
                GLES20.GL_FLOAT,false,
                0,colorBuffer);
        //绘制三角形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
        //禁止顶点数组的句柄
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
    

    相关文章

      网友评论

        本文标题:Android高级 GLSurfaceView用法解析

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