標籤雲

搜尋此網誌

2016/02/16

OpenGL ES 入門 - 使用 GLSurfaceView-2

在前面一篇我們定義了形狀並在 GLSurfaceView 利用 GLSurfaceView.Renderer 畫出
也學習到 Vertex Shader, Fragment Shader, Program

接下來要開始互動的部分

Projection and Camera Views
Projection 投影
負責依照座標及寬高計算物品顯示出的投射
如果沒有經過這個計算物品可能會由於 view window 的不等比例而變形
一般來說投影計算只需在 OpenGL view 建立時或是在 renderer 的 onSurfaceChanged() 時進行

Camera Views - 攝影機視角
OpenGL ES 並沒有定義一個實際的 camera 物件
但是提供了一個實用方法去模擬攝影機
Camera View 只需在當 GLSurfaceView 建立時進行計算
或是之後我們移動它時進行動態計算

在 GLSurfaceView.Renderer 的 onSurfaceChanged
用 Matrix.frustumM() 填充投影
// mMVPMatrix 是 "Model View Projection Matrix" 的縮寫
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];

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

    float ratio = (float) width / height;

    // 投影矩陣會在物件的 onDrawFrame() 中套用到座標上(投影並不會讓物件顯示出來,必須搭配 camera )
    Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}
camera 是透過 Matrix.setLookAtM() 去計算物件的變形
@Override
public void onDrawFrame(GL10 unused) {
    ...
    // 設定 camera 位置 (View matrix)
    Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
    // 計算投影與 view 變形
    Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
    // 畫出形狀
    mTriangle.draw(mMVPMatrix);
}
為了套用 Projection 跟 Camera,也必須修改一下其他程式
public class Triangle {

    private final String vertexShaderCode =
        // 這個矩陣變數提供了一個轉接去使用 vertex shader 操作物件座標 
        "uniform mat4 uMVPMatrix;" +
        "attribute vec4 vPosition;" +
        "void main() {" +
        // 矩陣必須包含作為 gl_Position 的變更者
        // 注意 uMVPMatrix *必須在前面* 才能確保矩陣的乘法結果正確
        "  gl_Position = uMVPMatrix * vPosition;" +
        "}";

    // 用來存取及設定 view 的變形
    private int mMVPMatrixHandle;
    ...
    public void draw(float[] mvpMatrix) { // 由計算過的變形矩陣傳入
        ...
        // 取得形狀的變形矩陣的 handle 
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        // 套用投影與 view 變形
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
        // 畫出形狀
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
        // 停用 vertex array
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
    ...
}

Adding Motion

要旋轉一個物品的話
我們必須建立另一個變形矩陣並把它結合到投影與攝影機的變形矩陣
(記得檢查一下你的 Renderer 是否有把 RenderMode 設為 GLSurfaceView.RENDERMODE_WHEN_DIRTY)
private float[] mRotationMatrix = new float[16];

public void onDrawFrame(GL10 gl) {
    float[] scratch = new float[16];
    ...
    // 建立旋轉變形
    long time = SystemClock.uptimeMillis() % 4000L;
    float angle = 0.090f * ((int) time);
    Matrix.setRotateM(mRotationMatrix, 0, angle, 0, 0, -1.0f);

    // 結合旋轉變形矩陣到投影與攝影機
    // 一樣的,mMVPMatrix *必須在最前面* 才能確保結果正確
    Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);

    mTriangle.draw(scratch);
}

Responding to Touch Events

為了讓 OpenGL ES app 能回應觸控事件
必須在 GLSurfaceView 裡實做 onTouchEvent()
private final float TOUCH_SCALE_FACTOR = 180.0f / 320;
private float mPreviousX;
private float mPreviousY;

@Override
public boolean onTouchEvent(MotionEvent e) {

    float x = e.getX();
    float y = e.getY();
    
    //這裡我們只關注 ACTION_MOVE 
    switch (e.getAction()) {
        case MotionEvent.ACTION_MOVE:

            float dx = x - mPreviousX;
            float dy = y - mPreviousY;

            // 反轉旋轉方向 above the mid-line
            if (y > getHeight() / 2) {
              dx = dx * -1 ;
            }

            // 反轉旋轉方向 to left of the mid-line
            if (x < getWidth() / 2) {
              dy = dy * -1 ;
            }

            mRenderer.setAngle(
                    mRenderer.getAngle() +
                    ((dx + dy) * TOUCH_SCALE_FACTOR));
            //要求重新渲染
            requestRender();
    }

    mPreviousX = x;
    mPreviousY = y;
    return true;
}
另外由於 Renderer 不是在 ActivityThread 上面運行 所以我們的 public 角度屬性必須加上 volatile 關鍵字
public class MyGLRenderer implements GLSurfaceView.Renderer {
    ...
    public volatile float mAngle;

    public float getAngle() {
        return mAngle;
    }

    public void setAngle(float angle) {
        mAngle = angle;
    }
    ...
    public void onDrawFrame(GL10 gl) {
        ...
        float[] scratch = new float[16];

        // 把前面自動旋轉的 code 給 comment 起來
        // long time = SystemClock.uptimeMillis() % 4000L;
        // float angle = 0.090f * ((int) time);
        Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);

        // Combine the rotation matrix with the projection and camera view
        // Note that the mMVPMatrix factor *must be first* in order
        // for the matrix multiplication product to be correct.
        Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);

        // Draw triangle
        mTriangle.draw(scratch);
    }
    ...
}
相關資料:
android developer-Displaying Graphics with OpenGL ES
android developer-OpenGL ES
android developer-Applying Projection and Camera Views
android developer-Adding Motion
android developer-Responding to Touch Events

沒有留言: