OpenGL(全寫Open Graphics Library)是一個跨語言、跨平台的三維圖象編程接口,同樣他也可以用來創建二維圖像。OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL三維圖形 API 的子集,針對手機、PDA和游戲主機等嵌入式設備而設計。android 平台上同樣集成了opengl es的開發包,opengl es在android平台上的運用,既有利於充分利用android平台不斷進步的硬件配置,也能為用戶提供更加多姿多彩的視覺體驗。
android平台創建三維動畫有二種方式,一種是使用matrix三維變形實現偽3D,一種就是使用opengl創建真3D。使用matrix三維變形,方法簡單,速度快,效率高,但因為其是偽3D,所以拋開視覺感受不談,其缺陷也非常明顯,他沒有真正的3D建模,只是對二維的VIEW做3D移動,大小變換和旋轉等操作,當你需要創建一個旋轉的六面體時,你需要創建六個VIEW,並讓他們實現繞一個虛擬的軸做同步旋轉,當這個3D模型有足夠多的面時,其復雜度大大增加了,因為在初始化時需要提前計算每個面的初始位置信息,旋轉,大小,位移的同步信息,這對於非矩形的view來說,這種計算相當繁瑣而且易於出錯並且不容易精確,而且目前來看,使用偽3D技術構建規則曲面似乎是難以實現。這時opengl es的優勢相當明顯。你可以創建任意形狀的多面體,只要把VIEW轉為BITMAP為多面體貼圖就行了。
android使用opengl ES創建立方體,需要用到下面一些函數。
gl.glFrontFace(GL10.GL_CW);
gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, 36, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
void glFrontFace(GLenum mode);
作用是控制多面形的正面是如何決定的。在默認情況下,mode是GL_CCW。一個多邊形有兩個面,每個面使用不同的介質貼圖,旋轉時是可以看到
mode的值為:
GL_CCW 表示窗口坐標上投影多邊形的頂點順序為逆時針方向的表面為正面。
GL_CW 表示頂點順序為順時針方向的表面為正面。
void glVertexPointer(GLint size,
GLenum type,
GLsizei stride,
const GLvoid * pointer)
作用是指定多面體每個頂點的坐標, size:指定了每個頂點對應的坐標個數,只能是2,3,4中的一個,默認值是4
type:指定了數組中每個頂點坐標的數據類型,可取常量:GL_BYTE, GL_SHORT,GL_FIXED,GL_FLOAT;
stride:指定了連續頂點間的字節排列方式,如果為0,數組中的頂點就會被認為是按照緊湊方式排列的,默認值為0
pointer:制訂了數組中第一個頂點的首地址,默認值為0,對於我們的android,大家可以不用去管什麼地址的,一般給一個IntBuffer就可以了。
需要說明的是第二個參數,一般在android中,會使用 GL10.GL_FIXED和GL10. FLOAT, 整型和浮點型相互對應, 0x10000=1.0f,整型的低八位表示浮點的小數,高八位表示浮點小數部分。而且第四個參數必須是 nativeOrder,如果是直接賦值的數組,需要使用下面代碼轉化
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
vbb.order(ByteOrder.nativeOrder());
mVertexBuffer = vbb.asIntBuffer();
mVertexBuffer.put(vertices);
mVertexBuffer.position(0);
gl.glColorPointer
這個是顏色了,和上一個函數一樣,是指定每個頂點的顏色值,參數結構也類似
gl.glDrawElements
這個是用來顯示的,六面體六個面,有12個三角形,每個三角形三個頂點,共36個
這樣就可以得到下面的四面體的類:
package com.example.openglactivity;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
public class Cube {
private IntBuffer mVertexBuffer;
private IntBuffer mColorBuffer;
private ByteBuffer mIndexBuffer;
// 定義本程序所使用的紋理
private int texture;
public Cube()
{
int one = 0x10000; //int 10000=1.0f
int vertices[] = {
-one, -one, -2000,//-one, //第三象限
one, -one, -2000,//-one, //第四象限
one, one, -2000,//-one, //第一象限
-one, one, -2000,//-one, //第二象限
-one, -one, 0, //one, //第三象限
one, -one, 0, //one, //第四象限
one, one, 0, //one, //第一象限
-one, one, 0, //one, //第二象限
};
int colors[] = {
0, 0, 0, one,
one, 0, 0, one,
one, one, 0, one,
0, one, 0, one,
0, 0, one, one,
one, 0, one, one,
one, one, one, one,
0, one, one, one,
};
byte indices[] = {
0, 4, 5, 0, 5, 1,
1, 5, 6, 1, 6, 2,
2, 6, 7, 2, 7, 3,
3, 7, 4, 3, 4, 0,
4, 7, 6, 4, 6, 5,
3, 0, 1, 3, 1, 2
};
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
vbb.order(ByteOrder.nativeOrder());
mVertexBuffer = vbb.asIntBuffer();
mVertexBuffer.put(vertices);
mVertexBuffer.position(0);
ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length*4);
cbb.order(ByteOrder.nativeOrder());
mColorBuffer = cbb.asIntBuffer();
mColorBuffer.put(colors);
mColorBuffer.position(0);
mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
mIndexBuffer.put(indices);
mIndexBuffer.position(0);
}
public void draw(GL10 gl)
{
gl.glFrontFace(GL10.GL_CW);
gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, 36, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
}
}
這是使用opengl創建的體面體,要導入android中使用,還需要經過一些步驟,一個是GLSurfaceView,一個是Renderer,Renderer是GLSurfaceView提供的接口,我們需要通過重載Renderer加載我們的四面體,然後通過setRenderer把包含我們六面體Renderer傳給GLSurfaceView,最後通過activity的setContentView把GLSurfaceView設置為activity的內容,代碼如下:
myGLRenderer 派生自Renderer,需要實現 Renderer的 onSurfaceCreated, onSurfaceChanged, onDrawFrame三個方法。這是比較重要的,定義了顯示3D模型的環境,顯示方式顯示特效等一系列東西。 onSurfaceCreated和 onSurfaceChanged熟悉surfaceview的人肯定不陌生,他原本就是surfaceview的方法,我們對於3D屏幕初始化,opengl環境設置都會在這兩個函數裡實現,然後就是onDrawFrame,用來顯示,大致來說,應該和view的onDraw差不多的功能,能夠自動適時刷新。用到的函數是這幾個:
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
設置背景顏色
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
啟動頂點數組支持,類似的函數還有
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
一個啟動法向量支持,一個啟動紋理渲染支持
gl.glViewport(0, 0, width, height);
設置顯示區域,一般就是整個屏幕吧
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
這兩句是設置當前矩陣模式和重載矩陣,不可省略,有三種模式,GL_MODELVIEW,對模型視景矩陣堆棧應用隨後的矩陣操作.GL_PROJECTION,對投影矩陣應用隨後的矩陣操作.GL_TEXTURE,對紋理矩陣堆棧應用隨後的矩陣操作.設置矩陣後必須使用 glLoadIdentity才會生效,該函數的功能是重置當前指定的矩陣為設置矩陣。還有一個函數gluPerspective也很重要,他是創建一個投影矩陣並且與當前矩陣相乘,得到的矩陣設定為當前變換,但要先通過glMatrixMode設定成GL_PROJECTION 投影矩陣才會得到想要的投影矩陣變換。
gl.glFrustumf(-ratio, ratio, -1, 1, 3, 100);
ratio=(float) width / height,所以 glFrustumf是設置長寬比例,因為opengl是按默認的正方形屏幕投影的,這樣當在一個高比寬大的長方形的屏上時,投影的正方形會變成高比寬大的長方形,要真實的投影,需要通過這個函數來校正投投影。
GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
創建觀察者坐標系,這裡可以參考下面的文章:http://www.cnblogs.com/chengmin/archive/2011/09/12/2174004.html
gl.glRotatef(ang, 0f, 0.2f, 0f);
這個是對函數進行旋轉,第一個參數是角度,後面三個參數是XYZ三個方向,類似的函數還有gl.glTranslatef(0, 0, -3.5f);移動操作
package com.example.openglactivity;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.GLU;
public class myGLRenderer implements Renderer {
private Cube m_cube;
private float ang = 0.0f;
public myGLRenderer()
{
m_cube = new Cube();
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// TODO Auto-generated method stub
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); //設置清除色
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// TODO Auto-generated method stub
gl.glViewport(0, 0, width, height);//設置視口
float ratio = (float) width / height;
gl.glMatrixMode(GL10.GL_PROJECTION); // 設置當前矩陣為投影矩陣
gl.glLoadIdentity(); // 重置矩陣為初始值
gl.glFrustumf(-ratio, ratio, -1, 1, 3, 100); // 根據長寬比設置投影矩陣
}
@Override
public void onDrawFrame(GL10 gl) {
// TODO Auto-generated method stub
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);//清空緩存
// 設置當前矩陣為模型視圖模式 //
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity(); // reset the matrix to its default state
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
// 設置視點
GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
gl.glRotatef(ang, 0f, 0.2f, 0f);
m_cube.draw(gl);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
ang+=1.0f;
}
}
myGLSurfaceView類派生自 GLSurfaceView,GLSurfaceView 派生自SurfaceView,這下一切就熟悉了,SurfaceView是為了動畫,特效而設計的高效UI類。 GLSurfaceView是專門為opengl服務的類。
這裡也十分簡單。 setRenderer就行了,可以查看setRenderer在 GLSurfaceView裡的實現:
public void setRenderer(Renderer renderer) {
checkRenderThreadState();
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory();
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;
mGLThread = new GLThread(renderer);
mGLThread.start();
}
開了個新線程去操作 renderer。下面是 myGLSurfaceView代碼
package com.example.openglactivity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.view.KeyEvent;
public class myGLSurfaceView extends GLSurfaceView {
private myGLRenderer mrender;
public myGLSurfaceView(Context context) {
super(context);
// TODO Auto-generated constructor stub
//getHolder().setFormat(PixelFormat.TRANSLUCENT);
mrender = new myGLRenderer();
setRenderer(mrender);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
return super.onKeyDown(keyCode, event);
}
}
最後看一下 Activity的調用,一切都了解了,和調用普通的view沒有區別。
package com.example.openglactivity;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity {
private myGLSurfaceView mGLSurfaceView;
public static Activity instance = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
instance = this;
mGLSurfaceView = new myGLSurfaceView(this);
setContentView(mGLSurfaceView);//這裡我們用mGLSurfaceView來替換以前常用的R.layout.main
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
instance = null;
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}