ref:php
Jayway Team Blog中OpenGL ES簡明開發教程
https://blog.jayway.com/tag/opengl-es/html
OpenGL ES 開發教程
http://www.guidebee.info/wordpress/%E6%95%99%E7%A8%8B/android-opengl-es-%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8Bjava
http://blog.csdn.net/jason0539/article/details/9164885android
https://developer.android.com/guide/topics/graphics/opengl.htmlgit
http://www.guidebee.info/wordpress/archives/category/opengl-esapi
http://blog.csdn.net/mapdigit/article/details/7526556
http://blog.csdn.net/column/details/apidemoopengl.html數組
本文只關注於如何一步步實如今Android平臺下運用OpenGl。 ide
GLSurfaceView是Android應用程序中實現OpenGl畫圖的重要組成部分。GLSurfaceView中封裝了一個Surface。而android平臺下關於圖像的現實,差很少都是由Surface來實現的。wordpress
有了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)。下面就這三個接口的具體意義作個簡單的介紹。
此方法看名字就知道它是在Surface建立的時候被調用的。所以咱們能夠在這個函數的實現中作一些初始化的工做。例如取出文房四寶、鋪好畫布、調好顏料之類的。它的函數原
型以下:
public abstract void onSurfaceCreated (GL10 gl, EGLConfig config)
第二個參數在文檔中沒有關於它的任何public方法和域。所以咱們能夠不用管它。
第一個參數很是重要。若是說Renderer是畫筆的話,那麼這個gl參數,就能夠說是咱們的手了。如何操做這支畫筆,都是它說了算!因此咱們絕大部分時候都是經過這個叫作gl的手來指揮Renderer畫圖的。
當GLSurfaceView大小改變時,對應的Surface大小也會改變。值得注意的是,在Surface剛建立的時候,它的size實際上是0,也就是說在畫第一次圖以前它也會被調用一次的。(並且對於不少時候,Surface的大小是不會改變的,那麼此函數就只在建立之初被調用一次而已)
原型以下:
public abstract void onSurfaceChanged (GL10 gl, int width, int height)
一樣的,畫圖的手是必需的。
另外值得注意的是,它告訴了咱們這張紙有多高多寬。這點很重要。由於在onSurfaceCreated的時候咱們是不知道紙的寬高的,因此有一些和長寬相關的初始化工做還得在此函數中來作。
好了,咱們的初始化工做作得差很少了,那麼如今就是該真刀真槍畫畫的時候了!此函數就是真正給你畫畫用的。每調用一次就畫一幅圖。可能的疑問是這個函數何時會被調
用呢?最開始的時候確定是會被調用的。之後會有兩種模式供你選擇:
第一種模式(RENDERMODE_CONTINUOUSLY):
接二連三的刷,畫完一幅圖立刻又畫下一幅。這種模式很明顯是用來畫動畫的;
第二種模式(RENDERMODE_WHEN_DIRTY):
只有在須要重畫的時候才畫下一幅。這種模式就比較節約CPU和GPU一些,適合用來畫不常常須要刷新的狀況。多說一句,系統如何知道須要重畫了呢?固然是你要告訴它……
調用GLSurfaceView的requestRender ()方法,告訴它,你髒了。
這兩種模式在什麼地方設置呢? GLSurfaceView的setRenderMode(int renderMode)方法。能夠供你設置你須要的刷新模式。
仍是來看看這個函數的原型吧: public abstract void onDrawFrame (GL10 gl) 很簡單,只有手。
咱們從畫一個三角形開始提及:
通過前面的介紹,咱們應該知道如今須要作的事,就是寫好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和座標系之間的映射關係。它是以透視投影的方式來進行映射的。
透視投影的意思見下圖:
映射說明:
一、 前面一個矩形表示的是咱們平面做圖的範圍。即Surface的做圖範圍。它有四條邊,
咱們將它們暫時命名edgeLeft、edgeRight、edgeTop、edgeBottom。
二、 glFrustumf 參數中的left、right、bottom、top指的是做圖範圍的四條edge在OpenGL x = -400的位置。同理top、right、bottom的值表示的是edgeRight、edgeTop、edgeBottom這幾條邊在座標系中的位置。
三、上面第二點定出了做圖範圍的x和y的範圍。那麼對於3D的OpenGL這張紙來講,咱們還須要定出z的範圍。首先,要想象一下,相機或者眼睛在座標系的哪一個位置?
默認的眼睛的位置在OpenGL座標的原點處(0,0,0)。視線方向是平行於Z軸往裏看。
near表示的是眼睛到做圖平面的距離(絕對值哦!),far表示眼睛到最遠可見處的平面範圍。因而,默認狀況下z的做圖範圍就是在-near到-far的位置。
四、好了,咱們已經肯定好x、y、z方向的做圖範圍了。回過頭來,就能夠發現,這張「立體」的紙,是一個方椎體切去頭部的平截頭體。咱們所畫的物體座標落在這個區域範圍內的部分將能夠被咱們看到(即在屏幕裏畫出來)。OK,至此,咱們把紙終於鋪好了。
glMatrixMode函數的選項(參數)有後面三種:GL_PROJECTION,GL_MODELVIEW和GL_TEXTURE;
順便說下,OpenGL裏面的操做,不少是基於對矩陣的操做的,好比位移,旋轉,縮放,因此,
這裏其實說的規範一點就是glMatrixMode是用來指定哪個矩陣是當前矩陣,而它的參數表明要操做的目標:
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.一、清理好你的紙
前面咱們說過了,在畫圖以前,必定要把紙給清理乾淨嘍:
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
另外,以前咱們在映射座標系的時候,用了glMatrixMode(GL10.GL_PROJECTION);來指定改變的是投影矩陣。那麼如今要畫圖了,因此咱們須要指定改變的是「視圖矩陣」:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
3.3.二、啓用數組
咱們的前面說過,畫圖的數據都放在數組緩衝區裏,最後再一塊兒傳過來做畫。那麼咱們首先要告訴 OpenGL,咱們須要用到哪些數組。例如咱們須要頂點數組和顏色數組:
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
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.四、畫圖!
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);
第一個參數指明瞭畫圖的類型——三角形(android 彷佛只支持畫三角形、點、線,不支持畫多邊形)。後面兩個參數指明,從哪一個頂點開始畫,畫多少個頂點。
OK!至此,咱們的第一個三角形就畫出來了,來看看效果吧。