开发文章

使用OpenGL ES显示图形

第一章 创建OpenGL ES的环境

首先需要为OpenGL ES创建一个视图(View)容器,一种实现方式是创建一个类实现GLSurfaceView和GLSurfaceView.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的最少代码实现:

复制内容到剪贴板
  1. public class OpenGLES20Activity extends Activity {  
  2.   
  3.     private GLSurfaceView mGLView;  
  4.   
  5.     @Override  
  6.     public void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.   
  9.         // 创建一个GLSurfaceView实例然后设置为activity的ContentView.  
  10.         mGLView = new MyGLSurfaceView(this);  
  11.         setContentView(mGLView);  
  12.     }  
  13. }  

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。
下面是一个简单的自定义类:

复制内容到剪贴板
  1. class MyGLSurfaceView extends GLSurfaceView {  
  2.   
  3.     private final MyGLRenderer mRenderer;  
  4.   
  5.     public MyGLSurfaceView(Context context){  
  6.         super(context);  
  7.   
  8.         // 创建OpenGL ES 2.0 的上下文  
  9.         setEGLContextClientVersion(2);  
  10.   
  11.         mRenderer = new MyGLRenderer();  
  12.   
  13.         // 设置Renderer 到 GLSurfaceView  
  14.         setRenderer(mRenderer);  
  15.     }  
  16. }  

除此之外你还需要设置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()再进行绘制,这样效率比较高。

复制内容到剪贴板
  1. public void onFrameAvailable(SurfaceTexture surfaceTexture) {  
  2.         // TODO Auto-generated method stub  
  3.         this.requestRender();  
  4.     }  

1.4 创建Renderer类

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

下面代码实现了GLSurfaceView.Renderer最基本功能,它仅在GLSurfaceView上画了一个黑色的背景。

复制内容到剪贴板
  1. public class MyGLRenderer implements GLSurfaceView.Renderer {  
  2.   
  3.     public void onSurfaceCreated(GL10 unused, EGLConfig config) {  
  4.         // 设置背景的颜色  
  5.         GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);  
  6.     }  
  7.   
  8.     public void onDrawFrame(GL10 unused) {  
  9.         // 重绘背景颜色  
  10.         GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);  
  11.     }  
  12.   
  13.     public void onSurfaceChanged(GL10 unused, int width, int height) {  
  14.         GLES20.glViewport(00, width, height);  
  15.     }  
  16. }  

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

第二章 定义形状

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

2.1 定义一个三角形

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

复制内容到剪贴板
  1. public class Triangle {  
  2.   
  3.     private FloatBuffer vertexBuffer;  
  4.   
  5.     // 数组中每个顶点的坐标数  
  6.     static final int COORDS_PER_VERTEX = 3;  
  7.     static float triangleCoords[] = {   // 按照逆时针方向:  
  8.              0.0f,  0.622008459f, 0.0f, // top  
  9.             -0.5f, -0.311004243f, 0.0f, // bottom left  
  10.              0.5f, -0.311004243f, 0.0f  // bottom right  
  11.     };  
  12.   
  13.     // 设置颜色RGBA(red green blue alpha)  
  14.     float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };  
  15.   
  16.     public Triangle() {  
  17.         // 为存放形状的坐标,初始化顶点字节缓冲  
  18.         ByteBuffer bb = ByteBuffer.allocateDirect(  
  19.                 // (坐标数 * 4 )float 占四个字节  
  20.                 triangleCoords.length * 4);  
  21.         // 使用设备的本点字节序  
  22.         bb.order(ByteOrder.nativeOrder());  
  23.   
  24.         // 从ByteBuffer创建一个浮点缓冲  
  25.         vertexBuffer = bb.asFloatBuffer();  
  26.         // 把坐标加入FloatBuffer中  
  27.         vertexBuffer.put(triangleCoords);  
  28.         // 设置buffer,从第一个坐标开始读  
  29.         vertexBuffer.position(0);  
  30.     }  
  31. }  

默认情况下,OpenGL ES将坐标[0,0,0](X,Y,Z)当做坐标轴的中心,[1,1,0]是坐标轴的右上角,[-1,-1,0]是坐标轴的坐下角。
绘制的顺序是非常重要的,绘制定义了哪边是正面的形状,哪边是反面的形状,一般情况下绘制的顺序是逆时针的方向,使用OpenGL ES的cull face特性,你可以只画正面而不画反面。

2.2 绘制正方形

在OpenGL上绘制三角形还是比较容易的,但是绘制正方形,相对来说就有一些麻烦了,绘制一个正方形一般的做法是,将两个三角形绘制在一起,如下图所示:

绘制正方形.png

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

复制内容到剪贴板
  1. public class Square {  
  2.   
  3.     private FloatBuffer vertexBuffer;  
  4.     private ShortBuffer drawListBuffer;  
  5.   
  6.     // 每个顶点的坐标数  
  7.     static final int COORDS_PER_VERTEX = 3;  
  8.     static float squareCoords[] = {  
  9.             -0.5f,  0.5f, 0.0f,   // top left  
  10.             -0.5f, -0.5f, 0.0f,   // bottom left  
  11.              0.5f, -0.5f, 0.0f,   // bottom right  
  12.              0.5f,  0.5f, 0.0f }; // top right  
  13.   
  14.     private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // 绘制顶点的顺序   
  15.   
  16.     public Square() {  
  17.         // initialize vertex byte buffer for shape coordinates  
  18.         ByteBuffer bb = ByteBuffer.allocateDirect(  
  19.         // (# of coordinate values * 4 bytes per float)  
  20.                 squareCoords.length * 4);  
  21.         bb.order(ByteOrder.nativeOrder());  
  22.         vertexBuffer = bb.asFloatBuffer();  
  23.         vertexBuffer.put(squareCoords);  
  24.         vertexBuffer.position(0);  
  25.   
  26.         // initialize byte buffer for the draw list  
  27.         ByteBuffer dlb = ByteBuffer.allocateDirect(  
  28.         // (# of coordinate values * 2 bytes per short)  
  29.                 drawOrder.length * 2);  
  30.         dlb.order(ByteOrder.nativeOrder());  
  31.         drawListBuffer = dlb.asShortBuffer();  
  32.         drawListBuffer.put(drawOrder);  
  33.         drawListBuffer.position(0);  
  34.     }  
  35. }  

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

第三章 绘制形状

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

3.1初始化图形

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

复制内容到剪贴板
  1. public class MyGLRenderer implements GLSurfaceView.Renderer {  
  2.   
  3.     ...  
  4.     private Triangle mTriangle;  
  5.     private Square   mSquare;  
  6.   
  7.     public void onSurfaceCreated(GL10 unused, EGLConfig config) {  
  8.         ...  
  9.   
  10.         // 初始化一个三角形  
  11.         mTriangle = new Triangle();  
  12.         // 初始化一个正方形  
  13.         mSquare = new Square();  
  14.     }  
  15.     ...  
  16. }  

3.2 绘制图形

使用OpenGL ES 2.0绘制一个定义好的图形需要大量的代码,因为需要必须提供图形渲染管道的许多细节,具体来说,你需要定义以下内容:
* vertex Shader - 顶点着色器,用来绘制图形的形状
* Fragment Shader - 片段着色器,用来绘制图形的颜色或者是纹理
* Program - 一个OpenGL ES对象,包含了用来绘制一个或者多个形状的shader。
你需要定义至少一个顶点着色器和片段着色器,这些形状必须被编译然后被添加到一个OpenGL ES program中,program之后会被用来绘制形状。下面代码是绘制三角形时定义的着色器语言:

复制内容到剪贴板
  1. public class Triangle {  
  2.   
  3.     private final String vertexShaderCode =  
  4.         "attribute vec4 vPosition;" +  
  5.         "void main() {" +  
  6.         "  gl_Position = vPosition;" +  
  7.         "}";  
  8.   
  9.     private final String fragmentShaderCode =  
  10.         "precision mediump float;" +  
  11.         "uniform vec4 vColor;" +  
  12.         "void main() {" +  
  13.         "  gl_FragColor = vColor;" +  
  14.         "}";  
  15.   
  16.     ...  
  17. }  

 着色器(shader)语言需要编译到OpenGL ES环境中,为了编译着色器的代码,需要在你的Renderer类中创建一个工具类方法:

复制内容到剪贴板
  1. public static int loadShader(int type, String shaderCode){  
  2.   
  3.     // 创建一个vertex shader 类型 (GLES20.GL_VERTEX_SHADER)  
  4.     // 或者一个 fragment shader 类型(GLES20.GL_FRAGMENT_SHADER)  
  5.     int shader = GLES20.glCreateShader(type);  
  6.   
  7.     // 将源码添加到shader并编译  
  8.     GLES20.glShaderSource(shader, shaderCode);  
  9.     GLES20.glCompileShader(shader);  
  10.   
  11.     return shader;  
  12. }  

为了绘制你的形状,必须编译shader代码,将着色器添加到OpengGL ES program对象中,然后链接到program,在renderer对象的构造函数中做这些事情,只需要定义一次就好。

复制内容到剪贴板
  1. public class Triangle() {  
  2.     ...  
  3.   
  4.     private final int mProgram;  
  5.   
  6.     public Triangle() {  
  7.         ...  
  8.   
  9.         int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,  
  10.                                         vertexShaderCode);  
  11.         int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,  
  12.                                         fragmentShaderCode);  
  13.   
  14.         // 创建一个空的 OpenGL ES Program  
  15.         mProgram = GLES20.glCreateProgram();  
  16.   
  17.         // 将vertex shader 添加到 program  
  18.         GLES20.glAttachShader(mProgram, vertexShader);  
  19.   
  20.         // 将fragment shader 添加到 program  
  21.         GLES20.glAttachShader(mProgram, fragmentShader);  
  22.   
  23.         // 创建一个可执行的 OpenGL ES program   
  24.         GLES20.glLinkProgram(mProgram);  
  25.     }  
  26. }  

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

复制内容到剪贴板
  1. private int mPositionHandle;  
  2. private int mColorHandle;  
  3.   
  4. private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;  
  5. private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex  
  6.   
  7. public void draw() {  
  8.     // 将program 添加到 OpenGL ES 环境中  
  9.     GLES20.glUseProgram(mProgram);  
  10.   
  11.     // 获取指向vertex shader的成员vPosition的句柄  
  12.     mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");  
  13.   
  14.     // 启用一个指向三角形的顶点数组的句柄  
  15.     GLES20.glEnableVertexAttribArray(mPositionHandle);  
  16.   
  17.     // 准备三角形的坐标数据  
  18.     GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,  
  19.                                  GLES20.GL_FLOAT, false,  
  20.                                  vertexStride, vertexBuffer);  
  21.   
  22.     // 获取指向fragment shader的成员vColor的句柄  
  23.     mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");  
  24.   
  25.     // 设置三角形的颜色  
  26.     GLES20.glUniform4fv(mColorHandle, 1, color, 0);  
  27.   
  28.     // 画三角形  
  29.     GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);  
  30.   
  31.     // 禁用指向三角形的定点数组  
  32.     GLES20.glDisableVertexAttribArray(mPositionHandle);  
  33. }  

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

复制内容到剪贴板
  1. public void onDrawFrame(GL10 unused) {  
  2.     ...  
  3.   
  4.     mTriangle.draw();  
  5. }  

OpenGL画三角形.png

 

在上面的代码示例中有一些问题:
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。

复制内容到剪贴板
  1. // mMVPMatrix is an abbreviation for "Model View Projection Matrix"  
  2. private final float[] mMVPMatrix = new float[16];  
  3. private final float[] mProjectionMatrix = new float[16];  
  4. private final float[] mViewMatrix = new float[16];  
  5.   
  6. @Override  
  7. public void onSurfaceChanged(GL10 unused, int width, int height) {  
  8.     GLES20.glViewport(0, 0, width, height);  
  9.   
  10.     float ratio = (float) width / height;  
  11.   
  12.     // 此投影矩阵在onDrawFrame()中将应用到对象的坐标   
  13.     Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);  
  14. }  

以上代码生成了一个投影矩阵,mProjectionMatrixonDrawFrame()方法中和相机视图结合起来。

只对你的对象应用一个投影变换一般会导致什么也看不到。通常,你必须也对其应用一个视图变换才能看到东西。

4.2 定义相机视图

再定义一个相机视图变换以使对绘制对象的变换处理变的完整。在下面的代码中,使用Matrix.setLookAtM()来计算相机视图转换,然后结合之前的投影矩阵。结合后的矩阵将之后传给要绘制的对象。

复制内容到剪贴板
  1. @Override  
  2. public void onDrawFrame(GL10 unused) {  
  3.     ...  
  4.     // 设置相机的位置 (视图矩阵)  
  5.     Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);  
  6.   
  7.     // 计算投影和视图变换  
  8.     Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);  
  9.   
  10.     // 绘制形状  
  11.     mTriangle.draw(mMVPMatrix);  
  12. }  

4.3 使用投影和相机转换

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

复制内容到剪贴板
  1. public class Triangle {  
  2.   
  3.     private final String vertexShaderCode =  
  4.         //这个矩阵成员变量提供了一个勾子来操控    
  5.         // 使用这个顶点着色器的对象的坐标   
  6.         "uniform mat4 uMVPMatrix;" +  
  7.         "attribute vec4 vPosition;" +  
  8.         "void main() {" +  
  9.         // the matrix must be included as a modifier of gl_Position  
  10.         // Note that the uMVPMatrix factor *must be first* in order  
  11.         // for the matrix multiplication product to be correct.  
  12.         "  gl_Position = uMVPMatrix * vPosition;" +  
  13.         "}";  
  14.   
  15.     // Use to access and set the view transformation  
  16.     private int mMVPMatrixHandle;  
  17.   
  18.     ...  
  19. }  

接下来,给draw()方法添加参数,接受之前的矩阵:

复制内容到剪贴板
  1. public void draw(float[] mvpMatrix) { // 传递计算出来的变换矩阵  
  2.     ...  
  3.   
  4.     // 获得形状的变换矩阵的句柄  
  5.     mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");  
  6.   
  7.     // Pass the projection and view transformation to the shader  
  8.     GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);  
  9.   
  10.     // 绘制三角形  
  11.     GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);  
  12.   
  13.     // Disable vertex array  
  14.     GLES20.glDisableVertexAttribArray(mPositionHandle);  
  15. }  

做完以上步骤后,就可以得到一个正确的图形了。

一个正确的图形.png

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

第五章 添加事件

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

5.1 旋转图形

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

复制内容到剪贴板
  1. 现在你拥有了一个正确显示形状的应用了,接下来应该给图形添加触摸事件了。  
  2. 第五章 添加事件  
  3.   
  4. 在屏幕上绘制是OpengGL的基础能力,但是你也可以使用其他的Android图形框架类来做,包括Canvas和Drawable。但是OpenGL ES提供了另外的能力,可以再三维上移动和变换对象。总之它能创造很好的用户体验。在本文中,你将学会如何使用OpenGL ES为形状添加旋转功能。  
  5. 5.1 旋转图形  
  6.   
  7. 在OpengGL ES 2.0中旋转一个图形,相对来说比较简单。在渲染的时候,创建另外一个转换矩阵(旋转矩阵),然后将这个矩阵合并到你的投影和相机图形变换矩阵就行了。  

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

5.2 持续的渲染

如果你已经跟着例子做到了这一步,确保GLSurfaceView的绘制模式不是dirty模式,否则,只有在调用requestRender()时,GLSurfaceView才会渲染。

复制内容到剪贴板
  1. public MyGLSurfaceView(Context context) {  
  2.     ...  
  3.     // Render the view only when there is a change in the drawing data.  
  4.     // To allow the triangle to rotate automatically, this line is commented out:  
  5.     // 注释掉,图形就可以自动旋转了  
  6.     //setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);  
  7. }  

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

第六章 响应触摸事件

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

6.1 设置触摸监听事件

为了使你的OpenGL ES应用响应触摸事件,你必须在GLSurfaceView类中实现onTouchEvent()方法。以下代码在MOtionEvent.ACTION_MOVE事件中计算了图形旋转的角度。

复制内容到剪贴板
  1. private final float TOUCH_SCALE_FACTOR = 180.0f / 320;  
  2. private float mPreviousX;  
  3. private float mPreviousY;  
  4.   
  5. @Override  
  6. public boolean onTouchEvent(MotionEvent e) {  
  7.     // MotionEvent reports input details from the touch screen  
  8.     // and other input controls. In this case, you are only  
  9.     // interested in events where the touch position changed.  
  10.   
  11.     float x = e.getX();  
  12.     float y = e.getY();  
  13.   
  14.     switch (e.getAction()) {  
  15.         case MotionEvent.ACTION_MOVE:  
  16.   
  17.             float dx = x - mPreviousX;  
  18.             float dy = y - mPreviousY;  
  19.   
  20.             // reverse direction of rotation above the mid-line  
  21.             if (y > getHeight() / 2) {  
  22.               dx = dx * -1 ;  
  23.             }  
  24.   
  25.             // reverse direction of rotation to left of the mid-line  
  26.             if (x < getWidth() / 2) {  
  27.               dy = dy * -1 ;  
  28.             }  
  29.   
  30.             mRenderer.setAngle(  
  31.                     mRenderer.getAngle() +  
  32.                     ((dx + dy) * TOUCH_SCALE_FACTOR));  
  33.             requestRender();  
  34.     }  
  35.   
  36.     mPreviousX = x;  
  37.     mPreviousY = y;  
  38.     return true;  
  39. }  

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

复制内容到剪贴板
  1. public MyGLSurfaceView(Context context) {  
  2.     ...  
  3.     // Render the view only when there is a change in the drawing data  
  4.     setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);  
  5. }  

6.2 添加旋转角度接口

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

复制内容到剪贴板
  1. public class MyGLRenderer implements GLSurfaceView.Renderer {  
  2.     ...  
  3.   
  4.     public volatile float mAngle;  
  5.   
  6.     public float getAngle() {  
  7.         return mAngle;  
  8.     }  
  9.   
  10.     public void setAngle(float angle) {  
  11.         mAngle = angle;  
  12.     }  
  13. }  

6.3 旋转图形

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

复制内容到剪贴板
  1. public void onDrawFrame(GL10 gl) {  
  2.     ...  
  3.     float[] scratch = new float[16];  
  4.   
  5.     // Create a rotation for the triangle  
  6.     // long time = SystemClock.uptimeMillis() % 4000L;  
  7.     // float angle = 0.090f * ((int) time);  
  8.     Matrix.setRotateM(mRotationMatrix, 0, mAngle, 00, -1.0f);  
  9.   
  10.     // Combine the rotation matrix with the projection and camera view  
  11.     // Note that the mMVPMatrix factor *must be first* in order  
  12.     // for the matrix multiplication product to be correct.  
  13.     Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);  
  14.   
  15.     // Draw triangle  
  16.     mTriangle.draw(scratch);  
  17. }  

旋转图形.png

至此,OpenGL ES的初步教程就叙述完了。

下载代码

http://download.csdn.net/detail/a296777513/9744535

感谢 a296777513 支持 磐实编程网 原文地址:
blog.csdn.net/a296777513/article/details/54729264

文章信息

发布时间:2017-02-01

作者:a296777513

发布者:aquwcw

浏览次数: