OpenGL ES

概述

Android通过Open GraphicsLibrary(OpenGL)来支持高性能的2D和3D图像,特别是OpenGL ES API.OpenGL是一款跨平台的图像API,它为3D图像处理指定了标准的软件接口.OpenGL ES是OpenGL中应用于嵌入式设备的分支.Android支持多个版本的OpenGL ES API,包括:

  • OpenGL ES 1.0和1.1 – 支持Android1.0及更高.

  • OpenGL ES 2.0 – 支持Android2.2(API8)及更高.

  • OpenGL ES 3.0 – Android4.3(API 18)及更高.

  • OpenGL ES 3.1 – Android5.0(API 21)及更高.

Android通过自己的framework和NDK来支持OpenGL.这里主要介绍Androidframework接口,关于NDK的可以查看AndroidNDK.在Android framework中有两个基础类可以让我们使用OpenGL ESAPI创建和操作图形: GLSurfaceView和GLSurfaceView.Renderer.如果我们想要在Android APP中使用OpenGL,那么如何在activity中使用这两个类便成了我们第一个要了解的事情:

  • GLSurfaceView

    这个类是一个View,它和SurfaceView很像,让我们可以使用OpenGL的API来绘制和处理对象.使用该类的时候只需要创建一个GLSurfaceView的实例,然后将我们的Renderer传给它.但是如果我们想要截获点击事件的话,就得继承GLSurfaceView类,并实现它的点击监听器才行.

  • GLSurfaceView.Renderer

    该接口定义了在GLSurfaceView中绘制图形的方法.我们必须这个接口作为一个独立的类,并通过GLSurfaceView.setRenderer()方法关联给GLSurfaceView.GLSurfaceView.Renderer接口需要我们实现这些方法:

    • onSurfaceCreated()

      当GLSurfaceView创建的时候,系统会调用该方法一次.使用该方法来处理那些只需要执行一次的操作,比如设置OpenGL环境参数,或者初始化OpenGL图像对象.

    • onDrawFrame()

      系统将会在GLSurfaceView每次重绘的时候调用该方法.应该使用该方法来绘制或者重绘图像对象.

    • onSurfaceChanged()

      当GLSurfaceView的几何形状发生改变的时候,系统调用该方法.包括GLSurfaceView的尺寸变化或者设备屏幕的方向发生变化.比如从横屏变竖屏,该方法就会被调用.使用该方法来响应GLSurfaceView的容器的改变.

OpenGL ES 包

一旦我们已经确定在一个View中使用OpenGL ES,我们可以使用下列这些类来调用OpenGL 的API:

  • OpenGL ES 1.0/1.1 API包:

    • android.opengl

      这个包为OpenGL ES1.0/1.1类提供了静态接口,并且比下面的javax.microedition.khronos包更加稳定.包括GLES10,GLES10Ext,GLES11,GLES11Ext.
    • Javax.microediton.khronos.opengles

      这个包提供了OpenGL ES 1.0/1.1的标准的实现.包括GL10,GL10Ext,GL11,GL11Ext,GL11ExtensionPack.
  • OpenGL ES 2.0 API类:

    • android.opengl.GLES20

      这个包提供了OpenGL ES 2.0接口并且从Android2.2开始可用.
  • OpenGL ES 3.0/3.1 API包:

    • android.opengl

      这个包提供了OpenGL ES 3.0/3.1类的接口.3.0版本从Android4.3开始可用,3.1版本则从Android5.0才开始可用.包括GLES30,GLES31,GLES31Ext.

声明OpenGL需求

如果我们的APP需要使用的OpenGL功能并非支持所有的设备,那么就必须包含这些需求在AndroidManifest.xml文件中.这是一些常用的OpenGL manifest声明:

  • OpenGL ES version requirements– 如果我们的APP需要指定OpenGL ES的版本,就必须声明requirement.需要增加如下的设置到manifest中:
    对于OpenGL ES 2.0:
<!-- Tell the system this app requires OpenGL ES 2.0.-->
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

增加这个声明的目的是Google play需要限制我们的APP不能安装在那些不支持OpenGLES 2.0的设备上.如果APP只能支持OpenGL ES 3.0,那么我们则需要这样指定:
对于OpenGL ES 3.0:

<!-- Tell the system this app requires OpenGL ES 3.0.-->
<uses-feature android:glEsVersion="0x00030000" android:required="true" />

对于OpenGL ES 3.1:

<!-- Tell the system this app requires OpenGL ES 3.1.-->
<uses-feature android:glEsVersion="0x00030001" android:required="true" />

OpenGL ES 3.x的API对OpenGL ES2.0的API是兼容的,这意味着我们可以使用更加灵活的方法来使用OpenGL ES.我们可以在manifest中声明OpenGL ES 2.0,并且默认情况下使用该版本,但是可以在运行时查询设备是否支持3.x,如果支持就可以使用.

  • Texture compressionrequirements – 如果我们的APP需要使用texture compression,那么就必须在manifest中用声明需要支持的格式.对此后文会有更加详细的介绍.

映射绘制对象的坐标主要为了解决宽高比的问题

在Android上显示图形的时候一个很基本的问题就是,这些设备的屏幕经常是不同的大小和形状的.默认情况下OpenGL会假设一个正方形的屏幕,然后将图形绘制在一个通常不是正方形的屏幕上.

默认情况下OpenGL的坐标系(左)和映射在典型屏幕上之后的样子(右).可以看到图形的比例有所变化,为了解决这个问题,我们可以申请OpenGL的投影模式(projection mode)和相机view(camera view)来转变坐标,这样图形就可以显示为正确的比例了.

为了申请projection 和camera view,我们需要创建一个projection矩阵和一个camera view矩阵并且将它们应用到OpenGL渲染管道.Projection矩阵负责重新计算图形的坐标,使他们可以正确的映射到Android的设备屏幕上.Camera view矩阵从渲染对象的一个指定的视角创建一个变换的图形.

OpenGLES 10中的projection和camera view

在ES 1.0 API中,我们通过创建对应的矩阵来使用projection和camera view,然后将它们添加到OpenGL环境中.

  • Projection矩阵: 创建一个Projection以通过屏幕的几何形状来重新计算对象的坐标,以便它们可以被正确的绘制出来.下面的栗子演示了如何基于屏幕的宽高比修改onSurfaceChanged()方法来创建Projection矩阵,并将其应用于OpenGL的渲染环境.
public void onSurfaceChanged(GL10 gl,int width,int height) {
    gl.glViewport(0,0,width,height);

    // make adjustments for screen ratio
    float ratio = (float) width / height;
    gl.glMatrixMode(GL10.GL_PROJECTION);        // set matrix to projection mode
    gl.glLoadIdentity();                        // reset the matrix to its default state
    gl.glFrustumf(-ratio,ratio,-1,1,3,7);  // apply the projection matrix
}
  • Camera transformation 矩阵: 一旦用projection算出了坐标系,下一步就必须用到cameraview了.下面的栗子演示了如何修改GLSurfaceView.Renderer的onDrawFrame()方法来应用一个model view和使用GLU.gluLookAt()工具来创建一个view模拟一个视角.
public void onDrawFrame(GL10 gl) {
    ...
    // Set GL_MODELVIEW transformation mode
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadIdentity();                      // reset the matrix to its default state

    // When using GL_MODELVIEW,you must set the camera view
    GLU.gluLookAt(gl,0,0,-5,0f,0f,0f,0f,1.0f,0.0f);
    ...
}

OpenGLES 20及更高版本中的projection和camera view

在ES2.0和3.0的API中,我们使用projection和camera view的时候,首先要向图形对象的vertex shaders(顶点着色器)中添加一个矩阵成员.通过增加这个矩阵成员,我们就可以生成和应用projection和camera view了.

  • 向顶点着色器中添加一个矩阵– 为projection矩阵创建一个变量,并把它作为着色器位置的乘数.在下面的栗子中,uMVPMatrix成员变量让我们可以应用projection和camera view矩阵到使用这个着色器的对象的坐标上.
private final String vertexShaderCode =

    // This matrix member variable provides a hook to manipulate
    // the coordinates of objects that use this vertex shader.
    "uniform mat4 uMVPMatrix;   \n" +

    "attribute vec4 vPosition;  \n" +
    "void main(){               \n" +
    // The matrix must be included as part of gl_Position
    // Note that the uMVPMatrix factor *must be first* in order
    // for the matrix multiplication product to be correct.
    " gl_Position = uMVPMatrix * vPosition; \n" +

    "}  \n";

上面的栗子在顶点着色器中定义了一个变换矩阵成员.根据APP的需求,我们可以定义独立的projection矩阵和camera view矩阵,这样我们就可以独立的修改它们了.

  • 访问着色器矩阵– 在顶点着色器中创建一个钩子之后,我们就可以访问这个变量来应用projection和camera view矩阵.下面的代码演示了如何修改GLSurfaceView.Renderer中的onSurfaceCreated()方法来访问上面栗子中的顶点着色器的矩阵变量.
public void onSurfaceCreated(GL10 unused,EGLConfig config) {
    ...
    muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,"uMVPMatrix");
    ...
}
  • 创建projection和camera view矩阵– 生成图形对象要使用的projection和view矩阵.下面的栗子演示了如何基于屏幕宽高比并通过GLSurfaceView.Renderer中的onSurfaceChanged()和onSurfaceCreated()方法来创建camera view矩阵和一个projection矩阵.
public void onSurfaceCreated(GL10 unused,EGLConfig config) {
    ...
    // Create a camera view matrix
    Matrix.setLookAtM(mVMatrix,0,0,0,-3,0f,0f,0f,0f,1.0f,0.0f);
}

public void onSurfaceChanged(GL10 unused,int width,int height) {
    GLES20.glViewport(0,0,width,height);

    float ratio = (float) width / height;

    // create a projection matrix from device screen geometry
    Matrix.frustumM(mProjMatrix,0,-ratio,ratio,-1,1,3,7);
}
  • 应用projection和camera view矩阵– 想要应用projection和camera view变换,需要将矩阵相乘后保存在顶点着色器中.下面的栗子中演示了如何通过onDrawFrame()方法组合projection矩阵和camera view然后将它们应用于将要被OpenGL渲染的图形对象.
public void onDrawFrame(GL10 unused) {
    ...
    // Combine the projection and camera view matrices
    Matrix.multiplyMM(mMVPMatrix,0,mProjMatrix,0,mVMatrix,0);

    // Apply the combined projection and camera view transformations
    GLES20.glUniformMatrix4fv(muMVPMatrixHandle,1,false,mMVPMatrix,0);

    // Draw objects
    ...
}

OpenGL的版本选择

OpenGL ES 1.0和1.1从Android1.0就已经开始支持,从Android2.2开始,可以支持OpenGL ES2.0.从Android4.3开始支持OpenGL ES 3.x.它们都提供了稳定的用于创建3D游戏的图形接口.对于2.0和3.0版本的OpenGL,它们的图形编程过程很相似.3.0版本是2.0的超集.但是1.0/1.1和2.0/3.0有很大的差别.所以开发之前应该考虑以下的一些因素:

  • 性能

    通常,2.0和3.0比1.0/1.1提供了更好的图片处理性能.但是性能同样也跟设备有关系.

  • 设备兼容性

    开发者应该考虑到设备的类型,Android的版本和OpenGL ES的版本.

  • 编码便利性

    1.0/1.1提供了一个固定的功能管道和便利功能,这些东西在2.0/3.0中没有.所以新的OpenGL的开发者可能会发现在1.0/1.1上开发更加便利和简单.

  • 图形控制

    OpenGL ES 2.0/3.0通过使用可编程管道提供了一个更高等级的控制,通过这些,开发者们可以比在1.0/1.1中更容易的控制图形.

    Texture 支持: OpenGL ES 3.0拥有对Texture压缩最好的支持.

Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1