Opengl ES(五):響應觸控事件

###

我們已經學習了opengl ES畫圖的框架,成功改變了背景顏色、繪製了一個三角形,並學會了怎麼去改變圖像顯示比例和虛擬攝像機的位置。但僅僅是顯示一張圖片,我們沒有必要去寫那麼多代碼,接下來就是怎麼來交互。在之前已經完成的框架下,完成這個過程就比較簡單了。

前情回顧

Opengl ES(一):第一個例子

Opengl ES(二):畫一個三角形之創造一個三角形

Opengl ES(三):畫一個三角形之顯示到屏幕

Opengl ES(四):設置projection和camera views

###

目前已經完成的代碼在Opengl ES(四):設置projection和camera views的最後面,這裏就不再貼出了。

我們先來嘗試然三角形動起來。

###

其實不用多說,之前Renderer下面的方法我們自然而然就能想到怎麼讓畫面動起來了,只要在onDrawFrame()這個方法下將每一幀繪製的參數進行修改不就完了。

事實上確實這麼簡單,比如我們來完成一個讓三角形轉起來的動作。

這個過程本質就是每一幀讓三角形旋轉一點點,旋轉變換對應着一個矩陣,opengl也提供了創造旋轉矩陣的接口

setRotateM

public static void setRotateM (float[] rm, 
                int rmOffset, 
                float a, 
                float x, 
                float y, 
                float z)

Creates a matrix for rotation by angle a (in degrees) around the axis (x, y, z).

Parameters
rm float: returns the result

 

rmOffset int: index into rm where the result matrix starts

 

a float: angle to rotate in degrees

 

x float: X axis component

 

y float: Y axis component

 

z float: Z axis component

這個接口相對於之前投影和設置攝像機的接口就十分簡單易懂了, 只需要提供一個旋轉角度,和旋轉所繞的軸的向量,然後這些信息會被封裝在我們傳入的浮點數組中,浮點數組依然是16的大小,偏移量依然爲0。

爲了連續旋轉,我們可以利用當前時間來實現,因爲時間是等間隔變化的。

private float[] rotationMatrix = new float[16];
Override
public void onDrawFrame(GL10 gl) {
    ...

    // 利用現在時間實現0-360取值循環
    long time = SystemClock.uptimeMillis() % 4000L;
    float angle = 0.090f * ((int) time);
    Matrix.setRotateM(rotationMatrix, 0, angle, 0, 0, -1.0f);
}

這時候rotationMatrix就i是旋轉作用矩陣了,再利用之前的方法,將它整合到同一個變換矩陣vPMatrix中,然後傳給繪製接口draw()繪製圖像即可。這時onDrawFrame()的代碼便是這樣。

@Override
    public void onDrawFrame(GL10 gl) {
        //把窗口顏色用剛剛設定的值(glClearColor)刷洗
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        //設置camera views
        Matrix.setLookAtM(viewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
        //將兩個矩陣合成一個
        Matrix.multiplyMM(vPMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
        // 利用現在時間實現0-360取值循環
        long time = SystemClock.uptimeMillis() % 4000L;
        float angle = 0.090f * ((int) time);
        Matrix.setRotateM(rotationMatrix, 0, angle, 0, 0, -1.0f);
        //再次合併變換矩陣
        Matrix.multiplyMM(vPMatrix, 0, vPMatrix, 0, rotationMatrix, 0);
        //調用繪製接口畫圖
//        mTriangle.draw();
        mTriangle.draw(vPMatrix);
    }

###完整代碼

這時運行程序,就可以看見一個自己旋轉的三角形了,我們將目前完整的代碼貼出

Triangle類,沒有任何修改。

import android.opengl.GLES20;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

public class Triangle {

    private FloatBuffer vertexBuffer;
    public final int mProgram;
    private int positionHandle;
    private int vPMatrixHandle;
    private int colorHandle;
    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;  //how many vertex
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    //shader代碼​
    private final String vertexShaderCode =
            "uniform mat4 uMVPMatrix;" +
                    "attribute vec4 vPosition;" +
                    "void main() {" +
//                    "  gl_Position = vPosition;"+
                    " gl_Position = uMVPMatrix * vPosition; " +
                    "}";
    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "uniform vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";

    // 數組中每個頂點的座標數(即維數)
    static final int COORDS_PER_VERTEX = 3;
    static float triangleCoords[] = {   // in counterclockwise order:
            0.0f,  0.622008459f, 0.0f, // top
            -0.5f, -0.311004243f, 0.0f, // bottom left
            0.5f, -0.311004243f, 0.0f  // bottom right
    };
    // 顏色信息,分別是RGB和alpha通道的歸一化值
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    public Triangle() {
        /**
         * 將頂點數據傳入Buffer
         */
        // 初始化頂點數據Buffer
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (一個float型4字節)
                triangleCoords.length * 4);
        // 字節序使用native order
        bb.order(ByteOrder.nativeOrder());

        // 將ByteBuffer轉換爲浮點型FloatBuffer
        vertexBuffer = bb.asFloatBuffer();
        // 將頂點數據添加到Buffer
        vertexBuffer.put(triangleCoords);
        // Buffer位置調整到開頭
        vertexBuffer.position(0);
        /**
         * 創建shader並鏈接到程序
         */
        int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        // 創建空的Opengl ES program
        mProgram = GLES20.glCreateProgram();
        // 將頂點着色器加入program
        GLES20.glAttachShader(mProgram, vertexShader);
        // 將片元着色器加入program
        GLES20.glAttachShader(mProgram, fragmentShader);
        // 創造opengl ES可執行的文件
        GLES20.glLinkProgram(mProgram);
    }
    public void draw(float[] mvpMatrix) {
        // 將program添加到Opengl ES環境
        GLES20.glUseProgram(mProgram);
        // 獲取mProgram中vPosition的句柄(頂點數據)
        positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        // 啓用頂點屬性
        GLES20.glEnableVertexAttribArray(positionHandle);

        // 頂點座標的處理方式,參數依次爲索引值(剛剛獲取的句柄),數據維數(頂點即3維)
        // 數據類型(float),當被訪問時固定點數值是否需要歸一化(false)
        // 步長,即連續頂點偏移量(COORDS_PER_VERTEX * 4),
        // 起始位置在緩衝區的偏移量(vertexBuffer)
        GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        // 獲取mProgram中vColor的句柄(顏色數據)
        colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
        // 傳入顏色矩陣
        GLES20.glUniform4fv(colorHandle, 1, color, 0);
        /**
         * 添加projection和camera views作用
         */
        // 獲得uMVPMatrix矩陣(我們用來變換作用)的句柄
        vPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        // 將之前設置好的矩陣信息傳到uMVPMatrix矩陣
        GLES20.glUniformMatrix4fv(vPMatrixHandle, 1, false, mvpMatrix, 0);

        // 繪製三角形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
        // 禁用頂點屬性
        GLES20.glDisableVertexAttribArray(positionHandle);
    }
}

Renderer,利用時間循環在0-360取值,然後調用接口創建了旋轉矩陣,並將旋轉矩陣的作用合併到一個矩陣中。其他部分沒有改變。 

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.os.SystemClock;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MyGLRenderer implements GLSurfaceView.Renderer {
    private Triangle mTriangle;
    private final float[] vPMatrix = new float[16];
    private final float[] projectionMatrix = new float[16];
    private final float[] viewMatrix = new float[16];
    private float[] rotationMatrix = new float[16];

    //shader加載方法,返回編譯好的shader
    public static int loadShader(int type, String shaderCode){
        // 創建頂點着色器的type是 GLES20.GL_VERTEX_SHADER
        // 創建片元着色器的type是 GLES20.GL_FRAGMENT_SHADER
        int shader = GLES20.glCreateShader(type);

        // 傳遞着色器代碼並編譯着色器
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //設置背景顏色,參數是RGB和alpha通道值的歸一值,即範圍爲0-1,我設置的是橙色
        GLES20.glClearColor(1.0f, 0.6f, 0f, 1.0f);
        mTriangle = new Triangle();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //調整窗口大小
        GLES20.glViewport(0, 0, width, height);
        /**
         * projection matrix
         */
        float ratio = (float) width / height;
        Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 2.5f, 30);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //把窗口顏色用剛剛設定的值(glClearColor)刷洗
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        //設置camera views
        Matrix.setLookAtM(viewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
        //將兩個矩陣合成一個
        Matrix.multiplyMM(vPMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
        // 利用現在時間實現0-360取值循環
        long time = SystemClock.uptimeMillis() % 4000L;
        float angle = 0.090f * ((int) time);
        Matrix.setRotateM(rotationMatrix, 0, angle, 0, 0, -1.0f);
        //再次合併變換矩陣
        Matrix.multiplyMM(vPMatrix, 0, vPMatrix, 0, rotationMatrix, 0);
        //調用繪製接口畫圖
//        mTriangle.draw();
        mTriangle.draw(vPMatrix);
    }
}

 GLSurfaceView類,沒有改動。

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.view.MotionEvent;

public class MyGLSurfaceView extends GLSurfaceView {
    private final MyGLRenderer renderer;
    public MyGLSurfaceView(Context context) {
        super(context);
        //設置版本
        setEGLContextClientVersion(2);
        //創建Renderer實例
        renderer = new MyGLRenderer();
        //將GLRenderer和GLSurfaceView連接
        setRenderer(renderer);
    }
}

MainActivity類,沒有改動。

import android.opengl.GLSurfaceView;
import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private GLSurfaceView gLView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //創建GLSurfaceView實例
        gLView = new MyGLSurfaceView(this);
        //連接GLSurfaceView
        setContentView(gLView);
    }
}

