1. 程式人生 > >Android平臺下OpenGL圖形編程

Android平臺下OpenGL圖形編程

alloc arch jsb config ble _array itl conf graphics

http://blog.csdn.net/jason0539/article/details/9164885

https://developer.android.com/guide/topics/graphics/opengl.html

http://www.guidebee.info/wordpress/archives/category/opengl-es

http://blog.csdn.net/mapdigit/article/details/7526556

本文只關註於如何一步步實現在Android平臺下運用OpenGl。

1、GLSurfaceView

GLSurfaceView是Android應用程序中實現OpenGl畫圖的重要組成部分。GLSurfaceView中封裝了一個Surface。而android平臺下關於圖像的現實,差不多都是由Surface來實現的。

2、Renderer

有了GLSurfaceView之後,就相當於我們有了畫圖的紙。現在我們所需要做的就是如何在這張紙上畫圖。所以我們需要一支筆。
Renderer是GLSurfaceView的內部靜態接口,它就相當於在此GLSurfaceView上作畫的筆。我們通過實現這個接口來“作畫”。最後通過GLSurfaceView的setRenderer(GLSurfaceView.Renderer renderer)方法,就可以將紙筆關聯起來了。


實現Renderer需要實現它的三個接口:onSurfaceCreated(GL10 gl, EGLConfig config)、 onSurfaceChanged(GL10 gl, int width, int height)、onDrawFrame(GL10 gl)。下面就這三個接口的具體意義做個簡單的介紹。

2.1、onSurfaceCreated

此方法看名字就知道它是在Surface創建的時候被調用的。因此我們可以在這個函數的實現中做一些初始化的工作。例如取出文房四寶、鋪好畫布、調好顏料之類的。它的函數原
型如下:
public abstract void onSurfaceCreated (GL10 gl, EGLConfig config)
第二個參數在文檔中沒有關於它的任何public方法和域。因此我們可以不用管它。
第一個參數非常重要。如果說Renderer是畫筆的話,那麽這個gl參數,就可以說是我們的手了。如何操作這支畫筆,都是它說了算!所以我們絕大部分時候都是通過這個叫做gl的手來指揮Renderer畫圖的。

2.2 onSurfaceChanged

當GLSurfaceView大小改變時,對應的Surface大小也會改變。值得註意的是,在Surface剛創建的時候,它的size其實是0,也就是說在畫第一次圖之前它也會被調用一次的。(而且對於很多時候,Surface的大小是不會改變的,那麽此函數就只在創建之初被調用一次而已)

原型如下:
public abstract void onSurfaceChanged (GL10 gl, int width, int height)
同樣的,畫圖的手是必需的。
另外值得註意的是,它告訴了我們這張紙有多高多寬。這點很重要。因為在onSurfaceCreated的時候我們是不知道紙的寬高的,所以有一些和長寬相關的初始化工作還得在此函數中來做。

2.3 onDrawFrame

好了,我們的初始化工作做得差不多了,那麽現在就是該真刀真槍畫畫的時候了!此函數就是真正給你畫畫用的。每調用一次就畫一幅圖。可能的疑問是這個函數什麽時候會被調
用呢?最開始的時候肯定是會被調用的。以後會有兩種模式供你選擇:

  1. RENDERMODE_CONTINUOUSLY
  2. RENDERMODE_WHEN_DIRTY

第一種模式(RENDERMODE_CONTINUOUSLY):
連續不斷的刷,畫完一幅圖馬上又畫下一幅。這種模式很明顯是用來畫動畫的;


第二種模式(RENDERMODE_WHEN_DIRTY):
只有在需要重畫的時候才畫下一幅。這種模式就比較節約CPU和GPU一些,適合用來畫不經常需要刷新的情況。多說一句,系統如何知道需要重畫了呢?當然是你要告訴它……
調用GLSurfaceView的requestRender ()方法,告訴它,你臟了。


這兩種模式在什麽地方設置呢? GLSurfaceView的setRenderMode(int renderMode)方法。可以供你設置你需要的刷新模式。


還是來看看這個函數的原型吧: public abstract void onDrawFrame (GL10 gl) 很簡單,只有手。

3、 Android下OpenGL繪圖基本流程:

我們從畫一個三角形開始說起:

3.1 MyRender

經過前面的介紹,我們應該知道現在需要做的事,就是寫好Renderer的三個接口方法。
我們需要重新寫一個類實現它,然後重寫這三個方法。
class MyRender implements GLSurfaceView.Renderer
OK,筆已經拿好了。“鋪好紙”是非常關鍵的一步。雖然我們說GLSurfaceView就是我們作圖的紙,但是“鋪”好這張紙,卻也非常的重要。

下面我們重點講下,這紙該怎麽鋪。OpenGL這張紙可不是一般的紙啊。最起碼,它是三維的。然而實際的顯示屏幕卻是一個平面。所以這紙還不好“鋪”。


首先,不一定整張紙都用來作畫吧,Surface不一定全部都用上(當然,一般情況下我們是全部用上的)。那麽我們需要計劃好,哪部分區域用來作畫。
gl.glViewport(0, 0, width, height);
根據這裏的參數width和height,我們可以知道這個寬高需要在onSurfaceChanged裏得知。


然後,這一步很關鍵。如何在平面上畫三維坐標的點或圖形呢?OpenGL有一個坐標系,如下圖:

技術分享


我們需要將這個坐標系和我們的GLSurfaceView裏的Surface做一個映射關系。
glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glFrustumf(-400, 400, -240, 240, 0.3f, 100);

glMatrixMode(GL10.GL_PROJECTION); 是說我們現在改變的是坐標系與Surface的映射關系(投影矩陣)。

下一句 gl.glLoadIdentity(); 是將以前的改變都清掉(之前對投影矩陣的任何改變)。

glFrustumf (float left, float right, float bottom, float top, float zNear, float zFar) 這個函數非常Powerful。它實現了Surface和坐標系之間的映射關系。它是以透視投影的方式來進行映射的。


透視投影的意思見下圖:

技術分享


映射說明:
1、 前面一個矩形表示的是我們平面作圖的範圍。即Surface的作圖範圍。它有四條邊,
我們將它們暫時命名edgeLeft、edgeRight、edgeTop、edgeBottom。
2、 glFrustumf 參數中的left、right、bottom、top指的是作圖範圍的四條edge在OpenGL x = -400的位置。同理top、right、bottom的值表示的是edgeRight、edgeTop、edgeBottom這幾條邊在坐標系中的位置。
3、上面第二點定出了作圖範圍的x和y的範圍。那麽對於3D的OpenGL這張紙來說,我們還需要定出z的範圍。首先,要想象一下,相機或者眼睛在坐標系的哪個位置?
默認的眼睛的位置在OpenGL坐標的原點處(0,0,0)。視線方向是平行於Z軸往裏看。
near表示的是眼睛到作圖平面的距離(絕對值哦!),far表示眼睛到最遠可見處的平面範圍。於是,默認情況下z的作圖範圍就是在-near到-far的位置。
4、好了,我們已經確定好x、y、z方向的作圖範圍了。回過頭來,就可以發現,這張“立體”的紙,是一個方椎體切去頭部的平截頭體。我們所畫的物體坐標落在這個區域範圍內的部分將可以被我們看到(即在屏幕裏畫出來)。OK,至此,我們把紙終於鋪好了。

glMatrixMode函數的選項(參數)有後面三種:GL_PROJECTION,GL_MODELVIEW和GL_TEXTURE;

  • GL_PROJECTION,是投影的意思,就是要對投影相關進行操作,也就是把物體投影到一個平面上,就像我們照相一樣,把3維物體投到2維的平面上。這樣,接下來的語句可以是跟透視相關的函數,比如glFrustum()或gluPerspective();
  • GL_MODELVIEW,是對模型視景的操作,接下來的語句描繪一個以模型為基礎的適應,這樣來設置參數,接下來用到的就是像gluLookAt()這樣的函數;
  • GL_TEXTURE,就是對紋理相關進行操作;

順便說下,OpenGL裏面的操作,很多是基於對矩陣的操作的,比如位移,旋轉,縮放,所以,
這裏其實說的規範一點就是glMatrixMode是用來指定哪一個矩陣是當前矩陣,而它的參數代表要操作的目標:

  • GL_PROJECTION是對投影矩陣操作;
  • GL_MODELVIEW是對模型視景矩陣操作;
  • GL_TEXTURE是對紋理矩陣進行隨後的操作;

3.2 畫圖之前先構圖


Android 的 OpenGL 作圖,不同於一般的作圖,這點我們不得不感慨。它將數據和畫完全分開來。例如,我們要畫一個三角形。很顯然,三角形有三個點。我們在畫圖之前首先要構圖,比如每個點在哪個地方。我們將這些數據放在一個一個數組緩沖區中,放好這些數據之後,再統一一起畫出來。

下面,主要講下,如何將頂點數據和顏色數據放入符合 Android OpenGL 的數組緩沖區中。

首先我們要明白的是,OpenGL 是一個非常底層的畫圖接口,它所使用的緩沖區存儲結構是和我們的 Java 程序中不相同的。Java 是大端字節序(BigEdian),而 OpenGL 所需要的數據是小端字節序(LittleEdian)。所以,我們在將 Java 的緩沖區轉化為 OpenGL 可用的緩沖區時需要作一些工作。


byte 數據緩沖區

不管我們的數據是整型的還是浮點型的,為了完成 BigEdian 到 LittleEdian 的轉換,我們都首先需要一個 ByteBuffer。我們通過它來得到一個可以改變字節序的緩沖區。

ByteBuffer mBuffer = ByteBuffer.allocateDirect(pointCount*dimension*4);
mBuffer.order(ByteOrder.nativeOrder());

註意,我們應該用“allocateDirect”來分配內存空間,因為這樣才可以 order 排序。最後我們就可以通過:
resultBuffer = mBuffer.asFloatBuffer();
resultBuffer = mBuffer.asIntBuffer();
將字節緩沖區轉化為整型或者浮點型緩沖區了。

3.3 終於畫圖了!


有了前面所有的準備工作之後,有一個好消息可以告訴你,我們終於可以畫圖了!而且,有了前面這麽多工作,真正畫圖的工作其實比較簡單。


3.3.1、清理好你的紙

前面我們說過了,在畫圖之前,一定要把紙給清理幹凈嘍:

gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

另外,之前我們在映射坐標系的時候,用了glMatrixMode(GL10.GL_PROJECTION);來指定改變的是投影矩陣。那麽現在要畫圖了,所以我們需要指定改變的是“視圖矩陣”:

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();

3.3.2、啟用數組

我們的前面說過,畫圖的數據都放在數組緩沖區裏,最後再一起傳過來作畫。那麽我們首先要告訴 OpenGL,我們需要用到哪些數組。例如我們需要頂點數組和顏色數組:

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

3.3.3、指定數組數據

我們前面已經構造好了我們的數據緩沖區,floatBuffer(或 IntBuffer)。現在我們只需要將這個數據緩沖區和對應的功能綁定起來就好了:

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, VertexBuffer);
gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer);

這兩句話分別綁定了頂點數據數組和顏色數據數組。其中第一個參數表示的是每個點有幾個坐標。例如頂點,有 x、y、z值,所以是 3;而顏色是 r、g、b、a 值,所以是 4。

3.3.4、畫圖!

gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);

第一個參數指明了畫圖的類型——三角形(android 似乎只支持畫三角形、點、線,不支持畫多邊形)。後面兩個參數指明,從哪個頂點開始畫,畫多少個頂點。

OK!至此,我們的第一個三角形就畫出來了,來看看效果吧。

技術分享

Android平臺下OpenGL圖形編程