1. 概述

Android通过使用Open Graphics Library(OpenGL®)提供了对高性能2D和3D图形的支持。 OpenGL是一个跨平台图形API,用于指定3D图形处理硬件的标准软件界面。 OpenGL ES是面向嵌入式设备的OpenGL规范。 Android支持多种版本的OpenGL ES API 规范:

OpenGL ES 1.0和1.1 - 此API规范由Android 1.0及更高版本支持。OpenGL ES 2.0 - 此API规范由Android 2.2(API级别8)及更高版本支持。OpenGL ES 3.0 - 此API规范由Android 4.3(API级别18)及更高版本支持。OpenGL ES 3.1 - 此API规范由Android 5.0(API级别21)及更高版本支持。注意:在设备上支持OpenGL ES 3.0 API需要设备的制造商提供此图形管道的实现。 运行Android 4.3或更高版本的设备可能不支持OpenGL ES 3.0 API。 开发时如何配置OpenGL ES的版本可以参考下面的6.3节。

2. OpenGL ES API简介

Android通过其framework API和Native Development Kit(NDK)来支持OpenGL。 本文侧重于Android framework 层面。 关于NDK可以参考 Android NDK。

Android framework中提供了两个基础类:GLSurfaceView和GLSurfaceView.Renderer。为了在Android应用程序中使用OpenGL ES API,那么在一个Activity中使用这些类是很关键的。

1. GLSurfaceView : 用来显示OpenGL ES API绘制和操作的图像对象的View,并且在功能上与SurfaceView类似。 通过创建GLSurfaceView的实例并且将Renderer添加到该实例中来使用此类。如果想要捕获触touch event,则应该继承GLSurfaceView类并且实现onTouchEvent方法,具体参考https://developer.android.google.cn/training/graphics/opengl/touch.html。2. GLSurfaceView.Renderer :该接口定义了在GLSurfaceView中绘制图形对象所需的方法。GLSurfaceView.Renderer接口中的方法:1> onSurfaceCreated():创建GLSurfaceView时,系统会调用此方法一次。 使用此方法完成只需要执行一次的操作,例如设置OpenGL环境参数或初始化OpenGL图形对象。2> onDrawFrame():系统在每次绘制GLSurfaceView时调用此方法。 使用此方法作为绘制图形对象的主要执行点。3> onSurfaceChanged():当GLSurfaceView geometry更改时,系统会调用此方法,包括GLSurfaceView的大小或设备屏幕方向的更改。 使用此方法来响应GLSurfaceView容器中的变化。

一旦建立了GLSurfaceView和GLSurfaceView.Renderer的关联,就可以使用以下OpenGL ES API:

  1. OpenGL ES 1.0/1.1 API Packages :
    1> android.opengl :该包为OpenGL ES 1.0 / 1.1类提供了一个静态接口,并且具有比javax.microedition.khronos包接口更好的性能。
    GLES10
    GLES10Ext
    GLES11
    GLES11Ext
    2> javax.microedition.khronos.opengles : 该包提供了OpenGL ES 1.0 / 1.1的标准实现。
    GL10
    GL10Ext
    GL11
    GL11Ext
    GL11ExtensionPack

  2. OpenGL ES 2.0 API Packages :
    android.opengl.GLES20:该包为OpenGL ES 2.0的提供了接口并且从Android 2.2 (API level 8)开始可用。

  3. OpenGL ES 3.0/3.1 API Packages:
    android.opengl:该包为OpenGL ES 3.0 / 3.1提供了接口。 版本3.0可从Android 4.3 (API level 18)开始。 版本3.1可从Android 5.0 (API level 21)开始。
    GLES30
    GLES31
    GLES31Ext (Android Extension Pack)

3. 声明OpenGL

如果您的应用程序使用了OpenGL ES,则必须在清单文件中作出声明。 以下是最常见的OpenGL 清单声明:
1> OpenGL ES版本 - 如果应用程序需要特定版本的OpenGL ES,则必须通过将以下设置添加到清单中来声明该需求,如下所示。
For OpenGL ES 2.0:

添加此声明将导致Google Play将限制应用程序只能安装在支持OpenGL ES 2.0的设备上。 如果您的应用程序只能安装在支持OpenGL ES 3.0的设备,则可以在清单中作出如下声明:
For OpenGL ES 3.0:

For OpenGL ES 3.1:

