站内搜索: 请输入搜索关键词

当前页面: 开发资料首页J2ME 专题针对 Java 移动设备的 3D 图形,第 1 部分: M3G 的快速模式

针对 Java 移动设备的 3D 图形,第 1 部分: M3G 的快速模式

摘要: 针对 Java 移动设备的 3D 图形,第 1 部分: M3G 的快速模式
出处:ibm 作者:Claus H?fele, 作家, 自由作家


2005 年 11 月 28 日

本文是此系列两部分中的第 1 部分,介绍了 Mobile 3D Graphics API (JSR 184) 的有关内容。作者将带领您进入 Java 移动设备的 3D 编程世界,并展示了处理光线、摄像机和材质的方法。
在移动设备上玩游戏是一项有趣的消遣。迄今为止,硬件性能已足以满足经典游戏概念的需求,这些游戏确实令人着迷,但图像非常简单。今天,人们开发出大量二维平面动作游戏,其图像更为丰富,弥补了俄罗斯方块和吃豆游戏的单调感。下一步就是迈进 3D 图像的世界。Sony PlayStation Portable 将移动设备能够实现的图像能力展现在世人面前。虽然普通的移动电话在技术上远不及这种特制的游戏机,但由此可以看出整个市场的发展方向。Mobile 3D Graphics API(简称为 M3G)是在 JSR 184(Java 规范请求,Java Specification Request)中定义的,JSR 184 是一项工业成就,用于为支持 Java 程序设计的移动设备提供标准 3D API。

M3G API 大致可分为两部分:快速模式和保留模式。在快速模式下,您渲染的是单独的 3D 对象;而在保留模式下,您需要定义并显示整个 3D 对象世界,包括其外观信息在内。可以将快速模式视为低级的 3D 功能实现方式,保留模式显示 3D 图像的方式更为抽象,令人感觉也更要舒服一些。本文将对快速模式 API 进行介绍。而本系列的第 2 部分将介绍保留模式的使用方法。

M3G 以外的技术

M3G 不是孤独的。HI Corporation 开发的 Mascot Capsule API 在日本国内非常流行,日本三大运营商均以不同形式选用了这项技术,在其他国家也广受欢迎。例如,Sony Ericsson 为手机增加了 M3G 和 HI Corporation 的特定 API。根据应用程序开发人员在 Sony Ericsson 网站上发布的报告,Mascot Capsule 是一种稳定且快速的 3D环境。

JSR 239 也就是 Java Bindings for OpenGL ES,它面向的设备与 M3G 相同。OpenGL ES 是人们熟知的 OpenGL 3D 库的子集,事实上已成为约束设备上本地 3D 实现的标准。JSR 239 定义了一个几乎与 OpenGL ES 的 C 接口相同的 Java API,使现有 OpenGL 内容的移植更为容易。到 2005 年 9 月为止,JSR 239 还依然处于早期的蓝图设计状态。关于它是否会给手机带来深刻的影响,我只能靠推测。尽管 OpenGL ES 与其 API 不兼容,但却对 M3G 的定义产生了一定影响:JSR 184 专家组确保了 MSG 在 OpenGL ES 之上的有效实现。如果您了解 OpenGL,那么就会在 M3G 中看到许多似曾相识的属性。

尽管还有其他可选技术,但 M3G 获得了所有主要电话制造商和运营商的支持。之前我提到过,游戏是最大的吸引力所在,但 M3G 是一种通用 API,您可以将其用于创建各种 3D 内容。未来的几年中,手机将广泛采用 3D API。






回页首




您的第一个 3D 对象

在第一个示例中,我们将创建一个如图 1 所示的立方体。



图 1. 示例立方体: a) 有顶点索引的正面图,b) 切割面的侧面视图(正面,侧面)


这个立方体存在于 M3G 定义的右手坐标系中。举起右手、伸出拇指、食指和中指,保持其中任一手指与其他两指均成直角,那么拇指就表示 x 轴、食指表示 y 轴,中指表示 z 轴。试着将拇指和食指摆成图 1a 中的样子,那么您的中指必然指向自己。我在这里使用了 8 个顶点(立方体的顶点)并使立方体的中心与坐标系的原点相重合。

从图 1 中可以看到,拍摄 3D 场景的摄像机朝向 z 轴的负轴方向,正对立方体。摄像机的位置和属性定义了随后将在屏幕上显示的东西。图 1b 展示了同一场景的侧面视图,这样您就可以更容易地看清摄像机究竟能看到 3D 世界中的哪些地方。限制因素之一就是观察角度,这与使用照相机的情况类似:长焦镜头的视野比广角镜头的观察角度要窄得多。因此观察角度决定了您的视野。与真实世界中的情况不同,3D 计算给我们增加了两个视图边界:近切割面和远切割面。观察角度和切割面共同定义了视域。视域中的一切都是可见的,而超出视域范围的一切均不可见。

在清单 1 中,您可以看到 VerticesSample 类,实现了上面提到的所有内容。


清单 1. 显示立方体的示例,第 1 部分:类成员


package m3gsamples1;

import javax.microedition.lcdui.*;
import javax.microedition.m3g.*;


/**
* Sample displaying a cube defined by eight vertices, which are connected
* by triangles.
*
* @author Claus Hoefele
*/
public class VerticesSample extends Canvas implements Sample
{
/** The cube's vertex positions (x, y, z). */
private static final byte[] VERTEX_POSITIONS = {
-1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1,
-1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1
};

/** Indices that define how to connect the vertices to build
* triangles. */
private static int[] TRIANGLE_INDICES = {
0, 1, 2, 3, 7, 1, 5, 4, 7, 6, 2, 4, 0, 1
};

/** The cube's vertex data. */
private VertexBuffer _cubeVertexData;

/** The cube's triangles defined as triangle strips. */
private TriangleStripArray _cubeTriangles;

/** Graphics singleton used for rendering. */
private Graphics3D _graphics3d;


VerticesSample 继承自 Canvas,应该能够直接绘制到屏幕。并且还实现了 Sample,定义它的目的是协助组织本文中的其他源代码示例。VERTEX_POSITIONS 以同样的顺序定义了与图 1a 相同的 8 个顶点。例如,顶点 0 定义为坐标(-1, -1, 1)。由于我将立方体的中心点放在坐标系原点位置处,因此立方体的各边长应为 2 个单位。随后,摄像机的位置和视角可定义一个单位在屏幕上所占的像素数。

仅有顶点位置还不够,您还必须描述出想要建立的几何图形。只能像逐点描图法那样,将顶点用直线连接起来,最终得到所需图形。但 M3G 也带来了一个约束:必须用三角形建立几何图形。任何多边形都可定义为一组三角形的集合,因此三角形在 3D 实现中应用十分广泛。三角形是基本的绘图操作,在此基础上可建立更为抽象的操作。

不幸的是,如果只能使用三角形描述立方体,就需要 6 条边 * 2 个三角形 * 3 个顶点 = 36 个顶点。这么多重复的顶点显然浪费了大量内存。为节约内存,首先应将顶点与其三角形定义分隔开来。TRIANGLE_INDICES 使用 VERTEX_POSITIONS 数组索引定义几何图形,使顶点可重用。然后用三角形带取代三角形,从而减少索引数量。通过使用三角形带,新的三角形可重用最后两个索引。举例来说,三角形带(0,1,2,3)可转换为两个三角形(0,1,2)及(1,2,3)。图 1a 的各角均已标注相应索引数,如果您在图 1a 的 TRIANGLE_INDICES 中遵循这一规则处理,就会发现两个面之间意外地多出了一些三角形。这只是一种用于避免定义某些三角形带的模式。我曾用一个有 14 个立方体索引的三角形带处理过 8 个顶点的情况。

使用其余的类成员即可绘制出立方体。清单 2 展示了其初始化方法。


清单 2. 显示立方体的示例,第 2 部分:初始化


/**
* Called when this sample is displayed.
*/
public void showNotify()
{
init();
}


/**
* Initializes the sample.
*/
protected void init()
{
// Get the singleton for 3D rendering.
_graphics3d = Graphics3D.getInstance();

// Create vertex data.
_cubeVertexData = new VertexBuffer();

VertexArray vertexPositions =
new VertexArray(VERTEX_POSITIONS.length/3, 3, 1);
vertexPositions.set(0, VERTEX_POSITIONS.length/3, VERTEX_POSITIONS);
_cubeVertexData.setPositions(vertexPositions, 1.0f, null);

// Create the triangles that define the cube; the indices point to
// vertices in VERTEX_POSITIONS.
_cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES,
new int[] {TRIANGLE_INDICES.length});

// Create a camera with perspective projection.
Camera camera = new Camera();
float aspect = (float) getWidth() / (float) getHeight();
camera.setPerspective(30.0f, aspect, 1.0f, 1000.0f);
Transform cameraTransform = new Transform();
cameraTransform.postTranslate(0.0f, 0.0f, 10.0f);
_graphics3d.setCamera(camera, cameraTransform);
}


init() 中的第一个步骤就是使用户获取图形上下文(GC),以便绘制 3D 图形。Graphics3D 是一个单元素,_graphics3d 中保存了一个引用,以备将来使用。接下来,创建一个 VertexBuffer 以保存顶点数据。在后文中可以看到,可以为一个顶点指派多种类型的信息,所有顶点都包含于 VertexBuffer 之中,在设置使用 _cubeVertexData.setPositions() 的 VertexArray 中,您惟一需要获取的信息就是顶点位置。VertexArray 构造函数中保存了顶点数量(8 个)、各顶点的组件数(x, y, z)以及各组件的大小(1 字节)。由于这个立方体非常小,1 个字节足以容纳一个坐标。如果需要创建大型的对象,那么还可以创建使用 Short 值(2 个字节)的 VertexArray。但不能使用实数,只能使用整数。接下来,使用 TRIANGLE_INDICES 中的索引对 TriangleStripArray 进行初始化操作。

初始化代码的最后一部分就是摄像机设置。在 setPersective() 中,可设置观察角度、纵横比和剪贴板。注意纵横比和剪贴板的值应为浮点值。M3G 需要 Java 虚拟机(Java Virtual Machine,JVM)提供浮点值支持,这是在 CLDC 1.1 以后的版本中增加的功能。经过观察后,将摄像机从立方体处移开,以查看对象的全视图。可通过平移实现这一操作,转换 部分将就这一主题进行详细讨论。现在,您只要相信,带有第三个正数参数的 postTranslate() 可使摄像机沿 z 轴移动。

初始化后,您就可以将场景渲染到屏幕上。清单 3 实现了此功能。


清单 3. 显示立方体的示例,第 3 部分:绘图


/**
* Renders the sample on the screen.
*
* @param graphics the graphics object to draw on.
*/
protected void paint(Graphics graphics)
{
_graphics3d.bindTarget(graphics);
_graphics3d.clear(null);
_graphics3d.render(_cubeVertexData, _cubeTriangles,
new Appearance(), null);
_graphics3d.releaseTarget();
}

关于示例代码

如果想尝试建立并运行本文中的示例,可以在 “下载” 部分中下载完整的源代码。我使用了 Sun 的 Java Wireless Toolkit 2.2,并将我的项目配置为使用 MIDP 1.0、CLDC 1.1 —— 当然,还有 M3G。我将各部分的示例均作为单独类加以实现。另外还实现了一个简单的界面,您可以在这里选择及执行各个示例。wi-m3gsamples1.zip 压缩包中包含 readme.txt 文件,其中的信息更为详细。


在 paint() 中,bindTarget() 将 Canvas 的图形上下文指派给 Graphics3D。从而开始渲染 3D 对象,到调用 releaseTarget() 时终止渲染。调用 clear() 清除背景后,即可通过 init() 中创建的顶点数据和三角形绘制对象。许多 Graphics3D 的方法都会抛出不可控异常,但绝大多数错误都是不可恢复的,所以我决定不在代码中使用 try/catch 块。可在 VerticesSample.java 处找到本文的全部源代码。

我编写了一个简单的 MIDlet,用于显示示例。可从 下载 中获得 MIDlet 及本文全部源代码。示例运行结果如图 2 所示。



图 2. 示例立方体


很难看出这个屏幕上的矩形就是立方体,这是因为我将摄像机放置在其正对面,这就像站在一堵白墙前面一样。为什么是白色呢?我还没有指派任何颜色,而默认颜色就是白色。下一节将在颜色方面对程序进行完善。





↑返回目录
前一篇: Java 移动设备的 3D 图形2
后一篇: 给游戏添加音乐