90
Department für Informatik Universität Fribourg, Schweiz http://diuf.unifr.ch/ Einführung in die Echtzeit-3D-Grafik mit Java Bachelorarbeit Daniel Egger September 2005 Unter der Aufsicht von: Prof. Dr. J. PASQUIER-ROCHA und Dr. P. FUHRER Software Engineering Group

Einführung in die Echtzeit-3D-Grafik mit Javadiuf.unifr.ch/drupal/sites/diuf.unifr.ch.drupal.softeng/files/... · zwei Wege gehen. Einerseits gibt es OpenGL-Wrapper für Java, wie

  • Upload
    trananh

  • View
    215

  • Download
    0

Embed Size (px)

Citation preview

Department für InformatikUniversität Fribourg, Schweiz

http://diuf.unifr.ch/

Einführung in die Echtzeit-3D-Grafik mit Java

Bachelorarbeit

Daniel EggerSeptember 2005

Unter der Aufsicht von:

Prof. Dr. J. PASQUIER-ROCHA undDr. P. FUHRER

Software Engineering Group

“Mighty is geometry. When joined with art, resistless.”

- Euripides

i

ii

Zusammenfassung

Das Ziel dieser Bachelorarbeit ist es, wie der Titel eigentlich schon treffend ausdrückt, eine Einführungin die Echtzeit-3D-Graphik mit Java zu geben. Als erster Schritt wurden dazu die verfügbaren Optionenuntersucht und angeschaut. Nach Auswahl der Java 3D-Engine jMonkey Engine auch jME genannt [36]wurden deren Einsaztmöglichkeiten getestet. Im dritten und letzten Teil der Arbeit wurde ein Tutorial ge-schrieben, um den Lesern zu zeigen, wie man mit Hilfe von jME Echtzeit-3D-Grafik auf den Bildschirmbringen kann. Es ist vor allem das Resultat dieses Tutorials, was man im Rapport finden kann. Schondiese Einleitung beinhaltet einige Wörter, die den einen oder anderen Leser vielleicht bereits verwirren,ich hoffe aber im Verlauf des Textes die meisten Unklarheiten klären zu können. Es wird aber voraus-gesetzt das die Leser zumindest einige Grundkenntnisse in Java haben oder zumindest gute Kenntnissein einer anderen objektorientierten Programmiersprache. Falls sie noch einige Probleme mit Java haben,kann ich das Buch Thinking in Java von Bruce Eckel[Eck02] empfehlen, das mir einen hervorragendenEinstieg in die objektorientierte Programmierung mit Java bereitet hat.

Schlüsselwörter:3D-Graphik, 3D-Engine, Echtzeit-Rendering, Java, jMonkey Engine, OpenGL,

Inhaltsverzeichnis

I. Einleitung 2

1. Bachelorprojekt 31.1. Beschreibung der Aufgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.2. Projektablauf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1.2.1. 1. Schritt: Auswahl einer 3D-Engine . . . . . . . . . . . . . . . . . . . . . . . . 31.2.2. 2. Schritt: Machbarkeitstest . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.2.3. 3. Schritt: Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

1.3. Gliederung der Dokumentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.4. Konventionen und Notationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2. 3D-Grafik 82.1. Was ist 3D-Grafik? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82.2. Was bedeutet Echtzeit-3D-Grafik? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102.3. Was ist eine 3D-Engine? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132.4. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

II. Tutorial 14

3. Erste Schritte mit jME 153.1. Unser erstes jME-Programm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153.2. Scenegraph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

3.2.1. Was ist ein Scenegraph? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183.2.2. Zustände im Kontext von jME . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

4. Wie funktioniert 3D-Grafik 224.1. Was macht eine 3D-Engine? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224.2. Das 3D-Koordinatensystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

4.2.1. 2D-Koordinatensystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234.2.2. 3D-Koordinatensystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234.2.3. Model-Space vs. World-Space . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

4.3. Transformationen im Raum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254.3.1. Verschiebungen (engl. translation) . . . . . . . . . . . . . . . . . . . . . . . . . 254.3.2. Rotationen (engl. rotation) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254.3.3. Skalierungen (engl. scaling) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254.3.4. Alle Bewegungen zusammen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

4.4. Perspektive und Projektion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264.5. Kamera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

iii

Inhaltsverzeichnis iv

4.6. jME, OpenGL, DirectX, 3D-Pipeline: Was ist das? Was machen die? . . . . . . . . . . . 294.7. Ressourcen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

5. Interaktion 315.1. Einfache Interaktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315.2. Ein objektorientierter Ansatz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

6. Simple Formen und Modelle 376.1. Einfache Formen und warum die Kugeln nicht rund sind . . . . . . . . . . . . . . . . . 376.2. Komplexere Modelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

6.2.1. Modell-Formate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406.2.2. Modelle laden in jME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

7. Es werde Licht 447.1. Lichtquellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447.2. Lichter einsetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

8. Texturen 508.1. Was sind Texturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508.2. Texturen einsetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508.3. Wie geht es weiter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

9. Landschaften 569.1. Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 569.2. Heightmaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 569.3. Landschaften darstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 589.4. Landschaften mit Texturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 619.5. Skybox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

10. Final Island 6810.1. Der Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6810.2. Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

A. CD-ROM 73

B. Ein jME-Demo in der Konsole starten und kompilieren 75B.1. Demos starten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75B.2. Demos kompilieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

C. Verwendete Software 77C.1. Entwicklung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

C.1.1. Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77C.1.2. Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

C.2. Dokumentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77C.2.1. LATEX und Co . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77C.2.2. Violet UML-Tool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77C.2.3. Dia Diagramm-Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78

Abbildungsverzeichnis

1.1. Screenshot aus dem finalen Projekt des Gameversity Kurses . . . . . . . . . . . . . . . 6

2.1. Ein Screenshot von Blender . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92.2. Ein Ausschnitt aus “Geri’s Game” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102.3. Ausschnitt aus dem Film Madagascar . . . . . . . . . . . . . . . . . . . . . . . . . . . 112.4. Zeitlinie Echtzeit-3D-Spiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

3.1. Screenshot aus HelloWorld . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163.2. Screenshot des Einstellungsdialogs der bei jedem Start erscheint . . . . . . . . . . . . . 173.3. Eine Hierarchie von einem Haus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193.4. UML-Diagramm der Scenegraph Elemente . . . . . . . . . . . . . . . . . . . . . . . . 203.5. UML Diagramm der RenderStates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

4.1. Ein rechthändiges 3D-Koordinatensystem . . . . . . . . . . . . . . . . . . . . . . . . . 234.2. Ein Würfel rotiert, bewegt und skaliert . . . . . . . . . . . . . . . . . . . . . . . . . . . 274.3. Projektionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284.4. 3D-Pipeline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284.5. jME und OpenGL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

5.1. Die verschiedenen InputActions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

6.1. Ein Screenshot aus der Demo SimpleGeometry.java . . . . . . . . . . . . . . . . . . . . 386.2. Milkshape 3D mit dem Ferrari Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . 406.3. Screenshot von ModelLoader.java mit Blick auf das Gittermodell . . . . . . . . . . . . . 41

7.1. Eine Szene mit Licht (links) und ohne Licht (rechts) . . . . . . . . . . . . . . . . . . . . 457.2. Ein Punktlicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 457.3. Screenshot aus der Licht Demo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

8.1. Eine Stadt mit und ohne Textur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 518.2. Textur vom Ferrari Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 518.3. Screenshot von der Texturen Demo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 528.4. Beispiel für Bump-Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

9.1. Screenshot aus Oblivion von Bethesda Softworks . . . . . . . . . . . . . . . . . . . . . 579.2. Eine sehr einfache 3D-Landschaft . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 579.3. Ein Beispiel für eine Heightmap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 589.4. Eine Landschaft generiert aus der Heightmap aus Abbildung 9.3 . . . . . . . . . . . . . 599.5. Screenshot aus TerrainDemo.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

v

Abbildungsverzeichnis vi

9.6. Unsere Landschaft mit einigen saftigen, grünen Hügeln . . . . . . . . . . . . . . . . . . 619.7. Rechts die Grastextur, links die Detailtextur . . . . . . . . . . . . . . . . . . . . . . . . 629.8. Würfelbild mit der Skybox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 649.9. Screenshot von der Skybox Demo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

10.1. Final Island: Blick auf das Wasser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6910.2. Final Island: Blick auf das Jeep-Modell . . . . . . . . . . . . . . . . . . . . . . . . . . 6910.3. Beispiel für eine Particle Engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

A.1. CD-Rom Inhalt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

Listings

3.1. HelloWorld.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153.2. HelloWorld.java, simpleInitGame . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174.1. SimpleTransformation.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265.1. InputDemo.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315.2. InputActions.java simpleInitGame and simpleUpdate . . . . . . . . . . . . . . . . . . 345.3. InputActions.java KeyNodeUpAction . . . . . . . . . . . . . . . . . . . . . . . . . . . 366.1. SimpleGeometry.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376.2. ModelLoader.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417.1. SimpleLight.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467.2. Lichter aktivieren mit LightStates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 488.1. TextureDemo.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509.1. TerrainDemo.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 589.2. TerrainWithTexture.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 629.3. SkyBoxTest.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 639.4. SkyBox.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6510.1. FinalIsland.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

1

Teil I.

Einleitung

2

1Bachelorprojekt

1.1. Beschreibung der Aufgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.2. Projektablauf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1.2.1. 1. Schritt: Auswahl einer 3D-Engine . . . . . . . . . . . . . . . . . . . . . . . 31.2.2. 2. Schritt: Machbarkeitstest . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.2.3. 3. Schritt: Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

1.3. Gliederung der Dokumentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.4. Konventionen und Notationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

1.1. Beschreibung der Aufgabe

Ziel dieser Bachelorarbeit ist es, Leuten mit Programmiererfahrung, die 3D-Grafikprogrammierung nä-her zu bringen. Es wird sogar angenommen das die Zielgruppe keinerlei spezielle vorherige Kenntnis-se der 3D-Grafik hat. Das bringt natürlich einige Einschränkungen mit sich. Einerseits will man zwarschnell einige sehenswerte Ergebnisse haben, deshalb wurde auch eine Engine ausgewählt, mit der manschnell etwas auf den Bildschirm “zaubern” kann. Andererseits sollte man trotzdem ein wenig die Theo-rie verstehen. Das ist keine leichte Aufgabe, wenn man bedenkt wie breit und zum Teil auch komplexallein der Teilbereich des 3D-Echtzeitrendering ist. Ich selbst bin häufig auf Schwierigkeiten gestossen,ein bestimmtes Teilgebiet dieser Materie zu erklären. Aber ich hoffe trotz allen Problemen und Schwie-rigkeiten ist das Tutorial immer noch verständlich.

1.2. Projektablauf

Das ganze Bachelorprojekt ist in drei Hauptschritten angegangen worden. Im ersten Schritt wurden ver-schiedene 3D-Engines evaluiert um dann eine von diesen auszuwählen. Im zweiten Schritt wurde einekleine Machbarkeitsstudie ausgeführt um zu sehen. Es wurde eine grössere Beispielanwendung ent-wickelt, die auch das Ziel des eigentlichen Tutorials ist. Im dritten und letzten und bei weitem auchumfangreichsten Schritt wurde das Tutorial selbst mit sehr vielen kleinen Beispielanwendungen ge-schrieben. Aber gehen wir auf die einzelnen Schritte doch noch ein wenig näher ein.

1.2.1. 1. Schritt: Auswahl einer 3D-Engine

Ziel des ersten Abschnitts meiner Bachelorarbeit bestand darin einen Überblick über die fast schonunzähligen 3D-Engines zu gewinnen und danach eine 3D-Engine für den weiteren Verlauf des Projektesauszuwählen. 3D-Engines gibt es wie Sand am Meer möchte man fast meinen. Allein eine Suche aufGoogle mit dem Stichwort “3D Engine” ergibt 469’000 Ergebnisse (Stand: August 2005) und die Zahl

3

1.2. Projektablauf 4

ist steigend. Man kann sich vorstellen, dass unter diesen Bedingungen schon dieser erste Schritt nichtganz leicht ist. Um die Auswahl etwas zu vereinfachen und zu systematisieren wurden einige Kriterienerstellt. Im einzelnen sind das folgende Kriterien:

1. Die 3D-Engine muss mit Java benutzt werden können und auf verschiedenen Plattformen laufen,nicht nur auf Windows sondern zum Beispiel auch auf Linux oder MacOSX.

2. Die 3D-Engine sollte frei verfügbar und gratis benutzbar sein. Am besten wäre eine Engine dieunter einer Open-Source-Lizenz veröffentlicht wurde, die dieses Kriterium langfristig garantiert.

3. Die 3D-Engine sollte ein gutes objektorientiertes Design vorweisen und ein “sauberes” Frame-work zur Verfügung stellen.

4. Die Engine sollte aktuelle Techniken des Echtzeit-Rendering unterstützen.

5. Die 3D-Engine sollte auch ausreichend dokumentiert sein.

Die oben genannten Kriterien ergeben nun einige Konsequenzen, die ich kurz aufgreifen möchte:

• Punkt eins und drei verlangen, dass die Engine in einer objektorientierten Sprache geschrieben ist.(Falls möglich in Java, aber dazu weiter unten noch mehr.) Engines die noch in C, oder anderenprozeduralen Sprachen geschrieben sind fallen weg.

• Punkt zwei schliesst von vorne Herein viele im Spielesektor bekannte, aber auch sehr teure (dieLizenzkosten belaufen sich zum Teil auf mehrere hunderttausend Dollar) Engines aus wie dieDoom3-Engine von id-Software [29], die Source-Engine von Valve [58], die CryEngine [16] vonCrytek, die Unreal3-Engine [57] von Epic, etc...

• Punkt vier erfordert schlussendlich eine moderne, hardwarebeschleunigte Engine. Das bedeutet,dass die Engine entweder Direct3D [20] oder OpenGL [47] unterstützen muss, wobei Direct3Dals Windows-only gleich wieder von der Liste verschwindet. Als Konsequenz davon müssenSoftware-Renderer gleich wieder von der Liste verschwinden. Der in Java-Kreisen bekanntesteVertreter eines Softwarerenderers dürfte dabei Java3D [33] von Sun sein. Als Randnotiz mussman aber beachten, dass seit der kürzlichen Open-Source Veröffentlichung von Sun auch voneiner 3D-Beschleunigung durch OpenGL diskutiert wird.

• Punkt fünf filtert schon viele Engines aus, die erst in den Kinderschuhen stecken oder gar nierichtig aus der Konzeptphase herausgekommen sind.

Mit Hilfe dieser Kriterien wurde nun eine Internetrecherche durchgeführt und die ersten Engines zurnäheren Betrachtung ausgewählt. Eine grosse Hilfe war dabei die 3D Engines Database [1]. Die Daten-bank ist sehr umfangreich und man kann die Engines sogar anhand von bestimmtem Kriterien sortierenund filtern.

Java gegen C++

In der Spieleindustrie ist immer noch C++, die am weitesten verbreitete Sprache. Das hat zur Folge,dass auch die meisten 3D-Engines in C++ geschrieben sind. Es gibt viele Gründe dafür. Die am wei-testen verbreiteten 3D-APIs1, OpenGL und Direct3D, sind selbst C-APIs. Wie oben bereits erwähnt istOpenGL-Unterstützung eine Bedingung für die 3D-Engine. Als Java Programmierer können wir nunzwei Wege gehen. Einerseits gibt es OpenGL-Wrapper für Java, wie JOGL von Sun [37] oder die Light-weight Java Game Library LWJGL [40]. Andererseits bieten viele C++-Engines eine Java Unterstützung,die auf dem Java Native Interface (JNI) basiert.

1API = Aplication Programming Interface. Eine API stellt ein Interface zur Verfügung, dass man als Programmierer benutzenkann.

1.2. Projektablauf 5

C++-Engines

Als erstes habe ich einige C++-Engines untersucht. Diese sind:

• Irrlicht Engine [30]

• OGRE Engine [46]

• Crystal Space Engine [17]

Diese Engines sind alle in C++ geschrieben. Sie sind alle Open Source und haben eine beeindruckendeFeature Liste. Mit der OGRE Engine wurde sogar ein kommerzielles Spiel programmiert. Leider habenaber auch alle drei Engines eine entscheidenden Nachteil. Es existiert zwar bei allen eine Java Unterstüt-zung, der Wrapper ist aber bei allen drei Engines sehr vernachlässigt worden und sogar die Entwicklerselbst raten davon ab ihn zu benutzen. Das heisst nun konsequenterweise, das alle diese Engines von derListe gestrichen werden.

Java-Engines

Glücklicherweise gibt es aber auch noch einige Engines, die direkt mit Java geschrieben wurden. Leiderführt Java im Spielentwicklungsbereich immer noch ein Mauerblümchendasein. Einerseits ist das immernoch auf das alte und eigentlich ausgemerzte Performanceproblem zurückzuführen, das sich in einigenKreisen aber immer noch sehr hartnäckig weiter vertreten wird. Ehrlicherweise muss man aber auchgestehen, dass sich Java in Sachen Grafikperformance bis vor kurzer Zeit nicht gerade mit Ruhm bekle-ckert hat. Obwohl Sun das Problem zuerst dementiert und relativiert hat, arbeiten sie aber in letzter Zeitdirekt an der bereits angesprochenen Unterstützung von OpenGL. Es gibt auch einige Bücher die sichdirekt mit der Spiele- und Grafikprogrammierung unter Java beschäftigen wie [BBV03] und [Dav05].Ich habe drei Java 3D-Engines in die nähere Betrachtung gezogen:

• Aviatrix3D [9]

• Espresso3D [24]

• jME jMonkey Engine [36]

Leider muss man bei Aviatrix3D einige Abstriche machen, da die Dokumentation einiges zu wünschenübrig lässt. Die Installation ist ausserdem sehr aufwändig. Espresso3D ist eine Engine, die noch nichtlange in Entwicklung ist. Es gibt noch nicht sehr viele Features. Sehr überzeugt hat mich hingegenjME. Die Website von jME [36] enthält schon eine ziemlich umfangreiche Dokumentation und auch dieDemos sehen sehr gut aus.

Entscheidung

Nach den Tests habe ich mich schlussendlich für jME entschieden. Sie hat alle Kriterien, die ich amAnfang aufgestellt habe erfüllt.

• jME ist komplett in Java geschrieben.

• jME steht unter der BSD-Lizenz [13]. Diese Open-Source Lizenz ist sehr liberal und erlaubt sogarden kommerziellen Einsatz. Die Engine ist also frei für jedermann und kann von allen herunter-geladen, benutzt und sogar verändert werden.

• Die Engine stellt ein gutes Framework zur Verfügung und ist leicht zu benutzen.

• jME verwendet OpenGL [47] mit LWJGL [40]. Die Engine unterstützt also hardwarebasiertesEchtzeit-Rendering.

1.2. Projektablauf 6

Abbildung 1.1.: Screenshot aus dem finalen Projekt des Gameversity Kurses

• jME hat bereits jetzt eine umfangreiche Dokumentation, was für ein Open Source Projekt dochsehr positiv bemerkbar ist.

In diesem ersten Schritt habe ich also jME ausgewählt. Die weiteren Schritte basieren natürlich aufdieser Entscheidung.

1.2.2. 2. Schritt: Machbarkeitstest

Nachdem nun eine 3D-Engine ausgewählt worden ist, hat sich die Frage gestellt, was man damit anfan-gen soll. Nach einigen Besprechungen mit den verantwortlichen Lehrpersonen wurde beschlossen einTutorial zum Thema 3D-Grafik zu schreiben, das auch Anfänger mit Programmierkenntnissen verstehensollten.Ich musste selbst noch meine 3D-Kenntnisse erweitern und habe dazu online Kurse besucht bei Game-versity [27] und Game Institute [26]. Konkret habe ich bei Game Institute den Kurs Graphics Program-ming with DirectX 9 - Module I und bei Gameversity den Kurs DirectX Graphic Programming besucht,einige Scripts aus diesen Kursen können Sie auf der CD (siehe Anhang A) im Verzeichnis dateien be-gutachten. Die Kurse werden sogar in einen amerikanischen Universitäten anerkannt. Das erfolgreicheBestehen dieser Kurse, ist ungefähr äquivalent mit 10 ECTS Punkten hier in der Schweiz. Diese Kursesind wirklich sehr empfehlenswert und jeder, der sich noch tiefer und grundlegender mit der Materiein dieser Arbeit beschäftigen will, kann ich diese Kurse sehr ans Herz legen. Diese Erfahrung hat mitgezeigt, dass man sehr wohl in Online basierten Kursen hervorragendes Wissen vermitteln kann.Lange Rede kurzer Sinn. Beim Gameversity Kurs, erlernte man das Anwenden und die Implementationeiner 3D-Engine und musste als letzte Aufgabe eine kleine Demo mit einer Insel, Wasser und nocheinigem mehr machen. Eine Abbildung dieser Demo sehen Sie auf Screenshot 1.1. Die ganze Demofinden Sie auch auf der CD zum Projekt, sehen Sie sich dazu Anhang A an. Da die Demo Direct3Dbenutzt, läuft sie nur unter Windows-Betriebssystemen. In dieser Bachelorarbeit geht es hauptsächlichum das Anwenden der 3D-Engine jME, ich habe mir also gedacht, das man die gleiche Demo in jMEimplementieren könnte.

1.3. Gliederung der Dokumentation 7

Gesagt, getan. In einer Coding-Session von gut einer Woche wurde eine ähnliche Demo in jME pro-grammiert. Es ist also durchaus möglich eine Applikation, die in C++ geschrieben wurde und Direct3Dbenutzt in Java zu schreiben und eine plattformübergreifende Engine zu benutzen. Der 2. Schritt istdamit abgeschlossen.

1.2.3. 3. Schritt: Tutorial

Nachdem nun die Engine ausgwählt und ein Endziel festgelelgt wurde, ging es im dritten und letztenSchritt darum die einzelnen Zwischenschritte zu erstellen, jeweils mit Bespielapplikationen, im Verlaufdes Dokuments Demos gennant, und dem jeweiligen Begleittext. Obwohl die Arbeit klar strukturiertund das Resultat klar umschrieben werden konnte, handelte es sich bei diesem Teil, dennoch um denmit Abstand zeitaufwändigsten und arbeitsintensivten Schritt. Einige fast unüberwindbar scheinendeHürden, mussten umschifft werden, bis wir zu diesem Endresultat gelangten. Ich hoffe Sie als Leserinnenund Leser können mit dieser Arbeit nun etwas sinnvolles anfangen.

1.3. Gliederung der Dokumentation

Dieses Dokument ist in zwei Hauptteile gegliedert:

1. Im ersten Teil befindet sich eine kurze allegemeine Einführung in die 3D-Computergrafik. DieseArbeit beschäftigt sich mit einem Unterteil dieses Themas. Desweiteren gibt es einen Überblicküber das Bacholorprojekt im allgemeinen.

2. Der zweite Teil dieses Dokumentes beschäftigt sich mit der 3D-Engine jME[36]. In jedem Ka-pitel wird im Tutorialstil Schritt für Schritt ein weiters Element der Engine jME und der 3D-Grafikprogrammierung beschrieben, bis wir am Schluss in der Lage sind die Demo in Kapitel 10nach zu vollziehen.

Am Ende des Dokumentes befinden sich noch einige Anhänge. Sie zeigen unter anderem den Inhaltder mitgelieferten CD auf (siehe Anhang A) und beschreiben wie Sie die jeweiligen Demos starten,bearbeiten und neu kompilieren können (siehe Anhang B).

1.4. Konventionen und Notationen

• Dateiname: wird benutzt um Dateinamen, Dateierweiterungen und Pfade anzugeben.

• Wichtig wird benutzt um wichtige Namen hervorzuheben.

• Variable wird benutzt um Klassennamen und Variablennamen und weiter Quellcodeextrakte imText anzuzeigen.

• Längere Quellcodeabschnitte und Listings werden folgendermassen angezeigt:

System.out.println( "Hallo schöne jME-Welt!" );

• Abbildungen und Listings sind innerhalb eines Kapitels nummeriert.

• Referenzen zu einem Objekt innerhalb des Literaturverzeichnisses sieht so aus: [AMH02]. Daskomplette Literaturverzeichnis befindet sich am Ende dieses Dokumentes.

• Referenzen auf Webseiten sehen folgendermassen aus: [36]. Das gesamte Verzeichnis mit denWeb Ressourcen befindet sich ebenfalls am Ende dieser Bachelorarbeit.

• Bei den einzelnen Klassendiagrammen in diesem Dokument handelt es sich um herkömmlicheUML-Klassendiagramme. Mehr zu UML können Sie im Buch UML Distilled[Fow04] lesen.

23D-Gra�k

2.1. Was ist 3D-Grafik? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82.2. Was bedeutet Echtzeit-3D-Grafik? . . . . . . . . . . . . . . . . . . . . . . . . . . . 102.3. Was ist eine 3D-Engine? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132.4. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3D-Grafik oder genauer gesagt 3D-Computergrafik ist ein sehr breites Thema und es kann leicht sein,dass man sich zu schnell von den Details erschlagen lässt. Deshalb will ich in den folgenden Unter-kapiteln eine kurze allgemeine Einführung in das Thema bereiten. Die Einführung wird sehr kurz seinund sich nur auf das Wesentliche konzentrieren. Aber ich hoffe dennoch, dass Sie damit einen kurzenÜberblick über das Thema gewinnen. Ich hoffe vor allem Leute, die sich noch nie mit dieser Thema-tik beschäftigt haben, werden einiges dazulernen, aber auch alle andern sollte dieser Überblick und diedazugehörige Abgrenzung der Themen helfen.

2.1. Was ist 3D-Grafik?

Was ist 3D-Grafik? Das ist vielleicht die grundlegendste Frage, die man im Zusammenhang mit 3D-Grafik überhaupt stellen kann. Und die Beantwortung ist gar nicht so einfach. Grundsätzlich gibt eszwei Teilbereiche in der 3D-Grafik: das Modellieren und das Rendern von räumlichen Daten.Mit der Modellierung bezeichnet man das Erstellen von räumlichen Daten, so genannten 3D-Objekten.Diese 3D-Objekte werden weitgehend von Hand mit spezialisierter Software erstellt. Diese Softwarewird 3D-Modelliersoftware oder auch nur “3D-Modeller” genannt. Einige der bekanntesten unter ihnensind “3D-Studio Max” von Autodesk [3] , Maya von Alias [5] und SOFTIMAGE|XSI von Softima-ge [54] . Die meisten dieser Applikationen sind sehr teuer, zum Teil mehrere zehntausend Franken1.Glücklicherweise gibt es auch einige freie Open-Source Alternativen wie Blender [12] (siehe auch Ab-bildung 2.1) oder Wings3D [62] . Abschliessend bleiben noch einige günstige Shareware Programme zuerwähnen, die auch ihre Daseinsberechtigung haben, zum Beispiel Milkshape 3D [43] oder AC3D [4] .Die Modellierung von 3D-Objekten ist sehr aufwändig, braucht viel Zeit und Geduld und auch einigeskünstlerisches Geschick, wie ich aus eigener missglückter Erfahrung berichten kann. Wir werden imweiteren Verlauf nicht mehr auf das Modellieren eingehen, wer sich dafür interessiert findet unzähligeTutorials und WebSites auf dem Internet dazu, wie zum Beispiel auf 3D Links [2] und den Seiten dereinzelnen oben genannten Programme. Die einzelnen 3D-Objekte werden dann unter Umständen nochzusammengefasst zu grösseren Szenen. Man kann sich zum Beispiel eine Stadtszene vorstellen, in der

1Es gibt aber meistens auch eine gratis Learning- bzw. Personal-Edition für Heimanwender und Studenten, die im Funktions-umfang eingeschränkt ist. Beispiele dafür sind “gmax” von den 3D Studio Max Entwicklern Autodesk [28] und die “MayaPersonal Learning Edition” von Alias [42].

8

2.1. Was ist 3D-Grafik? 9

Abbildung 2.1.: Ein Screenshot von Blender

2.2. Was bedeutet Echtzeit-3D-Grafik? 10

Abbildung 2.2.: Ein Ausschnitt aus “Geri’s Game”

wir einige Autos sehen, im Hintergrund hat es Geschäfte und Hochhäuser, das sind alles einzelne 3D-Objekte die in einer Szene zusammen kommen.Der zweite wichtige Teilbereich in der 3D-Grafik ist das Rendern. Unter dem Rendern verstehen, wirdas Erzeugen eines zweidimensionalen Bildes aus den räumlichen 3D-Daten. Es geht also darum nun dieerzeugten 3D-Objekte auf dem Bildschirm anzuzeigen. Man kann es vergleichen mit dem Fotografierenoder dem Filmen einer Szene, wir betrachten also unsere 3D-Szene aus einer virtuellen Kamera. Dazugibt es auch viele verschiedene Methoden und Techniken. In diesem Tutorial beschäftigen wir uns mitdem Echtzeit-Rendern. Wie in der Einleitung erwähnt, wollen wir ja Bilder mit Hilfe von Java in Echtzeitauf den Bildschirm bringen.Auf der Abbildung 2.2 können wir einen Ausschnitt aus dem Pixar Kurzfilm Geri’s Game [51] sehen.Links ist das Drahtmodell (engl. wireframe) zu sehen, das in einem 3D-Modeller modelliert wurde.Rechts ist eine fertig gerenderte Szene mit dem gleichen Kopf aus dem Film zu sehen.

2.2. Was bedeutet Echtzeit-3D-Grafik?

Wie der Titel schon andeutet, behandeln wir hier Echtzeit-3D-Grafik. Unter Echtzeit verstehen wir, dasswir eine 3D-Szene in Echtzeit rendern wollen.Viele von ihnen kennen vielleicht die bekannten Animationsfilme von Pixar [50] oder Dreamworks Ani-mation [21], wie “Shrek”, “The Incredibles”, “Findet Nemo” oder den neusten Titel “Madagascar”. Undobwohl diese Filme auch mit Hilfe von 3D-Grafik erstellt wurden (und keineswegs, wie manche viel-leicht meinen mit klassischen Trickfilmzeichnungen), handelt es sich eben gerade nicht um Echtzeit3D-Grafik. In solchen Filmen wird jeder einzelne Frame2 vorher gerendert und später zu einem Film zu-sammengefügt. Dieses Rendering einer einzelnen Szene kann aufgrund der verwendeten Effekte, Licht-einstellungen und anderen speziellen Einstellungen mitunter Stunden dauern. Das Ziel ist es das Bildmöglichst realistisch erscheinen zu lassen, natürlich in einem gewissen Rahmen, der für die Computernoch machbar ist. Ein Oberbegriff für diese Art des Renderns ist Raytracing. Beim Raytracing werdenLichtstrahlen simuliert und die Lichtverteilung errechnet um möglichst realistische Schatten und Farb-schattierungen zu erhalten. Die Methoden sind dabei auch für heutige Rechner sehr zeitaufwändig undlassen sich noch nicht in Echtzeit ausführen.

2Ein Frame ist ein einzelnes Bild aus einem Trickfilm oder eben einer 3D-Animation.

2.2. Was bedeutet Echtzeit-3D-Grafik? 11

Abbildung 2.3.: Ausschnitt aus dem Film Madagascar

Wenn wir von Echtzeit sprechen, meinen wir nun, dass eben genau diese Rendering nur Bruchteile vonSekunden dauern darf, damit wir uns auch frei in einer 3D-Welt umher bewegen können. Diese Echtzeit-Rendering wird in 3D-Simulationen und auch 3D-Spielen verwendet. Um aber das ganze in Echtzeit zubekommen, muss man auch einige Kompromisse eingehen. Die erzeugten Echtzeit-3D-Welten sind inder Regel nicht so realistisch, wie die vorgerenderten 3D-Welten. Das primäre Ziel ist die erzielbareGeschwindigkeit, mit der ein Frame gerendert werden kann, möglichst zu minimieren. Die Echtzeit-3D-Grafik wird aber immer realistischer. Das hängt auch mit der Einführung der so genannten 3D-Kartenzusammen, die man mittlerweile als Standardausrüstung, sogar auf Mittelklasse PCs zählen kann. DerMarkt für 3D Karten war in den letzten Jahren sehr dynamisch, im Moment gibt es aber nur zwei Firmen,die Karten für den End-User Markt herstellen: ATI [8] und NVidia [44]

Auf der Abbildung 2.4 können Sie die Entwicklung der Echtzeit-3D-Grafik in Spielen kurz, aber keines-falls wirklich repräsentativ, mitverfolgen. Auf dem Teilbild oben links sehen Sie einen Ausschnitt ausdem Spiel Battlezone [10] aus dem Jahre 1980. Das ist das erste Spiel, das eine Art pseudo 3D-Grafikenthielt. Oben rechts ist ein Ausschnitt aus dem Spiel Wolfenstein3D [63] der Firma id Software [29]zu sehen. Das ist eines der ersten 3D-Spiele für den PC und auch der erste “First-Person-Shooter” undsomit auch der Vater aller anderen Spiele dieser Art. Es wurde 1992 veröffentlicht. Aus dem Jahr 1999stammt der dritte Screenshot unten links. Es handelt sich um das Spiel Quake 3 [53], wiederum von idSoftware. Man kann hier schon gut die ersten eingesetzten Lichteffekte erkennen. Der letzte Screenshotist von Far Cry [25] . Ein Spiel das 2004 herausgeben wurde. Auf diesem Bild kann man einige inter-essante Wassereffekte erkennen. Auch die Darstellung der Bäume und Pflanzen ist bemerkenswert undder Detailreichtum der Umwelt.

Dieses Echtzeit-Rendering war zuerst nur auf spezieller Hardware möglich, die vor allem militärischen3D-Simulationen dienten. Im Jahre 1996 hat aber die Firma 3dfx [61] eine erste 3D-Beschleuniger Kar-te in bezahlbaren Regionen für den Endkundenmarkt hergestellt. Erst mit Hilfe dieser Karten ist daseinigermassen realistische 3D-Echtzeit Rendern auf normalen Computern möglich geworden. Die Ent-

2.2. Was bedeutet Echtzeit-3D-Grafik? 12

Abbildung 2.4.: Zeitlinie Echtzeit-3D-Spiele

oben links Battlezone [10] , 1980oben rechts Wolfenstein 3D [63] , 1992unten links Quake 3 [53] , 1999unten rechts Far Cry [25] , 2004

2.3. Was ist eine 3D-Engine? 13

wicklung der 3D-Grafik-Karten verläuft immer noch rasant. Sie übertrifft sogar das Moorsche Gesetz3.Die Geschwindigkeit der neuesten 3D-Grafikkarten verdoppelt sich fast alle sechs Monate und ein Endeist nicht abzusehen. Und das obwohl sich, nach einigen Konkursen und Übernahmen nur zwei grossePlayer, ATI [8] und NVidia [44], auf dem Endkundenmarkt tummeln. Es wird also nicht mehr langedauern, bis wir auch 3D-Echzeitgrafik in der gleichen Qualität, wie die aktuellen Pixar und DreamworksAnimation Filme geniessen können.Was es mit diesen 3D-Grafik-Karten auf sich hat und wie sie intern arbeiten, werden wir ein bisschengenauer im Kapitel 4 unter die Lupe nehmen.

2.3. Was ist eine 3D-Engine?

Wir wollen mit Hilfe der 3D-Engine jME [36] eine 3D-Welt aufbauen und schlussendlich auf den Bild-schirm bringen. Aber was genau ist eine 3D-Engine? Eine 3D-Engine verwaltet die ganzen 3D-Objekteund die Szenen, die ein 3D-Künstler oder wir selbst vorher angefertigt haben. Sie verwalten also kurz ge-sagt die ganzen 3D-Daten. Des weiteren ist eine 3D-Engine dafür zuständig, was wir auf den Bildschirmbringen und wie wir das ganze Zeichnen wollen. Diese Entscheidungen werden auf einer unteren undeiner oberen Ebene gemacht. Die Entscheidung auf der oberen Ebene werden von unserem Spiel oderunserer Anwendung mit Hilfe eines Scenegraphs auf Softwareebene gemacht, das ganze wird im Ab-schnitt 3.2 erläutert. Die Entscheidungen der unteren Ebene werden vom Renderer selbst gemacht. Dasbedeutet das wir die meiste Arbeit an die Hardware oder genauer gesagt an die Grafik-Karte delegierenkönnen. Wie das genau funktioniert wird im Kapitel 4 näher erklärt.

2.4. Zusammenfassung

Ich hoffe, dass Sie als Leser nun ein bisschen mehr Ahnung von der 3D-Grafik haben. Was Sie behal-ten sollten, ist das wir uns hier im Tutorial auf das Rendern mit der Java 3D-Engine jME beschränkenwerden. Wir werden uns genauer gesagt sogar auf das interaktive Echtzeit-Rendern beschränken. Wiedas abläuft, werden wir im Tutorial sehen. Im Tutorial werden wir uns auf die Handhabung von jME ausSicht eines Endbenutzers beschränken und nur wo nötig auf implementationstechnische Details einge-hen.

3Als Mooresches Gesetz wird die Beobachtung bezeichnet, dass sich durch den technischen Fortschritt die Komplexität vonintegrierten Schaltkreisen etwa alle 24 Monate verdoppelt.

Teil II.

Tutorial

14

3Erste Schritte mit jME

3.1. Unser erstes jME-Programm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153.2. Scenegraph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

3.2.1. Was ist ein Scenegraph? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

3.2.2. Zustände im Kontext von jME . . . . . . . . . . . . . . . . . . . . . . . . . . 21

3.1. Unser erstes jME-Programm

Jetzt geht es los liebe Leser! Statt lange um den heissen Brei zu reden, tauchen wir gleich ein in un-ser erstes jME-Programm. Wie jedes anständige Programmiertutorial beginnt auch dieses Tutorial miteinem HelloWorld Programm. Zu sehen ist aber nicht ein kurzer Textstring sondern ein einfacher Wür-fel. Sie finden den Quellcode und die Klasse selbst im Verzeichnis demos/firststeps mit dem NamenHelloWorld.java. Wie die CD genau aufgebaut ist, wird in Anhang A erklärt. Im Unterverzeichnis de-mos der CD finden Sie ausserdem zwei Dateien mit dem Namen firststeps_helloworld.bat und first-steps_helloworld.sh. Es handelt sich dabei um Startdateien, mit denen Sie die Demo unter Windowsbzw. unter Linux und MacOSX direkt starten können. Wie sie die Demo direkt in der Konsole startenkönnen, wird im Anhang B erläutert.

Listing 3.1: HelloWorld.java1 package firststeps;2

3 import helper.TutorialGame;4

5 import com.jme.math.Vector3f;6 import com.jme.scene.shape.Box;7

8 public class HelloWorld extends TutorialGame9 {

10 public static void main( String args[] )11 {12 new HelloWorld();13 }14

15 protected void simpleInitGame()16 {17 // set the title of our application18 display.setTitle( "Hello World!!!" );19

15

3.1. Unser erstes jME-Programm 16

Abbildung 3.1.: Screenshot aus HelloWorld

20 // create a simple cube21 Box b = new Box( "box", new Vector3f( 0, 0, 0 ), new Vector3f( 1, 1, 1

) );22

23 // put the box in the scene graph24 rootNode.attachChild( b );25 }26 }

Dieses einfache Programm beinhaltet bereits alles was auch ein grösseres jME-Programm beinhaltenmuss. Wir erben von einer Klasse namens TutorialGame. In TutorialGame werden verschiedene Din-ge aufgesetzt, die in jedem unseres jME-Programme nützlich sind:

• Eine einfache Kamera wird erzeugt, die wir mittels der in Spielen üblichen Manier bewegen kön-nen. Das heisst konkret: mit der Maus verändern wir unsere Blickrichtung, während wir uns mitder ’W’- und ’S’-Taste vorwärts und rückwärts bewegen. Mit ’A’ und ’D’ bewegen wir und linksbzw. rechts seitwärts. Das wird auch für die meisten anderen, kommenden Demos, die Standard-methode sein, mit der wir uns fortbewegen können. Wer bereits einen modernen 3D-Shooter ge-spielt hat, wird sich gleich zu Hause fühlen.

• Ein simples Licht wird aufgesetzt. In Kapitel 7 werden wir genauer darauf eingehen.

• Ein Scenegraph wird eingerichtet. Was ein Scenegraph genau ist werden wir am Ende diesesKapitels genauer erklären.

• Einige simple Tastenkommandos werden definiert wie zum Beispiel:

– ’T’ und den Wireframe-Modus ein- bzw. auszuschalten

– ’C’ um die Position der Kamera in der Konsole auszugeben

– ’L’ um die Lichter zu deaktivieren bzw. wieder zu aktivieren

3.1. Unser erstes jME-Programm 17

Abbildung 3.2.: Screenshot des Einstellungsdialogs der bei jedem Start erscheint

– ESCAPE um das Programm zu beenden

• TutorialGame stellt auch zwei Methoden namens simpleInitGame und simpleUpdate zur Ver-fügung, die wir in unseren eigenen Programmen überschreiben können, wenn wir etwas initiali-sieren wollen oder etwas in jedem Frame ändern wollen.

Für uns ist es nicht so wichtig zu wissen, wie TutorialGame genau seine Arbeit macht, wichtig zuwissen ist aber, das jedes unserer Programme von TutorialGame erben wird.Beschreiben wir nun unser erstes Programm ein bisschen genauer. Die Dialogbox mit dem Uni-FribourgLogo, die bei jedem Start gezeigt wird, ist sicher auch schon einigen aufgefallen, auf der Abbildung 3.2können Sie einen Screenshot davon sehen. Der Konstruktor von TutorialGame, ist dafür verantwortlich,dass dieser Dialog immer erscheint. In diesem Dialog kann man die Bildschirmauflösung des folgendenProgramms auswählen und bestimmen, ob die Applikation in einem Fenster oder als Vollbildanwendungausgeführt wird. Es ist also durchaus sinnvoll diesen Dialog vor jedem Start anzuzeigen.In Zeile 12 beginnt unser Programm dann richtig. Mit dem Aufrufen des Konstruktors wird auch derKonstruktor von TutorialGame aufgerufen, der wiederum eine Endlosschleife ausführt, die erst been-det wird, wenn wir auf ’Fenster schliessen’-Kreuz drücken oder die Taste Escape betätigen. Bevor dieSchleife startet wird das System initialisiert und unter anderem auch simpleInitGame aufgerufen. Inder Schleife selbst werden danach in jedem Iterationsschritt zwei Dinge gemacht: zunächst erhält jedesObjekt, denn Befehl sich zu bewegen und simpleUpdate wird aufgerufen, als zweites wird alles aufden Bildschirm gerendert.Unser main wird in jeder Demo gleich aussehen, die eigentliche Arbeit werden wir immer in den Metho-densimpleInitGame und zum Teil auch in simpleUpdate machen. Besprechen wir also im folgendenAbschnitt simpleInitGame etwas genauer:

Listing 3.2: HelloWorld.java, simpleInitGame15 protected void simpleInitGame()16 {17 // set the title of our application

3.2. Scenegraph 18

18 display.setTitle( "Hello World!!!" );19

20 // create a simple cube21 Box b = new Box( "box", new Vector3f( 0, 0, 0 ), new Vector3f( 1, 1, 1

) );22

23 // put the box in the scene graph24 rootNode.attachChild( b );25 }

Schon beim betrachten des Codes sehen wir, das genau drei Dinge passieren:

1. Wir geben unserer Demo einen Titel. Der Titel wird auf dem Fenster angezeigt und wird auch derName sein, mit dem das jeweilige Betriebssystem unsere Applikation kennt.

2. Als nächstes erstellen wir einen Würfel mit Box. Der Würfel ist, dieses Ding, das man auf demBildschirm sehen konnte.

3. Wir fügen unseren neu erstellten Würfel der Wurzel unseres Scenegraphen an. Die Wurzel desScenegraphen heisst rootNode und wird uns von TutorialGame zur Verfügung gestellt.

Wie wir oben sehen können, benutzen wir drei Argumente um einen Würfel zu kreieren. Als erstesgeben wir dem Objekt mittels eines String Objektes einen Namen. Allen Objekte, die wir irgend einmaleinem Scenegraph anhängen wollen müssen wir einen Namen geben. Dieser Würfel wurde in diesemBeispiel box genannt, wir hätten aber auch irgend einen anderen Namen wählen können. Die Nächstenzwei Argumente geben zwei Ecken unseres Würfels an. Der Würfel hat eine Ecke im Ursprung (0; 0; 0)und eine Ecke im Punkt (1; 1; 1) es handelt sich also um einen Einheitswürfel.Jetzt haben wir also einen Würfel erstellt, wir wollen diesen Würfel aber auch sehen. Deshalb müssenwir den Würfel mittels rootNode.attachChild unserem Scenegraph anhängen. Alle Objekte, die ge-rendert werden sollen, müssen wir dem Scenegraphen anhängen. rootNode ist dabei die Wurzel desScenegraph, der in TutorialGame definiert wurde. In diesem Beispiel besteht unser ganzer Scenegraphaus der Wurzel rootNode mit dem angehängten Würfel box. Wenn nun von TutorialGame der Befehlkommt rootNode zu zeichnen wird automatisch auch der Würfel mitgerendert.

3.2. Scenegraph

3.2.1. Was ist ein Scenegraph?

Kommen wir zurück zu der grundlegenden Frage, was eine 3D-Engine eigentlich macht. Wir haben be-reits gesagt, dass es die Hauptaufgabe einer 3D-Engine ist verschiedene 3D-Objekte zu verwalten undauf den Bildschirm zu bringen. Die einfachste Möglichkeit diese Objekte zu verwalten, wäre eine ver-kette Liste mit allen Objekte, die wir dann eines nach dem anderen auf den Bildschirm zeichnen. Das isteine einfache aber leider nicht sehr effiziente Methode Objekte zu verwalten. Wenn man sich näher miteinem modernen 3D-Spiel beschäftigt, erkennt man, dass die 3D-Welten aus tausenden von 3D-Objektenbestehen. Diese tausenden von Objekte in einer Liste zu speichern und nacheinander zu verarbeiten wür-de viele Spiele wohl zu einer eher langweiligen Dia-Show ausarten lassen. Denn selbst Objekte die nichtauf dem Bildschirm erscheinen würde man in diesem Falle einer zeitraubenden Bearbeitung unterziehenmüssen und wertvolle Ressourcen rauben. Die 3D-Hardware weiss noch nicht einmal, dass viele Objek-te nicht zu zeichnen sind, und schmeisst solche Objekte erst sehr spät aus der Pipeline, deshalb sprichtman auch davon, dass die 3D-Beschleuniger auf einem sehr tiefen sogenannten low-level arbeiten. AlsProgrammierer haben wir aber sehr wohl eine grössere high-level Ahnung von den 3D-Objekten auf demBildschirm und wie sie miteinander in Beziehung stehen.Die ganze Welt aus den 3D-Objekten wird, wie wir bereits einmal erwähnt haben als Szene (engl. scene)bezeichnet. Wenn wir nun unser Wissen von den Beziehung dieser 3D-Objekten einbringen erhalten

3.2. Scenegraph 19

Abbildung 3.3.: Eine Hierarchie von einem Haus

wir einen Scenegraphen1. Als Scenegraph wird in jME ein Baum verwendet. In einem Baum gibt eseine Wurzel und jedes Element kann mehrere Kindobjekte enthalten, besitzt aber nur ein Elternelement.jME stellt uns mit rootNode bereits die Wurzel eines Scenegraphen zur Verfügung, an den wir weitereBlätter anfügen können. Mit einem Baum können wir als Benutzer einer Engine eine Hierarchie von 3D-Objekten aufbauen. Dieser Ansatz gibt uns viele Vorteile, wie Sie in [Ebe00] auch nachlesen können:

• Stellen wir uns vor wir haben eine 3D-Welt, die aus vielen verschiedenen Räumen besteht. Wennwir nun ein Licht einsetzen, wie wir es in Kapitel 7 zeigen, dann wollen wir das dieses Licht nurdiesen Raum betrifft und beleuchtet auch aus Performancegründen. Mit einem Scenegraph ist daseinfach in dem wir das Licht einfach im Raum einsetzen den wir beleuchten wollen. Das Lichtbeleuchtet dann automatisch auch alle Kindobjekte von diesem Raum.

• Zweitens, in einem Scenegraphen kann man leicht lokale Gruppierung darstellen. Das hilft beson-ders, weil man mit dieser Methode schnell ganze Objektgruppen eliminieren kann, die nicht aufdem Bildschirm zu sehen sind. Nehmen wir als Beispiel an wir befinden uns im Raum 2 wie aufder Abbildung 3.3 zu sehen. Der Renderer kann den ganzen Unterbaum von Raum 1 direkt von der

1Für diesen Begriff scheint es leider keine geläufige deutsche Übersetzung zu geben, deshalb werden wir uns im Verlaufe desDokuments auf den englischen Begriff Scenegraph beschränken.

3.2. Scenegraph 20

Abbildung 3.4.: UML-Diagramm der Scenegraph Elemente

Bearbeitung ausschliessen, weil dieser Raum nicht zu sehen ist. Wenn man Objekte von der Be-arbeitung ausschliesst, die nicht auf dem Bildschirm zu sehen sind spricht man vom sogenanntenFrustum Culling. Das ist ein Konzept, das sehr wichtig ist im Echzeitrendern.

• Viele 3D-Objekte die wir darstellen wollen sind schon von Natur aus auf hierarchische Weiseaufgebaut. Das ist ein dritter Vorteil, den wir mit Scenegraphen haben. Das gilt besonders fürhumanoide Objekte. Die Lage und die Rotation von einer Hand hängt auf natürliche Weise ab vonder Lage und der Rotation des Ellbogens, der Schulter und der Hüfte. Mit einem Scenegraphenist es leicht solche Abhängigkeiten darzustellen. Wenn wir das ganze von Hand machten müssten,würde es sehr schnell kompliziert werden, wie Sie im Abschnitt 4.2.3 selbst sehen können.

Kommen wir nun zum Scenegraphen zurück den jME für uns bereitstellt. In jME gibt es Objekte vondrei Klassen, die Elemente eines Scenegraphen sein können, die Klassen Spatial, Geometry und Node.Wie auf der Abbildung 3.4 zu sehen ist, ist die Klasse Spatial die Oberklasse. Man kann Spatial nichtinstanzieren, da es sich um eine abstrakte Klasse handelt. In Spatial werden aber die Lage und dieRotation gespeichert, jedes Element des Scenegraphen hat also seine eigenen Standort, der immer relativzum Elternelement ist. Man kann auch sogenannten RenderStates setzen, der die Lichter und Texturenbeschreibt, dazu gibt es im weiteren Verlauf des Tutorials mehr.Die Klasse Geometry und ihre Unterklassen beinhalten all die geometrischen Daten, das heisst die Drei-ecke, die ein 3D-Objekt enthalten. Das heisst jedes Objekt, das wir am Schluss auf dem Bildschirm sehenist ein Geometry-Objekt. Die Box, die wir im ersten Demo benutzt haben (siehe Listing 3.1 und 3.2 Zeile21), ist auch im UML-Diagramm zu erkennen. Bei den Geometry-Objekten handelt es sich aber nur umdie Endblätter unseres Scenegraph-Baumes. Die Knoten werden durch die Klasse Node verwaltet, der

3.2. Scenegraph 21

Abbildung 3.5.: UML Diagramm der RenderStates

man beliebig viele Kindknoten und Kind Geometry-Objekte anfügen kann, dazu können Sie noch einmaldie Abbildung 3.3 betrachten. Für alles das wir in jME auf den Bildschirm sehen existieren also eigeneGeometry-Unterklassen oder spezialisierte Node-Klassen. Solche spezialisieren Klassen sind schwierigauf eine gute Art und Weise zu implementieren und das ist eine der eigentlichen Schwierigkeiten, wennman eine 3D-Engine entwickeln will.Man kann ohne zu übertreiben sagen, dass der Scenegraph das Rückgrat der 3D-Engine von jME ist. DasDesign und die Implementierung von jME ist dabei sehr komplex. Der Autor von jME sagt in seiner In-ternetseite, dass er das Design von jME an die beiden Bücher von David H. Eberly angelehnt hat [Ebe00]und [Ebe04]. In [Ebe00] wird die Implementierung einer Scenegraph basierten Engine beschrieben. DerText ist dabei sehr mathematisch gehalten. In [Ebe04] wird die Architektur dieser weiterentwickeltenScenegraph-Engine aus einem etwas höheren Level beschrieben. Beide Bücher sind über 500 Seitendick, das sollte nur ein kleiner Hinweis sein, wie komplex eine moderne 3D-Engine ist.Der ganze Scenegraph entspricht ausserdem dem Composite Pattern, wie er aus dem allseits bekann-ten Buch der Gang of Four, Design Patterns [GHJV95] bekannt ist. Im Scenegraphen, dessen WurzelrootNode in unserem Programm verwendet wird, wird einmal pro Frame die Methode draw aufgerufen.Dieser Aufruf bringt die ganze Engine zum Laufen.Im Artikel [BZ] von Avi Bar-Zeev finden Sie noch einige weiter Anmerkungen zu einem Scenegraphenallgemein. Die meisten modernen 3D-Engines benutzen eine Scenegraph-Implementierung, die der vonjME ähnlich ist.

3.2.2. Zustände im Kontext von jME

Wir werden im Verlauf des Tutorials auch einige Zuständen verwenden. Vor allem Lichter in Kapitel 7und Texturen in Kapitel 8. Ein Zustand den wir in jME verwenden können ist immer eine Unterklasse vonRenderState. Abbildung zeigt ein UML-Diagramm mit einigen der Zustände. Sehen Sie sich nun nocheinmal die Abbildung mit den Scenegraph-Elementen (Abbildung 3.4) an. Wie Sie auf dieser Abbildungsehen können, können wir zu jedem Element unseres Scenegraphen, das heisst zu jedem Spatial, einenRenderState also einen Zustand setzen, mit der treffenden Methode setRenderState. Der Zustand,den wir setzen, wirkt sich dann im Scenegraph auf alle Kinder, des jeweiligen Spatials aus dessenZustand wir erweitern. Betrachten wir dazu noch einmal die Hierarchie auf Abbildung 3.3. Falls wir imRaum1 ein Licht setzen, beleuchtet das Licht alle Elemente der Tischgruppe und auch den Stuhl, Raum2merkt aber nichts vom Licht.

4Wie funktioniert 3D-Gra�k

4.1. Was macht eine 3D-Engine? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224.2. Das 3D-Koordinatensystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

4.2.1. 2D-Koordinatensystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

4.2.2. 3D-Koordinatensystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

4.2.3. Model-Space vs. World-Space . . . . . . . . . . . . . . . . . . . . . . . . . . 24

4.3. Transformationen im Raum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254.3.1. Verschiebungen (engl. translation) . . . . . . . . . . . . . . . . . . . . . . . . 25

4.3.2. Rotationen (engl. rotation) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

4.3.3. Skalierungen (engl. scaling) . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

4.3.4. Alle Bewegungen zusammen . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

4.4. Perspektive und Projektion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264.5. Kamera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274.6. jME, OpenGL, DirectX, 3D-Pipeline: Was ist das? Was machen die? . . . . . . . . 294.7. Ressourcen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

4.1. Was macht eine 3D-Engine?

Man kann kurz sagen, eine 3D-Engine ist verantwortlich dafür die 3D-Daten zu verwalten und zu bestim-men, was auf den Bildschirm kommt und wie es zu zeichnen ist. Wir haben oben im Kapitel vor allem daswas beschrieben, das wie ist dabei meistens die Aufgabe der sogenannten Rendering-Pipeline. Das Zielder Rendering-Pipeline ist es ein zweidimensionales Bild auf dem Bildschirm zu erzeugen, aus dreidi-mensionalen Objekten, einer virtuellen Kamera, Texturen, Lichtern und noch einigem mehr. Als Benut-zer einer 3D-Engine haben wir es dabei glücklicherweise um einiges leichter. Die 3D-Engine ist meistensein Wrapper um eine gegebene 3D-Schnittstelle, wie OpenGL oder Direct3D und abstrahiert dement-sprechend die so genannte Rendering-Pipeline. Die Rendering-Pipeline selbst ist heutzutage auch direktin der Hardware implementiert, in den sogenannten 3D-Beschleunigern. Das sind 3D-Grafikkarten, diemittlerweile wohl jeder Computer enthält. Wir müssen uns also um die meisten Low-Level Angelegen-heiten nicht mehr selbst kümmern und können ganz einfach die 3D-Engine walten lassen.Dennoch ist es von Vorteil, zumindest ein grundlegendes Wissen zu haben, was in dieser ominösenPipeline passiert. Und genau deshalb werden wir uns in diesem Teil des Tutorials ein wenig um dietheoretischen Grundlagen kümmern. Ich hoffe, dass ich damit ein wenig Licht in das bereits jetzt vonFachwörtern gespickte Tutorial bringen kann. Gezwungenermassen kann ich hier keinen tiefen Einblickin das Thema geben. Ich verweise die interessierten Leser aber auf die kommentierte Bibliographie amEnde dieses Kapitels in Abschnitt 4.7.

22

4.2. Das 3D-Koordinatensystem 23

Abbildung 4.1.: Ein rechthändiges 3D-Koordinatensystem

Trotz allem wird hier ein wenig Vorwissen vorausgesetzt. Der Leser sollte wissen, was ein Vektor undeine Matrix ist. Grundlegende Vektor- und Matrix-Operationen sollten, deshalb auch schon verstandenwerden. Wer noch ein wenig Mühe mit dieser Materie hat oder sein mathematisches Wissen ganz all-gemein ein bisschen auffrischen will findet im Buch von Fletcher und Parberry 3D Math Primer forGraphics and Game Development [DP02] eine hervorragende Einführung.

4.2. Das 3D-Koordinatensystem

4.2.1. 2D-Koordinatensystem

Fast jeder wird wohl schon von einem kartesischen 2D-Koordinatensystem gehört haben und es wohlsogar selbst benutzt haben. Ein Koordinatensystem besteht aus einer oder mehreren Zahlengeraden.Jeder dieser Zahlengeraden heisst Achse. Die Anzahl Achsen in einem System entspricht der AnzahlDimensionen, die in diesem System repräsentiert werden. In einem 2D-Koordinatensystem sind das nor-malerweise die x- und die y-Achse. Diese Achsen entspringen dem Ursprung (engl. origin) des Systems.Dieser Ursprung entspricht dem Punkt (0; 0) in einem 2D-System, deshalb wird der Ursprung häufigauch Nullpunkt genannt.

4.2.2. 3D-Koordinatensystem

Ein 3D-System fügt nun dem 2D-System eine dritte Tiefendimension hinzu. Diese neue Achse wirdnormalerweise als z-Achse bezeichnet. Alle drei Achsen in einem 3D-Koordinatensystem stehen imrechten, 90 Grad, Winkel zueinander.Es gibt zwei Versionen des 3D-Koordinatensystem, die häufig benutzt werden. Das linkshändige System(engl: left-handed system) und das rechtshändige System (engl: right-handed system). Der Unterschiedzwischen den beiden ist die Richtung in welche die z-Achse zeigt. In einem linkshändigen System zeigtdie positive z-Achse gegen vorwärts und negative Zahlen zeigen sozusagen hinten von uns weg. In einem

4.2. Das 3D-Koordinatensystem 24

rechtshändigen System ist das genau umgekehrt. jME benutzt ein rechtshändiges Koordinatensystem,da auch das darunterliegende OpenGL ein rechtshändiges Koordinatensystem benutzt. Das linkshändigeKoordinatensystem wird von der anderen bekannten 3D-API Direct3D benutzt. Auf der Abbildung 4.1sehen Sie ein rechtshändiges Koordinatensystem.

4.2.3. Model-Space vs. World-Space

Im Bereich der 3D-Grafik benutzt man oft verschiedene Koordinatensysteme. Im Besonderen unter-scheidet man zwischen dem sogenannten World Space, dem Model- oder Object Space und dem Ca-mera Space. Man benötigt verschiedene Koordinatensysteme, weil einige Informationen nur in einembestimmten Kontext (das bedeutet in unserem Fall in einem bestimmtem Koordinatensystem) von Nut-zen sind. Das ganze Konzept ist am Anfang vielleicht ein bisschen schwierig zu verstehen aber dieKonzepte sind grundlegend.

Am besten wir beginnen mit dem World Space. Das World Space ist ein absolutes Koordinatensystem.Jede Position auf der Welt hat seine eigenen Koordinaten, die unverwechselbar sind. Am besten kannman sich das mit einer normalen Weltkarte verbildlichen. Auf einer Weltkarte ist die Welt in Längen- undBreitengrade aufgeteilt. Jeder Ort auf der Welt lässt sich nun eindeutig mit diesen Koordinaten beschrei-ben. Die Koordinaten von Freiburg sind zum Beispiel, 46,8◦ nördliche Breite, 7,15◦ östliche Länge.Auch jede 3D-Welt, besitzt nun ein solches absolutes Koordinatensystem, in dem jeder Ort eindeutigdurch die x-, y-, z-Koordinaten beschrieben werden kann. Um dieses Konzept besser zu illustrieren, sindin den meisten Demos, die dieses Kapitel begleiten die Achsen des World Space zu sehen.

Jedes Objekt in unserer 3D-Welt hat hingegen sein eigenes lokales Koordinatensystem, besitzt seineneigenen Achsen und hat seinen eigenen Nullpunkt. Der Nullpunkt kann beispielsweise in der Mitte desObjekts liegen. Die Achsen zeigen an, welche Richtungen für das Objekt “oben”, “unten”, “rechts”,“links”, “vorne” und “hinten” sind. Das ist genau so in der “echten” Welt. Für mich bedeutet zum Bei-spiel “links” etwas anderes als für jemanden, der vis-à-vis von mir sitzt. Diese lokale Koordinatensys-tem wird Object Space genannt. Ausserdem bewegt sich das lokale Koordinatensystem mit dem Objekt.Wenn sie zum Beispiel den linken Arm ausstrecken, wird dieser linke Arm immer einen Meter links vonihnen sein, egal wie sie sich im Raum umher bewegen.

Wenn sie sich aber bewegen, wird sich ihre Position in der Welt verändern. Die Position, die sie inneha-ben, wenn sie sich umher bewegen wird in Weltkoordinaten ausgedrückt. Natürlich hat auch ihr linkerArm, den sie immer noch ausgestreckt haben eine Position im World Space. Es genügt nun aber, wennwir nur die Position ihres Nullpunktes kennen (nehmen wir an der Nullpunkt von ihnen befindet sichin der Körpermitte). Die Position ihres linken Armes kann man nun leicht ausrechnen, weil man weiss,dass sich ihr linker Arm ein Meter links von ihrem Nullpunkt entfernt befindet.

Weil sich ein 3D-Objekt mitsamt seinem lokalen Koordinatensystem im absoluten globalen Koordina-tensystem bewegt, kann es hilfreich sein, das globale Koordinatensystem (World Space) als Eltern-Spaceund das lokale Koordinatensystem als Kind-Space zu verstehen. Ausserdem ist es sehr nützlich, die 3D-Objekte in weiter Subobjekte zu unterteilen. Der Roboter hat zum Beispiel einen Kopf mit einer Nase,zwei Arme und zwei Beine. Falls der Roboter nun nicken will, bewegt sich sein Kopf mitsamt Naserelativ zum Roboterkörper. Um den ganzen Roboter zu bewegen müssen, wie aber den Kopf und dieNase mit bewegen. Wir erhalten also ein Hierarchie von Objekten, die sich alle relativ zueinander bewe-gen. Genau diese Hierarchie kann man nun mit einem Scenegraph implementieren und das macht es unsleicht in jME1 solche Hierarchien von Objekten zu benutzen.

1Im Gegensatz zur “herkömmlichen” 3D-Programmierung mit OpenGL und Direct3D, in denen man solche Hierarchien undrelative Bewegungen mühsam von Hand selbst verwalten muss. Ausser man implementiert seinen eigenen Scenegraphnatürlich.

4.3. Transformationen im Raum 25

4.3. Transformationen im Raum

Transformationen ist ganz einfach die englische Bezeichnung für Bewegungen im Raum. Was könnenwir alles für Bewegungen machen? Trivialerweise können wir Objekte ganz einfach verschieben. Wirkönnen Objekte auch vergrössern oder verkleinern also skalieren. Zu guter Letzt können wir Objekteauch rotieren. Mittels dieser drei Transformationen können wir fast alle Bewegungen, die ein Objekt imdreidimensionalen Raum macht, nachbilden.Wir müssen nun genau definieren, was wir eigentlich verschieben und da sollte uns nun die obige Dis-kussion mit dem Model Space und dem World Space helfen. Wenn wir ein Objekt bewegen, bewegenwir das Objekt mitsamt seinem relativen Koordinatensystem, dem Model Space, im absoluten Raum,also dem World Space.Normalerweise werden alle diese Transformationen zusammengeführt und in einer einzigen Matrix ge-speichert. Fast alle Bücher, die sich mit der Thematik beschäftigen gehen auch näher darauf ein. jMEmacht uns aber die ganze Aufgabe ein wenig leichter, da für alle Transformationen geeignete Methodenzur Verfügung stehen. Intern wird aber auch jME eine einzige Bewegungsmatrix aus unseren Anweisun-gen erzeugen.

4.3.1. Verschiebungen (engl. translation)

Um ein Objekt im Raum zu verschieben rufen wir folgende Methode auf:

void setLocalTranslation(Vector3f localTranslation);

Beim Parameter localTranslation handelt es sich um den Punkt wohin wir das Objekt im absolutenRaum bewegen möchten.

4.3.2. Rotationen (engl. rotation)

Wir können alle Objekte im Raum um auch rotieren und dazu rufen wir diese Methode auf:

void setLocalRotation(Matrix3f rotation);

Vielleicht werden sich jetzt einige wundern, warum wir das ganze nun mit einer Matrix aufrufen müssen.Wie oben bereits erwähnt kann man jede Bewegung im Raum auch wunderbar platz sparend in einereinzigen Matrix speichern, aber zur Illustration ist das natürlich nicht so hilfreich. Deshalb stellt uns jMEeine Methode zur Verfügung mit der wir auf einfache Weise eine Rotationsmatrix herstellen können:

void fromAngleNormalAxis(float angle , Vector3f axis);

Diese Methode besitzt zwei Parameter. Mit dem ersten Parameter angle bestimmen wir einen Winkelin Radianten gemessen und mit dem zweiten Parameter axis geben wir einen normalisierten Vektor anum den rotiert werden soll.Wenn wir zum Beispiel unser Objekt box um 45◦ um die x-Achse drehen wollen rufen wir folgendenCode auf:

Matrix3f rotMat = new Matrix3f();rotMat.fromAngleNormalAxis( FastMath.DEG_TO_RAD * 45.0f, new Vector3f( 1.0f

, 0.0f, 0.0f));box.setLocalRotation( rotMat );

Das ganze sieht vielleicht ein wenig kompliziert aus, ist aber sehr flexibel. Wir können unser Objekt zumBeispiel um jeden beliebigen Winkel drehen und sind nicht auf die Weltachsen beschränkt.

4.3.3. Skalierungen (engl. scaling)

Falls ein Objekt nicht die gewünschte Grösse hat können wir es auch vergrössern oder verkleinern mitfolgenden Methoden:

4.4. Perspektive und Projektion 26

void setLocalScale(float localScale);void setLocalScale(Vector3f localScale);

Mit der ersten Methode skalieren wir das ganze Objekt um einen bestimmten Faktor localScale.Die zweite Methode skaliert das Objekt auf jeder Achse um einen anderen Wert, den wir mit einemdreidimensionalen Vektor bestimmen.

4.3.4. Alle Bewegungen zusammen

Als abschliessendes Beispiel wollen wir zeigen, wie wir die Grösse eines Würfels verdoppeln, ihn 45◦

um die x-Achse drehen und anschliessend zum Punkt (5.0, 2.0, 3.0) verschieben:

Listing 4.1: SimpleTransformation.java28 protected void simpleInitGame()29 {30 display.setTitle( "Simple Translation" );31

32 // create a unit -cube which is initially located in the world center33 Box box = new Box( "My box", new Vector3f( -1, -1, -1 ), new Vector3f(

1, 1, 1 ) );34 Node n = new Node( "boxholder" );35 Axis.createAxis( n, 5 );36

37 // move our box38 n.setLocalScale( 2.0f );39

40 // rotate the box41 Matrix3f rotMat = new Matrix3f();42 rotMat.fromAngleNormalAxis( FastMath.DEG_TO_RAD * 45.0f,43 new Vector3f( 1.0f, 0.0f, 0.0f) );44 n.setLocalRotation( rotMat );45

46 // scale the box47 n.setLocalTranslation( new Vector3f( 5.0f, 2.0f, 3.0f ) );48

49 // attach all to our rootNode50 n.attachChild( box );51 rootNode.attachChild( n );52 Axis.createAxis( rootNode , 12 );53

54 lightState.detachAll();55 }

Das ganze Beispiel finden Sie auch in Demos unter dem Namen SimpleTransformation.java im Unterord-ner theory in den Demos (siehe auch Anhang A). Man sollte noch anfügen, dass jME alle Verschiebun-gen immer in der gleiche Reihenfolge ausführt. Zuerst wird das Objekt skaliert, dann wird es rotiert undschlussendlich verschoben und zwar ganz egal in welcher Reihenfolge wir die oben genannten Methodenaufrufen. Also bitte denken Sie daran, wenn einmal etwas sich aus ihrer Sicht unerwartet bewegt.Die Transformationen wirken sich zudem nicht nur auf das Objekt selbst aus, sondern auf alle Kindob-jekte, die mit Vaterobjekt im Scenegraph verbunden sind. Das ist normalerweise auch die gewünschteEigenschaft von Scenegraph-Hierarchien.

4.4. Perspektive und Projektion

Es gibt viele verschiedene Methoden wie man die runde Erde auf ein rechteckiges Papier projizierenkann. Und analog dazu gibt es auch viele verschiedene Methoden, wie man eine dreidimensionale Szene

4.5. Kamera 27

Abbildung 4.2.: Ein Würfel rotiert, bewegt und skaliert

auf den rechteckigen Bildschirm projiziert.

Die bekannteste Methode ist die perspektive Projektion (engl. perspective projection). In dieser Projek-tion erscheinen Objekte, die weit von der Kamera entfernt sind kleiner auf dem Bildschirm. Zusätzlichkönnen parallele Linien am Horizont konvergieren. Die perspektivische Projektion simuliert also nach,wie wir in der Realität Grössen erfassen. Wenn wir ein Meter vor einem Baum stehen erscheint er grös-ser, als wenn wir hundert Meter vom selben Baum entfernt sind.

Eine weitere sehr verbreitete Projektionsmethode ist die orthogonale Projektion (engl. orthogonal projec-tion). Sie wird auch parallele Projektion genannt. Wenn wir orthogonale Projektion verwenden verändertsich die Grösse der Objekte nicht, egal wie weit entfernt die Kamera vor ihr steht. Ausserdem bleibenparallele Linien auch nach der Projektion parallel.

Ich hoffe, die ganzen Vorgänge werden klarer, wenn man sich die Abbildung 4.3 ansieht. Links siehtman eine perspektivische Projektion, rechts ist eine orthogonale Projektion zu sehen. Die Kamera istimmer an der gleichen Stelle positioniert. Man nennt diese Methoden Projektionen, weil vom 3D-Raumdas Bild auf eine 2D-Ebene projiziert wird. Wir werden uns im weiteren Verlauf auf die perspektivischeProjektion beschränken. Die perspektivische Projektion ist die am meisten benutzte Technik im Echtzeit-Rendering. Orthogonale Projektion wird vor allem für technische CAD-Anwendungen und 2D-Spielebenutzt. In jME wird zurzeit nur die perspektivische Projektion unterstützt.

4.5. Kamera

Zusätzlich zu dem World Space und dem Model Space haben wir noch einen Camera Space. Der CameraSpace ist genau der Raum den wir sehen, wenn wir durch unsere virtuelle Kamera die Szene betrachten.Die Kamera hat eine eigen Position im Raum und eine Richtung in die sie schaut.

4.5. Kamera 28

Abbildung 4.3.: Projektionen

Abbildung 4.4.: 3D-Pipeline

4.6. jME, OpenGL, DirectX, 3D-Pipeline: Was ist das? Was machen die? 29

Abbildung 4.5.: jME und OpenGL

4.6. jME, OpenGL, DirectX, 3D-Pipeline: Was ist das? Was machen die?

Wir haben jetzt häufig von der sogenannten 3D-Pipeline geredet ohne dabei zu erwähnen was diese Pipe-line genau macht. Der Begriff Pipeline wird ja vor allem im Ölsektor benutzt, was hat jetzt dieser Begriffin der 3D-Grafik zu suchen? In einer 3D-Pipeline geben wir unsere 3D-Daten als Input an, in der Pipelineselbst gehen dann durch Daten durch verschiedene Stufen, in denen die Daten jeweils verändert werden.Als Endresultat erhalten wir am Ende der Pipeline das fertige Bild auf dem Bildschirm. Abbildung 4.4sollte die ganze Aussage ein wenig verdeutlichen. Sie können auf dem Bild auch erkennen, das ein gros-ser Teil der Arbeit von der Hardware, das heisst konkret den 3D-Grafikbeschleunigern implementiert,wird. Die Liste, mit all den Sachen, die eine 3D-Karte direkt in der Hardware implementiert wird immerlänger. Früher unterstützten diese Karten nur das sogenannte Rasterizing, das Zeichnen der einzelnen Pi-xel auf den Bildschirm, heute wird selbst das Transformieren und Beleuchten von 3D-Objekten von derHardware ausgeführt. Die 3D-Karten implementieren dabei meistens einen 3D-API, von denen es heutezwei Bekannte gibt, Direct3D und OpenGL. Das heisst anstatt das wir als Programmierer direkt auf die3D-Hardware programmieren, benutzen wir die 3D-Schnittstellen Direct3D und OpenGL, die dann diegewünschten Operationen direkt auf der Hardware ausführen. Die genau Schnittstelle zwischen 3D-APIund Hardware ist dabei der 3D-Treiber, der von Grafikkartenentwicklern sehr häufig upgedatet wird.Das ist das sogenannte Layering, das in der Informatik allumfassend ist.

Man kann mit Direct3D und OpenGL sehr viele Sachen kontrollieren, viele Sachen, die man oft nurselten braucht. Ausserdem ist das ganze Verwalten von 3D-Objekten auf tiefer Ebene auch sehr kompli-ziert. Deshalb programmiert man mit einer 3D-Engine noch eine zusätzliche Schicht über OpenGL oderDirect3D. Der genau gleiche Weg macht auch jME, wie Sie auf Abbildung 4.5 erkennen können.

Wer sich dafür interessiert, was in der 3D-Pipeline noch alles gemacht wird und mit welchen Algo-rithmen die Konzepte implementiert werden sollte sich das 3D-Pipeline Tutorial [Sal01] durchlesen. Indiesem Tutorial werden wir uns nicht mehr weiter mit OpenGL und Direct3D beschäftigen, wer sichaber dafür interessiert, dem werden die Bücher OpenGL Game Programming [AH01] und Introductionto 3D Game Programming[Lun03] wärmstens empfohlen.

4.7. Ressourcen 30

4.7. Ressourcen

Wir haben in diesem Kapitel einen Schnelldurchlauf durch die Theorie des 3D-Rendering gemacht.Da ist es kein Wunder, wenn viele Details auf der Strecke bleiben. Viele Standardwerke in diesemBereich treten das Ganze Thema schliesslich auf mehreren hundert Seiten breit. Ein Standardwerk des3D-Rendering zweifelsohne Real-Time Rendering [AMH02] von Akenine-Möller und Haines in demalle theoretischen Konzepte auch von den kommenden Kapiteln befinden. Ein weiteres Standardwerkim Bereich der Computergrafik ist Computer Graphics: Principles and Practice [FvDFH95].

5Interaktion

5.1. Einfache Interaktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315.2. Ein objektorientierter Ansatz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

5.1. Einfache Interaktionen

Was macht eine Demo interaktiv könnte man fragen? Eine Antwort liegt auf der Hand, man muss mitder Applikation interagieren können. Interagieren können wir mit unserer Applikation, wenn sie auf un-sere Befehle reagiert. In diesem Kapitel werden wir Ihnen zeigen, wie sie in jME eigene Tastaturbefehleerzeugen und diese später dann abfragen können. Legen wir gleich los mit dem Quellcode. Sie findenden Quellcode wie immer auf der CD mit dem Pfad demos/keyinput/InputDemo.java. Im Anhang B wirderklärt, wie Sie die Demo starten und gegebenfalls verändern können. Wir haben hier auf einen Screens-hot verzichtet, weil wir in dieser Demo nur einen Würfel sehen. Diese Demo ist interaktiv, weil Sie mitden Tasten ’U’ und ’J’ den Würfel entlang der y-Achse verschieben können, das bedeutet nach obenoder unten, falls Sie die Kamera innerhalb der Demo nicht ändern. Mit den Tasten ’H’ beziehungsweise’K’ können Sie sich entlang der x-Achse verschieben, was einer Verschiebung nach links oder rechtsentspricht, von vorne angesehen.

Listing 5.1: InputDemo.java1 package keyinput;2

3 import com.jme.input.KeyBindingManager;4 import com.jme.input.KeyInput;5 import com.jme.math.Vector3f;6 import com.jme.scene.Text;7 import com.jme.scene.shape.Box;8

9 import helper.TutorialGame;10

11 /**12 * A demo to present how input handling and textdisplay works13 * @author Daniel Egger14 */15 public class InputDemo extends TutorialGame16 {17 private Box b;18 private static final float DELTA = 0.005f;19 private Text boxPrint;20

31

5.1. Einfache Interaktionen 32

21 public static void main( String[] args )22 {23 new InputDemo();24 }25

26 protected void simpleInitGame()27 {28 // set the title of our demo29 display.setTitle( "Input Demo" );30

31 // create and attach a new box32 b = new Box( "box", new Vector3f( -1, -1, -1), new Vector3f( 1, 1, 1 )

);33 rootNode.attachChild( b );34

35 // create some key actions36 // assign the key ’U’ to the action boxUp37 KeyBindingManager.getKeyBindingManager().set( "boxUp", KeyInput.KEY_U )

;38 // assign the key ’J’ to the action bowDown39 KeyBindingManager.getKeyBindingManager().set( "boxDown", KeyInput.KEY_J

);40 // assign the key ’H’ the the action boxLeft41 KeyBindingManager.getKeyBindingManager().set( "boxLeft", KeyInput.KEY_H

);42 // assign the key ’L’ to the action boxRight43 KeyBindingManager.getKeyBindingManager().set( "boxRight", KeyInput.

KEY_K );44

45 // create the text node46 Vector3f boxPosition = b.getLocalTranslation();47 boxPrint = new Text( "boxPrint", "Box Position: x=" + boxPosition.x + "

; y=" +48 boxPosition.y + "; z=" + boxPosition.z );49 boxPrint.setLocalTranslation( new Vector3f( 0, 20, 0 ) );50 textNode.attachChild( boxPrint );51 }52

53 protected void simpleUpdate()54 {55 Vector3f boxPosition = b.getLocalTranslation();56

57 // check for the key actions58 if( KeyBindingManager.getKeyBindingManager().isValidCommand(59 "boxUp", true ) )60 {61 boxPosition.y += DELTA;62 }63 if( KeyBindingManager.getKeyBindingManager().isValidCommand(64 "boxDown", true ) )65 {66 boxPosition.y -= DELTA;67 }68 if( KeyBindingManager.getKeyBindingManager().isValidCommand(69 "boxLeft", true ) )70 {71 boxPosition.x -= DELTA;72 }

5.1. Einfache Interaktionen 33

73 if( KeyBindingManager.getKeyBindingManager().isValidCommand(74 "boxRight", true ) )75 {76 boxPosition.x += DELTA;77 }78

79 // set the box to its new position80 b.setLocalTranslation( boxPosition );81

82 // update our text string83 boxPrint.print( "Box Position: x=" + boxPosition.x + "; y=" +

boxPosition.y +84 "; z=" + boxPosition.z );85 }86 }

In den Zeilen 35 bis 43 verbinden wir Tasten mit Aktionen. Tastatureingaben werden in jME, wie vieleandere Sachen auch abstrahiert. Der KeyInputManager ist ein Singleton (sehen Sie dazu Design Pat-terns[GHJV95] auf das wir immer zugreifen können. Sehen wir uns eine Zeile noch etwas genauer an:

// assign the key ’U’ to the action boxUpKeyBindingManager.getKeyBindingManager().set("boxUp", KeyInput.KEY_U);

Hier verbinden wir die Aktion boxUp mit der Taste ’U’ auf der Tastatur. Der Name der Aktion kann dabeiein beliebiger String sein. Alle Tasten, die Ihnen zur Verfügung stehen, sind in der Klasse KeyInput alsKonstanten definiert. Für annähernd jede Taste die sich auf einer Tastatur befindet gibt es dabei eineeigene Konstante. Später werden wir nicht testen, ob die Taste ’U’ gedrückt wurde, wir werden testen obdie Aktion boxUp eingetroffen ist. Diese Annäherung hat einige Vorteile: wir können mehrere Tasten dergleichen Aktion zuteilen, ausserdem ist es für einen Anwender leicht seine eigenen Tasten für Aktionenzu definieren beziehungsweise umzustellen. Wenn wir direkt auf Tastatureingaben reagieren würden,wäre das wesentlich komplizierter.In den Zeilen 53 bis 83 benutzen wir zum ersten Mal die Methode simpleUpdate. simpleUpdatewird jeden Frame, das heisst jeden Zwischenschritt, den unsere Demo macht, aufgerufen. Wir müssenschliesslich auch in jedem Zwischenschritt testen, ob eine Taste gedrückt wurde. Um nun abzufragen obeine Aktion eingetreten ist rufen wir folgende Methode auf:

if( KeyBindingManager.getKeyBindingManager().isValidCommand("boxUp", true ) )

{boxPosition.y += DELTA;

}

Der Name der Aktion muss natürlich mit einer oben definierten Aktion übereinstimmen, sonst passiertnichts. Das kann ein Fehler sein, den man gerne und leicht macht. Als zweites Argument geben wir nochein true mit. Es bedeutet, dass die Aktion in jedem Frame ausgeführt werden soll. Hätten wir hier einfalse geschrieben, würde die Aktion nur einmal pro Tastendruck ausgeführt. Das gleiche Prozederewiederholen wir mit jeder Aktion die wir oben definiert haben. Am Schluss können wir den Würfel mitsetLocalTranslation auf die neue Position verschieben.Neben den Tastenaktionen wird in dieser Demo auch gezeigt, wie man Text auf dem Bildschirm dar-stellen kann. Dazu haben wir eine Instanz der Klasse Text namens boxPrint erzeugt. In Zeile 47 und48 sehen wir, wie das abläuft. Wie jedem anderen zu zeichnenden Objekt, müssen wir auch unseremTextobjekt einen Namen geben. Den Text den wir darstellen wollen, ist die Position des Würfels, die inboxPosition gespeichert ist. Danach verschieben wir unser Textobjekt mit setLocalTranslation inden unteren rechten Rand des Bildschirms. Hierbei gilt zu beachten, dass für Textobjekte nicht das glei-che Koordinatensystem verwendet wird wie für die üblichen 3D-Objekte. Dieses Koordinatensystem hatseinen Ursprung im Punkt rechts unten im Bildschirm und erstreckt sich Pixel für Pixel entsprechend derBildschirmauflösung die wir gewählt haben. Schlussendlich fügen wir unser Textobjekt der textNode

5.2. Ein objektorientierter Ansatz 34

an. textNode ist ein spezieller Scenegraph, der dazu optimiert wurde Texte auf dem Bildschirm darzu-stellen und auch für das andere Koordinatensystem verantwortlich ist.Da sich die Position des Würfels in jedem Frame ändern kann, müssen wir auch den Text von printBoxjeden Frame ändern. Dazu benutzen wir die Methode print, wie auf Zeile 83 und 84 zu sehen.

5.2. Ein objektorientierter Ansatz

Viele OO-Puristen werden vielleicht leicht die Nase gerümpft haben, als sie das oben gezeigte Verfahrengesehen haben. Das Vorgehen mit der Methode isValidCommand werden vielleicht einige Program-mierer als “dirty trick” abwerten und das ist es vielleicht sogar, aber es ist die schnellste Methode umTastatureingaben zu überprüfen.Einen Anderen Ansatz kann man gehen, indem man einen ähnlichen Weg geht wie im EntwurfsmusterBefehl (engl. Command) geht, dieses Entwurfsmuster ist mit vielen anderen Entfursmuster im Katalogder Gang of Four[GHJV95] beschrieben. Wir kapseln nun unsere Aktionen in sogenannten Actions. Wiedas genau geht, wird in diesem Abschnitt erklärt. Diese Demo macht genau das gleiche wie die vorhe-rige Demo, auch die Tastaturbelegung ist die gleiche, das einzige das sich geändert hat ist die Art undWeise, wie wir auf die Tastatureingaben reagieren. Sie finden die Quellcodedatei im Unterverzeichnisdemos/keyinput mit dem Namen InputActions.java.

Listing 5.2: InputActions.java simpleInitGame and simpleUpdate

28 protected void simpleInitGame()29 {30 // set the title of our demo31 display.setTitle( "Input Demo" );32

33 // create and attach a new box34 b = new Box( "box", new Vector3f( -1, -1, -1 ), new Vector3f( 1, 1, 1 )

);35 rootNode.attachChild( b );36

37 // create some key actions38 // assign the key ’U’ to the action boxUp39 KeyBindingManager.getKeyBindingManager().set( "boxUp", KeyInput.KEY_U )

;40 // assign the key ’J’ to the action bowDown41 KeyBindingManager.getKeyBindingManager().set( "boxDown", KeyInput.KEY_J

);42 // assign the key ’H’ the the action boxLeft43 KeyBindingManager.getKeyBindingManager().set( "boxLeft", KeyInput.KEY_H

);44 // assign the key ’L’ to the action boxRight45 KeyBindingManager.getKeyBindingManager().set( "boxRight", KeyInput.

KEY_K );46

47 // create a new up action and assign it to the "boxUp" call48 KeyInputAction upAction = new KeyNodeUpAction( b );49 upAction.setKey( "boxUp" );50 input.addAction( upAction );51

52 // create a new down action and assign it to the "boxDown" call53 KeyInputAction downAction = new KeyNodeDownAction( b );54 downAction.setKey( "boxDown" );55 input.addAction( downAction );56

57 // create a left action and assign it to the "boxLeft" call

5.2. Ein objektorientierter Ansatz 35

58 KeyInputAction leftAction = new KeyNodeLeftAction( b );59 leftAction.setKey( "boxLeft" );60 input.addAction( leftAction );61

62 // create a right action and assign it to the "boxRight" call63 KeyInputAction rightAction = new KeyNodeRightAction( b );64 rightAction.setKey( "boxRight" );65 input.addAction( rightAction );66

67 // create the text node68 Vector3f boxPosition = b.getLocalTranslation();69 boxPrint = new Text( "boxPrint", "Box Position: x=" + boxPosition.x70 + "; y=" + boxPosition.y + "; z=" + boxPosition.z );71 boxPrint.setLocalTranslation( new Vector3f( 0, 20, 0 ) );72 textNode.attachChild( boxPrint );73 }74

75 protected void simpleUpdate()76 {77 Vector3f boxPosition = b.getLocalTranslation();78 // update our text string79 boxPrint.print( "Box Position: x=" + boxPosition.x + "; y=" +

boxPosition.y80 + "; z=" + boxPosition.z );81 }

Wie Sie sehen können haben wir in der Methode simpleInitGame etwas hinzugefügt, namentlich dieCodezeilen 48 bis 65, in denen wir die verschiedenen KeyNodeActions instanzieren und gleichzeitighaben wir die Methode simpleUpdate entschlankt indem wir alle if’s mit den isValidCommand Auf-rufen entfernt haben.Sehen wir uns nun genauer an wie wir eine Action einsetzen:

// create a new up action and assign it to the "boxUp" callKeyInputAction upAction = new KeyNodeUpAction( b );upAction.setKey( "boxUp" );input.addAction( upAction );

1. Als erstes erstellen wir eine Instanz einer Action, in diesem konkreten Falle eine Instanz vonKeyNodeUpAction. Als Argument geben wir unsere Box an, weil das auch das Objekt ist, das wirwährend einem Tastendruck bewegen wollen.

2. Im nächsten Schritt bestimmen wir die Taste, die wir zu unserer Action hinzufügen. Wir benutzendabei die gleichen Strings, die wir auch schon in der vorherigen Demo verwendete haben. DieStrings selbst müssen hier wiederum mir Hilfe des KeyBindingManager einer jeweiligen Tastehinzu gefügt werden.

3. Schlussendlich müssen wir unsere Action einem InputHandler anschliessen. Der InputHandlerkümmert sich danach darum, das die Actions auch ausgeführt werden. Als Benutzer der KlasseTutorialGame können wir den InputHandler namens input verwenden, der in dieser Klasseals protected member definiert wurde.

Diese drei Schritte bleiben jeweils die gleichen wenn wir Actions definieren und sie bestimmten Tastenhinzufügen. Die Schritte wiederholen sich auch für die nächsten drei Aktionen die wir definieren.In dieser Demo müssen wir natürlich auch die verschiedenen KeyInputActions definieren, die dieeigentliche Arbeit machen, wenn wir auf eine bestimmte Taste drücken. Dazu schauen wir uns in derDatei InputActions.java die Zeilen 89 bis 103 genauer an:

5.2. Ein objektorientierter Ansatz 36

Abbildung 5.1.: Die verschiedenen InputActions

Listing 5.3: InputActions.java KeyNodeUpAction84 /**85 * This class moves the node up the world y-axis86 */87 class KeyNodeUpAction extends KeyInputAction88 {89 // the node to manipulate , our Box is also a spatial90 private Spatial node;91

92 KeyNodeUpAction( Spatial node )93 {94 this.node = node;95 this.speed = 1;96 }97

98 // every KeyAction has to implement the performAction method99 public void performAction( InputActionEvent evt )

100 {101 node.getLocalTranslation().y += (speed * evt.getTime());102 }103 }

Jede Action, die wir deklarieren, muss ein Unterklasse von KeyInputAction sein, deshalb lassen wirauch unsere KeyNodeUpAction von KeyInputAction erben. Unser Konstruktor verlangt einen Spatialund eine float Variable speed als Argument. Wie wir bereits in Abbildung 3.4 gesehen haben ist Spatialdie Oberklasse aller möglichen Blätter in unserem Scenegraphen. Im Konstruktor weisen wir demSpatial node einfach ein gewünschtes Objekt zu.Die Arbeitsmaschine einer InputAction-Klasse ist die Methode performAction, die von uns imple-mentiert werden muss, da sie als abstrakte Methode in InputAction definiert ist. Diese Methode wirdjeweils aufgerufen, wenn wir die gewünschte Taste drücken In unserer Klasse KeyNodeUpAction wol-len wir unser Objekt entlang der y-Achse nach oben bewegen, wir verändern also die y-Koordinate vonunserem Objekt. Mit dem InputActionEvent-Argument evt können wir mit der Methode getTimedie Zeit abrufen, die seit dem letzten Frame vergangen ist. Das ist genau die Zeit, die wir auch mitspeed multiplizieren wollen um die gewünschte Bewegungsdistanz zu erhalten. Die anderen drei Acti-on Klassen sehen fast gleich aus nur die Implementation der Methode performAction ändert sich einBisschen.Um den ganzen Sourcecode noch ein wenig zu visualisieren können Sie auf der Abbildung 5.1 noch einkleines Klassendiagramm sehen, das die Vererbungssituation der verschiedenen KeyInputActions dar-stellt.

6Simple Formen und Modelle

6.1. Einfache Formen und warum die Kugeln nicht rund sind . . . . . . . . . . . . . . 376.2. Komplexere Modelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

6.2.1. Modell-Formate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

6.2.2. Modelle laden in jME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

6.1. Einfache Formen und warum die Kugeln nicht rund sind

In unseren ersten Programmen haben wir einen simplen Würfel erstellt. Aber jME kann noch anderesimple geometrische Formen erzeugen. Zum Beispiel Kreise, Zylinder und noch einige mehr. Im Bereichder 3D-Grafik benutzt man sehr häufig die Worte Model und Mesh, wenn man von Formen spricht. Hiergibt es dazu ein Beispielprogramm. Wenn ich also in Zukunft von einem Modell spreche, ist nichtsanderes gemeint als eine dreidimensionale Form.

Listing 6.1: SimpleGeometry.java40 protected void simpleInitGame()41 {42 // set a new title43 display.setTitle( "A Demo of Some Simple Geometry" );44

45 // create a box46 Box b = new Box( "box", new Vector3f( -1, -1, -1 ), new Vector3f( 1, 1,

1 ) );47 b.setLocalTranslation( new Vector3f( -2, 2, 0 ) );48 b.setModelBound( new BoundingSphere() );49 b.updateModelBound();50 rootNode.attachChild( b );51

52 // create a sphere53 Sphere s = new Sphere( "sphere", 10, 15, 1.5f );54 s.setLocalTranslation( new Vector3f( 2, 2, 0 ) );55 s.setModelBound( new BoundingBox() );56 s.updateModelBound();57 rootNode.attachChild( s );58

59 // create a torus60 Torus t = new Torus( "torus", 20, 10, 0.5f, 1.5f );61 t.setLocalTranslation( new Vector3f( -2, -2, 0 ) );62 t.setModelBound( new BoundingBox() );

37

6.1. Einfache Formen und warum die Kugeln nicht rund sind 38

Abbildung 6.1.: Ein Screenshot aus der Demo SimpleGeometry.javaLinks sind die normalen Modelle zu sehen, rechts sieht man die Drahtgittermodelle auf denen die ein-zelnen Punkte und Dreiecke zu erkennen sind.

63 t.updateModelBound();64 rootNode.attachChild( t );65

66 // create a cylinder67 Cylinder c = new Cylinder( "cylinder", 10, 15, 1.5f, 3 );68 c.setLocalTranslation( new Vector3f( 2.5f, -2, 0 ) );69 // rotate the cylinder 20 degrees around the y-axis70 Matrix3f m = new Matrix3f();71 m.loadIdentity();72 m.fromAngleNormalAxis( FastMath.DEG_TO_RAD * 20.0f, new Vector3f( 0.0f,73 1.0f, 0.0f ) );74 c.setLocalRotation( m );75 c.setModelBound( new BoundingBox() );76 c.updateModelBound();77 rootNode.attachChild( c );78

79 initText();80 assignKeys();81 }

Hier wurde nur der essentielle Teil des Quellcodes abgedruckt, den Rest finden Sie auf der CD (sieheAnhang A) im Verzeichnis demos/geometry mit dem Dateinamen SimpleGeometry.java. Beim essentiel-len Teil handelt es sich vorwiegend um die Methode simpleInitGame. Wie oben bereits erwähnt bleibtder Code in main in fast allen Demos gleich. Was sich vorwiegend von Demo zu Demo ändert sind dieimportierten Java-Pakete. Ausserdem wurden einige Hilfsanweisungen und dazugehörige Tastaturkom-mandos programmiert, diese sind aber für das Verständnis des Programmes nicht von Bedeutung.Bevor wir das Programm aber ganz erklären können, müssen wir noch ein wenig Theorie behandeln. jMEist nicht in der Lage Kurven und Rundungen darzustellen, sondern kann nur Dreiecke zeichnen. Das istnicht nur ein Problem das jME allein betrifft, sondern ein Problem das alle heutigen 3D-Echtzeitrenderertrifft. Die 3D-Beschleuniger, die dafür verantwortlich sind die Grafik zu verarbeiten und auf den Bild-schirm zu bringen, können nur Dreiecke (engl. Triangles) verarbeiten und wurden genau für diesenVorgang optimiert. Diese Dreiecke werden intern als Liste von Punkten (engl. Vertices) gespeichert.jME benutzt die Klasse TriMesh um solche Listen von Punkten und Dreiecken zu verwalten. Es solltealso nicht erstaunen, wenn man erfährt, dass alle simplen geometrischen Formen direkt von TriMesherben. Anstatt die von jME zur Verfügung gestellten Formen zu verwenden kann man auch von Hand

6.1. Einfache Formen und warum die Kugeln nicht rund sind 39

solche Listen von Punkten erstellen und die jeweiligen Dreiecke angeben indem man direkt eine Instanzvon TriMesh erzeugt. Das wäre aber bereits für ein einfachen Würfel mit 8 Punkten und mindestens 12Dreiecken eine ziemlich mühsame Aufgabe und ist fast ganz undenkbar, wenn man auf diese Weise einAuto oder eine Person erstellen will. Wie das Programm aussieht, kann man auch auf dem Screenshotin Abbildung 6.1 erkennen. Links sind die jeweiligen Modelle im Standardmodus zu erkennen. Rechtssieht man die Wireframe Modelle und man kann die einzelnen Punkte und Dreiecke betrachten. Denwireframe Modus können Sie mit der Taste ’T’ einschalten und wieder ausschalten.Gehen wir das Programm nun schrittweise durch. Wie sie einen Würfel mit Box erstellen können wissenSie ja bereits. Wir verschieben den Würfel nun nach links oben mit Hilfe von setLocalTranslation.Was in diesem Programm neu hinzu kommt sind die Zeilen 48 und 49. Wir erstellen hier eine Hüllefür unseren Würfel mit dem Befehl setModelBound. Die Hülle um unsere Objekte ist eine einfacheMöglichkeit unser Programm zu beschleunigen. Mit Hilfe von diesen Bounding Volumes kann der Sce-negraph von jME einen einfachen und schnellen Test machen, ob ein Objekt überhaupt auf dem Bild-schirm sichtbar ist und deshalb überhaupt verarbeitet werden muss. Für diese einfachen Objekte ist derVorteil nicht auf Anhieb erkennbar, aber nehmen wir an wir haben ein sehr komplexes Modell von ei-nem Obergegner, das wir mit einer Kugelhülle (engl. BoundingSphere) überziehen. Falls die Kugel dieunser Gegner verhüllt nicht sichtbar ist, ist auch der Gegner selbst nicht sichtbar und jME versucht erstgar nicht ihn auf dem Bildschirm zu zeichnen, was ein sehr wichtiger Perfomancesteigerung unseresProgramms bedeutet.Sie können die Hüllen (engl. BoundingVolumes) in der Demo sichtbar machen, indem Sie die Taste ’B’drücken. In diesem Beispiel haben wir unseren Würfel mit einer Kugel überzogen was nicht sehr sinnvollist, aber so kann man den Effekt besser erkennen auf dem Bildschirm. Wenn wir dem Objekt eine Hüllezugewiesen haben, müssen wir die Hülle mit updateModelBound noch auf den neusten Stand bringen,damit die Hülle auch wirklich das ganze Objekt überzieht. Wie bereits erwähnt müssen wir alle Objekte,die wir auf dem Bildschirm sehen wollen an den Scenegraph anhängenIn Zeile 52 bis 57 erstellen wir eine Kugel mit Sphere. Wie jedes andere Objekt in jME müssen wir unse-rer Kugel als erstes Argument einen Namen geben. Da sich unsere Kreativität in Grenzen hält, benennenwir die Kugel mit sphere. Die nächsten beiden Argumente geben an in wieviele Teile und somit Drei-ecke unsere Kugel unterteilt wird. Das zweite Argument gibt an in wieviele Teile entlang der z-Achseunsere Kugel unterteilt wird, wir haben 10 genommen. Das dritte Argument gibt in wieviele Teile dieKugel im Radius unterteilt wird, in unserem Beispiel sind es 15. Diese Argumente sind relativ schwerzu beschreiben, am besten probieren Sie selbst einige Zahlen aus. Je grösser diese Zahlen sind desto“runder” wird die Kugel, sie wird in kleinere Dreiecke unterteilt es braucht aber auch mehr Power siezu rendern. Je kleiner wir diese Zahlen wählen desto eckiger wird die Kugel, bis fast gar nicht mehr alsKugel erkennbar ist. Versuchen sie einmal die Werte 6 und 6 für diese Argumente, sie werden ein ecki-ges Gebilde erhalten, das nur entfernt einer Kugel ähnelt. Das vierte und letzte Argument ist der Radiusder Kugel, wir haben hier 1.5 genommen. Das Zentrum der Kugel ist der Ursprung des Koordinatensys-tems also der Punkt (0; 0; 0). Wir verschieben die Kugel gegen oben rechts mit setLocalTranslation.Wiederum geben wir unserem Objekt eine Hülle und wir hängen es dem Scenegraphen an.Ein Torus ist ein geometrisches Objekt, das wie ein Donut aussieht. Der Torus wird ähnlich erstellt wieeine Kugel, es enthält aber zwei Radien einen inneren Radius und einen äusseren Radius. Ausserdemgibt es wieder zwei Argumente die, die Zerteilung in Dreiecke bestimmen. Am besten Sie spielen selbstein bisschen mit den Argumenten herum. Wir verschieben den Torus ein wenig nach rechts unten understellen ausserdem eine Hülle.Als viertes und letztes Objekt erstellen wir einen Zylinder in den Zeilen 67 bis 77. Die jeweiligen Ar-gumente schauen Sie sich am besten selbst an in der JavaDoc-Dokumentation [34] von jME, die Sienotfalls auch online finden können. Wir verschieben den Zylinder nach unten rechts und rotieren ihnausserdem um 20◦ auf der y-Achse.Am Ende von simpleInitGame rufen wir noch die Methoden initText und assignKeys auf. Die-se Methoden wurden auch in der Klasse SimpleGeometry definiert und kümmern sich um den an-zuzeigenden Text (initText) und stellt ausserdem einige neue Tastenkombinationen zur Verfügung

6.2. Komplexere Modelle 40

Abbildung 6.2.: Milkshape 3D mit dem Ferrari Modell

(assignKeys). Diese Methoden werden auch in fast allen unseren Demoprogrammen definiert, sie sindaber für das Verständnis zum jetzigen Zeitpunkt nicht von grosser Bedeutung, wir werden später daraufzurückkommen.

6.2. Komplexere Modelle

6.2.1. Modell-Formate

In der Einführung zur 3D-Grafik haben wir bereits von Modellierungsprogrammen gesprochen. Mitdiesen Modellierungsprogrammen können 3D-Künstler sehr komplexe Modelle und auch ganze Szenenmodellieren. Häufig erstellen 3D-Modellierer Modelle her, die man später in Spielen oder anderen Echt-zeitanwendungen verwenden will. Man denke nur an die verschiedenen Gegner in einem 3D-Ballerspieloder die verschiedensten Waffen, die wir in einem 3D-Rollenspiel antreffen, auch die ganzen Gebäudeund überhaupt fast alles, das man in einem 3D-Spiel sehen kann wurde ursprünglich mit einem 3D-Modellierprogramm erschaffen.Auf dem Screenshot in Abbildung 6.2 sehen wir zum Beispiel das Programm Milkshape 3D. Wir habenbereits ein Modell geöffnet und können direkt die Punkte manipulieren und neue Dinge hinzufügen, eshandelt sich um das gleiche Modell, das wir in der nachfolgenden Demo auch in jME darstellen werden.Wie wir bereits im vorherigen Kapitel gesehen haben, besteht jedes Modell im Prinzip aus zusammen-hängenden Dreiecken und Listen von Punkten. Die meisten Spiele und Anwendungen erstellen aus-

6.2. Komplexere Modelle 41

Abbildung 6.3.: Screenshot von ModelLoader.java mit Blick auf das Gittermodell

serdem ein eigenes meist binäres Dateiformat um diese Modelle, das heisst Listen von Punkten undDreiecken, zu speichern. Da sich einige dieser Spiele und Anwendungen grösster Beliebtheit erfreuenhaben sich einige Quasistandards gebildet, die auch von jME direkt unterstützt werden. Leider ist keinoffizieller zertifizierter Standard darunter. Jahrelang hat jeder Hersteller sein eigens Süppchen gekocht.Man scheint jedoch auf dem richtigen Weg zu sein und in letzter Zeit scheint sich ein Standard zu bil-den, der von mehreren Herstellern unterstützt wird. Dieser Standard heisst COLLADA [14], er befindetsich aber leider noch in den Kinderschuhen. jME kann unter anderem die Formate ms3d von Milkshape3D und md2 aus Quake 2 direkt laden. Natürlich unterscheidet sich der Umfang dieser Modell-Formatezum Teil beträchtlich. Einige enthalten ganze Szenen, mit Lichtern, Animation, Kameraführung, Textu-ren und noch einigem mehr. Wir und auch jME begnügen uns mit einem Unterteil von all diesen Datenund genügt meist, die Form eines Objektes selbst und den Rest machen wir von Hand. Leider können wirdie Modelle selbst auch nicht von verändern, wir müssen die Modelle in ein Modellierungsprogrammladen, das auch mit dem Modell-Format umgehen können muss. Die Formate selbst enthalten nur binäreDaten, die Spezifikation dazu kann man aber meistens im Internet finden.Im Internet kann man unzählige von Seiten finden mit Modellen. Leider ist die Qualität zum Teil sehrdurchwachsen, aber wenn man genug Zeit investiert kann man durchaus die eine oder andere Perlefinden. Auf Psionic’s 3D Resources [52] und Creepy’s Object Downloads [15] können Sie einige freiverwendbare Modelle von hoher Qualität finden.

6.2.2. Modelle laden in jME

In der folgenden Demo werden wir einen Ferrari laden und auf dem Bildschirm zeichnen. Der Ferrariist im Unterordner demos/data mit dem Namen f360.ms3d gespeichert. Einen Screenshot von diesemProgramm sehen Sie auf Abbildung 6.3. Fangen wir gleich mit dem Quellcode an:

Listing 6.2: ModelLoader.java37 protected void simpleInitGame()38 {39 display.setTitle( "Model Loader" );40

41 try42 {43 // convert the input file to a jme -binary model44 FormatConverter converter = new MilkToJme();45 ByteArrayOutputStream modelData = new ByteArrayOutputStream();46 URL modelFile = ModelLoader.class.getClassLoader().getResource(

6.2. Komplexere Modelle 42

47 "data/f360.ms3d" );48 converter.convert( new BufferedInputStream( modelFile.openStream() ),49 modelData );50

51 // get the model52 JmeBinaryReader jbr = new JmeBinaryReader();53 Node carModel = jbr.loadBinaryFormat( new ByteArrayInputStream(54 modelData.toByteArray() ) );55

56 // load and create a texture for the model57 URL texLoc;58 texLoc = ModelLoader.class.getClassLoader().getResource(59 "data/fskiny.jpg" );60 TextureState tsMipMap = display.getRenderer().createTextureState();61 Texture tex = TextureManager.loadTexture( texLoc ,62 Texture.MM_LINEAR_LINEAR , Texture.FM_LINEAR );63 tsMipMap.setTexture( tex );64 carModel.setRenderState( tsMipMap );65

66 // make the model a little smaller67 carModel.setLocalScale( 0.1f );68

69 // attach our model to the scene graph70 rootNode.attachChild( carModel );71 } catch( IOException e )72 {73 System.out.println( "Couldn’t load the input file:" + e );74 e.printStackTrace();75 }76

77 initText();78 assignKeys();79 }

Wir beschränken uns hier wiederum nur auf die Methode simpleInitGame. Der Rest bleibt fast gleichwie in den vorherigen Demos. Wiederum ist der gesamte Quellcode auf der CD zu finden (siehe AnhangA), konkret ist das die Datei demos/geometry/ModelLoader.java.Man kann in jME nicht eine beliebige Modell-Datei öffnen und dann direkt zeichnen. Man muss denUmweg über das jME-eigene binäre Dateiformat gehen. Dabei erstellen wir eine Instanz der KlasseFormatConverter (Zeile 44). Wie Sie auch auf dem Diagramm sehen können besitzt jedes unterstützteDateiformat auch eine eigene Unterklasse von FormatConverter. Wir benutzen hier MilkToJme, weilwir ein Milkshape-Modell mit der Dateierweiterung ms3d öffnen wollen. Als nächstes können wir dieDatei in das binäre jME-Format konvertieren (Zeile 45 bis 49). Wir machen hier die ganze Arbeit mitStreams. In einer richtigen Anwendung würden wir wohl zuerst alle unsere Modelle am Schluss in dasjME eigene Dateiformat konvertieren und dann diese Datei laden. Dabei müssen wir nicht den Umwegüber die jeweiligen FormatConverter gehen, unsere Anwendung würde also die Modelle schnellerladen können.In den Zeilen 52 bis 54 laden wir endlich unsere Daten mit JmeBinaryReader und erstellen eine Node.Der Knoten kann dann direkt in unseren Scenegraph eingefügt werden. In den Zeilen 57 bis 67 wirdausserdem eine Textur geladen und auf unser Auto “geklebt”, damit es realistischer aussieht. Was esgenau auf sich hat mit Texturen werden wir in Kapitel 8 sehen. Sie können diesen Teil also bis aufweiteres ignorieren. Nun haben wir also schon mal ein Demo mit einem Ferrari. Wenn Sie während derAusführung auf ’T’ drücken können Sie wiederum das Wireframe Modell unseres Ferraris erkennen, wieSie nun auf dem Bildschirm oder auch auf dem Screenshot sehen, besteht auch der Ferrari im Grundegenommen nur aus Dreiecken.

6.2. Komplexere Modelle 43

Im Ordner demos/data gibt es noch mehr Milkshape-Modelle, wie zum Beispiel das Modell jeep1.ms3d,versuchen Sie doch einmal dieses Modell zu laden.

7Es werde Licht

7.1. Lichtquellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447.2. Lichter einsetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

Wenn wir dreidimensionale Objekte rendern wollen wir nicht nur die Formen richtig auf den Bildschirmbringen, wir wollen auch, dass die Objekte realistisch aussehen. Das kann man mit verschiedenen Tech-niken erreichen. Eine der wichtigsten Techniken sind dabei die Lichter und die Beleuchtung. Ohne Lichtwürden wir in der realen Welt nichts erkennen, ähnlich geht es auch einer 3D Welt ohne Licht. In allenDemos, die wir bis jetzt gesehen haben, war auch schon ein Licht integriert, das wir von unserer Klas-se TutorialGame übernommen haben. Wie eine Szene ohne Licht aussehen würde können Sie auf derAbbildung 7.1 sehen. Man sieht zwar im Gegensatz zu der realen Welt noch etwas, auch wenn es keinLicht gibt, die wahre Form unseres Würfels kann man aber nicht mehr richtig erkennen. Deshalb sindLichter im 3D-Rendering von enormer Bedeutung.

7.1. Lichtquellen

Wir können Objekte sehen weil Photonen von Objekten abprallen oder ausgesendet werden, die schlus-sendlich unsere Augen erreichen. Diese Photonen kommen in der Regel von Lichtquellen. Im Kontextder 3D-Grafik gibt es auch Lichtquellen. Man spricht von Parallelem Licht (engl. directional light),Punkt Lichtern (engl. point light) und Spot Lichtern (engl. spot light).Ein paralleles Licht ist unendlich weit weg positioniert von den Objekten, die beleuchtet werden. DieLichtstrahlen kommen also alle parallel an. Ein Beispiel für ein solches Licht wäre die Sonne, derenLichtstrahlen fast parallel auf die Erde eintreffen, da sie so weit weg positioniert ist. Punkt und SpotLichter nennt man auch Positionslichter, da sie eine Position im Raum besitzen. Man kann sich einPunktlicht vorstellen, als ein Licht das in einem Punkt Lichtstrahlen in alle Richtungen aussendet. Einesimple Glühbirne wäre ein Beispiel für ein solches Licht. Auf Abbildung 7.2 können Sie sehen, wie einPunktlicht in jME funktionert.Ein Licht hat ausserdem verschiedene Attribute wie Intensität und Farbe. Man spricht in der Regel voneinem Ambient Value und einem Diffuse Value. Der Diffuse Value ist die Farbe des Lichtes, hierbei kön-nen wir jede beliebige Farbe übergeben. Die meisten Lichter in unserer Umgebung haben dabei eineweisse Farbe oder gehen leicht ins gelbliche, es gibt aber natürlich auch in der echten Welt Glühbirnenin jeder erdenklichen Farbe, denken Sie nur an Ihre letzte Gartenparty zurück. Der sogenannte AmbientValue ist schwieriger zu erklären. Wir geben hier die Farbe des Streuungslichtes an, das heisst das Licht,das Sie noch sehen können, wenn ein Objekt nicht direkt von der Lichtquelle beschienen wird. Es han-delt sich also um das Hintergrundlicht, das die Objekte selber eigentlich abstrahlen. Wir müssen imEchtzeitrendering ein bisschen flunkern und hier einfach einen Wert angeben, da wir momentan nichtdie Rechenpower haben um jede einzelne dieser Lichtstrahlen in Echtzeit zu berechnen.

44

7.1. Lichtquellen 45

Abbildung 7.1.: Eine Szene mit Licht (links) und ohne Licht (rechts)

Abbildung 7.2.: Ein Punktlicht

7.2. Lichter einsetzen 46

Abbildung 7.3.: Screenshot aus der Licht Demo

Die Lichter, die wir hier in jME einsetzen können entsprechen aber nicht wirklich realen Lichtern, son-dern es sind einfache Lichter, die echte Lichter nur auf eine relative Weise nachahmen. Schatten könnendiese Lichte in jME beispielsweise keine erzeugen. Ausserdem werden nur ganze Dreiecke auf ein-mal beleuchtet, wie Sie auf dem Screenshot und der Demo erkennen können, sehr gut sieht man diesesPhänomen auf dem Würfel, dessen Oberflächen jeweils nur eine Farbe hat. Die modernen Grafikkartenhaben sich aber in letzter Zeit auch in diesem Punkt weiterentwickelt und stellen eine programmierbarePipeline, die sogenannte Programmable Shader Unit zur Verfügung, in der man mit speziellen Program-men, sogenannten Shader Programmen, die Lichter von Hand genauer programmieren kann. Auch jMEstelle und einen ShaderState zur Verfügung mit der wir solche Shader einsetzen können. Wie manShader einsetzen kann, können wir leider aber hier nicht erklären, da das den Rahmen sprengen würde.In der Tat gibt es schon viele Bücher allein zu diesem Thema wie zum Beispiel das Buch ProgrammingVertex and Pixel Shaders von Engel [Eng04]. Diese Shader sind momentan der neuste Trend in der Echt-zeit Grafikprogrammierung und werden erst von der neusten Generation von 3D-Grafikkarten und denallerneusten Computerspielen unterstützt.

7.2. Lichter einsetzen

Nach der kurzen theoretischen Einführung wollen wir nun sehen, wie wir Lichter in jME benutzenund einsetzen können. In der folgenden Demo haben wir zwei Lichter eingesetzt. Ein rotes parallelesLicht und ein blaues Punktlicht, das unsere Objekte beleuchtet. Die Position des blauen Punktlichteswird durch einen kleinen blauen Würfel dargestellt. Wir können die Position, dieses Lichtes interaktivverändern, schalten Sie dazu die Hilfe in der Demo ein. Ausserdem gibt es ein globales Umgebungslicht,das in der 3D-Grafik Ambient Light genannt wird. Auf dem Screenshot 7.3 sehen Sie das Endresultat.Sehen wir uns nun den Quellcode an, den Sie wie immer ganz auf der CD (siehe Anhang A) findenkönnen, im Verzeichnis demos/light mit dem Namen SimpleLight.java.

7.2. Lichter einsetzen 47

Listing 7.1: SimpleLight.java

47 protected void simpleInitGame()48 {49 display.setTitle( "Simple Light Test" );50

51 // our root node52 Node node = new Node( "objects" );53

54 // we set a point light55 pl = new PointLight();56 pl.setLocation( lightPosition );57 pl.setDiffuse( ColorRGBA.blue );58 pl.setAmbient( new ColorRGBA( 0, 0, 0.4f, 1 ) );59 pl.setEnabled( true );60

61 // set up the ambient light62 al = new AmbientLight();63 al.setDiffuse( new ColorRGBA( 1.0f, 1.0f, 1.0f, 1.0f ) );64 alValue = 0.2f;65 al.setAmbient( new ColorRGBA( alValue , alValue , alValue , 1 ) );66 al.setEnabled( true );67

68 // set up a new directional light69 dl = new DirectionalLight();70 dl.setDiffuse( ColorRGBA.red );71 dl.setAmbient( new ColorRGBA( 0.4f, 0, 0, 1 ) );72 dl.setEnabled( true );73 dl.setDirection( new Vector3f( 1, -0.5f, -0.75f ) );74 dl.setEnabled( true );75

76 // the light box shows the position of the light77 Box lightBox = new Box( "lightPosition",78 new Vector3f( -0.1f, -0.1f, -0.1f ), new Vector3f( 0.1f, 0.1f, 0.1f )

);79 lightBoxHolder = new Node( "lightBoxHolder" );80 lightBox.setSolidColor( new ColorRGBA( 0.25f, 0.25f, 1.0f, 1 ) );81 lightBoxHolder.attachChild( lightBox );82

83 lightPosition = new Vector3f( 2, 2, 1 );84 lightBoxHolder.setLocalTranslation( lightPosition );85 rootNode.attachChild( lightBoxHolder );86

87 // we create some simple geometry88 Sphere s = new Sphere( "sphere", 15, 15, 1 );89 s.setLocalTranslation( new Vector3f( 2, 0, 0 ) );90

91 Box b = new Box( "box", new Vector3f( -1, -1, -1 ),92 new Vector3f( 1, 1, 1 ) );93 b.setLocalTranslation( new Vector3f( -2, 0, 0 ) );94

95 // attach the simple geometry96 node.attachChild( s );97 node.attachChild( b );98 rootNode.attachChild( node );99

100 // the TutorialGame class already has a default light , we have todetach it

101 // to get the lighting effects we want to get

7.2. Lichter einsetzen 48

102 lightState.detachAll();103

104 // a light is always attached to a light node105 LightState ls = display.getRenderer().createLightState();106 ls.attach( pl );107 ls.attach( al );108 ls.attach( dl );109

110 // set the light state to our node we created before111 node.setRenderState( ls );112 rootNode.updateRenderState();113

114 // initialize the helper text and the keys115 assignKeys();116 initText();117 }

Auf der Zeile 52 erstellen wir eine Node namens node. An diesen Knoten werden wir den Würfel unddie Kugel anhängen und auch die Lichter einsetzen. In den Zeilen 54 bis 74 erstellen wir dann endlichunsere Lichter. Gehen wir noch genauer auf das Punkt Licht ein:

// we set a point lightpl = new PointLight();pl.setLocation( lightPosition );pl.setDiffuse( ColorRGBA.blue );pl.setAmbient( new ColorRGBA( 0, 0, 0.4f, 1 ) );pl.setEnabled( true );

Wir haben zuvor pl als privates Feld von der Klasse PointLight definiert. Wir haben im Abschnittzuvor ebenfalls erwähnt, dass jedes Positionslicht eine Position hat. Deshalb setzen wir die Position mitsetLocation. Die Farbe des Lichtes bestimmen wir mit setDiffuse. Wir geben hier dem Licht dieFarbe blau. Als nächstes setzen wir den Umgebungslichtfaktor mit setAmbient, dem wir ein dunkleresblau übergeben. Sie können in der Demo alle anderen Lichter ausschalten und falls Sie sich vom Lichtentfernen, werden Sie erkennen, dass beide Objekte noch mit einem sehr dunklen Blau zu erkennen sind.Zu guter Letzt müssen wir das Licht noch aktivieren, was wir mit dem Aufruf setEnabled( true )auch machen.Das Umgebungslicht al und das Parallellicht dl erstellen wir in den darauf folgenden Zeilen auf ähnlicheWeise. Beim Umgebungslicht al handelt es sich um ein AmbientLight, das heisst es besitzt weder einePosition noch eine Richtung sondern erleuchte unsere 3D-Szene sozusagen auf globale Art und Weise.In der Demo können Sie die Intensität dieses Umbegungslichtes mit den Tasten + und - steuern. Siewerden sehen, dass Sie damit die Helligkeit der Objekte steuern können. Das Parallellicht dl wird imenglischen und in jME selbst DirectionalLight genannt. In Gegensatz zum Punktlicht pl besitzt eskeine Position, sondern nur eine Richtungsvektor, die alle Lichtstrahlen besitzen die von diesem Lichteabgestrahlt werden.Auf den Zeilen 76 bis 85 erstellen wir einen kleinen Würfel, den wir benutzen um die Position desPunktlichtes pl anzuzeigen, das wir in der Demo bewegen können. Diese Aufrufe sollten Sie nach derLektüre der vorherigen Kapitel nun schon verstehen. Ausserdem erstellen wir in den Zeilen 87 bis 98unsere Objekte, den Würfel und die Kugel, die wir in unserem Knoten node zusammen gruppieren undanschliessend an den rootNode hängen.Die Lichter, die wir erzeugt haben müssen wir aber jetzt noch mit der Szene verbinden, das passiert inden Zeilen 100 bis 111, die wir hier noch einmal genauer anschauen wollen:

Listing 7.2: Lichter aktivieren mit LightStates100 // the TutorialGame class already has a default light , we have to

detach it101 // to get the lighting effects we want to get

7.2. Lichter einsetzen 49

102 lightState.detachAll();103

104 // a light is always attached to a light node105 LightState ls = display.getRenderer().createLightState();106 ls.attach( pl );107 ls.attach( al );108 ls.attach( dl );109

110 // set the light state to our node we created before111 node.setRenderState( ls );112 rootNode.updateRenderState();

Um Lichter in der Szene aktivieren zu können brauchen wir einen Zustand namens LightState. Alserstes müssen wir das Standardlicht, das in TutorialGame definiert wurde abschalten. Wir machen dasmit dem Aufruf von lightState.detachAll(), wie es in Zeile 102 zu sehen ist. Anschliessend müssenwir einen LightState erzeugen, was wir in Zeile 105 auch machen. An diesen LightState ls könnenwir nun alle Lichter, die wir erzeugt haben anhängen (Zeilen 106 bis 108). Als letzten Schritt müssen wirunseren LightState noch an ein Objekt verbinden. Wir setzen den LightState an den Knoten node,der schon die Kugel und den Würfel als Kinder besitzt. Wir rufen am Schluss updateRenderState auf,damit jME erkennt, das wir einen internen Zustand geändert haben.

8Texturen

8.1. Was sind Texturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508.2. Texturen einsetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508.3. Wie geht es weiter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

8.1. Was sind Texturen

Texturen sind eines der mit Abstand wichtigsten Mittel um Realismus in eine Szene zu bringen. Einfachausgedrückt können wir mit Texturen unsere Modelle tapezieren, indem wir Details, meistens in Formeines zweidimensionalen Bildes oder Fotos auf unsere Modelle zeichnen. Sehen wir uns dazu ein Bildan, denn ein Bild spricht ja bekanntlich mehr als tausend Worte. Auf dem Screenshot in Abbildung 8.1sehen Sie eine kleine Stadtszene, oben ohne und unten mit Textur. Die untere Abbildung erscheint unsauf den ersten Blick realistischer.Unter einer Textur versteht man das Bild, das wir auf unsere Modelle abbilden wollen, den Prozessan sich nennt man Texture Mapping. Als Textur kann man jedes zweidimensionale Bild verwenden. Ammeisten verbreitet sind die Formate jpg und png, die auch von jME direkt unterstützt werden. Wir könnenTexturen auch zur Laufzeit auf prozedurale Art erstellen im Kapitel 9 werden wir darauf zurückkommen.Texturen werden, wie die Modelle auch, von Grafikern erstellt. Im Internet findet man aber tausendefrei verfügbare Texturen wie zum Beispiel im Texture Warehouse[56]. Eine Textur muss aber in derRegel speziell auf ein Modell angepasst werden. Wie sie auf der Abbildung 8.2 sehen können kanneine Textur zum Teil etwas komisch aussehen. Wie eine Textur auf ein Modell geklebt wird, wird mitsogenannten u/v-Koordinaten bestimmt. Diese u/v-Koordinaten sind in den Modellen in der Regel schonvom 3D-Grafiker erstellt worden und enthalten, wir werden also hier nicht mehr weiter darauf eingehen.Es kann aber Situationen geben, in denen auch Sie als Programmierer Texturkoordinaten verändernmüssen. Jedes Buch zum Thema 3D-Grafik wird sich bestimmt auch mit u/v-Koordinaten auseinandersetzen. Schauen Sie sich ein verfügbares Buch aus der ausführlichen Bibliographie an.

8.2. Texturen einsetzen

Im folgenden Demo werden wir Ihnen zeigen, wie Sie Texturen laden und mit Modellen verbindenkönnen, das Programm sollte wie auf dem Screenshot 8.3 aussehen.

Listing 8.1: TextureDemo.java40 protected void simpleInitGame()41 {42 display.setTitle( "Texture Demo" );

50

8.2. Texturen einsetzen 51

Abbildung 8.1.: Eine Stadt mit und ohne Textur

Abbildung 8.2.: Textur vom Ferrari Modell

8.2. Texturen einsetzen 52

Abbildung 8.3.: Screenshot von der Texturen Demo

43 URL texLoc;44

45 // 1: create a new box46 Box box1 = new Box( "box1", new Vector3f( -1, -1, -1 ), new Vector3f(

1, 1, 1 ) );47

48 // 2: get a path to a texture graphic file49 texLoc = TextureDemo.class.getClassLoader().getResource(50 "data/crate.png" );51

52 // 3: create a texture state53 TextureState tsBox1 = display.getRenderer().createTextureState();54

55 // 4: load the texture from the system56 Texture texBox1 = TextureManager.loadTexture( texLoc , Texture.

MM_LINEAR_LINEAR ,57 Texture.FM_LINEAR );58

59 // 5: assign the texture to the texturestate60 tsBox1.setTexture( texBox1 );61

62 // 6: set the texture state as a new render state to our box63 box1.setRenderState( tsBox1 );64

65 // 7: attach the box to the rootNode66 rootNode.attachChild( box1 );67

68 // create another box with a texture , we make the69 // same steps as before , but with a different texture70 Box box2 = new Box( "box2", new Vector3f( -1, -1, -1 ), new Vector3f(

1, 1, 1 ) );

8.2. Texturen einsetzen 53

71 box2.setLocalTranslation( new Vector3f( -3, 0, 0 ) );72 texLoc = TextureDemo.class.getClassLoader().getResource(73 "data/xboxes01.jpg" );74 TextureState tsBox2 = display.getRenderer().createTextureState();75 Texture texBox2 = TextureManager.loadTexture( texLoc , Texture.

MM_LINEAR_LINEAR ,76 Texture.FM_LINEAR );77 tsBox2.setTexture( texBox2 );78 box2.setRenderState( tsBox2 );79 rootNode.attachChild( box2 );80

81 // create a third box with a texture , we make the82 // same steps as before , but with a different texture83 Box box3 = new Box( "box3", new Vector3f( -1, -1, -1 ), new Vector3f(

1, 1, 1 ) );84 box3.setLocalTranslation( new Vector3f( 3, 0, 0 ) );85 texLoc = TextureDemo.class.getClassLoader().getResource(86 "data/BumpyMetal.jpg" );87 TextureState tsBox3 = display.getRenderer().createTextureState();88 Texture texBox3 = TextureManager.loadTexture( texLoc , Texture.

MM_LINEAR_LINEAR ,89 Texture.FM_LINEAR );90 tsBox3.setTexture( texBox3 );91 box3.setRenderState( tsBox3 );92 rootNode.attachChild( box3 );93

94 AmbientLight al = new AmbientLight();95 al.setAmbient( new ColorRGBA( 1.0f, 1.0f, 1.0f, 1.0f ) );96 al.setEnabled( true );97 lightState.attach( al );98

99 initText();100 assignKeys();101 }

Den ganzen Quellcode und das ausführbare Programm finden Sie wie immer auf der CD (siehe AnhangA). Der Quellcode ist im Verzeichnis demos/textures in der Datei TextureDemo.java. In der Demo er-stellen wir drei Würfel, die wir mit jeweils drei verschiedenen Texturen bekleben. Obwohl der Würfeljeweils der gleiche ist, haben wir von jedem einzelnen einen völlig anderen Eindruck nur weil die Tex-turen sich unterscheiden. Der linke Würfel sieht aus, wie eine Metallbox, der mittlere Würfel scheinteine Holzkiste zu sein und im rechten Würfel sehen wir eine Wandstruktur. Die Behauptung im vorheri-gen Abschnitt, dass Texturen eines der wichtigsten Mittel sind um Realismus in eine Szene zu bringen,scheint also nicht übertrieben zu sein.Gehen wir nun unser Programm zeilenweise durch. Die Schritte, die wir machen müssen um, eine Texturzu laden und mit einer Textur zu verbinden, sind jeweils sehr ähnlich. Deshalb werden wir unseren Fokusvor allem auf den ersten Würfel legen. Den ersten Würfel mit Textur erstellen wir in den Zeilen 45 bis66. Auch im Quellcode wird jeder Schritt genau kommentiert.

1. Wir erstellen einen Würfel.

2. Wir kreieren ein URL-Objekt mit dem genauen Pfad der Grafikdatei, die wir laden wollen.

3. Ein Zustand namens TextureState wird erstellt.

4. Mit dem TextureManager laden wir unsere Textur. Der TextureManager verwaltet, dabei internunsere Textur. Mit komisch klingenden Argumente namens Texture.MM_LINEAR_LINEAR undTexture.FM_LINEAR erhalten Sie die besten Resultate. Diese Argumente bestimmen, ob wir MIP-Mapping wollen oder nicht und wie unsere Texturen gefiltert werden sollen. Auch zu diesem

8.3. Wie geht es weiter 54

Abbildung 8.4.: Beispiel für Bump-Mapping

Thema finden Sie in Real-Time Rendering [AMH02] weitere Informationen. Sie können ruhigenGewissens immer die hier genannten Argumente verwenden. Wie die Texturen aussehen, wennSie andere Argumente verwenden können Sie im Demo TextureFiltering.java sehen, das Sie aufder CD mit dem Pfad demos/textures/TextureFiltering.java finden können.

5. Als nächsten Schritt weisen wir unserem Texturen-Zustand tsBox1 die soeben geladene Texturzu.

6. Der TextureState wird nun an unseren Würfel mittels setRenderState weitergeleitet.

7. Im letzten Schritt fügen wir, wie immer, unseren Würfel dem rootNode hinzu.

Die Schritte wiederholen sich für die nächsten beiden Würfel. Das einzige, das sich jeweils ändert sinddie Namen der verschiedenen Variablen und der Name der Textur selbst, wie wir laden wollen. Über-haupt bleiben die sieben Schritte immer gleich, die wir vornehmen müssen um eine Textur zu laden. Siekönnen das auch überprüfen indem sie in Abschnitt 6.2.2 die Codezeilen anschauen, die wir benötigthaben um die Textur für unseren Ferrari zu laden.

8.3. Wie geht es weiter

Wir haben in diesem Kapitel nur die einfachsten Dinge gezeigt, die Sie mit Texturen machen können. Aufviele fortgeschrittene Anwendungen können wir aus Zeit- und Platzmangel hier leider nicht mehr ein-gehen. Ganz wichtig wäre zum Beispiel noch das Multitexturing, in dem mehrere Texturen aufeinandergereiht werden um interessante Effekte zu erzielen, vor allem auch um bestimmte Beleuchtungseffektezu simulieren. Eng dazu gehört auch das Blending (deutsch: vermischen) von Texturen und speziell dasAlpha Blending. Ein in letzter Zeit auch sehr beliebter Texture Effekt ist das Bump Mapping ein Beispieldafür ist auf der Abbildung 8.4 zu sehen. In dem eine Textur verwendet wird um die Normalenvektoreneiner Fläche zu repräsentieren.

8.3. Wie geht es weiter 55

Zu allen diesen Effekten finden Sie im Buch Real-Time Rendering [AMH02] und der dazugehörigenWebseite weitere Informationen.

9Landschaften

9.1. Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 569.2. Heightmaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 569.3. Landschaften darstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 589.4. Landschaften mit Texturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 619.5. Skybox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

9.1. Einführung

Wir haben und in den vorherigen Kapiteln die Grundlagen der 3D-Grafik erarbeitet. In diesem Kapitelgehen wir nun einen Schritt weiter. Wir haben schon früher von 3D-Szenen gesprochen. Solche Szenensind aber ziemlich langweilig, wenn sie nur aus ein paar Würfeln und einzelnen Modellen bestehen. Umeine realistische 3D-Szene zu erhalten brauchen wir auch eine realistische Umwelt. Deshalb werden wirund in diesem Kapitel mit dem Rendern von Landschaften befassen. Das Rendern von Landschaftenwird üblicherweise mit dem englischen Begriff Terrain Rendering bezeichnet.Das Terrain Rendering ist natürlich nicht nur für Spiele wichtig, sondern hat auch eine Vielzahl von“ernsthaften” Anwendungen, wie militärische Simulationen, Wettersimulationen und noch vieles mehr.Auf der Website Virtual Terrain Project [60] finden Sie viele Informationen dazu. Wie Sie sehen das Vi-sualisieren und Rendern von Landschaften ist ein wichtiges Forschungsgebiet aus verschiedenen Grün-den. Die Landschaften müssen realistisch wirken und genügend schnell gerendert werden damit einegute Framerate erzeugt wird. In den letzten Jahren wurden deshalb eine Vielzahl von Algorithmen ent-wickelt, die diese Ziele erfüllen. Diese Algorithmen werden zum Teil von vielen modernen Spielenimplentiert, was zu eindrucksvollen Landschaften führt, wie Sie auf dem Screenshot in Abbildung 9.1selbst sehen können. Leider können wir uns in dieser Einführung nur auf simple Methoden beschrän-ken. Eine tiefergehende Einführung können Sie aber zum Beispiel aus dem Buch Focus on 3D TerrainProgramming [Pol02] entnehmen. Ausserdem werden auf einer Unterseite von VTerrain [60] viele derzum Teil sehr aktuellen Artikel und Papers zum Thema gesammelt.

9.2. Heightmaps

Wie wir bereits mehrmals erwähnt haben besteht in der Echtzeit 3D-Grafik alles aus Dreiecken. Dasist auch beim Terrain Rendering nicht anders. Um eine Landschaft darzustellen brauchen wir also eineReihe von Dreiecken. Die einfachste Landschaft, die man sich vorstellen kann ist ausserdem eine ebeneFläche. Eine einfache 3D-Landschaft ist also nicht anderes als eine Fläche mit einigen Dreiecken, wieauf dem Screenshot 9.2 zu sehen.

56

9.2. Heightmaps 57

Abbildung 9.1.: Screenshot aus Oblivion von Bethesda Softworks

Abbildung 9.2.: Eine sehr einfache 3D-Landschaft

9.3. Landschaften darstellen 58

Abbildung 9.3.: Ein Beispiel für eine Heightmap

Zugegeben, das ist eine ziemlich langweilige Landschaft und lädt nicht so wirklich zum verweilen ein,es fehlt einfach irgend etwas. Was am allermeisten fehlt, sind einige Hügelzüge und Höhenunterschiede.Das lässt sich realisieren indem wir jedem einzelnen Punkt unserer flachen Ebene eine individuelle Hö-he geben. Von Hand wäre dieser Vorgang ziemlich mühsam. Aber glücklicherweise gibt es eine weitauseinfachere Methode, nämlich sogenannte Heightmaps. Eine Heightmap ist ein Graustufenbild, ein Bei-spiel könne Sie auf der Abbildung 9.3 sehen. Jede einzelne Graustufe wird nun eine Zahl zwischen 0und 255 zugeordnet. Weiss entspricht der Zahl 255 und Schwarz der Zahl 0. Diese Zahlen entsprechenausserdem einer Höhe. Wenn wir nun diese Heightmap auf unsere Ebene anwenden erhalten wir eineweitaus realistischere Landschaft, sehen sie dazu Abbildung 9.4 an.Je heller ein Abschnitt auf der Heightmap ist, desto höher ist dieser Teil auf der gerenderten Landschaft.Je dunkler die Heightmap, desto tiefer das Tal in der Landschaft. Noch ein weiteres Wort zu Heightmaps.Heightmaps werden in der Regel als raw Dateien gespeichert. Das bedeutet, dass die Höhenwerte direktohne Semantik und weitere Angaben in der Datei gespeichert sind. Es handelt ganz genau genommennicht um Bilddateien. Man kann aber solche raw-Dateien mit den meisten üblichen Bildbearbeitungs-programmen öffnen, wie zum Beispiel Paint Shop Pro[48].

9.3. Landschaften darstellen

Wir wollen nun das oben erworbene Wissen anwenden. In der folgenden Demo werden wir eine Land-schaft generieren mit dem oben genannten Heightmap. Wie in den meisten Demos beschränken wir undhier auf die Methode simpleInitGame. Den gesamten Quellcode und das ausführbare Programm findenauf der CD (siehe Anhang A) im Verzeichnis demos/terrain mit dem Namen TerrainDemo.java.

Listing 9.1: TerrainDemo.java41 protected void simpleInitGame()42 {43 // set a new title to our application44 display.setTitle( "Terrain Demo" );45

9.3. Landschaften darstellen 59

Abbildung 9.4.: Eine Landschaft generiert aus der Heightmap aus Abbildung 9.3

Abbildung 9.5.: Screenshot aus TerrainDemo.java

9.3. Landschaften darstellen 60

46 // load the heightmap47 URL loc = Terrain.class.getClassLoader().getResource( "data/heightmap64

.raw" );48 AbstractHeightMap heightMap = new RawHeightMap( loc.getPath(), 64 );49

50 // create a TerrainBlock out of the heightmap51 Vector3f terrainScale = new Vector3f( 4.0f, 0.4f, 4.0f );52 TerrainBlock tb = new TerrainBlock( "terrain", heightMap.getSize(),53 terrainScale , heightMap.getHeightMap(), new Vector3f( 0, 0, 0 ),

false );54

55 // with backface culling we reduce the number of vertices to draw56 CullState cs = display.getRenderer().createCullState();57 cs.setCullMode( CullState.CS_BACK );58 cs.setEnabled( true );59 rootNode.setRenderState( cs );60

61 // attach the terrainblock to the scenegraph62 rootNode.attachChild( tb );63

64 // set the camera to a new location65 cam.setLocation( new Vector3f( 100, 60, 100 ) );66

67 // disable the default light and create a new directional light68 // to simulate sunrays69 lightState.detachAll();70 DirectionalLight light = new DirectionalLight();71 light.setDirection( new Vector3f( 0.6f, -0.5f, 0.5f ) );72 float intensity = 1.0f;73 light.setDiffuse( new ColorRGBA( intensity , intensity , intensity , 1.0f

) );74 light.setAmbient( new ColorRGBA( intensity , intensity , intensity , 1.0f

) );75 light.setEnabled( true );76 lightState.attach( light );77 rootNode.updateRenderState();78

79 // initialize the text system and the key inputs80 initText();81 assignKeys();82 }

In den Zeilen 47 und 48 laden wir die Heightmap namens heightmap64.raw. Der Name kommt daher,da es sich um eine 64 mal 64 Punkte grosse Heightmap handelt. Wir speichern die Heightmap als einAbstractHeightMap Objekt. Wir können nämlich Heightmaps auch direkt aus Bildern laden oder siesogar zur Laufzeit mit einem Algorithmus auf dynamische Art erzeugen. Die Klassen, die diese Funk-tionalität zur Verfügung stellen heissen MidPointHeightMap, ParticleDepositionHeightMap undFaultFractalHeightMap, die Details dazu können Sie in der Javadoc von jME nachschlagen. Falls Siealso unsere Landschaft langweilt, können Sie auf diese Weise neue Terrains erstellen.Wir erstellen nun mit unserer Heightmap ein TerrainBlock Objekt, wie Sie in den Zeilen 51 bis 53sehen können. TerrainBlock ist eine Unterklasse von Geometry, die wir ja bekanntlich alle an unserenScenegraphen anhängen können. Der Konstruktor von TerrainBlock erwartet einige Argumente, unteranderem die Länge und Breite von unserer Heightmap. Ausserdem können wir noch einen Vector3fmitgeben, der die Skalierung der Landschaft angibt. Diesen Skalierungsvektor haben wir in Zeile 51 mitdem Namen terrainScale erstellt. Wir geben in der x- und in der z-Achse an, dass wir unsere Ter-rain 4 mal vergrössern wollen, das beeinflusst die Länge und die Breite unseres sichtbaren Terrains. Inder y-Achse bestimmen wir den Faktor 0.4. Das heisst, dass wir die Höhenunterschiede etwas vermin-

9.4. Landschaften mit Texturen 61

Abbildung 9.6.: Unsere Landschaft mit einigen saftigen, grünen Hügeln

dern wollen. Sie können diese Faktoren durchaus verändern um zum Beispiel eine bergige mit grossenHöhenunterschieden zu erstellen.In den Zeilen 56 bis 59 schalten wir das sogenannte Backface Culling ein. Damit reduzieren wir dieAnzahl der zu rendernden Dreicke auf dem Bildschirm. Wie der Name Backface schon ausdrückt, eli-minieren wir auf diese Weise alle rückseitigen Dreiecke. Genau genommen handelt es sich dabei aufdie Rückseite von unserem Terrain, das wir in der Regel ja sowieso nur von oben sehen wollen. Siekönnen mit der Kamera versuchen unter das Terrain zu steuern und nach oben schauen, Sie werden be-merken, dass Sie von unten fast nichts mehr von unserer Landschaft sehen. Wir schalten in dieser Demodas Backface Culling aus Perfomancegründen ein. Wir könnten sonst unseren Rechner mit der hohenAnzahl von Dreiecken in unserer Landschaft durchaus in die Knie zwingen.In Zeile 65 verstellen wir ausserdem die Position von unserer Kamera, damit wir mittendrin in der Actionsind und uns nicht vom Rand noch mühsam in die Mitte von unserer Landschaft kämpfen müssen.In den Zeilen 69 bis 77 schalten wir zu guter Letzt noch unsere Standardmässiges PointLight ausTutorialGame aus und erstellen ein DirectionalLight um auf simple Art und Weise die Sonne zusimulieren. Beachten Sie ausserdem, dass die zu sehenden Graustufen auf der Landschaft nicht vonunserer Landschaft stammen, sondern eine Folge der eingesetzten Lichter sind.

9.4. Landschaften mit Texturen

Wenn wir uns noch einmal die oben gezeigten Abbildungen ansehen, fällt auf, dass etwas fehlt. DieLandschaften sehen zwar schon aus, sie wirken aber etwas fade mit ihren Grautönen, fast so als ob wirauf dem Mond einen Spaziergang machen würden. Fast fehlt sind einfache saftige, grüne Wiesen. Diesewerden wir nun in der folgenden Demo hinzufügen. Auf der Abbildung 9.6 sehen Sie unsere neue, vielrealistischere Landschaft. Sie ist zwar noch nicht perfekt, aber fast.Hier sehen Sie die Codezeilen, die wir unserer vorherigen Demo hinzugefügt haben, die beiden Demossind ansonsten gleich. Wiederum ist der ganze Quellcode auf der CD zu finden. Der genaue Pfad laute:demos/terrain/TerrainWithTexture.java.

9.4. Landschaften mit Texturen 62

Abbildung 9.7.: Rechts die Grastextur, links die Detailtextur

Listing 9.2: TerrainWithTexture.java

59 // create a texture state60 TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().

createTextureState();61

62 // add the grass texture63 URL grass = Terrain.class.getClassLoader().getResource(64 "data/tr_grass.bmp" );65 Texture texGrass = TextureManager.loadTexture( grass ,66 Texture.MM_LINEAR , Texture.FM_LINEAR );67 texGrass.setWrap( Texture.WM_WRAP_S_WRAP_T );68 ts.setTexture( texGrass );69

70 // add the detail texture71 URL detail = Terrain.class.getClassLoader().getResource(72 "data/tr_detail.bmp" );73 Texture texDetail = TextureManager.loadTexture( detail ,74 Texture.MM_LINEAR , Texture.FM_LINEAR );75 texDetail.setWrap( Texture.WM_WRAP_S_WRAP_T );76 ts.setTexture( texDetail , 1 );77

78 // set the number of texture repeats79 tb.setDetailTexture( 0, 10 );80 tb.setDetailTexture( 1, 4 );81 tb.setRenderState( ts );

Wir haben für das Terrain zwei Texturen benutzt zum eine grüne Grastextur und als zweite Textur einehelle, sogenannte Detailtextur. Die Texturen sind auf der Abbildung 9.7 zu sehen. In den oben abge-druckten Zeilen laden wir die beiden Texturen und setzen sie dem gleichen TextureState zu. Diegrundlegende Vorgehensweise bleibt dabei gleich wie wir im Kapitel 8.2 “Texturen einsetzen” beschrie-ben haben.Auf Zeile 76 können Sie vielleicht sehen, dass wir beim Aufruf von setTexture noch ein weiteresArgument, nämlich eine 1 mitgeben, im Gegensatz zu allen anderen Beispielen. Die 1 gibt an welcheTextureinheit wir benutzen wollen. Für jeden Texturplatz stellen uns moderne 3D-Karten mehrere Tex-tureinheiten bereit, die wir mit unterschiedlichen Texturen belegen können. Im Normalfall benutzen wirnur eine Textureinheit, die standardmässig die Nummer 0 trägt, und die wir auch nicht speziell angeben

9.5. Skybox 63

müssen wenn wir ein Bild als Textur setzen. Wenn wir nun mehrere Textureinheiten benutzen könnenwir verschiedene Effekte erzielen, indem wir unsere verschiedenen Texturen miteinander verknüpfen.In diesem Falle, benutzen wir die Standardeinstellung, wo die Farben der beiden Texturen miteinanderaddiert werden, was eine neue Farbe ergibt. Wir machen das ganze, weil eine einzige Textur auf unse-rem Terrain verteilt ziemlich langweilig aussieht und man die Wiederholungen, die wir auch verwendenziemlich schnell erkennt.Neu hinzu kommen unter anderem auch die Zeilen 67 und 75, wo wir den sogenannten Wrap Modeunseres Textur auf Texture.WM_WRAP_S_WRAP_T setzen. Dieser Wrap Mode setzt fest was, wir machenwollen wenn wir unsere Textur mehrmals auf unserem Objekt verwenden wollen. Mit diesem Moduslegen wir fest, dass wir unsere Textur kacheln wollen, also das sie einfache mehrere Male nebeneinandergereiht werden soll.Als letzte Neuerung setzen wir in Zeile 79 und 80 den Wiederholungswert unserer Texturen. Wir habendie Grastextur standardmässig auf Textureinheit 0 gestellt und die Detailtextur auf die Textureinheit 1.Hier geben wir nun an, dass wir unsere Grastextur zehn mal und unsere Detailtextur vier mal auf unsererLandschaft wiederholen möchten.

9.5. Skybox

Wenn Sie zurück blicken auf das letzte Kapitel, in dem wir eine texturierte Landschaft erstellt, haben,werden Sie immer noch zweifeln, ob diese Landschaft tatsächlich in der Realität vorkommen kann,nehme ich an. Was besonders stört, ist der schwarze Hintergrund. Wenn wir draussen umschauen, habenwir immer einen Horizont um uns und sehen die Sonne, den Himmel, falls wir auf dem Land wohnenvielleicht sogar einige Berge und sonstige Hügellandschaften.Im 3D-Rendering gibt es nun einige Tricks, wie wir einen Horizont simulieren können. Einen der ein-fachsten dieser Tricks nennt man Skybox und wir direkt von jME zur Verfügung gestellt. Die Idee hintereiner Skybox ist sehr simpel. Die entfernte Landschaft wird auf sechs Texturen aufgeteilt, vier für jedeHimmelsrichtung und je eine Textur für oben und unten. Diese sechs Texturen werden auf die Innenseiteeines Würfels geklebt. Die Kamera, also wir selbst in der Demo befinden uns in der Mitte dieses Würfels.Wir können uns frei rotieren, unsere Position bleibt aber immer fix in der Mitte des Würfels. Wenn dieSzene gerendert wird erzeugen die Texturen, die auf den Würfel projiziert werden, den Eindruck voneinem Horizont.Auf der Abbildung 9.8 können Sie die Bilder sehen die wir als Texturen verwenden werden. Wie Siesehen, passen die Ränder nahtlos aufeinander. Wenn Sie das Bild ausschneiden würden, könnten Sieausserdem daraus einen Würfel basteln. Dieses Bild wird später in sechs Teilbilder geschnitten, was diesechs oben genannten Texturen gibt. Diese Bilder werden in der Regel von speziellen 3D-Anwendungenerzeugt wie zum Beispiel Terragen[55]. Sie können aber im Internet schon viele freie solche SkyboxBilder finden, zum Beispiel auf der Seite Octane Digital Studios[45], von der auch das hier verwendeteBild stammt.In der folgenden Demo wollen wir zeigen, wie Sie eine Skybox in jME erstellen können. Wir werden indieser Demo nicht mehr die ganze Arbeit in unserer simpleInitGame Methode machen. Wir haben eineeigene Skybox Klasse erzeugt, die wir in unserer Demo Klasse SkyBoxTest verwenden werden. Aufdiese Weise können wir die SkyBox Klasse direkt in unserer Enddemo FinalIsland wieder verwenden.Wir fangen hier mit dem Quellcode von SkyBoxTest an. Den ganzen Quellcode finden Sie auf der CD(siehe Anhang A) im Verzeichnis demos/specialfx in der Datei mit dem Namen SkyBoxTest.java.

Listing 9.3: SkyBoxTest.java36 protected void simpleInitGame()37 {38 // set the title of our application39 display.setTitle( "SkyBox test" );40

41 // create our skybox

9.5. Skybox 64

Abbildung 9.8.: Würfelbild mit der Skybox

Abbildung 9.9.: Screenshot von der Skybox Demo

9.5. Skybox 65

42 sb = new SkyBox( rootNode , cam.getLocation() );43

44 // create an additional box in our scene45 Box b = new Box( "box", new Vector3f( -1, -1, -1 ), new Vector3f( 1, 1,

1 ) );46 rootNode.attachChild( b );47

48 initText();49 assignKeys();50 }51

52 /**53 * this method is called every frame to eventually update all things54 */55 protected void simpleUpdate()56 {57 sb.update();58 checkInput();59 }

Das einzig neue das in dieser Demo erscheint, ist die Instanzierung einer Skybox auf Zeile 42. Wirhaben das Feld sb in der Klasse als private SkyBox sb definiert. Die Klasse benötigt einen Nodeals Argument und zusätzlich die Position der Kamera. Wie wir ja bereits erwähnt haben, wird sich dieSkyBox immer mit der Kamera bewegen, was wir damit bewerkstelligen wollen. In dieser Demo müssenwir zusätzlich noch die Methode simpleUpdate verwenden, um die SkyBox in jedem Frame bewegenzu können.Somit haben wir unsere Demo erstellt. Schauen wir uns nun die Klasse SkyBox an. Diese Klasse istebenfalls auf der CD im Verzeichnis demos/specialfx zu finden. Sie trägt den Namen SkyBox.java.

Listing 9.4: SkyBox.java1 package specialfx;2

3 import java.net.URL;4

5 import com.jme.image.Texture;6 import com.jme.math.Vector3f;7 import com.jme.scene.Node;8 import com.jme.scene.Skybox;9 import com.jme.util.TextureManager;

10

11 /**12 * @author Daniel Egger , [email protected] *14 * this is a skybox helper class15 */16 public class SkyBox17 {18 // A sky box for our scene19 private Skybox skybox;20

21 private Vector3f camLocation;22

23 /**24 * a helper class to create our skybox25 * @param node we will attach our skybox to this node26 * @param camLocation we want to track the camera so we store its

location27 */

9.5. Skybox 66

28 public SkyBox( Node node , Vector3f camLocation )29 {30 // we have to store the camera location31 this.camLocation = camLocation;32

33 // initialize the skybox34 skybox = new Skybox( "skybox", 500, 500, 500 );35

36 // load all the 6 side textures for our skybox37 URL texturelocation;38

39 // load the west side of the skybox40 texturelocation = SkyBoxTest.class.getClassLoader().getResource(41 "data/blue_right.jpg");42 skybox.setTexture( Skybox.WEST , TextureManager.loadTexture(

texturelocation ,43 Texture.MM_LINEAR , Texture.FM_LINEAR ) );44

45 // load the east side of the skybox46 texturelocation = SkyBoxTest.class.getClassLoader().getResource(47 "data/blue_left.jpg");48 skybox.setTexture( Skybox.EAST , TextureManager.loadTexture(

texturelocation ,49 Texture.MM_LINEAR , Texture.FM_LINEAR ) );50

51 // load the upper side52 texturelocation = SkyBoxTest.class.getClassLoader().getResource(53 "data/blue_up.jpg");54 skybox.setTexture( Skybox.UP, TextureManager.loadTexture(

texturelocation ,55 Texture.MM_LINEAR , Texture.FM_LINEAR ) );56

57 // load the down side58 texturelocation = SkyBoxTest.class.getClassLoader().getResource(59 "data/blue_down.jpg");60 skybox.setTexture( Skybox.DOWN , TextureManager.loadTexture(

texturelocation ,61 Texture.MM_LINEAR , Texture.FM_LINEAR ) );62

63 // load the north side64 texturelocation = SkyBoxTest.class.getClassLoader().getResource(65 "data/blue_front.jpg");66 skybox.setTexture( Skybox.NORTH , TextureManager.loadTexture(

texturelocation ,67 Texture.MM_LINEAR , Texture.FM_LINEAR ) );68

69 // load the south side70 texturelocation = SkyBoxTest.class.getClassLoader().getResource(71 "data/blue_back.jpg");72 skybox.setTexture( Skybox.SOUTH , TextureManager.loadTexture(

texturelocation ,73 Texture.MM_LINEAR , Texture.FM_LINEAR ) );74

75 node.attachChild( skybox );76 }77

78 public void update()79 {

9.5. Skybox 67

80 // we have to move our skybox according to the camera location81 skybox.setLocalTranslation( camLocation );82 }83 }

Die Klasse ist sehr einfach gehalten und bedarf kaum einer Erklärung. Wir laden unsere sechs Texturenund weisen sie jeweils einer Richtung in der Skybox zu. Wie gesagt stellt uns jME bereits eine Skyboxzur Verfügung, die wir auch verwenden. Wir müssen in der Methode update die Skybox jeweils neu mitder Kamera verschieben, damit die Kamera auch immer genau im Zentrum bleibt. Und das ist schon dieganze Hexerei.Bei der Skybox handelt es sich eigentlich um einen Trick Horizonte darzustellen. Sie haben vielleichtbereits erkannt, das der Horizont in dieser Technik fast immer statisch ist. Das kann störend wirken,zum Beispiel bewegen sich die Wolken nicht und auch der Sonnenstand bleibt immer gleich. Es gibteine Vielzahl von weiteren Techniken um realistische Horizonte und Wolken darzustellen, wie zumBeispiel das Rendern von Skydomes. Im Buch [Pol02] und auf der Webseite [60] können Sie weiterInformationen dazu finden.

10Final Island

10.1. Der Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6810.2. Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

10.1. Der Code

Wir kommen nun zum letzten Abschnitt in unserem Tutorial. Wir haben nun genügend Grundlagen ge-lernt und können alle zusammen in einer kleinen Demo anwenden. In unserer Abschlussdemo könnenwir auf einer kleinen Insel laufen, wir können uns nicht mehr frei umher bewegen, wie in den vorherigenDemos. Ausserdem werden wir im Wasser nur langsam vorwärts kommen und können auch nicht tau-chen. Auf den Abbildungen 10.1 und 10.2 sehen Sie zwei Screenshots von unserer Final Island Demo.Wir verwenden in dieser Demo alles, was wir in den vorherigen Kapiteln gelernt haben. Wir habe eineschon texturierte Landschaft mit einem Horizont, den wir als Skybox implementiert haben. Das Licht,das einer Sonne empfunden ist scheint aus einem eher tieferen Winkel, was einer schönen Abendsonneentspricht.Die meisten Element der Demo sind in eigenen Klassen definiert, wie Sie später noch sehen werden.Die Demo finden Sie im Unterordner demos/finalisland in der Datei FinalIsland.java auf der CD (sieheAnhang A). Im Listing 10.1 ist der ganze Quellcode abgedruckt:

Listing 10.1: FinalIsland.java1 package finalisland;2

3 import helper.TutorialGame;4

5 import com.jme.light.DirectionalLight;6 import com.jme.math.Vector3f;7 import com.jme.renderer.ColorRGBA;8 import com.jme.scene.Node;9 import com.jme.scene.state.LightState;

10

11 /**12 * The Final Island , a simple tech demo of what we learned13 * @author Daniel Egger14 */15

16 public class FinalIsland extends TutorialGame17 {18 // the helper classes from the same package19 private SkyBox skybox;

68

10.1. Der Code 69

Abbildung 10.1.: Final Island: Blick auf das Wasser

Abbildung 10.2.: Final Island: Blick auf das Jeep-Modell

10.1. Der Code 70

20 private Terrain terrain;21 private Water water;22 private Jeep jeep;23

24 public static void main( String[] args )25 {26 new FinalIsland();27 }28

29 protected void simpleInitGame()30 {31 display.setTitle( "Final Island" );32

33 // set up the scene graph34 Node terrainScene = new Node( "terrainScene" );35

36 // create the skybox37 skybox = new SkyBox( terrainScene , cam.getLocation() );38

39 // create the terrain40 terrain = new Terrain( terrainScene );41

42 // create the water43 water = new Water( terrainScene , 39.7147f );44

45 // create a jeep46 jeep = new Jeep( terrainScene );47 jeep.setPosition( new Vector3f( 167, terrain.getHeightAt( 167, 190 ),

190 ) );48

49 // simulate a sun50 LightState ls = display.getRenderer().createLightState();51 DirectionalLight light = new DirectionalLight();52 light.setDirection( new Vector3f( 0.6f, -0.75f, 0.6f ) );53 float intensity = 1.0f;54 light.setDiffuse( new ColorRGBA( intensity , intensity , intensity , 1.0f

) );55 light.setAmbient( new ColorRGBA( intensity , intensity , intensity , 1.0f

) );56 light.setEnabled( true );57 ls.attach( light );58 terrainScene.setRenderState( ls );59

60 // switch of the standard light61 lightState.detachAll();62

63 // attach the whole scene and update the render states64 rootNode.attachChild( terrainScene );65 rootNode.updateRenderState();66

67 // move the camera to the middle of the island68 cam.setLocation( new Vector3f( 150, 80, 180 ) );69 }70

71 protected void simpleUpdate()72 {73 Vector3f vec = cam.getLocation();74

10.2. Fazit 71

75 // check that we cannot leave the terrain76 // terrain has a size of about 252 * 252 units77 if( vec.x < 2 ) vec.x = 2;78 if( vec.x > 250 ) vec.x = 250;79 if( vec.z < 2 ) vec.z = 2;80 if( vec.z > 250 ) vec.z = 250;81

82 // set our camera height to the terrain heigth83 // to simulate moving on the terrain84 vec.y = terrain.getHeightAt( vec.x, vec.z ) + 2;85

86 // check that we do not go into the water87 float waterHeight = water.getHeight() + 1.0f;88 if( vec.y < waterHeight )89 {90 vec.y = waterHeight;91 input.setKeySpeed( 5 );92 }93 else94 {95 input.setKeySpeed( 10 );96 }97

98 // update the water height99 water.update( tpf );

100

101 // update the skybox102 skybox.update();103 }104 }

Wir deklarieren als erstes einige Hilfsobjekte in den Zeilen 18 bis 22. Diese Hilfsobjekte haben wirin eigenen Klassen implementiert, die Sie im gleichen Unterordner demos/finalisland finden können.Die Klassen heissen SkyBox.java, Terrain.java, Water.java und Jeep.java. Diese Klassen implementierengenau das, war wir in der vorherigen Kapiteln gelernt haben. Wir werden Sie also nicht noch einmal hierwiedergeben.

Sehen wir uns zuerst die allseits bekannte und beliebte Methode simpleInitGame an. In den Zeilen 33bis 47 initialisieren wir diese Objekte. Wir geben jeweils den Node terrainScene als Argument an.Unsere Objekte werden dann ihrerseits alles an terrainScene anhängen. Je nach Objekt müssen wirnoch weiter Argumente übergeben, was diese aber für eine Bedeutung haben, sehen Sie am besten selbstnach im Quellcode, der jeweiligen Objekte.

Als nächstes definieren wir eine Parallellicht in den Zeilen 49 bis 58. Das Licht soll auf einfache Weiseeiner Sonne entsprechen. Wir haben schon in den vorherigen Kapiteln, das genau gleiche gemacht.Deshalb gehen wir hier nicht weiter darauf ein.

Das einzige neue, das wir hier in FinalIsland machen, finden Sie in der Methode simpleUpdate. Wirwollen hier überprüfen, dass wir den Spielfeldrand nicht überschreiten und auch nicht in das Wasserabtauchen können. Die Landschaft erstreckt sich auf 255 * 255 Einheiten. Wir müssen also überprüfen,dass unsere x- und z-Koordinate in den Grenzen 2 bis 250 bleiben, damit wir uns zu jeder Zeit aufdem Terrain befinden. Genau diese Überprüfung nehmen wir in den Zeilen 75 bis 80 vor. Falls wir dieGrenzen der erlaubten Zahlen verlassen, setzen wir die Position der Kamera einfach auf den erlaubtenMinimal- bzw. Maximalwert. In der Zeile 84 rufen wir die Höhe des Terrains ab auf dem wir stehen undsetzen die Kamera um zwei Einheiten höher auf.

10.2. Fazit 72

Abbildung 10.3.: Beispiel für eine Particle Engine

10.2. Fazit

Wir haben zusammen in vielen kleinen Schritten, die Grundlagen der 3D-Grafik mit Hilfe der 3D-EnginejME gelernt und trotzdem haben wir bis jetzt nur die Spitze des Eisbergs gesehen. Wir haben viele Mög-lichkeiten von jME noch gar nicht richtig ausgeschöpft. Mit keinem Wort haben wir beispielsweise Par-ticle Engines (siehe Abbildung 10.3 bedacht, mit denen man einige wunderbare Effekte erzielen kann.Ich empfehle Ihnen nun die Demos, die zusammen mit jME ausgeliefert werden genauer zu studieren.Auf der Seite von jME gibt es ausserdem noch weitere Dokumentation. Dort finden Sie auch die pdf-Datei Learning jME [Lin] von Jack Lindamood, das zum Teil die gleichen Themen, wie das vorliegendeTutorial behandelt, aber von dem Sie bestimmt noch einiges lernen können.So viel zum grafischen Teil des Tutorials. Was wir nun sträflich vernachlässigt haben sind Dinge wie dieKollisionserkennung und auch Animationen. Dabei handelt es sich um ziemlich komplexe Teilgebietemit denen man auch Bücher oder weitere Bachelorarbeiten füllen könnte und dementsprechend ausPlatzmangel und Zeitgründen keinen Platz in diesem Tutorial fanden. Wer sich mit diesem Bereich derProgrammierung noch weiter beschäftigen möchte sollte einen Blick auf das jME Physics System[35]werfen. Diese Library versucht unsere Welt in physikalischer Hinsicht möglichst genau nachzubilden.Es lohnt sich ausserdem immer mal wieder die Webseite von jME[36] zu konsultieren, weil es immerwieder interressante Links auf Projekte gibt und auch die Dokumentation ständig verbessert und weiteraufgebaut wird.

ACD-ROM

Inhalt der mitgelieferten CD-ROM:

• Im Verzeichnis dateien sind weiter Dokumente aufgeführt, die für Sie nützlich sein können.

• Den Quellcode und ausführbare Klassen aller Demos finden Sie im Unterordner demos. Die Klas-sen sind jeweils im Unterordner mit dem jeweiligen Paketnamen zu finden. Ausserdem findenSie im Ordner demos batch-Dateien und shell-Scripts um die jeweiligen Demos gleich starten zukönnen. In den jeweiligen Kapiteln wird jeweils erwähnt, wie das Unterverzeichnis heisst, das dieDemos enthält. Falls Sie interessiert sind die Demos selbst zu verändern finden Sie in Anhang Beine Anleitung, wie Sie die Demos selbst kompilieren können.

• Im Unterordner dokumentation finden Sie eine Kopie dieses Dokumentes im pdf-Format und denQuellcode dieses Dokumentes im LATEX-Format.

• Der Unterodner finalisland_directx schlussendlich enthält die Demo des Gameversity-Kurses

73

74

|-- dateien| |-- Learning_jME.pdf| |-- gameinstitute| ‘-- gameversity|-- demos| |-- data| |-- finalisland| |-- firststeps| |-- geometry| |-- helper| |-- keyinput| |-- lib| |-- light| |-- specialfx| |-- terrain| |-- textures| ‘-- theory|-- dokumentation| ‘-- source‘-- finalisland_directx

Abbildung A.1.: CD-Rom Inhalt

BEin jME-Demo in der Konsole

starten und kompilieren

B.1. Demos starten

Es ist nicht ganz einfach eine jME-Demo in der Konsole zu starten. Die Bibliothek jME ist in mehre-re Dateien aufgeteilt, ausserdem wird mit dem OpenGL-Wrapper LWJGL eine Systemeigene Libraryverwendet. Das müssen wir Java speziell mitteilen. Sehen wir us am besten ein Beispiel an. Um dieDemo HelloWorld.java zu starten, müssen wir unter Windows folgende Eingabe machen im Verzeichnisdemos:

java -Djava.library.path=./lib -cp ./lib/lwjgl.jar;./lib/jme.jar;firststeps/HelloWorld

Mit dem Argument -Djava.library.path=./lib geben wir an, dass das jeweilige Betriebssystemin diesem Unterverzeichnis nach Betriebseigenen Bibliotheken suchen soll. jME benötigt in Windowsunter anderem die Datei lwjgl.dll die in diesem Verzeichnis zu finden ist. Entsprechende Dateienfür Linux und MacOSX sind in diesem Ordner ebenfalls vorhanden. Mit der Argument -cp geben wirdie einzelnen jar-Dateien an, die wir benötigen. Für ein jME-Demo brauchen wir immer zumindest dieDateien lwjgl.jar und jme.jar. Bei mehreren jar-Dateien müssen diese unter Windows immer miteinem Semikolon abgetrennt werden. Unter Linux und MacOSX benötigt man einen Doppelpunkt alsAbtrenner. Der Befehl um eine Demo zu starten lautet unter Linux und MacOSX also folgendermassen:

java -Djava.library.path=./lib -cp ./lib/lwjgl.jar:./lib/jme.jar:firststeps/HelloWorld

Wie in den vorherigen Kapiteln im Text bereits erwähnt, finden Sie im Verzeichnis demos jeweils batch-Dateien und Shell-Skripts, die diese Befehle bereits enthalten.

B.2. Demos kompilieren

Ähnlich müssen wir vorgehen wenn wir eine Demo kompilieren wollen. Es reicht wenn wir in diesemFalle den Classpath anpassen. Wenn wir also die Demo HelloWorld.java kompilieren wollen, müssenwir unter Windows folgenden Befehl eingeben:

javac -cp ./lib/lwjgl.jar;./lib/jme.jar; firststeps/HelloWorld.java

Achten Sie wiederum darauf, dass Sie sich mit der Kommandozeile im Verzeichnis demos befinden. Umeine Demo zu kompilieren müssen Sie natürlich den ganzen Ordner von der CD zuerst auf die lokaleFestplatte kopiert haben.Unter Linux und MacOSX müssen Sie wiederum die Semikolons durch Doppelpunkte ersetzen, hier einBeispiel:

75

B.2. Demos kompilieren 76

javac -cp ./lib/lwjgl.jar:./lib/jme.jar: firststeps/HelloWorld.java

Das ganze Vorgehen kann in der Kommandozeile ziemlich mühsam werden. Auf der Seite von [36]finden Sie eine ausführliche Anleitung, wie Sie ein jME-Projekt in Eclipse[22] aufsetzen können.

CVerwendete Software

C.1. Entwicklung

C.1.1. Java

Die mitgelieferten jME-Demos und jME selbst wurden mit Java 1.4[32] entwickelt. Es wurde diese JavaVersion ausgewählt, das Sie zum Startzeitpunkt auf allen drei benutzten Betriebssystemen, WindowsXP,Linux und MacOSX eine lauffähige Version existiert.

C.1.2. Eclipse

Als Entwicklungsumgebung wurde die freie OpenSource IDE Eclipse[22] ausgewählt. Alle Demos wur-den in Eclipse implementiert. Eclipse wurde unter anderem ausgewählt, weil es unter allen benutztenBetriebssystemen einwandfrei läuft. Ausserdem war der Portieraufwand mit Eclipse minimal. Auf derWebsite von jME[36] wird zusätzlich ausführlich erklärt, wie Sie mit Eclipse eine neue Version vonjME direkt mit dem Versionskontrollsystem CVS[18] beziehen können. Diese Vorgehen wir auch vonden Autoren von jME vorgeschlagen, da sich jME immer noch in der Entwicklung befindet.

C.2. Dokumentation

C.2.1. LATEX und Co

Der grösste Teil der Dokumentation wurde mit dem Programm LyX[41] geschrieben. LyX ist ein grafi-sches Frontend für das Textsatzsystem LATEX[39], das nach dem WYSIWYM1-System arbeitet im Ge-gensatz zu herkömmlichen Textverarbeitungssystemen, die nach dem WYSIWYG2-System arbeteien.Am Ende des Projektes wurde der gesamte Text direkt in LATEX formatiert und letzte Änderungen wur-den mit dem LATEX-Editor Kile[38] vorgenommen.Für die Erstellung des Literaturverzeichnisses und des Online-Verzeichnisses wurde BibTEX[11] ver-wendet. BibTEX ist ein Werkzeug zur Erstellung von Literaturverzeichnissen mit LATEX. BibTEX ver-wendet dazu eine Datebank von Werken in einer Textdatei, die eine bestimmte Syntax verwendet. DieseTextdatenbank wurde mit OpenSource Tool JabRef[31] erstellt. Die meisten Bücher wurden ausserdemmit dem Online-Tool Amatex[6] gefunden. Amatex sucht anhand der ISBN-Nummer oder anderen An-gaben eines Werkes alle nötigen BibTEX angaben mit einer Abfrage von Amazon[7] .

C.2.2. Violet UML-Tool

Die UML-Diagramme wurden mit dem Tool Violet[59] erstellt. Violet ist ein sehr kleiner OpenSourceUML-Tool, mit dem sehr leicht, einfache UML-Diagramme erstellen kann.

1What you see is what you mean2What you see is what you get

77

C.2. Dokumentation 78

C.2.3. Dia Diagramm-Editor

Einige Abbildungen in diesem Dokument wurden mit dem OpenSource Programm Dia[19] erzeugt. Diaist laut der Homepage ähnlich aufgebaut wie das Windows Programm Visio. Es eignet sich hervorragendum Diagramme und ähnliche Abbildungen zu erzeugen.

Literaturverzeichnis

[AH01] Dave Astle and Kevin Hawkins. OpenGL Game Programming. Prima Tech, 2001.

[AMH02] Tomas Akenine-Moller and Eric Haines. Real-Time Rendering. AK Peters, Ltd., 2nd editi-on edition, 2002. http://www.realtimerendering.com/ (letzter Aufruf 29. August, 2005).

[BBV03] David Brackeen, Bret Barker, and Laurence Vanhelswue. Developing Games in Java. NewRiders Publishing, 2003.

[BZ] Avi Bar-Zeev. Scenegraphs: Past, Present and Future. Website. http://www.realityprime.com/scenegraph.php (letzter Aufruf 29. August, 2005).

[Dav05] Andrew Davison. Killer Game Programming in Java. O’Reilly, 2005. http://fivedots.coe.psu.ac.th/~ad/jg/ (letzter Aufruf 1. September, 2005).

[DP02] Fletcher Dunn and Ian Parberry. 3D Math Primer for Graphics and Game Development.Wordware Publishing Inc., 2002.

[Ebe00] David H. Eberly. 3D Game Engine Design : A Practical Approach to Real-Time ComputerGraphics. Morgan Kaufmann, 2000.

[Ebe04] David H. Eberly. 3D Game Engine Architecture. Morgan Kaufmann, 2004.

[Eck02] Bruce Eckel. Thinking in Java. Prentice Hall PTR, 3rd edition edition, 2002.

[Eng04] Wolfgang Engel. Programming Vertex and Pixel Shaders. Charles River Media, 2004.

[Fow04] Martin Fowler. UML Distilled. Addison Wesley, 2004.

[FvDFH95] James D. Foley, Andries van Dam, Steven K. Feiner, and John F. Hughes. Computer Gra-phics: Principles and Practice in C. Addison-Wesley Professional, 2nd edition edition,1995.

[GHJV95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns.Addison-Wesley Professional, 1995.

[Len03] Eric Lengyel. Mathematics for 3D Game Programming and Computer Graphics. CharlesRiver Media, 2nd edition edition, 2003.

[Lin] Jack Lindamood. Learning jME. Website. https://www.dev.java.net/files/documents/73/10905/Starter.pdf (letzter Aufruf 29. August, 2005), auch auf der mitgelieferten CD enthalten.

[Lun03] Frank D. Luna. Introduction to 3D Game Programming with DirectX 9.0. WordwarePublishing, Inc., 2003.

79

Literaturverzeichnis 80

[Pol02] Trent Polack. Focus On 3D Terrain Programming. Muska & Lipman/Premier-Trade, 2002.

[Sal01] Dave Salvator. ExtremeTech 3D Pipeline Tutorial. Website, 2001. http://www.extremetech.com/article2/0,1558,9722,00.asp (letzter Aufruf 29. August, 2005).

[SWN+03] David Shreiner, Mason Woo, Jackie Neider, Tom Davis, and OpenGL Architecture Re-view Board. Opengl Programming Guide: The Official Guide to Learning Opengl, Version1.4. Addison Wesley, 2003. http://fly.cc.fer.hr/~unreal/theredbook/ (letzter Aufruf 29. August,2005).

[WP00] Alan Watt and Fabio Policarpo. 3D Games: Real-Time Rendering and Software Technology,Volume 1. Addison Wesley, 2000.

[ZDA04] Stefan Zerbst, Oliver Düvel, and Eike Anderson. 3D-Spieleprogrammierung Kompendium.Markt und Technik, 2004.

Web Ressourcen

[1] 3D Engine Database. http://www.devmaster.net/engines/ (letzter Aufruf 25. August, 2005).

[2] 3D Links. http://www.3dlinks.com (letzter Aufruf 25. August, 2005).

[3] 3D Studio Max. http://www.discreet.com/3dsmax (letzter Aufruf 25. August, 2005).

[4] AC3D. http://www.ac3d.org (letzter Aufruf 25. August, 2005).

[5] Alias Maya. http://www.alias.com/maya (letzter Aufruf 25. August, 2005).

[6] Amatex. http://www.2ndminute.org:8080/amatex (letzter Aufruf 26. September, 2005).

[7] Amazon. http://www.amazon.com (letzter Aufruf 26. September, 2005).

[8] Ati. http://www.ati.com (letzter Aufruf 25. August, 2005).

[9] Aviatrix 3D Engine. http://aviatrix3d.j3d.org (letzter Aufruf 25. August, 2005).

[10] Battlezone Review. http://www.thelogbook.com/phosphor/summer99/batlzone.html (letzter Aufruf 25.August, 2005).

[11] BibTeX Wikipedia Eintrag. http://de.wikipedia.org/wiki/BibTeX (letzter Aufruf 26. September, 2005).

[12] Blender. http://www.blender.org (letzter Aufruf 25. August, 2005).

[13] BSD License. http://www.opensource.org/licenses/bsd-license.php (letzter Aufruf 25. August, 2005).

[14] Collada. http://www.collada.org (letzter Aufruf 25. August, 2005).

[15] Creepy’s Object Download. http://www.creepyclown.com/downloads.htm (letzter Aufruf 25. August,2005).

[16] CryEngine. http://www.crytek.de/technology/index.php?sx=cryengine (letzter Aufruf 25. August, 2005).

[17] Crystal Space Engine. http://www.crystalspace3d.org (letzter Aufruf 25. August, 2005).

[18] CVS - Concurrent Versions System. http://www.nongnu.org/cvs (letzter Aufruf 26. September, 2005).

[19] Dia. http://www.gnome.org/projects/dia (letzter Aufruf 26. September, 2005).

[20] DirectX. http://www.microsoft.com/windows/directx/ (letzter Aufruf 25. August, 2005).

[21] Dreamworks Animation. http://www.dreamworksanimation.com (letzter Aufruf 25. August, 2005).

[22] Eclipse. http://www.eclipse.org (letzter Aufruf 2. September, 2005).

81

Web Ressourcen 82

[23] Elder Scrolls IV, Oblivion. http://www.elderscrolls.com/games/oblivion_overview.htm (letzter Aufruf 26.September, 2005).

[24] Espresso 3D Engine. http://www.espresso3d.com (letzter Aufruf 25. August, 2005).

[25] Far Cry. http://www.farcry.de/ (letzter Aufruf 29. August, 2005).

[26] Game Institute. http://www.gameinstitute.com (letzter Aufruf 2. September, 2005).

[27] Gameversity. http://www.gameversity.com (letzter Aufruf 2. September, 2005).

[28] gmax. www.autodesk.com/gmax (letzter Aufruf 25. August, 2005).

[29] id Software. http://www.idsoftware.com (letzter Aufruf 25. August, 2005).

[30] Irrlicht Engine. http://irrlicht.sourceforge.net/ (letzter Aufruf 25. August, 2005).

[31] JabRef. http://jabref.sourceforge.net (letzter Aufruf 26. September, 2005).

[32] Java. http://java.sun.com (letzter Aufruf 26. September, 2005).

[33] Java 3D. http://java.sun.com/products/java-media/3D/ (letzter Aufruf 24. August, 2005).

[34] jME Javadoc Documentation. http://www.jmonkeyengine.com/doc/ (letzter Aufruf 25. August, 2005).

[35] jME Physics System. http://jme-physics.sourceforge.net (letzter Aufruf 12. Oktober, 2005).

[36] jMonkey Engine. http://jmonkeyengine.com/ (letzter Aufruf 24. August, 2005).

[37] Jogl Java bindings for OpenGL. https://jogl.dev.java.net/ (letzter Aufruf 25. August, 2005).

[38] Kile. http://kile.sourceforge.net (letzter Aufruf 26. September, 2005).

[39] LaTeX. http://www.latex-project.org (letzter Aufruf 26. September, 2005).

[40] Lightweight Java Game Library. http://www.lwjgl.org (letzter Aufruf 25. August, 2005).

[41] LyX. http://www.lyx.org (letzter Aufruf 26. September, 2005).

[42] Maya Personal Learning Edition. http://www.alias.com/glb/eng/products-services/product_details.jsp?productId=1900003 (letzter Aufruf 25. August, 2005).

[43] Milkshape 3D. http://www.swissquake.ch/chumbalum-soft/ (letzter Aufruf 25. August, 2005).

[44] NVidia. http://www.nvidia.com (letzter Aufruf 25. August, 2005).

[45] Octane Digital Studios. http://www.octanedigitalstudios.com/page4 (letzter Aufruf 26. September,2005).

[46] OGRE Engine. http://www.ogre3d.org (letzter Aufruf 25. August, 2005).

[47] OpenGL. http://www.opengl.org (letzter Aufruf 25. August, 2005).

[48] Paint Shop Pro. http://www.jasc.com (letzter Aufruf 26. September, 2005).

[49] Panda3D. http://www.panda3d.org/ (letzter Aufruf 2. September, 2005).

[50] Pixar. http://www.pixar.com (letzter Aufruf 25. August, 2005).

[51] Pixars Geri’s Game. http://www.pixar.com/shorts/gg/ (letzter Aufruf 25. August, 2005).

[52] Psionic’s 3D Game Resources. http://www.psionic3d.co.uk (letzter Aufruf 25. August, 2005).

Web Ressourcen 83

[53] Quake 3. http://www.idsoftware.com/games/quake/quake3-arena/ (letzter Aufruf 25. August, 2005).

[54] SOFTIMAGE|XSI. http://www.softimage.com/xsi (letzter Aufruf 25. August, 2005).

[55] Terragen. http://www.planetside.co.uk/terragen (letzter Aufruf 26. September, 2005).

[56] Texture Warehouse. http://www.texturewarehouse.com (letzter Aufruf 26. September, 2005).

[57] Unreal 3 Engine. http://www.unrealtechnology.com (letzter Aufruf 25. August, 2005).

[58] Valve Software. http://valvesoftware.com (letzter Aufruf 25. August, 2005).

[59] Violet UML-Tool. http://www.horstmann.com/violet (letzter Aufruf 26. September, 2005).

[60] Virtual Terrain Project. http://www.vterrain.org (letzter Aufruf 2. September, 2005).

[61] Wikipedia 3dfx. http://de.wikipedia.org/wiki/3dfx (letzter Aufruf 25. August, 2005).

[62] Wings3D. http://www.wings3d.com (letzter Aufruf 25. August, 2005).

[63] Wolfenstein 3D. http://www.3drealms.com/wolf3d/ (letzter Aufruf 25. August, 2005).