Aufbruch in die dritte Dimension

Mobile 3D Graphics API

Lars Röwekamp und Sebastian Szczygiel

Nach wie vor sind Klingeltöne, Logos und Games die Umsatzbringer Nummer eins im Geschäftsumfeld von Mobiltelefonen. Während in den frühen Anfängen nahezu alle Mobile Games in C oder C++ entwickelt wurden, setzt sich in der jüngsten Vergangenheit mehr und mehr die mobile Variante der Programmiersprache Java durch. Einen weiteren deutlichen Schub erwarten sich die Hersteller von Endgeräten durch die vielfältigen Möglichkeiten des neuen 3D Graphics API.

Computerspiele sind seit jeher eine besondere Herausforderung für die Entwicklergemeinde gewesen und werden dies wohl auch in Zukunft bleiben. Nicht umsonst liefen viele der Entwickler der ersten DOOM-Variante voller Stolz mit einem T-Shirt herum, welches sie als Urheber dieses Machwerks outete. Abgesehen von dem eigentlichen Spielwitz sind Anwendungen dieser Art immer ein guter Indikator für das aktuell Machbare im Bereich der 3D-Computer-Grafik.

Seit Einführung des Java 3D API für J2SE lassen sich auch in Java einfach und effizient 3D-Anwendungen und Applets entwickeln, ohne dabei in die typischen Probleme zu laufen, welche die klassische Grafikprogrammiersprache C bzw. C++ mit sich bringt. Um eine einfache 3D-Entwicklung auch für mobile, J2ME-fähige Geräte zu ermöglichen, hat Sun unter dem Java Specification Request (JSR) 184 bereits vor geraumer Zeit das Mobile 3D Graphics API for J2ME (im Folgenden M3G API) spezifiziert, welche seit Ende Dezember 2003 als Final Release vorliegt. Und auch ein Endgerät, welches dieses API umsetzt, ließ nicht lange auf sich warten.

Das im Sommer letzten Jahres auf dem Markt erschienene Sony Ericsson K700 ist das erste Mobiltelefon, auf welchem dieses API implementiert ist. Das vorinstallierte Spiel „Super Real Tennis“ von Sega Mobile zeigt eindrucksvoll, wie die Zukunft von mobilen 3D-Spielen aussehen kann und wird. Die Umsetzung der M3G API-Spezifikation auf diesem Mobiltelefon trägt den Namen Mascot Capsule 3D Engine und stammt von dem japanischen Unternehmen Hicorp. Neben dieser Implementierung existieren derzeit noch zwei weitere Varianten. Da ist zum einen die M3G-Implementierung von der britischen Firma Superscape namens Swerve und zum anderen ein Produkt aus Finnland mit dem Namen Hybrid M3G von Hybrid Graphics.

Da der Swerve Client zusammen mit dem Wireless Toolkit ab Version 2.2 ausgeliefert wird, dürfte er unweigerlich zu der bekanntesten M3G-Implementierung werden. Neben der eben erwähnten MIDP 2.0-Variante wurde Swerve unter anderem auch für Symbian, Linux, Brew und Pocket PC (2000, 02, 03) – insgesamt 15 unterschiedliche Plattformen – portiert. Als fester Bestandteil des WTK 2.2 ist Swerve natürlich zu 100 Prozent JSR 184-konform. Was sich hier so lapidar anhört, bedeutet letztendlich, dass diese API-Implementierung das entsprechende TCK (Technology Compatibility Kit) durchlaufen haben muss, welches mit mehr als 250.000 Tests umfangreicher ist als das der MIDP 2.0-Spezifikation selbst. Darüber hinaus verhält sich Swerve laut Hersteller exakt wie die M3G- Referenzimplementierung von Nokia, welche auf der Forum Nokia Website bezogen werden kann. Wie bereits erwähnt, existiert von Swerve nur eine MIDP 2.0-Version, was jedoch nicht die einzige Voraussetzung für ein Mobiltelefon bezüglich des Java 3D Graphics API darstellt. Um all die komplexen Berechnungen, welche die 3D-Entwicklung mit sich bringt, durchzuführen, werden Fließkommazahlen benötigt, welche erst seit der Version 1.1 der CLDC von der JVM unterstützt werden müssen.

Low Level vs. High Level

Nachdem die Verfügbarkeit des M3G API geklärt ist, stellt sich nun die Frage nach der Art und Weise, wie man zum gewünschten Ziel – also zu einer mobilen 3D-Anwendung – gelangt. Für den Zugriff auf das API sieht die Spezifikation zwei unterschiedliche Modi vor:

  • immediate Modus
  • retained Modus

Im immediate-Modus lässt sich das Verhalten des M3G API mit der Low-Level-Funktionalität von OpenGL oder ähnlichen 3D-APIs vergleichen. In diesem Modus kann direkt auf jedes Detail der Darstellung von 3D-Welten zugegriffen werden. So können zum Beispiel Objekte selbst definiert oder aber Texturen, Lichter, Kameras und die Bewegungen der Objekte innerhalb des 3D-Raums bestimmt werden.

Der retained-Modus dagegen versteckt vor dem Entwickler die Low-Level-Funktionalität und erlaubt es, ganze animierte 3D-Szenen zu laden und mit wenigen Zeilen Code auf dem Bildschirm zu präsentieren. Für diesen Zweck wurde in der Spezifikation das Dateiformat .m3g definiert, welches es erlaubt, 3D-Content sehr kompakt in einer einzigen Datei abzuspeichern.

Im Verlauf des Artikels soll der Umgang mit beiden Modi demonstriert werden.

„Hello 3D World“

Die meisten Tutorials für Programmiersprachen beginnen mit einem einfachen „HelloWorld“-Programm, um so dem interessierten Anfänger einen schnellen Einstieg zu ermöglichen. Auch wir werden – wenn auch in leicht abgewandelter Form – diesen Weg beschreiten. In unserem einfachen „Hello 3D World“-Beispiel namens 3DCube soll ein MIDlet erstellt werden, innerhalb dessen ein rotierender 3D-Würfel mit Texturen auf den Würfelseiten dargestellt wird. Wer sich in der Vergangenheit bereits mit Java 3D, anderen 3D-APIs oder allgemein mit der 3D-Programmierung beschäftigt hat, wird sich dabei recht schnell in dem M3G API und somit auch in unserem Beispiel zurechtfinden.

Abb. 1: „Hello 3D World“-Beispielprogramm im Emulator des Wireless Toolkit 2.2

Als Grundvoraussetzung für die Umsetzung des Beispiels wird, wie bereits oben erläutert, das Wireless Toolkit in einer Version ab 2.2 benötigt. Unter dem Projektnamen „Demo3D“ sind dort bereits drei Midlets vorinstalliert, welche einen ersten Einblick in die mobile 3D-Welt vermitteln. Wie binden Sie die Anwendung (Download (ZIP)) in das WTK 2.2 einbinden können. Kopieren Sie zu diesem Zweck zunächst einfach das Verzeichnis 3dcube in das WTK-Verzeichnis wtk22apps. Öffnen Sie anschließend mithilfe des Menüpunktes OPEN PROJECT innerhalb der KToolbar des WTK 2.2 das dort enthaltene Projekt 3dcube. Nun fehlt nur noch ein einfaches Build und schon kann die Beispielanwendung mittels RUN gestartet werden.

Schauen wir uns das Programm und dessen Aufbau einmal genauer an. Interessant aus Sicht der mobilen 3D-Programmierung ist eigentlich nur die Klasse MyCanvas, da in ihr die gesamte Implementierung der 3D-Logik enthalten ist. Auf MainMIDlet dagegen werden wir an dieser Stelle nicht näher eingehen, da es sich bei dieser Klasse lediglich um eine minimale Standardimplementierung handelt, welche dazu dient, den Lebenszyklus des MIDlets zu verwalten. Den größten Teil des Quellcodes beansprucht in der Klasse MyCanvas die Methode init3D(). Innerhalb dieser Methode werden initial alle erforderlichen Eigenschaften zur Darstellung des 3D-Würfels definiert. Entsprechend der bereits bekannten 2-D-Variante Graphics holen wir uns mit dem Aufruf von Graphics3D.getInstance() zunächst einen grafischen Context für 3D-Grafiken.

Wie genau das Rendering von 3D-Grafiken mit Graphics3D funktioniert, wird etwas später beschrieben. Im Moment setzen wir erst einmal mit der Beschreibung der Initialisierungen fort. Die Klasse Camera wird benötigt, um den dreidimensionalen Raum auf die zweidimensionale Bildschirmoberfläche zu transformieren. Licht bringt die Klasse Light in die Szene.

...
camera = new Camera();
camera.setPerspective(5F, (float) getWidth() / (float) getHeight(), 1F, 50F);

light = new Light();
light.setMode(Light.AMBIENT); 
...

Um eine Szene zu beleuchten, stehen vier unterschiedliche Lichteffekte zur Verfügung. Mit Light.AMBIENT entscheiden wir uns für den schnellsten und einfachsten Lichteffekt, welcher lediglich die gesamte Szene mit gleicher Lichtstärke beleuchtet.

Im nächsten Schritt werden anhand eines Array, des so genannten Vertex Array , die einzelnen Flächen des Quadrats mit seinen Ecken definiert. Da wir uns in einem 3D-Raum bewegen besteht natürlich jede Ecke aus einem X-, einem Y- und einem Z-Wert. Und da jede Seite des dreidimensionalen Quadrats aus vier Ecken besteht, werden pro Fläche zwölf Bytes verbraucht. Wieviel Bytes pro Wert im Array vom Speicher verbraucht werden, muss übrigens beim Aufruf des VertexArray-Konstruktors als letzter Parameter mit angeben werden.

...
VertexArray vertArray = new VertexArray(verticies.length / 3, 3, 1);
vertArray.set(0, verticies.length / 3, verticies);
...

Die Angaben des nächsten Byte-Array definieren die Texturkoordinaten des Würfels, welche beschreiben, wie die Textur auf die einzelnen Flächen des Würfels positioniert werden soll. Stellen Sie sich dazu einfach eine Tischdecke vor, die Sie flach auf einen Tisch legen. Sie können die Decke richtig auf den Tisch legen, verkehrt rum oder um 90 Grad gedreht. Bei den Texturkoordinaten, in dem API als S-, T- oder allgemein oft auch als U-, V-Koordinaten bekannt, bezieht sich der Punkt (0, 0) auf die linke obere Ecke der Textur und (1, 1) auf die rechte untere. S und T können jedoch auch größer als 1 sein. Ein S-Wert größer 1 bedeutet, dass die Textur in horizontaler Richtung wiederholt wird und bei dem T-Wert erfolgt die Wiederholung in vertikaler Richtung. Das folgende Beispiel soll die Funktionsweise der ST-Koordinaten verdeutlichen:

...
byte tex[] = {
  2, 0, 0, 0, 2, 3, 0, 3, // vordere Würfelseite
  2, 0, 0, 0, 2, 3, 0, 3, // hintere Würfelseite
  2, 0, 0, 0, 2, 3, 0, 3, // linke Würfelseite
  2, 0, 0, 0, 2, 3, 0, 3, // rechte Würfelseite
  2, 0, 0, 0, 2, 3, 0, 3, // obere Würfelseite
  2, 0, 0, 0, 2, 3, 0, 3  // untere Würfelseite
};
...
Geschrieben von
Lars Röwekamp und Sebastian Szczygiel
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.