Android上OpenGL开发一些经验记录(上)
Android沿用了J2ME的OPENGL ES API.
相比C版本的OpenGL,Opengl ES 没有glu和glut库,而且只能画三角形(多边形需要三角化)。
没有直接的drawXXX 方法,只有通过 glVertiexPointer传入顶点画图。
另外参数上,没有指针和C风格的数组,Java需要用Buffer类来代替这个。
先看android的glVertexPointer :
GL10.glVertexPointer(int size, int type, int stride, Buffer pointer):
这个方法为 后面openGL绘图准备顶点数据。
size : 代表每个顶点包含几个坐标参数 ,如pointer的buffer中只含有 x,y坐标, 则传2, OpenGL会默认使用0作为z坐标。如果包含 x,y,z 坐标,则传3。其他值在这里都不适用。
type : 是一个枚举值,可以为 GL_FLOAT和 GL_FIXED,浮点数可对应java 的 float,要求pointer为 FloatBuffer.
GL_FIXED意为定点数,长度4字节,高16位表示整数部分,低16位表示小数部分。传入int值前需要先左移16位(即需扩大 0x10000倍)。要求pointer为IntBuffer.
stride :指每个元素之间,间隔多少个值。加入buffer中坐标点数值间紧密相连,那么stride就是0,如果点1,点2间还有不用到的值,则stride就是这种值的个数。
pointer:这是最需要注意的一个参数,也是Java版OpenGL特有的。Buffer类型是一个基类,具体的类型是 FloatBuffer还是IntBuffer要根据前面type参数来决定。
Java中的 Buffer分两种,一种direct,一种非direct.这里只支持 DirectBuffer,也就是通过ByteBuffer.allocateDirect(int cap) 分配而来的buffer.
通过allocateDirect 得到的是一个 ByteBuffer,实际使用时,如果要作为FloatBuffer,可以通过 ByteBuffer.asFloatBuffer来得到,IntBuffer也同样。
做到这些当然还不够,ByteOrder也对这个参数有影响。当的ByteBuffer作为FloatBuffer,则float的4字节默认是按照小端排列在directBuffer中,在
某些大端的设备中这个buffer的格式就不正确,需要根据设备设置ByteOrder.
所以以FloatBuffer为例,创建方法如下:
FloatBuffer buffer = ByteBuffer.allocateDirect(1024).order(ByteOrder.nativeOrder()).asFloatBuffer();
有了FloatBuffer,我们还需要将实际顶点坐标传入这个buffer。这只需要调用 FloatBuffer.put(float)的方法即可。
注意放顶点坐标是x,y...的格式,还是x,y,z...的格式要和前面第一个参数 size是2还是3统一。
所有的值放完以后,需要调用 buffer.rewind()或者 buffer.position(0),将buffer内游标置回0,否则OpenGL会从最后一次put的下一个位置开始读取。
关于三角化的算法:
Android的OpenGL API 画多边形目前只支持 triangle_fan,triangles, triangle_strip 三种。如果只有简单无洞的凸多边形,只需要将所有点当作triangle_fan的顶点就可以画出。
如果存在凹多边形,就需要进行三角化(当然凸多边形也可以三角化,保持逻辑上的统一)。
有个最简单的三角化算法叫做 ear clipping三角化。几何上非常容易理解:
一个多边形的一个顶点,和它相邻两个顶点可以组成一个三角形,如果这个三角形内部不存在这个多边形的其他顶点,就可以把这个顶点和相邻点组成的三角形当作一个ear.
这个ear可以被切下来(沿两个相邻顶点切)。每切一次,得到一个三角形和一个少了一个顶点的多边形,然后再对这个多边形做同样操作,直到剩下的这个多边形也只剩下3个顶点。
如此一来,这个多边形被完全分解为3角形。
要把把几何上的操作变成代码,只要解决这几个问题:
#如何判断多边形的一串顶点是逆时针还是顺时针排列的?
- 从某个顶点指向它下一个顶点,可以得到一个向量,多边形有几条边就得到几个向量,如果所有相邻两个向量的叉积(前一个向量和后一个向量的叉积)之和大于0,则排列是逆时针的,反之是顺时针的
(根据右手坐标系判断,一系列逆时针向量围成的面积是指向z轴正向的),为0有两种情况,这串点是直线,或者多边形某两条边交叉,这种多变形不符合这个三角化算法的条件。
#根据顶点顺逆情况,调整顶点排列,保证逆时针排序,则可以得到如下结论:
- 如果相邻两个向量,前一个向量和后一个向量叉积,值为正,则两个向量连接点这个顶点是凸点,值为负则为凹点。
- 如果有相邻3个顶点 p1,p2,p3 组成三角形,有另外一个顶点 P,如何判断P是否在三角形内?
分别做如下几个叉积p2P X p1p2, p3P X p2p3 , p1P X p3p1 ,如果这3个叉积同为正或同为负,则这个顶点在三角形内部。
关于 glEnableClientState, glDisableClientState,
glEnableClientState(int cap);
cap : GL_COLOR_ARRAY
GL_EDGE_FLAG_ARRAY
GL_FOG_COORD_ARRAY
GL_INDEX_ARRAY
GL_NORMAL_ARRAY
GL_SECONDARY_COLOR_ARRAY
GL_TEXTURE_COORD_ARRAY
GL_VERTEX_ARRAY
是用来启用或者关闭和这些数组功能。
如同前面的glVertexPointer ,还有别的诸如 glColorPointer, glEdgeFlagPointer等等接口,
glVertexPointer描述顶点位置,其他的描述顶点或者平面的属性。
这些数组默认都是disable状态,如果用这两个接口enable了某个数组,那么当glDrawArrays 调用之前必须把相应的数组准备好。否则draw不能成功。
所以如果不准备传入某个数组,draw之前要先 disable这个数组(如果前面enable过)。
反之如果要使用某个数组,也要事先enable。
还有Java中缺少的API,比如没有glPerspective的时候如何用glFrustm实现glPerspective,以及没有gluLookAt的时候如何用modelView来得到同样效果。将在之后文章里面解说。