欢迎参加我们将于 6 月 3 日举行的 #Android11:Beta 版发布会

OpenGL ES

Android 可通过开放图形库 (OpenGL®)(特别是 OpenGL ES API)来支持高性能 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 版本,请参阅检查 OpenGL ES 版本

注意:Android 框架提供的特定 API 类似于 J2ME JSR239 OpenGL ES API,但两者并不完全相同。如果您熟悉 J2ME JSR239 规范,请注意各种变体。

另请参阅

基础知识

Android 通过其框架 API 和原生开发套件 (NDK) 来支持 OpenGL。本主题侧重于介绍 Android 框架接口。如需详细了解 NDK,请参阅 Android NDK

Android 框架中有如下两个基本类,用于通过 OpenGL ES API 来创建和操控图形:GLSurfaceViewGLSurfaceView.Renderer。如果您要在 Android 应用中使用 OpenGL,那么了解如何在 Activity 中实现这些类应是您的首要任务。

GLSurfaceView
此类是一个 View,您可以使用 OpenGL API 调用在其中绘制和操控对象,其功能类似于 SurfaceView。您可以通过创建 GLSurfaceView 的实例并将 Renderer 添加到其中来使用此类。不过,如果要捕获触摸屏事件,则应通过扩展 GLSurfaceView 类来实现触摸监听程序,如 OpenGL 培训课程响应触摸事件中所述。
GLSurfaceView.Renderer
此接口定义了在 GLSurfaceView 中绘制图形所需的方法。您必须将此接口的一个实现作为单独的类提供,并使用 GLSurfaceView.setRenderer() 将其附加到您的 GLSurfaceView 实例。

GLSurfaceView.Renderer 接口要求您实现以下方法:

  • onSurfaceCreated():系统会在创建 GLSurfaceView 时调用一次此方法。使用此方法可执行仅需发生一次的操作,例如设置 OpenGL 环境参数或初始化 OpenGL 图形对象。
  • onDrawFrame():系统会在每次重新绘制 GLSurfaceView 时调用此方法。请将此方法作为绘制(和重新绘制)图形对象的主要执行点。
  • onSurfaceChanged():系统会在 GLSurfaceView 几何图形发生变化(包括 GLSurfaceView 大小发生变化或设备屏幕方向发生变化)时调用此方法。例如,系统会在设备屏幕方向由纵向变为横向时调用此方法。使用此方法可响应 GLSurfaceView 容器中的更改。

OpenGL ES 软件包

使用 GLSurfaceViewGLSurfaceView.Renderer 为 OpenGL ES 建立容器视图后,您便可以开始使用以下类调用 OpenGL API:

如果要立即开始使用 OpenGL ES 构建应用,请按照使用 OpenGL ES 显示图形中的说明操作。

声明 OpenGL 要求

如果您的应用使用的 OpenGL 功能不一定在所有设备上可用,则您必须在 AndroidManifest.xml 文件中包含这些要求。以下是最常见的 OpenGL 清单声明:

  • OpenGL ES 版本要求 - 如果您的应用需要特定版本的 OpenGL ES,则您必须通过将以下设置添加到清单中来声明该要求,如下所示。

    对于 OpenGL ES 2.0:

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

    如果添加此声明,Google Play 会阻止将应用安装到不支持 OpenGL ES 2.0 的设备中。如果您的应用是专为支持 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 可向后兼容 2.0 API,这意味着您可以在应用中更灵活地使用 OpenGL ES 实现。在清单中将 OpenGL ES 2.0 API 声明为必需项之后,您就可以将该 API 版本作为默认版本,在运行时检查 3.x API 的可用性,然后,如果设备支持的话,便可使用 OpenGL ES 3.x 功能。如需详细了解如何检查设备支持的 OpenGL ES 版本,请参阅检查 OpenGL ES 版本

  • 纹理压缩要求 - 如果您的应用使用了纹理压缩格式,那么您必须使用 <supports-gl-texture> 在清单文件中声明应用支持的格式。如需详细了解可用的纹理压缩格式,请参阅纹理压缩支持

    如果您在清单中声明了纹理压缩要求,而用户的设备不支持您声明的压缩类型中的至少一种,则您的应用不会对此类用户显示。如需详细了解 Google Play 过滤如何处理纹理压缩,请参阅 <supports-gl-texture> 文档的 Google Play 和纹理压缩过滤部分。

映射已绘制对象的坐标

在 Android 设备上显示图形时,一个的基本问题在于屏幕的尺寸和形状各不相同。OpenGL 假设屏幕采用均匀的方形坐标系,默认情况下,您可以将这些坐标恰当地绘制到通常为非方形的屏幕上,就好像这些屏幕是完美的方形一样。

图 1. 默认 OpenGL 坐标系(左)与典型的 Android 设备屏幕(右)的映射。

上图在左侧显示了针对 OpenGL 帧假定的均匀坐标系,还显示了这些坐标实际上如何映射到右侧屏幕方向为横向的典型设备屏幕。要解决此问题,您可以通过应用 OpenGL 投影模式和相机视图来转换坐标,这样,您的图形对象在任何屏幕上都具有正确的比例。

为了应用投影和相机视图,您可以创建一个投影矩阵和一个相机视图矩阵,并将它们应用到 OpenGL 渲染管道。投影矩阵会重新计算图形的坐标,以便它们能够正确地映射到 Android 设备屏幕。相机视图矩阵会创建一个转换,用于从特定的眼睛位置渲染对象。

OpenGL ES 1.0 中的投影和相机视图

在 ES 1.0 API 中,您可以分别通过创建每个投影矩阵或相机视图矩阵并将它们添加到 OpenGL 环境来应用投影或相机视图。

  1. 投影矩阵 - 使用设备屏幕的几何图形来创建投影矩阵,以便重新计算对象坐标,从而以正确的比例绘制这些坐标。以下示例代码演示了如何修改 GLSurfaceView.Renderer 实现的 onSurfaceChanged() 方法,以便基于屏幕的宽高比来创建投影矩阵并将其应用到 OpenGL 渲染环境。

    Kotlin

        override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
            gl.apply {
                glViewport(0, 0, width, height)
    
                // make adjustments for screen ratio
                val ratio: Float = width.toFloat() / height.toFloat()
    
                glMatrixMode(GL10.GL_PROJECTION)            // set matrix to projection mode
                glLoadIdentity()                            // reset the matrix to its default state
                glFrustumf(-ratio, ratio, -1f, 1f, 3f, 7f)  // apply the projection matrix
            }
        }
        

    Java

        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. 相机转换矩阵 - 一旦您使用投影矩阵调整了坐标系,就必须也应用相机视图。以下示例代码演示了如何通过修改 GLSurfaceView.Renderer 实现的 onDrawFrame() 方法来应用模型视图,并使用 GLU.gluLookAt() 实用程序来创建可模拟相机位置的视图转换。

    Kotlin

        override fun onDrawFrame(gl: GL10) {
            ...
            gl.apply {
                // Set GL_MODELVIEW transformation mode
                glMatrixMode(GL10.GL_MODELVIEW)
                glLoadIdentity()                     // reset the matrix to its default state
            }
    
            // When using GL_MODELVIEW, you must set the camera view
            GLU.gluLookAt(gl, 0f, 0f, -5f, 0f, 0f, 0f, 0f, 1.0f, 0.0f)
            ...
        }
        

    Java

        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);
            ...
        }
        

OpenGL ES 2.0 及更高版本中的投影和相机视图

在 ES 2.0 和 3.0 API 中,您可以应用投影或相机视图,只需先分别向图形对象的顶点着色程序添加投影矩阵或相机视图矩阵成员即可。添加此矩阵成员后,您便可以分别生成投影和相机视图矩阵,并将它们应用到您的对象。

  1. 为顶点着色程序添加矩阵 - 为视图投影矩阵创建一个变量,并将其作为着色程序位置的调节系数添加。以下顶点着色程序代码示例中包含一个 uMVPMatrix 成员,它使您可以将投影和相机视图矩阵应用到使用此着色程序的对象的坐标。

    Kotlin

        private val 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"
        

    Java

        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";
        

    注意:上述示例在顶点着色程序中定义了单个转换矩阵成员,您可以在该着色程序中应用合并后的投影矩阵和相机视图矩阵。根据您的应用的要求,您可能需要在顶点着色程序中定义单独的投影矩阵和相机视图矩阵成员,以便单独更改它们。

  2. 访问着色程序矩阵 - 在顶点着色程序中创建钩子机制以分别应用投影和相机视图后,您可以访问该变量,以应用投影和相机查看矩阵。以下代码展示了如何通过修改 GLSurfaceView.Renderer 实现的 onSurfaceCreated() 方法来访问上述顶点着色程序中定义的矩阵变量。

    Kotlin

        override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
            ...
            muMVPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix")
            ...
        }
        

    Java

        public void onSurfaceCreated(GL10 unused, EGLConfig config) {
            ...
            muMVPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
            ...
        }
        
  3. 创建投影和相机视图矩阵 - 生成要应用于图形对象的投影和视图矩阵。以下示例代码展示了如何通过修改 GLSurfaceView.Renderer 实现的 onSurfaceCreated()onSurfaceChanged() 方法,根据设备的屏幕宽高比创建相机视图矩阵和投影矩阵。

    Kotlin

        override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
            ...
            // Create a camera view matrix
            Matrix.setLookAtM(vMatrix, 0, 0f, 0f, -3f, 0f, 0f, 0f, 0f, 1.0f, 0.0f)
        }
    
        override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
            GLES20.glViewport(0, 0, width, height)
    
            val ratio: Float = width.toFloat() / height.toFloat()
    
            // create a projection matrix from device screen geometry
            Matrix.frustumM(projMatrix, 0, -ratio, ratio, -1f, 1f, 3f, 7f)
        }
        

    Java

        public void onSurfaceCreated(GL10 unused, EGLConfig config) {
            ...
            // Create a camera view matrix
            Matrix.setLookAtM(vMatrix, 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(projMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
        }
        
  4. 应用投影和相机视图矩阵 - 要应用投影和相机视图转换,请将这些矩阵相乘,然后再将它们设置到顶点着色程序中。以下示例代码展示了如何通过修改 GLSurfaceView.Renderer 实现的 onDrawFrame() 方法,合并在上述代码中创建的投影矩阵和相机视图,然后将其应用到将由 OpenGL 渲染的图形对象。

    Kotlin

        override fun onDrawFrame(gl: GL10) {
            ...
            // Combine the projection and camera view matrices
            Matrix.multiplyMM(vPMatrix, 0, projMatrix, 0, vMatrix, 0)
    
            // Apply the combined projection and camera view transformations
            GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, vPMatrix, 0)
    
            // Draw objects
            ...
        }
        

    Java

        public void onDrawFrame(GL10 unused) {
            ...
            // Combine the projection and camera view matrices
            Matrix.multiplyMM(vPMatrix, 0, projMatrix, 0, vMatrix, 0);
    
            // Apply the combined projection and camera view transformations
            GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, vPMatrix, 0);
    
            // Draw objects
            ...
        }
        

有关如何通过 OpenGL ES 2.0 来应用投影和相机视图的完整示例,请参阅使用 OpenGL ES 显示图形

形状面和环绕

在 OpenGL 中,形状的面是由三维空间中的三个或更多点定义的表面。一个包含三个或更多三维点(在 OpenGL 中被称为顶点)的集合具有一个正面和一个背面。如何知道哪一面为正面,哪一面为背面呢?这个问题问得好!答案与环绕(即您定义形状的点的方向)有关。

三角形顶点处的坐标

图 1. 可转换为逆时针绘制顺序的坐标列表的图示。

在此示例中,三角形的点按照使它们沿逆时针方向绘制的顺序定义。这些坐标的绘制顺序定义了该形状的环绕方向。默认情况下,在 OpenGL 中,沿逆时针方向绘制的面为正面。图 1 中所示的三角形经过了定义,因此您看到的是该形状的正面(根据 OpenGL 解释),而另一面是背面。

知道形状的哪一面为正面为何如此重要呢?答案与 OpenGL 的“面剔除”这一常用功能有关。面剔除是 OpenGL 环境的一个选项,它允许渲染管道忽略(不计算或不绘制)形状的背面,从而节省时间和内存并缩短处理周期:

Kotlin

    gl.apply {
        // enable face culling feature
        glEnable(GL10.GL_CULL_FACE)
        // specify which faces to not draw
        glCullFace(GL10.GL_BACK)
    }
    

Java

    // enable face culling feature
    gl.glEnable(GL10.GL_CULL_FACE);
    // specify which faces to not draw
    gl.glCullFace(GL10.GL_BACK);
    

如果您在尝试使用面剔除功能时并不知道形状的哪些面为正面和哪些面为背面,那么您的 OpenGL 图形可能看起来会有些单薄,或者根本不会显示。因此,请务必按照逆时针绘制顺序定义 OpenGL 形状的坐标。

注意:您可以对 OpenGL 环境进行设置,使其将顺时针那面视为正面,但这样做需要编写更多代码,并且在您向经验丰富的 OpenGL 开发者寻求帮助时,这样的设置可能会使他们感到困惑。因此,请不要这样做。

OpenGL 版本和设备兼容性

从 Android 1.0 开始,OpenGL ES 1.0 和 1.1 API 规范均受支持。从 Android 2.2(API 级别 8)开始,框架支持 OpenGL ES 2.0 API 规范。大多数 Android 设备都支持 OpenGL ES 2.0,并且我们建议将其用于使用 OpenGL 开发的新应用。搭载 Android 4.3(API 级别 18)及更高版本且提供 OpenGL ES 3.0 API 实现的设备支持 OpenGL ES 3.0。如需了解支持指定版本的 OpenGL ES 且搭载 Android 的设备的相对数量,请参阅 OpenGL ES 版本信息中心

使用 OpenGL ES 1.0/1.1 API 的图形编程明显不同于使用 2.0 及更高版本的图形编程。OpenGL ES 1.x API 提供更多便捷方法和一个固定图形管道,而 2.0 和 3.0 API 使用了 OpenGL 着色程序,可更直接地控制管道。您应该仔细考虑图形要求,并选择最适合您的应用的 API 版本。如需了解详情,请参阅选择 OpenGL API 版本

与 OpenGL ES 2.0 API 相比,3.0 API 提供了更多的功能,性能更出色,并且也具有向后兼容性。这意味着您或许可以编写以 OpenGL ES 2.0 为目标的应用,并且在满足一定条件后添加 OpenGL ES 3.0 图形功能(如果这些功能可用)。如需详细了解如何检查 3.0 API 的可用性,请参阅检查 OpenGL ES 版本

纹理压缩支持

纹理压缩可以通过降低内存需求和更高效地利用内存带宽来显著提高 OpenGL 应用的性能。Android 框架支持 ETC1 压缩格式并将其作为标准功能,包括 ETC1Util 实用程序类和 etc1tool 压缩工具(位于 <sdk>/tools/ 处的 Android SDK 内)。有关使用纹理压缩的 Android 应用示例,请参阅 Android SDK 中的 CompressedTextureActivity 代码示例(<sdk>/samples/<version>/ApiDemos/src/com/example/android/apis/graphics/)。

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

注意:ETC1 纹理压缩格式不支持具有透明度(Alpha 通道)的纹理。如果您的应用需要具有透明度的纹理,则应研究目标设备上可用的其他纹理压缩格式。

使用 OpenGL ES 3.0 API 时,ETC2/EAC 纹理压缩格式一定为可用状态。这种纹理格式提供极佳的压缩比和出色的视觉效果,并且还支持透明度(Alpha 通道)。

除了 ETC 格式之外,Android 设备还为纹理压缩提供各种支持,具体取决于 GPU 芯片组和 OpenGL 实现。您应研究目标设备上提供的纹理压缩支持,确定您的应用应支持哪些压缩类型。为了确定指定设备上支持的纹理格式,您必须查询设备,并查看 OpenGL 扩展名称,此类名称用于标识该设备支持哪些纹理压缩格式(和其他 OpenGL 功能)。一些受支持的常用纹理压缩格式如下:

  • ATITC (ATC) - ATI 纹理压缩(ATITC 或 ATC)在多种设备上可用,并支持针对具有和不具有 Alpha 通道的 RGB 纹理进行固定速率压缩。此格式可以由多个 OpenGL 扩展名称表示,例如:
    • GL_AMD_compressed_ATC_texture
    • GL_ATI_texture_compression_atitc
  • PVRTC - PowerVR 纹理压缩 (PVRTC) 在多种设备上可用,并且支持具有或不具有 Alpha 通道的每像素 2 位和 4 位纹理。此格式由以下 OpenGL 扩展名称表示:
    • GL_IMG_texture_compression_pvrtc
  • S3TC (DXTn/DXTC) - S3 纹理压缩 (S3TC) 具有多种格式变体(DXT1 至 DXT5),它并非广泛适用。该格式支持具有 4 位 Alpha 通道或 8 位 Alpha 通道的 RGB 纹理。这些格式由以下 OpenGL 扩展名称表示:
    • GL_EXT_texture_compression_s3tc
    有些设备仅支持 DXT1 格式变体;这种有限的支持由以下 OpenGL 扩展名称表示:
    • GL_EXT_texture_compression_dxt1
  • 3DC - 3DC 纹理压缩 (3DC) 是一种未广泛使用的格式,支持具有 Alpha 通道的 RGB 纹理。此格式由以下 OpenGL 扩展名称表示:
    • GL_AMD_compressed_3DC_texture

警告:这些纹理压缩格式不一定在所有设备上均受支持。对这些格式的支持因制造商和设备而异。如需了解如何确定特定设备支持的纹理压缩格式,请参阅下一部分。

注意:一旦确定应用支持的纹理压缩格式,请务必使用 <supports-gl-texture> 在清单中声明这些格式。使用此声明即可通过 Google Play 等外部服务进行过滤,以便应用仅安装在支持应用所需格式的设备上。如需了解详情,请参阅 OpenGL 清单声明

确定 OpenGL 扩展

OpenGL 的实现支持的 OpenGL ES API 扩展因 Android 设备而异。这些扩展包括纹理压缩,但通常还包括对 OpenGL 功能集的其他扩展。

要确定特定设备上支持的纹理压缩格式和其他 OpenGL 扩展,请执行以下操作:

  1. 对目标设备运行以下代码,以确定支持的纹理压缩格式:

    Kotlin

        var extensions = gl.glGetString(GL10.GL_EXTENSIONS)
        

    Java

        String extensions = gl.glGetString(GL10.GL_EXTENSIONS);
        

    警告:此调用的结果因设备型号而异!您必须对多个目标设备运行此调用,才能确定通常支持哪些压缩类型。

  2. 查看此方法的输出内容即可确定设备支持哪些 OpenGL 扩展。

Android Extension Pack (AEP)

AEP 可确保您的应用支持除 OpenGL 3.1 规范中描述的核心 OpenGL 扩展集之外的标准化扩展集。将这些扩展打包在一起可促使在各种设备上实现一致的功能集,同时使开发者能够充分利用最新的移动 GPU 设备。

AEP 还改进了对图片、着色程序存储缓冲区和片段着色程序中的原子计数器的支持。

为了使应用能够使用 AEP,其清单必须将 AEP 声明为必需项。此外,平台版本必须支持此 AEP。

在清单中将 AEP 声明为必需项,如下所示:

    <uses feature android:name="android.hardware.opengles.aep"
                  android:required="true" />
    

要验证平台版本是否支持 AEP,请使用 hasSystemFeature(String) 方法,并传入 FEATURE_OPENGLES_EXTENSION_PACK 作为参数。以下代码段通过示例展示了如何做到这一点:

Kotlin

    var deviceSupportsAEP: Boolean =
            packageManager.hasSystemFeature(PackageManager.FEATURE_OPENGLES_EXTENSION_PACK)
    

Java

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

如果该方法返回 true,则表示支持 AEP。

如需详细了解 AEP,请访问 Khronos OpenGL ES Registry 中的相应页面。

检查 OpenGL ES 版本

Android 设备上提供了多个版本的 OpenGL ES。您可以在清单中指定应用所需的最低 API 版本,但与此同时,您可能也想要使用较新版 API 中的功能。例如,OpenGL ES 3.0 API 向后兼容 2.0 API,因此,您不妨这样编写应用:使其使用 OpenGL ES 3.0 功能,但在 3.0 API 不可用时回退至 2.0 API。

如果要使用某个版本中的 OpenGL ES 功能,而这个版本高于应用清单中所要求的最低版本,那么应用应首先检查设备上可用的 API 版本。为此,您可以采用下列两种方式之一:

  1. 尝试创建更高级别的 OpenGL ES 上下文 (EGLContext),并检查结果。
  2. 创建支持的最低级别的 OpenGL ES 上下文,并检查版本值。

以下示例代码演示了如何通过创建 EGLContext 和检查结果来检查可用的 OpenGL ES 版本。具体来说,此示例演示了如何检查是否存在 OpenGL ES 3.0 版本:

Kotlin

    private const val EGL_CONTEXT_CLIENT_VERSION = 0x3098
    private const val glVersion = 3.0
    private class ContextFactory : GLSurfaceView.EGLContextFactory {

        override fun createContext(egl: EGL10, display: EGLDisplay, eglConfig: EGLConfig): EGLContext {

            Log.w(TAG, "creating OpenGL ES $glVersion context")
            return egl.eglCreateContext(
                    display,
                    eglConfig,
                    EGL10.EGL_NO_CONTEXT,
                    intArrayOf(EGL_CONTEXT_CLIENT_VERSION, glVersion.toInt(), EGL10.EGL_NONE)
            ) // returns null if 3.0 is not supported
        }
    }
    

Java

    private static double glVersion = 3.0;

    private 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;
      }
    }
    

如果上述 createContext() 方法返回 null,则表示您的代码应改为创建 OpenGL ES 2.0 上下文,并回退为仅使用该 API。

以下代码示例演示了如何先创建支持的最低上下文,然后检查版本字符串,从而检查 OpenGL ES 版本:

Kotlin

    // Create a minimum supported OpenGL ES context, then check:
    gl.glGetString(GL10.GL_VERSION).also {
        Log.w(TAG, "Version: $it")
    }
     // The version format is displayed as: "OpenGL ES <major>.<minor>"
     // followed by optional content provided by the implementation.
    

Java

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

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

选择 OpenGL API 版本

OpenGL ES 1.0 API 版本(以及 1.1 扩展)、2.0 版本和 3.0 版本均可提供高性能图形界面,用于创建 3D 游戏、可视化图表和界面。OpenGL ES 2.0 和 3.0 的图形编程基本相似,不同之处在于版本 3.0 表示 2.0 API 与其他功能的超集。OpenGL ES 1.0/1.1 API 的编程明显不同于 OpenGL ES 2.0 和 3.0,因此开发者在开始借助这些 API 进行开发之前应仔细考虑以下因素:

  • 性能 - 通常,OpenGL ES 2.0 和 3.0 可提供比 ES 1.0/1.1 API 更快的图形性能。不过,具体的性能差异可能因运行 OpenGL 应用的 Android 设备而异,这是因为硬件制造商对 OpenGL ES 图形管道的实现存在不同。
  • 设备兼容性 - 开发者应考虑他们的客户可用的设备类型、Android 版本和 OpenGL ES 版本。如需详细了解各种设备的 OpenGL 兼容性,请参阅 OpenGL 版本和设备兼容性部分。
  • 编码便利性 - OpenGL ES 1.0/1.1 API 提供了一个固定函数管道和多个便捷函数,这些是 OpenGL ES 2.0 或 3.0 API 不具备的。刚接触 OpenGL ES 的开发者可能会发现针对版本 1.0/1.1 进行编码更快且更便捷。
  • 图形控制力 - OpenGL ES 2.0 和 3.0 API 通过使用着色程序提供完全可编程的管道,因而能够提供更强的控制力。通过更直接地控制图形处理管道,开发者可以打造使用 1.0/1.1 API 难以生成的效果。
  • 纹理支持 - OpenGL ES 3.0 API 能够最好地支持纹理压缩,因为它保证了支持透明度的 ETC2 压缩格式的可用性。1.x 和 2.0 API 实现通常包含对 ETC1 的支持,但这种纹理格式不支持透明度,因此您通常必须采用目标设备支持的其他压缩格式来提供资源。如需了解详情,请参阅纹理压缩支持

尽管性能、兼容性、便利性、控制力等因素可能会影响您的决策,但您应选择一种您认为可为用户提供最佳体验的 OpenGL API 版本。