OpenGL ES

Android 通过开放图形库 (OpenGL®),具体来说是 OpenGL ES API,支持高性能 2D 和 3D 图形。OpenGL 是一种跨平台图形 API,它指定了 3D 图形处理硬件的标准软件接口。OpenGL ES 是 OpenGL 规范的一种变体,适用于嵌入式设备。Android 支持 OpenGL ES API 的多个版本

  • 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.2 - 此 API 规范受 Android 7.0(API 级别 24)及更高版本支持。

注意:无论 Android 平台版本如何,除非设备制造商提供此图形管道的实现,否则设备无法支持 OpenGL ES 3.0 API。如果在清单中指定需要 OpenGL ES 3.0,则可以确保该版本存在于设备上。如果指定需要较低级别的版本,但如果可用,则希望使用 3.0 功能,则应在运行时检查设备支持的 OpenGL 版本。有关如何执行此操作的信息,请参阅 检查 OpenGL ES 版本

注意:Android 包含对 OpenGL ES 1.0 和 1.1 的支持,但这些版本的 API 已弃用,现代应用程序不应使用它们。

注意: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,则了解如何在活动中实现这些类应该是您的首要目标。

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 2.0 API 类
    • android.opengl.GLES20 - 此包提供 OpenGL ES 2.0 的接口,从 Android 2.2(API 级别 8)开始可用。
  • OpenGL ES 3.0/3.1/3.2 API 包
    • android.opengl - 此包提供 OpenGL ES 3.0/3.1 类的接口。版本 3.0 从 Android 4.3(API 级别 18)开始可用。版本 3.1 从 Android 5.0(API 级别 21)开始可用。版本 3.2 从 Android 7.0(API 级别 24)开始可用。

如果您想立即开始使用 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.2

    <!-- Tell the system this app requires OpenGL ES 3.2. -->
    <uses-feature android:glEsVersion="0x00030002" 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 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. 访问着色器矩阵 - 在顶点着色器中创建用于应用投影和摄像机视图的挂钩后,您可以访问该变量以应用投影和摄像机视图矩阵。以下代码显示了如何修改 onSurfaceCreated() 方法(GLSurfaceView.Renderer 实现)以访问在上面顶点着色器中定义的矩阵变量。

    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. 创建投影和摄像机视图矩阵 - 生成要应用于图形对象的投影和视图矩阵。以下示例代码显示了如何修改 onSurfaceCreated()onSurfaceChanged() 方法(GLSurfaceView.Renderer 实现)以基于设备的屏幕纵横比创建摄像机视图矩阵和投影矩阵。

    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. 应用投影和摄像机视图矩阵 - 要应用投影和摄像机视图变换,请将矩阵相乘,然后将其设置到顶点着色器中。以下示例代码显示了如何修改 onDrawFrame() 方法(GLSurfaceView.Renderer 实现)以组合上面代码中创建的投影矩阵和摄像机视图,然后将其应用于要由 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 中称为顶点)具有正面和背面。您如何知道哪个面是正面,哪个面是背面?好问题。答案与绕序有关,或者说,您定义形状点的方向。

Coordinates at
vertices of a triangle

图 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 版本和设备兼容性

OpenGL ES 1.0 和 1.1 API 规范自 Android 1.0 起一直得到支持。使用 OpenGL ES 1.0/1.1 API 进行图形编程与使用 2.0 及更高版本有很大不同。从 Android 2.2(API 级别 8)开始,所有 Android 设备都支持 OpenGL ES 2.0,并且是推荐用于使用 OpenGL ES 开发新应用程序的最低版本。Android 4.3(API 级别 18)及更高版本在提供 OpenGL ES 3.0 API 实现的设备上支持 OpenGL ES 3.0。有关支持特定版本的 OpenGL ES 的 Android 设备的相对数量的信息,请参阅 OpenGL ES 版本仪表板

您应该仔细考虑图形需求并选择最适合您的应用程序的 API 版本。有关更多信息,请参阅 选择 OpenGL API 版本

OpenGL ES 3.0 API 提供了比 2.0 API 更多的功能和更好的性能,并且还向后兼容。这意味着您有可能编写针对 OpenGL ES 2.0 的应用程序,并在可用时有条件地包含 OpenGL ES 3.0 图形功能。有关检查 3.0 API 可用性的更多信息,请参阅 检查 OpenGL ES 版本

纹理压缩支持

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

所有支持 OpenGL ES 2.0 或更高版本的 Android 设备都支持 ETC1 格式。

注意: ETC1 纹理压缩格式不支持具有透明度(alpha 通道)的纹理。如果您的应用程序需要具有透明度的纹理,您应该调查目标设备上可用的其他纹理压缩格式。使用 ETC1 渲染 alpha 通道纹理的一种方法是绑定两个 ETC1 纹理对象:第一个包含颜色数据,第二个包含 alpha 通道数据,然后在片段着色器中组合来自这两个纹理的值。

使用 OpenGL ES 3.0 API 时,可以保证 ETC2/EAC 纹理压缩格式可用。此纹理格式提供了极佳的压缩比和高视觉质量,并且该格式还支持透明度(alpha 通道)。

除了 ETC 格式之外,Android 设备对纹理压缩的支持因其 GPU 芯片组和 OpenGL 实现而异。您应该调查目标设备上的纹理压缩支持,以确定您的应用程序应该支持哪些压缩类型。为了确定给定设备上支持哪些纹理格式,您必须 查询设备 并查看OpenGL 扩展名称,这些名称标识设备支持哪些纹理压缩格式(以及其他 OpenGL 功能)。一些常用的纹理压缩格式如下

  • 自适应可缩放纹理压缩 (ASTC) - 一种旨在取代先前格式的纹理压缩格式。由于支持各种块大小,因此比以前的格式更灵活。
    • GL_KHR_texture_compression_astc_ldr
    • GL_KHR_texture_compression_astc_hdr(高动态范围)
  • 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

以下纹理压缩格式被视为旧格式,不建议在新应用程序中使用

  • 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
  • 3DC - 3DC 纹理压缩 (3DC) 是一种可用性较低的格式,支持具有 alpha 通道的 RGB 纹理。此格式由以下 OpenGL 扩展名称表示
    • GL_AMD_compressed_3DC_texture

警告: 这些纹理压缩格式不受所有设备支持。对这些格式的支持可能因制造商和设备而异。有关如何确定特定设备上有哪些纹理压缩格式的信息,请参阅下一节。

注意: 一旦确定了应用程序将支持哪些纹理压缩格式,请确保使用 <supports-gl-texture> 在清单中声明它们。使用此声明可以启用 Google Play 等外部服务的过滤,以便仅在支持应用程序所需格式的设备上安装应用程序。有关详细信息,请参阅 OpenGL 清单声明

确定 OpenGL 扩展

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

要确定特定设备上支持哪些纹理压缩格式和其他 OpenGL 扩展

  1. 在目标设备上运行以下代码以确定支持哪些纹理压缩格式

    Kotlin

    var extensions = gl.glGetString(GL10.GL_EXTENSIONS)
    

    Java

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

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

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

Android 扩展包 (AEP)

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

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

为了使您的应用能够使用 AEP,应用的清单必须声明需要 AEP。此外,平台版本必须支持它。

AEP 中指定的所有附加功能都包含在基本 OpenGL ES 3.2 规范中。如果您的应用需要 OpenGL ES 3.2,则无需要求 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 注册表 上的页面。

检查 OpenGL ES 版本

Android 设备上提供多个版本的 OpenGL ES。您可以在您的 清单 中指定应用程序所需的 API 的最低版本,但您可能也希望同时利用较新 API 中的功能。例如,OpenGL ES 3.0 API 向后兼容 API 的 2.0 版本,因此您可能希望编写应用程序,使其使用 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 版本 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 开始开发之前,开发人员应仔细考虑以下因素

  • 设备兼容性 - 开发人员应考虑其客户的设备类型、Android 版本以及可用的 OpenGL ES 版本。有关设备之间 OpenGL 兼容性的更多信息,请参阅 OpenGL 版本和设备兼容性 部分。
  • 纹理支持 - OpenGL ES 3.0 API 对纹理压缩的支持最佳,因为它保证了 ETC2 压缩格式的可用性,该格式支持透明度。2.0 API 实现包括对 ETC1 的支持,但是这种纹理格式不支持透明度。要使用压缩纹理实现透明度,您必须使用两种 ETC1 纹理(在颜色和 alpha 之间分割)或提供您要定位的设备支持的其他压缩格式中的资源。有关更多信息,请参阅 纹理压缩支持

虽然兼容性和纹理支持可能会影响您的决定,但您应该根据您认为最适合用户体验的 OpenGL API 版本进行选择。