OpenGL ES
Android包括了高性能的2D和3D图形开放图形库(OpenGL)的支持,具体而言,是OpenGL ES的API。 OpenGL是一个跨平台的图形API,用于指定3D图形处理硬件标准的软件接口。OpenGL ES为规范嵌入式设备。 Android支持几个版本的OpenGL ES的API:
· OpenGL ES 1.0 and 1.1 - This API specification is supported by Android 1.0 and higher.
· OpenGL ES 2.0 - This API specification is supported by Android 2.2 (API level 8) and higher.
· OpenGL ES 3.0 - This API specification is supported by Android 4.3 (API level 18) and higher.
· OpenGL ES 3.1 - This API specification is supported by Android 5.0 (API level 21) and higher.
Caution: 支持OpenGL ES3.0 API的设备,需要由设备制造商提供了这个图形通道的实现。一个android 4.3以上的设备可能不支持OpenGL ES 3.0 API。了解OpenGL ES 支持运行时的版本信息,看:Checking OpenGL ES Version。
Note: 由Android框架提供的特定的API是类似于J2ME JSR239的OpenGL ES API,但并不相同。如果您熟悉J2ME JSR239规范,需要对变化的保持警惕。
一.The Basics
Android支持的OpenGL是通过其框架API和本地开发工具包(NDK)。其重点在于Android框架接口。关于NDK的更多信息,看Android NDK。
在Android 框架里有两个基本类让你用OpenGL ES API来创建和操作图像:GLSurfaceView 和 GLSurfaceView.Render。如果你的目标是使用OpenGL在你的Android应用程序,了解如何在一个activity里实现这些类应该是你的第一目标。
GLSurfaceView
这是一个View,你可以绘制和操作使用OpenGL API调用的对象,并在功能上与SurfaceView类似。你可以通过创建GLSurfaceView的实例,并增加渲染使用这个类。然而,如果你想获得触摸事件,就应该继承GLSurfaceView 类来实现事件监听方法,参考OpenGL 训练章节,Responding to Touch Events。
GLSurfaceView.Renderer
这个接口定义了所需的方法在GLSurfaceView绘制图形。你必须提供这个接口作为一个单独的类来实现,并使用GLSurfaceView.setRenderer将其附加到你的GLSurfaceView实例。
此接口需要实现下面的方法:
· onSurfaceCreated():在创建GLSurfaceView时被调用。此方法只会执行一次,比如设置OpenGL 环境参数或初始化OpenGL 绘制对象。
· onDrawFraw():每次重画GLSurfaceView的时候调用。这个方法是绘制(和重绘)图形对象的重点
· onSurfaceChanged():在GLSurfaceView发生几何变化时调用此方法,包括改变GLSurfaceView的大小和屏幕方向。
OpenGL ES packages
一旦你使用GLSurfaceView 和 GLSurfaceView.Renderer 建立了一个容器视图 , 你就可以开始调用OpenGL API使用以下类:
- OpenGL ES 1.0/1.1 API Packages
~ android.opengl - 这个包提供了一个静态的接口给OpenGL ES 1.0/1.1 , 它的性能比 javax.microedition.khronos 里的接口更好。
· GLES10
· GLES10Ext
· GLES11
· GLES11Ext
~ javax.microedition.khronos.opengles - 这个包提供OpenGL ES 1.0/1.1的标准实现。
· GL10
· GL10Ext
· GL11
· GL11Ext
· GL11ExtensionPack OpenGL ES 2.0 API Class
android.opengl.GLES20 - 这个包提供了OpenGL ES 2.0和可用的接口从Android 2.2(API级别8)。OpenGL ES 3.0/3.1 API Packages
android.opengl - 这个包提供了OpenGL ES 3.0/3.1的接口类。3.0版本是从Android 4.3(API 18)。3.1版本是从Android 5.0(API 21)开始。
· GLES30
· GLES31
· GLES31Ext (Android Extension Pack)
如果你想马上构建一个app用OpenGL ES , 按照Displaying Graphics with OpenGL ES
Declaring OpenGL Requirements
必须声明权限,下面是最常用的清单声明
· OpenGL ES version requirements
<!-- Tell the system this app requires OpenGL ES 2.0. -->
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<!-- Tell the system this app requires OpenGL ES 3.0. -->
<uses-feature android:glEsVersion="0x00030000" android:required="true" />
<!-- Tell the system this app requires OpenGL ES 3.1. -->
<uses-feature android:glEsVersion="0x00030001" android:required="true" />
Note: 3.0是向下兼容2.0的
· Texture compression requirements - 如果app用了纹理压缩,必须在清单文件声明 supports-gl-texture
Mapping Coordinates for Drawn Objects
绘制图像的基本问题之一是android 手机有很多不同的分辨率。OpenGL 假设了一个平面统一坐标系,默认情况下,恰当的在不是一个完美的平面上绘制坐标。
Figure 1 . 默认OpenGL坐标系(左),特殊设备(右)。
上边左图给OpenGL 虚拟了一个统一坐标,右边这幅图实际是针对个别典型的屏幕。为了解决这个问题,采用投影模式和相机视图转换坐标来得到正确的显示比例。
为了使用投影和相机视图,得创建一个投影矩阵和相机视图矩阵应用到OpenGL 渲染通道。投影矩阵重新计算图形的坐标,使他们正确地映射到Android设备的屏幕。相机视图矩阵创建一个转换,它渲染对象从一个特定的位置。
Projection and camera view in OpenGL ES 1.0
在 ES 1.0 API里,投影和相机视图创建各自的矩阵,然后将它们添加到OpenGL环境。
1. Projection matrix - 创建一个投影矩阵以重新计算对象的几何坐标来在屏幕上绘制正确比例。下面的一段代码演示怎么在onSurfaceChanged() 里创建投影矩阵基于屏幕的横纵比例应用到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
}
1. Camera transformation matrix - 一旦你使用投影矩阵调整了坐标系,那就必须得用相机视图了。下面代码演示怎么用onDrawFrame() 方法去申请一个model view和用GLU.gluLookAt()实用程序来创建一个模拟相机观察位置的变化。
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);
...
}
Projection and camera view in OpenGL ES 2.0 and higher
In the ES 2.0 and 3.0 APIs, 首先应用投影和相机视图添加一个矩阵对象到你绘制对象的顶点着色器。添加了这个矩阵成员,就可以生成和应用投影和相机观察矩阵对象。
1. Add matrix to 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";
Note: 上面的例子定义了一个变换矩阵成员在顶点着色器中结合投影矩阵和相机视图一起。取决于应用程序需求,可能需要定义单独的投影矩阵和相机观察矩阵成员在你的顶点着色器,这样你就可以独立改变他们。
Access the shader matrix
2. Access the shader matrix 在顶点着色器中应用投影和相机视图创建一个hook后,就可以访问该变量应用投影和相机观察矩阵。
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
...
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
...
}
3 . Create projection and camera viewing matrices 生成投影和视图矩阵用来绘制对象。下面是基于屏幕分辨率。
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. Apply projection and camera viewing matrices - 将投影和相机视图矩阵结合然后设置到顶点着色器。
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
...
}
完整例子,请看 Displaying Graphics with OpenGL ES
Shape Faces and Winding
In OpenGL , 一个形状被定义在三维空间里通过3个或更多的点。一组3个或更多三维点里(被叫做顶点在OpenGL里)有的在前有的在后。问题是怎么知道那个在前那个在后呢?从侧面回答这个问题,或者
Figure 1 . 插图的坐标list按逆时针drawing.
在这个例子中,三角形的三个点逆时针方向来定义。默认情况是,逆时针绘制的那一面是正面。所以上面你看到的形状在正面(以OpenGL的角度)和其他边是后面。
为什么它是重要的知道哪个是正面的? 这个涉及到OpenGL的常用功能有关,叫face culling。Face culling 是OpenGL环境的一个选项,为了允许渲染通道去忽略后面的形状,节省了时间,优化了内存和效率。
// enable face culling feature
gl.glEnable(GL10.GL_CULL_FACE);
// specify which faces to not draw
gl.glCullFace(GL10.GL_BACK);
如果使用脸face culling特性不知道它的形状是正面背面,你OpenGL图形看起来会有点瘦,或者根本不出现。所以,总是定义你的OpenGL图形的坐标逆时针顺序。
Note: 可以设置一个OpenGL环境延顺时针,但这样做需要更多的代码,和可能会迷惑有经验的OpenGL开发人员当你向他们请求帮助。所以不要这样做。
OpenGL Versions and Device Compatibility
OpenGL ES 1.0和1.1 API规范以来一直支持Android 1.0。从Android 2.2(API级别8)开始,该框架支持OpenGL ES 2.0 API规范。OpenGL ES 2.0支持大多数Android设备和建议与OpenGL开发的新应用程序。OpenGL ES 3.0支持Android4.3(API 18)或者更高,更多信息——OpenGL ES Version Dashboard。
OpenGL ES 1.0/1.1 API比使用2.0及更高版本有明显不同。1.x版本的API有更多的便利方法和一个固定的绘图通道,而OpenGL ES 2.0和3.0 API提供更多直接控制的管道通过使用OpenGL着色器。你应该仔细考虑绘图需求,选择最适合你应用程序的API版本。
OpenGL ES 3.0比起2.0有更多的额外功能并且向下兼容。这意味着你可以编写应用程序针对OpenGL ES 2.0和有条件地包括OpenGL ES 3.0图形特性是否可用。
Texture compression support
纹理压缩可以显著提高OpenGL应用程序的性能通过减少内存需求和更有效的利用内存带宽。Android框架提供了支持ETC1压缩格式作为标准,提供了ETC1Util类和etc1tool压缩工具(在sdk/tools/)。关于纹理压缩的例子,看CompressedTextureActivity (在sdk/samples/version/ApiDemos/src/com/example/android/apis/graphics)。
Caution: ETC1格式支持大多数的Android设备,但它不能保证是可用的。检查设备是否支持ETC1格式,调用ETC1Util.isETC1Supported()方法。
Note: ETC1纹理压缩格式不支持纹理透明度(alpha通道)。如果您的应用程序需要纹理透明,你应该调查其他纹理压缩格式可以在你的目标设备是否支持。
ETC2 / EAC纹理压缩格式是保证使用OpenGL ES 3.0 API时可用。这个纹理格式提供优秀的压缩比和高视觉质量并且格式还支持透明度(alpha通道)。
除了ETC格式,Android设备有不同的纹理压缩支持基于GPU芯片和OpenGL实现。你应该调查纹理压缩支持你的设备来确定压缩类型。为了确定纹理格式是被支持在你的设备,你必须查询设备和审查OpenGL的扩展名,确定纹理压缩格式(和其他OpenGL功能)是否支持的设备。一些常见的支持纹理压缩格式如下:
· ATITC (ATC) - ATI的纹理压缩(ATITC或ATC)适用于各种设备和支持固定比例的RGB纹理压缩,没有一个alpha通道。这种格式可能由几个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)和更广泛使用。支持RGB格式的纹理与4比特或8位alpha通道。这些格式由以下OpenGL扩展名:
GL_EXT_texture_compression_s3tc
有些设备只支持DXT1格式变化;这有限制的支持由以下OpenGL扩展名:
GL_EXT_texture_compression_dxt1
· 3DC - 3DC纹理压缩不是一种广泛使用的格式支持RGB纹理alpha通道。这种格式是由以下OpenGL扩展名:
GL_AMD_compressed_3DC_texture
Warning: 这些纹理压缩格式不是支持所有设备。这些格式由设备制造商决定。如何确定纹理压缩格式是在一个设备上,请参阅下一节。
Note: 一旦你的设备支持其中一个纹理压缩格式,确保你在清单文件声明supports-gl-textture。使用这个声明能过滤从外部服务,如google play,这样你的应用只有在支持的格式的设备上安装您的应用程序。详情,请参阅 OpenGL manifest declarations。
Determining OpenGL extensions ##
实现的OpenGL随Android设备的扩展支持OpenGL ES API。这些扩展包括纹理压缩,但通常也包括其他对OpenGL的特性集的扩展。
确定纹理压缩格式,和其他OpenGL扩展,支持在一个特定的设备:
- 目标设备上运行下面的代码来确定纹理压缩格式是否支持:
String extensions = javax.microedition.khronos.opengles.GL10.glGetString(GL10.GL_EXTENSIONS);
Warning: 这个结果由设备而定,你必须多测试几个设备确定这种压缩类型是普片支持的。
Android Extension Pack (AEP)
AEP确保您的应用程序支持一组标准化的OpenGL扩展并且超越OpenGL 3.1规范中描述的核心。封装这些扩展让这些设备有一套一致的功能,同时允许开发人员充分利用最新的移动GPU设备。
AEP也改善了支持图片、着色器存储缓冲区 和 原子计数器在fragment里的着色。
为了你的应用程序能够使用AEP,清单必须声明。此外,平台必须支持版本。
<uses feature android:name="android.hardware.opengles.aep"
android:required="true" />
验证平台的版本是否支持AEP,使用 hasSystemFeature(String) 方法,通过 FEATURE_OPENGLES_EXTENSION_PACK 作为参数。下面的代码片段显示了一个示例:
boolean deviceSupportsAEP = getPackageManager().hasSystemFeature
(PackageManager.FEATURE_OPENGLES_EXTENSION_PACK);
返回true,表示支持。
跟多信息,参考: Khronos OpenGL ES Registry.。
Checking the OpenGL ES Version
有几个版本的OpenGL ES在Android设备上。你可以指定最低版本的API,但你可能同时还想利用新的API。例如, 3.0的向下支持2.0的。
之前使用OpenGL ES高版本需要声明manifest , 应该检查设备是否实用此版本API。你可以二选一:
1. 尝试创建更高级的OpenGL ES上下文(EGLContext)和检查结果。
2. 创建一个最低版本 OpenGL ES上下文和检查版本的值。
下面的示例代码演示了如何检查可用的OpenGL ES版本通过创建EGLContext并检查结果。这个例子展示了如何检查OpenGL ES 3.0版本:
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版本通过创建一个最低支持环境,然后检查版本字符串:
// 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 <major>.<minor>"
// followed by optional content provided by the implementation.
使用这种方法,如果发现设备支持更高级的API版本,你必须摧毁的最低OpenGL ES上下文和创建一个新的上下文与更高可用的API版本。
Choosing an OpenGL API Version
OpenGL ES 1.0 API (和1.1扩展) 2.0 , 和3.0都提供高性能的接口为3D游戏开发,可视化和用户界面。2.0和3.0的图形编程大部分是相似的。3.0比起2.0增加了些特色。1.0版本比起2.0和3.0有很大的不同。所以开发者应该小心下面这些在开发前:
· Performance - 总之,2.0和3.0比1.0更快.。然而,正在运行的OpenGL应用程序的性能差异取决于android 设备,因为OpenGL ES 绘图通道的实现是由不同硬件制造商决定。
· Device Compatibility - 开发者应该考虑设备的类型,Android版本和OpenGL ES 版本提供给客户可用,关于OpenGL 适配的更多信息,参考:OpenGL Versions and Device Compatibility 。
· Coding Convenience - OpenGL ES 1.0/1.1 API提供了一个固定功能pipeline和便利功能,他们不可用在OpenGL ES 2.0或3.0 API。开发人员使用新的OpenGL ES的可能会发现编码更快和更方便比起1.0/1.1版本。
· Graphics Control - OpenGL ES 2.0和3.0 api提供更高程度的控制通过一个完全可编程管线通过使用着色器。与更多直接控制的图形处理管道,开发人员可以创建的效果,很难使用1.0/1.1 API创建出来。
· Texture Support - OpenGL ES 3.0 API支持最好的纹理压缩,因为它保证ETC2压缩格式可用,它支持透明度。1.0x 和 2.0 API 支持ETC1 , 但是这种格式的压缩不支持透明 所以你必须提供资源在其他设备支持的压缩格式的目标 ,更多信息参考:Texture compression support。
在性能、兼容性、便捷、控制和其他因素可能会影响到你的决定,你应该选择一个OpenGL API版本基于你想为您的用户提供最好的体验。