OpenGL ES繪製3D圖形

Erichn發表於2011-10-12

 

      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程式的基本概念;最後是一個應用程式示例。
 
EGL
       OpenGL ES 本質上是一個圖形渲染管線的狀態機,而 EGL 則是用於監控這些狀態以及維護幀緩衝和其他渲染面的外部層。圖1 是一個典型的 EGL 系統佈局圖。EGL 視窗設計是基於人們熟悉的用於 Microsoft Windows ( WGL )和 UNIX ( GLX )上的 OpenGL 的 Native 介面,與後者比較接近。 OpenGL ES 圖形管線的狀態被儲存於 EGL 管理的一個上下文中。幀緩衝和其他繪製渲染面通過 EGL API 建立、管理和銷燬。 EGL 同時也控制和提供了對裝置顯示和可能的裝置渲染配置的訪問。
 
       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物件。
 
  1. // EGLContext的靜態方法getEGL獲得EGL例項  
  2. EGL10 egl = (EGL10)EGLContext.getEGL();   
  3.     //建立EGLDisplay, EGL_DEFAULT_DISPLAY獲得預設的本地視窗系統型別  
  4. EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);   
  5. //初始化EGLDispla的同時獲得版本號   
  6. int[] version = new int[2];   
  7. egl.eglInitialize(dpy, version);  

     每個 EGLDisplay 在使用前都需要初始化。初始化 EGLDisplay 的同時能夠得到系統中 EGL 的實現版本號。通過版本號,合理運用相應OpenGL ES API,可以編寫相容性良好的程式,以適應更多的裝置以及提供最大限度的移植性。初始化函式原型:

  1. boolean eglInitialize(EGLDisplay display, int[] major_minor)  
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的程式設計介面。下面的程式片段總結了上述內容。
      
  1. EGL10 egl = (EGL10)EGLContext.getEGL();   
  2.        EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);        int[] version = new int[2];   
  3.         egl.eglInitialize(dpy, version);   
  4.         int[] configSpec = {   
  5.                 EGL10.EGL_RED_SIZE,      5,   
  6.                 EGL10.EGL_GREEN_SIZE,    6,   
  7.                 EGL10.EGL_BLUE_SIZE,     5,   
  8.                 EGL10.EGL_DEPTH_SIZE,   16,   
  9.                 EGL10.EGL_NONE   
  10.         };    
  11.         EGLConfig[] configs = new EGLConfig[1];   
  12.         int[] num_config = new int[1];   
  13.         egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config);   
  14.         EGLConfig config = configs[0];   
  15.        EGLContext context = egl.eglCreateContext(dpy, config,   
  16.                 EGL10.EGL_NO_CONTEXT, null);    
  17.        EGLSurface surface = egl.eglCreateWindowSurface(dpy, config,    
  18.               sHolder, null);   
  19.        egl.eglMakeCurrent(dpy, surface, surface, context);   
  20.        GL10 gl = (GL10)context.getGL();  
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個頂點可表示為:
  1. float vertices[] = {   
  2.       -1.0f, 1.0f, 0.0f, //左上  
  3.       -1.0f, -1.0f, 0.0f, //左下  
  4.        1.0f, -1.0f, 0.0f, //右下  
  5.        1.0f, 1.0f, 0.0f,  //右上  
  6. };   
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, //右上 };
 
       為了提高效能, 需要將浮點陣列存入一個位元組緩衝中。 所以有了下面的操作:
  1. ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);   
  2. vbb.order(ByteOrder.nativeOrder());   
  3. FloatBuffer vertexBuffer = vbb.asFloatBuffer();   
  4. vertexBuffer.put(vertices);   
  5. vertexBuffer.position(0);  
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來完成。
  1. // 指定需要啟用定點陣列   
  2. gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);   
  3. // 說明啟用陣列的型別和位元組緩衝,型別為GL_FLOAT   
  4. gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);   
  5. // 不再需要時,關閉頂點陣列   
  6. gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);  
// 指定需要啟用定點陣列 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定義了渲染頂點的方向。
  1. // 設定CCW方向為“正面”,CCW即CounterClockWise,逆時針  
  2. glFrontFace(GL_CCW);    
  3. // 設定CW方向為“正面”,CW即ClockWise,順時針  
  4. glFrontFace(GL_CW);     
// 設定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_STRIP:繪製一組相連的三角形。對於奇數點n,頂點n,n+1和n+2定義了第n個三角形;對於偶數n,頂點n+1,n和n+2定義了第n個三角形,總共繪製N-2個三角形。
 
 
 
 
 
 GL_TRIANGLE_STRIP:繪製一組相連的三角形。對於奇數點n,頂點n,n+1和n+2定義了第n個三角形;對於偶數n,頂點n+1,n和n+2定義了第n個三角形,總共繪製N-2個三角形。
 
 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個三角形。
 
  主要的繪製程式:
 
  1. static private FloatBuffer vertex;//頂點對應的位元組緩衝  
  2. static private FloatBuffer normal;//法向量對應的位元組緩衝  
  3.     
  4.     float[] lightPos = new float[] {10.0f, 10.0f, 10.0f, 1.0f };//光源的座標  
  5.     private static final int STEP = 24;//  
  6. private static final float RADIUS = 1.0f;//半徑  
  7.     
  8.     protected void init(GL10 gl) {   
  9.        gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);//設定背景顏色  
  10.         gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPos, 0);   
  11.         gl.glEnable(GL10.GL_LIGHTING);//啟用光照  
  12.         gl.glEnable(GL10.GL_LIGHT0); //開啟光源  
  13.        gl.glClearDepthf(1.0f);//設定深度快取  
  14. gl.glDepthFunc(GL10.GL_LEQUAL);//設定深度快取比較函式,GL_LEQUAL表示新的畫素的深度快取值小於等於當前畫素的深度快取值時通過深度測試  
  15.        gl.glEnable(GL10.GL_DEPTH_TEST);//啟用深度快取  
  16.        gl.glEnable(GL10.GL_CULL_FACE);   
  17.        gl.glShadeModel(GL10.GL_SMOOTH);//設定陰影模式GL_SMOOTH  
  18.     }   
  19.        
  20.     protected void drawFrame(GL10 gl) {   
  21. gl.glClear(GL10.GL_COLOR_BUFFER_BIT |    
  22. GL10.GL_DEPTH_BUFFER_BIT);            
  23.        gl.glMatrixMode(GL10.GL_MODELVIEW);   
  24.        gl.glLoadIdentity();   
  25.        GLU.gluLookAt(gl, 00, 7f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);//  
  26.         drawSphere(gl, RADIUS, STEP, STEP); //繪製球形  
  27.     }  

 

  1. 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}。
  1. private static void drawSphere(GL10 gl, float radius,    
  2. int stacks, int slices) {   
  3.         vertex=allocateFloatBuffer( 46 * stacks * (slices+1) );   
  4.         normal=allocateFloatBuffer( 46 * stacks * (slices+1) );   
  5.         int i, j, triangles;   
  6.         float slicestep, stackstep;   
  7.     
  8.         stackstep = ((float)Math.PI) / stacks;   
  9.         slicestep = 2.0f * ((float)Math.PI) / slices;   
  10.         for (i = 0; i < stacks; ++i)   
  11.         {   
  12.             float a = i * stackstep;   
  13.             float b = a + stackstep;    
  14.             float s0 = (float)Math.sin(a);   
  15.             float s1 = (float)Math.sin(b);    
  16.             float c0 = (float)Math.cos(a);   
  17.             float c1 = (float)Math.cos(b);   
  18.     
  19.             float nv;   
  20.             for (j = 0; j <= slices; ++j)   
  21.             {   
  22.                 float c = j * slicestep;   
  23.                 float x = (float)Math.cos(c);   
  24.                 float y = (float)Math.sin(c);    
  25.                 nv=x * s0;   
  26.                 normal.put(nv);   
  27.                 vertex.put( nv * radius);    
  28.                 nv=y * s0;   
  29.                 normal.put(nv);   
  30.                 vertex.put( nv * radius);    
  31.                 nv=c0;   
  32.                 normal.put(nv);   
  33.                 vertex.put( nv * radius);    
  34.                 nv=x * s1;   
  35.                 normal.put(nv);   
  36.                 vertex.put( nv * radius);    
  37.                 nv=y * s1;   
  38.                 normal.put(nv);   
  39.                 vertex.put( nv * radius);    
  40.                 nv=c1;   
  41.                 normal.put(nv);   
  42.                 vertex.put( nv * radius);   
  43.             }   
  44.         }   
  45.         normal.position(0);   
  46.         vertex.position(0);   
  47.         gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertex);   
  48.         gl.glNormalPointer(GL10.GL_FLOAT, 0, normal);   
  49.         gl.glEnableClientState (GL10.GL_VERTEX_ARRAY);   
  50.         gl.glEnableClientState (GL10.GL_NORMAL_ARRAY);   
  51.         triangles = (slices + 1) * 2;   
  52.         for(i = 0; i < stacks; i++)   
  53.             gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,    
  54. i * triangles, triangles);   
  55.         gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);   
  56.         gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);   
  57.     }   
  58.     
  59.     private static FloatBuffer allocateFloatBuffer(int capacity){   
  60.         ByteBuffer vbb = ByteBuffer.allocateDirect(capacity);   
  61.         vbb.order(ByteOrder.nativeOrder());   
  62.         return vbb.asFloatBuffer();   
  63.     }  

總結     本文介紹了Ophone中利用OpenGL ES繪製圖形的基本概念和方法。OpenGL ES中還有很多其他內容,諸如紋理、光照和材質、混合、霧、蒙板、反射、3D模型的載入等。利用OpenGL ES函式可以繪製豐富的圖形應用和遊戲介面。

相關文章