注意:OpenGL ES 3.x API向后兼容2.0 API,因此当声明的版本是2.0时,在设备支持的情况下也可以使用3.0版本中的功能,具体参考6.3。

2> Texture compression 声明 - 如果应用程序使用了texture compression格式,则必须在清单文件中使用声明应用程序支持的texture compression格式。 有关Texture compression格式的更多信息,参考下面的6.1节。

如果设备不支持清单中texture compression声明的所用格式,则该应用无法安装到该设备中, 关于Google Play过滤texture compression的详细信息,请参阅文档的Google Play and texture compression filtering部分。

4. 坐标系映射

在Android设备上显示图形的一个基本问题是屏幕的尺寸和形状是不同的。 OpenGL假设屏幕坐标系时一个正方形并且分布均匀的坐标系,因此将图形绘制到非正方形屏幕上时图形会被压缩或者拉伸,如下图所示:


图1 默认OpenGL坐标系(左)映射到矩形的Android设备屏幕(右)。

要解决右侧图形变形的问题,就需要用到OpenGL 的projection 和 camera views来转换坐标系,以使图形对象在任何显示屏上都具有正确的比例。

通过创建projection matrix 和 camera view matrix并且将其应用于OpenGL渲染环境来应用projection 和 camera views。 projection matrix重新计算图形坐标,以使图形以正确的比例绘制到Android设备屏幕上。 camera view matrix创建一个从特定视角渲染图形对象的转换。

4.1 Projection and camera view in OpenGL ES 1.0

在ES 1.0 API中,通过创建projection matrix 和 camera view matrix并且将其应用于OpenGL渲染环境来应用projection 和 camera views。

1> Projection matrix - 使用设备屏幕的geometry创建projection matrix,然后利用projection matrix重新计算图形坐标,以使图形以正确的比例绘制到Android设备屏幕上。 下面onSurfaceChanged方法中演示了根据屏幕的宽高比创建projection matrix ,并将其应用于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}2> Camera transformation matrix - 一旦使用projection matrix调整坐标系,就必须使用camera view。 下面onDrawFrame()方法中演示了使用model view并且使用GLU.gluLookAt() utility创建模拟摄像头位置的视角转换。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);    ...}
4.2 Projection and camera view in OpenGL ES 2.0 and higher

在ES 2.0和3.0 API中,为了应用projection 和 camera view,首先将matrix成员添加到图形对象的vertex shaders中,然后生成和应用projection matrix 和 camera view matrix到图形对象。

1> 将matrix成员添加到vertex shaders中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";注意:上面的示例定义了应用单个matrix成员的vertex shader,该matrix成员可以是projection matrix 和camera view matrix的组合形式。2> 访问 shader matrix - 在vertex shaders中添加matrix成员后,就可以通过访问该matrix成员以应用projection 和 camera view matrices。下面onSurfaceCreated()方法中演示了访问上面vertex shaders中定义的matrix成员。public void onSurfaceCreated(GL10 unused, EGLConfig config) {    ...    muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");    ...}3> 创建 projection 和 camera view matrices - 生成要应用到图形对象的projection 和 camera view matrix。下面onSurfaceCreated()和onSurfaceChanged()方法中演示了创建camera view matrix 和 基于设备屏幕宽高比的projection matrix。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);}4> 应用 projection 和 camera view matrices - 为了应用projection和camera viewing matrices,先将projection和camera viewing matrices相乘然后将结果设置给vertex shader。 下面onDrawFrame()方法中演示了组合上述代码中创建的projection matrix 和camera view matrix,然后将结果应用于由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    ...}

5. Shape 的面和Winding

在OpenGL中,Shape的面是由三维空间中的三个或更多个点定义的表面。 一组三个或更多的三维点(在OpenGL中称为vertices)具有正面和背面。 你怎么知道哪个面是正面的,哪个是背面? 这和绘制Shape坐标点的顺序有关。



在该示例中, 绘制这些坐标的顺序定义了shape的winding方向。 默认情况下,在OpenGL中,逆时针方向绘制的面是正面。

为什么要知道shape的哪一面是正面? 这与OpenGL的常用功能有关,称为face culling。 face culling是OpenGL环境的一个选择,使渲染管道忽略(不计算或绘制)shape的背面,以节省时间、内存和处理周期:

// enable face culling featuregl.glEnable(GL10.GL_CULL_FACE);// specify which faces to not drawgl.glCullFace(GL10.GL_BACK);

在不知道shape的哪一面是正面的情况下使用face culling功能,shape可能看起来有点薄或者根本不显示,因此建议以逆时针方向定义shape的坐标。

注意:可以通过配置OpenGL环境来将顺时针绘制的面视为正面,但这样做需要更多的代码,因此没有必要这样做。

6. OpenGL版本和设备兼容性

OpenGL ES 1.0和1.1 API规范自Android 1.0以来一直得到支持。 从Android 2.2(API 8级)开始,开始支持OpenGL ES 2.0 API规范。 大多数Android设备都支持OpenGL ES 2.0 API规范,并且在开发具有OpenGL功能的应用时推荐使用OpenGL ES 2.0。 在提供OpenGL ES 3.0 API实现的设备上,Android 4.3(API级别18)及更高版本支持OpenGL ES 3.0。 有关支持给定版本OpenGL ES的Android设备相对数量的信息,请参阅OpenGL ES Version Dashboard。

使用OpenGL ES 1.0 / 1.1 API的图形编程与使用2.0及更高版本显着不同。 API的1.x版本具有更多的便利方法和固定的图形管道,而OpenGL ES 2.0和3.x API通过使用OpenGL shaders提供对管道更直接的控制。 您应该仔细考虑图形要求,并选择最适合应用程序的API版本。

OpenGL ES 3.x API提供了比2.0 API更多的功能和更好的性能,并且还向后兼容2.0版本。 这意味着可以在编写面向OpenGL ES 2.0的应用程序时,有条件地包含OpenGL ES 3.x的图形功能(如果可用)。 有关检查3.x API可用性的更多信息,请参阅下面的6.3节。

6.1 Texture compression support

Texture compression可以通过减少内存需求和更有效地利用内存带宽来显著提高OpenGL应用程序的性能。 Android framework提供了对的ETC1 texture compression格式的支持,作为标准功能,包括ETC1Util工具类和etc1tool compression tool(位于Android SDK中的 / platform-tools /目录中)。 有关使用texture compression的Android应用程序的示例,请参阅Android SDK( / samples / / ApiDemos / src / com / example / android / apis / graphics /)中的CompressedTextureActivity代码示例。

注意:大多数Android设备都支持ETC1格式,但不能保证可用。 要检查设备是否支持ETC1格式,请调用ETC1Util.isETC1Supported()方法。

注意:ETC1 texture compression格式不支持具有透明度(Alpha通道)的纹理。 如果您的应用程序需要具有透明度的纹理,则可以看看目标设备上其他的可用的texture compression格式是否支持。

当使用OpenGL ES 3.0 API时,ETC2 / EAC texture compression格式一定可用。 这种texture compression格式提供出色的压缩比,具有高视觉质量,同时也支持透明度(Alpha通道)。

除了ETC格式,Android设备还可以根据GPU chipsets和OpenGL的实现对texture compression有不同的支持。 首先看看目标设备上支持的texture compression格式,继而确定应用程序应支持哪些texture compression格式。 为了确定给定设备上支持什么texture compression格式,您必须查询设备的OpenGL扩展名称(如下面的确定OpenGL扩展),这些名称标识了设备支持的texture compression格式(以及其他OpenGL特性)。 一些通常支持的texture compression格式如下:

1> ATITC (ATC) - ATI texture compression(ATITC或ATC)可用于各种设备,并支持具有或者不具有Alpha通道的RGB纹理的固定速率压缩。 此格式可以由以下几个OpenGL扩展名称表示:GL_AMD_compressed_ATC_textureGL_ATI_texture_compression_atitc2> PVRTC - PowerVR texture compression(PVRTC)可用于各种设备,并支持具有或不具有Alpha通道的每像素2位或者4位的纹理。 此格式由以下OpenGL扩展名称表示:GL_IMG_texture_compression_pvrtc3> S3TC (DXTn/DXTC) - S3texture compression(S3TC)具有多种格式变体(DXT1至DXT5),并且是一种不太广泛的可用格式。 该格式支持具有4位alpha或者8位alpha通道的RGB纹理。 这些格式由以下OpenGL扩展名称表示:GL_EXT_texture_compression_s3tc一些设备只支持DXT1格式变体; 有此限制的OpenGL扩展名称为:GL_EXT_texture_compression_dxt14> 3DC - 3DC texture compression(3DC)是一种不太广泛的可用格式,支持具有Alpha通道的RGB纹理。 此格式由以下OpenGL扩展名称表示:GL_AMD_compressed_3DC_texture注意:对这些格式的支持可能因制造商和设备而异。 有关如何确定特定设备上的texture compression格式的信息,请参阅下一节。一旦您决定应用程序将支持哪种纹理压缩格式,请确保使用在清单中声明它们。 使用此声明可以通过外部服务(如Google Play)进行过滤,以便您的应用程序仅安装在支持应用程序所需格式的设备上。
6.2 确定OpenGL扩展

由于Android设备支持的OpenGL ES API扩展方面的不同,因此OpenGL的实现因Android设备而异。 这些扩展包括texture compressions,通常还包括其他的OpenGL扩展。

要确定特定设备支持什么texture compressions格式和其他OpenGL扩展:
1> 在目标设备上运行以下代码,以确定支持哪种texture compressions格式:

String extensions = javax.microedition.khronos.opengles.GL10.glGetString(        GL10.GL_EXTENSIONS);

注意:此调用的结果因设备型号而异! 您必须在多个目标设备上运行此调用,以确定通常支持哪些texture compressions格式。

2> 查看此方法的输出以确定设备上支持哪些OpenGL扩展。

Android Extension Pack (AEP)

AEP确保应用程序支持OpenGL 3.1规范中描述的核心集以外的标准化的OpenGL扩展。 将这些扩展打包在一起促进跨设备的一整套功能,同时允许开发人员充分利用最新的移动GPU设备。

AEP还改进了对fragment shaders中的images,shader存储缓冲区和atomic计数器的支持。

为了使您的应用程序能够使用AEP,必须在应用程序的清单声明AEP。 此外, Android版本必须支持它。在清单中声明AEP:

要验证Android平台版本是否支持AEP,请使用hasSystemFeature(String)方法,传入FEATURE_OPENGLES_EXTENSION_PACK作为参数。 以下代码片段显示了如何执行此操作:

boolean deviceSupportsAEP = getPackageManager().hasSystemFeature     (PackageManager.FEATURE_OPENGLES_EXTENSION_PACK);

如果该方法返回true,则支持AEP。
有关AEP的更多信息,请访问 Khronos OpenGL ES Registry页面。

6.3 配置OpenGL ES的版本

通常而言,现在的Android设备没有低于API 8版本的,因此在应用的清单文件中指定OpenGL ES最低版本是2.0,由于OpenGL ES 3.x向后兼容2.0版本,因此在支持3.x的设备上是可以使用3.x版本中的功能,为了使用3.x版本中的功能,首先应该检查当前设备是否支持3.x版本,如果当前设备支持3.x版本,则通过创建和应用3.x版本EGLContext的方式来使用3.x版本中的功能。

检查设备支持的OpenGL ES版本有如下两种方式:

1. 创建更高版本的OpenGL ES context(EGLContext)并检查结果的方式。2. 创建一个最低版本OpenGL ES context并检查版本的方式。

1> 通过第一种方式检查设备是否支持的OpenGL ES 3.x版本:

private static double glVersion = 3.x; //3.0或者3.1private static class ContextFactory implements GLSurfaceView.EGLContextFactory {  private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;  public EGLContext createContext(          EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {      Log.w(TAG, "creating OpenGL ES " + glVersion + " context");      int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, (int) glVersion,              EGL10.EGL_NONE };      // attempt to create a OpenGL ES 3.0 context      EGLContext context = egl.eglCreateContext(              display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);      return context; // returns null if 3.0 is not supported;  }}

2> 通过第二种方式来检查设备支持的OpenGL ES版本:

// Create a minimum supported OpenGL ES context, then check:String version = javax.microedition.khronos.opengles.GL10.glGetString(        GL10.GL_VERSION);Log.w(TAG, "Version: " + version );// The version format is displayed as: "OpenGL ES ."// followed by optional content provided by the implementation.

使用这种方法,如果您发现设备支持更高级的API版本,则必须销毁最低的OpenGL ES context,并使用更高的可用API版本创建新的context。

通过上面两种方式检查设备是否支持3.x版本的EGLContext的后,如果支持,就创建3.x版本的EGLContext并且通过GLSurfaceView的setEGLContextFactory方法应用3.x版本的EGLContext。

7. 举个例子

