Android包含了Open Graphics Library(OpenGL)支持高性能的2D和3D图像。特别是OpenGL ES API。OpenGL是一个跨平台图像API,为3D图像处理指定了标准的软件接口。OpenGL ES是为嵌入式设备提供的标准。Android1.0就开始支持OpenGL ES 1.0和1.1,从Android2.2开始支持OpenGL ES 2.0 API规格。
提示:Android框架提供的API类似J2ME JSR239 OpenGL ES API,但是不完全相同,要注意他们的区别。
基础
Android通过框架API和NDK支持OpenGL。这里主要讲框架接口。
Android提供了两个基本类用来使用OpenGL ES API创建和操作图像:GLSurfaceView和GLSurfaceView.Renderer。知道怎么实现这两个类是首要目标。
GLSurfaceView
类似SurfaceView的View类,可以使用OpenGL API绘制和操作对象。你可以使用这个类创建一个实例,然后添加你的Renderer进去。如果你想捕捉屏幕触摸事件,你需要继承GLSurfaceView类,实现触摸监听。
GLSurfaceView.Renderer
这个接口定义了绘制图像的方法。你需要用一个专门的类实现这个接口,然后使用GLSurfaceView.setRenderer()添加到你的GLSurfaceView实例中。
GLSurfaceView.Renderer接口需要实现下面这些方法:
- onSurfaceCreated():系统在创建GLSurfaceView时执行一次,一般用来设置OpenGL环境参数或者初始化OpenGL图像对象。
- onDrawFrame():每次重绘GLSurfaceView实现会执行,这个方法是绘制图像对象的主要执行点。
- onSurfaceChanged():当GLSurfaceView几何改变时被调用,包括它的尺寸或者屏幕方向。
OpenGL 包
使用GLSurfaceView和GLSurfaceView.Renderer创建了视图容器后,你要开始调用下面这些OpenGL API:
- OpenGL ES 1.0/1.1 API 包
android.opengl - 给OpenGL ES 1.0/1.1提供一个静态接口,性能优于javax.microedition.khronos 包。
javax.microedition.khronos.opengls - 标准实现。
- OpenGL ES 2.0 API 类
android.opengl.GLES20 - 从Android2.2开始提供。
说明OpenGL需求
不是所有的设备都支持OpenGL,你必须在AndroidManifest.xml中包含下面的需求说明。
为绘制对象映射坐标
在Android设备上显示图像最基本的问题就是,多样化的屏幕尺寸和形状。OpenGL模拟了一个正方形的均匀坐标系统,如果屏幕不是正方形的,也会被当作正方形来使用。
图解:左边是默认的坐标系统,右边是典型Android设备屏幕。
在Android设备上比例已经失调,为了解决这个问题,你可以使用OpenGL的投影模式和摄像视角来转换坐标,从而让图像呈现合适的比例。
为了应用投射和摄像视角,你需要创建一个投射矩阵和摄像视角矩阵,然后应用它们到OpenGL渲染管道。投射矩阵为你的图像计算坐标,以便映射到对应的设备屏幕中。摄像视角矩阵创建转换,以便从一个指定视角渲染对象。
OpenGL ES 1.0中的投射和摄像视角
1. 投射矩阵 - 使用几何学创建一个投射矩阵,用来计算对象的坐标,以便图像被以正确的比例绘制。下面的例子展示了如何基于屏幕尺寸比例创建一个投射矩阵,然后应用到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
}
2. 摄像转换矩阵 - 一旦你已经使用投射矩阵调整了坐标系统,你也必须使用一个摄像视角。下面的例子展示了如何修改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);
...
}
OpenGL ES 2.0中的投射和摄像视角
在ES2.0API中,先要添加一个矩阵成员到图像对象的顶点着色器中。
1. 添加矩阵到顶点着色器 - 下面的代码中包含uMVPMatrix成员,允许你应用投射和摄像视角矩阵到对象坐标中。
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
" gl_Position = uMVPMatrix * vPosition; \n" +
"} \n";
2. 访问着色器矩阵 - 在顶点着色器中创建了一个钩子后,我们可以访问顶点着色器中的矩阵变量了。
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
...
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
...
}
3. 创建投射和摄像视角矩阵。
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. 应用投射和摄像视角矩阵
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
...
}
形状朝向和旋转方向
在OpenGL中,一个形状的朝向在3D区域中被3个或者多个点所定义。有向前和向后的区分。那么怎么知道那面是前那面是后呢?答案就通过点被定义的旋转方向决定。
图解:坐标点被安装逆时针的方向被绘制的。
在这个例子中,三角形的点被安装逆时针的方向绘制,默认情况下,这一面就是被认为是正面。另一面就是反面了。
为什么要区分正反面呢?这是OpenGL的一种特征,叫face culling,让OpenGL可以选择一面来渲染而忽略看不到的背面,这样可以节约时间,内存和处理周期。
// enable face culling feature
gl.glEnable(GL10.GL_CULL_FACE);
// specify which faces to not draw
gl.glCullFace(GL10.GL_BACK);
提示:可以设置顺时针为正面,但是需要更多代码,也会混淆给你提供帮助的开发者,所以不要这样做。
OpenGL版本和设备兼容性
从Android1.0开始就执行OpenGL ES 1.0和1.1了,Android2.2开始支持OpenGL ES 2.0。OpenGL ES 2.0被大多数Android设备所支持。
纹理压缩支持
纹理压缩可以显著提高OpenGL程序的性能,可以减轻内存需求,更有效的使用内存带宽。Android框架提供ETC1压缩格式作为标准特征,包含ETC1Util作用类和etc1tool压缩工具(在<sdk>/tools/)。可以看
CompressedTextureActivity
例子。
大多数android设备都支持ETC格式,使用ETC1Util.isETC1Supported()判断是否支持。
提示:ETC1不支持透明度通道纹理,所以你要使用这种技术的话,请选择其他纹理压缩格式。
除了ETC1,Android设备基于GPU芯片集和OpenGL实现支持很多纹理压缩格式。你应该调查设备支持的压缩格式,然后选择使用哪一种。下面是一些常见的支持格式:
- ATITC(ATC) - 支持RGB纹理固定比率的压缩,使用或者不使用持透明度通道。这种格式可能代表了一些OpenGL扩展名,例如:
GL_AMD_compressed_ATC_texture
GL_ATI_texture_compression_atitc
- PVRTC - 支持2比特和4比特每像素的纹理压缩,使用或者不使用一个透明通道,扩展名是:
GL_IMG_texture_compression_pvrtc
- S3TC(DXTn/DXTC) - 不太常用,支持4比特透明通道或者8比特透明通道的RGB纹理。扩展名:
GL_OES_texture_compression_S3TC
GL_EXT_texture_compression_s3tc
GL_EXT_texture_compression_dxt1
GL_EXT_texture_compression_dxt2
GL_EXT_texture_compression_dxt3
- 3DC - 不是很常用的格式,支持透明通道的RGB纹理,扩展名:
GL_AMD_compressed_3DC_texture
警告:不是所有的设备都支持文件压缩格式,要看具体的制造商和设备。
提示:使用某个纹理压缩格式时,需要在manifest文件中使用<supports-gl-texture>指明,这样Google Play能够过滤不支持的设备。
指定OpenGL扩展
1. 运行下面的语句去确认设备支持什么样的纹理压缩格式:
String extensions = javax.microedition.khronos.opengles.GL10.glGetString(GL10.GL_EXTENSIONS);
警告:不同的设备得到的结果不一样,你需要在多种设备上运行,以确认一直普遍支持的格式。
2. 检查这个方法的输出,指定设备支持的扩展。
选择一个OpenGL API 版本
1.0/1.1和2.0版本都提供了创建3D游戏和可视化界面的高性能图像接口。不过它们之间是有很多不同的,可以通过下面的比较来决定使用哪个:
- 性能 - 通常2.0的性能是优于1.0/1.1的,不过这也要看运行OpenGL程序的设备和不同的OpenGL图像管道的实现。
- 设备兼容性 - 开发者要考虑设备类型,详细信息参考上面的 OpenGL版本和设备兼容性 章节。
- 代码便利性 - 1.0/1.1提供了一些固定函数通道和方便的函数,这是2.0没有的。有时候开发者使用1.0/1.1版本会更快更方便。
- 图像控制 - 2.0通过使用着色器提供的可设计的通道来提供一个更大程度的图像控制。通过一些直接的图像控制,开发者可以创建更多效果。