這篇文章主要介紹了Android開發OpenGL 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 同時也控制和提供了對設備顯示和可能的設備渲染配置的存取。
圖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个点。
GL_LINES:把每一个顶点作为一个独立的线段,顶点2n-1和2n之间共定义了n个线段,总共绘制N/2条线段。,如果N为奇数,则忽略最后一个顶点。
GL_LINE_STRIP:绘制从第一个顶点到最后一个顶点依次相连的一组线段,第n和n+1个顶点定义了线段n,总共绘制N-1条线段。
GL_LINE_LOOP:绘制从定义第一个顶点到最后一个顶点依次相连的一组线段,然后最后一个顶点与第一个顶点相连。第n和n+1个顶点定义了线段n,然后最后一个线段是由顶点N和1之间定义,总共绘制N条线段。
GL_TRIANGLES:把每三个顶点作为一个独立的三角形。顶点3n-2,3n-1和3n定义了第n个三角形,总共绘制N/3个三角形。
GL_TRIANGLE_STRIP:绘制一组相连的三角形。对于奇数点n,顶点n,n+1和n+2定义了第n个三角形;对于偶数n,顶点n+1,n和n+2定义了第n个三角形,总共绘制N-2个三角形。
GL_TRIANGLE_FAN:绘制一组相连的三角形。三角形是由第一个顶点及其后给定的顶点所确定。顶点1,n+1和n+2定义了第n个三角形,总共绘制N-2个三角形。
绘制函数:
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球形的程序。效果图如下:
图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函數可以繪製豐富的圖形應用和遊戲介面。
以上是OpenGL ES在Android開發中繪製3D圖形的實例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!