该系列文章均转载自
http://blog.csdn.net/mapdigit/article/details/7526556
由于原文好像无法打开,正好自己有记录,所以正好分享出来,其中也对一些API作了解释。
一个3D图形通常是由一些小的基本元素(顶点,边,面,多边形)构成的,每个基本元素都可以单独操作。
Vertex (顶点)
顶点是3D建模时用到的最小构成元素,顶点定义为两条或是多条边交会的地方。在3D模型中一个顶点可以为多条边,面或是多边形所共享。一个顶点也可以代表一个点光源或是Camera的位置。下图中标识为黄色的点为一个顶点(Vertex)。
在Android系统中可以使用一个浮点数数组来定义一个顶点,浮点数数组通常放在一个Buffer(java.nio)中来提高性能。
比如:下图中定义了四个顶点和对应的Android 顶点定义:
private float vertices[] = {
-1.0f,1.0f,0.0f, // v0,左上角点
-1.0f,-1.0f,0.0f, // v1,左下角点
1.0f,-1.0f,0.0f, // v2,右下角点
1.0f,1.0f,0.0f // v3,右上角点
};
为了提高性能,通常将这些数组存放到java.io 中定义的Buffer类中:
// 一个浮点数是四个字节,因此ByteBuffer的容量需要在基础上乘以4
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
FloatBuffer floatBuffer = byteBuffer.asFloatBuffer();// 将ByteBuffer转成FloatBuffer对象
floatBuffer.put(vertices); // sets the position of the buffer
floatBuffer.position(0);
有了顶点的定义,下面一步就是如何将它们传给OpenGL ES库,OpenGL ES提供一个成为”管道Pipeline”的机制,这个管道定义了一些“开关”来控制OpenGL ES支持的某些功能,缺省情况这些功能是关闭的,如果需要使用OpenGL ES的这些功能,需要明确告知OpenGL “管道”打开所需功能。因此对于我们的这个示例,需要告诉OpenGL库打开 Vertex buffer以便传入顶点坐标Buffer。要注意的使用完某个功能之后,要关闭这个功能以免影响后续操作:
/**
* 启用客户端的某项功能
* GL_VERTEX_ARRAY——如果启用,顶点矩阵可以用来写入以及调用glDrawArrays方法或者glDrawElements方法时进行渲染。
*/
gl10.glEnableClientState(GL10.GL_VERTEX_ARRAY);
/**
* 定义一个顶点坐标矩阵。
* 参数:
size——每个顶点的坐标维数,必须是2, 3或者4,初始值是4。
type——指明每个顶点坐标的数据类型,允许的符号常量有GL_BYTE, GL_SHORT, GL_FIXED和GL_FLOAT,初始值为GL_FLOAT。
stride——指明连续顶点间的位偏移,如果为0,顶点被认为是紧密压入矩阵,初始值为0。
pointer——指明顶点坐标的缓冲区,如果为null,则没有设置缓冲区。
*/
gl10.glVertexPointer(3, GL10.GL_FLOAT, 0, floatBuffer); // OpenGL docs.
/**
* 禁用客户端的某项功能
*/
gl10.glDisableClientState(GL10.GL_VERTEX_ARRAY);// OpenGL docs.
Edge(边)
边定义为两个顶点之间的线段。边是面和多边形的边界线。在3D模型中,边可以被相邻的两个面或是多边形所共享。对一个边做变换将影响边相接的所有顶点,面或多边形。在OpenGL中,通常无需直接来定义一个边,而是通过顶点定义一个面,从而由面定义了其所对应的三条边。可以通过修改边的两个顶点来更改一条边,下图黄色的线段代表一条边:
Face (面)
在OpenGL ES中,面特指一个三角形,由三个顶点和三条边构成,对一个面所做的变化影响到连接面的所有顶点和边,面多边形。下图黄色区域代表一个面。
定义面的顶点的顺序很重要 在拼接曲面的时候,用来定义面的顶点的顺序非常重要,因为顶点的顺序定义了面的朝向(前向或是后向),为了获取绘制的高性能,一般情况不会绘制面的前面和后面,只绘制面的“前面”。虽然“前面”“后面”的定义可以应人而易,但一般为所有的“前面”定义统一的顶点顺序(顺时针或是逆时针方向)。
/**
* 定义多边形的正面和背面。
* GL10.GL_CCW:选择逆时针多边形为正面
* GL10.GL_CW:选择顺时针多边形为正面
* 默认逆时针多边形为正面
*/
gl10.glFrontFace(GL10.GL_CCW);
/**
* 打开 忽略“后面”设置:
* 开启或禁止拣选功能,调用glEnable方法和glDisable方法并以GL_CULL_FACE为参数
* 拣选功能初始值为禁止
*/
gl10.glEnable(GL10.GL_CULL_FACE);
/**
* 明确指明“忽略“哪个面的代码如下
* 功能:指明“忽略”哪个面
* 允许的符号常量有:GL_FRONT,GL_BACK和GL_FRONT_AND_BACK。初始值为GL_BACK。
*/
gl10.glCullFace(GL10.GL_BACK);
Polygon (多边形)
多边形由多个面(三角形)拼接而成,在三维空间上,多边形并一定表示这个Polygon在同一平面上。这里我们使用缺省的逆时针方向代表面的“前面Front,下图黄色区域为一个多边形。
来看一个多边形的示例在Android系统如何使用顶点和buffer 来定义,如下图定义了一个正方形:
对应的顶点和buffer 定义代码:
// 一个short整形是两个字节,所以乘以2
short indices[] = {0,1,2,0,2,3};
ByteBuffer buffer = ByteBuffer.allocateDirect(indices.length * 2);
buffer.order(ByteOrder.nativeOrder());
ShortBuffer shortBuffer = buffer.asShortBuffer();
shortBuffer.put(indices);
shortBuffer.position(0);
Render (渲染)
我们已定义好了多边形,下面就要了解如和使用OpenGL ES的API来绘制(渲染)这个多边形了。OpenGL ES提供了两类方法来绘制一个空间几何图形:
- public abstract void glDrawArrays(int mode, int first, int count) 使用VetexBuffer 来绘制,顶点的顺序由vertexBuffer中的顺序指定。
参数:
mode——指明渲染哪一种图元。允许的符号常量有GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP,GL_TRIANGLE_FAN和GL_TRIANGLES。
first——指明在允许访问的矩阵中的起始索引。
count——指明要渲染的索引的数量。
- public abstract void glDrawElements(int mode, int count, int type, Buffer indices) ,可以重新定义顶点的顺序,顶点的顺序由indices Buffer 指定。
参数:
mode——指明被渲染的是哪种图元,被允许的符号常量有GL_POINTS,GL_LINE_STRIP,GL_LINE_LOOP,GL_LINES,GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN和GL_TRIANGLES
count——指明被渲染的元素个数。
type——指明索引指的类型,不是GL_UNSIGNED_BYTE就是GL_UNSIGNED_SHORT。
indices——指明存储索引的位置指针
前面我们已定义了顶点数组,因此我们将采用glDrawElements 来绘制多边形。
同样的顶点,可以定义的几何图形可以有所不同,比如三个顶点,可以代表三个独立的点,也可以表示一个三角形,这就需要使用mode 来指明所需绘制的几何图形的基本类型。
GL_POINTS
绘制独立的点。
GL_LINE_STRIP
绘制一系列线段。
GL_LINE_LOOP
类同上,但是首尾相连,构成一个封闭曲线。
GL_LINES
顶点两两连接,为多条线段构成。
GL_TRIANGLES
每隔三个顶点构成一个三角形,为多个三角形组成。
GL_TRIANGLE_STRIP
每相邻三个顶点组成一个三角形,为一系列相接三角形构成。
GL_TRIANGLE_FAN
以一个点为三角形公共顶点,组成一系列相邻的三角形。
定义一个Square类
public class Square {
// 定义点
private float vertices[] = {
-1.0f,1.0f,0.0f, // v0,左上角点
-1.0f,-1.0f,0.0f, // v1,左下角点
1.0f,-1.0f,0.0f, // v2,右下角点
1.0f,1.0f,0.0f // v3,右上角点
};
// 连接点的顺序
private short indices[] = {0,1,2,0,2,3};
private FloatBuffer verexBuffer;
private ShortBuffer indexBuffer;
public Square() {
// 一个浮点数是四个字节,因此ByteBuffer的容量需要在基础上乘以4
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
verexBuffer = byteBuffer.asFloatBuffer();
verexBuffer.put(vertices);
verexBuffer.position(0);
ByteBuffer byteBuffer1 = ByteBuffer.allocateDirect(indices.length * 2);
byteBuffer1.order(ByteOrder.nativeOrder());
indexBuffer = byteBuffer1.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position(0);
}
/**
* 画出正方形
* @param gl10
*/
public void draw(GL10 gl10) {
/**
* 定义多边形的正面和背面。
* GL10.GL_CCW:选择逆时针多边形为正面
* GL10.GL_CW:选择顺时针多边形为正面
* 默认逆时针多边形为正面
*/
gl10.glFrontFace(GL10.GL_CCW);
/**
* 打开 忽略“后面”设置:
* 开启或禁止拣选功能,调用glEnable方法和glDisable方法并以GL_CULL_FACE为参数
* 拣选功能初始值为禁止
*/
gl10.glEnable(GL10.GL_CULL_FACE);
/**
* 明确指明“忽略“哪个面的代码如下
* 功能:指明“忽略”哪个面
* 允许的符号常量有:GL_FRONT,GL_BACK和GL_FRONT_AND_BACK。初始值为GL_BACK。
*/
gl10.glCullFace(GL10.GL_BACK);
/**
* 启用客户端的某项功能
* GL_VERTEX_ARRAY——如果启用,顶点矩阵可以用来写入以及调用glDrawArrays方法或者glDrawElements方法时进行渲染。
*/
gl10.glEnableClientState(GL10.GL_VERTEX_ARRAY);
/**
* 定义一个顶点坐标矩阵。
* 参数:
size——每个顶点的坐标维数,必须是2, 3或者4,初始值是4。
type——指明每个顶点坐标的数据类型,允许的符号常量有GL_BYTE, GL_SHORT, GL_FIXED和GL_FLOAT,初始值为GL_FLOAT。
stride——指明连续顶点间的位偏移,如果为0,顶点被认为是紧密压入矩阵,初始值为0。
pointer——指明顶点坐标的缓冲区,如果为null,则没有设置缓冲区。
*/
gl10.glVertexPointer(3, GL10.GL_FLOAT, 0, verexBuffer); // OpenGL docs.
/**
* 由矩阵数据渲染图元。
* mode——指明被渲染的是哪种图元,被允许的符号常量有GL_POINTS,GL_LINE_STRIP,GL_LINE_LOOP,GL_LINES,GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN和GL_TRIANGLES
count——指明被渲染的元素个数。
type——指明索引指的类型,不是GL_UNSIGNED_BYTE就是GL_UNSIGNED_SHORT。
indices——指明存储索引的位置指针。
*/
gl10.glDrawElements(GL10.GL_TRIANGLES,indices.length,GL10.GL_UNSIGNED_SHORT,indexBuffer);
/**
* 禁用客户端的某项功能
*/
gl10.glDisableClientState(GL10.GL_VERTEX_ARRAY);// OpenGL docs.
gl10.glDisable(GL10.GL_CULL_FACE);
}
在openGLRenderer中添加初始化
Square mSquare = new Square();
并在onDrawnFrame()方法中调用draw方法
mSquare.draw(gl10);
因为OpenGL ES从当前位置开始渲染,缺省坐标为(0,0,0),和View port 的坐标一样,相当于把画面放在眼前,对应这种情况OpenGL不会渲染离view Port很近的画面,因此我们需要将画面向后退一点距离:
gl10.glTranslatef(0,0,-4);
因为每次调用onDrawFrame 时,每次都再向后移动4个单位,需要加上重置Matrix的代码。
gl10.glLoadIdentity();
完整的代码下载:
https://github.com/upperLucky/OpenGLDemo_Polygon_
最后的效果: