Android OpenGL ES 翻译

官方文档地址

Android包含Open Graphics Library(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规范,请注意它们的变化。

OpenGL ES 1.0的投影和相机视图

其他说明:

基础

android框架APINDK(Native Development Kit)都支持OpenGL。这里主要关注的是Android框架接口。想了解更多关于NDK的信息,请参见Android NDK

Android框架中有两个基础类:GLSurfaceViewGLSurfaceView.Renderer,可让您使用OpenGL ES API创建和操作图形。如果你的目的是在你的安卓应用程序中使用OpenGL,那么弄明白如何在activity中实现这两个类应该是你的首要目标

GLSurfaceView

这个类是一个view,你可以调用 OpenGL API 绘制、操作GLSurfaceView的对象,GLSurfaceView的功能和SurfaceView类似。使用GLSurfaceView需要创建GLSurfaceView的实例并向它添加一个Renderer。如果你想要获取屏幕触摸事件,你应该像教程响应触摸事件中演示的那样扩展GLSurfaceView类实现触摸监听。

GLSurfaceView.Renderer

此接口定义了在GLSurfaceView中绘制图形所需的方法。你必须提供一个类实现这个接口,并且创建一个实例,然后使用GLSurfaceView.setRenderer()添加到你的GLSurfaceView上。

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

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

OpenGL ES packages

一旦你使用GLSurfaceViewGLSurfaceView.Renderer创建OpenGL ES容器后,你就可以使用下面的类调用OpenGLAPI

OpenGL ES 1.0/1.1 API Packages

OpenGL ES 2.0 API Class

  • android.opengl.GLES20 - 这个包提供OpenGL ES 2.0的接口。从Android 2.2 (API level 8)开始可用

OpenGL ES 3.0/3.1 API Packages

如果您想立即开始使用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.0API,这意味着您可以更灵活地在应用程序中实现OpenGL ES。通过在清单中声明OpenGL ES 2.0 API作为要求,您可以将该API版本用作默认值,在运行时检查3.x API的可用性,如果设备支持则使用OpenGL ES 3.x。有关检查设备支持的OpenGL ES版本的更多信息,请参阅检查OpenGL ES版本

  • 纹理压缩要求 - 如果您的应用程序使用纹理压缩格式,则必须使用<supports-gl-texture>在清单文件中声明应用程序支持的格式。有关可用纹理压缩格式的详细信息,请参阅纹理压缩支持
    对于不支持任何一种你在清单文件中声明的纹理压缩格式的设备,Google play会对该用户隐藏你的应用。
    在清单中声明纹理压缩要求会使用不支持至少一种声明压缩类型的设备的用户隐藏应用程序。有关Google Play如何过滤用于纹理压缩的详细信息,请参Google Play的纹理压缩过滤关于<supports-gl-texture>的文档。

映射绘制对象的坐标

在安卓设备上现实图形的一个基本问题是它们的屏幕大小和形状都可能不同。OpenGL默认情况下采用方形,均匀的坐标系统,可以将这些图形坐标绘制到普通的非方形屏幕上,就好像它是完美的正方形一样。


image

图1:默认的OpenGL坐标系统(左)映射到典型的安卓设备屏幕(右)

上图显示了左侧OpenGL框架的统一坐标系统,以及这些坐标如何实际映射到右侧横向的典型设备屏幕。要解决此问题,您可以应用OpenGL投影模式和摄像机视图来转换坐标,以便您的图形对象在任何显示上都具有正确的比例。
为了应用投影和摄像机视图,您可以创建投影矩阵和摄像机视图矩阵,并将它们应用于OpenGL渲染管道。投影矩阵重新计算图形的坐标,以便它们正确映射到Android设备屏幕。摄像机视图矩阵创建一个从特定视角变换的渲染对象。

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

ES 1.0 API中,您可以通过创建每个矩阵然后将它们添加到OpenGL环境来应用投影和摄像机视图。

  1. 投影矩阵 -
    使用设备屏幕的几何图形创建投影矩阵,以便重新计算对象坐标按照正确的比例绘制它们。以下示例代码演示了如何修改GLSurfaceView.Renderer实现的onSurfaceChanged()方法,以根据屏幕的宽高比创建投影矩阵,并将其应用于OpenGL渲染环境。
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
    }
}
  1. 相机变换矩阵 - 使用投影矩阵调整坐标系后,还必须应用摄像机视图。以下示例代码显示如何修改GLSurfaceView.Renderer实现的onDrawFrame()方法以应用模型视图并使用GLU.gluLookAt()实用程序去创建一个摄像机位置的模拟查看转换。
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)
    ...
}

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

ES 2.03.0 API中使用投影和摄像机视图,首先将矩阵成员添加到图形对象的顶点着色器。添加此矩阵成员后,您可以生成投影并且将投影和相机视图矩阵应用于你的对象。

  1. 将矩阵添加到顶点着色器 - 创建一个投影矩阵变量,并将其包含为着色器位置的乘数。在以下示例顶点着色器代码中,包含的uMVPMatrix成员允许您将投影和camera viewing矩阵应用于使用此着色器的对象的坐标。
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"

注意: 上面的示例在顶点着色器中定义了一个变换矩阵成员,您可以在其中应用projection矩阵和camera view矩阵。根据您的应用需求,您可能希望在顶点着色器中定义单独的投影矩阵和camera viewing矩阵成员,以便您可以单独更改它们。

  1. 访问着色器矩阵 - 在顶点着色器中创建钩子以应用投影和摄像机视图后,您可以访问该变量以应用投影和camera viewing矩阵。以下代码显示如何修改GLSurfaceView.Renderer实现的onSurfaceCreated()方法以访问上面顶​​点着色器中定义的矩阵变量。
override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
    ...
    muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix")
    ...
}
  1. 创建投影和camera viewing矩阵 - 生成要应用于图形对象的投影和查看矩阵。以下示例代码显示如何修改GLSurfaceView.Renderer实现的onSurfaceCreated()onSurfaceChanged()方法,以根据设备的屏幕宽高比创建camera view矩阵和投影矩阵。
override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
    ...
    // Create a camera view matrix
    Matrix.setLookAtM(mVMatrix, 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(mProjMatrix, 0, -ratio, ratio, -1f, 1f, 3f, 7f)
}
  1. 应用投影和camera viewing矩阵 - 要应用投影和摄像机视图变换,请将矩阵相乘,然后将它们设置为顶点着色器。以下示例代码显示如何修改GLSurfaceView.Renderer实现的onDrawFrame()方法,以组合在上面的代码中创建的投影矩阵和camera view,然后将其应用于要由OpenGL渲染的图形对象。
override fun onDrawFrame(gl: GL10) {
    ...
    // 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 ES 2.0应用投影和摄像机视图的完整示例,请参阅使用OpenGL ES类显示图形

Shape faces and winding

在OpenGL中,形状的面是由三维空间中的三个或更多个点定义的表面。一组三个或更多个三维点(在OpenGL中称为顶点)具有正面和背面。你怎么知道哪个面朝前,哪个面朝后?好问题。答案与缠绕或者定义形状的点的方向有关。


image

图 1. 坐标列表的图示,该列表展示逆时针绘图顺序。

在该示例中,三角形点的定义顺序使得它们以逆时针方向绘制。绘制这些坐标的顺序定义了形状的缠绕方向。默认情况下,在OpenGL中,逆时针绘制的面是正面。图1中所示的三角形您看到的是三角形的正面(由OpenGL解释),另一面是背面。
为什么知道形状的哪个面是正面很重要?答案与OpenGL的常用功能有关,称为面部剔除。面部剔除是OpenGL环境的一个选项,它允许渲染管道忽略(不计算或绘制)形状的背面,从而节省时间、内存和处理周期:

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

如果您尝试使用面部剔除功能而不知道形状的哪一侧是正面和背面,那么您的OpenGL图形看起来会有点薄,或者可能根本不会显示。所以,始终以逆时针的顺序定义OpenGL形状的坐标

注意:可以将OpenGL环境设置为将顺时针面作为正面,但这样做需要更多代码,并且当您向经验丰富的OpenGL开发人员寻求帮助时可能会使他们感到困惑。所以不要这样做。

OpenGL 版本和设备兼容性

Android 1.0开始支持OpenGL ES1.0和1.1的规范。Android框架从2.2(lv8)开始支持OpenGL ES 2.0的API规范。大多数Android设备都支持OpenGL ES 2.0,建议新开发的使用OpenGL的应用程序使用OpenGL2.0开发。对于提供了OpenGL ES 3.0 API 实现的设备,Android 4.3(lv 18)以及更高的版本支持OpenGL ES 3.0 API。有关支持特定 OpenGL ES 版本的设备的比例信息,请参阅OpenGL ES平台版本

使用OpenGL ES 1.0 / 1.1 API进行图形编程与使用2.0及更高版本有很大不同。 API的1.x版本具有更多便利方法和固定图形管道,而OpenGL ES 2.0和3.0 API通过使用OpenGL着色器提供更直接的管道控制。 您应该仔细考虑图形要求,并选择最适合您应用的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/)。

注意:大多数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)可在各种设备上使用,并支持每像素2位和4位纹理,带或不带alpha通道。 此格式由以下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 ES API的扩展方面,OpenGL的实现因Android设备而异。这些扩展包括纹理压缩,但通常还包括OpenGL功能集的其他扩展。

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

  1. 在目标设备上运行以下代码以确定支持的纹理压缩格式:
var extensions = gl.glGetString(GL10.GL_EXTENSIONS)

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

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

Android扩展包(AEP)

AEP确保您的应用程序支持OpenGL 3.1规范中描述的核心集之上和之外的标准化OpenGL扩展集。将这些扩展包装在一起可以促进跨设备的一致功能集,同时允许开发人员充分利用最新的移动GPU设备。
AEP还改进了片段着色器中对图像、着色器存储缓冲区和原子计数器的支持。
为了使您的应用能够使用AEP,应用的清单必须声明需要AEP。此外,平台版本必须支持它。
在清单中声明AEP要求如下:

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

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

var deviceSupportsAEP: Boolean =
        packageManager.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向后兼容API的2.0版本,因此您可能希望编写应用程序以使其使用OpenGL ES 3.0功能,但如果3.0 API不可用,则可以回退到2.0。
在使用高于应用程序清单中所需最低版本的OpenGL ES功能之前,应用程序应检查设备上可用API的版本。您可以通过以下两种方式之一完成此操作:

  1. 尝试创建更高级别的OpenGL ES上下文(EGLContext)并检查结果。
  2. 创建最低支持的OpenGL ES上下文并检查版本值。
    以下示例代码演示了如何通过创建EGLContext并检查结果来检查可用的OpenGL ES版本。此示例显示如何检查OpenGL ES 3.0版本:
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
    }
}

如果上面显示的createContext()方法返回null,那么您的代码应该创建一个OpenGL ES 2.0上下文,然后仅使用该API。

以下代码示例演示如何通过首先创建最小支持的上下文,然后检查版本字符串来检查OpenGL ES版本:

// 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.

使用此方法,如果您发现设备支持更高级别的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 ES图形管道的实现存在差异,性能差异可能因运行OpenGL应用程序的Android设备而异。
  • 设备兼容性 - 开发人员应考虑客户可用的设备类型,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版本。

推荐阅读更多精彩内容