opengles(二)畫個三角型

首先我們需要知道opengles在屏幕上的座標,即中間座標點是x=0,y=0; x軸往右是正,往左是負,y軸往上是正,往下是負,且x, y的範圍都是-1到1;所以我們先定義座標數組,順序是逆時針方向:

private static final float[] TRIANGLE_VERTEX_COORD = {0, 0.5f,
            -0.5f, -0.5f,
            0.5f, -0.5f};

爲了最大限度地提高效率,我們可以將這些座標寫入FloatBuffer,然後將其傳遞給OpenGL ES圖形管道進行處理:

    private FloatBuffer mTriangleCoordBuffer;

    public Triangle() {
        // 數組轉化爲buffer,提高opengles性能
        mTriangleCoordBuffer = ByteBuffer.allocateDirect(TRIANGLE_VERTEX_COORD.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mTriangleCoordBuffer.put(TRIANGLE_VERTEX_COORD);

        // 設置從第一位開始讀
        mTriangleCoordBuffer.position(0);
    }       

現在我們要把這些座標數據傳給opengles圖形管道,那麼我們怎麼傳呢,這裏需要用到shader,即是着色器,opengles裏分爲兩種shader,一種是vertex shader(我們的頂點座標就是傳到這裏), 另一種是fragment shader(我們的顏色數據傳到這),並且由opengles特有的語言把這兩種shader組織起來,下面我們來看看例子:

private static final String VERTEX_SHADER_SOURCE = "attribute vec4 vPosition;" +
            "void main() {" +
            "   gl_Position = vPosition;" +
            "}";

    private static final String FRAGMENT_SHADER_SOURCE = "uniform vec4 vColor;" +
            "void main() {" +
            "   gl_FragColor = vColor;" +
            "}";

它們的寫法類似於C語言,叫做GLES語言,當有一些自己獨有的東西;
attribute僅在vertex shader中使用,常用於指出vertex shader的輸入數據;
vec4代表這個向量裏有四個float型的數據;
vPosition是我們自己起的一個變量名;
gl_Position是opengles的特有名詞,指的是頂點的座標;
uniform用於存儲shader需要的各種數據,例如轉換矩陣、光線參數和顏色等。可以被vertex shader 和 fragment shader 共享使用;
gl_FragColor也是opengles特有的名詞,指的是顏色;

好了,寫完GLES代碼,我們需要把代碼編譯後傳給opengles,下面看下該怎麼做:
我們先寫一個加載shader的方法

    private int getShader(int type, String source) {
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, source);
        GLES20.glCompileShader(shader);

        // 獲取編譯的狀態
        final int[] compileStatus = new int[1];
        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,
                compileStatus, 0);

        Log.v(TAG, "代碼編譯結果:" + "\n" + source
                    + "\n:" + GLES20.glGetShaderInfoLog(shader));

        if (compileStatus[0] == 0) {
            Log.w(TAG, "編譯失敗!.");
            return 0;
        }

        return shader;
    }

接着把得到的shader和program連接,這樣opengles就知道該怎麼處理我們的着色邏輯了;

private int mVertexShader, mFragmentShader;

public Triangle() {
        // ...
        // 得到編譯後的shader
        mVertexShader = getShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_SOURCE);
        mFragmentShader = getShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE);

        // 創建並連接program
        int program = GLES20.glCreateProgram();
        GLES20.glAttachShader(program, mVertexShader);
        GLES20.glAttachShader(program, mFragmentShader);
        GLES20.glLinkProgram(program);
        GLES20.glUseProgram(program);
}

我們之前定義好的頂點buffer該怎麼傳進去給opengles呢,這裏我們就需要先找到對應的變量的locaction:

    private static final String POSITION_NAME = "vPosition";
    private static final String COLOR_NAME = "vColor";

    public Triangle() {
        // ...
        // 找到對應變量的index
        mPositionLocation = GLES20.glGetAttribLocation(program, POSITION_NAME);
        mFragColorLocation = GLES20.glGetUniformLocation(program, COLOR_NAME);
    }

