1. 程式人生 > >Android OpenGL ES2.0(一):詳細講解如何繪製一個三角形

Android OpenGL ES2.0(一):詳細講解如何繪製一個三角形

一、Android OpenGL ES2.0簡介

1. 什麼是OpenGL?

OpenGL(全寫Open Graphics Library)是指定義了一個跨程式語言、跨平臺的程式設計介面規格的專業的圖形程式介面。它用於三維影象(二維的亦可),是一個功能強大,呼叫方便的底層圖形庫。

OpenGL在不同的平臺上有不同的實現,但是它定義好了專業的程式介面,不同的平臺都是遵照該介面來進行實現的,思想完全相同,方法名也是一致的,所以使用時也基本一致,只需要根據不同的語言環境稍有不同而已。
2.什麼是OpenGL ES

OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三維圖形 API 的子集,針對手機、PDA和遊戲主機等嵌入式裝置而設計。

OpenGL ES相對於OpenGL來說,減少了許多不是必須的方法和資料型別,去掉了不必須的功能,對代價大的功能做了限制,比OpenGL更為輕量。在OpenGL ES的世界裡,沒有四邊形、多邊形,無論多複雜的圖形都是由點、線和三角形組成的,也去除了glBegin/glEnd等方法

3.OpenGL ES 可以做什麼?

OpenGL ES是手機、PDA和遊戲主機等嵌入式裝置三維(二維也包括)圖形處理的API,當然是用來在嵌入式裝置上的圖形處理了,OpenGL ES 強大的渲染能力使其成為我們在嵌入式裝置上進行圖形處理的優良選擇。我們經常使用的場景有:

  • 圖片處理。比如圖片色調轉換、美顏等。

  • 攝像頭預覽效果處理。比如美顏相機、惡搞相機等。

  • 視訊處理。攝像頭預覽效果處理可以,這個自然也不在話下了。

  • 3D遊戲。比如神廟逃亡、都市賽車等。

4.OpenGL ES 2.0中基本概念

4.1 著色器

OpenGL ES 2.0中最重要的一個概念就是關於著色器:頂點著色器和片元著色器。

著色器(Shader)是在GPU上執行的小程式。從名稱可以看出,可通過處理它們來處理頂點。此程式使用OpenGL ES SL語言來編寫。它是一個描述頂點或畫素特性的簡單程式。

  • 頂點著色器

其功能是把每個頂點在虛擬空間中的三維座標變換為可以在螢幕上顯示的二維座標,並帶有用於z-buffer的深度資訊。頂點著色器可以操作的屬性有:位置、顏色、紋理座標,但是不能建立新的頂點。

  • 片元著色器

片元著色器計算每個畫素的顏色和其它屬性。它通過應用光照值、凹凸貼圖,陰影,鏡面高光,半透明等處理來計算畫素的顏色並輸出。它也可改變畫素的深度(z-buffering)或在多個渲染目標被啟用的狀態下輸出多種顏色。

著色器語言(Shading Language)是一種高階的圖形程式語言,僅適合於GPU程式設計,其源自應用廣泛的C語言。對於頂點著色器和片元著色器的開發都需要用到著色器語言進行開發。它是面向過程的而非面向物件。具體可參照相關資料。因此,對於繪製不同的圖形時,所編寫的著色器語言不同,這個需要大家參照相關例子學習。

4.2座標系
OpenGL ES 採用的是右手座標,即向右為X正軸方向,向左為X負軸方向,向上為Y軸正軸方向,向下為Y軸負軸方向,螢幕面垂直向上為Z軸正軸方向,垂直向下為Z軸負軸方向。下圖是對比右手和左手座標系。
這裡寫圖片描述
由於實際的繪製是3D圖形,投影到螢幕中顯示的是2D效果,因此這裡需要轉換就是3D到2D圖形的轉換。如下圖所示,實際執行是採用選取螢幕中心為原點,原點到螢幕邊緣的長度為單位1,由於螢幕中心點到邊緣的長度不同(螢幕一般都是長方形,而不是正方形),即:螢幕到寬的距離相對較小,因此從原點到(1,0,0)的距離和到(0,1,0)的距離在螢幕上展示的並不相同。
這裡寫圖片描述
在實際執行設定座標時,就會出現一定的圖形變換,例如繪製一個等邊三角形,在實際投射到螢幕中時,顯示的卻是等邊三角形,這個就是上面提到的螢幕中心點到螢幕邊緣的長度是單位長度,在實際投射時,螢幕到寬和高的比例不同,出現如下圖所示的展示效果。
這裡寫圖片描述

4.3 其他
除了上面提到的著色器和座標外,還包括投影、光照和紋理對映等。

  • 投影:OpenGL ES中有兩種投影方式:正交投影和透視投影。正交投影,物體不會隨距離觀測點的位置而大小發生變化。而透視投影,距離觀測點越遠,物體越小,距離觀測點越近,物體越大。
  • 光照:在螢幕中很難直接的顯示3D場景的效果,因為曲面相對平面更具有光照效果,因此在實際的繪製中,需要加入光照元素:環境光、鏡面光和散射光。
  • 紋理對映:現實世界中的物體往往是絢麗多彩的,要模擬現實世界的絢麗多彩,繪製出更加真實、酷炫的3D物體,就需要用到紋理映射了。紋理對映是將2D的紋理對映到3D場景中的立體物體上。

    4.4 OpenGL ES 2.0執行過程
    讀取頂點資料——執行頂點著色器——組裝圖元——光柵化圖元——執行片元著色器——寫入幀緩衝區——顯示到螢幕上。

  • OpenGL作為本地庫直接執行在硬體上,沒有虛擬機器,也沒有垃圾回收或者記憶體壓縮。在Java層定義影象的資料需要能被OpenGL存取,因此,需要把記憶體從Java堆複製到本地堆。

  • 頂點著色器是針對每個頂點都會執行的程式,是確定每個頂點的位置。同理,片元著色器是針對每個片元都會執行的程式,確定每個片元的顏色。
  • 著色器需要進行編譯,然後連結到OpenGL程式中。一個OpenGL的程式就是把一個頂點著色器和一個片段著色器連結在一起變成單個物件。

二、繪製一個簡單的三角形的步驟

繪製一個三角形的步驟,包括如下幾點:

  • 在manifest中宣告使用OpenGL ES 2.0 API
  • 構造GLSurfaceView.Renderer物件
  • 構造GLSurfaceView物件
  • 將Activity的ContentView設為GLSurfaceView

1、在manifest中宣告使用OpenGL ES 2.0 API

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

0x00020000代表OpenGL ES 2.0版本,3.0版本是0x00030000,3.1版本是0x00030001

2、構造GLSurfaceView.Renderer物件
Renderer類提供三個回撥方法供Android系統呼叫,用來計算在GLSurfaceView中繪製什麼以及如何繪製。
onSurfaceCreated():僅呼叫一次,用於設定view的OpenGL ES環境(初始化)
onDrawFrame():每次重繪view時呼叫,我們自定義的繪製主要是在該方法中實現
onSurfaceChanged():當view的幾何形狀發生變化時呼叫,比如裝置螢幕方向改變時

public class TriggerRender implements GLSurfaceView.Renderer {
    float triangleCoords[] = {
            0.5f, 0.5f, 0.0f, // top
            -0.5f, -0.5f, 0.0f, // bottom left
            0.5f, -0.5f, 0.0f  // bottom right
    };
    float color[] = {1.0f, 0f, 0f, 1.0f}; //red
    private FloatBuffer vertexBuffer;
    private final String vertexShaderCode =
            "attribute vec4 vPosition;" +
                    "void main() {" +
                    "  gl_Position = vPosition;" +
                    "}";

    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "uniform vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";
    private int mProgram;
    private int mPositionHandle;
    private int mColorHandle;
    static final int COORDS_PER_VERTEX = 3;
    //頂點個數
    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    //頂點之間的偏移量
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 每個頂點四個位元組

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        //將背景設定為灰色
        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
        //將座標資料轉換為FloatBuffer,用以傳入給OpenGL ES程式
        vertexBuffer = BufferUtil.fBuffer(triangleCoords);

        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        //建立一個空的OpenGLES程式
        mProgram = GLES20.glCreateProgram();
        //將頂點著色器加入到程式
        GLES20.glAttachShader(mProgram, vertexShader);
        //將片元著色器加入到程式中
        GLES20.glAttachShader(mProgram, fragmentShader);
        //連線到著色器程式
        GLES20.glLinkProgram(mProgram);
    }

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

    @Override
    public void onDrawFrame(GL10 gl10) {
        //將程式加入到OpenGLES2.0環境(載入
        // )
        GLES20.glUseProgram(mProgram);

        //獲取頂點著色器的vPosition成員控制代碼
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        //啟用三角形頂點的控制代碼
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        //準備三角形的座標資料
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);
        //獲取片元著色器的vColor成員的控制代碼
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
        //設定繪製三角形的顏色
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);
        //繪製三角形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
        //禁止頂點陣列的控制代碼
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
    public int loadShader(int type, String shaderCode) {
        //根據type建立頂點著色器或者片元著色器
        int shader = GLES20.glCreateShader(type);
        //將資源加入到著色器中,並編譯
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
    }
}

以上程式碼有完善的註釋,不需額外講解,有一點需要大家注意,在建立頂點座標陣列時,由於Java採用的是大端模式,OpenGL ES採用的是小端模式,需要大家手動轉換一下,否則會報錯誤,詳細分析請看Must use a native order direct Buffer
3、構造GLSurfaceView物件

public class MyGLSurfaceView extends GLSurfaceView {
    private final TriggerRender mRenderer;

    public MyGLSurfaceView(Context context) {
        super(context);
        // 建立OpenGL ES 2.0的上下文
        setEGLContextClientVersion(2);
        mRenderer = new TriggerRender();
        //設定Renderer用於繪圖
        setRenderer(mRenderer);
        //只有在繪製資料改變時才繪製view,可以防止GLSurfaceView幀重繪
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }
}

4、將Activity的ContentView設為GLSurfaceView

   private MyGLSurfaceView mGLSurfaceView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //例項化物件
        mGLSurfaceView = new MyGLSurfaceView(this);
        setContentView(mGLSurfaceView);
    }

這裡需要注意,我們不需要額外的修改Activity的佈局內容(不需要額外將自定義的View放置在layout中)。

三、小結

至此,通過OpenGL ES 來繪製一個簡單的三角形,就完成了,總結一下本文重點:

  • 講解OpenGL 和OpenGL ES是什麼?,OpenGL ES可以做什麼?
  • 講解OpenGL ES的基本概念:著色器、座標系和其他相關概念
  • 詳細敘述OpenGL ES繪製一個三角形的步驟

有需要該Demo的可在GitHub中下載。