(欢迎大家加入android技术交流QQ群:209796692)
概述
Android通过OpenGL包含了对高性能2D和3D图形的支持.尤其支持OpenGLES API.OpenGL是一个跨平台的图形API,提供了软件操作3D图形硬件的接口.OpenGLES是一个专用于嵌入式设备的OpenGL规格.从android1.0开始支持OpenGLES 1.0和1.1API规格.从Android2.2 (API Level 8)开始,框架支持OpenGLES 2.0 API规格.
注:Android框架所提供的API与J2MEJSR239 OpenGL ES API非常相似,但并不是完全相同.如果你熟悉J2MEJSR239规格,请注意不同的地方.
基础知识
Android的框架API和NDK都支持OpenGL.本主题面向Android框架接口.关于NDK的更多信息,请观NDK开发文档.
在Android框架中有两个基本的类使你可以通过OpenGLES API创建和操作图形系统:
GLSurfaceView和GLSurfaceView.Renderer.如果你的目标是在你的Android应用中使用OpenGL,了解如何在一个activity中实现这些类是首要目标.
GLSurfaceView
这个类是一个View,你可以用OpenGLAPI调用来绘制对象并管理它们.它与SurfaceView很相似.你可以创建一个GLSurfaceView的实例然后把你的绘制操作添加给它.然而,如果你想捕获触屏事件,你应扩展GLSurfaceView类来实现触屏事件监听器,就像在SDK的OpenGL例子ES1.0, ES 2.0和TouchRotateActivity中所示.
GLSurfaceView.Renderer
此接口定义了在一个OpenGL GLSurfaceView上作画所需的方法们.你必须提供另一个类来实现这个接口然后把它附加到你的GLSurfaceView实例上,使用GLSurfaceView.setRenderer().
GLSurfaceView.Renderer接口需要你实现以下方法们:
onSurfaceCreated():当创建GLSurfaceView时被调用,只调用一次.在这个方法中执行只发生一次的动作,比如设置OpenGL环境参数或初始化OpenGL图形对象.
onDrawFrame():系统在每次重绘GLSurfaceView时调用此方法.此方法是绘制图形对象的主要的执行点.
onSurfaceChanged():当GLSurfaceView几何体改变时系统调用此方法,比如GLSurfaceView的大小改变或设备屏幕的方向改变.使用此方法来响应GLSurfaceView容器的变化.
OpenGL包
一旦你使用GLSurfaceView和GLSurfaceView.Renderer为OpenGL建立起一个容器,你就可以开始用以下类来调用OpenGLAPIs:
OpenGLES 1.0/1.1 API 包
android.opengl- 这个包为OpenGLES 1.0/1.1 类提供了一个静态接口并且其性能好于javax.microedition.khronos包中的接口.
GLES10
GLES10Ext
GLES11
GLES10Ext
javax.microedition.khronos.opengles- 这个包提供了OpenGLES 1.0/1.1 的标准实现.
GL10
GL10Ext
GL11
GL11Ext
GL11ExtensionPack
OpenGLES 2.0 API 类
android.opengl.GLES20- 这个包提供了OpenGLES 2.0 的接口并且从Android2.2 (API Level 8)开始才能用.
如果你想正确创建支持OpenGL的应用,请看OpenGL ES 1.0或OpenGLES 2.0的指南.
声明OpenGL的需求
如果你的应用使用的OpenGL特性不能被所有的设备支持,你必须在AndroidManifest.xml文件中包含你的OpenGL的需求.下面是最常见的OpenGLmanifest声明:
OpenGLES 版本需求-如果你的应用只支持OpenGLES 2.0,你必须把以下设置添加到manifest中以声明这个需求:
<!--Tell the system this app requires OpenGL ES 2.0. -->
<uses-featureandroid:glEsVersion="0x00020000" android:required="true"/>
添加这个声明使得Android市场阻止你的应用被安装到不支持OpenGLES 2.0的设备上.
纹理压缩需求-如果你的应用使用了纹理压缩格式,你必须在你的manifest文件中用<supports-gl-texture>来声明所用格式.
在你的manifest中声明纹理压缩格式需求使得使用不支持其中任何一种压缩格式的设备的用户看不到你的应用.
为绘制对象映射坐标系
在Android设备中显示图形的一个基本问题是它们的屏幕的尺寸和形状可能不同.OpenGL假设一个正方形的,一致的坐标系统,并且,默认情况下,也乐于把这些坐标画到你的非正方形屏幕上,就像在正方形上一样.
图 1.默认OpenGL坐标系统(left)映射到典型的Android设备屏幕(right).
上面的插图,左图演示了一个OpenGL帧的一致的坐标系,以及如何映射到像右图这样的横向屏幕上.要解决这个问题,你可以设置OpenGL投影模式和相机视进行坐标变换使得你的图形对象在任何地方都具有正确的比例.
为了应用投影和视口,你要创建一个投影矩阵和一个视口矩阵然后把它们应用到OpenGL呈现管线中.投影矩阵重新计算你的图形的坐标于是它们可以正确地映射到Android设备屏幕上.视口矩追创建一个变换以从指定的眼睛位置呈现对象.
OpenGLES 1.0中的投影和视口
在ES1.0 API中,你通过创建每个矩追并把它们添加到OpenGL环境中来应用投影和视口.
投影矩阵-使用设备屏幕的几何体创建一个投影矩阵是为了重新计算对象坐标以使它们能按照正确的比例画出.下面的示例代码演示了如何实现GLSurfaceView.Renderer的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 }
视口变换矩阵-一旦你使用投影矩阵调整了坐标系统,你必须同时应用一个视口矩阵.下面的示例代码演示了如何实现GLSurfaceView.Renderer的onDrawFrame()方法来应用视图模型并且使用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);
...
}
OpenGLES 2.0中的投影和视口
在ES2.0API中,你首先要添加一个矩阵到顶点着色器才能应用投影和视口变换.通过添加这种矩阵成员,你就可以为你的对象产生并应用投影和视口变换.
1添加矩阵到顶点着色器-为投影矩阵创建一个变量并把它作为着色器位置的乘积来包含.在下面的顶点着色器示例代码中,所包含的uMVPMatrix成员允许你应用投影和视口矩阵到使用这个着色器的对象的坐标上.
private final String vertexShaderCode =
// 这个矩阵成员变量提供了一个勾子来操控
// 使用这个顶点着色器的对象的坐标
"uniform mat4 uMVPMatrix; \n" +
"attribute vec4 vPosition; \n" +
"void main(){ \n" +
// the matrix must be included as part of gl_Position
" gl_Position = uMVPMatrix * vPosition; \n" +
"} \n";
注:上面的例子在顶点着色器中定义了一个单独的变换矩阵成员,在着色器中你应用了组合的投影矩阵和视口矩阵.跟据你的应用的需求,你可能想在你的顶点着色器中分别定义投影矩阵和视口矩阵成员,这样可以单独地改变它们.
2操作顶点着色器-在你的顶点着色器中创建一个钩子来应用投影和视口后,你就可以操作这些变量来应用投影和视口矩阵.下面的代码演示了如何修改GLSurfaceView.Renderer的onSurfaceCreated()方法的实现来操作上面的定义在顶点着色器中的矩阵变量.
public void onSurfaceCreated(GL10 unused, EGLConfig config) { ... muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); ... }
3创建投影和视口矩阵 -生成要应用到图形对象的投影和视口矩阵.下面的示例代码演示了如何修改GLSurfaceView.Renderer的onSurfaceCreated()和 onSurfaceChanged()方法的实现来基于设备的屏幕的宽高比创建视口矩阵和投影矩阵.
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
...
// 创建一个视口矩阵
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;
// 跟据设备屏幕的几何特征创建投影矩阵
Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}
4应用投影和视口矩阵 -要应用投影和视口矩阵,需将矩阵们相乘然后把它们设置给顶点着色器.下面的示例代码演示了如何GLSurfaceView.Renderer的方法onDrawFrame()的实现来把上面代码所创建的投影和视口矩阵合并然后应应到图形对象上.
public void onDrawFrame(GL10 unused) {
...
// 合并投影和视口矩阵
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);
// 应用合并后的投影和视口变换
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
// 绘制对象们
...
}