###實現觸控事件響應

簡單的幾行添加我們就實現了三角形的旋轉,那麼我們能然三角形的旋轉受人來控制嗎?

很顯然,我們只需要接收觸控屏幕的事件,再根據觸控位置設置旋轉角度,即將上面利用時間取值控制角度改成利用觸控位置控制角度即可。

在GLSurfaceView中有響應屏幕觸控事件的方法onTouchEvent(),這個方法應該是繼承自View類。

onTouchEvent

public boolean onTouchEvent (MotionEvent event)

Implement this method to handle touch screen motion events.

Parameters
event MotionEvent: The motion event.

 

Returns
boolean True if the event was handled, false otherwise.

我們可以在GLSurfaceView下寫出

@Override
public boolean onTouchEvent(MotionEvent e) {
    //獲取觸控位置
    float x = e.getX();
    float y = e.getY();

    return true;
}

每當有觸控事件,這個方法就會響應,我們就能夠獲得觸控的位置。那麼接下來怎麼處理就是我們根據需求自己設計的了。

那麼我們接下來根據常見的旋轉交互方式來設計一個算法

通常來講,我們在屏幕上超一個方向旋轉,那麼三角形朝一個方向轉動。

所以我們需要測定一個滑動的狀態,也就是需要求出連續兩個狀態之間的變化過程,那麼我們就先定義之前的狀態(之前觸摸的位置)和現在的狀態(現在觸摸的位置,我們已經定義成了x和y)

private float previousX;
private float previousY;
@Override
public boolean onTouchEvent(MotionEvent e) {
    //獲取觸控位置
    float x = e.getX();
    float y = e.getY();

    return true;
}

另外,MotionEvent類中提供了狀態判定,可以幫我們更準確的操作。

我們需要的滑動操作對應着MotionEvent.ACTION_MOVE,他能檢測到我們在屏幕上按下並且移動了的狀態。

ACTION_MOVE

public static final int ACTION_MOVE

Constant for getActionMasked(): A change has happened during a press gesture (between ACTION_DOWN and ACTION_UP). The motion contains the most recent point, as well as any intermediate points since the last down or move event.

Constant Value: 2 (0x00000002)

所以我們添加一個switch來判定是否是我們需要處理的狀態改變。我們在case MotionEvent.ACTION_MOVE:下進行操作

private float previousX;
private float previousY;
@Override
public boolean onTouchEvent(MotionEvent e) {
    //獲取觸控位置
    float x = e.getX();
    float y = e.getY();

    switch (e.getAction()) {
        case MotionEvent.ACTION_MOVE:

    }
    return true;
}

 顯然我們需要計算出前一位置和後一位置的差,來判定是往哪個方向旋轉。

我們需要將情況稍微歸納整理一下,這裏直接給出一種判定方法:

如果在上半部分,向左劃是逆時針,向右劃是順時針;在下半部分則相反。

如果在左半部分,向下劃是逆時針,向上劃是順時針;在右半部分則相反。

所以我們判定前先要獲取窗口的上下和左右中間界限值,窗口大小是隨時會改變的,所以每次都判定前都進行查詢,好在View類中有可以使用的方法getHeight()和getWidth(),我們直接使用即可。

我們選取某一區爲基準,然後另一區取反即可。就好像物理學中方向的定義一樣,正負號只是一個劃分。

然後很顯然,我們將兩個狀態的變換量dx和dy加起來,他們的符號也可以作爲一個劃分。

我們再設置一個旋轉速率TOUCH_SCALE_FACTOR,(dx+dy)*TOUCH_SCALE_FACTOR即爲我們旋轉的角度。

然後再編寫個方法,將這個角度傳給Renderer來繪製即可。

最後方法如下,renderer.setAngle和renderer.getAngle就是我們用來傳輸旋轉角度的方法。previousX和previousY就是上一時刻的觸控位置。其中requestRender()是GLSurfaceView提供的一個方法,我們可以利用setRenderMode方法設置渲染的模式,如果設置爲RENDERMODE_WHEN_DIRTY模式,只有當surface創立和調用requestRender()時纔會渲染畫面,這極大提升了我們畫圖的效率。

所以我們還需要在GLSurfaceView類的構造函數最後(設置了Renderer之後),添加一句

        // 修改Render模式
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
private final float TOUCH_SCALE_FACTOR = 180.0f / 900;
private float previousX;
private float previousY;
@Override
    public boolean onTouchEvent(MotionEvent e) {
        //獲取觸控位置
        float x = e.getX();
        float y = e.getY();
        switch (e.getAction()) {
            case MotionEvent.ACTION_MOVE:

                float dx = x - previousX;
                float dy = y - previousY;
                // 以下半部分爲基準
                if (y > getHeight() / 2) {
                    dx = dx * -1 ;
                }
                // 以右半部分爲基準
                if (x < getWidth() / 2) {
                    dy = dy * -1 ;
                }
                renderer.setAngle(
                        renderer.getAngle() -
                                ((dx + dy) * TOUCH_SCALE_FACTOR));
                requestRender();
        }
        previousX = x;
        previousY = y;
        return true;
    }

傳輸角度方法編寫就十分簡單了,就是傳個參。我們直接在Renderer中定義一個參數,然後編寫傳遞這個參數的方法。

    public volatile float mAngle;
    //傳遞角度參數
    public float getAngle() {
        return mAngle;
    }
    public void setAngle(float angle) {
        mAngle = angle;
    }    public volatile float mAngle;

最後只需將onDrawFrame中旋轉矩陣的角度參數由時間產生改成直接使用我們傳遞過來的角度mAngle

//        // 利用現在時間實現0-360取值循環
//        long time = SystemClock.uptimeMillis() % 4000L;
//        float angle = 0.090f * ((int) time);
//        Matrix.setRotateM(rotationMatrix, 0, angle, 0, 0, -1.0f);
        Matrix.setRotateM(rotationMatrix, 0, mAngle, 0, 0, -1.0f);

這時候就可以觸控去旋轉三角形了!

###完整代碼

Triangle類

import android.opengl.GLES20;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

public class Triangle {

    private FloatBuffer vertexBuffer;
    public final int mProgram;
    private int positionHandle;
    private int vPMatrixHandle;
    private int colorHandle;
    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;  //how many vertex
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    //shader代碼​
    private final String vertexShaderCode =
            "uniform mat4 uMVPMatrix;" +
                    "attribute vec4 vPosition;" +
                    "void main() {" +
//                    "  gl_Position = vPosition;"+
                    " gl_Position = uMVPMatrix * vPosition; " +
                    "}";
    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "uniform vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";

