第一章 创建OpenGL ES的环境

首先需要为OpenGL ES创建一个视图(View)容器,一种实现方式是创建一个类实现GLSurfaceViewGLSurfaceView.Renderer。GLSurfaceView是显示图形的视图(View)容器,GLSurfaceView.Renderer是控制画图的方法。更多的介绍可以看OpenGL ES的开发指南

GLSurfaceView是多种集成OpenGL ES到应用方法中的一种,对于全屏或者近乎全屏的图形视图(graphics view)来说,应该选择这种方式。当用户只是对一小区域使用OpenGL ES绘制图形,那么可以选择TextureView

这篇文章只是简单的实现绘制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图形创建一个Activity

这个Activity与其他类型应用的Activity并无不同,要说不同,也仅仅是放到Activity的layout的view不一样,你需要放入的是一个GLSurfaceView
下面的代码展示了使用GLSurfaceView作为主视图的Activity的最少代码实现:

public class OpenGLES20Activity extends Activity {

    private GLSurfaceView mGLView;

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

        // 创建一个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 创建GLSurfaceView对象

GLSurfaceView是专门用来绘制OpenGL ES图形的,但是它自己没有做多少工作,真正的绘制是由GLSurfaceView.Renderer来完成的。所以GLSurfaceView的代码页不多,你可以直接使用GLSurfaceView,但是不要这样做,因为需要处理触摸事件,我们需要自定义一个类继承GLSurfaceView。
下面是一个简单的自定义类:

class MyGLSurfaceView extends GLSurfaceView {

    private final MyGLRenderer mRenderer;

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

        // 创建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,就是当有数据时才rendered或者主动调用了GLSurfaceView的requestRender.默认是连续模式,很显然Camera适合脏模式,一秒30帧,当有数据来时再渲染。

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

这里我们设置成为RENDERMODE_WHEN_DIRTY模式,当有数据时,我们调用requestRender()再进行绘制,这样效率比较高。

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

1.4 创建Renderer类

GLSurfaceView.Renderer控制向GLSurfaceView的绘制工作,它有三个方法被Android系统调用来计算在GLSurfaceView上画什么以及如何画:
* onSurfaceCreated():当GLSurfaceView被创建时,会调用一次,用于设置view的OpenGL ES环境。
* onDrawFrame():每次绘制图像的时候都会调用这个方法。
* onSurfaceChanged():当几何图形变化时,会调用此方法,例如,当手机屏幕大小变化时。

下面代码实现了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);
    }
}

以上就是所有需要做的东西了,上面的代码创建了一个简单的Android应用,它使用OpengGL 显示了一个黑色的屏幕。单这段代码并没有什么特殊之处,只是为使用OpenGL绘制做好准备。

第二章 定义形状

会定义在OpenGL ES View上所绘制的形状,是你创建高端图形应用杰作的第一步。如果你不懂OpenGL ES定义图形对象的一些基本只是,使用OpenGL ES可能会有点棘手。
本章解释OpenGL ES相对于Android设备屏幕的坐标系统、定义一个形状的基础只是、形状的外观、以及如何定义三角形和正方形。

2.1 定义一个三角形

OpenGL ES允许使用三维坐标系来绘制图像。在绘制图像之前,首先需要定义一个坐标系。然后,在OpenGL中,典型的做法是定义一个浮点类型的顶点数组来指向相应的坐标系。为了高效,将这个数组代表的坐标写入到ByteBuffer,ByteBuffer会传输到OpenGL ES图形管道中进行处理。

public class Triangle {

    private FloatBuffer vertexBuffer;

    // 数组中每个顶点的坐标数
    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() {
        // 为存放形状的坐标,初始化顶点字节缓冲
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (坐标数 * 4 )float 占四个字节
                triangleCoords.length * 4);
        // 使用设备的本点字节序
        bb.order(ByteOrder.nativeOrder());

        // 从ByteBuffer创建一个浮点缓冲
        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.

和绘制三角形一样,你需要定义一个逆时针绘制顶点的数组,并且将这个数组放入到ByteBuffer中。为了避免分别为两个三角形定义两个坐标数组,我们需要使用一个绘制列表列来告诉OpengGL ES图形管道怎么绘制这些顶点坐标,一下是具体的代码:

public class Square {

    private FloatBuffer vertexBuffer;
    private ShortBuffer drawListBuffer;

    // 每个顶点的坐标数
    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);
    }
}

这个例子告诉你创建OpenGL更复杂的形状需要什么。通常,使用三角形的集合来绘制对象,下一张,将会描述这些形状怎样绘制到屏幕上。

第三章 绘制形状

当你定义使用OpenGL绘制的形状之后,你可能想把它们绘制到屏幕上。使用OpenGL ES2.0绘制这些图形可能会比较麻烦,因为API提供了大量的功能来控制图形的渲染管道。

3.1初始化图形

在做任何绘制图形之前,你必须初始化形状然后再加载它。除非形状的结构(指原始坐标)在执行过程中发生改变,你需要在Renderer的方法onSurfaceCreated()中进行内存和效率方面的初始化工作。

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绘制一个定义好的图形需要大量的代码,因为需要必须提供图形渲染管道的许多细节,具体来说,你需要定义以下内容:
* 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环境中,为了编译着色器的代码,需要在你的Renderer类中创建一个工具类方法:

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

    // 创建一个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对象的构造函数中做这些事情,只需要定义一次就好。

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

        // 创建一个空的 OpenGL ES Program
        mProgram = GLES20.glCreateProgram();

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

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

        // 创建一个可执行的 OpenGL ES program 
        GLES20.glLinkProgram(mProgram);
    }
}

使用OpengGL ES绘制图形需要调用很多的函数和使用很多的参数,一个比较明智的做法是创建一个自己的着色类,来管理这些操作。
创建draw()函数来绘制图形。以下代码设置了顶点着色器和片段着色器的位置和颜色,然后执行绘制功能。

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 环境中
    GLES20.glUseProgram(mProgram);

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

    // 启用一个指向三角形的顶点数组的句柄
    GLES20.glEnableVertexAttribArray(mPositionHandle);

    // 准备三角形的坐标数据
    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);

    // 禁用指向三角形的定点数组
    GLES20.glDisableVertexAttribArray(mPositionHandle);
}

在renderer的onDrawFrame()方法中调用draw()就会绘制三角形了。

public void onDrawFrame(GL10 unused) {
    ...

    mTriangle.draw();
}

Triangle drawn without a projection or camera view

在上面的代码示例中有一些问题:
1. 给人留下的印象不深刻。
2. 当你改变屏幕的方向时,三角形有点压扁和变形。形状变形的的原因是由于顶点的租表没有根据GLSurfaceView的改变而改变,下一章节中会有解决方法。
3. 这个图形没有互动,也就是没有触摸事件。

在OpenGL ES环境中,投影和相机视图的方式展现出来的物体更像人眼中的物体。通过转换矩阵坐标的方式可以模拟出这种物理视图。

  • 投影-需要基于GLSurfaceView所展示界面的大小来进行坐标的调整。如果不这样,展示出来的图像的比例是不对的。一般情况是是在方法onSurfaceChanged()中进行计算的。
  • 相机视图-需要基于虚拟的相机位置来进行坐标的转换。OpenGL ES没有定义一个真实的相机对象,但是提供了方法来模拟相机。

第四章 应用投影和相机视图

在OpenGL ES环境中,投影和相机视图使你绘制的对象以更接近物理对象的样子显示。这是通过对坐标精确的数学变换实现的。

  • 投影-这种变化根据所在的GLSurfaceView的宽和高调整对象的坐标。如果没有此变化,对象会被不规则的视口扭曲。投射变换一般只需要在OpenGL view创建或者发生变化时调用,代码卸载renderer的onSurfaceChanged()方法中。
  • 相机视图-次变换是基于一个虚拟相机的位置调整对象的坐标。注意OpenGL ES并没有定义一个真的相机对象,而是提供了一些工具方法变换绘制对象的显示来模拟一个相机。一个相机视图的变换只可能在创建GLSurfaceView时调用,或者根据用户动作动态调用。

本章讲解了如何创建一个投影和一个相机视图,然后用刀GLSurfaceView的形状绘制过程。

4.1 定义投影(projection)

投影一般定义在GLSurfaceView.RendereronSurfaceChanged()方法中。下面的例子就是根据GLSurfaceView的宽和高来计算转换矩阵,使用了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()来计算相机视图转换,然后结合之前的投影矩阵。结合后的矩阵将之后传给要绘制的对象。

@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 使用投影和相机转换

为了使用前面合并后的投影和相机图像变换矩阵,需要在顶点着色器定义语句中添加以下代码:

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()方法添加参数,接受之前的矩阵:

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.

现在你拥有了一个正确显示形状的应用了,接下来应该给图形添加触摸事件了。

第五章 添加事件

在屏幕上绘制是OpengGL的基础能力,但是你也可以使用其他的Android图形框架类来做,包括Canvas和Drawable。但是OpenGL ES提供了另外的能力,可以再三维上移动和变换对象。总之它能创造很好的用户体验。在本文中,你将学会如何使用OpenGL ES为形状添加旋转功能。

5.1 旋转图形

在OpengGL ES 2.0中旋转一个图形,相对来说比较简单。在渲染的时候,创建另外一个转换矩阵(旋转矩阵),然后将这个矩阵合并到你的投影和相机图形变换矩阵就行了。

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

    // 把旋转矩阵合并到投影和相机矩阵
    // 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);
}

如果你的三角形在此新代码后旋转不起来,则要查看是否把GLSurfaceView.RENDERMODE_WHEN_DIRTY设置注释了。

5.2 持续的渲染

如果你已经跟着例子做到了这一步,确保GLSurfaceView的绘制模式不是dirty模式,否则,只有在调用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:
    // 注释掉,图形就可以自动旋转了
    //setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}

除非你不让对象与用户有交互,否则启用这个设置是一个好做法。要准备接触这句的注释了,因为下一章节会用到它。

第六章 响应触摸事件

使你的OpenGL ES应用能相应触摸的关键是扩展你实现的GLSurfaceView代码,覆写onTouchEvent()方法来监听触摸事件。
本章节向你展示如何监听用户的触摸事件以使用户可以旋转某个OpenGL ES对象。

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

从代码中可以看到,在计算完旋转的角度后,调用了requesetRender(),这是为了告诉renderer要渲染帧了。这样做的好处就是效率高,因为只有在旋转的时候才需要去渲染,其他时候不需要渲染。为了达到这种效果,我们需要设置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 添加旋转角度接口

我们需要在自定义的MyGLRenderer类中,定义旋转角度的接口,并且暴露出去。


public class MyGLRenderer implements GLSurfaceView.Renderer {
    ...

    public volatile float mAngle;

    public float getAngle() {
        return mAngle;
    }

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

6.3 旋转图形

将旋转的角度添加到渲染的代码中:

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的初步教程就叙述完了。

下载代码

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