初識Android OpenGL ES

第一章 創(chuàng)建OpenGL ES的環(huán)境

首先需要為OpenGL ES創(chuàng)建一個視圖(View)容器,一種實現(xiàn)方式是創(chuàng)建一個類實現(xiàn)GLSurfaceViewGLSurfaceView.Renderer岗照。GLSurfaceView是顯示圖形的視圖(View)容器蚪拦,GLSurfaceView.Renderer是控制畫圖的方法吨凑。更多的介紹可以看OpenGL ES的開發(fā)指南

GLSurfaceView是多種集成OpenGL ES到應用方法中的一種柱徙,對于全屏或者近乎全屏的圖形視圖(graphics view)來說澎粟,應該選擇這種方式应闯。當用戶只是對一小區(qū)域使用OpenGL ES繪制圖形纤控,那么可以選擇TextureView

這篇文章只是簡單的實現(xiàn)繪制OpenGL ES視圖的功能碉纺。

1.1 在Manifest中聲明所使用到的OpenGL ES

如果你使用到OpenGL ES 2.0API船万,你需要在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" />

1.2 為OpenGL ES圖形創(chuàng)建一個Activity

這個Activity與其他類型應用的Activity并無不同,要說不同态贤,也僅僅是放到Activity的layout的view不一樣舱呻,你需要放入的是一個GLSurfaceView
下面的代碼展示了使用GLSurfaceView作為主視圖的Activity的最少代碼實現(xiàn):

public class OpenGLES20Activity extends Activity {

    private GLSurfaceView mGLView;

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

        // 創(chuàng)建一個GLSurfaceView實例然后設置為activity的ContentView.
        mGLView = new MyGLSurfaceView(this);
        setContentView(mGLView);
    }
}

Note: OpenGL ES 2.0要求Android2.2(API Level 8)或者更高,所以需要指定Android工程的目標API必須高于或者等于 API 8.

1.3 創(chuàng)建GLSurfaceView對象

GLSurfaceView是專門用來繪制OpenGL ES圖形的悠汽,但是它自己沒有做多少工作箱吕,真正的繪制是由GLSurfaceView.Renderer來完成的。所以GLSurfaceView的代碼頁不多柿冲,你可以直接使用GLSurfaceView茬高,但是不要這樣做,因為需要處理觸摸事件假抄,我們需要自定義一個類繼承GLSurfaceView怎栽。
下面是一個簡單的自定義類:

class MyGLSurfaceView extends GLSurfaceView {

    private final MyGLRenderer mRenderer;

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

        // 創(chuàng)建OpenGL ES 2.0 的上下文
        setEGLContextClientVersion(2);

        mRenderer = new MyGLRenderer();

        // 設置Renderer 到 GLSurfaceView
        setRenderer(mRenderer);
    }
}

除此之外你還需要設置GLSurfaceView繪制的方式,GLSurfaceView有兩種繪制方式宿饱,Google API:

When renderMode is RENDERMODE_CONTINUOUSLY, the renderer is called repeatedly to re-render the scene. When renderMode is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface is created, or when requestRender is called. Defaults to RENDERMODE_CONTINUOUSLY.

Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance by allowing the GPU and CPU to idle when the view does not need to be updated.

大意是RENDERMODE_CONTINUOUSLY模式就會一直Render熏瞄,如果設置成RENDERMODE_WHEN_DIRTY,就是當有數(shù)據(jù)時才rendered或者主動調(diào)用了GLSurfaceView的requestRender.默認是連續(xù)模式刑棵,很顯然Camera適合臟模式巴刻,一秒30幀,當有數(shù)據(jù)來時再渲染蛉签。

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

這里我們設置成為RENDERMODE_WHEN_DIRTY模式胡陪,當有數(shù)據(jù)時,我們調(diào)用requestRender()再進行繪制碍舍,這樣效率比較高柠座。

public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        // TODO Auto-generated method stub
        this.requestRender();
    }

1.4 創(chuàng)建Renderer類

GLSurfaceView.Renderer控制向GLSurfaceView的繪制工作,它有三個方法被Android系統(tǒng)調(diào)用來計算在GLSurfaceView上畫什么以及如何畫:

  • onSurfaceCreated():當GLSurfaceView被創(chuàng)建時片橡,會調(diào)用一次妈经,用于設置view的OpenGL ES環(huán)境。
  • onDrawFrame():每次繪制圖像的時候都會調(diào)用這個方法。
  • onSurfaceChanged():當幾何圖形變化時吹泡,會調(diào)用此方法骤星,例如,當手機屏幕大小變化時爆哑。

下面代碼實現(xiàn)了GLSurfaceView.Renderer最基本功能洞难,它僅在GLSurfaceView上畫了一個黑色的背景。

public class MyGLRenderer implements GLSurfaceView.Renderer {

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        // 設置背景的顏色
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 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);
    }
}

以上就是所有需要做的東西了揭朝,上面的代碼創(chuàng)建了一個簡單的Android應用队贱,它使用OpengGL 顯示了一個黑色的屏幕。單這段代碼并沒有什么特殊之處潭袱,只是為使用OpenGL繪制做好準備柱嫌。

第二章 定義形狀

會定義在OpenGL ES View上所繪制的形狀,是你創(chuàng)建高端圖形應用杰作的第一步屯换。如果你不懂OpenGL ES定義圖形對象的一些基本只是编丘,使用OpenGL ES可能會有點棘手。
本章解釋OpenGL ES相對于Android設備屏幕的坐標系統(tǒng)彤悔、定義一個形狀的基礎只是瘪吏、形狀的外觀、以及如何定義三角形和正方形蜗巧。

2.1 定義一個三角形

OpenGL ES允許使用三維坐標系來繪制圖像掌眠。在繪制圖像之前,首先需要定義一個坐標系幕屹。然后蓝丙,在OpenGL中,典型的做法是定義一個浮點類型的頂點數(shù)組來指向相應的坐標系望拖。為了高效渺尘,將這個數(shù)組代表的坐標寫入到ByteBuffer,ByteBuffer會傳輸?shù)絆penGL ES圖形管道中進行處理说敏。

public class Triangle {

    private FloatBuffer vertexBuffer;

    // 數(shù)組中每個頂點的坐標數(shù)
    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
    };

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

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

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

默認情況下盔沫,OpenGL ES將坐標[0,0,0](X,Y,Z)當做坐標軸的中心医咨,[1,1,0]是坐標軸的右上角,[-1,-1,0]是坐標軸的坐下角架诞。
繪制的順序是非常重要的拟淮,繪制定義了哪邊是正面的形狀,哪邊是反面的形狀谴忧,一般情況下繪制的順序是逆時針的方向很泊,使用OpenGL ES的cull face特性角虫,你可以只畫正面而不畫反面。

2.2 繪制正方形

在OpenGL上繪制三角形還是比較容易的委造,但是繪制正方形戳鹅,相對來說就有一些麻煩了,繪制一個正方形一般的做法是昏兆,將兩個三角形繪制在一起粉楚,如下圖所示:


Drawing a square using two triangles.

和繪制三角形一樣,你需要定義一個逆時針繪制頂點的數(shù)組亮垫,并且將這個數(shù)組放入到ByteBuffer中。為了避免分別為兩個三角形定義兩個坐標數(shù)組伟骨,我們需要使用一個繪制列表列來告訴OpengGL ES圖形管道怎么繪制這些頂點坐標饮潦,一下是具體的代碼:

public class Square {

    private FloatBuffer vertexBuffer;
    private ShortBuffer drawListBuffer;

    // 每個頂點的坐標數(shù)
    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(
        // (# of coordinate values * 4 bytes per float)
                squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

        // initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(
        // (# of coordinate values * 2 bytes per short)
                drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
    }
}

這個例子告訴你創(chuàng)建OpenGL更復雜的形狀需要什么。通常携狭,使用三角形的集合來繪制對象继蜡,下一張,將會描述這些形狀怎樣繪制到屏幕上逛腿。

第三章 繪制形狀

當你定義使用OpenGL繪制的形狀之后稀并,你可能想把它們繪制到屏幕上。使用OpenGL ES2.0繪制這些圖形可能會比較麻煩单默,因為API提供了大量的功能來控制圖形的渲染管道碘举。

3.1初始化圖形

在做任何繪制圖形之前,你必須初始化形狀然后再加載它搁廓。除非形狀的結構(指原始坐標)在執(zhí)行過程中發(fā)生改變引颈,你需要在Renderer的方法onSurfaceCreated()中進行內(nèi)存和效率方面的初始化工作。

public class MyGLRenderer implements GLSurfaceView.Renderer {

    ...
    private Triangle mTriangle;
    private Square   mSquare;

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        ...

        // 初始化一個三角形
        mTriangle = new Triangle();
        // 初始化一個正方形
        mSquare = new Square();
    }
    ...
}

3.2 繪制圖形

使用OpenGL ES 2.0繪制一個定義好的圖形需要大量的代碼境蜕,因為需要必須提供圖形渲染管道的許多細節(jié)蝙场,具體來說,你需要定義以下內(nèi)容:

  • vertex Shader - 頂點著色器粱年,用來繪制圖形的形狀
  • Fragment Shader - 片段著色器售滤,用來繪制圖形的顏色或者是紋理
  • Program - 一個OpenGL ES對象,包含了用來繪制一個或者多個形狀的shader台诗。
    你需要定義至少一個頂點著色器和片段著色器完箩,這些形狀必須被編譯然后被添加到一個OpenGL ES program中,program之后會被用來繪制形狀拉队。下面代碼是繪制三角形時定義的著色器語言:
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)語言需要編譯到OpenGL ES環(huán)境中嗜憔,為了編譯著色器的代碼,需要在你的Renderer類中創(chuàng)建一個工具類方法:

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

    // 創(chuàng)建一個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;
}

為了繪制你的形狀氏仗,必須編譯shader代碼吉捶,將著色器添加到OpengGL ES program對象中夺鲜,然后鏈接到program,在renderer對象的構造函數(shù)中做這些事情呐舔,只需要定義一次就好币励。

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);

        // 創(chuàng)建一個空的 OpenGL ES Program
        mProgram = GLES20.glCreateProgram();

        // 將vertex shader 添加到 program
        GLES20.glAttachShader(mProgram, vertexShader);

        // 將fragment shader 添加到 program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // 創(chuàng)建一個可執(zhí)行的 OpenGL ES program 
        GLES20.glLinkProgram(mProgram);
    }
}

使用OpengGL ES繪制圖形需要調(diào)用很多的函數(shù)和使用很多的參數(shù),一個比較明智的做法是創(chuàng)建一個自己的著色類珊拼,來管理這些操作食呻。
創(chuàng)建draw()函數(shù)來繪制圖形。以下代碼設置了頂點著色器和片段著色器的位置和顏色澎现,然后執(zhí)行繪制功能仅胞。

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() {
    // 將program 添加到 OpenGL ES 環(huán)境中
    GLES20.glUseProgram(mProgram);

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

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

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

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

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

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

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

在renderer的onDrawFrame()方法中調(diào)用draw()就會繪制三角形了。

public void onDrawFrame(GL10 unused) {
    ...

    mTriangle.draw();
}
Triangle drawn without a projection or camera view

在上面的代碼示例中有一些問題:

  1. 給人留下的印象不深刻剑辫。
  2. 當你改變屏幕的方向時干旧,三角形有點壓扁和變形。形狀變形的的原因是由于頂點的租表沒有根據(jù)GLSurfaceView的改變而改變妹蔽,下一章節(jié)中會有解決方法椎眯。
  3. 這個圖形沒有互動,也就是沒有觸摸事件胳岂。

在OpenGL ES環(huán)境中编整,投影和相機視圖的方式展現(xiàn)出來的物體更像人眼中的物體。通過轉(zhuǎn)換矩陣坐標的方式可以模擬出這種物理視圖乳丰。

  • 投影-需要基于GLSurfaceView所展示界面的大小來進行坐標的調(diào)整掌测。如果不這樣,展示出來的圖像的比例是不對的产园。一般情況是是在方法onSurfaceChanged()中進行計算的赏半。
  • 相機視圖-需要基于虛擬的相機位置來進行坐標的轉(zhuǎn)換。OpenGL ES沒有定義一個真實的相機對象淆两,但是提供了方法來模擬相機断箫。

第四章 應用投影和相機視圖

在OpenGL ES環(huán)境中,投影和相機視圖使你繪制的對象以更接近物理對象的樣子顯示秋冰。這是通過對坐標精確的數(shù)學變換實現(xiàn)的仲义。

  • 投影-這種變化根據(jù)所在的GLSurfaceView的寬和高調(diào)整對象的坐標。如果沒有此變化剑勾,對象會被不規(guī)則的視口扭曲埃撵。投射變換一般只需要在OpenGL view創(chuàng)建或者發(fā)生變化時調(diào)用,代碼卸載renderer的onSurfaceChanged()方法中虽另。
  • 相機視圖-次變換是基于一個虛擬相機的位置調(diào)整對象的坐標暂刘。注意OpenGL ES并沒有定義一個真的相機對象,而是提供了一些工具方法變換繪制對象的顯示來模擬一個相機捂刺。一個相機視圖的變換只可能在創(chuàng)建GLSurfaceView時調(diào)用谣拣,或者根據(jù)用戶動作動態(tài)調(diào)用募寨。

本章講解了如何創(chuàng)建一個投影和一個相機視圖,然后用刀GLSurfaceView的形狀繪制過程森缠。

4.1 定義投影(projection)

投影一般定義在GLSurfaceView.RendereronSurfaceChanged()方法中拔鹰。下面的例子就是根據(jù)GLSurfaceView的寬和高來計算轉(zhuǎn)換矩陣,使用了Matrix.frustumM()方法來計算出了一個投影變化Matrix贵涵。

// 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;

    // 此投影矩陣在onDrawFrame()中將應用到對象的坐標 
    Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}

以上代碼生成了一個投影矩陣列肢,mProjectionMatrixonDrawFrame()方法中和相機視圖結合起來。

只對你的對象應用一個投影變換一般會導致什么也看不到宾茂。通常瓷马,你必須也對其應用一個視圖變換才能看到東西。

4.2 定義相機視圖

再定義一個相機視圖變換以使對繪制對象的變換處理變的完整跨晴。在下面的代碼中欧聘,使用Matrix.setLookAtM()來計算相機視圖轉(zhuǎn)換,然后結合之前的投影矩陣坟奥。結合后的矩陣將之后傳給要繪制的對象。

@Override
public void onDrawFrame(GL10 unused) {
    ...
    // 設置相機的位置 (視圖矩陣)
    Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

    // 計算投影和視圖變換
    Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

    // 繪制形狀
    mTriangle.draw(mMVPMatrix);
}

4.3 使用投影和相機轉(zhuǎn)換

為了使用前面合并后的投影和相機圖像變換矩陣拇厢,需要在頂點著色器定義語句中添加以下代碼:

public class Triangle {

    private final String vertexShaderCode =
        //這個矩陣成員變量提供了一個勾子來操控  
        // 使用這個頂點著色器的對象的坐標 
        "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()方法添加參數(shù),接受之前的矩陣:

public void draw(float[] mvpMatrix) { // 傳遞計算出來的變換矩陣
    ...

    // 獲得形狀的變換矩陣的句柄
    mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

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

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

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

做完以上步驟后孝偎,就可以得到一個正確的圖形了访敌。


Triangle drawn with a projection and camera view applied.

現(xiàn)在你擁有了一個正確顯示形狀的應用了,接下來應該給圖形添加觸摸事件了衣盾。

第五章 添加事件

在屏幕上繪制是OpengGL的基礎能力寺旺,但是你也可以使用其他的Android圖形框架類來做,包括Canvas和Drawable势决。但是OpenGL ES提供了另外的能力阻塑,可以再三維上移動和變換對象」矗總之它能創(chuàng)造很好的用戶體驗陈莽。在本文中,你將學會如何使用OpenGL ES為形狀添加旋轉(zhuǎn)功能虽抄。

5.1 旋轉(zhuǎn)圖形

在OpengGL ES 2.0中旋轉(zhuǎn)一個圖形走搁,相對來說比較簡單。在渲染的時候迈窟,創(chuàng)建另外一個轉(zhuǎn)換矩陣(旋轉(zhuǎn)矩陣)私植,然后將這個矩陣合并到你的投影和相機圖形變換矩陣就行了。

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

    ...

    // 為三角形創(chuàng)建一個旋轉(zhuǎn)變化
    long time = SystemClock.uptimeMillis() % 4000L;
    float angle = 0.090f * ((int) time);
    Matrix.setRotateM(mRotationMatrix, 0, angle, 0, 0, -1.0f);

    // 把旋轉(zhuǎn)矩陣合并到投影和相機矩陣
    // 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);

    // 畫三角形
    mTriangle.draw(scratch);
}

如果你的三角形在此新代碼后旋轉(zhuǎn)不起來车酣,則要查看是否把GLSurfaceView.RENDERMODE_WHEN_DIRTY設置注釋了曲稼。

5.2 持續(xù)的渲染

如果你已經(jīng)跟著例子做到了這一步索绪,確保GLSurfaceView的繪制模式不是dirty模式,否則躯肌,只有在調(diào)用requestRender()時者春,GLSurfaceView才會渲染。

public MyGLSurfaceView(Context context) {
    ...
    // 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:
    // 注釋掉清女,圖形就可以自動旋轉(zhuǎn)了
    //setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}

除非你不讓對象與用戶有交互钱烟,否則啟用這個設置是一個好做法。要準備接觸這句的注釋了嫡丙,因為下一章節(jié)會用到它拴袭。

第六章 響應觸摸事件

使你的OpenGL ES應用能相應觸摸的關鍵是擴展你實現(xiàn)的GLSurfaceView代碼,覆寫onTouchEvent()方法來監(jiān)聽觸摸事件曙博。
本章節(jié)向你展示如何監(jiān)聽用戶的觸摸事件以使用戶可以旋轉(zhuǎn)某個OpenGL ES對象拥刻。

6.1 設置觸摸監(jiān)聽事件

為了使你的OpenGL ES應用響應觸摸事件,你必須在GLSurfaceView類中實現(xiàn)onTouchEvent()方法父泳。以下代碼在MOtionEvent.ACTION_MOVE事件中計算了圖形旋轉(zhuǎn)的角度般哼。

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

從代碼中可以看到,在計算完旋轉(zhuǎn)的角度后惠窄,調(diào)用了requesetRender()蒸眠,這是為了告訴renderer要渲染幀了。這樣做的好處就是效率高杆融,因為只有在旋轉(zhuǎn)的時候才需要去渲染楞卡,其他時候不需要渲染。為了達到這種效果脾歇,我們需要設置GLSurfaceView的渲染模式蒋腮。

public MyGLSurfaceView(Context context) {
    ...
    // Render the view only when there is a change in the drawing data
    setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}

6.2 添加旋轉(zhuǎn)角度接口

我們需要在自定義的MyGLRenderer類中,定義旋轉(zhuǎn)角度的接口藕各,并且暴露出去池摧。


public class MyGLRenderer implements GLSurfaceView.Renderer {
    ...

    public volatile float mAngle;

    public float getAngle() {
        return mAngle;
    }

    public void setAngle(float angle) {
        mAngle = angle;
    }
}

6.3 旋轉(zhuǎn)圖形

將旋轉(zhuǎn)的角度添加到渲染的代碼中:

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);
}
Triangle being rotated with touch input (circle shows touch location).

至此,OpenGL ES的初步教程就敘述完了激况。

下載代碼

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末险绘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子誉碴,更是在濱河造成了極大的恐慌宦棺,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黔帕,死亡現(xiàn)場離奇詭異代咸,居然都是意外死亡,警方通過查閱死者的電腦和手機成黄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門呐芥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逻杖,“玉大人,你說我怎么就攤上這事思瘟≥┌伲” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵滨攻,是天一觀的道長够话。 經(jīng)常有香客問我,道長光绕,這世上最難降的妖魔是什么女嘲? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮诞帐,結果婚禮上欣尼,老公的妹妹穿的比我還像新娘。我一直安慰自己停蕉,他們只是感情好愕鼓,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著慧起,像睡著了一般菇晃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上完慧,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天谋旦,我揣著相機與錄音剩失,去河邊找鬼屈尼。 笑死,一個胖子當著我的面吹牛拴孤,可吹牛的內(nèi)容都是我干的脾歧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼演熟,長吁一口氣:“原來是場噩夢啊……” “哼鞭执!你這毒婦竟也來了?” 一聲冷哼從身側響起芒粹,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤兄纺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后化漆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體估脆,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年座云,在試婚紗的時候發(fā)現(xiàn)自己被綠了疙赠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片付材。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖圃阳,靈堂內(nèi)的尸體忽然破棺而出厌衔,到底是詐尸還是另有隱情,我是刑警寧澤捍岳,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布富寿,位于F島的核電站,受9級特大地震影響祟同,放射性物質(zhì)發(fā)生泄漏作喘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一晕城、第九天 我趴在偏房一處隱蔽的房頂上張望泞坦。 院中可真熱鬧,春花似錦砖顷、人聲如沸贰锁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豌熄。三九已至,卻和暖如春物咳,著一層夾襖步出監(jiān)牢的瞬間锣险,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工览闰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留芯肤,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓压鉴,卻偏偏與公主長得像崖咨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子油吭,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容