Mobile 3D Graphics API (M3G, JSR 184) 為 CLDC 1.1 / MIDP 2.0 設備帶來了保留模式 3D 引擎:場景圖、動畫、照相機、光照、材質、骨骼動畫、M3G 文件格式。本文基於 WTK 2.2 Demo3D 範例,從零構建第一個旋轉 3D 場景,解析核心對象生命週期與渲染管線。
swerve.m3g (WTK 安裝目錄 apps/Demo3D/res/com/superscape/m3g/wtksamples/retainedmode/content/)。MIDlet (First3DMIDlet)
└─ Canvas (First3DCanvas) implements Runnable
├─ World (場景根節點,從 .m3g 載入)
├─ Graphics3D (單例渲染引擎)
├─ Camera (視角、投影、視口)
└─ Thread (渲染循環,驅動動畫時間)
package first3d;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class First3DMIDlet extends MIDlet {
private First3DCanvas canvas = new First3DCanvas();
protected void startApp() {
Display.getDisplay(this).setCurrent(canvas);
}
protected void pauseApp() {}
protected void destroyApp(boolean u) {}
}
package first3d;
import javax.microedition.lcdui.*;
import javax.microedition.m3g.*;
import java.util.*;
public class First3DCanvas extends Canvas implements Runnable {
private World world;
private Graphics3D g3d = Graphics3D.getInstance();
private Camera cam;
private int vpX, vpY, vpW, vpH;
private long startTime;
private int validity;
public First3DCanvas() {
// 1. 啟動渲染線程
new Thread(this).start();
try {
// 2. 載入 M3G 場景 (World 是頂層容器)
Object[] objs = Loader.load("/swerve.m3g");
world = (World) objs[0];
// 3. 獲取場景中預定義的活動攝像機
cam = world.getActiveCamera();
// 4. 計算視口以保持投影長寬比
vpX = 0; vpY = 0;
vpW = getWidth(); vpH = getHeight();
float[] params = new float[4];
int projType = cam.getProjection(params); // params: [near, far, fovy/aspect, ...]
if (projType != Camera.GENERIC) {
float aspect = (float) vpW / vpH;
if (aspect < params[1]) { // params[1] 為投影長寬比
float h = vpW / params[1];
vpH = (int) h;
vpY = (getHeight() - vpH) / 2;
} else {
float w = vpH * params[1];
vpW = (int) w;
vpX = (getWidth() - vpW) / 2;
}
}
startTime = System.currentTimeMillis();
} catch (Exception e) {
e.printStackTrace(); // 實際項目應顯示錯誤界面
}
}
protected void paint(Graphics g) {
// 1. 清背景
g.setColor(0); g.fillRect(0, 0, getWidth(), getHeight());
// 2. 綁定 Graphics3D 到 LCDUI Graphics
g3d.bindTarget(g);
// 3. 設置視口
g3d.setViewport(vpX, vpY, vpW, vpH);
// 4. 推進動畫時間 (毫秒)
long elapsed = System.currentTimeMillis() - startTime;
validity = world.animate((int) elapsed); // 更新骨骼/頂點動畫
try {
// 5. 渲染整個場景圖
g3d.render(world);
} finally {
// 6. 釋放綁定,歸還 LCDUI Graphics 控制權
g3d.releaseTarget();
}
}
public void run() {
while (true) {
// 7. 請求重繪視口區域
repaint(vpX, vpY, vpW, vpH);
try { Thread.sleep(30); } catch (InterruptedException e) { break; } // ~33 FPS
}
}
}
Loader.load("/model.m3g")
→ World (場景圖根)
├─ Group/Node (層級結構)
│ ├─ Mesh (幾何體: VertexBuffer + IndexBuffer + Appearance)
│ ├─ Camera (投影/視口/視點)
│ ├─ Light (方向/點/聚光)
│ ├─ Background (純色/圖片/立方體貼圖)
│ └─ AnimationController (時間驅動動畫軌跡)
└─ 調用 world.animate(time) 更新所有控制器
Graphics3D.render(World)
→ 遍歷場景圖 → 視錐剔除 → 頂點變換 → 光照 → 光柵化 → 輸出到 LCDUI Graphics
res/ 目錄,打包進 JAR。Loader.load("/model.m3g") 載入。| 策略 | 說明 |
|---|---|
| 視口裁剪 | g3d.setViewport 限制繪製區域,避免全屏渲染浪費 |
| 層級細節 (LOD) | 遠距離物體換低面數 Mesh,M3G 支持 LOD 節點 |
| 靜態場景預烘焙 | 光照貼圖、法線貼圖燒錄進紋理,運行時無動態光照計算 |
| 動畫時間步長 | world.animate(dt) 傳入真實幀間隔,避免快慢機速度不一 |
| 釋放資源 | destroyApp() 中 world = null; System.gc(); 釋放頂點緩衝/紋理 |
JSR 184 以保留模式場景圖抽象了 3D 渲染管線,「載入 → 綁定 → 設置視口 → 推進時間 → 渲染 → 釋放」六步構成標準幀循環。掌握 World/Camera/Graphics3D 三大核心對象交互,配合 M3G 文件製作管線,即可在功能機上呈現驚艷的 3D 體驗。