3D技术对我们来说已经非常熟悉了,最常用的3D API有OpenGL和Microsoft的Direct 3D,在桌面游戏中早已广泛应用。对于J2ME程序而言,Mobile 3D Graphics API(JSR184)的出现,使得为手机应用程序添加3D功能成为可能。
JSR184标准(M3G:Mobile 3D Graphics)为Java移动应用程序定义了一个简洁的3D API接口,J2ME程序可以非常方便地使用M3G来实现3D应用比如游
戏等等。M3G被设计为非常轻量级的,整个API的完整实现不超过150kb。
如果熟悉OpenGL技术,那么M3G是非常容易理解的。在JSR-184中,Graphics3D是3D渲染的屏幕接口,World代表整个虚拟3D场景,包括Camera(用于设置观察者视角)、Light(灯光)、Background(背景)和树型结构的任意数量的3D物体。Camera默认是朝向Z轴的负方向。3D对象在计算机中用点(Point,Pixel)、线(Line,Polyline,Spline)、面(Mesh)来描述,各个物体都是由Mesh类创建的。不同的Mesh属性对应不同的物体。构造物体时
,先要构造物体的骨架(即形状),然后用一种材料蒙到骨架上。物体的形状,是由一组点和每个点的法向量决定的。材料则可以选择为图片。物体的具体存储和运算(如旋转、投影、特技处理)等动作都是矩阵运算和变换。
M3G是J2ME的一个可选包,以OpenGL为基础的精简版,总共约有250个方法及30个类,运行在CLDC1.1/CLDC2.0上(必须支持浮点运算),可以在MIDP1.0和MIDP2.0中使用。目前,支持M3G的手机有Nokia 6230/3650/7650/6600、Siemens S65/CX65/S55/M55、Sony-Ericsson K700i/P800/P900、Moto 220/T720等。M3G只是一个Java接口,具体的底层3D引擎一般由C代码实现,比如许多手机厂商的3D引擎采用的便是SuperScape公司的Swerve引擎,这是一个专门为移动设备设计的高性能3D引擎。
类似于Microsoft的D3D,M3G支持两种3D模式:立即模式(immediate mode)和保留模式(retained mode)。在立即模式下,开发者必须手动渲染每一帧,从而获得较快的速度,但代码较繁琐;在保留模式下,开发者只需设置好关键帧,剩下的动画由M3G完成,代码较简单,但速度较慢。M3G也允许混合使用这两种模式。
3D模型可以在程序中创建,但是非常繁琐。因此,M3G提供一个Loader类,允许直接从一个单一的.m3g文件中读入全部3D场景。m3g文件可以通过3D Studio Max之类的软件创建。
M3G基于的需求如下:
1)支持保留的场景图形模式;
2)支持直接访问模式;
3)支持两种模式的混合;
4)支持建模,结构,材质以及场景,灯光的三维要素;
5)支持虚拟的浮点运算,不需要硬件支持的浮点运算;
6)保证大小在实际终端小于150K
7)高效的垃圾收集机制(我个人觉得这点非常的重要)
8)能很好的和MIDP相关的应用接口衉作(当然指的就是Canvas)
Mobile 3D Graphics API中有几个比较重要的类:
- Object3D:几乎包中所有类都继承自此类,并且所有可以渲染的类及从m3g文件中载入的类都是它的子类;
- World:整个场景图形结构的基础,所有3D对象都应加入到World对象中。当载入一个m3g文件时,其根对象一般就是一个World对象。当我们渲染场景时,只要将World对象作为一个渲染对象传递给Graphics3D对象即可。
- Graphics3D:3D图形上下文,是一个单例类,提供对象绑定接口,所有渲染都是通过它来实现的。
- Loader:针对所有场景图形、单独的分支以及属性对象的异步载入器(解析器)。可以用于载入m3g文件,即其中所以3D对象。
基本的程序框架:
M3GCanvas.java:
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.m3g.Graphics3D;
import javax.microedition.m3g.World;
public class M3GCanvas extends GameCanvas implements Runnable {
public static final int FPS = 20; //每秒绘制的帧数
private Graphics3D g3d;
private World world;
private boolean runnable=true;
private Thread thread;
protected M3GCanvas() {
// 不抑制按钮事件
super(false);
setFullScreenMode(true);
g3d = Graphics3D.getInstance();
world = new World();
}
public void run() {
Graphics g = getGraphics();
while (runnable) {
long startTime = System.currentTimeMillis();
// 处理游戏逻辑
try {
// Binds the given Graphics or mutable Image2D
// as the rendering target of this Graphics3D
g3d.bindTarget(g);
g3d.render(world); // Render the world
} finally {
g3d.releaseTarget();
}
flushGraphics();
long endTime = System.currentTimeMillis();
long costTime = endTime - startTime;
if(costTime<1000/FPS)
{
try{
Thread.sleep(1000/FPS-costTime);
}
catch(Exception e){
e.printStackTrace();
}
}
}
System.out.println("Canvas stopped");
}
public void start()
{
thread=new Thread(this);
thread.start();
}
public void stop()
{
this.runnable=false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
M3GDemo.java:
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class M3GDemo extends MIDlet implements CommandListener {
private M3GCanvas canvas;
public M3GDemo() {
this.canvas=new M3GCanvas();
}
protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
this.canvas.stop();
}
protected void pauseApp() {
}
protected void startApp() throws MIDletStateChangeException {
Display.getDisplay(this).setCurrent(this.canvas);
this.canvas.start();
}
public void commandAction(Command c, Displayable d) {
notifyDestroyed();
}
}