傳入數據,就可以基本完成我們的繪圖了:

    public void draw() {
        // 由於性能方面的考慮,opengles默認讀取不到緩衝區裏的數據,需要先enable後再讀
        GLES20.glEnableVertexAttribArray(mPositionLocation);
        GLES20.glVertexAttribPointer(mPositionLocation, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false, 0, mTriangleCoordBuffer);

        GLES20.glUniform4f(mFragColorLocation, 0, 0.5f, 0, 1.0f);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, VERTEX_SIZE);

        // 調用完glDrawArrays後才能disable;
        GLES20.glDisableVertexAttribArray(mPositionLocation);
    }

這裏給出較完整的代碼:

public class MyRenderer implements GLSurfaceView.Renderer {

    private Triangle mTriangle;

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);

        mTriangle = new Triangle();
    }

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

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        mTriangle.draw();
    }
}
public class Triangle {
    private static final String TAG = "Triangle";

    // 三角形的三個頂點
    private static final float[] TRIANGLE_VERTEX_COORD = {0, 0.5f,
            -0.5f, -0.5f,
            0.5f, -0.5f};

    private static final int VERTEX_SIZE = 3;
    private static final int COORDS_PER_VERTEX = TRIANGLE_VERTEX_COORD.length / VERTEX_SIZE;

    private FloatBuffer mTriangleCoordBuffer;

    private static final String VERTEX_SHADER_SOURCE = "attribute vec4 vPosition;" +
            "void main() {" +
            "   gl_Position = vPosition;" +
            "}";

    private static final String FRAGMENT_SHADER_SOURCE = "uniform vec4 vColor;" +
            "void main() {" +
            "   gl_FragColor = vColor;" +
            "}";

    private int mVertexShader, mFragmentShader;

    private static final String POSITION_NAME = "vPosition";
    private static final String COLOR_NAME = "vColor";

    private int mPositionLocation, mFragColorLocation;

    public Triangle() {
        // 數組轉化爲buffer,提高opengles性能
        mTriangleCoordBuffer = ByteBuffer.allocateDirect(TRIANGLE_VERTEX_COORD.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mTriangleCoordBuffer.put(TRIANGLE_VERTEX_COORD);
        // 設置從第一位開始讀
        mTriangleCoordBuffer.position(0);

        // 得到編譯後的shader
        mVertexShader = getShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_SOURCE);
        mFragmentShader = getShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE);

        // 創建並連接program
        int program = GLES20.glCreateProgram();
        GLES20.glAttachShader(program, mVertexShader);
        GLES20.glAttachShader(program, mFragmentShader);
        GLES20.glLinkProgram(program);
        GLES20.glUseProgram(program);

        // 找到對應變量的index
        mPositionLocation = GLES20.glGetAttribLocation(program, POSITION_NAME);
        mFragColorLocation = GLES20.glGetUniformLocation(program, COLOR_NAME);
    }

    private int getShader(int type, String source) {
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, source);
        GLES20.glCompileShader(shader);

        // 獲取編譯的狀態
        final int[] compileStatus = new int[1];
        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,
                compileStatus, 0);

        Log.v(TAG, "代碼編譯結果:" + "\n" + source
                    + "\n:" + GLES20.glGetShaderInfoLog(shader));

        if (compileStatus[0] == 0) {
            Log.w(TAG, "編譯失敗!.");
            return 0;
        }

        return shader;
    }

    public void draw() {
        // 由於性能方面的考慮,opengles默認讀取不到緩衝區裏的數據,需要先enable後再讀
        GLES20.glEnableVertexAttribArray(mPositionLocation);
        GLES20.glVertexAttribPointer(mPositionLocation, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false, 0, mTriangleCoordBuffer);

        GLES20.glUniform4f(mFragColorLocation, 0, 0.5f, 0, 1.0f);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, VERTEX_SIZE);

        GLES20.glDisableVertexAttribArray(mPositionLocation);
    }

}

運行後的結果:
這裏寫圖片描述

不過我們的三角型有點變形,我們定義的明明是等邊三角型,但實際畫出來的不是,這裏是因爲opengles的座標和實際屏幕的座標是不對應的,opengles的長和寬都是一樣的,就是-1到1,實際屏幕的長和寬肯定不一樣,沒有正方形的手機屏吧,該如何解決呢?這裏我們需要用到矩陣,下節再說。

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