Android OpenGL ES2.0(一)—— 繪制靜態形狀

快速入門

關鍵類

  1. GLSurfaceView
    • 繪制的載體
    • 小部分區域時可使用TextureView
    • 理論上可使用SurfaceView
  2. GLSurfaceView.Renderer
    • 實質的繪制動作
授權
登錄
簽名

構鍵一個GLSurfaceView對象

class MyGLSurfaceView extends GLSurfaceView {

    public MyGLSurfaceView(Context context){
        super(context);
        
        // 創建一個OpenGL ES 2.0 context
        setEGLContextClientVersion(2);
        
        // 設置Renderer到GLSurfaceView
        setRenderer(new MyRenderer());
         
        // 只有在繪制數據改變時才繪制view
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }
}

GLSurfaceView.RENDERMODE_WHEN_DIRTY會阻止繪制GLSurfaceView的幀,直到你調用了requestRender(),這樣會非常高效。

繪制灰色背景的Renderer

public class MyGL20Renderer implements GLSurfaceView.Renderer {

    // 僅調用一次,用于設置view的OpenGLES環境
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        // 設置背景的顏色
        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
    }

    public void onDrawFrame(GL10 unused) {
        // 重繪背景色
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    }
    
    public void onSurfaceChanged(GL10 unused, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
    }
}

這些方法們都具有一個GL10參數,但你使用的卻是OpengGLES 2.0 API們,這其實是為了使Android框架能簡單的兼容各OpenGLES版本而做的。

定義形狀

通過OpenGLES相對Android的坐標系統,可以定義各種形狀。其中三角形是所有形狀的基礎,因為很多復雜的三位圖形都可以看做是大量三角形組成的。因為我們先了解定義三角形,再通過三角形組成正方形。

三角形

定義一個三角形,需要定義這個三角形的三個頂點在三維空間中的坐標。在OpenGL中,典型的方式是為坐標定義一個浮點類型的頂點數組。

為了最高效,你應把這些坐標都寫進一個ByteBuffer,它會被傳到OpenGLES圖形管線以進行處理。

缺省情況下,OpenGLES 假定[0,0,0]是GLSurfaceView 幀的中心,[1,1,0]是右上角,[-1,-1,0]是左下角。坐標的順序為(X, Y, Z)。

class Triangle {

    private FloatBuffer vertexBuffer;

    // 數組中每個頂點的坐標數
    static final int COORDS_PER_VERTEX = 3;
    static float triangleCoords[] = { // 按逆時針方向順序:
         0.0f,  0.622008459f, 0.0f,   // top
        -0.5f, -0.311004243f, 0.0f,   // bottom left
         0.5f, -0.311004243f, 0.0f    // bottom right
    };

    // 設置顏色,分別為red, green, blue 和alpha (opacity)
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    public Triangle() {
        // 為存放形狀的坐標,初始化頂點字節緩沖
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (坐標數 * 4)float占四字節
                triangleCoords.length * 4);
        // 設用設備的本點字節序
        bb.order(ByteOrder.nativeOrder());

        // 從ByteBuffer創建一個浮點緩沖
        vertexBuffer = bb.asFloatBuffer();
        // 把坐標們加入FloatBuffer中
        vertexBuffer.put(triangleCoords);
        // 設置buffer,從第一個坐標開始讀
        vertexBuffer.position(0);
    }
}

此形狀的坐標是按逆時針方向定義的。繪制順序很重要,因為它定義了哪面是形狀的正面,哪面是反面,使用OpenGLES 的cullface特性,你可以只畫正面而不畫反面。

正方形

定義正方形有很多方法,典型的做法是使用兩個三角形。

opengl_square.jpg

按照上面的圖形,定義4個關鍵坐標點,以及兩個三角形的繪制順序,即可定義好這個正方形。

class Square {

    private FloatBuffer vertexBuffer;
    private ShortBuffer drawListBuffer;

    // 每個頂點的坐標數
    static final int COORDS_PER_VERTEX = 3;
    static float squareCoords[] = { -0.5f,  0.5f, 0.0f,   // top left
                                    -0.5f, -0.5f, 0.0f,   // bottom left
                                     0.5f, -0.5f, 0.0f,   // bottom right
                                     0.5f,  0.5f, 0.0f }; // top right

    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // 頂點的繪制順序

    public Square() {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
        // (坐標數 * 4)
                squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

        // 為繪制列表初始化字節緩沖
        ByteBuffer dlb = ByteBuffer.allocateDirect(
        // (對應順序的坐標數 * 2)short是2字節
                drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
    }
}

繪制形狀

OpenGL ES 2.0畫一個定義好的圖形需要很多代碼,典型的需要定義以下幾個東西:

  1. VertexShader-用于渲染形狀的頂點
  2. FragmentShader-用于渲染形狀的外觀(顏色或紋理)
  3. Program-包含了你想要用來繪制一個或多個形狀的Shader。

繪制Program中至少需要保護一個VertexShader和一個FragmentShader。

OpenGLShading Language

如下為OpenGLShading Language (GLSL)代碼,Shader必須經過編譯才能添加到Program中,然后通過Program進行繪制。

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;" +
    "}";

可使用如下的靜態方法編譯Shader

public static int loadShader(int type, String shaderCode){

    // 創建一個vertex shader類型(GLES20.GL_VERTEX_SHADER)
    // 或fragment shader類型(GLES20.GL_FRAGMENT_SHADER)
    int shader = GLES20.glCreateShader(type);

    // 將源碼添加到shader并編譯之
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);

    return shader;
}

編譯OpenGLES shader們和鏈接Program們是很耗CPU的,所以你應該避免多次執行。

繪制

一般在形狀類中創建draw()負責繪制。下面的代碼設置位置和顏色值到形狀的VertexShader和FragmentShader,然后執行繪制功能。

private final int vertexCount = triangleCoords.length; // COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // bytes per vertex

public void draw() {
    // 將program加入OpenGL ES環境中
    GLES20.glUseProgram(mProgram);

    // 獲取指向vertex shader的成員vPosition的 handle
    mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

    // 啟用一個指向三角形的頂點數組的handle
    GLES20.glEnableVertexAttribArray(mPositionHandle);

    // 準備三角形的坐標數據
    GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                                 GLES20.GL_FLOAT, false,
                                 vertexStride, vertexBuffer);

    // 獲取指向fragment shader的成員vColor的handle 
    mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

    // 設置三角形的顏色
    GLES20.glUniform4fv(mColorHandle, 1, color, 0);

    // 畫三角形
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

    // 禁用指向三角形的頂點數組
    GLES20.glDisableVertexAttribArray(mPositionHandle);
}

最終只需要在Renderer的onDrawFrame()調用draw()方法,即可完成形狀的繪制。

Square的繪制邏輯和Triangle類似,只需要修改關鍵的繪制方法。

//GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);

參考文章

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容