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 ?guring 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......