您的位置:首页 > 移动开发 > Android开发

3D绘图基本概念

2011-10-19 21:08 309 查看


前面介绍了使用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
67

// 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
67
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 来定义,如下图定义了一个正方形:


1
2
3
4
5
67
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 void glDrawArrays(int mode, int first, int count) 使用VetexBuffer 来绘制,顶点的顺序由vertexBuffer中的顺序指定。

* public abstract void glDrawElements(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_FAN

GL_TRIANGLE_STRIP

每相邻三个顶点组成一个三角形,为一系列相接三角形构成。


以一个点为三角形公共顶点,组成一系列相邻的三角形。


1
2
3
4
5
67
8
9
10
1112
13
14
15
16
17
18
19
20
2122
23
24
25
26
27
28
29
30
3132
33
34
35
36
37
38
39
40
4142
43
44
45
46
47
48
49
50
5152
53
54
55
56
57
58
59
60
6162
63
64
65
66
67
68
69
70
7172
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成员变量并初始化:

12

// Initialize our square.
Square square = new Square();

并在public void onDrawFrame(GL10 gl) 添加

12

// Draw our square.
square.draw(gl);

来绘制这个正方形,编译运行,什么也没显示,这是为什么呢?这是因为OpenGL ES从当前位置开始渲染,缺省坐标为(0,0,0),和View port 的坐标一样,相当于把画面放在眼前,对应这种情况OpenGL不会渲染离view Port很近的画面,因此我们需要将画面向后退一点距离:

12

// Translates 4 units into the screen.
gl.glTranslatef(0, 0, -4);

在编译运行,这次倒是有显示了,当正方形迅速后移直至看不见,这是因为每次调用onDrawFrame 时,每次都再向后移动4个单位,需要加上重置Matrix的代码。

12

// Replace the current matrix with the identity matrix
gl.glLoadIdentity();

最终onDrawFrame的代码如下:

1
2
3
4
5
67
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 )
 
}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息