天天看點

Android平台下OpenGL初步1、GLSurfaceView2、Renderer3、 Android下OpenGL繪圖基本流程:

轉自網上,網上沒找到出處,隻看到一些論壇中有這篇文章,組織的有點混亂,這篇文章感覺講的挺好的。

http://www.bangchui.org/read.php?tid=7572&page=1

本文隻關注于如何一步步實作在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有一個坐标系,如下圖:

Android平台下OpenGL初步1、GLSurfaceView2、Renderer3、 Android下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和坐标系之間的映射關系。它是以透視投影的方式來進行映射的。

透視投影的意思見下圖:

Android平台下OpenGL初步1、GLSurfaceView2、Renderer3、 Android下OpenGL繪圖基本流程:

映射說明:

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初步1、GLSurfaceView2、Renderer3、 Android下OpenGL繪圖基本流程: