【代碼】Android 開發 Open GL ES 繪製 3D 圖形

OpenGL ES是 OpenGL三維圖形API 的子集,針對手機、PDA和遊戲主機等嵌入式設備而設計。 Ophone目前支持OpenGL ES 1.0 ,OpenGL ES 1.0 是以 OpenGL 1.3 規範爲基礎的,OpenGL ES 1.1 是以 OpenGL 1.5 規範爲基礎的。本文主要介紹利用OpenGL ES繪製圖形方面的基本步驟。
本文內容由三部分構成。首先通過EGL獲得OpenGL ES的編程接口;其次介紹構建3D程序的基本概念;最後是一個應用程序示例。
OpenGL ES 本質上是一個圖形渲染管線的狀態機,而 EGL 則是用於監控這些狀態以及維護幀緩衝和其他渲染面的外部層。圖1 是一個典型的 EGL 系統佈局圖。EGL 視窗設計是基於人們熟悉的用於 Microsoft Windows ( WGL )和 UNIX ( GLX )上的 OpenGL 的 Native 接口,與後者比較接近。 OpenGL ES 圖形管線的狀態被存儲於 EGL 管理的一個上下文中。幀緩衝和其他繪製渲染面通過 EGL API 創建、管理和銷燬。 EGL 同時也控制和提供了對設備顯示和可能的設備渲染配置的訪問。
http://www.android100.org/uploadfile/2013/0215/20130215064055201.png
圖1
OpenGL ES 需要一個渲染上下文和渲染面。渲染上下文中存儲OpenGL ES的狀態信息,渲染面用於圖元的繪製。編寫OpenGL ES之前需要EGL的操作有:
查詢設備可以支持的顯示句柄,並初始化。
創建渲染面,繪製OpenGL ES圖形。
創建渲染上下文。EGL需要創建OpenGL ES渲染上下文用於關聯到某個渲染面。
Ophone中EGL包括4個類,分別是EGLDisplay:顯示句柄、EGLConfig:配置類;EGLContext:渲染上下文;的類和EGLSurface:可渲染的視圖類。
EGL可以認爲成OpenGL ES和本地窗口系統之間的中間層。 本地窗口系統指GNU/Linux上X窗口系統,或者Mac OX X's Quartz等。在EGL確定渲染面的類型前,EGL需要和底層的窗口系統進行通訊。因爲在不同的操作系統上的窗口系統的不同,EGL提供一個透明窗口類型,即EGLDisplay。它抽象了各種窗口系統。所以首先要創建、初始化一個EGLDisplay對象。
// EGLContext的靜態方法getEGL獲得EGL實例
EGL10 egl = (EGL10)EGLContext.getEGL();
//創建EGLDisplay, EGL_DEFAULT_DISPLAY獲得缺省的本地窗口系統類型
EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
//初始化EGLDispla的同時獲得版本號
int[] version = new int[2];
egl.eglInitialize(dpy, version);
每個 EGLDisplay 在使用前都需要初始化。初始化 EGLDisplay 的同時能夠得到系統中 EGL 的實現版本號。通過版本號,合理運用相應OpenGL ES API,可以編寫兼容性良好的程序,以適應更多的設備以及提供最大限度的移植性。初始化函數原型:
boolean eglInitialize(EGLDisplay display, int[] major_minor)
其中的display是一個有效的 EGLDisplay實例。函數調用完成時, major_minor將被賦予當前 EGL 版本號。比如 EGL1.0 , major_minor[0]爲1,major_minor[1]爲0。EGLSurface包含了EGL渲染面相關的所有信息。查詢 EGLSurface配置信息有兩種方法,一是查詢所有的配置信息,從中選擇一個最爲適合的;二是指定好配置信息,由系統給出最佳匹配結果。一般採用第二種方法。用戶通過configSpec指定出希望獲得的配置,函數eglChooseConfig通過參數Configs返回最佳的配置列表。之後利用已獲得的Configs,調用eglCreateContext創建一個渲染上下文,該函數返回EGLContext結構。渲染面EGLSurface的創建通過函數eglCreateWindowSurface完成。一個應用程序可以創建多個EGLContext。 eglMakeCurrent就是將某個渲染上下文綁定到渲染面。查詢函數 eglGetCurrentContext, eglGetCurrentDisplay和eglGetCurrentSurface 分別用於獲得當前系統的渲染上下文、顯示句柄和渲染面。最後EGLContext的靜態方法getGL獲得OpenGL ES的編程接口。下面的程序片段總結了上述內容。
EGL10 egl = (EGL10)EGLContext.getEGL();
EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); int[] version = new int[2];
egl.eglInitialize(dpy, version);
int[] configSpec = {
EGL10.EGL_RED_SIZE, 5,
EGL10.EGL_GREEN_SIZE, 6,
EGL10.EGL_BLUE_SIZE, 5,
EGL10.EGL_DEPTH_SIZE, 16,
EGL10.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] num_config = new int[1];
egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config);
EGLConfig config = configs[0];
EGLContext context = egl.eglCreateContext(dpy, config,
EGL10.EGL_NO_CONTEXT, null);
EGLSurface surface = egl.eglCreateWindowSurface(dpy, config,
sHolder, null);
egl.eglMakeCurrent(dpy, surface, surface, context);
GL10 gl = (GL10)context.getGL();

構建3D圖形的點
點是構建3D模型的基礎。 OpenGL ES的內部計算是基於點的。 用點也可以表示光源的位置,物體的位置。一般我們用一組浮點數來表示點。 例如一個正方形的4個頂點可表示爲:
float vertices[] = {
-1.0f, 1.0f, 0.0f, //左上
-1.0f, -1.0f, 0.0f, //左下
1.0f, -1.0f, 0.0f, //右下
1.0f, 1.0f, 0.0f, //右上
};
爲了提高性能, 需要將浮點數組存入一個字節緩衝中。 所以有了下面的操作:
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
FloatBuffer vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);

其中ByteOrder.nativeOrder()是獲取本機字節順序。OpenGL ES有操作圖形渲染管線的函數,在默認情況下這些函數功能的使用狀態是處於關閉的。 啓用和關閉這些函數可以用glEnableClientState、glDisableClientState來完成。
// 指定需要啓用定點數組
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// 說明啓用數組的類型和字節緩衝,類型爲GL_FLOAT
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
// 不再需要時,關閉頂點數組
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

邊是連接兩個點的一條線,是多邊形面的邊緣。
多邊形
多邊形是由邊構成的單閉合環。 OpenGL ES中的多邊形必須是凸多邊形,即在多邊形的內部任意取兩點, 如果連接這兩個點的線段都在多變的內部,這個多邊形就是凸多邊形。 繪製多邊形時需要指定渲染的方向, 分爲順時針和逆時針。 因爲方向決定了多邊形的朝向, 即正面和背面。 避免渲染那些被遮擋的部分可以了有效提高程序性能。 函數glFrontFace定義了渲染頂點的方向。
// 設置CCW方向爲“正面”,CCW即CounterClockWise,逆時針
glFrontFace(GL_CCW);
// 設置CW方向爲“正面”,CW即ClockWise,順時針
glFrontFace(GL_CW);
渲染
有了以上的概念講解後,現在要進行最主要的工作—渲染。渲染是把物體座標所指定的圖元轉化成幀緩衝區中的圖像。圖像和頂點座標有着密切的關係。這個關係通過繪製模式給出。常用到得繪製模式有GL_POINTS、GL_LINE_STRIP、GL_LINE_LOOP、GL_LINES、 GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN。下面分別介紹:
GL_POINTS:把每一個頂點作爲一個點進行處理,頂點n即定義了點n,共繪製n個點。
http://www.android100.org/uploadfile/2013/0215/20130215064055786.png

GL_LINES:把每一個頂點作爲一個獨立的線段,頂點2n-1和2n之間共定義了n個線段,總共繪製N/2條線段。,如果N爲奇數,則忽略最後一個頂點。
http://www.android100.org/uploadfile/2013/0215/20130215064055534.png

GL_LINE_STRIP:繪製從第一個頂點到最後一個頂點依次相連的一組線段,第n和n+1個頂點定義了線段n,總共繪製N-1條線段。
http://www.android100.org/uploadfile/2013/0215/20130215064056149.png

GL_LINE_LOOP:繪製從定義第一個頂點到最後一個頂點依次相連的一組線段,然後最後一個頂點與第一個頂點相連。第n和n+1個頂點定義了線段n,然後最後一個線段是由頂點N和1之間定義,總共繪製N條線段。
http://www.android100.org/uploadfile/2013/0215/20130215064056857.png

GL_TRIANGLES:把每三個頂點作爲一個獨立的三角形。頂點3n-2,3n-1和3n定義了第n個三角形,總共繪製N/3個三角形。
http://www.android100.org/uploadfile/2013/0215/20130215064056216.png

GL_TRIANGLE_STRIP:繪製一組相連的三角形。對於奇數點n,頂點n,n+1和n+2定義了第n個三角形;對於偶數n,頂點n+1,n和n+2定義了第n個三角形,總共繪製N-2個三角形。
http://www.android100.org/uploadfile/2013/0215/20130215064056847.png

GL_TRIANGLE_FAN:繪製一組相連的三角形。三角形是由第一個頂點及其後給定的頂點所確定。頂點1,n+1和n+2定義了第n個三角形,總共繪製N-2個三角形。
http://www.android100.org/uploadfile/2013/0215/20130215064057609.png

繪製函數:
void glDrawArrays(int mode, int first, int count)
void glDrawElements(int mode, int count, int type, Buffer indices)
glDrawArrays創建一個幾何圖元序列,使用每個被的數組中從first開始,到first + count – 1結束的數組元素, mode爲繪製模式。
glDrawElements使用count個元素定義一個圖元序列,type是indices數組中的數據類型,mode爲繪製模式,indices數組存儲頂點的索引值。
應用舉例
利用上面講解的內容給出一個Ophone上繪製一個3D球形的程序。效果圖如下:
http://www.android100.org/uploadfile/2013/0215/20130215064057309.png

圖2 球形示例
主要的繪製程序:
static private FloatBuffer vertex;//頂點對應的字節緩衝
static private FloatBuffer normal;//法向量對應的字節緩衝
float[] lightPos = new float[] {10.0f, 10.0f, 10.0f, 1.0f };//光源的座標
private static final int STEP = 24;//
private static final float RADIUS = 1.0f;//半徑
protected void init(GL10 gl) {
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);//設置背景顏色
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPos, 0);
gl.glEnable(GL10.GL_LIGHTING);//啓用光照
gl.glEnable(GL10.GL_LIGHT0); //打開光源
gl.glClearDepthf(1.0f);//設置深度緩存
gl.glDepthFunc(GL10.GL_LEQUAL);//設置深度緩存比較函數,GL_LEQUAL表示新的像素的深度緩存值小於等於當前像素的深度緩存值時通過深度測試
gl.glEnable(GL10.GL_DEPTH_TEST);//啓用深度緩存
gl.glEnable(GL10.GL_CULL_FACE);
gl.glShadeModel(GL10.GL_SMOOTH);//設置陰影模式GL_SMOOTH
}
protected void drawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT |
GL10.GL_DEPTH_BUFFER_BIT);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
GLU.gluLookAt(gl, 0, 0, 7f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);//
drawSphere(gl, RADIUS, STEP, STEP); //繪製球形
}
public static void gluLookAt (GL10 gl, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)

它共接受三組座標,分別爲eye、 center和up。eye表示我們眼睛在"世界座標系"中的位置,center表示眼睛"看"的那個點的座標,up座標表示觀察者本身的方向,如果將觀察點比喻成我們的眼睛,那麼這個up則表示我們是正立還是倒立異或某一個角度在看,這裏是正立方式,所以是{0,1,0}。
private static void drawSphere(GL10 gl, float radius,
int stacks, int slices) {
vertex=allocateFloatBuffer( 4* 6 * stacks * (slices+1) );
normal=allocateFloatBuffer( 4* 6 * stacks * (slices+1) );
int i, j, triangles;
float slicestep, stackstep;
stackstep = ((float)Math.PI) / stacks;
slicestep = 2.0f * ((float)Math.PI) / slices;
for (i = 0; i < stacks; ++i)
{
float a = i * stackstep;
float b = a + stackstep;
float s0 = (float)Math.sin(a);
float s1 = (float)Math.sin(b);
float c0 = (float)Math.cos(a);
float c1 = (float)Math.cos(b);
float nv;
for (j = 0; j <= slices; ++j)
{
float c = j * slicestep;
float x = (float)Math.cos(c);
float y = (float)Math.sin(c);
nv=x * s0;
normal.put(nv);
vertex.put( nv * radius);
nv=y * s0;
normal.put(nv);
vertex.put( nv * radius);
nv=c0;
normal.put(nv);
vertex.put( nv * radius);
nv=x * s1;
normal.put(nv);
vertex.put( nv * radius);
nv=y * s1;
normal.put(nv);
vertex.put( nv * radius);
nv=c1;
normal.put(nv);
vertex.put( nv * radius);
}
}
normal.position(0);
vertex.position(0);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertex);
gl.glNormalPointer(GL10.GL_FLOAT, 0, normal);
gl.glEnableClientState (GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState (GL10.GL_NORMAL_ARRAY);
triangles = (slices + 1) * 2;
for(i = 0; i < stacks; i++)
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,
i * triangles, triangles);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
}
private static FloatBuffer allocateFloatBuffer(int capacity){
ByteBuffer vbb = ByteBuffer.allocateDirect(capacity);
vbb.order(ByteOrder.nativeOrder());
return vbb.asFloatBuffer();
}

總結:
本文介紹了Ophone中利用OpenGL ES繪製圖形的基本概念和方法。OpenGL ES中還有很多其他內容,諸如紋理、光照和材質、混合、霧、蒙板、反射、3D模型的加載等。利用OpenGL ES函數可以繪製豐富的圖形應用和遊戲界面。  
發佈了31 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章