LiveWallPaper 让您的壁纸动起来 连载(三)——OpenGL ES特辑
CS——LiveWallPaper 动态壁纸程序开发(三)之 OpenGL ES 持续更新中
首届 Google 暑期大学生博客分享大赛——2010 Android 篇
《Ed Burnette's Hello,Android Third Edition》 重点篇章读书学习改编笔记
上回书说到OpenGL ES来构建我们的立方体,那我们就先要了解一下何为OpenGL 以及OpenGL ES。
OpenGL的前世今生
1992年Silicon Graphics 开发出了OpenGL。它从此开始为程序员提供一个统一的平台让他们能够驾驭来自不同生产商的硬件。它的核心,OpenGL迎合了三维图形开发的一些经典概念如viewpoints和lighting并试图让开发者可以基本不去考虑错综复杂的硬件层就能实现3D的效果。您可以浏览http://www.opengl.org了解OpenGL的更多内容。
不过正是因为当初它是为工作站设计的,所以OpenGL对于一部手机来说实在是太大了。所以Google Android采用了OpenGL的一个子集——(OpenGL for Embedded Systems 针对嵌入式系统的OpenGL 简称OpenGL ES)。这个标准是由Intel、AMD、Nividia、Nokia、SONY、三星等行业巨头共同支持的 Khronos Group行业协会提出来的,包括Android、塞班和Iphone在内的主要手机平台都采用了这个库。虽然他们彼此之间还是有着细微的差别。您可以浏览http://www.khronos.org/opengles了解OpenGL ES的更多内容。
几乎每种计算机语言都有他自己与OpenGL ES相绑定的部分。当然JAVA也不例外。在JAVA Specification Request(JSR)239中定义了这部分绑定。
Android的OpenGL ES
OpenGL ES 1.0 基于完整的OpenGL 1.3,而 ES 1.1 基于 OpenGL 1.5。JSR 239 有两个版本:一个原有的1.0 和正式的1.0.1。而我们这次使用的便是OpenGL ES1.1
不过从Android2.2开始,OpenGL ES 2.0开始通过android.opengl 包得到支持。您也可以通过NDK来调用它。OpenGL ES 2.0 。不过还没有对应于OpenGL ES 2.0的JSR标准。如下图五所示
图五
现在让我们用OpenGL ES来建立我们自己的立方体模型吧。
1 package org.example.opengl;
2 -
3 - import java.nio.ByteBuffer;
4 - import java.nio.ByteOrder;
5 5 import java.nio.IntBuffer;
6 -
7 - import javax.microedition.khronos.opengles.GL10;
8 -
9 - import android.content.Context;
10 10 import android.graphics.Bitmap;
11 - import android.graphics.BitmapFactory;
12 - import android.opengl.GLUtils;
13 -
14 - class GLCube {
15 private final IntBuffer mVertexBuffer;
16 - public GLCube() {
17 - int one = 65536;
18 - int half = one / 2;
19 - int vertices[] = {
20 // 前
21 - -half, -half, half, half, -half, half,
22 - -half, half, half, half, half, half,
23 - // 后
24 - -half, -half, -half, -half, half, -half,
25 half, -half, -half, half, half, -half,
26 - // 左
27 - -half, -half, half, -half, half, half,
28 - -half, -half, -half, -half, half, -half,
29 - // 右
30 half, -half, -half, half, half, -half,
31 - half, -half, half, half, half, half,
32 - // 顶
33 - -half, half, half, half, half, half,
34 - -half, half, -half, half, half, -half,
35 // 底
36 - -half, -half, half, -half, -half, -half,
37 - half, -half, half, half, -half, -half, };
38 -
45 ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
46 - vbb.order(ByteOrder.nativeOrder());
47 - mVertexBuffer = vbb.asIntBuffer();
48 - mVertexBuffer.put(vertices);
49 - mVertexBuffer.position(0);
50 5 }- public void draw(GL10 gl) {
51 - gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
52 -
53 gl.glColor4f(1, 1, 1, 1);
54 - gl.glNormal3f(0, 0, 1);
55 - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
56 - gl.glNormal3f(0, 0, -1);
57 - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 4, 4);
58
59 - gl.glColor4f(1, 1, 1, 1);
60 - gl.glNormal3f(-1, 0, 0);
61 - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 8, 4);
62 - gl.glNormal3f(1, 0, 0);
63 gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 12, 4);
64 -
65 - gl.glColor4f(1, 1, 1, 1);
66 - gl.glNormal3f(0, 1, 0);
67 - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 16, 4);
68 gl.glNormal3f(0, -1, 0);
69 - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 20, 4);
70 - }
71 - }
在上述代码的第19行的矩阵通过点坐标确定了立方体的各个顶点。我们都知道立方体的每一个面都是个正方形。而这个正方形又是由两个等腰直角三角形组成的。 我们可以使用OpenGL中一个常用的绘图模型——GL_TRIANGLE_STRIP来画这个正方形。在这个模型中,我们指定两个起始点,随后标出的每一个点都将与起始点确定一个三角形。这是在图形加速硬件上绘出大量几何图形的一种十分快捷的方法。需要注意的是每个点都有x、y、z三个坐标。x轴指向屏幕右端、y轴指向屏幕上端而z轴则指向屏幕外朝向用户的视角。在代码第45行绘图方法中,我们使用构造的VertexBuffer并且为立方体的六条边画出六个不同向的等腰直角三角形。在项目实战中,您可能会把几个调用组合在一或两个strips中,因为您代码中的OpenGL命令调用越少,您的程序运行起来越快。
光、动作与质地
在真实的的生活中,我们周围有很多光源如太阳、灯光、火炬或是萤火虫的生物光。为了打造虚拟的3D世界,OpenGL让我们可以在场景中给出8种光源。并且如果我们想要它成为一个动态的背景,那么就一定要加入动作。当然这还不够,如果不给它加入质地(材质)的话,没人会相信它是个真东西。那在接下来的代码中我们将一一实现这些功能。
1 package org.example.opengl;
2
3 import javax.microedition.khronos.egl.EGLConfig;
4 import javax.microedition.khronos.opengles.GL10;
5
6 import android.content.Context;
7 import android.opengl.GLSurfaceView;
8 import android.opengl.GLU;
9 import android.util.Log;
10
11 class GLRenderer implements GLSurfaceView.Renderer {
12 private static final String TAG = "GLRenderer";
13 private final Context context;
14
15
16 private final GLCube cube = new GLCube();
17
18
19 private long startTime;
20 private long fpsStartTime;
21 private long numFrames;
22
23
24
25 GLRenderer(Context context) {
26 this.context = context;
27 }
28
29
30
31 public void onSurfaceCreated(GL10 gl, EGLConfig config) {
32
37 boolean SEE_THRU = false;
38 // 定义了一个布尔变量用于设置是否使程序执行第79行使立方体变得透明的语句
39
40 startTime = System.currentTimeMillis();
41 fpsStartTime = startTime;
42 numFrames = 0;
43
44
45 // 定义光源
46
47 float lightAmbient[] = new float[] { 0.2f, 0.2f, 0.2f, 1 };
48 float lightDiffuse[] = new float[] { 1, 1, 1, 1 };
49 float[] lightPos = new float[] { 1, 1, 1, 1 };
50 gl.glEnable(GL10.GL_LIGHTING);
51 gl.glEnable(GL10.GL_LIGHT0);
52 gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, lightAmbient, 0);
53 gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightDiffuse, 0);
54 gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPos, 0);
55
56
57 // 立方体的材质,这可以决定光照在它上面的效果 漫反射还是镜面反射。
58
59 float matAmbient[] = new float[] { 1, 1, 1, 1 };
60 float matDiffuse[] = new float[] { 1, 1, 1, 1 };
61 gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT,
62 matAmbient, 0);
63 gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE,
64 matDiffuse, 0);
65
66
67
68 // 设置我们需要的各种参数
69 gl.glEnable(GL10.GL_DEPTH_TEST);
70 gl.glDepthFunc(GL10.GL_LEQUAL);
71 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
72
73 // 选项:禁用抖动特效,以提高性能。
74 // gl.glDisable(GL10.GL_DITHER);
75
76
77
78 // 如上面提到的使透明的语句
79 if (SEE_THRU) {
80 gl.glDisable(GL10.GL_DEPTH_TEST);
81 gl.glEnable(GL10.GL_BLEND);
82 gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
83 }
84
85
86 // 启用纹理特效
87 gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
88 gl.glEnable(GL10.GL_TEXTURE_2D);
89
90 // 将一张点阵图加载为立方体的纹理。
91 GLCube.loadTexture(gl, context, R.drawable.android);
92
93
94
95
96 }
97
98
99
100
101 public void onSurfaceChanged(GL10 gl, int width, int height) {
102
103 // ...
104
105
106 // 定义视角
107 gl.glViewport(0, 0, width, height);
108 gl.glMatrixMode(GL10.GL_PROJECTION);
109 gl.glLoadIdentity();
110 float ratio = (float) width / height;
111 GLU.gluPerspective(gl, 45.0f, ratio, 1, 100f);
112
113 }
114
115
116
117
118
119 public void onDrawFrame(GL10 gl) {
120
121 // ...
122
123
124
125
126 // 清到黑屏
127 gl.glClear(GL10.GL_COLOR_BUFFER_BIT
128 | GL10.GL_DEPTH_BUFFER_BIT);
129
130 // ...
131 gl.glMatrixMode(GL10.GL_MODELVIEW);
132 gl.glLoadIdentity();
133 gl.glTranslatef(0, 0, -3.0f);
134
135 // 您的其它的绘图命令可以写在这里。
136
137
138 // 根据时间设置每次旋转的角度
139 long elapsed = System.currentTimeMillis() - startTime;
140 gl.glRotatef(elapsed * (25f / 1000f), 0, 1, 0); //每秒钟它都会沿Y轴转25度
141 gl.glRotatef(elapsed * (10f / 1000f), 1, 0, 0); //每秒钟它沿x轴转10度
142
143
144 // 画出模型
145 cube.draw(gl);
146
147
148 // 随时跟踪得出的帧数
149
150 numFrames++;
151 long fpsElapsed = System.currentTimeMillis() - fpsStartTime;
152 if (fpsElapsed > 3 * 1000) { // 每3秒一次
153 float fps = (numFrames * 1000.0F) / fpsElapsed;
154 Log.d(TAG, "Frames per second: " + fps + " (" + numFrames
155 + " frames in " + fpsElapsed + " ms)");
156 fpsStartTime = System.currentTimeMillis();
157 numFrames = 0;
158 }
159
160
161
162
163
164 }
165
166
167
168 }
169
在代码的第69行,我们设置了一组OpenGL 参数。当然OpenGL那几十个参数都是通过glEnable( ) 来启用,通过 glDisable( )来禁用。在下面列出的是最常用到的参数选项。
选项说明:
GL_BLEND | 允许将新进来的颜色值与已经在缓冲区中的颜色值融合。 |
GL_CULL_FACE |
Ignore polygons(多边形) based on their winding (clockwise or counterclockwise (顺时针还是逆时针)) in window coordinates. This is a cheap way to eliminate back faces. (正在琢磨如何解释) |
GL_DEPTH_TEST |
进行更深入的比较,并且更新缓冲区的深度。 忽略掉像素数已经远高于之前绘制好的。 |
GL_LIGHTi | Include light number i when figuring out an object’s brightness and color.(正在琢磨如何解释) |
GL_LIGHTING | 开启照明和材质计算。 |
GL_LINE_SMOOTH | 绘制抗锯齿线(无锯齿线)。 |
GL_MULTISAMPLE | 启动多重采样抗锯齿和其他效果。 |
GL_POINT_SMOOTH | 绘制抗锯齿点。 |
GL_TEXTURE_2D | 使用纹理绘制表面。 |
除了GL_DITHER和GL_MULTISAMPLE以外其他选项都是默认关闭的。请注意,我们启动的任何一项特性都会带有一定的性能开销。
请注意一下第150行开始的代码,请问我们为什么要检测每秒钟的帧数呢?
为的是检测画面的流畅程度
我们如何界定流畅程度。对一个游戏或对图形加速能力要求较高的程序,可以通过它刷洗屏幕的速度来看出它的流畅度。我们经常使用FPS即每秒帧数来衡量。不同的人对于流畅与否的尺度是不同的。一般来讲 15–30FPS对于普通玩家来说就可以接受了。但较骨灰级的玩家来说可能要60FPS或是更高才能让他们满意。作为专业的程序员,我们应该向着60FPS的目标而努力。但这可能不太现实,毕竟一些服务于低端的Android手机,他们的3D硬件加速相对于他们的分辨率要弱一些。当然对于越快的设备,达到这个标准越是没有问题。高帧频是具有挑战性的,因为我们要在60fps下 即1/60th秒(16.67毫秒之间)调用onDrawFrame()做一切需要做的事情,包括任何动画、物理计算、游戏计算,再加上花费在实际绘制图形上的时间。而唯一能够检验程序的FPS是否达到了我们设计的预期的方法,就是就是通过程序自己来测量它。
如上面的代码所示。每三秒它就会将您的平均FPS值发到Android系统的Log Messages上。如果这个数字低于您的预期,您就可以调整您的算法并重试。当然AVD毕竟是模拟。我们最终还是要看实际真机测试。
Continuous update......
posted on 2010-08-15 02:30 ClassroomStudio 阅读(5946) 评论(1) 编辑 收藏 举报