Android音視頻開發入門(六)

1.任務目標

學習 Android 平臺 OpenGL ES API,瞭解 OpenGL 開發的基本流程,使用 OpenGL 繪製一個三角形

2.OpenGL ES 基本介紹

Android的基本框架大家都不陌生,在Libraries層面包含了OpenGL,特別是OpenGL ES API支持高性能的2D和3D圖形。OpenGL是一個跨平臺的圖形API,它爲3D圖形處理硬件指定了標準的軟件接口。OpenGL ES是適用於嵌入式設備的OpenGL規範。Android支持如下幾種OpenGL ES API版本:

  • OpenGL ES 1.0 and 1.1 - Android1.0及以上更高版本支持此規範
  • OpenGL ES 2.0 - Android2.2(API level 8)及以上更高版本支持此規範
  • OpenGL ES 3.0 - Android4.3(API level 18)及以上更高版本支持此規範
  • OpenGL ES 3.1 - Android 5.0 (API level 21)及以上更高版本支持此規範

注意: 在一個設備上支持OpenGL ES 3.0 API需要實現設備製造商提供的設備圖形管道,運行Android4.3或者更低Android版本的設備可能不支持OpenGL ES 3.0。要檢查支持什麼版本的OpenGL ES請參考如下代碼

private static double glVersion = 3.0;

private static class ContextFactory implements GLSurfaceView.EGLContextFactory {

  private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;

  public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {

      Log.w(TAG, "creating OpenGL ES " + glVersion + " context");
      int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, (int) glVersion, EGL10.EGL_NONE };
      // attempt to create a OpenGL ES 3.0 context
      EGLContext context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
      return context; // returns null if 3.0 is not supported;
  }
}

如果createContext()方法返回null,說明此設備不支持OpenGL ES 3.0,需要重新創建一個OpenGL ES 2.0的上下文。還有OpenGL ES 3.0向後兼容2.0的API.

3.OpenGL ES 使用

Android框架中有兩個基本的功能類GLSurfaceView和GLSurfaceView.Renderer,讓你通過OpenGL ES API創建和操作圖形。

3.1 GLSurfaceView

這是一個可以讓你通過調用OpenGL ES API繪製和操作對象的View,其功能類似於SurfaceView。你可以通過這個類創建一個GLSurfaceView實例並給它添加你自己的Renderer。如果你想捕獲屏幕的觸摸事件,就要擴展GLSurfaceView類實現它的觸摸監聽了,下文繪製圖形部分會介紹。

3.2 GLSurfaceView.Renderer

此接口定義了在GLSurfaceView中繪製的方法。你必須實現此接口作爲單獨的一個類使用,並使用GLSurfaceView.setRenderer()將此類添加到你的GLSurfaceView實例。
GLSurfaceView.Renderer接口需要實現下面幾個方法:

  • onSurfaceCreated(): 創建GLSurfaceView時,系統調用一次該方法。使用此方法執行只需要發生一次的操作,例如設置 OpenGL 環境參數或者初始化 OpenGL 圖形對象。
  • onDrawFrame(): 每次重繪 GLSurfaceView 時系統調用次方法。使用此方法作爲繪製(或重新繪製)圖形對象的主要方法。
  • onSurfaceChanged(): 當 GLSurfaceView 的幾何圖形發生變化時系統調用此方法,包括 GLSurfaceView 的大小或設備的屏幕方向改變。如設備從縱向變爲橫向時系統調用次方法。使用次方法響應 GLSurfaceView 容器的改變。

4.OpenGL ES 繪製圖形

下面我們用OpenGL ES 2.0 API來繪製一個三角形,這也是當前絕大部分設備推薦使用的API版本。

4.1 構建OpenGL ES 環境

爲了使用OpenGL ES 在你的應用程序中繪製圖形,必須爲它們創建一個視圖容器。其中最直接的方式就是實現GLSurfaceView和 GLSurfaceView.Renderer。GLSurfaceView 是用 OpenGL 繪製圖形的視圖容器,GLSurfaceView.Renderer 控制在該視圖內繪製的內容。
GLSurfaceView 是將圖形合併到你應用程序中的一種方式。對於全屏或接近全屏的圖形視圖,這是一種合理的選擇。想要在佈局的一小部分添加 OpenGL ES 視圖的開發人員應該看一下 TextureView。
下面使用 GLSurfaceView 和 GLSurfaceView.Renderer 在一個應用的Activity中做一個最簡單的完整實現。

4.1.1 在Manifest中聲明OpenGL ES使用

爲了讓你的應用程序能夠使用OpenGL ES 2.0的API,你必須添加以下聲明到manifest:

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

如果你的應用程序需要使用紋理壓縮,你還需要聲明你的應用程序需要支持哪種壓縮格式,以便他們安裝在兼容的設備上。

<supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture" />
<supports-gl-texture android:name="GL_OES_compressed_paletted_texture" />

關於更多的紋理壓縮格式信息,請參考OpenGL開發者引導。

4.1.2 創建一個Activity 展示OpenGL ES 圖形

使用OpenGL ES的應用程序的Activity和其他應用程的Activity一樣,不同的地方是你 Activity 的佈局設置,在許多其它的應用中你可能會使用 TextView, Button 和 ListView 等,但在使用 OpenGL ES 的APP中你還可以使用GLSurfaceView。
下面的代碼展示了使用GLSurfaceView做爲主視圖的基本實現:

public class OpenGLES20Activity extends Activity {

    private GLSurfaceView mGLView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create a GLSurfaceView instance and set it
        // as the ContentView for this Activity.
        mGLView = new MyGLSurfaceView(this);
        setContentView(mGLView);
    }
}
4.1.3 構建 GLSurfaceView 對象

GLSurfaceView 是一個特殊的View,在裏面可以繪製 OpenGL ES 圖形。它本身並沒有做太多的事情。主要的繪製對象的控制是通過設置到此View的渲染器 GLSurfaceView.Renderer 實現的。實際上,創建這個對象的代碼是很少的,你能會想嘗試跳過extends的操作,只去創建一個沒有被修改的GLSurfaceView實例,但是不建議這樣去做。在某些情況下你需要繼承這個類來捕獲觸摸事件,這個會在後面的文章裏講解。
GLSurfaceView的基本代碼很少,爲了快速的實現,通常會在使用它的Activity中創建一個內部類來做實現:

class MyGLSurfaceView extends GLSurfaceView {

    private final MyGLRenderer mRenderer;

    public MyGLSurfaceView(Context context){
        super(context);

        // Create an OpenGL ES 2.0 context
        setEGLContextClientVersion(2);

        mRenderer = new MyGLRenderer();

        // Set the Renderer for drawing on the GLSurfaceView
        setRenderer(mRenderer);
    }
}

GLSurfaceView 實現的另一個可選操作是設置渲染模式爲 GLSurfaceView.RENDERMODE_WHEN_DIRTY ,讓它僅在繪製數據改變時進行繪製視圖。

// Render the view only when there is a change in the drawing data
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

這個設置可以防止 GLSurfaceView 重繪直到你調用 requestRender()方法, 這對於此app是更有效的。

4.1.4 構建一個Renderer類

這個類控制着與它關聯的GLSurfaceView 繪製的內容。在renderer 裏面有三個方法能夠被Android系統調用,以便知道在 GLSurfaceView 上繪製什麼以及如何繪製。

  • onSurfaceCreated() - 在View的OpenGL環境被創建的時候調用
  • onDrawFrame() - 每一次View的重繪都會調用
  • onSurfaceChanged() - 如果視圖的幾何形狀發生變化(例如,當設備的屏幕方向改變時),則調用此方法

下面是使用OpenGL ES 渲染器的基本實現,僅僅做的事情就是在GLSurfaceView繪製一個黑色背景。

public class MyGLRenderer implements GLSurfaceView.Renderer {

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        // Set the background frame color
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    }

    public void onDrawFrame(GL10 unused) {
        // Redraw background color
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    }

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

4.2 定義圖形

OpenGL ES允許你使用三維空間座標系定義繪製的圖像,所以你在繪製一個三角形之前必須要先定義它的座標。在OpenGL中,這樣做的典型方法是爲座標定義浮點數的頂點數組。爲了獲得最大的效率,可以將這些座標寫入ByteBuffer,並傳遞到OpenGL ES圖形管道進行處理。

public class Triangle {

    private FloatBuffer vertexBuffer;

    // number of coordinates per vertex in this array
    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
    };

    // Set color with red, green, blue and alpha (opacity) values
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    public Triangle() {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (number of coordinate values * 4 bytes per float)
                triangleCoords.length * 4);
        // use the device hardware's native byte order
        bb.order(ByteOrder.nativeOrder());

        // create a floating point buffer from the ByteBuffer
        vertexBuffer = bb.asFloatBuffer();
        // add the coordinates to the FloatBuffer
        vertexBuffer.put(triangleCoords);
        // set the buffer to read the first coordinate
        vertexBuffer.position(0);
    }
}

4.3 繪製圖形

4.3.1 初始化形狀

在你做任何繪製操作之前,你必須要初始化並加載你準備繪製的形狀。除非形狀的結構(指原始的座標)在執行過程中發生改變,你都應該在你的Renderer的方法onSurfaceCreated()中進行內存和效率方面的初始化工作。

public class MyGLRenderer implements GLSurfaceView.Renderer {

    ...
    private Triangle mTriangle;
    private Square   mSquare;

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        ...
        // initialize a triangle
        mTriangle = new Triangle();
        // initialize a square
        mSquare = new Square();
    }
    ...
}
4.3.2 繪製形狀

使用OpenGLES 2.0畫一個定義好的形狀需要比較多的代碼,因爲你必須爲圖形渲染管線提供一大堆信息。特別的,你必須定義以下幾個東西:

  • Vertex Shader - 用於渲染形狀的頂點的OpenGLES 圖形代碼
  • Fragment Shader - 用於渲染形狀的外觀(顏色或紋理)的OpenGLES 代碼
  • Program - 一個OpenGLES對象,包含了你想要用來繪製一個或多個形狀的shader

你至少需要一個vertexshader來繪製一個形狀和一個fragmentshader來爲形狀上色。這些形狀必須被編譯然後被添加到一個OpenGLES program中,program之後被用來繪製形狀。下面是一個展示如何定義一個可以用來繪製形狀的基本shader的例子:

public class Triangle {

    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們包含了OpenGLShading Language (GLSL)代碼,必須在使用前編譯。要編譯這些代碼,在你的Renderer類中創建一個工具類方法:

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

    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    int shader = GLES20.glCreateShader(type);

    // add the source code to the shader and compile it
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);

    return shader;
}

爲了繪製你的形狀,你必須編譯shader代碼,添加它們到一個OpenGLES program 對象然後鏈接這個program。在renderer對象的構造器中做這些事情,從而只需做一次即可。

注:編譯OpenGLES shader們和鏈接linkingprogram們是很耗CPU的,所以你應該避免多次做這些事。如果在運行時你不知道shader的內容,你應該只創建一次code然後緩存它們以避免多次創建。

public class Triangle() {
    ...

    private final int mProgram;

    public Triangle() {
        ...

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

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram();

        // add the vertex shader to program
        GLES20.glAttachShader(mProgram, vertexShader);

        // add the fragment shader to program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // creates OpenGL ES program executables
        GLES20.glLinkProgram(mProgram);
    }
}

此時,你已經準備好增加真正的繪製調用了。需要爲渲染管線指定很多參數來告訴它你想畫什麼以及如何畫。因爲繪製操作因形狀而異,讓你的形狀類包含自己的繪製邏輯是個很好主意。

創建一個draw()方法負責繪製形狀。下面的代碼設置位置和顏色值到形狀的vertexshader和fragmentshader,然後執行繪製功能:

private int mPositionHandle;
private int mColorHandle;

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

public void draw() {
    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram);

    // get handle to vertex shader's vPosition member
    mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

    // Enable a handle to the triangle vertices
    GLES20.glEnableVertexAttribArray(mPositionHandle);

    // Prepare the triangle coordinate data
    GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                                 GLES20.GL_FLOAT, false,
                                 vertexStride, vertexBuffer);

    // get handle to fragment shader's vColor member
    mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

    // Set color for drawing the triangle
    GLES20.glUniform4fv(mColorHandle, 1, color, 0);

    // Draw the triangle
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

    // Disable vertex array
    GLES20.glDisableVertexAttribArray(mPositionHandle);
}

一旦完成了所有這些代碼,繪製該對象只需要在渲染器的onDrawFrame()方法中調用draw()方法:

public void onDrawFrame(GL10 unused) {
    ...

    mTriangle.draw();
}

When you run the application, it should look something like this:
在這裏插入圖片描述

4.4 應用投影和相機視圖

在OpenGL ES環境中,投影和攝像機視圖允許您以更接近您用眼睛看物理對象的方式顯示繪製對象。這種物理觀察模擬是通過繪製對象座標的數學變換完成的

  • Projection — 這個變換是基於他們所顯示的GLSurfaceView的寬和高來調整繪製對象的座標的。沒有這個計算變換,通過OpenGL繪製的形狀會在不同顯示窗口變形。這個投影變化通常只會在OpenGL view的比例被確定或者在你渲染器的onSurfaceChanged()方法中被計算。想要了解更多的關於投影和座標映射的相關信息,請看繪製對象的座標映射。
  • Camera View — 這個換是基於虛擬的相機的位置來調整繪製對象座標的。需要着重注意的是,OpenGL ES並沒有定義一個真實的相機對象,而是提供一個實用方法,通過變換繪製對象的顯示來模擬一個相機。相機視圖變換可能只會在你的GLSurfaceView被確定時被計算,或者基於用戶操作或你應用程序的功能來動態改變。
4.4.1 定義投影

投影變化的數據是在你GLSurfaceView.Renderer類的onSurfaceChanged()方法中被計算的。下面的示例代碼是獲取GLSurfaceView的高和寬,並通過Matrix.frustumM()方法用它們填充到投影變換矩陣中

// mMVPMatrix is an abbreviation for "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;

    // this projection matrix is applied to object coordinates
    // in the onDrawFrame() method
    Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}

上面的代碼填充有一個投影矩陣mProjectionMatrix,mProjectionMatrix可以在onFrameDraw()方法中與下一部分的相機視圖結合在一起。

注意:如果僅僅只把投影矩陣應用的到你繪製的對象中,通常你只會得到一個非常空的顯示。一般情況下,你還必須爲你要在屏幕上顯示的任何內容應用相機視圖。

4.4.2 定義相機視圖

通過在你的渲染器中添加相機視圖變換作爲你繪製過程的一部分來完成你的繪製圖像的變換過程。在下面的代碼中,通過Matrix.setLookAtM()方法計算相機視圖變換,然後將其與之前計算出的投影矩陣結合到一起。合併後的矩陣接下來會傳遞給繪製的圖形。

@Override
public void onDrawFrame(GL10 unused) {
    ...
    // Set the camera position (View matrix)
    Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

    // Calculate the projection and view transformation
    Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

    // Draw shape
    mTriangle.draw(mMVPMatrix);
}
4.4.3 應用投影和相機變換

爲了使用在上一部分內容中展示的投影和相機視圖變換的合併矩陣,首先要在之前Triangle類中定義的定點着色器代碼中添加一個矩陣變量:

public class Triangle {

    private final String vertexShaderCode =
        // This matrix member variable provides a hook to manipulate
        // the coordinates of the objects that use this vertex shader
        "uniform mat4 uMVPMatrix;" +
        "attribute vec4 vPosition;" +
        "void main() {" +
        // the matrix must be included as a modifier of gl_Position
        // Note that the uMVPMatrix factor *must be first* in order
        // for the matrix multiplication product to be correct.
        "  gl_Position = uMVPMatrix * vPosition;" +
        "}";

    // Use to access and set the view transformation
    private int mMVPMatrixHandle;

    ...
}

下一步,修改你的圖形對象的draw()方法來接收聯合變換矩陣,並將它們應用到圖形中:
public void draw(float[] mvpMatrix) { // pass in the calculated transformation matrix

// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

// Pass the projection and view transformation to the shader
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

// Draw the triangle
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);

}
一旦你正確的計算並應用投影和相機視圖變換,你的繪圖對象將會以正確的比例繪製,它看起來應該像下面這樣:
在這裏插入圖片描述

4.5 添加動作

在屏幕上繪製圖形只是OpenGL的相當基礎的特點,你也可以用其他的Android圖形框架類來實現這些,包括Canvas和Drawable對象。OpenGL ES爲在三維空間中移動和變換提供了額外的功能,並提供了創建引人注目的用戶體驗的獨特方式。

4.5.1 旋轉一個圖形

用OpenGL ES 2.0來旋轉一個繪製對象是相對簡單的。在你的渲染器中,添加一個新的變換矩陣(旋轉矩陣),然後把它與你的投影與相機視圖變換矩陣合併到一起:

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

    ...

    // Create a rotation transformation for the triangle
    long time = SystemClock.uptimeMillis() % 4000L;
    float angle = 0.090f * ((int) time);
    Matrix.setRotateM(mRotationMatrix, 0, angle, 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);
}

如果做了這些改變後你的三角形還沒有旋轉,請確保你是否註釋掉了GLSurfaceView.RENDERMODE_WHEN_DIRTY設置項,這將在下一部分講到。

4.5.2 允許連續渲染

如果你勤懇地遵循本系列課程的示例代碼到這個點,請確保你註釋了設置只有當dirty的時候才渲染的渲染模式這一行,否則OpenGL旋轉圖形,只會遞增角度然後等待來自GLSurfaceView容器的對requestRender()方法的調用:

public MyGLSurfaceView(Context context) extends GLSurfaceView {
    ...
    // Render the view only when there is a change in the drawing data.
    // To allow the triangle to rotate automatically, this line is commented out:
    //setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}

4.6 響應觸摸事件

像旋轉三角形一樣,通過預設程序來讓對象移動對於吸引注意是很有用的,但是如果你想讓你的OpenGL圖形有用戶交互呢?讓你的OpenGL ES應用有觸摸交互的關鍵是,擴展你的GLSurfaceView的實現重載onTouchEvent()方法來監聽觸摸事件。
本節內容將向你展示如何監聽觸摸事件來讓用戶旋轉一個圖形。

4.6.1 設置觸摸事件

爲了你的OpenGL ES應用能夠響應觸摸事件,你必須在你的GLSurfaceView中實現onTouchEvent()方法,下面的實現例子展示了怎樣監聽MotionEvent.ACTION_MOVE事件,並將該事件轉換成圖形的旋轉角度。

private final float TOUCH_SCALE_FACTOR = 180.0f / 320;
private float mPreviousX;
private float mPreviousY;

@Override
public boolean onTouchEvent(MotionEvent e) {
    // MotionEvent reports input details from the touch screen
    // and other input controls. In this case, you are only
    // interested in events where the touch position changed.

    float x = e.getX();
    float y = e.getY();

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

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

            // reverse direction of rotation above the mid-line
            if (y > getHeight() / 2) {
              dx = dx * -1 ;
            }

            // reverse direction of rotation 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;
}

需要注意的是,計算完旋轉角度後,需要調用requestRender()方法來告訴渲染器是時候渲染幀畫面了。在本例子中這種方法是最高效的,因爲除非旋轉有改變,否則幀畫面不需要重繪。然而除非你還用setRenderMode()方法要求渲染器只有在數據改變時才進行重繪,否則這對性能沒有任何影響。因此,確保渲染器中的下面這行是取消註釋的:

public MyGLSurfaceView(Context context) {
    ...
    // Render the view only when there is a change in the drawing data
    setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
4.6.2 暴露旋轉角度

上面的例程代碼中需要你通過在渲染器中添加共有的成員來暴露旋轉角度。當渲染代碼是在獨立於你應用程序的主用戶界面線程的單獨線程執行的時候,你必須聲明這個共有變量是volatile類型的。下面的代碼聲明瞭這個變量並且暴露了它的getter和setter方法對:

public class MyGLRenderer implements GLSurfaceView.Renderer {
    ...

    public volatile float mAngle;

    public float getAngle() {
        return mAngle;
    }

    public void setAngle(float angle) {
        mAngle = angle;
    }
}
4.6.3 應用旋轉

爲了應用觸摸輸入產生的旋轉,先註釋掉產生角度的代碼,並添加一個右觸摸事件產生的角度mAngle:

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

    // Create a rotation for the triangle
    // 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);
}

當你完成上面介紹的步驟,運行你的程序,然後在屏幕上拖拽你的手指來旋轉這個三角形。
在這裏插入圖片描述
代碼已上傳GitHub

5.參考資料

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