|
|
January 25
一、简述
既然现在你已对3D API比较熟悉并了解了3D图形是如何加入到移动Java应用程序中的。下面将继续告诉你怎样使用3D造型软件以使编码和设计更为简单。
如今,3D图形几乎是任何一部游戏的关键部分,甚至一些应用程序也通过用3D形式来描述信息而获得了成功。如前文中所述,以立即模式和手工编码建立所有的3D对象的方式进行开发速度很慢且很复杂。应用程序中多边形的所有角点必须在数组中独立编码。在JSR 184中,这称为立即模式。
另外一种更高级的模式称为保留模式,它允许设计者使用诸如3D Max Studio等3D建模软件来设计场景图,然后把它们应用在程序中。
二、3D编辑器
现在,最流行的商业动画制作软件应是3D Studio Max,它支持输出模型或场景图到M3G格式(JSR 184中指定的文件格式)。该文件格式是专门制订的,以适用于移动设备的特有需要。然而,3D Studio Max非常昂贵,即使它是一个很好的工具,也可能并不适合于任何一个人。 Superscape公司有他自己的Swerve产品家族(Swerve Studio,Swerve Client,Swerve Content),以帮助软件开发者来开发基于3D Java的本机应用程序。遗憾的是,Swerve Studio仅适于有限数目的对Superscape非常熟悉的开发者。
还有一个自由工具可以选择使用:Blender。Blender是一个开源的3D造型工具,其实它的功能相当强大。你可以用Blender来进行任何3D设计-从简单的造型到完整的动画制作。尽管现在还没有输出工具来输出Blender模型到M3G文件中,但是可能很快就出现一些可用的工具(因为Blender是开源的)。
三、建模
如何在MIDP应用程序中使用M3G 文件呢?首先,你需要一个已有某种3D模型的M3G文件。你可以用Google引擎快速查找一下,也可以使用和WirelessToolkit 2.2(在Demo3D 文件夹下)开发包一起发布的现成文件。在本文中,我们将对Sun的Pogoroo例程(编者注:Sun开发工具包自带例程)作深度修改(简化)。我们不让它动起来或者做任何奇特的事情,而仅仅在屏幕上展示各个对象。
四、加载World
首先,要从M3D文件中加载World。在pogoroo.m3g文件中,你会看到一只袋鼠在一根弹簧单高跷杆上跳跃,其身边是一片绿茵。下面的列表1调用了加载器类的方法load()。
列表1. 加载
try { //从M3D文件中加载World myWorld = (World)Loader.load("/pogoroo.m3g")[0]; getObjects(); setupAspectRatio(); } catch(Exception e) { e.printStackTrace(); }
五、从3D世界中取得对象
3D世界已经被加载,现在你必须从中取得各个对象(见列表2)。这里,3D世界中有四个对象,其中之一是有关动画(袋鼠在单脚跳)的信息。你可以使用World的find()方法来取得这些对象。
列表2. 从3D World中取得对象
try { tRoo = (Group) myWorld.find(POGOROO); tCams = (Group) myWorld.find(CAMERA); acRoo = (Group) myWorld.find(TRANSFORM); animRoo = (AnimationController) myWorld.find(ROO); //取得动画的长度 AnimationTrack track = acRoo.getAnimationTrack(0); animLength = 1000; // 缺省长度为1秒 if (track != null) { KeyframeSequence ks = track.getKeyframeSequence(); if (ks != null) animLength = ks.getDuration(); }
} catch(Exception e) { e.printStackTrace(); }
六、设置窗口宽高比例
你必须设置窗口的宽高比例以使对象能够正确着色。列表3中的代码是未改动的-基本上同Sun的例子一样。首先,检查画布的宽度和高度,然后根据相机的类型来计算宽高比例。
列表3. 设置宽高比例
void setupAspectRatio() { viewport_x = 0; viewport_y = 0; viewport_width = myCanvas.getWidth(); viewport_height = myCanvas.getHeight(); Camera cam = myWorld.getActiveCamera(); float[] params = new float[4]; int type = cam.getProjection(params); if(type != Camera.GENERIC) { //计算窗口的宽高比 float waspect=viewport_width/viewport_height; if (waspect float height = viewport_width/params[1]; viewport_height=(int)height; viewport_y=(myCanvas.getHeight()-viewport_height)/2; } else { float width = viewport_height*params[1]; viewport_width=(int)width; viewport_x=(myCanvas.getWidth()-viewport_width)/2; } } }
七、刷新视图
为了刷新视图,你可以用TimerTask来调用画布的repaint()方法。另一种方法是直接使用线程,然后创建ExampleCanvas(画布类的名字)来实现Runnable接口。
列表4. 刷新视图
private class RefreshTask extends TimerTask { public void run(){ if(myCanvas != null && myGraphics3D != null && myWorld != null) { int startTime = (int)System.currentTimeMillis(); int validity = myWorld.animate(startTime); myCanvas.repaint(viewport_x, viewport_y, viewport_width, viewport_height); } } }
一、简述 现在,移动游戏和移动应用开发极为热门!游戏中需要有时髦漂亮的图形,其设计标准比以前任何时候都要高。本文将告诉你怎样用酷毙的移动3D图形API为J2ME设备开发3D图形游戏。 如果你在用MIDP1.0进行用户接口编程,那么有两条路你可以选择:使用高级的UI类或者一切由你自己从头开始。作为游戏开发者,第一种选择往往是不可能的;这是为什么游戏开发者不得不为他们的高级游戏开发自己的3D引擎的原因。无疑,这需要付出大量的时间和努力,而缺乏浮点数支持的CLDC 1.0(MIDP 1.0正是建于其上)对问题的解决没有多大帮助。 在MIDP 2.0中,有一个可选的叫移动3D图形API的软件包,或者叫JSR 184。该API是第一个基于Java标准开发的移动设备上的三维图形软件包。该API既有高级又有低级图形特征;其中,高级特征称为保留模式,低级特征称为立即模式。保留模式使得开发者有可能使用场景图形并使场景中的物体根据虚拟相机和灯光的位置进行自身的着色。立即模式能够允许应用程序直接进行物体绘制。如果需要,可以在同一个应用程序中使用这两种模式。 本文着重介绍立即模式。 二、3D API 让我们以列举和解释该3D API中的类作为开始。除了这些API外,JSR 184还包含了一个场景图形结构和一个相应的文件格式以有效地管理和配置3D内容。该文件格式定义了一种m3g文件,这种文件典型地从3D建模文件应用程序中转换而来。 表1.3D API类
| 类
| 描述
|
| AnimationController
| 控制动画顺序。
|
| AnimationTrack
| 把一个KeyframeSequence同一个AnimationController相关联。
|
| Appearance
| 定义一个网眼(Mesh)或一个Spring3D的着色属性的一组对象。
|
| Background
| 定义视图是怎样被清除的。
|
| Camera
| 一个场景图顶点,它定义了场景中观察者的位置以及从3D到2D的投影。
|
| CompositingMode
| 一个Appearance类,它封装了每一个像素的合成属性。
|
| Fog
| 一个Appearance类,它包含了雾化的有关属性。
|
| Graphics3D
| 一个单独的3D图形上下文。所有的着色操作都是在该类中的render()方法中实现的。
|
| Group
| 一个场景图形结点,它存储了一个无序的结点集作为它的子结点。
|
| Image2D
| 一个二维图像,可用于纹理,背景,或者精灵图像。
|
| IndexBuffer
| 该类定义了如何把顶点连接起来以形成一个几何体。
|
| KeyframeSequence
| 封装了一系列的具有时间戳和矢量值的关键帧的动画数据。
|
| Light
| 描述了不同类型的光源。
|
| Loader
| 下载和反串行化图形结点及结点成分,以及整个场景图形。
|
| Material
| 封装了进行光学计算的材质属性。
|
| Mesh
| 描述了一个3D对象,它是用多边形面定义的。
|
| MorphingMesh
| 描述了一个顶点-变形的多边形网眼。
|
| Node
| 所有场景图形结点的抽象基类。其五个具体子类是:Camera,Mesh,Sprite3D,Light和Group。
|
| Object3D
| 所有可以成为3D世界中组成部分的对象的抽象基类。
|
| olygonMode
| 封装了多边形级别属性。
|
| RayIntersection
| 存储了对于分割的Mesh或Sprite3D的引用,以及有关分割点的信息。
|
| SkinnedMesh
| 描述了一个框架动画的多边形网眼。
|
| Sprite3D
| 用3D位置来描述一个2D图像。
|
| Texture2D
| 封装了一个2D纹理图像和一个属性集合,这些属性指出该图像是如何应用到子网眼上的。
|
| Transform
| 一个通用的4x4的浮点数矩阵,用来描述一个变换。
|
| Transformable
| Node和Texture2D类的抽象基类。
|
| TriangleStripArray
| 定义了一个三角形带数组。
|
| VertexArray
| 一个整型矢量数组,描述了顶点位置,法线,颜色或者纹理坐标。
|
| VertexBuffer
| 存储对于VertexArrays的引用,它包含了一个顶点集的位置,颜色,法线,以及纹理坐标。
|
| World
| 一个特别的Group结点,它作为场景图最顶层的容器。 | 三、举例 我们将开发一个简单的旋转一个多边形的3D应用程序为例。该多边形是一个立方体,它的纹理是一张旧汽车相片。列表1展示了例程midlet的主要类-应用程序的中心类。该类负责创建应用程序并建立起运行MyCanvas的计时器。 列表1. MIDletMain类
import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import java.util.*;
public class MIDletMain extends MIDlet { static MIDletMain midlet; MyCanvas d = new MyCanvas(); Timer iTimer = new Timer();
public MIDletMain() { this.midlet = this; } public void startApp() { Display.getDisplay(this).setCurrent(d); iTimer.schedule( new MyTimerTask(), 0, 40 ); } public void pauseApp() {} public void destroyApp(boolean unconditional) {} public static void quitApp() { midlet.destroyApp(true); midlet.notifyDestroyed(); midlet = null; }
class MyTimerTask extends TimerTask { public void run() { if( d != null ) { d.repaint(); } } } } | 列表2显示了MyCanvas类,该类包含了应用程序的所有图形逻辑。init()方法负责结点的创建,纹理文件的装载并设置纹理,外观和背景也被一起设置。paint()方法负责着色并旋转立方体。图1展示了正在一个模拟器中运行的实际程序。 列表2. MyCanvas类
import javax.microedition.lcdui.*; import javax.microedition.m3g.*;
public class MyCanvas extends Canvas {
private Graphics3D graphics3d; private Camera camera; private Light light; private float angle = 0.0f; private Transform transform = new Transform(); private Background background = new Background(); private VertexBuffer vbuffer; private IndexBuffer indexbuffer; private Appearance appearance; private Material material = new Material(); private Image image;
public MyCanvas() { // 创建Displayable对象以探听命令事件 setCommandListener(new CommandListener() { public void commandAction(Command c, Displayable d) { if (c.getCommandType() == Command.EXIT) { MIDletMain.quitApp();}} }); try { init();} catch(Exception e) { e.printStackTrace();} }
/** * 组件的初始化 */ private void init() throws Exception { addCommand(new Command("Exit", Command.EXIT, 1)); graphics3d = Graphics3D.getInstance();
camera = new Camera(); camera.setPerspective( 60.0f,(float)getWidth()/ (float)getHeight(),1.0f,1000.0f );
light = new Light(); light.setColor(0xffffff); light.setIntensity(1.25f);
short[] vert = { 5, 5, 5, -5, 5, 5, 5,-5, 5, -5,-5, 5, -5, 5,-5, 5, 5,-5, -5,-5,-5, 5,-5,-5, -5, 5, 5, -5, 5,-5, -5,-5, 5, -5,-5,-5, 5, 5,-5, 5, 5, 5, 5,-5,-5, 5,-5, 5, 5, 5,-5, -5, 5,-5, 5, 5, 5, -5, 5, 5, 5,-5, 5, -5,-5, 5, 5,-5,-5, -5,-5,-5 };
VertexArray vertArray = new VertexArray(vert.length / 3, 3, 2); vertArray.set(0, vert.length/3, vert);
//立方体的各个结点法线 byte[] norm = { 0, 0, 127, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0, 0,-127, 0, 0,-127, 0, 0,-127, 0, 0,-127, -127, 0, 0, -127, 0, 0, -127, 0, 0, -127, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0, 0,-127, 0, 0,-127, 0, 0,-127, 0, 0,-127, 0 };
VertexArray normArray = new VertexArray(norm.length / 3, 3, 1); normArray.set(0, norm.length/3, norm);
//各个结点的纹理坐标 short[] tex = { 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1 };
VertexArray texArray = new VertexArray(tex.length / 2, 2, 2); texArray.set(0, tex.length/2, tex);
int[] stripLen = { 4, 4, 4, 4, 4, 4 };
// 对象的VertexBuffer VertexBuffer vb = vbuffer = new VertexBuffer(); vb.setPositions(vertArray, 1.0f, null); vb.setNormals(normArray); vb.setTexCoords(0, texArray, 1.0f, null);
indexbuffer = new TriangleStripArray( 0, stripLen );
//纹理图像 image = Image.createImage( "/pic1.png" ); Image2D image2D = new Image2D( Image2D.RGB, image ); Texture2D texture = new Texture2D( image2D ); texture.setFiltering(Texture2D.FILTER_NEAREST,Texture2D.FILTER_NEAREST); texture.setWrapping(Texture2D.WRAP_CLAMP,Texture2D.WRAP_CLAMP); texture.setBlending(Texture2D.FUNC_MODULATE);
// 创建外观(Appearance)对象 appearance = new Appearance(); appearance.setTexture(0, texture); appearance.setMaterial(material); material.setColor(Material.DIFFUSE, 0xFFFFFFFF); material.setColor(Material.SPECULAR, 0xFFFFFFFF); material.setShininess(100.0f);
background.setColor(0xffffcc); }
protected void paint(Graphics g) { graphics3d.bindTarget(g, true, Graphics3D.DITHER | Graphics3D.TRUE_COLOR); graphics3d.clear(background);
//设置照相机 Transform transform = new Transform(); transform.postTranslate(0.0f, 0.0f, 30.0f); graphics3d.setCamera(camera, transform);
//设置灯光 graphics3d.resetLights(); graphics3d.addLight(light, transform);
//设置旋转 angle += 1.0f; transform.setIdentity(); transform.postRotate(angle, 1.0f, 1.0f, 1.0f);
graphics3d.render(vbuffer, indexbuffer, appearance, transform); graphics3d.releaseTarget(); } } |
 图1 正在一个模拟器中运行的应用程序 |
四、小结
JSR 184对于可以运行MIDP 2.0的设备来说,是一个节省时间和空间的可选的软件开发包。它允许开发者使用两种图形方式-保留模式和立即模式-来产生3D图形。本文集中讲述了立即模式,并给出一个例子程序来说明怎样使用3D。
|