先看一下效果:


上面的效果很简单,三角形会随着手指的滑动而转动,下面来讲解一下具体实现:
1> 配置OpenGL ES版本

在清单文件中指定所需要的版本号为2.0如果设备支持3.0版本,则使用3.0中的功能:public class MyContextFactory implements GLSurfaceView.EGLContextFactory {    private static final String TAG = "MyContextFactory";    private static double glVersion = 3.0;    private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;    @Override    public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {        Log.w(TAG, "creating OpenGL ES " + glVersion + " context");        int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, (int) glVersion,                EGL10.EGL_NONE};        // attempt to create a OpenGL ES 3.0 context        EGLContext context = egl.eglCreateContext(                display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);        if (context == null || context == EGL10.EGL_NO_CONTEXT) {            attrib_list = new int[]{EGL_CONTEXT_CLIENT_VERSION, 2,                    EGL10.EGL_NONE};            context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);        }        return context;    }    @Override    public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {        egl.eglDestroyContext(display, context);    }}在createContext方法中,会尝试创建3.0版本的EGLContext,如果创建失败,即该设备不支持3.0版本,那么就会去创建2.0版本的EGLContext。上面的代码大部分是参考GLSurfaceView的内部类DefaultContextFactory(该类是被默认使用的)。接下来就是将MyContextFactory应用到GLSurfaceView实例中,代码如下:MyContextFactory contextFactory = new MyContextFactory();setEGLContextFactory(contextFactory);注意setEGLContextFactory方法必须在setRenderer方法执行之前被调用(具体原因可以参考setRenderer的源码)。

2> 定义三角形
最常用的方法是创建一个保存三角形坐标的浮点型数值,如下所示:

// number of coordinates per vertex in this arraystatic final int COORDS_PER_VERTEX = 3;static float triangleCoords[] = {        // in counterclockwise order:        0.0f,  0.622008459f, 0.0f,   // top        -0.5f, -0.311004243f, 0.0f,   // bottom left        0.5f, -0.311004243f, 0.0f    // bottom right};private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex// Set color with red, green, blue and alpha (opacity) valuesfloat color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };/** * Sets up the drawing object data for use in an OpenGL ES context. */public Triangle() {    // initialize vertex byte buffer for shape coordinates    ByteBuffer bb = ByteBuffer.allocateDirect(            // (number of coordinate values * 4 bytes per float)            triangleCoords.length * 4);    // use the device hardware's native byte order    bb.order(ByteOrder.nativeOrder());    // create a floating point buffer from the ByteBuffer    vertexBuffer = bb.asFloatBuffer();    // add the coordinates to the FloatBuffer    vertexBuffer.put(triangleCoords);    // set the buffer to read the first coordinate    vertexBuffer.position(0);}

3> 绘制三角形
为了在设备屏幕上绘制这个三角形,就要用到GLSurfaceView和GLSurfaceView.Renderer:

public class MyGLSurfaceView extends GLSurfaceView {    private MyGLRenderer mRenderer;    public MyGLSurfaceView(Context context) {        super(context);        initView();    }    public MyGLSurfaceView(Context context, AttributeSet attrs) {        super(context, attrs);        initView();    }    private void initView() {        MyContextFactory contextFactory = new MyContextFactory();        setEGLContextFactory(contextFactory);        // Set the Renderer for drawing on the GLSurfaceView        mRenderer = new MyGLRenderer();        setRenderer(mRenderer);    }}public class MyGLRenderer implements GLSurfaceView.Renderer {    private static final String TAG = "MyGLRenderer";    @Override    public void onSurfaceCreated(GL10 unused, EGLConfig config) {        // Set the background frame color        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);    }    @Override    public void onDrawFrame(GL10 unused) {        // Draw background color        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);    }    @Override    public void onSurfaceChanged(GL10 unused, int width, int height) {        // Adjust the viewport based on geometry changes,        // such as screen rotation        GLES20.glViewport(0, 0, width, height);    }}

上面的代码完成了GLSurfaceView.Renderer与GLSurfaceView的关联,接下来就是在MyGLRenderer的onSurfaceCreated方法中初始化三角形对象:

public class MyGLRenderer implements GLSurfaceView.Renderer {    ...    private Triangle mTriangle;    public void onSurfaceCreated(GL10 unused, EGLConfig config) {        ...        // initialize a triangle        mTriangle = new Triangle();    }    ...}

接下来就是在MyGLRenderer的onDrawFrame方法中绘制上面初始化的三角形:

public void onDrawFrame(GL10 unused) {    ...    mTriangle.draw();}

Triangle的draw方法是用来完成三角形的绘制操作,使用OpenGL ES 2.0绘制定义的形状需要大量的代码,因为必须向图形渲染管道提供大量细节,必须提供以下内容:

1. Vertex Shader - OpenGL ES graphics code for rendering the vertices of a shape.2. Fragment Shader - OpenGL ES code for rendering the face of a shape with colors or textures.3. Program - An OpenGL ES object that contains the shaders you want to use for drawing one or more shapes.

以下是定义用于绘制三角形的shaders的代码:

public class Triangle {    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;" +        "}";    ...}

上面的Shader是一段执行在GPU上的程序,此程序使用OpenGL Shading Language (GLSL) 语言编写的。接下来在MyGLRenderer创建工具方法loadShader用于编译Shader:

public static int loadShader(int type, String shaderCode){    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)    int shader = GLES20.glCreateShader(type);    // add the source code to the shader and compile it    GLES20.glShaderSource(shader, shaderCode);    GLES20.glCompileShader(shader);    return shader;}

编译完Shader后,需要将Shader添加到Program对象中,然后链接该Program对象,由于编译Shader和链接Program对象是非常耗时的,因此将 编译Shader和链接Program对象操作 放在Triangle的构造方法中,只会被执行一次:

public class Triangle() {    ...    private final int mProgram;    public Triangle() {        ...        int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,                                        vertexShaderCode);        int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,                                        fragmentShaderCode);        // create empty OpenGL ES Program        mProgram = GLES20.glCreateProgram();        // add the vertex shader to program        GLES20.glAttachShader(mProgram, vertexShader);        // add the fragment shader to program        GLES20.glAttachShader(mProgram, fragmentShader);        // creates OpenGL ES program executables        GLES20.glLinkProgram(mProgram);    }}

接下来在Triangle的draw方法中,首先将三角形的坐标和颜色传递给Vertex Shader和Fragment Shader,然后执行绘制操作:

private int mPositionHandle;private int mColorHandle;private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertexpublic void draw() {    // Add program to OpenGL ES environment    GLES20.glUseProgram(mProgram);    // get handle to vertex shader's vPosition member    mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");    // Enable a handle to the triangle vertices    GLES20.glEnableVertexAttribArray(mPositionHandle);    // Prepare the triangle coordinate data    GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,                                 GLES20.GL_FLOAT, false,                                 vertexStride, vertexBuffer);    // get handle to fragment shader's vColor member    mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");    // Set color for drawing the triangle    GLES20.glUniform4fv(mColorHandle, 1, color, 0);    // Draw the triangle    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);    // Disable vertex array    GLES20.glDisableVertexAttribArray(mPositionHandle);}

接下来运行应用,结果如下:



可以看到三角形被横向压扁了,这是因为没有应用Projection 和 Camera Views。

4> 应用Projection 和 Camera Views
在MyGLRenderer的onSurfaceChanged方法中创建Projection matrix:

// mMVPMatrix is an abbreviation for "Model View Projection Matrix"private final float[] mMVPMatrix = new float[16];private final float[] mProjectionMatrix = new float[16];private final float[] mViewMatrix = new float[16];@Overridepublic void onSurfaceChanged(GL10 unused, int width, int height) {    GLES20.glViewport(0, 0, width, height);    float ratio = (float) width / height;    // this projection matrix is applied to object coordinates    // in the onDrawFrame() method    Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);}

在MyGLRenderer的onSurfaceCreated方法中创建Camera Views matrix,然后在onSurfaceChanged方法中两个matrix相乘:

@Overridepublic void onSurfaceCreated(GL10 unused, EGLConfig config) {    // Set the background frame color    GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);    // Set the camera position (View matrix)    Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);    mTriangle = new Triangle();    mSquare   = new Square();}@Overridepublic void onSurfaceChanged(GL10 unused, int width, int height) {    ...    // Calculate the projection and view transformation    Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);}

接下来首先修改一下vertex shader的代码,在其中添建uMVPMatrix成员,然后在draw方法中将上面Projection matrix和 Camera Views matrix相乘的结果设置给vertex shader的uMVPMatrix成员:

public class Triangle {    private final String vertexShaderCode =        // This matrix member variable provides a hook to manipulate        // the coordinates of the objects that use this vertex shader        "uniform mat4 uMVPMatrix;" +        "attribute vec4 vPosition;" +        "void main() {" +        // the matrix must be included as a modifier 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;" +        "}";    // Use to access and set the view transformation    private int mMVPMatrixHandle;    ...    public void draw(float[] mvpMatrix) { // pass in the calculated transformation matrix        ...        // get handle to shape's transformation matrix        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");        // Pass the projection and view transformation to the shader        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);        // Draw the triangle        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);        // Disable vertex array        GLES20.glDisableVertexAttribArray(mPositionHandle);    }}

接下来在运行应用,三角形就会以正确的比例显示。

5> 响应Touch Event
GLSurfaceView响应Touch Event和普通View类似,重写onTouchEvent方法:

public class MyGLSurfaceView extends GLSurfaceView {    ...    private void initView() {        ...        // Render the view only when there is a change in the drawing data        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);    }    private final float TOUCH_SCALE_FACTOR = 180.0f / 320;    private float mPreviousX;    private float mPreviousY;    @Override    public boolean onTouchEvent(MotionEvent e) {        // MotionEvent reports input details from the touch screen        // and other input controls. In this case, you are only        // interested in events where the touch position changed.        float x = e.getX();        float y = e.getY();        switch (e.getAction()) {            case MotionEvent.ACTION_MOVE:                float dx = x - mPreviousX;                float dy = y - mPreviousY;                mRenderer.setAngle(                        mRenderer.getAngle() +                                ((dx + dy) * TOUCH_SCALE_FACTOR));  // = 180.0f / 320                requestRender();        }        mPreviousX = x;        mPreviousY = y;        return true;    }}

上面的代码根据手指滑动的偏移量设置三角形旋转的角度,然后调用requestRender方法请求重写渲染三角形,既而实现了三角形旋转的效果。由于只有在角度发生改变的情况下重新渲染三角形才有意义,因此在initView方法中设置渲染模式为GLSurfaceView.RENDERMODE_WHEN_DIRTY以提高效率。

接下来就来看看MyGLRenderer中是如何旋转图形的:

private final float[] mRotationMatrix = new float[16];private volatile float mAngle;public void onDrawFrame(GL10 gl) {    ...    float[] scratch = new float[16];    // Create a rotation for the triangle    // long time = SystemClock.uptimeMillis() % 4000L;    // float angle = 0.090f * ((int) time);    Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);    // Combine the rotation matrix with the projection and camera view    // Note that the mMVPMatrix factor *must be first* in order    // for the matrix multiplication product to be correct.    Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);    // Draw triangle    mTriangle.draw(scratch);}/** * Returns the rotation angle of the triangle shape (mTriangle). * * @return - A float representing the rotation angle. */public float getAngle() {    return mAngle;}/** * Sets the rotation angle of the triangle shape (mTriangle). */public void setAngle(float angle) {    mAngle = angle;}

上面代码中的保存当前旋转角度的成员mAngle是volatile类型的,这是因为OpenGL 的渲染代码(比如onDrawFrame方法)运行在独立的线程(GLThread)中。
然后运行应用,滑动屏幕时三角形就会发生旋转,与本节最开始的效果一样。

更多相关文章

  1. android抓取网络通讯包
  2. android icon适配最新版本
  3. 闹钟AlarmAndMusic 和支持播放音乐效果的《IT蓝豹》
  4. 建个android程序,弱弱的介绍下它的目录结构
  5. Android蓝牙栈bluez使用方法
  6. Android(安卓)Studio使用gradle-retrolambda支持Java8 新特性 La
  7. ubuntu+ndk编译ffmpeg
  8. 《Kotlin》 Android(安卓)Studio3.0 新特性 ~ New Features in An
  9. 踩坑日记-Android版本9 API28 NetworkSecurityConfig

随机推荐

  1. Android笔试面试题(二)
  2. Android开发者指南(5) ―― monkeyrunner
  3. android 开发笔记
  4. android:layout_gravity="bottom"不起作
  5. android中listview控件覆盖了其它控件使
  6. Android(安卓)NDK概述
  7. Android(安卓)开发环境下载地址 -- 百度
  8. error: Error retrieving parent for ite
  9. Android获得系统(system)权限
  10. Android之WebView