Android平台下OpenGL初步


Android OpenGL ES 開發教程 從入門到精通 http://blog.csdn.net/zhoudailiang/article/details/50176143

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

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!至此,我們的第一個三角形就畫出來了,來看看效果吧。

 

OpenGL ES之glFrustumf設置投影視角詳解 http://blog.csdn.net/wangyuchun_799/article/details/7841429

-(void)setClipping
{
float aspectRatio;
const float zNear = 0.1; //1
const float zFar = 1000; //2
const float fieldOfView = 60.0; //3
GLfloat size;
CGRect frame = [[UIScreen mainScreen] bounds]; //4

aspectRatio=(float)frame.size.width/(float)frame.size.height; //5

 

//Set the OpenGL projection matrix.

glMatrixMode(GL_PROJECTION); //6
glLoadIdentity();
size = zNear * tanf(GLKMathDegreesToRadians (fieldOfView) / 2.0); //7
glFrustumf(-size, size, -size /aspectRatio, size /aspectRatio, zNear, zFar); //8
glViewport(0, 0, frame.size.width, frame.size.height); //9
//Make the OpenGL ModelView matrix the default.
glMatrixMode(GL_MODELVIEW); //10
}

glFrustumf中文API文檔參考OpenGL ES之glFrustum函數

        第1、2行指定了近裁面和遠裁面的距離。這兩個值的意思是,任何遠於1000或近於0.1的對象都將被過濾掉。你可能會問一千什么?就是一千!單位看你自己設想。你可以把它想象成光年,或者英尺,都無所謂。不信的話你自己隨便設置一下試試。

        第3行設定視角為60度。

        第4行獲取屏幕的尺寸大小。

        第5行根據屏幕的尺寸計算最終屏幕的縱橫比例。它的高度和寬度的值決定了相對高度的視域(FOV),如果將其翻轉的話,將變成相對於寬度的視域。如果我們要設置一個60度視域,就像一個寬角度鏡頭,那么它將基於窗口的高度而非寬度。當渲染到一個非正方形的屏幕上時尤為重要。

        由於glfrustumf影響的是投影矩陣,所以我們需要確認將當前矩陣從模型視圖矩陣設置成投影矩陣。第6行就是要做這個滴。

第7行計算錐形視角的左右上下的限制值。你可以把它想象成3D空間中的虛擬窗口。原點在屏幕中央,所以x軸和y軸的值都是從-size到+size。這就是為什么會有GLKMathDegreesToRadians (fieldOfView) / 2.0將窗口分為兩部分——視角的角度是從-30度到+30度的。乘以zNear就可以計算出近剪裁面在坐標軸各個方向上的大小。這就是正切函數的作用了,眼睛在z軸上,到原點的距離是zNear,視域被z軸分為上下兩部分各為30度,所以就可以明白size就是近剪裁面在x和y軸上的長度。

    第8行將計算的左右上下以及近剪裁面和遠剪裁面的值傳進glFrustumf函數。這里下邊和上邊的值都除以了aspectRatio(屏幕寬高比),而左右邊沒有,這是因為調用glLoadIdentity函數標准化投影矩陣的時候將所有的頂點數據都標准化到了-1~1的范圍內,屏幕寬度和高度實際大小不一樣,但都被標准化成了1。所以如果左右值和上下值一樣的話得到的就是一個寬度比較大而高度比較小的長方形,而不是預期的正方形,所以左右值不變,而上下值要除以寬高比。

        第9行用來設置視口,一般為屏幕的大小。不過你可以根據需要來設置坐標和寬度、高度。

        第10行為將當前的矩陣從投影矩陣設置為模型視圖矩陣。

      

        由於設置投影視角的方法基本上是固定的,只不過需要調整一下近剪裁面和遠剪裁面以及視角的大小,所以可以將setClipping函數進一步封裝為:

-(void)setClippingWithNear:(const float)zNear far:(const float)zFar angle:(const float) fieldOfView
{

float aspectRatio;
GLfloat size;
CGRect frame = [[UIScreen mainScreen] bounds]; //4

aspectRatio=(float)frame.size.width/(float)frame.size.height; //5

 

//Set the OpenGL projection matrix.

glMatrixMode(GL_PROJECTION); //6
glLoadIdentity();
size = zNear * tanf(GLKMathDegreesToRadians (fieldOfView) / 2.0); //7
glFrustumf(-size, size, -size /aspectRatio, size /aspectRatio, zNear, zFar); //8
glViewport(0, 0, frame.size.width, frame.size.height); //9
//Make the OpenGL ModelView matrix the default.
glMatrixMode(GL_MODELVIEW); //10

}

 初識openGL---openGL學習筆記(一) http://blog.csdn.net/a358333644/article/details/50732170

openGL:

openGL是用C語言實現的,而我們作為安卓開發者,嚴格來講,我們使用的是es,也就是openGL es,原因無非就像數據庫一樣,安卓上使用的數據庫永遠不可能是MySQL之類的,因為終端講究輕量。

接下來我們來創建第一個openGL項目:

首先我們來了解一下GLSurfaceView這個類,這個類相當於是在屏幕上的一個模板,如下圖。

所以,創建一個類繼承GLSurfaceView之后,我們就可以new出GLSurfaceView類的對象,調用GLSurfaceView類的對象的setRenderer()方法,就可以對GLSurfaceView進行渲染,而我們的主要工作是,如何進行渲染。

class MyGLSurfaceView extends GLSurfaceView{

    public MyGLSurfaceView(Context context) {
        super(context);
    }

    public MyGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    }

 

 

那么就要自定義一個渲染器:new一個類實現Renderer,需重寫三個方法:onSurfaceCreated,onSurfaceChanged和onDrawFrame。

 

//自定義渲染器
class MyRenderer implements GLSurfaceView.Renderer{
    //表層創建時調用
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //設置清屏色
        gl.glClearColor(0, 0, 0, 1);
        //啟用頂點緩沖區
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

    }
    //當表層size改變時調用
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {

        //設置視口,輸出畫面的區域。
        gl.glViewport(0,0,width,height);
        //寬高比
        float ratio = (float)width/(float)height;
        //矩陣模式,投影矩陣,openGL基於狀態機。
        gl.glMatrixMode( GL10.GL_PROJECTION );
        //加載單位矩陣
        gl.glLoadIdentity();
        //設置平截頭體
        gl.glFrustumf(-1f,1f,-ratio,ratio,3,7); //-1f,1f為平截頭體左右大小比,-ratio,ratio為平截頭體底面和頂面,3,7為近平面和原平面距離

    }
    //繪圖調用
    @Override
    public void onDrawFrame(GL10 gl) {
        //清除顏色緩沖區。
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

        //模型視圖矩陣。
        gl.glMatrixMode( GL10.GL_MODELVIEW );
        //加載單位矩陣。
        gl.glLoadIdentity();
        //0,0,5:放置眼球的坐標。
        //0,0,0:眼球觀察的中心點坐標。
        //0,1,0:指定眼球向上的向量。
        GLU.gluLookAt(gl,0,0,5,0,0,0,0,1,0);

        /**
         * 畫三角形
         */

        //三角形頂點坐標
        float [] coords = {
                0f,0.5f,0f,
                -0.5f,-0.5f,0f,
                0.5f,-0.5f,0f
        };
        //分配字節緩存空間,存放頂點坐標數據。
        ByteBuffer ibb = ByteBuffer.allocateDirect(coords.length * 4);
        //設置順序(本地順序)。
        ibb.order(ByteOrder.nativeOrder());
        //放置頂點數組。
        FloatBuffer fbb = ibb.asFloatBuffer();
        fbb.put(coords);
        //定位指針位置,從該位置開始讀取頂點數據。
        ibb.position(0);

        //設置繪圖顏色:紅色。
        gl.glColor4f(1f,0,0,1f);
        //3→三維點,使用三個坐標表示一個點。
        //每個點的數據類型。
        //0→跨度。
        //ibb指定緩沖區。
        gl.glVertexPointer(3,GL10.GL_FLOAT,0,ibb);
        //畫數組
        gl.glDrawArrays(GL10.GL_TRIANGLES,0,3);


    }
}

顯而易見,onSurfaceCreated和onSurfaceChanged都是在做初始化工作,關鍵的是onDrawFrame方法:

 

需要注意的是,onSurfaceChanged中設置的平截頭體,所謂平截頭體就是一個類似棱錐的區域,如下圖:

 


 

最左邊的眼睛通過中間牆上的孔,可以看得見與孔相同比例的空間,那么牆上的孔到牆后景物中間的四棱錐,就是一個平截頭體。牆上的孔叫近平面,后方景物叫遠平面。因為光是直射的,所以,我們只要指定好近平面的比例,以及圖中兩段紅線的比例,那么就能確定遠平面,繼而確定平截頭體。

 

確定了平截頭體之后,就要關注onDrawFrame方法,類似於拍照的過程,我確定了要拍什么之后,還是要確定拍照時候要確定相機的位置,相機的朝向,相機的反正。這個三個屬性就對應了程序中的GLU.gluLookAt()方法。詳細參數見代碼注釋。

 

通過這幾步之后,還是不能拍照,因為我們還有一些參數沒有設置,比如繪圖顏色。上例程序中是畫了一個三角形,那么還需指定三角形的三點坐標等等,具體細節代碼中注釋的很詳細,不再贅述。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM