首先我要告诉你一件比较重要的事情,我们画出来的所有东西其实都是由三角形拼成的,所以实际上我们一直在画不同的各种各样的三角形最后达到了我们想要的效果。
明确一些概念:点、线、面。
点(Vertex ):
在Android系统中可以使用一个浮点数数组来定义一个顶点,浮点数数组通常放在一个Buffer(java.nio)中来提高性能。
比如:
private float[] vertices = { -1.0f, 1.0f, 0.0f, // 0, Top Left -1.0f, -1.0f, 0.0f, // 1, Bottom Left 1.0f, -1.0f, 0.0f, // 2, Bottom Right 1.0f, 1.0f, 0.0f, // 3, Top Right };
上面定义了4个点,每一行分别代表x,y,z坐标。当然了,z坐标为0,那所有点都是在X-Y平面上。
为了提高性能,通常将这些数组存放到java.io 中定义的Buffer类中:
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); // 分配地址空间,一个float是4个字节,所以乘4 vbb.order(ByteOrder.nativeOrder()); vertexBuffer = vbb.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0);
将顶点传给OpenGL ES库:
// 打开传入buffer功能 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); /* * void glVertexPointer(GLint size, GLenum type, GLsizei stride, * const GLvoid * pointer); * 参数:
* size:指定了每个顶点对应的坐标个数,只能是2,3,4中的一个,默认值是4 * type:指定了数组中每个顶点坐标的数据类型,可取常量:GL_BYTE, GL_SHORT,GL_FIXED,GL_FLOAT; * stride:指定了连续顶点间的字节排列方式,如果为0,数组中的顶点就会被认为是按照紧凑方式排列的,默认值为0 * pointer:制订了数组中第一个顶点的首地址,默认值为0,对于我们的android,一般给一个IntBuffer就可以了。 */ gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer); // 关闭传入buffer功能 gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
定义面的顶点的顺序很重要,其顺序(逆时针或顺时针)决定面的正面和反面:
// 设置逆时针方法为面的“前面” gl.glFrontFace(GL10.GL_CCW); // 打开 忽略“后面”设置 gl.glEnable(GL10.GL_CULL_FACE); // 明确指明“忽略”某个面 gl.glCullFace(GL10.GL_BACK);
如下图定义了一个正方形:
对应的顶点的顺序和buffer 定义代码:
private short[] indices = { 0, 1, 2, 0, 2, 3 }; ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2); ibb.order(ByteOrder.nativeOrder()); indexBuffer = ibb.asShortBuffer(); indexBuffer.put(indices); indexBuffer.position(0);
如上都定义好了画一个正方形需要的点以及点的顺序(并不是必须的),最后就是渲染(绘制)正方形了,OpenGL ES提供了两类方法来绘制一个空间几何图形:
1、使用VetexBuffer 来绘制,顶点的顺序由vertexBuffer中的顺序指定。
public abstract void glDrawArrays(int mode, int first, int count) ;
2、可以重新定义顶点的顺序,顶点的顺序由indices Buffer 指定。
public abstract void glDrawElements(int mode, int count, int type, Buffer indices) ;
其中的mode有以下几种方式:
GL_POINTS 绘制独立的点。
GL_LINE_STRIP 绘制一系列线段。
GL_LINE_LOOP 类同上,但是首尾相连,构成一个封闭曲线。
GL_LINES 顶点两两连接,为多条线段构成。
GL_TRIANGLES 每隔三个顶点构成一个三角形,为多个三角形组成。
GL_TRIANGLE_STRIP 每相邻三个顶点组成一个三角形,为一系列相接三角形构成。
GL_TRIANGLE_FAN 以一个点为三角形公共顶点,组成一系列相邻的三角形。
以上为所需的基本知识,下面就可以绘制正方形了:
定义一个Square.java类:
package seven.demo.openglspuqre; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.ShortBuffer; import javax.microedition.khronos.opengles.GL10; /** * 类描述: 功能详细描述: * * @author mengsifan * @date [2012-8-29] */ public class Square { private float[] vertices = { -1.0f, 1.0f, 0.0f, // 0, Top Left -1.0f, -1.0f, 0.0f, // 1, Bottom Left 1.0f, -1.0f, 0.0f, // 2, Bottom Right 1.0f, 1.0f, 0.0f, // 3, Top Right }; private short[] indices = { 0, 1, 2, 0, 2, 3 }; private FloatBuffer vertexBuffer; private ShortBuffer indexBuffer; public Square() { ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); // 分配地址空间,一个float是4个字节,所以乘4 vbb.order(ByteOrder.nativeOrder()); vertexBuffer = vbb.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0); ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2); ibb.order(ByteOrder.nativeOrder()); indexBuffer = ibb.asShortBuffer(); indexBuffer.put(indices); indexBuffer.position(0); } public Square(float width) { width /= 2; for (int i = 0; i < vertices.length; i++) { vertices[i] *= width; } ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); vbb.order(ByteOrder.nativeOrder()); vertexBuffer = vbb.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0); ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2); ibb.order(ByteOrder.nativeOrder()); indexBuffer = ibb.asShortBuffer(); indexBuffer.put(indices); indexBuffer.position(0); } public void draw(GL10 gl) { gl.glFrontFace(GL10.GL_CCW); gl.glEnable(GL10.GL_CULL_FACE); gl.glCullFace(GL10.GL_BACK); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer); gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_SHORT, indexBuffer); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); gl.glDisable(GL10.GL_CULL_FACE); } }
OpenGLRenderer.java:
package seven.demo.openglspuqre; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.opengl.GLSurfaceView.Renderer; import android.opengl.GLU; /** * 类描述: 功能详细描述: * * @author mengsifan * @date [2012-8-29] */ public class OpenGLRenderer implements Renderer { Square square = new Square(); // 相比HelloWorld新增代码 /** * {@inheritDoc} 在这个方法中主要用来设置一些绘制时不常变化的参数,比如:背景色,是否打开 z-buffer等。 */ @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { gl.glClearColor(.0f, .0f, .0f, .5f); gl.glShadeModel(GL10.GL_SMOOTH); gl.glClearDepthf(1.0f); gl.glEnable(GL10.GL_DEPTH_TEST); gl.glDepthFunc(GL10.GL_LEQUAL); gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); } /** * {@inheritDoc} 如果设备支持屏幕横向和纵向切换,这个方法将发生在横向<->纵向互换时。此时可以重新设置绘制的纵横比率。 */ @Override public void onSurfaceChanged(GL10 gl, int width, int height) { gl.glViewport(0, 0, width, height); gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity(); GLU.gluPerspective(gl, 45.0f, (float) width / (float) height, 0.1f, 100.0f); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); } /** * {@inheritDoc} 定义实际的绘图操作。 */ @Override public void onDrawFrame(GL10 gl) { gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); gl.glLoadIdentity(); // 相比HelloWorld新增代码 gl.glTranslatef(0, 0, -4); // 相比HelloWorld新增代码 square.draw(gl); // 相比HelloWorld新增代码 } }
注意相比HelloWorld(OpenGL ES for Android 教程1)新增的几句:
Square square = new Square();和onDrawFrame()中的square.draw(gl);
这是为了画正方形所需要的。
gl.glTranslatef(0, 0, -4);
OpenGL ES从当前位置开始渲染,缺省坐标为(0,0,0),和View port 的坐标一样,相当于把画面放在眼前,对应这种情况OpenGL不会渲染离view Port很近的画面,因此我们需要将画面向后退一点距离。
gl.glLoadIdentity();
如果没有这句会发现正方形迅速后移直至看不见,这是因为每次调用onDrawFrame 时,每次都再向后移动4个单位,需要加上重置Matrix的代码。这句就是为了重置Matrix。
有兴趣的话可以不加gl.glTranslatef(0, 0, -4);和gl.glLoadIdentity();两句看下会出现什么状况。
本教程及后续教程全部参考或者转载于:OpenGL ES Tutorial for Android