    // 數組中每個頂點的座標數(即維數)
    static final int COORDS_PER_VERTEX = 3;
    static float triangleCoords[] = {   // in counterclockwise order:
            0.0f,  0.622008459f, 0.0f, // top
            -0.5f, -0.311004243f, 0.0f, // bottom left
            0.5f, -0.311004243f, 0.0f  // bottom right
    };
    // 顏色信息,分別是RGB和alpha通道的歸一化值
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    public Triangle() {
        /**
         * 將頂點數據傳入Buffer
         */
        // 初始化頂點數據Buffer
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (一個float型4字節)
                triangleCoords.length * 4);
        // 字節序使用native order
        bb.order(ByteOrder.nativeOrder());

        // 將ByteBuffer轉換爲浮點型FloatBuffer
        vertexBuffer = bb.asFloatBuffer();
        // 將頂點數據添加到Buffer
        vertexBuffer.put(triangleCoords);
        // Buffer位置調整到開頭
        vertexBuffer.position(0);
        /**
         * 創建shader並鏈接到程序
         */
        int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        // 創建空的Opengl ES program
        mProgram = GLES20.glCreateProgram();
        // 將頂點着色器加入program
        GLES20.glAttachShader(mProgram, vertexShader);
        // 將片元着色器加入program
        GLES20.glAttachShader(mProgram, fragmentShader);
        // 創造opengl ES可執行的文件
        GLES20.glLinkProgram(mProgram);
    }
    public void draw(float[] mvpMatrix) {
        // 將program添加到Opengl ES環境
        GLES20.glUseProgram(mProgram);
        // 獲取mProgram中vPosition的句柄(頂點數據)
        positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        // 啓用頂點屬性
        GLES20.glEnableVertexAttribArray(positionHandle);

        // 頂點座標的處理方式,參數依次爲索引值(剛剛獲取的句柄),數據維數(頂點即3維)
        // 數據類型(float),當被訪問時固定點數值是否需要歸一化(false)
        // 步長,即連續頂點偏移量(COORDS_PER_VERTEX * 4),
        // 起始位置在緩衝區的偏移量(vertexBuffer)
        GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        // 獲取mProgram中vColor的句柄(顏色數據)
        colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
        // 傳入顏色矩陣
        GLES20.glUniform4fv(colorHandle, 1, color, 0);
        /**
         * 添加projection和camera views作用
         */
        // 獲得uMVPMatrix矩陣(我們用來變換作用)的句柄
        vPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        // 將之前設置好的矩陣信息傳到uMVPMatrix矩陣
        GLES20.glUniformMatrix4fv(vPMatrixHandle, 1, false, mvpMatrix, 0);

        // 繪製三角形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
        // 禁用頂點屬性
        GLES20.glDisableVertexAttribArray(positionHandle);
    }
}

Renderer

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.os.SystemClock;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MyGLRenderer implements GLSurfaceView.Renderer {
    private Triangle mTriangle;
    private final float[] vPMatrix = new float[16];
    private final float[] projectionMatrix = new float[16];
    private final float[] viewMatrix = new float[16];
    private float[] rotationMatrix = new float[16];
    public volatile float mAngle;
    //傳遞角度參數
    public float getAngle() {
        return mAngle;
    }
    public void setAngle(float angle) {
        mAngle = angle;
    }

    //shader加載方法,返回編譯好的shader
    public static int loadShader(int type, String shaderCode){
        // 創建頂點着色器的type是 GLES20.GL_VERTEX_SHADER
        // 創建片元着色器的type是 GLES20.GL_FRAGMENT_SHADER
        int shader = GLES20.glCreateShader(type);

        // 傳遞着色器代碼並編譯着色器
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //設置背景顏色,參數是RGB和alpha通道值的歸一值,即範圍爲0-1,我設置的是橙色
        GLES20.glClearColor(1.0f, 0.6f, 0f, 1.0f);
        mTriangle = new Triangle();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //調整窗口大小
        GLES20.glViewport(0, 0, width, height);
        /**
         * projection matrix
         */
        float ratio = (float) width / height;
        Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 2.5f, 30);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //把窗口顏色用剛剛設定的值(glClearColor)刷洗
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        //設置camera views
        Matrix.setLookAtM(viewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
        //將兩個矩陣合成一個
        Matrix.multiplyMM(vPMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
//        // 利用現在時間實現0-360取值循環
//        long time = SystemClock.uptimeMillis() % 4000L;
//        float angle = 0.090f * ((int) time);
//        Matrix.setRotateM(rotationMatrix, 0, angle, 0, 0, -1.0f);
        Matrix.setRotateM(rotationMatrix, 0, mAngle, 0, 0, -1.0f);
        //再次合併變換矩陣
        Matrix.multiplyMM(vPMatrix, 0, vPMatrix, 0, rotationMatrix, 0);
        //調用繪製接口畫圖
//        mTriangle.draw();
        mTriangle.draw(vPMatrix);
    }
}

GLSurfaceView類

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.os.SystemClock;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MyGLRenderer implements GLSurfaceView.Renderer {
    private Triangle mTriangle;
    private final float[] vPMatrix = new float[16];
    private final float[] projectionMatrix = new float[16];
    private final float[] viewMatrix = new float[16];
    private float[] rotationMatrix = new float[16];
    public volatile float mAngle;
    //傳遞角度參數
    public float getAngle() {
        return mAngle;
    }
    public void setAngle(float angle) {
        mAngle = angle;
    }

    //shader加載方法,返回編譯好的shader
    public static int loadShader(int type, String shaderCode){
        // 創建頂點着色器的type是 GLES20.GL_VERTEX_SHADER
        // 創建片元着色器的type是 GLES20.GL_FRAGMENT_SHADER
        int shader = GLES20.glCreateShader(type);

        // 傳遞着色器代碼並編譯着色器
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //設置背景顏色,參數是RGB和alpha通道值的歸一值,即範圍爲0-1,我設置的是橙色
        GLES20.glClearColor(1.0f, 0.6f, 0f, 1.0f);
        mTriangle = new Triangle();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //調整窗口大小
        GLES20.glViewport(0, 0, width, height);
        /**
         * projection matrix
         */
        float ratio = (float) width / height;
        Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 2.5f, 30);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //把窗口顏色用剛剛設定的值(glClearColor)刷洗
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        //設置camera views
        Matrix.setLookAtM(viewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
        //將兩個矩陣合成一個
        Matrix.multiplyMM(vPMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
//        // 利用現在時間實現0-360取值循環
//        long time = SystemClock.uptimeMillis() % 4000L;
//        float angle = 0.090f * ((int) time);
//        Matrix.setRotateM(rotationMatrix, 0, angle, 0, 0, -1.0f);
        Matrix.setRotateM(rotationMatrix, 0, mAngle, 0, 0, -1.0f);
        //再次合併變換矩陣
        Matrix.multiplyMM(vPMatrix, 0, vPMatrix, 0, rotationMatrix, 0);
        //調用繪製接口畫圖
//        mTriangle.draw();
        mTriangle.draw(vPMatrix);
    }
}

MainActivity類


import android.opengl.GLSurfaceView;
import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private GLSurfaceView gLView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //創建GLSurfaceView實例
        gLView = new MyGLSurfaceView(this);
        //連接GLSurfaceView
        setContentView(gLView);
    }
}

###

至此這個系列主幹線就完結了,其實這系列的教程是按照https://developer.android.google.cn/training/graphics/opengl來的。

把代碼在這裏再寫一遍,一方面是原版有些地方過於簡略,理解並不容易,這裏可以做一些補充;另一方面也是當作自己做的一個學習筆記,把代碼帶着講解性質寫一遍和只是看幾遍效果是完全不一樣的。

###目錄導航

Opengl ES(一):第一個例子

Opengl ES(二):畫一個三角形之創造一個三角形

Opengl ES(三):畫一個三角形之顯示到屏幕

Opengl ES(四):設置projection和camera views

Opengl ES(五):響應觸控事件

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