美文网首页图形编程
OpenGL ES---绘制二维图形

OpenGL ES---绘制二维图形

作者: 风雪围城 | 来源:发表于2018-03-04 10:37 被阅读263次

    背景

     绘制 3D 图,总觉得是一件很炫酷的事。虽然在项目中一直没有用到过,但是还是想找个时间,实践一下。
     绘制二维图形,尽管使用 OpenGL 有它的优势,但是还是感觉有点杀鸡用牛刀的意 思。这里主要是借助对 二维图形 的绘制过程,解释相关概念。

    什么是 OpenGL ES

     首先,来说明一下 OpenGL ES for Embedded Systems(OpenGL ES)。它是 OpenGL 的子集,用以渲染 2D 、3D 矢量图的跨语言、跨平台的API,这个 API 通常会和 GPU 交互,完成硬件加速渲染。
     Android 平台支持不同版本 OpenGL ES 的 API。其中:

    • OpenGL ES 1.0 and 1.1 - 该 API 被Android 1.0 及更高版本支持.
    • OpenGL ES 2.0 - 该 API 被 Android 2.2 (API level 8) 及更高版本支持.
    • OpenGL ES 3.0 - 该 API 被 Android 4.3 (API level 18) 及更高版本支持.
    • OpenGL ES 3.1 - 该 API 被 Android 5.0 (API level 21) 及更高版本支持.

     为了获取更广泛的设备支持,通常会基于 OpenGL ES 2.0 做开发。本文也是基于该版本展开。

    Android 平台提供的基础

     Android 框架层提供了两个对象以使用 OpenGL ES API 操作图像:GLSurfaceView 和 GLSurfaceView.Renderer。

    GLSurfaceView

     GLSurfaceView 继承自 SurfaceView,拥有专用的 surface 以展示 OpenGL 渲染。它提供了一下特性:

    • 管理 surface,同时使得 OpenGL 可以在 surface 上渲染;
    • 可以使用用户自定义的 Renderer 对象进行实际的渲染工作;
    • 渲染线程独立于 UI 线程之外 ;
    • 支持在需要时才进行的被动渲染 和 不间断自动进行的主动渲染两种渲染方式;
    //设置一下模式,为被动刷新
    glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    
    • 调试渲染调用

    简单的使用方式如下所示:

    //直接创建一个 GLSurfaceView,当然,也可以通过布局文件创建
    glSurfaceView = new GLSurfaceView(this) ;
    //使用 OpenGL ES 2.0 context.
    glSurfaceView.setEGLContextClientVersion(2);
    //设置我们自定义的 renderer
    glSurfaceView.setRenderer(new MyRenderer());
    setContentView(glSurfaceView);
    
    GLSurfaceView.Renderer

     负责进行帧渲染。提供的回调函数有:

    • onDrawFrame:负责对当前帧的绘制;
    • onSurfaceChanged:当 surface 的大小发生变化时候的回调,比如刚创建 surface 的时候,绘制屏幕发生旋转;
    • onSurfaceCreated:当 surface 创建或者重建的时候被调用。调用发生在渲染线程开始的时候,或者当 EGL context 丢失的时候(该 context 通常会在设备从睡眠中唤醒的时候丢失)。
      注意,当 EGL context 丢失,和 context 关联的所有 OpenGL 自愿将会被自动删除。

    管线渲染过程

     要明白这个过程,首先要知道什么是管线。所谓管线,就是在显卡上执行的将数据源转换投射到屏幕像素点上的过程。也就是将我们通过顶点定义的形状,显示到屏幕上。

    管线渲染-1

    如上图所示,

    1. 定义顶点;
    2. 通过 vertexShader着色器 告知 GPU 顶点的位置等属性;
    3. 通过图元装配,生成要绘制的形状;
    4. 光栅化处理,将所有的点转化为片元(fragment);
    5. 通过 fragmentShader着色器 为片元上色;
    6. 将片元投射到屏幕上的像素上。

    更加形象一点的过程如下所示:


    管线渲染-2

     注意,其中 vertexShader 和 fragmentShader 是通过 GLSL 语言定义的,并直接运行在 GPU 上。

    VertexShader

    顶点着色器,主要用于确定顶点位置,由 GLSL 语言定义,对于每个顶点都会执行该程序。通常用法如下:

    //通过 GLSL 定义 VertexShader
    private final String vertexShaderCode =
                "attribute vec4 vPosition;" +
                         "uniform mat4 u_Matrix;"+
                        "void main() {" +
                       // "gl_Position = vPosition;" +
                         "gl_Position = u_Matrix * vPosition;" +
                        "gl_PointSize = 10.0;"+
                        "}";
    
     @Override
     public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        ...
    //取出位置索引
            aPositionLocation = GLES20.glGetAttribLocation(program,"vPosition") ;
     //将位置索引和我们定义的数据源 vertexes 进行绑定,将 vertexes 中的每个顶点拿出来赋值到 vPosition ,并分别执行上面定义的 GLSL 程序
           GLES20.glVertexAttribPointer(aPositionLocation,2,GLES20.GL_FLOAT,false,0,vertexes);
            GLES20.glEnableVertexAttribArray(aPositionLocation);
    }
    
    FragmentShader

    片元着色器,目的就是告诉 GPU 每个片段的最终颜色应该是什么。对于基于图元的每个片段,片段着色器都会被调用一次。因此,如果一个三角形被映射到 10000 个片段,片段着色器就会被调用 10000次。

     private final String fragmentShaderCode =
                "precision mediump float;" +
                        "uniform vec4 vColor;" +
                        "void main() {" +
                        "  gl_FragColor = vColor;" +
                        "}";
       @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    //取出颜色索引
          aColorLocation = GLES20.glGetUniformLocation(program,"vColor") ;
    }
    
    @Override
    public void onDrawFrame(GL10 gl) {
    //给颜色赋值
            GLES20.glUniform4f(aColorLocation,  0f,1,1, 1.0f);
    //绘制
            GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
    }
    

    完整代码如下:

    public class MyRenderer implements GLSurfaceView.Renderer {
    
        private FloatBuffer vertexes ;
    
        private final String vertexShaderCode =
                "attribute vec4 vPosition;" +
                         "uniform mat4 u_Matrix;"+
                        "void main() {" +
                         "gl_Position = u_Matrix * vPosition;" +
                        "gl_PointSize = 10.0;"+
                        "}";
    
        private final String fragmentShaderCode =
                "precision mediump float;" +
                        "uniform vec4 vColor;" +
                        "void main() {" +
                        "  gl_FragColor = vColor;" +
                        "}";
    
        private int aPositionLocation ;
        private int aColorLocation ;
        private int uMatrixLocation;
        private final float[] projectionMatrix = new float[16];
    
        private void createVertexes(){
            float [] vertexesArray = new float[]{
                    0,1,
                    -1,-1,
                    1,-1
            } ;
            vertexes.clear();
            vertexes.put(vertexesArray) ;
        }
    
        private void init(){
    //创建本地内存,以便将我们定义的顶点放进去,供设备访问
    //堆内存上的数据,GPU是无法直接访问的
            vertexes =  ByteBuffer.allocateDirect(6*4)
                    .order(ByteOrder.nativeOrder())
                    .asFloatBuffer() ;
            //创建 顶点 坐标
            createVertexes();
        }
    
        public MyRenderer(){
            init();
        }
    
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            GLES20.glClearColor(1f,1f,0f,0f);
    
            int vertexShader = ShaderHelper.compileVertexShader(vertexShaderCode) ;
            int fragmentShader = ShaderHelper.compileFragmentShader(fragmentShaderCode) ;
            int program =  ShaderHelper.linkProgram(vertexShader, fragmentShader);
    
            if (LoggerConfig.ON) {
                ShaderHelper.validateProgram(program);
            }
    
            GLES20.glUseProgram(program);
            aPositionLocation = GLES20.glGetAttribLocation(program,"vPosition") ;
            aColorLocation = GLES20.glGetUniformLocation(program,"vColor") ;
            uMatrixLocation = GLES20.glGetUniformLocation(program, "u_Matrix");
            vertexes.position(0) ;
            GLES20.glVertexAttribPointer(aPositionLocation,2,GLES20.GL_FLOAT,false,0,vertexes);
            GLES20.glEnableVertexAttribArray(aPositionLocation);
        }
    
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            GLES20.glViewport(0, 0, width, height);
            // 根据屏幕方向设置投影矩阵
            float ratio= width > height ? (float)width / height : (float)height / width;
            if (width > height) {
                // 横屏
                Matrix.orthoM(projectionMatrix, 0, -ratio, ratio, -1, 1, 0, 5);
            } else {
                Matrix.orthoM(projectionMatrix, 0, -1, 1, -ratio, ratio, 0, 5);
            }
        }
    
        @Override
        public void onDrawFrame(GL10 gl) {
            // Clear the rendering surface.
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
            // Assign the matrix
            GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);
            GLES20.glUniform4f(aColorLocation,  0f,1,1, 1.0f);
            GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
        }
    }
    

    下面对以上代码块做几点说明:

    • 在使用 OpenGL ES 的时候,我们一般会做以下处理:1. 编译顶点着色器;2. 编译出片元着色器;3. 创建程序;4.通过程序链接所有着色器;5. 使用程序,并通过程序,取出位置、颜色等索引,以便后期绘制。
    • 上面代码块中,回调方法 onDrawFrame 的调用时机需要注意。默认情况下,会按照屏幕刷新的周期来调用该方法,即每秒执行 60 次。当然,我们可以通过在代码中设置以改变这种默认行为。
    glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    
    • 注意本地内存和虚拟机中堆内存的使用。后者是无法直接被设备访问的,后者可以,同时后者不受 GC 的影响。
    • GLES20 中的所有方法,实际上都是本地方法,即通过 JNI 调用实现的,底层是由 C 语言实现。

    小结

     本节主要借用二维图形的绘制,讲解了 OpenGL ES 在 Android 应用中使用的相关概念。下面想讲述一下坐标变换。

    参考链接:
    https://developer.android.com/training/graphics/opengl/environment.html
    http://www.cs.ucr.edu/~shinar/courses/cs130-spring-2012/schedule.html
    https://www.zhihu.com/question/29163054
    https://en.wikibooks.org/wiki/GLSL_Programming/OpenGL_ES_2.0_Pipeline

    相关文章

      网友评论

        本文标题:OpenGL ES---绘制二维图形

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