Android OpenGL 顯示基本圖形及相關概念解讀

轉載請註明出處:【huachao1001的專欄:http://blog.csdn.net/huachao1001】

在上一篇文章中,我們知道了如何在Android開發一個OpenGL模型顯示。但是並沒有對具體模型數據進行顯示,只是展示一個背景顏色而已,在本章中,我們學習如何將一個模型數據顯示成一個具體的3D圖形。在Android中開發OpenGL程序非常簡單,但是卻有很多OpenGL相關概念是必須要清楚的,瞭解這些相關概念才能寫出正確的代碼,否則,你寫出來的程序可能會無緣無故崩潰,或者是畫出來的模型顯示不出來等等問題。

本文是建立在上一篇文章之上,只修改GLRender類,其他部分保持不變,如果你沒有看上一篇文章,請先移步【Android OpenGL入門】

1 模型數據

前面我們說過,一個3D模型一般是由很多三角片(或四邊形)組成,因此,首先我們需要有三角形的點數據。既然是3D模型,自然每個點座標是在三維座標系中,因此,每個點需要3個數來表示。

我們定義一個三角形,需要9個數,如果我們有float類型表示一個數,那麼定義一個三角形(三個點)如下:

 private float[] mTriangleArray = {
            0f, 1f, 0f,
            -1f, -1f, 0f,
            1f, -1f, 0f
};

此時,我們就有了一個三角形的3個點數據了。但是,OpenGL並不是對堆裏面的數據進行操作,而是在直接內存中(Direct Memory),即操作的數據需要保存到NIO裏面的Buffer對象中。而我們上面聲明的float[]對象保存在堆中,因此,需要我們將float[]對象轉爲java.nio.Buffer對象。

我們可以選擇在構造函數裏面,將float[]對象轉爲java.nio.Buffer,如下所示:

private FloatBuffer mTriangleBuffer;
public GLRenderer() {
    //先初始化buffer,數組的長度*4,因爲一個float佔4個字節
    ByteBuffer bb = ByteBuffer.allocateDirect(mTriangleArray.length * 4);
    //以本機字節順序來修改此緩衝區的字節順序
    bb.order(ByteOrder.nativeOrder());
    mTriangleBuffer = bb.asFloatBuffer();
    //將給定float[]數據從當前位置開始,依次寫入此緩衝區
    mTriangleBuffer.put(mTriangleArray);
    //設置此緩衝區的位置。如果標記已定義並且大於新的位置,則要丟棄該標記。 
    mTriangleBuffer.position(0);

}

注意,ByteBufferFloatBuffer以及IntBuffer都是繼承自抽象類java.nio.Buffer

另外,OpenGL在底層的實現是C語言,與Java默認的數據存儲字節順序可能不同,即大端小端問題。因此,爲了保險起見,在將數據傳遞給OpenGL之前,我們需要指明使用本機的存儲順序。

此時,我們順利地將float[]轉爲了FloatBuffer,後面繪製三角形的時候,直接通過成員變量mTriangleBuffer即可。

2 矩陣變換

在現實世界中,我們要觀察一個物體可以通過如下幾種方式:

  • 從不同位置去觀察。(視圖變換)
  • 移動或旋轉物體,放縮物體(雖然實際生活中不能放縮,但是計算機世界是可以的)。(模型變換)
  • 給物體拍照印成照片。可以做到“近大遠小”、裁剪只看部分等等透視效果。(投影變換)
  • 只拍攝物體的一部分,使得物體在照片中只顯示部分。(視口變換)

上面所述效果,可以在OpenGL中全部實現。有一點需要很清楚,就是OpenGL的變換其實都是通過矩陣相乘來實現的。

2.1 模型變換和視圖變換

高中我們學過相對運動,就是說,改變觀測點的位置與改變物體位置都可以達到等效的運動效果。因此,在OpenGL中,這兩種變換本質上用的是同一個函數。

在進行變換之前,我們需要聲明當前是使用哪種變換。在本節中,聲明使用模型視圖變換,而模型視圖變換在OpenGL中對應標識爲:GL10.GL_MODELVIEW。通過glMatrixMode函數來聲明:

gl.glMatrixMode(GL10.GL_MODELVIEW);

接下來你就可以對模型進行:平移、放縮、旋轉等操作啦。但是有一點值得注意的是,在此之前,你可能針對模型做了其他的操作,而我們知道,每次操作相當於一次矩陣相乘。OpenGL中,使用“當前矩陣”表示要執行的變化,爲了防止前面執行過變換“保留”在“當前矩陣”,我們需要把“當前矩陣”復位,即變爲單位矩陣(對角線上的元素全爲1),通過執行如下函數:

gl.glLoadIdentity();

此時,當前變換矩陣爲單位矩陣,後面纔可以繼續做變換,例如:


//繞(1,0,0)向量旋轉30度
gl.glRotatef(30, 1, 0, 0);

//沿x軸方向移動1個單位
gl.glTranslatef(1, 0, 0);

//x,y,z方向放縮0.1倍
gl.glScalef(0.1f, 0.1f, 0.1f);

上面的效果都是矩陣相乘實現,因此我們需要注意變換次序問題,舉個例子,假設“當前矩陣”爲單位矩陣,然後乘以一個表示旋轉的矩陣R,再乘以一個表示移動的矩陣T,最後得到的矩陣,再與每個頂點相乘。假設表示模型所以頂點的矩陣爲V,則實際就是((RT)V),由矩陣乘法結合律,((RT)V)=(R(TV)),這導致的就是,先移動再旋轉。即:

實際變換順序與代碼中的順序是相反的

上面所講的都是改變物體的位置或方向來實現“相對運動”的,如果我們不想改變物體,而是改變觀察點,可以使用如下函數

/**
* gl: GL10型變量
* eyeX,eyeY,eyeZ: 觀測點座標(相機座標)
* centerX,centerY,centerZ:觀察位置的座標
* upX,upY,upZ :相機向上方向在世界座標系中的方向(即保證看到的物體跟期望的不會顛倒)
*/
GLU.gluLookAt(gl,eyeX,eyeY,eyeZ,centerX,centerY,centerZ,upX,upY,upZ);

2.2 投影變換

投影變換就是定義一個可視空間,可視空間之外的物體是看不到的(即不會再屏幕中)。在此之前,我們的三維座標中的三個座標軸取值爲[-1,1],從現在開始,座標可以不再是從-11了!

OpenGL支持主要兩種投影變換:

  • 透視投影
  • 正投影

當然了,投影也是通過矩陣來實現的,如果想要設置爲投影變換,跟前面類似:

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

同樣的道理,glLoadIdentity()函數也需要立即調用。

通過如下函數可將當前可視空間設置爲透視投影空間:

gl.glFrustumf(left,right,bottom,top,near,far);

上面函數對應參數如下圖所示(圖片出自www.opengl.org):

透視投影變換glFrustumf

當然了,也可以通過另一個函數實現相同的效果:

 GLU.gluPerspective(gl,fovy,aspect,near,far);

上面函數對應的參數如下圖所示(圖片出自www.opengl.org):

透視投影變換gluPerspective

而對於正投影來說,相當於觀察點處於無窮遠,當然了,這是一種理想狀態,但是有時使用正投影效率可能會更高。可以通過如下函數設置正投影:

gl.glOrthof(left,right,bottom,top,near,far);

上面函數對應的參數如下圖所示(圖片出自www.opengl.org):
正投影

2.3 視口變換

我們可以選擇將圖像繪製到屏幕窗口的那個區域,一般默認是在整個窗口中繪製,但是,如果你不希望在整個窗口中繪製,而是在窗口的某個小區域中繪製,你也可以自己定製:

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    gl.glViewport(0, 0, width, height);
}

每次窗口發生變化時,我們可以設置繪製區域,即在onSurfaceChanged函數中調用glViewport函數。

3 啓用相關功能及配置

3.1 glClearColor()

設置清屏顏色,每次清屏時,使用該顏色填充整個屏幕。使用例子:

 gl.glClearColor(1.0f, 1.0f, 1.0f, 0f);

裏面參數分別代表RGBA,取值範圍爲[0,1]而不是[0,255]

3.2 glDepthFunc()

OpenGL中物體模型的每個像素都有一個深度緩存的值(在01之間,可以看成是距離),可以通過glClearDepthf函數設置默認的“當前像素”z值。在繪製時,通過將待繪製的模型像素點的深度值與“當前像素”z值進行比較,將符合條件的像素繪製出來,不符合條件的不繪製。具體的“指定條件”可取以下值:

  • GL10.GL_NEVER:永不繪製
  • GL10.GL_LESS:只繪製模型中像素點的z<當前像素z值的部分
  • GL10.GL_EQUAL:只繪製模型中像素點的z=當前像素z值的部分
  • GL10.GL_LEQUAL:只繪製模型中像素點的z<=當前像素z值的部分
  • GL10.GL_GREATER :只繪製模型中像素點的z>當前像素z值的部分
  • GL10.GL_NOTEQUAL:只繪製模型中像素點的z!=當前像素z值的部分
  • GL10.GL_GEQUAL:只繪製模型中像素點的z>=當前像素z值的部分
  • GL10.GL_ALWAYS:總是繪製

通過目標像素與當前像素在z方向上值大小的比較是否滿足參數指定的條件,來決定在深度(z方向)上是否繪製該目標像素。

注意, 該函數只有啓用“深度測試”時纔有效,通過glEnable(GL_DEPTH_TEST)開啓深度測試以及glDisable(GL_DEPTH_TEST)關閉深度測試。

例子:

 gl.glDepthFunc(GL10.GL_LEQUAL);

3.3 glClearDepthf()

給深度緩存設定默認值。

緩存中的每個像素的深度值默認都是這個, 假設在 gl.glDepthFunc(GL10.GL_LEQUAL);前提下:

  • 如果指定“當前像素值”爲1時,我們知道,一個模型深度值取值和範圍爲[0,1]。這個時候你往裏面畫一個物體, 由於物體的每個像素的深度值都小於等於1, 所以整個物體都被顯示了出來。
  • 如果指定“當前像素值”爲0, 物體的每個像素的深度值都大於等於0, 所以整個物體都不可見。
    如果指定“當前像素值”爲0.5, 那麼物體就只有深度小於等於0.5的那部分纔是可見的

使用例子:

gl.glClearDepthf(1.0f);

3.3 glEnable(),glDisable()

glEnable()啓用相關功能,glDisable()關閉相關功能。

比如:

//啓用深度測試
gl.glEnable(GL10.GL_DEPTH_TEST);
//關閉深度測試
gl.glDisable(GL10.GL_DEPTH_TEST)
//開啓燈照效果
gl.glEnable(GL10.GL_LIGHTING);
// 啓用光源0
gl.glEnable(GL10.GL_LIGHT0);
// 啓用顏色追蹤
gl.glEnable(GL10.GL_COLOR_MATERIAL);

3.5 glHint()

如果OpenGL在某些地方不能有效執行是,給他指定其他操作。

函數原型爲:

void glHint(GLenum target,GLenum mod)

其中,target:指定所控制行爲的符號常量,可以是以下值(引自【OpenGL函數思考-glHint 】):

  • GL_FOG_HINT:指定霧化計算的精度。如果OpenGL實現不能有效的支持每個像素的霧化計算,則GL_DONT_CAREGL_FASTEST霧化效果中每個定點的計算。
  • GL_LINE_SMOOTH_HINT:指定反走樣線段的採樣質量。如果應用較大的濾波函數,GL_NICEST在光柵化期間可以生成更多的像素段。
  • GL_PERSPECTIVE_CORRECTION_HINT:指定顏色和紋理座標的差值質量。如果OpenGL不能有效的支持透視修正參數差值,那麼GL_DONT_CAREGL_FASTEST可以執行顏色、紋理座標的簡單線性差值計算。
  • GL_POINT_SMOOTH_HINT:指定反走樣點的採樣質量,如果應用較大的濾波函數,GL_NICEST在光柵化期間可以生成更多的像素段。
  • GL_POLYGON_SMOOTH_HINT:指定反走樣多邊形的採樣質量,如果應用較大的濾波函數,GL_NICEST在光柵化期間可以生成更多的像素段。

mod:指定所採取行爲的符號常量,可以是以下值:

  • GL_FASTEST:選擇速度最快選項。
  • GL_NICEST:選擇最高質量選項。
  • GL_DONT_CARE:對選項不做考慮。

例子:

gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);

3.6 glEnableClientState()

當我們需要啓用頂點數組(保存每個頂點的座標數據)、頂點顏色數組(保存每個頂點的顏色)等等,就要通過glEnableClientState()函數來開啓:

//以下兩步爲繪製顏色與頂點前必做操作
// 允許設置頂點
//GL10.GL_VERTEX_ARRAY頂點數組
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// 允許設置顏色
//GL10.GL_COLOR_ARRAY顏色數組
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

3.7 glShadeModel()

設置着色器模式,有如下兩個選擇:

  • GL10.GL_FLAT
  • GL10.GL_SMOOTH(默認)

如果爲每個頂點指定了頂點的顏色,此時:

  • GL_SMOOTH:根據頂點的不同顏色,最終以漸變的形式填充圖形。
  • GL_FLAT:假設有n個三角片,則取最後n個頂點的顏色填充着n個三角片。

使用例子:

gl.glShadeModel(GL10.GL_SMOOTH);

4 開始繪製

前面講了很多概念,但是其實都是非常值得學習的。有了這些基礎,我們才能理解如何寫OpenGL,從上一篇文章中我們知道,開發OpenGL大部分工作都是在Renderer類上面,我直接粘Renderder代碼:

/**
 * Package com.hc.opengl
 * Created by HuaChao on 2016/7/28.
 */
public class GLRenderer implements GLSurfaceView.Renderer {
    private float[] mTriangleArray = {
            0f, 1f, 0f,
            -1f, -1f, 0f,
            1f, -1f, 0f
    };
    //三角形各頂點顏色(三個頂點)
    private float[] mColor = new float[]{
            1, 1, 0, 1,
            0, 1, 1, 1,
            1, 0, 1, 1
    };
    private FloatBuffer mTriangleBuffer;
    private FloatBuffer mColorBuffer;


    public GLRenderer() {
        //點相關
        //先初始化buffer,數組的長度*4,因爲一個float佔4個字節
        ByteBuffer bb = ByteBuffer.allocateDirect(mTriangleArray.length * 4);
        //以本機字節順序來修改此緩衝區的字節順序
        bb.order(ByteOrder.nativeOrder());
        mTriangleBuffer = bb.asFloatBuffer();
        //將給定float[]數據從當前位置開始,依次寫入此緩衝區
        mTriangleBuffer.put(mTriangleArray);
        //設置此緩衝區的位置。如果標記已定義並且大於新的位置,則要丟棄該標記。
        mTriangleBuffer.position(0);


        //顏色相關
        ByteBuffer bb2 = ByteBuffer.allocateDirect(mColor.length * 4);
        bb2.order(ByteOrder.nativeOrder());
        mColorBuffer = bb2.asFloatBuffer();
        mColorBuffer.put(mColor);
        mColorBuffer.position(0);
    }

    @Override
    public void onDrawFrame(GL10 gl) {


        // 清除屏幕和深度緩存
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        // 重置當前的模型觀察矩陣
        gl.glLoadIdentity();

        // 允許設置頂點
        //GL10.GL_VERTEX_ARRAY頂點數組
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        // 允許設置顏色
        //GL10.GL_COLOR_ARRAY顏色數組
        gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

        //將三角形在z軸上移動
        gl.glTranslatef(0f, 0.0f, -2.0f);

        // 設置三角形
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mTriangleBuffer);
        // 設置三角形顏色
        gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);
        // 繪製三角形
        gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);


        // 取消顏色設置
        gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
        // 取消頂點設置
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

        //繪製結束
        gl.glFinish();

    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        float ratio = (float) width / height;
        // 設置OpenGL場景的大小,(0,0)表示窗口內部視口的左下角,(w,h)指定了視口的大小
        gl.glViewport(0, 0, width, height);
        // 設置投影矩陣
        gl.glMatrixMode(GL10.GL_PROJECTION);
        // 重置投影矩陣
        gl.glLoadIdentity();
        // 設置視口的大小
        gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
        //以下兩句聲明,以後所有的變換都是針對模型(即我們繪製的圖形)
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();

    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // 設置白色爲清屏
        gl.glClearColor(1, 1, 1, 1);

    }
}

效果如下:

顯示效果

5 幾個重要的函數

5.1 glVertexPointer()

其實就是設置一個指針,這個指針指向頂點數組,後面繪製三角形(或矩形)根據這裏指定的頂點數組來讀取數據。
函數原型如下:

void glVertexPointer(int size,int type,int stride,Buffer pointer)

其中:

  • size: 每個頂點有幾個數值描述。必須是2,3 ,4 之一。
  • type: 數組中每個頂點的座標類型。取值:GL_BYTE,GL_SHORT, GL_FIXED, GL_FLOAT
  • stride:數組中每個頂點間的間隔,步長(字節位移)。取值若爲0,表示數組是連續的
  • pointer:即存儲頂點的Buffer

5.2 glColorPointer()

跟上面類似,只是設定指向顏色數組的指針。
函數原型:

void glColorPointer(
        int size,
        int type,
        int stride,
        java.nio.Buffer pointer
    );
  • size: 每種顏色組件的數量。 值必須爲 3 或 4。
  • type: 顏色數組中的每個顏色分量的數據類型。 使用下列常量指定可接受的數據類型:GL_BYTE GL_UNSIGNED_BYTEGL_SHORT GL_UNSIGNED_SHORTGL_INT GL_UNSIGNED_INTGL_FLOAT,或 GL_DOUBLE
  • stride:連續顏色之間的字節偏移量。 當偏移量爲0時,表示數據是連續的。
  • pointer:即顏色的Buffer

5.3 glDrawArrays()

繪製數組裏面所有點構成的各個三角片。

函數原型:

void glDrawArrays(
    int mode,
    int first,
    int count
);

其中:

  • mode:有三種取值
    • GL_TRIANGLES:每三個頂之間繪製三角形,之間不連接
    • GL_TRIANGLE_FAN:以V0 V1 V2,V0 V2 V3,V0 V3 V4,……的形式繪製三角形
    • GL_TRIANGLE_STRIP:順序在每三個頂點之間均繪製三角形。這個方法可以保證從相同的方向上所有三角形均被繪製。以V0 V1 V2 ,V1 V2 V3,V2 V3 V4,……的形式繪製三角形
  • first:從數組緩存中的哪一位開始繪製,一般都定義爲0
  • count:頂點的數量

相關資料:

【三維變換glMatrixMode】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章