ApiDemos 的Graphics示例中含有OpenGL ES 例子,OpenGL ES 主要用来开发3D图形应用的。OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三维图形 API 的子集,针对手机、PDA和游戏主机等嵌入式设备而设计。
下面是维基百科中对应OpenGL ES的简介:
OpenGL ES 是从 OpenGL 裁剪定制而来的,去除了 glBegin/glEnd,四边形(GL_QUADS)、多边形(GL_POLYGONS)等复杂图元等许多非绝对必要的特性。经过多年发展,现在主要有两个版本,OpenGL ES 1.x 针对固定管线硬件的,OpenGL ES 2.x 针对可编程管线硬件。OpenGL ES 1.0 是以 OpenGL 1.3 规范为基础的,OpenGL ES 1.1 是以 OpenGL 1.5 规范为基础的,它们分别又支持 common 和 common lite 两种profile。lite profile只支持定点实数,而common profile既支持定点数又支持浮点数。 OpenGL ES 2.0 则是参照 OpenGL 2.0 规范定义的,common profile发布于2005-8,引入了对可编程管线的支持。
在解析AndroidApiDemos 中OpenGL ES示例前,有必要对OpenGL ES 开发单独做个简明开发教程,可以帮助从未接触过3D开发的程序员了解OpenGL 的开发的基本概念和方法,很多移动手机平台都提供了对OpenGL ES 开发包的支持,因此尽管这里使用Android平台介绍OpenGL ES ,但基本概念和步骤同样适用于其它平台。
简明开发教程主要参考 Jayway Team Blog中OpenGL ES开发教程 , 这是一个写的比较通俗易懂的开发教程,适合OpenGL ES初学者。
除了这个OpenGL ES 简明开发教程外,以后将专门针对OpenGL ES写个由浅入深的开发教程,尽请关注。
在Andorid平台上构造一个OpenGL View非常简单,主要有两方面的工作:
GLSurfaceView
Android平台提供的OpenGL ES API主要定义在包android.opengl ,javax.microedition.khronos.egl ,javax.microedition.khronos.opengles ,java.nio 等几个包中,其中类GLSurfaceView 为这些包中的核心类:
- 起到连接OpenGL ES与Android的View层次结构之间的桥梁作用。
- 使得Open GL ES库适应于Anndroid系统的Activity生命周期。
- 使得选择合适的Frame buffer像素格式变得容易。
- 创建和管理单独绘图线程以达到平滑动画效果。
- 提供了方便使用的调试工具来跟踪OpenGL ES函数调用以帮助检查错误。
因此编写OpenGL ES应用的起始点是从类GLSurfaceView开始,设置GLSurfaceView只需调用一个方法来设置OpenGLView用到的GLSurfaceView.Renderer.
1
|
public void setRenderer(GLSurfaceView.Renderer renderer)
|
GLSurfaceView.Renderer
GLSurfaceView.Renderer定义了一个统一图形绘制的接口,它定义了如下三个接口函数:
1
2
3
4
5
6
7
8
|
// Called when the surface is created or recreated.
public void onSurfaceCreated(GL10 gl, EGLConfig config)
// Called to draw the current frame.
public void onDrawFrame(GL10 gl)
// Called when the surface changed size.
public void onSurfaceChanged(GL10 gl, int width, int height)
|
- onSurfaceCreated : 在这个方法中主要用来设置一些绘制时不常变化的参数,比如:背景色,是否打开 z-buffer等。
- onDrawFrame: 定义实际的绘图操作。
- onSurfaceChanged: 如果设备支持屏幕横向和纵向切换,这个方法将发生在横向<->纵向互换时。此时可以重新设置绘制的纵横比率。
有了上面的基本定义,可以写出一个OpenGL ES应用的通用框架。
创建一个新的Android项目:OpenGLESTutorial, 在项目在添加两个类TutorialPartI.java 和OpenGLRenderer.java.
具体代码如下:
TutorialPartI.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class TutorialPartI extends Activity {
// Called when the activity is first created.
@Override
public void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
this .requestWindowFeature(Window.FEATURE_NO_TITLE); // (NEW)
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN); // (NEW)
GLSurfaceView view = new GLSurfaceView( this );
view.setRenderer( new OpenGLRenderer());
setContentView(view);
}
}
|
OpenGLRenderer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
public class OpenGLRenderer implements Renderer {
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Set the background color to black ( rgba ).
gl.glClearColor( 0 .0f, 0 .0f, 0 .0f, 0 .5f); // OpenGL docs.
// Enable Smooth Shading, default not really needed.
gl.glShadeModel(GL10.GL_SMOOTH); // OpenGL docs.
// Depth buffer setup.
gl.glClearDepthf( 1 .0f); // OpenGL docs.
// Enables depth testing.
gl.glEnable(GL10.GL_DEPTH_TEST); // OpenGL docs.
// The type of depth testing to do.
gl.glDepthFunc(GL10.GL_LEQUAL); // OpenGL docs.
// Really nice perspective calculations.
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, // OpenGL docs.
GL10.GL_NICEST);
}
public void onDrawFrame(GL10 gl) {
// Clears the screen and depth buffer.
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | // OpenGL docs.
GL10.GL_DEPTH_BUFFER_BIT);
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
// Sets the current view port to the new size.
gl.glViewport( 0 , 0 , width, height); // OpenGL docs.
// Select the projection matrix
gl.glMatrixMode(GL10.GL_PROJECTION); // OpenGL docs.
// Reset the projection matrix
gl.glLoadIdentity(); // OpenGL docs.
// Calculate the aspect ratio of the window
GLU.gluPerspective(gl, 45 .0f,
( float ) width / ( float ) height,
0 .1f, 100 .0f);
// Select the modelview matrix
gl.glMatrixMode(GL10.GL_MODELVIEW); // OpenGL docs.
// Reset the modelview matrix
gl.glLoadIdentity(); // OpenGL docs.
}
}
|
编译后运行,屏幕显示一个黑色的全屏。这两个类定义了AndroidOpenGL ES应用的最基本的类和方法,可以看作是OpenGL ES的”Hello ,world”应用,后面将逐渐丰富这个例子来画出3D图型。
框架代码下载见http://www.linuxidc.com/Linux/2011-10/45756p8.htm:可以作为你自己的OpenGL 3D 的初始代码。
前面介绍了使用Android编写OpenGL ES应用的程序框架,本篇介绍3D绘图的一些基本构成要素,最终将实现一个多边形的绘制。
一个3D图形通常是由一些小的基本元素(顶点,边,面,多边形)构成,每个基本元素都可以单独来操作。
Vertex (顶点)
顶点是3D建模时用到的最小构成元素,顶点定义为两条或是多条边交会的地方。在3D模型中一个顶点可以为多条边,面或是多边形所共享。一个顶点也可以代表一个点光源或是Camera的位置。下图中标识为黄色的点为一个顶点(Vertex)。
在Android系统中可以使用一个浮点数数组来定义一个顶点,浮点数数组通常放在一个Buffer(java.nio)中来提高性能。
比如:下图中定义了四个顶点和对应的Android顶点定义:
1
2
3
4
5
6
|
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
};
|
为了提高性能,通常将这些数组存放到java.io 中定义的Buffer类中:
1
2
3
4
5
6
7
|
// a float is 4 bytes, therefore we multiply the
//number if vertices with 4.
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4 );
vbb.order(ByteOrder.nativeOrder());
FloatBuffer vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position( 0 );
|
有了顶点的定义,下面一步就是如何将它们传给OpenGL ES库,OpenGL ES提供一个成为”管道Pipeline”的机制,这个管道定义了一些“开关”来控制OpenGL ES支持的某些功能,缺省情况这些功能是关闭的,如果需要使用OpenGL ES的这些功能,需要明确告知OpenGL “管道”打开所需功能。因此对于我们的这个示例,需要告诉OpenGL库打开 Vertex buffer以便传入顶点坐标Buffer。要注意的使用完某个功能之后,要关闭这个功能以免影响后续操作:
1
2
3
4
5
6
7
8
|
// Enabled the vertex buffer for writing and to be used during rendering.
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); // OpenGL docs.
// Specifies the location and data format of an array of vertex
// coordinates to use when rendering.
gl.glVertexPointer( 3 , GL10.GL_FLOAT, 0 , vertexBuffer); // OpenGL docs.
When you are done with the buffer don't forget to disable it.
// Disable the vertices buffer.
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); // OpenGL docs.
|
Edge(边)
边定义为两个顶点之间的线段。边是面和多边形的边界线。在3D模型中,边可以被相邻的两个面或是多边形形共享。对一个边做变换将影响边相接的所有顶点,面或多边形。在OpenGL中,通常无需直接来定义一个边,而是通过顶点定义一个面,从而由面定义了其所对应的三条边。可以通过修改边的两个顶点来更改一条边,下图黄色的线段代表一条边:
Face (面)
在OpenGL ES中,面特指一个三角形,由三个顶点和三条边构成,对一个面所做的变化影响到连接面的所有顶点和边,面多边形。下图黄色区域代表一个面。
定义面的顶点的顺序很重要在拼接曲面的时候,用来定义面的顶点的顺序非常重要,因为顶点的顺序定义了面的朝向(前向或是后向),为了获取绘制的高性能,一般情况不会绘制面的前面和后面,只绘制面的“前面”。虽然“前面”“后面”的定义可以应人而易,但一般为所有的“前面”定义统一的顶点顺序(顺时针或是逆时针方向)。
下面代码设置逆时针方法为面的“前面”:
1
|
gl.glFrontFace(GL10.GL_CCW);
|
打开 忽略“后面”设置:
1
|
gl.glEnable(GL10.GL_CULL_FACE);
|
明确指明“忽略“哪个面的代码如下:
1
|
gl.glCullFace(GL10.GL_BACK);
|
Polygon (多边形)
多边形由多个面(三角形)拼接而成,在三维空间上,多边形并一定表示这个Polygon在同一平面上。这里我们使用缺省的逆时针方向代表面的“前面Front),下图黄色区域为一个多边形。
来看一个多边形的示例在Android系统如何使用顶点和buffer 来定义,如下图定义了一个正方形:
对应的顶点和buffer 定义代码:
1
2
3
4
5
6
7
8
|
private short [] indices = { 0 , 1 , 2 , 0 , 2 , 3 };
To gain some performance we also put this ones in a byte buffer.
// short is 2 bytes, therefore we multiply the number if vertices with 2.
ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2 );
ibb.order(ByteOrder.nativeOrder());
ShortBuffer indexBuffer = ibb.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position( 0 );
|
Render (渲染)
我们已定义好了多边形,下面就要了解如和使用OpenGL ES的API来绘制(渲染)这个多边形了。OpenGL ES提供了两类方法来绘制一个空间几何图形:
- public abstract voidglDrawArrays(int mode, int first, int count) 使用VetexBuffer 来绘制,顶点的顺序由vertexBuffer中的顺序指定。
- public abstract voidglDrawElements(int mode, int count, int type, Buffer indices) ,可以重新定义顶点的顺序,顶点的顺序由indices Buffer 指定。
前面我们已定义里顶点数组,因此我们将采用glDrawElements 来绘制多边形。
同样的顶点,可以定义的几何图形可以有所不同,比如三个顶点,可以代表三个独立的点,也可以表示一个三角形,这就需要使用mode 来指明所需绘制的几何图形的基本类型。
GL_POINTS
绘制独立的点。
GL_LINE_STRIP
绘制一系列线段。
GL_LINE_LOOP
类同上,但是首尾相连,构成一个封闭曲线。
GL_LINES
顶点两两连接,为多条线段构成。
GL_TRIANGLES
每隔三个顶点构成一个三角形,为多个三角形组成。
GL_TRIANGLE_STRIP
每相邻三个顶点组成一个三角形,为一系列相接三角形构成。
GL_TRIANGLE_FAN
以一个点为三角形公共顶点,组成一系列相邻的三角形。
下面可以来绘制正方形了,在项目中添加一个Square.java 定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
package se.jayway.opengl.tutorial;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import javax.microedition.khronos.opengles.GL10;
public class Square {
// Our vertices.
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
};
// The order we like to connect them.
private short [] indices = { 0 , 1 , 2 , 0 , 2 , 3 };
// Our vertex buffer.
private FloatBuffer vertexBuffer;
// Our index buffer.
private ShortBuffer indexBuffer;
public Square() {
// a float is 4 bytes, therefore we
// multiply the number if
// vertices with 4.
ByteBuffer vbb
= ByteBuffer.allocateDirect(vertices.length * 4 );
vbb.order(ByteOrder.nativeOrder());
vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position( 0 );
// short is 2 bytes, therefore we multiply
//the number if
// vertices with 2.
ByteBuffer ibb
= ByteBuffer.allocateDirect(indices.length * 2 );
ibb.order(ByteOrder.nativeOrder());
indexBuffer = ibb.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position( 0 );
}
/**
* This function draws our square on screen.
* @param gl
*/
public void draw(GL10 gl) {
// Counter-clockwise winding.
gl.glFrontFace(GL10.GL_CCW);
// Enable face culling.
gl.glEnable(GL10.GL_CULL_FACE);
// What faces to remove with the face culling.
gl.glCullFace(GL10.GL_BACK);
// Enabled the vertices buffer for writing
//and to be used during
// rendering.
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// Specifies the location and data format of
//an array of vertex
// coordinates to use when rendering.
gl.glVertexPointer( 3 , GL10.GL_FLOAT, 0 ,
vertexBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, indices.length,
GL10.GL_UNSIGNED_SHORT, indexBuffer);
// Disable the vertices buffer.
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
// Disable face culling.
gl.glDisable(GL10.GL_CULL_FACE);
}
}
|
在OpenGLRenderer 中添加Square成员变量并初始化:
1
2
|
// Initialize our square.
Square square = new Square();
|
并在public void onDrawFrame(GL10 gl) 添加
1
2
|
// Draw our square.
square.draw(gl);
|
来绘制这个正方形,编译运行,什么也没显示,这是为什么呢?这是因为OpenGL ES从当前位置开始渲染,缺省坐标为(0,0,0),和View port 的坐标一样,相当于把画面放在眼前,对应这种情况OpenGL不会渲染离view Port很近的画面,因此我们需要将画面向后退一点距离:
1
2
|
// Translates 4 units into the screen.
gl.glTranslatef( 0 , 0 , - 4 );
|
在编译运行,这次倒是有显示了,当正方形迅速后移直至看不见,这是因为每次调用onDrawFrame 时,每次都再向后移动4个单位,需要加上重置Matrix的代码。
1
2
|
// Replace the current matrix with the identity matrix
gl.glLoadIdentity();
|
最终onDrawFrame的代码如下:
1
2
3
4
5
6
7
8
9
10
|
public void onDrawFrame(GL10 gl) {
// Clears the screen and depth buffer.
gl.glClear(GL10.GL_COLOR_BUFFER_BIT |
GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
gl.glTranslatef( 0 , 0 , - 4 );
// Draw our square.
square.draw(gl); // ( NEW )
}
|
本篇代码下载见http://www.linuxidc.com/Linux/2011-10/45756p8.htm
本篇介绍3D 坐标系下的坐标变换transformations。
Coordinate System坐标系
OpenGL使用了右手坐标系统,右手坐标系判断方法:在空间直角坐标系中,让右手拇指指向x轴的正方向,食指指向y轴的正方向,如果中指能指向z轴的正方向,则称这个坐标系为右手直角坐标系。
Translate平移变换
方法public abstract voidglTranslatef(float x, float y, float z) 用于坐标平移变换。
在上个例子中我们把需要显示的正方形后移了4个单位,就是使用的坐标的平移变换,可以进行多次平移变换,其结果为多个平移矩阵的累计结果,矩阵的顺序不重要,可以互换。
Rotate旋转
方法public abstract voidglRotatef(float angle, float x, float y, float z)用来实现选择坐标变换,单位为角度。 (x,y,z)定义旋转的参照矢量方向。多次旋转的顺序非常重要。
比如你选择一个骰子,首先按下列顺序选择3次:
1
2
3
|
gl.glRotatef(90f, 1 .0f, 0 .0f, 0 .0f);
gl.glRotatef(90f, 0 .0f, 1 .0f, 0 .0f);
gl.glRotatef(90f, 0 .0f, 0 .0f, 1 .0f);
|
然后打算逆向旋转回原先的初始状态,需要有如下旋转:
1
2
3
|
gl.glRotatef(90f, - 1 .0f, 0 .0f, 0 .0f);
gl.glRotatef(90f, 0 .0f, - 1 .0f, 0 .0f);
gl.glRotatef(90f, 0 .0f, 0 .0f, - 1 .0f);
|
或者如下旋转:
1
2
3
|
gl.glRotatef(90f, 0 .0f, 0 .0f, - 1 .0f);
gl.glRotatef(90f, 0 .0f, - 1 .0f, 0 .0f);
gl.glRotatef(90f, - 1 .0f, 0 .0f, 0 .0f);
|
旋转变换glRotatef(angle, -x, -y, -z) 和glRotatef(-angle, x, y, z)是等价的,但选择变换的顺序直接影响最终坐标变换的结果。 角度为正时表示逆时针方向。
Translate & Rotate (平移和旋转组合变换)
在对Mesh(网格,构成三维形体的基本单位)同时进行平移和选择变换时,坐标变换的顺序也直接影响最终的结果。
比如:先平移后旋转, 旋转的中心为平移后的坐标。
先选择后平移: 平移在则相对于旋转后的坐标系:
一个基本原则是,坐标变换都是相对于变换的Mesh本身的坐标系而进行的。
Scale(缩放)
方法public abstract voidglScalef(float x, float y, float z)用于缩放变换。
下图为使用gl.glScalef(2f, 2f, 2f) 变换后的基本,相当于把每个坐标值都乘以2.
Translate & Scale(平移和缩放组合变换)
同样当需要平移和缩放时,变换的顺序也会影响最终结果。
比如先平移后缩放:
1
2
|
gl.glTranslatef( 2 , 0 , 0 );
gl.glScalef( 0 .5f, 0 .5f, 0 .5f);
|
如果调换一下顺序:
1
2
|
gl.glScalef( 0 .5f, 0 .5f, 0 .5f);
gl.glTranslatef( 2 , 0 , 0 );
|
结果就有所不同:
矩阵操作,单位矩阵
在进行平移,旋转,缩放变换时,所有的变换都是针对当前的矩阵(与当前矩阵相乘),如果需要将当前矩阵回复最初的无变换的矩阵,可以使用单位矩阵(无平移,缩放,旋转)。
public abstract voidglLoadIdentity()。
在栈中保存当前矩阵和从栈中恢复所存矩阵,可以使用
public abstract voidglPushMatrix()
和
public abstract voidglPopMatrix()。
在进行坐标变换的一个好习惯是在变换前使用glPushMatrix保存当前矩阵,完成坐标变换操作后,再调用glPopMatrix恢复原先的矩阵设置。
最后利用上面介绍的坐标变换知识,来绘制3个正方形A,B,C。进行缩放变换,使的B比A小50%,C比B小50%。 然后以屏幕中心逆时针旋转A,B以A为中心顺时针旋转,C以B为中心顺时针旋转同时以自己中心高速逆时针旋转。
修改 onDrawFrame 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
public void onDrawFrame(GL10 gl) {
// Clears the screen and depth buffer.
gl.glClear(GL10.GL_COLOR_BUFFER_BIT
| GL10.GL_DEPTH_BUFFER_BIT);
// Replace the current matrix with the identity matrix
gl.glLoadIdentity();
// Translates 10 units into the screen.
gl.glTranslatef( 0 , 0 , - 10 );
// SQUARE A
// Save the current matrix.
gl.glPushMatrix();
// Rotate square A counter-clockwise.
gl.glRotatef(angle, 0 , 0 , 1 );
// Draw square A.
square.draw(gl);
// Restore the last matrix.
gl.glPopMatrix();
// SQUARE B
// Save the current matrix
gl.glPushMatrix();
// Rotate square B before moving it,
//making it rotate around A.
gl.glRotatef(-angle, 0 , 0 , 1 );
// Move square B.
gl.glTranslatef( 2 , 0 , 0 );
// Scale it to 50% of square A
gl.glScalef(.5f, .5f, .5f);
// Draw square B.
square.draw(gl);
// SQUARE C
// Save the current matrix
gl.glPushMatrix();
// Make the rotation around B
gl.glRotatef(-angle, 0 , 0 , 1 );
gl.glTranslatef( 2 , 0 , 0 );
// Scale it to 50% of square B
gl.glScalef(.5f, .5f, .5f);
// Rotate around it's own center.
gl.glRotatef(angle* 10 , 0 , 0 , 1 );
// Draw square C.
square.draw(gl);
// Restore to the matrix as it was before C.
gl.glPopMatrix();
// Restore to the matrix as it was before B.
gl.glPopMatrix();
// Increse the angle.
angle++;
}
|
前面的例子显示的正方形都是白色,看其来不是很吸引人,本篇介绍如何给Mesh(网格)添加颜色。OpenGL ES使用颜色是我们熟知的RGBA模式(红,绿,蓝,透明度)。
颜色的定义通常使用Hex格式0xFF00FF 或十进制格式(255,0,255), 在OpenGL 中却是使用0…1之间的浮点数表示。 0为0,1相当于255(0xFF)。
最简单的上色方法叫做顶点着色(Vertxt coloring),可以使用单色,也可以定义颜色渐变或者使用材质(类同于二维图形中各种Brush类型)。
Flat coloring(单色)
是通知OpenGL使用单一的颜色来渲染,OpenGL将一直使用指定的颜色来渲染直到你指定其它的颜色。
指定颜色的方法为
public abstract voidglColor4f(float red, float green, float blue, float alpha)。
缺省的red,green,blue为1,代表白色。这也是为什么前面显示的正方形都是白色的缘故。
我们创建一个新的类为FlatColoredSquare,作为Sequare的子类,将它的draw重定义如下:
1
2
3
4
|
public void draw(GL10 gl) {
gl.glColor4f( 0 .5f, 0 .5f, 1 .0f, 1 .0f);
super .draw(gl);
}
|
将OpenGLRenderer的square的类型改为FlatColoredSquare。
1
|
private FlatColoredSquare square= new FlatColoredSquare();
|
编译运行,正方形颜色变成了蓝色:
Smooth coloring (平滑颜色过渡)
当给每个顶点定义一个颜色时,OpenGL自动为不同顶点颜色之间生成中间过渡颜色(渐变色)。
在项目中添加一个SmoothColoredSquare 类,作为Square子类,为每个顶点定义一个颜色值。
1
2
3
4
5
6
7
|
// The colors mapped to the vertices.
float [] colors = {
1f, 0f, 0f, 1f, // vertex 0 red
0f, 1f, 0f, 1f, // vertex 1 green
0f, 0f, 1f, 1f, // vertex 2 blue
1f, 0f, 1f, 1f, // vertex 3 magenta
};
|
颜色定义的顺序和顶点的顺序是一致的。为了提高性能,和顶点坐标一样,我们也把颜色数组放到Buffer中:
1
2
3
4
5
6
7
|
// float has 4 bytes, colors (RGBA) * 4 bytes
ByteBuffer cbb
= ByteBuffer.allocateDirect(colors.length * 4 );
cbb.order(ByteOrder.nativeOrder());
colorBuffer = cbb.asFloatBuffer();
colorBuffer.put(colors);
colorBuffer.position( 0 );
|
最后修改draw方法,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public void draw(GL10 gl) {
gl.glVertexPointer( 3 , GL10.GL_FLOAT, 0 , vertexBuffer);
// Enable the color array buffer to be
//used during rendering.
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
// Point out the where the color buffer is.
gl.glColorPointer( 4 , GL10.GL_FLOAT, 0 , colorBuffer);
super .draw(gl);
// Disable the color buffer.
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
}
|
将OpenGLRenderer中的Square类型改成SmoothColoredSquare,编译运行结果如下:
前面的例子尽管使用了OpenGL ES 3D图形库,但绘制的还是二维图形(平面上的正方形)。Mesh(网格,三角面)是构成空间形体的基本元素,前面的正方形也是有两个Mesh构成的。本篇将介绍使用Mesh构成四面体,椎体等基本空间形体。
Design设计
在使用OpenGL 框架时一个好的设计原则是使用“Composite Pattern”,本篇采用如下设计:
Mesh
首先定义一个基类 Mesh,所有空间形体最基本的构成元素为Mesh(三角形网格) ,其基本定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
public class Mesh {
// Our vertex buffer.
private FloatBuffer verticesBuffer = null ;
// Our index buffer.
private ShortBuffer indicesBuffer = null ;
// The number of indices.
private int numOfIndices = - 1 ;
// Flat Color
private float [] rgba
= new float [] { 1 .0f, 1 .0f, 1 .0f, 1 .0f };
// Smooth Colors
private FloatBuffer colorBuffer = null ;
// Translate params.
public float x = 0 ;
public float y = 0 ;
public float z = 0 ;
// Rotate params.
public float rx = 0 ;
public float ry = 0 ;
public float rz = 0 ;
public void draw(GL10 gl) {
// Counter-clockwise winding.
gl.glFrontFace(GL10.GL_CCW);
// Enable face culling.
gl.glEnable(GL10.GL_CULL_FACE);
// What faces to remove with the face culling.
gl.glCullFace(GL10.GL_BACK);
// Enabled the vertices buffer for writing and
//to be used during
// rendering.
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// Specifies the location and data format
//of an array of vertex
// coordinates to use when rendering.
gl.glVertexPointer( 3 , GL10.GL_FLOAT, 0 , verticesBuffer);
// Set flat color
gl.glColor4f(rgba[ 0 ], rgba[ 1 ], rgba[ 2 ], rgba[ 3 ]);
// Smooth color
if (colorBuffer != null ) {
// Enable the color array buffer to be
//used during rendering.
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glColorPointer( 4 , GL10.GL_FLOAT, 0 , colorBuffer);
}
gl.glTranslatef(x, y, z);
gl.glRotatef(rx, 1 , 0 , 0 );
gl.glRotatef(ry, 0 , 1 , 0 );
gl.glRotatef(rz, 0 , 0 , 1 );
// Point out the where the color buffer is.
gl.glDrawElements(GL10.GL_TRIANGLES, numOfIndices,
GL10.GL_UNSIGNED_SHORT, indicesBuffer);
// Disable the vertices buffer.
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
// Disable face culling.
gl.glDisable(GL10.GL_CULL_FACE);
}
protected void setVertices( float [] vertices) {
// a float is 4 bytes, therefore
//we multiply the number if
// vertices with 4.
ByteBuffer vbb
= ByteBuffer.allocateDirect(vertices.length * 4 );
vbb.order(ByteOrder.nativeOrder());
verticesBuffer = vbb.asFloatBuffer();
verticesBuffer.put(vertices);
verticesBuffer.position( 0 );
}
protected void setIndices( short [] indices) {
// short is 2 bytes, therefore we multiply
//the number if
// vertices with 2.
ByteBuffer ibb
= ByteBuffer.allocateDirect(indices.length * 2 );
ibb.order(ByteOrder.nativeOrder());
indicesBuffer = ibb.asShortBuffer();
indicesBuffer.put(indices);
indicesBuffer.position( 0 );
numOfIndices = indices.length;
}
protected void setColor( float red, float green,
float blue, float alpha) {
// Setting the flat color.
rgba[ 0 ] = red;
rgba[ 1 ] = green;
rgba[ 2 ] = blue;
rgba[ 3 ] = alpha;
}
protected void setColors( float [] colors) {
// float has 4 bytes.
ByteBuffer cbb
= ByteBuffer.allocateDirect(colors.length * 4 );
cbb.order(ByteOrder.nativeOrder());
colorBuffer = cbb.asFloatBuffer();
colorBuffer.put(colors);
colorBuffer.position( 0 );
}
}
|
- setVertices 允许子类重新定义顶点坐标。
- setIndices 允许子类重新定义顶点的顺序。
- setColor /setColors允许子类重新定义颜色。
- x,y,z 定义了平移变换的参数。
- rx,ry,rz 定义旋转变换的参数。
Plane
有了Mesh定义之后,再来构造Plane,plane可以有宽度,高度和深度,宽度定义为沿X轴方向的长度,深度定义为沿Z轴方向长度,高度为Y轴方向。
Segments为形体宽度,高度,深度可以分成的份数。 Segments在构造一个非均匀分布的Surface特别有用,比如在一个游戏场景中,构造地貌,使的Z轴的值随机分布在-0.1到0.1之间,然后给它渲染好看的材质就可以造成地图凹凸不平的效果。
上面图形中Segments为一正方形,但在OpenGL中我们需要使用三角形,所有需要将Segments分成两个三角形。为Plane 定义两个构造函数:
// Let you decide the size of the plane but still only one segment.
public Plane(float width, float height)
// For alla your settings.
public Plane(float width, float height, int widthSegments, int heightSegments)
比如构造一个1 unit 宽和 1 unit高,并分成4个Segments,使用图形表示如下:
左边的图显示了segments ,右边的图为需要创建的Face(三角形)。
Plane类的定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
public class Plane extends Mesh {
public Plane() {
this ( 1 , 1 , 1 , 1 );
}
public Plane( float width, float height) {
this (width, height, 1 , 1 );
}
public Plane( float width, float height, int widthSegments,
int heightSegments) {
float [] vertices
= new float [(widthSegments + 1 )
* (heightSegments + 1 ) * 3 ];
short [] indices
= new short [(widthSegments + 1 )
* (heightSegments + 1 )* 6 ];
float xOffset = width / - 2 ;
float yOffset = height / - 2 ;
float xWidth = width / (widthSegments);
float yHeight = height / (heightSegments);
int currentVertex = 0 ;
int currentIndex = 0 ;
short w = ( short ) (widthSegments + 1 );
for ( int y = 0 ; y < heightSegments + 1 ; y++) {
for ( int x = 0 ; x < widthSegments + 1 ; x++) {
vertices[currentVertex] = xOffset + x * xWidth;
vertices[currentVertex + 1 ] = yOffset + y * yHeight;
vertices[currentVertex + 2 ] = 0 ;
currentVertex += 3 ;
int n = y * (widthSegments + 1 ) + x;
if (y < heightSegments && x < widthSegments) {
// Face one
indices[currentIndex] = ( short ) n;
indices[currentIndex + 1 ] = ( short ) (n + 1 );
indices[currentIndex + 2 ] = ( short ) (n + w);
// Face two
indices[currentIndex + 3 ] = ( short ) (n + 1 );
indices[currentIndex + 4 ] = ( short ) (n + 1 + w);
indices[currentIndex + 5 ] = ( short ) (n + 1 + w - 1 );
currentIndex += 6 ;
}
}
}
setIndices(indices);
setVertices(vertices);
}
}
|
Cube
下面来定义一个正方体(Cube),为简单起见,这个四面体只可以设置宽度,高度,和深度,没有和Plane一样提供Segments支持。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public class Cube extends Mesh {
public Cube( float width, float height, float depth) {
width /= 2 ;
height /= 2 ;
depth /= 2 ;
float vertices[] = { -width, -height, -depth, // 0
width, -height, -depth, // 1
width, height, -depth, // 2
-width, height, -depth, // 3
-width, -height, depth, // 4
width, -height, depth, // 5
width, height, depth, // 6
-width, height, depth, // 7
};
short indices[] = { 0 , 4 , 5 ,
0 , 5 , 1 ,
1 , 5 , 6 ,
1 , 6 , 2 ,
2 , 6 , 7 ,
2 , 7 , 3 ,
3 , 7 , 4 ,
3 , 4 , 0 ,
4 , 7 , 6 ,
4 , 6 , 5 ,
3 , 0 , 1 ,
3 , 1 , 2 , };
setIndices(indices);
setVertices(vertices);
}
}
|
Group
Group可以用来管理多个空间几何形体,如果把Mesh比作Android的View ,Group可以看作Android的ViewGroup,Android的View的设计也是采用的“Composite Pattern”。
Group的主要功能是把针对Group的操作(如draw)分发到Group中的每个成员对应的操作(如draw)。
Group定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
public class Group extends Mesh {
private Vector<Mesh> children = new Vector<Mesh>();
@Override
public void draw(GL10 gl) {
int size = children.size();
for ( int i = 0 ; i < size; i++)
children.get(i).draw(gl);
}
/**
* @param location
* @param object
* @see java.util.Vector#add(int, java.lang.Object)
*/
public void add( int location, Mesh object) {
children.add(location, object);
}
/**
* @param object
* @return
* @see java.util.Vector#add(java.lang.Object)
*/
public boolean add(Mesh object) {
return children.add(object);
}
/**
*
* @see java.util.Vector#clear()
*/
public void clear() {
children.clear();
}
/**
* @param location
* @return
* @see java.util.Vector#get(int)
*/
public Mesh get( int location) {
return children.get(location);
}
/**
* @param location
* @return
* @see java.util.Vector#remove(int)
*/
public Mesh remove( int location) {
return children.remove(location);
}
/**
* @param object
* @return
* @see java.util.Vector#remove(java.lang.Object)
*/
public boolean remove(Object object) {
return children.remove(object);
}
/**
* @return
* @see java.util.Vector#size()
*/
public int size() {
return children.size();
}
}
|
其它建议
上面我们定义里Mesh, Plane, Cube等基本空间几何形体,对于构造复杂图形(如人物),可以预先创建一些通用的几何形体,如果在组合成较复杂的形体。除了上面的基本形体外,可以创建如Cone,Pryamid, Cylinder等基本形体以备后用。
本例示例代码下载见http://www.linuxidc.com/Linux/2011-10/45756p8.htm,显示结果如下:
前面讨论了如何给3D图形染色,更一般的情况是使用位图来给Mesh上色(渲染材质)。主要步骤如下:
创建Bitmap对象
使用材质渲染,首先需要构造用来渲染的Bitmap对象,Bitmap对象可以从资源文件中读取或是从网络下载或是使用代码构造。为简单起见,本例从资源中读取:
1
2
|
Bitmap bitmap = BitmapFactory.decodeResource(contect.getResources(),
R.drawable.icon);
|
要注意的是,有些设备对使用的Bitmap的大小有要求,要求Bitmap的宽度和长度为2的几次幂(1,2,4,8,16,32,64.。。。),如果使用不和要求的Bitmap来渲染,可能只会显示白色。
创建材质(Generating a texture)
下一步使用OpenGL库创建一个材质(Texture),首先是获取一个Texture Id。
1
2
3
4
5
|
// Create an int array with the number of textures we want,
// in this case 1.
int [] textures = new int [ 1 ];
// Tell OpenGL to generate textures.
gl.glGenTextures( 1 , textures, 0 );
|
textures中存放了创建的Texture ID,使用同样的Texture Id ,也可以来删除一个Texture:
1
2
|
// Delete a texture.
gl.glDeleteTextures( 1 , textures, 0 )
|
有了Texture Id之后,就可以通知OpenGL库使用这个Texture:
1
|
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[ 0 ]);
|
设置Texture参数glTexParameter
下一步需要给Texture填充设置参数,用来渲染的Texture可能比要渲染的区域大或者小,这是需要设置Texture需要放大或是缩小时OpenGL的模式:
1
2
3
4
5
6
7
8
9
|
// Scale up if the texture if smaller.
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_MAG_FILTER,
GL10.GL_LINEAR);
// scale linearly when image smalled than texture
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_MIN_FILTER,
GL10.GL_LINEAR);
|
常用的两种模式为GL10.GL_LINEAR和GL10.GL_NEAREST。
需要比较清晰的图像使用GL10.GL_NEAREST:
而使用GL10.GL_LINEAR则会得到一个较模糊的图像:
UV Mapping
下一步要告知OpenGL库如何将Bitmap的像素映射到Mesh上。这可以分为两步来完成:
定义UV坐标
UV Mapping指将Bitmap的像素映射到Mesh上的顶点。UV坐标定义为左上角(0,0),右下角(1,1)(因为使用的2D Texture),下图坐标显示了UV坐标,右边为我们需要染色的平面的顶点顺序:
为了能正确的匹配,需要把UV坐标中的(0,1)映射到顶点0,(1,1)映射到顶点2等等。
1
2
3
4
|
float textureCoordinates[] = { 0 .0f, 1 .0f,
1 .0f, 1 .0f,
0 .0f, 0 .0f,
1 .0f, 0 .0f };
|
如果使用如下坐标定义:
1
2
3
4
|
float textureCoordinates[] = { 0 .0f, 0 .5f,
0 .5f, 0 .5f,
0 .0f, 0 .0f,
0 .5f, 0 .0f };
|
Texture匹配到Plane的左上角部分。
而
1
2
3
4
|
float textureCoordinates[] = { 0 .0f, 2 .0f,
2 .0f, 2 .0f,
0 .0f, 0 .0f,
2 .0f, 0 .0f };
|
将使用一些不存在的Texture去渲染平面(UV坐标为0,0-1,1 而 (0,0)-(2,2)定义超过UV定义的大小),这时需要告诉OpenGL库如何去渲染这些不存在的Texture部分。
有两种设置
- GL_REPEAT重复Texture。
- GL_CLAMP_TO_EDGE只靠边线绘制一次。
下面有四种不同组合:
本例使用如下配置:
1
2
3
4
5
6
|
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_WRAP_S,
GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_WRAP_T,
GL10.GL_REPEAT);
|
然后是将Bitmap资源和Texture绑定起来:
1
|
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0 , bitmap, 0 );
|
使用Texture
为了能够使用上面定义的Texture,需要创建一Buffer来存储UV坐标:
1
2
3
4
5
|
FloatBuffer byteBuf = ByteBuffer.allocateDirect(texture.length * 4 );
byteBuf.order(ByteOrder.nativeOrder());
textureBuffer = byteBuf.asFloatBuffer();
textureBuffer.put(textureCoordinates);
textureBuffer.position( 0 );
|
渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// Telling OpenGL to enable textures.
gl.glEnable(GL10.GL_TEXTURE_2D);
// Tell OpenGL where our texture is located.
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[ 0 ]);
// Tell OpenGL to enable the use of UV coordinates.
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Telling OpenGL where our UV coordinates are.
gl.glTexCoordPointer( 2 , GL10.GL_FLOAT, 0 , textureBuffer);
// ... here goes the rendering of the mesh ...
// Disable the use of UV coordinates.
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Disable the use of textures.
gl.glDisable(GL10.GL_TEXTURE_2D);
|
本例代码是在一个平面上(SimplePlane)下使用Texture来渲染,首先是修改Mesh基类,使它能够支持定义UV 坐标:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// Our UV texture buffer.
private FloatBuffer mTextureBuffer;
/**
* Set the texture coordinates.
*
* @param textureCoords
*/
protected void setTextureCoordinates( float [] textureCoords) {
// float is 4 bytes, therefore we multiply the number if
// vertices with 4.
ByteBuffer byteBuf = ByteBuffer.allocateDirect(
textureCoords.length * 4 );
byteBuf.order(ByteOrder.nativeOrder());
mTextureBuffer = byteBuf.asFloatBuffer();
mTextureBuffer.put(textureCoords);
mTextureBuffer.position( 0 );
}
|
并添加设置Bitmap和创建Texture的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
// Our texture id.
private int mTextureId = - 1 ;
// The bitmap we want to load as a texture.
private Bitmap mBitmap;
/**
* Set the bitmap to load into a texture.
*
* @param bitmap
*/
public void loadBitmap(Bitmap bitmap) {
this .mBitmap = bitmap;
mShouldLoadTexture = true ;
}
/**
* Loads the texture.
*
* @param gl
*/
private void loadGLTexture(GL10 gl) {
// Generate one texture pointer...
int [] textures = new int [ 1 ];
gl.glGenTextures( 1 , textures, 0 );
mTextureId = textures[ 0 ];
// ...and bind it to our array
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
// Create Nearest Filtered Texture
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
GL10.GL_LINEAR);
// Different possible texture parameters, e.g. GL10.GL_CLAMP_TO_EDGE
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
GL10.GL_CLAMP_TO_EDGE);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
GL10.GL_REPEAT);
// Use theAndroidGLUtils to specify a two-dimensional texture image
// from our bitmap
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0 , mBitmap, 0 );
}
|
最后修改draw方法来渲染材质:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
// Indicates if we need to load the texture.
private boolean mShouldLoadTexture = false ;
/**
* Render the mesh.
*
* @param gl
* the OpenGL context to render to.
*/
public void draw(GL10 gl) {
...
// Smooth color
if (mColorBuffer != null ) {
// Enable the color array buffer to be used during rendering.
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glColorPointer( 4 , GL10.GL_FLOAT, 0 , mColorBuffer);
}
if (mShouldLoadTexture) {
loadGLTexture(gl);
mShouldLoadTexture = false ;
}
if (mTextureId != - 1 && mTextureBuffer != null ) {
gl.glEnable(GL10.GL_TEXTURE_2D);
// Enable the texture state
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Point to our buffers
gl.glTexCoordPointer( 2 , GL10.GL_FLOAT, 0 , mTextureBuffer);
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
}
gl.glTranslatef(x, y, z);
...
// Point out the where the color buffer is.
gl.glDrawElements(GL10.GL_TRIANGLES, mNumOfIndices,
GL10.GL_UNSIGNED_SHORT, mIndicesBuffer);
...
if (mTextureId != - 1 && mTextureBuffer != null ) {
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
}
...
}
|
本例使用的SimplePlane定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
package se.jayway.opengl.tutorial.mesh;
/**
* SimplePlane is a setup class for Mesh that creates a plane mesh.
*
* @author Per-Erik Bergman (per-erik.bergman@jayway.com)
*
*/
public class SimplePlane extends Mesh {
/**
* Create a plane with a default with and height of 1 unit.
*/
public SimplePlane() {
this ( 1 , 1 );
}
/**
* Create a plane.
*
* @param width
* the width of the plane.
* @param height
* the height of the plane.
*/
public SimplePlane( float width, float height) {
// Mapping coordinates for the vertices
float textureCoordinates[] = { 0 .0f, 2 .0f, //
2 .0f, 2 .0f, //
0 .0f, 0 .0f, //
2 .0f, 0 .0f, //
};
short [] indices = new short [] { 0 , 1 , 2 , 1 , 3 , 2 };
float [] vertices = new float [] { - 0 .5f, - 0 .5f, 0 .0f,
0 .5f, - 0 .5f, 0 .0f,
- 0 .5f, 0 .5f, 0 .0f,
0 .5f, 0 .5f, 0 .0f };
setIndices(indices);
setVertices(vertices);
setTextureCoordinates(textureCoordinates);
}
}
|
本例示例代码下载见http://www.linuxidc.com/Linux/2011-10/45756p8.htm,到本篇为止介绍了OpenGL ES开发的基本方法,更详细的教程将在以后发布,后面先回到AndroidApiDemos中OpenGL ES的示例。
前面简单介绍了OpenGL ES的开发:
- AndroidOpenGL ES 简明开发教程一:概述
- AndroidOpenGL ES 简明开发教程二:构造OpenGL ES View
- AndroidOpenGL ES 简明开发教程三:3D绘图基本概念
- AndroidOpenGL ES 简明开发教程四:3D 坐标变换
- AndroidOpenGL ES 简明开发教程五:添加颜色
- AndroidOpenGL ES 简明开发教程六: 真正的3D图形
- AndroidOpenGL ES 简明开发教程七:材质渲染
和2D图形相比,3D绘图要复杂的多,Android提供了OpenGL ES 3D 图形开发包,对应熟悉OpenGL开发的不会很难,但如果一直没有从事3D开发过,一时还不容易上手,因此暂时跳过Android ApiDemos 中后OpenGL相关的例子,计划将在后面详细介绍Android OpenGL ES开发,之后补上这部分例子。
ApiDemo 中 OpenGL ES 示例解析
Graphics/OpenGL ES/Compressed Texture
Graphics/OpenGL ES/Cube Map
Graphics/OpenGL ES/Frame Buffer Object
Graphics/OpenGL ES/GLSurfaceView
Graphics/OpenGL ES/Kube
Graphics/OpenGL ES/Matrix Palette Skinning
Graphics/OpenGL ES/OpenGL ES 2.0
Graphics/OpenGL ES/Sprite Text
Graphics/OpenGL ES/Textured Triangle
Graphics/OpenGL ES/Touch Rotate
Graphics/OpenGL ES/Translucent GLSurfaceView
Graphics/SurfaceView Overlay
本文全部源码下载:
免费下载地址在http://linux.linuxidc.com/
用户名与密码都是www.linuxidc.com
具体下载目录在/pub/Android源码集锦/2011年/10月/Android OpenGL ES 简明开发教程相关源码/
ApiDemos 的Graphics示例中含有OpenGL ES 例子,OpenGL ES 主要用来开发3D图形应用的。OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三维图形 API 的子集,针对手机、PDA和游戏主机等嵌入式设备而设计。
下面是维基百科中对应OpenGL ES的简介:
OpenGL ES 是从 OpenGL 裁剪定制而来的,去除了 glBegin/glEnd,四边形(GL_QUADS)、多边形(GL_POLYGONS)等复杂图元等许多非绝对必要的特性。经过多年发展,现在主要有两个版本,OpenGL ES 1.x 针对固定管线硬件的,OpenGL ES 2.x 针对可编程管线硬件。OpenGL ES 1.0 是以 OpenGL 1.3 规范为基础的,OpenGL ES 1.1 是以 OpenGL 1.5 规范为基础的,它们分别又支持 common 和 common lite 两种profile。lite profile只支持定点实数,而common profile既支持定点数又支持浮点数。 OpenGL ES 2.0 则是参照 OpenGL 2.0 规范定义的,common profile发布于2005-8,引入了对可编程管线的支持。
在解析AndroidApiDemos 中OpenGL ES示例前,有必要对OpenGL ES 开发单独做个简明开发教程,可以帮助从未接触过3D开发的程序员了解OpenGL 的开发的基本概念和方法,很多移动手机平台都提供了对OpenGL ES 开发包的支持,因此尽管这里使用Android平台介绍OpenGL ES ,但基本概念和步骤同样适用于其它平台。
简明开发教程主要参考 Jayway Team Blog中OpenGL ES开发教程 , 这是一个写的比较通俗易懂的开发教程,适合OpenGL ES初学者。
除了这个OpenGL ES 简明开发教程外,以后将专门针对OpenGL ES写个由浅入深的开发教程,尽请关注。
在Andorid平台上构造一个OpenGL View非常简单,主要有两方面的工作:
GLSurfaceView
Android平台提供的OpenGL ES API主要定义在包android.opengl ,javax.microedition.khronos.egl ,javax.microedition.khronos.opengles ,java.nio 等几个包中,其中类GLSurfaceView 为这些包中的核心类:
- 起到连接OpenGL ES与Android的View层次结构之间的桥梁作用。
- 使得Open GL ES库适应于Anndroid系统的Activity生命周期。
- 使得选择合适的Frame buffer像素格式变得容易。
- 创建和管理单独绘图线程以达到平滑动画效果。
- 提供了方便使用的调试工具来跟踪OpenGL ES函数调用以帮助检查错误。
因此编写OpenGL ES应用的起始点是从类GLSurfaceView开始,设置GLSurfaceView只需调用一个方法来设置OpenGLView用到的GLSurfaceView.Renderer.
1
|
public void setRenderer(GLSurfaceView.Renderer renderer)
|
GLSurfaceView.Renderer
GLSurfaceView.Renderer定义了一个统一图形绘制的接口,它定义了如下三个接口函数:
1
2
3
4
5
6
7
8
|
// Called when the surface is created or recreated.
public void onSurfaceCreated(GL10 gl, EGLConfig config)
// Called to draw the current frame.
public void onDrawFrame(GL10 gl)
// Called when the surface changed size.
public void onSurfaceChanged(GL10 gl, int width, int height)
|
- onSurfaceCreated : 在这个方法中主要用来设置一些绘制时不常变化的参数,比如:背景色,是否打开 z-buffer等。
- onDrawFrame: 定义实际的绘图操作。
- onSurfaceChanged: 如果设备支持屏幕横向和纵向切换,这个方法将发生在横向<->纵向互换时。此时可以重新设置绘制的纵横比率。
有了上面的基本定义,可以写出一个OpenGL ES应用的通用框架。
创建一个新的Android项目:OpenGLESTutorial, 在项目在添加两个类TutorialPartI.java 和OpenGLRenderer.java.
具体代码如下:
TutorialPartI.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class TutorialPartI extends Activity {
// Called when the activity is first created.
@Override
public void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
this .requestWindowFeature(Window.FEATURE_NO_TITLE); // (NEW)
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN); // (NEW)
GLSurfaceView view = new GLSurfaceView( this );
view.setRenderer( new OpenGLRenderer());
setContentView(view);
}
}
|
OpenGLRenderer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
public class OpenGLRenderer implements Renderer {
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Set the background color to black ( rgba ).
gl.glClearColor( 0 .0f, 0 .0f, 0 .0f, 0 .5f); // OpenGL docs.
// Enable Smooth Shading, default not really needed.
gl.glShadeModel(GL10.GL_SMOOTH); // OpenGL docs.
// Depth buffer setup.
gl.glClearDepthf( 1 .0f); // OpenGL docs.
// Enables depth testing.
gl.glEnable(GL10.GL_DEPTH_TEST); // OpenGL docs.
// The type of depth testing to do.
gl.glDepthFunc(GL10.GL_LEQUAL); // OpenGL docs.
// Really nice perspective calculations.
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, // OpenGL docs.
GL10.GL_NICEST);
}
public void onDrawFrame(GL10 gl) {
// Clears the screen and depth buffer.
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | // OpenGL docs.
GL10.GL_DEPTH_BUFFER_BIT);
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
// Sets the current view port to the new size.
gl.glViewport( 0 , 0 , width, height); // OpenGL docs.
// Select the projection matrix
gl.glMatrixMode(GL10.GL_PROJECTION); // OpenGL docs.
// Reset the projection matrix
gl.glLoadIdentity(); // OpenGL docs.
// Calculate the aspect ratio of the window
GLU.gluPerspective(gl, 45 .0f,
( float ) width / ( float ) height,
0 .1f, 100 .0f);
// Select the modelview matrix
gl.glMatrixMode(GL10.GL_MODELVIEW); // OpenGL docs.
// Reset the modelview matrix
gl.glLoadIdentity(); // OpenGL docs.
}
}
|
编译后运行,屏幕显示一个黑色的全屏。这两个类定义了AndroidOpenGL ES应用的最基本的类和方法,可以看作是OpenGL ES的”Hello ,world”应用,后面将逐渐丰富这个例子来画出3D图型。
框架代码下载见http://www.linuxidc.com/Linux/2011-10/45756p8.htm:可以作为你自己的OpenGL 3D 的初始代码。
前面介绍了使用Android编写OpenGL ES应用的程序框架,本篇介绍3D绘图的一些基本构成要素,最终将实现一个多边形的绘制。
一个3D图形通常是由一些小的基本元素(顶点,边,面,多边形)构成,每个基本元素都可以单独来操作。
Vertex (顶点)
顶点是3D建模时用到的最小构成元素,顶点定义为两条或是多条边交会的地方。在3D模型中一个顶点可以为多条边,面或是多边形所共享。一个顶点也可以代表一个点光源或是Camera的位置。下图中标识为黄色的点为一个顶点(Vertex)。
在Android系统中可以使用一个浮点数数组来定义一个顶点,浮点数数组通常放在一个Buffer(java.nio)中来提高性能。
比如:下图中定义了四个顶点和对应的Android顶点定义:
1
2
3
4
5
6
|
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
};
|
为了提高性能,通常将这些数组存放到java.io 中定义的Buffer类中:
1
2
3
4
5
6
7
|
// a float is 4 bytes, therefore we multiply the
//number if vertices with 4.
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4 );
vbb.order(ByteOrder.nativeOrder());
FloatBuffer vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position( 0 );
|
有了顶点的定义,下面一步就是如何将它们传给OpenGL ES库,OpenGL ES提供一个成为”管道Pipeline”的机制,这个管道定义了一些“开关”来控制OpenGL ES支持的某些功能,缺省情况这些功能是关闭的,如果需要使用OpenGL ES的这些功能,需要明确告知OpenGL “管道”打开所需功能。因此对于我们的这个示例,需要告诉OpenGL库打开 Vertex buffer以便传入顶点坐标Buffer。要注意的使用完某个功能之后,要关闭这个功能以免影响后续操作:
1
2
3
4
5
6
7
8
|
// Enabled the vertex buffer for writing and to be used during rendering.
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); // OpenGL docs.
// Specifies the location and data format of an array of vertex
// coordinates to use when rendering.
gl.glVertexPointer( 3 , GL10.GL_FLOAT, 0 , vertexBuffer); // OpenGL docs.
When you are done with the buffer don't forget to disable it.
// Disable the vertices buffer.
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); // OpenGL docs.
|
Edge(边)
边定义为两个顶点之间的线段。边是面和多边形的边界线。在3D模型中,边可以被相邻的两个面或是多边形形共享。对一个边做变换将影响边相接的所有顶点,面或多边形。在OpenGL中,通常无需直接来定义一个边,而是通过顶点定义一个面,从而由面定义了其所对应的三条边。可以通过修改边的两个顶点来更改一条边,下图黄色的线段代表一条边:
Face (面)
在OpenGL ES中,面特指一个三角形,由三个顶点和三条边构成,对一个面所做的变化影响到连接面的所有顶点和边,面多边形。下图黄色区域代表一个面。
定义面的顶点的顺序很重要在拼接曲面的时候,用来定义面的顶点的顺序非常重要,因为顶点的顺序定义了面的朝向(前向或是后向),为了获取绘制的高性能,一般情况不会绘制面的前面和后面,只绘制面的“前面”。虽然“前面”“后面”的定义可以应人而易,但一般为所有的“前面”定义统一的顶点顺序(顺时针或是逆时针方向)。
下面代码设置逆时针方法为面的“前面”:
1
|
gl.glFrontFace(GL10.GL_CCW);
|
打开 忽略“后面”设置:
1
|
gl.glEnable(GL10.GL_CULL_FACE);
|
明确指明“忽略“哪个面的代码如下:
1
|
gl.glCullFace(GL10.GL_BACK);
|
Polygon (多边形)
多边形由多个面(三角形)拼接而成,在三维空间上,多边形并一定表示这个Polygon在同一平面上。这里我们使用缺省的逆时针方向代表面的“前面Front),下图黄色区域为一个多边形。
来看一个多边形的示例在Android系统如何使用顶点和buffer 来定义,如下图定义了一个正方形:
对应的顶点和buffer 定义代码:
1
2
3
4
5
6
7
8
|
private short [] indices = { 0 , 1 , 2 , 0 , 2 , 3 };
To gain some performance we also put this ones in a byte buffer.
// short is 2 bytes, therefore we multiply the number if vertices with 2.
ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2 );
ibb.order(ByteOrder.nativeOrder());
ShortBuffer indexBuffer = ibb.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position( 0 );
|
Render (渲染)
我们已定义好了多边形,下面就要了解如和使用OpenGL ES的API来绘制(渲染)这个多边形了。OpenGL ES提供了两类方法来绘制一个空间几何图形:
- public abstract voidglDrawArrays(int mode, int first, int count) 使用VetexBuffer 来绘制,顶点的顺序由vertexBuffer中的顺序指定。
- public abstract voidglDrawElements(int mode, int count, int type, Buffer indices) ,可以重新定义顶点的顺序,顶点的顺序由indices Buffer 指定。
前面我们已定义里顶点数组,因此我们将采用glDrawElements 来绘制多边形。
同样的顶点,可以定义的几何图形可以有所不同,比如三个顶点,可以代表三个独立的点,也可以表示一个三角形,这就需要使用mode 来指明所需绘制的几何图形的基本类型。
GL_POINTS
绘制独立的点。
GL_LINE_STRIP
绘制一系列线段。
GL_LINE_LOOP
类同上,但是首尾相连,构成一个封闭曲线。
GL_LINES
顶点两两连接,为多条线段构成。
GL_TRIANGLES
每隔三个顶点构成一个三角形,为多个三角形组成。
GL_TRIANGLE_STRIP
每相邻三个顶点组成一个三角形,为一系列相接三角形构成。
GL_TRIANGLE_FAN
以一个点为三角形公共顶点,组成一系列相邻的三角形。
下面可以来绘制正方形了,在项目中添加一个Square.java 定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
package se.jayway.opengl.tutorial;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import javax.microedition.khronos.opengles.GL10;
public class Square {
// Our vertices.
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
};
// The order we like to connect them.
private short [] indices = { 0 , 1 , 2 , 0 , 2 , 3 };
// Our vertex buffer.
private FloatBuffer vertexBuffer;
// Our index buffer.
private ShortBuffer indexBuffer;
public Square() {
// a float is 4 bytes, therefore we
// multiply the number if
// vertices with 4.
ByteBuffer vbb
= ByteBuffer.allocateDirect(vertices.length * 4 );
vbb.order(ByteOrder.nativeOrder());
vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position( 0 );
// short is 2 bytes, therefore we multiply
//the number if
// vertices with 2.
ByteBuffer ibb
= ByteBuffer.allocateDirect(indices.length * 2 );
ibb.order(ByteOrder.nativeOrder());
indexBuffer = ibb.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position( 0 );
}
/**
* This function draws our square on screen.
* @param gl
*/
public void draw(GL10 gl) {
// Counter-clockwise winding.
gl.glFrontFace(GL10.GL_CCW);
// Enable face culling.
gl.glEnable(GL10.GL_CULL_FACE);
// What faces to remove with the face culling.
gl.glCullFace(GL10.GL_BACK);
// Enabled the vertices buffer for writing
//and to be used during
// rendering.
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// Specifies the location and data format of
//an array of vertex
// coordinates to use when rendering.
gl.glVertexPointer( 3 , GL10.GL_FLOAT, 0 ,
vertexBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, indices.length,
GL10.GL_UNSIGNED_SHORT, indexBuffer);
// Disable the vertices buffer.
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
// Disable face culling.
gl.glDisable(GL10.GL_CULL_FACE);
}
}
|
在OpenGLRenderer 中添加Square成员变量并初始化:
1
2
|
// Initialize our square.
Square square = new Square();
|
并在public void onDrawFrame(GL10 gl) 添加
1
2
|
// Draw our square.
square.draw(gl);
|
来绘制这个正方形,编译运行,什么也没显示,这是为什么呢?这是因为OpenGL ES从当前位置开始渲染,缺省坐标为(0,0,0),和View port 的坐标一样,相当于把画面放在眼前,对应这种情况OpenGL不会渲染离view Port很近的画面,因此我们需要将画面向后退一点距离:
1
2
|
// Translates 4 units into the screen.
gl.glTranslatef( 0 , 0 , - 4 );
|
在编译运行,这次倒是有显示了,当正方形迅速后移直至看不见,这是因为每次调用onDrawFrame 时,每次都再向后移动4个单位,需要加上重置Matrix的代码。
1
2
|
// Replace the current matrix with the identity matrix
gl.glLoadIdentity();
|
最终onDrawFrame的代码如下:
1
2
3
4
5
6
7
8
9
10
|
public void onDrawFrame(GL10 gl) {
// Clears the screen and depth buffer.
gl.glClear(GL10.GL_COLOR_BUFFER_BIT |
GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
gl.glTranslatef( 0 , 0 , - 4 );
// Draw our square.
square.draw(gl); // ( NEW )
}
|
本篇代码下载见http://www.linuxidc.com/Linux/2011-10/45756p8.htm
本篇介绍3D 坐标系下的坐标变换transformations。
Coordinate System坐标系
OpenGL使用了右手坐标系统,右手坐标系判断方法:在空间直角坐标系中,让右手拇指指向x轴的正方向,食指指向y轴的正方向,如果中指能指向z轴的正方向,则称这个坐标系为右手直角坐标系。
Translate平移变换
方法public abstract voidglTranslatef(float x, float y, float z) 用于坐标平移变换。
在上个例子中我们把需要显示的正方形后移了4个单位,就是使用的坐标的平移变换,可以进行多次平移变换,其结果为多个平移矩阵的累计结果,矩阵的顺序不重要,可以互换。
Rotate旋转
方法public abstract voidglRotatef(float angle, float x, float y, float z)用来实现选择坐标变换,单位为角度。 (x,y,z)定义旋转的参照矢量方向。多次旋转的顺序非常重要。
比如你选择一个骰子,首先按下列顺序选择3次:
1
2
3
|
gl.glRotatef(90f, 1 .0f, 0 .0f, 0 .0f);
gl.glRotatef(90f, 0 .0f, 1 .0f, 0 .0f);
gl.glRotatef(90f, 0 .0f, 0 .0f, 1 .0f);
|
然后打算逆向旋转回原先的初始状态,需要有如下旋转:
1
2
3
|
gl.glRotatef(90f, - 1 .0f, 0 .0f, 0 .0f);
gl.glRotatef(90f, 0 .0f, - 1 .0f, 0 .0f);
gl.glRotatef(90f, 0 .0f, 0 .0f, - 1 .0f);
|
或者如下旋转:
1
2
3
|
gl.glRotatef(90f, 0 .0f, 0 .0f, - 1 .0f);
gl.glRotatef(90f, 0 .0f, - 1 .0f, 0 .0f);
gl.glRotatef(90f, - 1 .0f, 0 .0f, 0 .0f);
|
旋转变换glRotatef(angle, -x, -y, -z) 和glRotatef(-angle, x, y, z)是等价的,但选择变换的顺序直接影响最终坐标变换的结果。 角度为正时表示逆时针方向。
Translate & Rotate (平移和旋转组合变换)
在对Mesh(网格,构成三维形体的基本单位)同时进行平移和选择变换时,坐标变换的顺序也直接影响最终的结果。
比如:先平移后旋转, 旋转的中心为平移后的坐标。
先选择后平移: 平移在则相对于旋转后的坐标系:
一个基本原则是,坐标变换都是相对于变换的Mesh本身的坐标系而进行的。
Scale(缩放)
方法public abstract voidglScalef(float x, float y, float z)用于缩放变换。
下图为使用gl.glScalef(2f, 2f, 2f) 变换后的基本,相当于把每个坐标值都乘以2.
Translate & Scale(平移和缩放组合变换)
同样当需要平移和缩放时,变换的顺序也会影响最终结果。
比如先平移后缩放:
1
2
|
gl.glTranslatef( 2 , 0 , 0 );
gl.glScalef( 0 .5f, 0 .5f, 0 .5f);
|
如果调换一下顺序:
1
2
|
gl.glScalef( 0 .5f, 0 .5f, 0 .5f);
gl.glTranslatef( 2 , 0 , 0 );
|
结果就有所不同:
矩阵操作,单位矩阵
在进行平移,旋转,缩放变换时,所有的变换都是针对当前的矩阵(与当前矩阵相乘),如果需要将当前矩阵回复最初的无变换的矩阵,可以使用单位矩阵(无平移,缩放,旋转)。
public abstract voidglLoadIdentity()。
在栈中保存当前矩阵和从栈中恢复所存矩阵,可以使用
public abstract voidglPushMatrix()
和
public abstract voidglPopMatrix()。
在进行坐标变换的一个好习惯是在变换前使用glPushMatrix保存当前矩阵,完成坐标变换操作后,再调用glPopMatrix恢复原先的矩阵设置。
最后利用上面介绍的坐标变换知识,来绘制3个正方形A,B,C。进行缩放变换,使的B比A小50%,C比B小50%。 然后以屏幕中心逆时针旋转A,B以A为中心顺时针旋转,C以B为中心顺时针旋转同时以自己中心高速逆时针旋转。
修改 onDrawFrame 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
public void onDrawFrame(GL10 gl) {
// Clears the screen and depth buffer.
gl.glClear(GL10.GL_COLOR_BUFFER_BIT
| GL10.GL_DEPTH_BUFFER_BIT);
// Replace the current matrix with the identity matrix
gl.glLoadIdentity();
// Translates 10 units into the screen.
gl.glTranslatef( 0 , 0 , - 10 );
// SQUARE A
// Save the current matrix.
gl.glPushMatrix();
// Rotate square A counter-clockwise.
gl.glRotatef(angle, 0 , 0 , 1 );
// Draw square A.
square.draw(gl);
// Restore the last matrix.
gl.glPopMatrix();
// SQUARE B
// Save the current matrix
gl.glPushMatrix();
// Rotate square B before moving it,
//making it rotate around A.
gl.glRotatef(-angle, 0 , 0 , 1 );
// Move square B.
gl.glTranslatef( 2 , 0 , 0 );
// Scale it to 50% of square A
gl.glScalef(.5f, .5f, .5f);
// Draw square B.
square.draw(gl);
// SQUARE C
// Save the current matrix
gl.glPushMatrix();
// Make the rotation around B
gl.glRotatef(-angle, 0 , 0 , 1 );
gl.glTranslatef( 2 , 0 , 0 );
// Scale it to 50% of square B
gl.glScalef(.5f, .5f, .5f);
// Rotate around it's own center.
gl.glRotatef(angle* 10 , 0 , 0 , 1 );
// Draw square C.
square.draw(gl);
// Restore to the matrix as it was before C.
gl.glPopMatrix();
// Restore to the matrix as it was before B.
gl.glPopMatrix();
// Increse the angle.
angle++;
}
|
前面的例子显示的正方形都是白色,看其来不是很吸引人,本篇介绍如何给Mesh(网格)添加颜色。OpenGL ES使用颜色是我们熟知的RGBA模式(红,绿,蓝,透明度)。
颜色的定义通常使用Hex格式0xFF00FF 或十进制格式(255,0,255), 在OpenGL 中却是使用0…1之间的浮点数表示。 0为0,1相当于255(0xFF)。
最简单的上色方法叫做顶点着色(Vertxt coloring),可以使用单色,也可以定义颜色渐变或者使用材质(类同于二维图形中各种Brush类型)。
Flat coloring(单色)
是通知OpenGL使用单一的颜色来渲染,OpenGL将一直使用指定的颜色来渲染直到你指定其它的颜色。
指定颜色的方法为
public abstract voidglColor4f(float red, float green, float blue, float alpha)。
缺省的red,green,blue为1,代表白色。这也是为什么前面显示的正方形都是白色的缘故。
我们创建一个新的类为FlatColoredSquare,作为Sequare的子类,将它的draw重定义如下:
1
2
3
4
|
public void draw(GL10 gl) {
gl.glColor4f( 0 .5f, 0 .5f, 1 .0f, 1 .0f);
super .draw(gl);
}
|
将OpenGLRenderer的square的类型改为FlatColoredSquare。
1
|
private FlatColoredSquare square= new FlatColoredSquare();
|
编译运行,正方形颜色变成了蓝色:
Smooth coloring (平滑颜色过渡)
当给每个顶点定义一个颜色时,OpenGL自动为不同顶点颜色之间生成中间过渡颜色(渐变色)。
在项目中添加一个SmoothColoredSquare 类,作为Square子类,为每个顶点定义一个颜色值。
1
2
3
4
5
6
7
|
// The colors mapped to the vertices.
float [] colors = {
1f, 0f, 0f, 1f, // vertex 0 red
0f, 1f, 0f, 1f, // vertex 1 green
0f, 0f, 1f, 1f, // vertex 2 blue
1f, 0f, 1f, 1f, // vertex 3 magenta
};
|
颜色定义的顺序和顶点的顺序是一致的。为了提高性能,和顶点坐标一样,我们也把颜色数组放到Buffer中:
1
2
3
4
5
6
7
|
// float has 4 bytes, colors (RGBA) * 4 bytes
ByteBuffer cbb
= ByteBuffer.allocateDirect(colors.length * 4 );
cbb.order(ByteOrder.nativeOrder());
colorBuffer = cbb.asFloatBuffer();
colorBuffer.put(colors);
colorBuffer.position( 0 );
|
最后修改draw方法,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public void draw(GL10 gl) {
gl.glVertexPointer( 3 , GL10.GL_FLOAT, 0 , vertexBuffer);
// Enable the color array buffer to be
//used during rendering.
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
// Point out the where the color buffer is.
gl.glColorPointer( 4 , GL10.GL_FLOAT, 0 , colorBuffer);
super .draw(gl);
// Disable the color buffer.
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
}
|
将OpenGLRenderer中的Square类型改成SmoothColoredSquare,编译运行结果如下:
前面的例子尽管使用了OpenGL ES 3D图形库,但绘制的还是二维图形(平面上的正方形)。Mesh(网格,三角面)是构成空间形体的基本元素,前面的正方形也是有两个Mesh构成的。本篇将介绍使用Mesh构成四面体,椎体等基本空间形体。
Design设计
在使用OpenGL 框架时一个好的设计原则是使用“Composite Pattern”,本篇采用如下设计:
Mesh
首先定义一个基类 Mesh,所有空间形体最基本的构成元素为Mesh(三角形网格) ,其基本定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
public class Mesh {
// Our vertex buffer.
private FloatBuffer verticesBuffer = null ;
// Our index buffer.
private ShortBuffer indicesBuffer = null ;
// The number of indices.
private int numOfIndices = - 1 ;
// Flat Color
private float [] rgba
= new float [] { 1 .0f, 1 .0f, 1 .0f, 1 .0f };
// Smooth Colors
private FloatBuffer colorBuffer = null ;
// Translate params.
public float x = 0 ;
public float y = 0 ;
public float z = 0 ;
// Rotate params.
public float rx = 0 ;
public float ry = 0 ;
public float rz = 0 ;
public void draw(GL10 gl) {
// Counter-clockwise winding.
gl.glFrontFace(GL10.GL_CCW);
// Enable face culling.
gl.glEnable(GL10.GL_CULL_FACE);
// What faces to remove with the face culling.
gl.glCullFace(GL10.GL_BACK);
// Enabled the vertices buffer for writing and
//to be used during
// rendering.
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// Specifies the location and data format
//of an array of vertex
// coordinates to use when rendering.
gl.glVertexPointer( 3 , GL10.GL_FLOAT, 0 , verticesBuffer);
// Set flat color
gl.glColor4f(rgba[ 0 ], rgba[ 1 ], rgba[ 2 ], rgba[ 3 ]);
// Smooth color
if (colorBuffer != null ) {
// Enable the color array buffer to be
//used during rendering.
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glColorPointer( 4 , GL10.GL_FLOAT, 0 , colorBuffer);
}
gl.glTranslatef(x, y, z);
gl.glRotatef(rx, 1 , 0 , 0 );
gl.glRotatef(ry, 0 , 1 , 0 );
gl.glRotatef(rz, 0 , 0 , 1 );
// Point out the where the color buffer is.
gl.glDrawElements(GL10.GL_TRIANGLES, numOfIndices,
GL10.GL_UNSIGNED_SHORT, indicesBuffer);
// Disable the vertices buffer.
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
// Disable face culling.
gl.glDisable(GL10.GL_CULL_FACE);
}
protected void setVertices( float [] vertices) {
// a float is 4 bytes, therefore
//we multiply the number if
// vertices with 4.
ByteBuffer vbb
= ByteBuffer.allocateDirect(vertices.length * 4 );
vbb.order(ByteOrder.nativeOrder());
verticesBuffer = vbb.asFloatBuffer();
verticesBuffer.put(vertices);
verticesBuffer.position( 0 );
}
protected void setIndices( short [] indices) {
// short is 2 bytes, therefore we multiply
//the number if
// vertices with 2.
ByteBuffer ibb
= ByteBuffer.allocateDirect(indices.length * 2 );
ibb.order(ByteOrder.nativeOrder());
indicesBuffer = ibb.asShortBuffer();
indicesBuffer.put(indices);
indicesBuffer.position( 0 );
numOfIndices = indices.length;
}
protected void setColor( float red, float green,
float blue, float alpha) {
// Setting the flat color.
rgba[ 0 ] = red;
rgba[ 1 ] = green;
rgba[ 2 ] = blue;
rgba[ 3 ] = alpha;
}
protected void setColors( float [] colors) {
// float has 4 bytes.
ByteBuffer cbb
= ByteBuffer.allocateDirect(colors.length * 4 );
cbb.order(ByteOrder.nativeOrder());
colorBuffer = cbb.asFloatBuffer();
colorBuffer.put(colors);
colorBuffer.position( 0 );
}
}
|
- setVertices 允许子类重新定义顶点坐标。
- setIndices 允许子类重新定义顶点的顺序。
- setColor /setColors允许子类重新定义颜色。
- x,y,z 定义了平移变换的参数。
- rx,ry,rz 定义旋转变换的参数。
Plane
有了Mesh定义之后,再来构造Plane,plane可以有宽度,高度和深度,宽度定义为沿X轴方向的长度,深度定义为沿Z轴方向长度,高度为Y轴方向。
Segments为形体宽度,高度,深度可以分成的份数。 Segments在构造一个非均匀分布的Surface特别有用,比如在一个游戏场景中,构造地貌,使的Z轴的值随机分布在-0.1到0.1之间,然后给它渲染好看的材质就可以造成地图凹凸不平的效果。
上面图形中Segments为一正方形,但在OpenGL中我们需要使用三角形,所有需要将Segments分成两个三角形。为Plane 定义两个构造函数:
// Let you decide the size of the plane but still only one segment.
public Plane(float width, float height)
// For alla your settings.
public Plane(float width, float height, int widthSegments, int heightSegments)
比如构造一个1 unit 宽和 1 unit高,并分成4个Segments,使用图形表示如下:
左边的图显示了segments ,右边的图为需要创建的Face(三角形)。
Plane类的定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
public class Plane extends Mesh {
public Plane() {
this ( 1 , 1 , 1 , 1 );
}
public Plane( float width, float height) {
this (width, height, 1 , 1 );
}
public Plane( float width, float height, int widthSegments,
int heightSegments) {
float [] vertices
= new float [(widthSegments + 1 )
* (heightSegments + 1 ) * 3 ];
short [] indices
= new short [(widthSegments + 1 )
* (heightSegments + 1 )* 6 ];
float xOffset = width / - 2 ;
float yOffset = height / - 2 ;
float xWidth = width / (widthSegments);
float yHeight = height / (heightSegments);
int currentVertex = 0 ;
int currentIndex = 0 ;
short w = ( short ) (widthSegments + 1 );
for ( int y = 0 ; y < heightSegments + 1 ; y++) {
for ( int x = 0 ; x < widthSegments + 1 ; x++) {
vertices[currentVertex] = xOffset + x * xWidth;
vertices[currentVertex + 1 ] = yOffset + y * yHeight;
vertices[currentVertex + 2 ] = 0 ;
currentVertex += 3 ;
int n = y * (widthSegments + 1 ) + x;
if (y < heightSegments && x < widthSegments) {
// Face one
indices[currentIndex] = ( short ) n;
indices[currentIndex + 1 ] = ( short ) (n + 1 );
indices[currentIndex + 2 ] = ( short ) (n + w);
// Face two
indices[currentIndex + 3 ] = ( short ) (n + 1 );
indices[currentIndex + 4 ] = ( short ) (n + 1 + w);
indices[currentIndex + 5 ] = ( short ) (n + 1 + w - 1 );
currentIndex += 6 ;
}
}
}
setIndices(indices);
setVertices(vertices);
}
}
|
Cube
下面来定义一个正方体(Cube),为简单起见,这个四面体只可以设置宽度,高度,和深度,没有和Plane一样提供Segments支持。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public class Cube extends Mesh {
public Cube( float width, float height, float depth) {
width /= 2 ;
height /= 2 ;
depth /= 2 ;
float vertices[] = { -width, -height, -depth, // 0
width, -height, -depth, // 1
width, height, -depth, // 2
-width, height, -depth, // 3
-width, -height, depth, // 4
width, -height, depth, // 5
width, height, depth, // 6
-width, height, depth, // 7
};
short indices[] = { 0 , 4 , 5 ,
0 , 5 , 1 ,
1 , 5 , 6 ,
1 , 6 , 2 ,
2 , 6 , 7 ,
2 , 7 , 3 ,
3 , 7 , 4 ,
3 , 4 , 0 ,
4 , 7 , 6 ,
4 , 6 , 5 ,
3 , 0 , 1 ,
3 , 1 , 2 , };
setIndices(indices);
setVertices(vertices);
}
}
|
Group
Group可以用来管理多个空间几何形体,如果把Mesh比作Android的View ,Group可以看作Android的ViewGroup,Android的View的设计也是采用的“Composite Pattern”。
Group的主要功能是把针对Group的操作(如draw)分发到Group中的每个成员对应的操作(如draw)。
Group定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
public class Group extends Mesh {
private Vector<Mesh> children = new Vector<Mesh>();
@Override
public void draw(GL10 gl) {
int size = children.size();
for ( int i = 0 ; i < size; i++)
children.get(i).draw(gl);
}
/**
* @param location
* @param object
* @see java.util.Vector#add(int, java.lang.Object)
*/
public void add( int location, Mesh object) {
children.add(location, object);
}
/**
* @param object
* @return
* @see java.util.Vector#add(java.lang.Object)
*/
public boolean add(Mesh object) {
return children.add(object);
}
/**
*
* @see java.util.Vector#clear()
*/
public void clear() {
children.clear();
}
/**
* @param location
* @return
* @see java.util.Vector#get(int)
*/
public Mesh get( int location) {
return children.get(location);
}
/**
* @param location
* @return
* @see java.util.Vector#remove(int)
*/
public Mesh remove( int location) {
return children.remove(location);
}
/**
* @param object
* @return
* @see java.util.Vector#remove(java.lang.Object)
*/
public boolean remove(Object object) {
return children.remove(object);
}
/**
* @return
* @see java.util.Vector#size()
*/
public int size() {
return children.size();
}
}
|
其它建议
上面我们定义里Mesh, Plane, Cube等基本空间几何形体,对于构造复杂图形(如人物),可以预先创建一些通用的几何形体,如果在组合成较复杂的形体。除了上面的基本形体外,可以创建如Cone,Pryamid, Cylinder等基本形体以备后用。
本例示例代码下载见http://www.linuxidc.com/Linux/2011-10/45756p8.htm,显示结果如下:
前面讨论了如何给3D图形染色,更一般的情况是使用位图来给Mesh上色(渲染材质)。主要步骤如下:
创建Bitmap对象
使用材质渲染,首先需要构造用来渲染的Bitmap对象,Bitmap对象可以从资源文件中读取或是从网络下载或是使用代码构造。为简单起见,本例从资源中读取:
1
2
|
Bitmap bitmap = BitmapFactory.decodeResource(contect.getResources(),
R.drawable.icon);
|
要注意的是,有些设备对使用的Bitmap的大小有要求,要求Bitmap的宽度和长度为2的几次幂(1,2,4,8,16,32,64.。。。),如果使用不和要求的Bitmap来渲染,可能只会显示白色。
创建材质(Generating a texture)
下一步使用OpenGL库创建一个材质(Texture),首先是获取一个Texture Id。
1
2
3
4
5
|
// Create an int array with the number of textures we want,
// in this case 1.
int [] textures = new int [ 1 ];
// Tell OpenGL to generate textures.
gl.glGenTextures( 1 , textures, 0 );
|
textures中存放了创建的Texture ID,使用同样的Texture Id ,也可以来删除一个Texture:
1
2
|
// Delete a texture.
gl.glDeleteTextures( 1 , textures, 0 )
|
有了Texture Id之后,就可以通知OpenGL库使用这个Texture:
1
|
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[ 0 ]);
|
设置Texture参数glTexParameter
下一步需要给Texture填充设置参数,用来渲染的Texture可能比要渲染的区域大或者小,这是需要设置Texture需要放大或是缩小时OpenGL的模式:
1
2
3
4
5
6
7
8
9
|
// Scale up if the texture if smaller.
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_MAG_FILTER,
GL10.GL_LINEAR);
// scale linearly when image smalled than texture
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_MIN_FILTER,
GL10.GL_LINEAR);
|
常用的两种模式为GL10.GL_LINEAR和GL10.GL_NEAREST。
需要比较清晰的图像使用GL10.GL_NEAREST:
而使用GL10.GL_LINEAR则会得到一个较模糊的图像:
UV Mapping
下一步要告知OpenGL库如何将Bitmap的像素映射到Mesh上。这可以分为两步来完成:
定义UV坐标
UV Mapping指将Bitmap的像素映射到Mesh上的顶点。UV坐标定义为左上角(0,0),右下角(1,1)(因为使用的2D Texture),下图坐标显示了UV坐标,右边为我们需要染色的平面的顶点顺序:
为了能正确的匹配,需要把UV坐标中的(0,1)映射到顶点0,(1,1)映射到顶点2等等。
1
2
3
4
|
float textureCoordinates[] = { 0 .0f, 1 .0f,
1 .0f, 1 .0f,
0 .0f, 0 .0f,
1 .0f, 0 .0f };
|
如果使用如下坐标定义:
1
2
3
4
|
float textureCoordinates[] = { 0 .0f, 0 .5f,
0 .5f, 0 .5f,
0 .0f, 0 .0f,
0 .5f, 0 .0f };
|
Texture匹配到Plane的左上角部分。
而
1
2
3
4
|
float textureCoordinates[] = { 0 .0f, 2 .0f,
2 .0f, 2 .0f,
0 .0f, 0 .0f,
2 .0f, 0 .0f };
|
将使用一些不存在的Texture去渲染平面(UV坐标为0,0-1,1 而 (0,0)-(2,2)定义超过UV定义的大小),这时需要告诉OpenGL库如何去渲染这些不存在的Texture部分。
有两种设置
- GL_REPEAT重复Texture。
- GL_CLAMP_TO_EDGE只靠边线绘制一次。
下面有四种不同组合:
本例使用如下配置:
1
2
3
4
5
6
|
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_WRAP_S,
GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_WRAP_T,
GL10.GL_REPEAT);
|
然后是将Bitmap资源和Texture绑定起来:
1
|
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0 , bitmap, 0 );
|
使用Texture
为了能够使用上面定义的Texture,需要创建一Buffer来存储UV坐标:
1
2
3
4
5
|
FloatBuffer byteBuf = ByteBuffer.allocateDirect(texture.length * 4 );
byteBuf.order(ByteOrder.nativeOrder());
textureBuffer = byteBuf.asFloatBuffer();
textureBuffer.put(textureCoordinates);
textureBuffer.position( 0 );
|
渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// Telling OpenGL to enable textures.
gl.glEnable(GL10.GL_TEXTURE_2D);
// Tell OpenGL where our texture is located.
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[ 0 ]);
// Tell OpenGL to enable the use of UV coordinates.
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Telling OpenGL where our UV coordinates are.
gl.glTexCoordPointer( 2 , GL10.GL_FLOAT, 0 , textureBuffer);
// ... here goes the rendering of the mesh ...
// Disable the use of UV coordinates.
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Disable the use of textures.
gl.glDisable(GL10.GL_TEXTURE_2D);
|
本例代码是在一个平面上(SimplePlane)下使用Texture来渲染,首先是修改Mesh基类,使它能够支持定义UV 坐标:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// Our UV texture buffer.
private FloatBuffer mTextureBuffer;
/**
* Set the texture coordinates.
*
* @param textureCoords
*/
protected void setTextureCoordinates( float [] textureCoords) {
// float is 4 bytes, therefore we multiply the number if
// vertices with 4.
ByteBuffer byteBuf = ByteBuffer.allocateDirect(
textureCoords.length * 4 );
byteBuf.order(ByteOrder.nativeOrder());
mTextureBuffer = byteBuf.asFloatBuffer();
mTextureBuffer.put(textureCoords);
mTextureBuffer.position( 0 );
}
|
并添加设置Bitmap和创建Texture的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
// Our texture id.
private int mTextureId = - 1 ;
// The bitmap we want to load as a texture.
private Bitmap mBitmap;
/**
* Set the bitmap to load into a texture.
*
* @param bitmap
*/
public void loadBitmap(Bitmap bitmap) {
this .mBitmap = bitmap;
mShouldLoadTexture = true ;
}
/**
* Loads the texture.
*
* @param gl
*/
private void loadGLTexture(GL10 gl) {
// Generate one texture pointer...
int [] textures = new int [ 1 ];
gl.glGenTextures( 1 , textures, 0 );
mTextureId = textures[ 0 ];
// ...and bind it to our array
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
// Create Nearest Filtered Texture
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
GL10.GL_LINEAR);
// Different possible texture parameters, e.g. GL10.GL_CLAMP_TO_EDGE
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
GL10.GL_CLAMP_TO_EDGE);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
GL10.GL_REPEAT);
// Use theAndroidGLUtils to specify a two-dimensional texture image
// from our bitmap
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0 , mBitmap, 0 );
}
|
最后修改draw方法来渲染材质:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
// Indicates if we need to load the texture.
private boolean mShouldLoadTexture = false ;
/**
* Render the mesh.
*
* @param gl
* the OpenGL context to render to.
*/
public void draw(GL10 gl) {
...
// Smooth color
if (mColorBuffer != null ) {
// Enable the color array buffer to be used during rendering.
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glColorPointer( 4 , GL10.GL_FLOAT, 0 , mColorBuffer);
}
if (mShouldLoadTexture) {
loadGLTexture(gl);
mShouldLoadTexture = false ;
}
if (mTextureId != - 1 && mTextureBuffer != null ) {
gl.glEnable(GL10.GL_TEXTURE_2D);
// Enable the texture state
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Point to our buffers
gl.glTexCoordPointer( 2 , GL10.GL_FLOAT, 0 , mTextureBuffer);
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
}
gl.glTranslatef(x, y, z);
...
// Point out the where the color buffer is.
gl.glDrawElements(GL10.GL_TRIANGLES, mNumOfIndices,
GL10.GL_UNSIGNED_SHORT, mIndicesBuffer);
...
if (mTextureId != - 1 && mTextureBuffer != null ) {
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
}
...
}
|
本例使用的SimplePlane定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
package se.jayway.opengl.tutorial.mesh;
/**
* SimplePlane is a setup class for Mesh that creates a plane mesh.
*
* @author Per-Erik Bergman (per-erik.bergman@jayway.com)
*
*/
public class SimplePlane extends Mesh {
/**
* Create a plane with a default with and height of 1 unit.
*/
public SimplePlane() {
this ( 1 , 1 );
}
/**
* Create a plane.
*
* @param width
* the width of the plane.
* @param height
* the height of the plane.
*/
public SimplePlane( float width, float height) {
// Mapping coordinates for the vertices
float textureCoordinates[] = { 0 .0f, 2 .0f, //
2 .0f, 2 .0f, //
0 .0f, 0 .0f, //
2 .0f, 0 .0f, //
};
short [] indices = new short [] { 0 , 1 , 2 , 1 , 3 , 2 };
float [] vertices = new float [] { - 0 .5f, - 0 .5f, 0 .0f,
0 .5f, - 0 .5f, 0 .0f,
- 0 .5f, 0 .5f, 0 .0f,
0 .5f, 0 .5f, 0 .0f };
setIndices(indices);
setVertices(vertices);
setTextureCoordinates(textureCoordinates);
}
}
|
本例示例代码下载见http://www.linuxidc.com/Linux/2011-10/45756p8.htm,到本篇为止介绍了OpenGL ES开发的基本方法,更详细的教程将在以后发布,后面先回到AndroidApiDemos中OpenGL ES的示例。
前面简单介绍了OpenGL ES的开发:
- AndroidOpenGL ES 简明开发教程一:概述
- AndroidOpenGL ES 简明开发教程二:构造OpenGL ES View
- AndroidOpenGL ES 简明开发教程三:3D绘图基本概念
- AndroidOpenGL ES 简明开发教程四:3D 坐标变换
- AndroidOpenGL ES 简明开发教程五:添加颜色
- AndroidOpenGL ES 简明开发教程六: 真正的3D图形
- AndroidOpenGL ES 简明开发教程七:材质渲染
和2D图形相比,3D绘图要复杂的多,Android提供了OpenGL ES 3D 图形开发包,对应熟悉OpenGL开发的不会很难,但如果一直没有从事3D开发过,一时还不容易上手,因此暂时跳过Android ApiDemos 中后OpenGL相关的例子,计划将在后面详细介绍Android OpenGL ES开发,之后补上这部分例子。
ApiDemo 中 OpenGL ES 示例解析
Graphics/OpenGL ES/Compressed Texture
Graphics/OpenGL ES/Cube Map
Graphics/OpenGL ES/Frame Buffer Object
Graphics/OpenGL ES/GLSurfaceView
Graphics/OpenGL ES/Kube
Graphics/OpenGL ES/Matrix Palette Skinning
Graphics/OpenGL ES/OpenGL ES 2.0
Graphics/OpenGL ES/Sprite Text
Graphics/OpenGL ES/Textured Triangle
Graphics/OpenGL ES/Touch Rotate
Graphics/OpenGL ES/Translucent GLSurfaceView
Graphics/SurfaceView Overlay
本文全部源码下载:
免费下载地址在http://linux.linuxidc.com/
用户名与密码都是www.linuxidc.com
具体下载目录在/pub/Android源码集锦/2011年/10月/Android OpenGL ES 简明开发教程相关源码/