Android開發(fā) OpenGL ES繪制3D 圖形實(shí)例詳解
OpenGL ES是 OpenGL三維圖形API 的子集,針對手機(jī)、PDA和游戲主機(jī)等嵌入式設(shè)備而設(shè)計。 Ophone目前支持OpenGL ES 1.0 ,OpenGL ES 1.0 是以 OpenGL 1.3 規(guī)范為基礎(chǔ)的,OpenGL ES 1.1 是以 OpenGL 1.5 規(guī)范為基礎(chǔ)的。本文主要介紹利用OpenGL ES繪制圖形方面的基本步驟。
本文內(nèi)容由三部分構(gòu)成。首先通過EGL獲得OpenGL ES的編程接口;其次介紹構(gòu)建3D程序的基本概念;最后是一個應(yīng)用程序示例。
OpenGL ES 本質(zhì)上是一個圖形渲染管線的狀態(tài)機(jī),而 EGL 則是用于監(jiān)控這些狀態(tài)以及維護(hù)幀緩沖和其他渲染面的外部層。圖1 是一個典型的 EGL 系統(tǒng)布局圖。EGL 視窗設(shè)計是基于人們熟悉的用于 Microsoft Windows ( WGL )和 UNIX ( GLX )上的 OpenGL 的 Native 接口,與后者比較接近。 OpenGL ES 圖形管線的狀態(tài)被存儲于 EGL 管理的一個上下文中。幀緩沖和其他繪制渲染面通過 EGL API 創(chuàng)建、管理和銷毀。 EGL 同時也控制和提供了對設(shè)備顯示和可能的設(shè)備渲染配置的訪問。
圖1
OpenGL ES 需要一個渲染上下文和渲染面。渲染上下文中存儲OpenGL ES的狀態(tài)信息,渲染面用于圖元的繪制。編寫OpenGL ES之前需要EGL的操作有:
查詢設(shè)備可以支持的顯示句柄,并初始化。
創(chuàng)建渲染面,繪制OpenGL ES圖形。
創(chuàng)建渲染上下文。EGL需要創(chuàng)建OpenGL ES渲染上下文用于關(guān)聯(lián)到某個渲染面。
Ophone中EGL包括4個類,分別是EGLDisplay:顯示句柄、EGLConfig:配置類;EGLContext:渲染上下文;的類和EGLSurface:可渲染的視圖類。
EGL可以認(rèn)為成OpenGL ES和本地窗口系統(tǒng)之間的中間層。 本地窗口系統(tǒng)指GNU/Linux上X窗口系統(tǒng),或者M(jìn)ac OX X's Quartz等。在EGL確定渲染面的類型前,EGL需要和底層的窗口系統(tǒng)進(jìn)行通訊。因為在不同的操作系統(tǒng)上的窗口系統(tǒng)的不同,EGL提供一個透明窗口類型,即EGLDisplay。它抽象了各種窗口系統(tǒng)。所以首先要創(chuàng)建、初始化一個EGLDisplay對象。
// EGLContext的靜態(tài)方法getEGL獲得EGL實(shí)例 EGL10 egl = (EGL10)EGLContext.getEGL(); //創(chuàng)建EGLDisplay, EGL_DEFAULT_DISPLAY獲得缺省的本地窗口系統(tǒng)類型 EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); //初始化EGLDispla的同時獲得版本號 int[] version = new int[2]; egl.eglInitialize(dpy, version);
每個 EGLDisplay 在使用前都需要初始化。初始化 EGLDisplay 的同時能夠得到系統(tǒng)中 EGL 的實(shí)現(xiàn)版本號。通過版本號,合理運(yùn)用相應(yīng)OpenGL ES API,可以編寫兼容性良好的程序,以適應(yīng)更多的設(shè)備以及提供最大限度的移植性。初始化函數(shù)原型:
boolean eglInitialize(EGLDisplay display, int[] major_minor)
其中的display是一個有效的 EGLDisplay實(shí)例。函數(shù)調(diào)用完成時, major_minor將被賦予當(dāng)前 EGL 版本號。比如 EGL1.0 , major_minor[0]為1,major_minor[1]為0。EGLSurface包含了EGL渲染面相關(guān)的所有信息。查詢 EGLSurface配置信息有兩種方法,一是查詢所有的配置信息,從中選擇一個最為適合的;二是指定好配置信息,由系統(tǒng)給出最佳匹配結(jié)果。一般采用第二種方法。用戶通過configSpec指定出希望獲得的配置,函數(shù)eglChooseConfig通過參數(shù)Configs返回最佳的配置列表。之后利用已獲得的Configs,調(diào)用eglCreateContext創(chuàng)建一個渲染上下文,該函數(shù)返回EGLContext結(jié)構(gòu)。渲染面EGLSurface的創(chuàng)建通過函數(shù)eglCreateWindowSurface完成。一個應(yīng)用程序可以創(chuàng)建多個EGLContext。 eglMakeCurrent就是將某個渲染上下文綁定到渲染面。查詢函數(shù) eglGetCurrentContext, eglGetCurrentDisplay和eglGetCurrentSurface 分別用于獲得當(dāng)前系統(tǒng)的渲染上下文、顯示句柄和渲染面。最后EGLContext的靜態(tài)方法getGL獲得OpenGL ES的編程接口。下面的程序片段總結(jié)了上述內(nèi)容。
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();
構(gòu)建3D圖形的點(diǎn)
點(diǎn)是構(gòu)建3D模型的基礎(chǔ)。 OpenGL ES的內(nèi)部計算是基于點(diǎn)的。 用點(diǎn)也可以表示光源的位置,物體的位置。一般我們用一組浮點(diǎn)數(shù)來表示點(diǎn)。 例如一個正方形的4個頂點(diǎn)可表示為:
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, //右上 };
為了提高性能, 需要將浮點(diǎn)數(shù)組存入一個字節(jié)緩沖中。 所以有了下面的操作:
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); vbb.order(ByteOrder.nativeOrder()); FloatBuffer vertexBuffer = vbb.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0);
其中ByteOrder.nativeOrder()是獲取本機(jī)字節(jié)順序。OpenGL ES有操作圖形渲染管線的函數(shù),在默認(rèn)情況下這些函數(shù)功能的使用狀態(tài)是處于關(guān)閉的。 啟用和關(guān)閉這些函數(shù)可以用glEnableClientState、glDisableClientState來完成。
// 指定需要啟用定點(diǎn)數(shù)組
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// 說明啟用數(shù)組的類型和字節(jié)緩沖,類型為GL_FLOAT
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
// 不再需要時,關(guān)閉頂點(diǎn)數(shù)組
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
邊
邊是連接兩個點(diǎn)的一條線,是多邊形面的邊緣。
多邊形
多邊形是由邊構(gòu)成的單閉合環(huán)。 OpenGL ES中的多邊形必須是凸多邊形,即在多邊形的內(nèi)部任意取兩點(diǎn), 如果連接這兩個點(diǎn)的線段都在多變的內(nèi)部,這個多邊形就是凸多邊形。 繪制多邊形時需要指定渲染的方向, 分為順時針和逆時針。 因為方向決定了多邊形的朝向, 即正面和背面。 避免渲染那些被遮擋的部分可以了有效提高程序性能。 函數(shù)glFrontFace定義了渲染頂點(diǎn)的方向。
// 設(shè)置CCW方向為“正面”,CCW即CounterClockWise,逆時針
glFrontFace(GL_CCW);
// 設(shè)置CW方向為“正面”,CW即ClockWise,順時針
glFrontFace(GL_CW);
渲染
有了以上的概念講解后,現(xiàn)在要進(jìn)行最主要的工作—渲染。渲染是把物體坐標(biāo)所指定的圖元轉(zhuǎn)化成幀緩沖區(qū)中的圖像。圖像和頂點(diǎn)坐標(biāo)有著密切的關(guān)系。這個關(guān)系通過繪制模式給出。常用到得繪制模式有GL_POINTS、GL_LINE_STRIP、
GL_LINE_LOOP、GL_LINES、 GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN。下面分別介紹:
GL_POINTS:把每一個頂點(diǎn)作為一個點(diǎn)進(jìn)行處理,頂點(diǎn)n即定義了點(diǎn)n,共繪制n個點(diǎn)。
GL_LINES:把每一個頂點(diǎn)作為一個獨(dú)立的線段,頂點(diǎn)2n-1和2n之間共定義了n個線段,總共繪制N/2條線段。,如果N為奇數(shù),則忽略最后一個頂點(diǎn)。
GL_LINE_STRIP:繪制從第一個頂點(diǎn)到最后一個頂點(diǎn)依次相連的一組線段,第n和n+1個頂點(diǎn)定義了線段n,總共繪制N-1條線段。
GL_LINE_LOOP:繪制從定義第一個頂點(diǎn)到最后一個頂點(diǎn)依次相連的一組線段,然后最后一個頂點(diǎn)與第一個頂點(diǎn)相連。第n和n+1個頂點(diǎn)定義了線段n,然后最后一個線段是由頂點(diǎn)N和1之間定義,總共繪制N條線段。
GL_TRIANGLES:把每三個頂點(diǎn)作為一個獨(dú)立的三角形。頂點(diǎn)3n-2,3n-1和3n定義了第n個三角形,總共繪制N/3個三角形。
GL_TRIANGLE_STRIP:繪制一組相連的三角形。對于奇數(shù)點(diǎn)n,頂點(diǎn)n,n+1和n+2定義了第n個三角形;對于偶數(shù)n,頂點(diǎn)n+1,n和n+2定義了第n個三角形,總共繪制N-2個三角形。
GL_TRIANGLE_FAN:繪制一組相連的三角形。三角形是由第一個頂點(diǎn)及其后給定的頂點(diǎn)所確定。頂點(diǎn)1,n+1和n+2定義了第n個三角形,總共繪制N-2個三角形。
繪制函數(shù):
void glDrawArrays(int mode, int first, int count)
void glDrawElements(int mode, int count, int type, Buffer indices)
glDrawArrays創(chuàng)建一個幾何圖元序列,使用每個被的數(shù)組中從first開始,到first + count – 1結(jié)束的數(shù)組元素, mode為繪制模式。
glDrawElements使用count個元素定義一個圖元序列,type是indices數(shù)組中的數(shù)據(jù)類型,mode為繪制模式,indices數(shù)組存儲頂
點(diǎn)的索引值。
應(yīng)用舉例
利用上面講解的內(nèi)容給出一個Ophone上繪制一個3D球形的程序。效果圖如下:
圖2 球形示例
主要的繪制程序:
static private FloatBuffer vertex;//頂點(diǎn)對應(yīng)的字節(jié)緩沖 static private FloatBuffer normal;//法向量對應(yīng)的字節(jié)緩沖 float[] lightPos = new float[] {10.0f, 10.0f, 10.0f, 1.0f };//光源的坐標(biāo) 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);//設(shè)置背景顏色 gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPos, 0); gl.glEnable(GL10.GL_LIGHTING);//啟用光照 gl.glEnable(GL10.GL_LIGHT0); //打開光源 gl.glClearDepthf(1.0f);//設(shè)置深度緩存 gl.glDepthFunc(GL10.GL_LEQUAL);//設(shè)置深度緩存比較函數(shù),GL_LEQUAL表示新的像素的深度緩存值小于等于當(dāng)前像素的深度緩存值時通過深度測試 gl.glEnable(GL10.GL_DEPTH_TEST);//啟用深度緩存 gl.glEnable(GL10.GL_CULL_FACE); gl.glShadeModel(GL10.GL_SMOOTH);//設(shè)置陰影模式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)
它共接受三組坐標(biāo),分別為eye、 center和up。eye表示我們眼睛在"世界坐標(biāo)系"中的位置,center表示眼睛"看"的那個點(diǎn)的坐標(biāo),up坐標(biāo)表示觀察者本身的方向,如果將觀察點(diǎn)比喻成我們的眼睛,那么這個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(); }
總結(jié):
本文介紹了Ophone中利用OpenGL ES繪制圖形的基本概念和方法。OpenGL ES中還有很多其他內(nèi)容,諸如紋理、光照和材質(zhì)、混合、霧、蒙板、反射、3D模型的加載等。利用OpenGL ES函數(shù)可以繪制豐富的圖形應(yīng)用和游戲界面。
相關(guān)文章
Android 中ContentProvider的實(shí)例詳解
這篇文章主要介紹了Android 中ContentProvider的實(shí)例詳解的相關(guān)資料,希望通過本文大家能掌握這部分內(nèi)容,需要的朋友可以參考下2017-09-09Android編程基礎(chǔ)之獲取手機(jī)屏幕大小(DisplayMetrics應(yīng)用)示例
這篇文章主要介紹了Android獲取手機(jī)屏幕大小的方法,結(jié)合實(shí)例形式分析了TextView ,Button ,以及DisplayMetrics的使用方法,并重點(diǎn)分析了DisplayMetrics的應(yīng)用技巧,需要的朋友可以參考下2016-10-10Android實(shí)現(xiàn)后臺服務(wù)拍照功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)后臺服務(wù)拍照功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-05-05android 之Spinner下拉菜單實(shí)現(xiàn)級聯(lián)
android 之Spinner下拉菜單實(shí)現(xiàn)級聯(lián),需要的朋友可以參考一下2013-02-02Android自定義View實(shí)現(xiàn)圓弧進(jìn)度的效果
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)圓弧進(jìn)度的效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-01-01Android編程實(shí)現(xiàn)項目中異常捕獲及對應(yīng)Log日志文件保存功能
這篇文章主要介紹了Android編程實(shí)現(xiàn)項目中異常捕獲及對應(yīng)Log日志文件保存功能,涉及Android異常處理、日志讀寫及權(quán)限控制等相關(guān)操作技巧,需要的朋友可以參考下2018-02-02