3D-Game-Engine

Diplomarbeit

vorgelegt von Torben Fugger Matrikelnr. 200210277

Institut für Computervisualistik Arbeitsgruppe Computergrafik

Betreuer und Prüfer: Prof. Dr. Stefan Müller

November 2004 Inhaltsverzeichnis

1. Einführung 5

2. Grundlagen 6

3. Bestehende Ansätze 8 3.1. 3D-Game-Engines ...... 8 3.2. Szenengraph ...... 8 3.2.1. Buch „3D Design“ ...... 9 3.2.2. OpenSG ...... 10 3.2.3. VRML ...... 10 3.3. Physikalisch basierte Simulation ...... 11 3.3.1. Artikel von Chris Hecker ...... 11 3.3.2. SIGGRAPH Papers: „Physically based modeling“ ...... 13 3.4. Kollisionserkennung ...... 14 3.4.1. Bounding volumes ...... 14 3.4.2. Closest Feature Tracking ...... 15 3.4.3. Separating plane ...... 16

4. Anforderungen 18 4.1. Engine-Design ...... 18 4.2. Szenengraph ...... 19 4.3. Physikalisch basierte Simulation ...... 20 4.4. Kollisionsmanagement ...... 20 4.5. Tech-Demo ...... 20

5. Konzept 22 5.1. Engine-Design ...... 22 5.2. Szenengraph ...... 27 5.3. Physikalisch basierte Simulation ...... 32 5.4. Kollisionsmanagement ...... 32

6. Implementierung 36 6.1. Engine-Design ...... 36 6.2. Szenengraph ...... 37 6.3. Physikalisch basierte Simulation ...... 40

2 Inhaltsverzeichnis

6.4. Kollisionsmanagement ...... 40 6.4.1. Erkennung ...... 41 6.4.2. Behandlung ...... 43 6.5. Weiteres ...... 44 6.5.1. Terrain ...... 44 6.5.2. Partikelsysteme ...... 46

7. Ergebnisse 49

8. Fazit und Zukunftsaussichten 52

A. Bilder der Tech-Demo 53

3 Abbildungsverzeichnis

3.1. Zweidimensionales Beispiel des Closest-Feature-Verfahrens. Die beiden Fea- tures, die am dichtesten beieinander liegen, befinden sich jeweils in der Voronoi- Region des gegenüberliegenden Features...... 16 3.2. Zweidimensionales Beispiel des Separating-Plane-Verfahrens. Die Vertices der beiden Geometrien liegen bezüglich der hervorgehobenen Ebene in verschie- denen Halbräumen...... 17

5.1. Modularer Aufbau der Game-Engine...... 23 5.2. Der GameManager verwaltet Objekte der Klasse GameObject, von der wie- derum spezifische Spielobjekte abgeleitet werden können...... 23 5.3. Der InputManager und seine drei Geräteklassen ...... 24 5.4. Die wesentlichen Elemente der Update-Schleife im GameManager...... 26 5.5. Die Klasse SceneGraph ist Teil des GraphicsManager-Moduls...... 27 5.6. Die Klasse SceneNode und ihre Ableitungen ...... 27 5.7. Die Klasse ShapeNode und ihre NodeComponents...... 30 5.8. Die Klasse ComponentController und ihre Ableitungen...... 30 5.9. Der beispielhafte Einsatz von ComponentControllern zur Manipulation des Szenengraphen...... 31 5.10. Die erste Stufe der Kollisionserkennung mit Hilfe des Szenengraphen .... 33 5.11. Eingrenzung des Kollisionszeitpunkts durch Wiederholung der Simulation mit feineren Zeitintervallen ...... 34

6.1. Beispiel für die Funktionsweise der Klasse FuRenderGraph...... 39 6.2. Ein Knoten des Terrain-Quadtrees mit einem Kindknoten (oben rechts). ... 44 6.3. Texturierung der Terrain-Geometrie...... 46 6.4. Beispiele für Partikelsysteme der FuEngine...... 48

7.1. Testszene für die Evaluierung der Engine...... 50

A.1. Kollisionen zwischen einfachen Würfel-Geometrien...... 53 A.2. Kollisionen zwischen komplexen Geometrien...... 54 A.3. Partikelsysteme und Festkörpersimulation...... 55 A.4. Ansicht auf das versunkene Dorf...... 56

4 1. Einführung

Die Entwicklung von Computersystemen schreitet heutzutage mit rasender Geschwindigkeit voran — diese Tatsache ist fast jedem von uns bekannt. Doch kaum jemand ist sich bewusst, dass diese Entwicklung besonders stark durch Computerspiele vorangetrieben wird. Beson- ders ausgeprägt konnte man diese Tatsache in letzter Zeit im Bereich der Grafikhardware feststellen, da immer mehr Spiele die dreidimensionale Darstellung verwenden, welche durch moderne Grafikchips sehr effizient unterstützt wird. Beispiele für den Umstieg auf die drit- te Dimension sind die neusten Episoden der erfolgreichen Aufbauspiel-Serien Siedler und Stronghold, deren vorherige Teile eine zweidimensionalen Darstellung verwendeten. Diese 3D-Darstellung ist so beliebt, da sie wesentlich mehr Freiheiten bei der Gestaltung der Inhal- te bietet und dem Spieler realistischere Szenarien bereitstellen kann. Allerdings ist sie auch sehr komplex und verlangt aufwendige Verfahren zum Erzeugen und Verwalten von grafischen Spielinhalten. Um diese Verfahren für die Entwicklung mehrerer Spiele wiederverwenden zu können, kapselt man sie in Form einer Game-Engine ab. Lässt sich die Engine als Grundlage für Spiele verwenden, die sich in ihrem Typ unterscheiden, wie z.B. ein Autorennspiel und ein Strategiespiel, so handelt es sich um eine universelle Engine. Innerhalb dieser Arbeit soll eine solche universelle 3D-Game-Engine entwickelt werden, was ein Konzept und eine prototypische Implementierung umfasst. Die Schwerpunkte liegen da- bei in der Erstellung und Verwendung eines Szenengraphen, in der physikalisch basierten Simulation von Spielobjekten und in der Erkennung und Behandlung von Kollisionen zwi- schen Geometrien der Szene1. Deshalb befasst sich das nächste Kapitel vorerst mit einigen grundlegenden Kenntnissen über Game-Engines und den drei Schwerpunktthemen dieser Ar- beit. Anschließend werden einige Quellen erläutert, die in Vorbereitung auf die Entwicklung des Konzepts recherchiert wurden. Dann kommen die Anforderungen an das Konzept der Engine im Allgemeinen und an das Konzept des Szenengraphen, der Physiksimulation und des Kollisionsmanagements im Speziellen zur Sprache. Das darauf folgende Kapitel enthält schließlich detaillierte Informationen zum entwickelten Konzept. Unmittelbar danach werden wesentliche Eigenschaften der prototypischen Implementierung, die auf das Konzept aufbaut, erläutert. Ein weiteres Kapitel zeigt und evaluiert die Ergebnisse der Entwicklung mittels einer auf der Engine basierten Beispielanwendung (Tech-Demo). Abschließend wird ein Fazit aus dem den gewonnenen Erkenntnissen dieser Arbeit gezogen und ein Ausblick auf mögliche Erweiterungen der Engine gegeben.

1Menge aller Inhalte eines Spiels, umfasst Spielobjekte, Geometrien, Geräusche usw.

5 2. Grundlagen

Eine Game-Engine ist eine Sammlung von Technologien und Hilfsmitteln, die die Erstellung von Spielen in großem Maße unterstützt und erleichtert. Eine Engine umfasst dazu eine große Menge von Funktionen, die von nahezu allen Spielen verwendet werden, und keinen spezi- fischen Spielinhalt. Eine Game-Engine wird auch des Öfteren als „Betriebssystem für Spie- le“ bezeichnet. Es existieren Engines, die sich nur für die Erstellung von Spielen eines be- stimmten Genres eignen und dafür optimiert sind, wie z.B. einige Engines für Spiele aus der -Perspektive, die nur in Gebäudekomplexen angesiedelte Szenarien und keine weitläufigen Außenlandschaften zulassen, dafür aber über sehr ausgefeilte Beleuchtungsmodelle verfügen. Andere Engines hingegen versuchen eine möglichst breite Bandbreite an Funktionalität anzu- bieten, um die auf ihrer Basis erstellten Spiele so wenig wie möglich im Genre und in den Möglichkeiten einzuschränken. Diese Engines lassen Spieltypen mit völlig unterschiedlichen Anforderungen zu oder ermöglichen sogar die Verschmelzung verschiedener Genres. Ein Bei- spiel dafür sind Spiele, in denen der Spieler in großflächigen Arealen umherlaufen kann, aber auch in der Lage ist, in der Szene befindliche Flugzeuge zu verwenden, um mit diesen im Stil eines Flugsimulators das gleiche Areal zu überfliegen. Durch die Vielseitigkeit bei der Nutzung einer Engine sind Spiele möglich, die den Spieler wesentlich stärker in das Spielge- schehen eintauchen lassen. Zugleich stellt die Vielseitigkeit bezüglich der Vermarktung einer ausgereiften Engine auch einen kommerziellen Faktor dar, da sich somit die Zahl der potenti- ellen Lizenznehmer erhöht. Ebenso sind auf Basis einer Game-Engine außer Spielen auch eine Vielzahl weiterer inter- aktiver Anwendungen möglich. So nutzt z.B. Virtual Reality die gleichen Techniken wie ein Spiel und vermittelt damit unter anderem wissenschaftliche Inhalte, dient als Unterstützung bei der Planung von Gebäuden oder bringt Piloten im Simulator gefahrlos das Fliegen bei. Vor kurzem wurde die „Total War“-Engine, deren Stärke die Verwaltung hunderter, autonom agierender Spielobjekte ist, verwendet, um für eine Dokumentation, die vom Fernsehsender BBC ausgestrahlt wurde, historische Schlachten nachzustellen. Da Spiele in der Regel sehr komplexe Szenarien mit einer Vielzahl von Geometrien verwen- den, werden effiziente und übersichtliche Mechanismen für den Aufbau und die Verwaltung der jeweils dargestellten Szene benötigt. Ein bewährtes Verfahren, um eine Szene zu verwal- ten, ist der Szenengraph. Dieser speichert die einzelnen Dinge, die zum Aufbau einer Szene benötigt werden, wie z.B. Geometrien, Transformationen oder Gruppierungen, in einem hier- archischen Graphen als Knoten ab, wodurch irrelevante Teile der Szene, z.B. nicht sichtba- re Objekte, beim Traversieren des Graphen sehr schnell ausgeschlossen werden können. Ein Szenengraph eröffnet außerdem die Möglichkeit, hierarchische Systeme, die in der Realität oft vorkommen (z.B. die Beziehung zwischen Arm, Hand und Fingern), nachzustellen und

6 2. Grundlagen anschaulich zu verwalten. Sein modularer Aufbau kann zur Laufzeit erweitert und verändert werden. Neben der großen Entwicklung im Bereich der Grafik-Hardware, durch die eine starke Annä- herung an den Photorealismus möglich geworden ist, versucht man im Bereich der Spieleent- wicklung auch auf andere Weise der Wirklichkeit näher zu kommen. Dabei spielt die physika- lisch basierte Simulation von Spielobjekten eine große Rolle. Indem die Gesetze der Dynamik und Kinematik auf Gegenstände und Charaktere im Spiel angewendet werden, kommen deren Bewegungen den Gegenstücken der realen Welt sehr nah. Es können sogar plausible Bewe- gungen für fiktive Objekte, wie z.B. Raumschiffe, simuliert werden, die dem Betrachter sofort korrekt erscheinen, obwohl die tatsächliche Vergleichsmöglichkeit fehlt. Folge einer physika- lisch basierten Simulation ist oft, dass das Spiel insgesamt realistischer auf den Betrachter wirkt und aufgrund der Erwartungskonformität auch intuitiver zu bedienen ist. Eng verbunden mit der Physik-Simulation ist das Kollisionsmanagement, welches sich in zwei Bereiche teilt: Die Kollisionserkennung dient dem Prüfen, ob zwei Geometrien innerhalb der Szenen kollidieren, wobei gewöhnlich auch die Ermittlung von Kollisionszeit und Kontakt- punkt ein wichtige Rolle spielt. Die Kollisionsbehandlung sorgt für die Ausführung von ent- sprechenden Aktionen nach einer Kollision. Oft wird dabei die physikalisch basierte Simu- lation verwendet, um das Verhalten der beteiligten Geometrien nach einem Zusammenstoß zu berechnen. Durch das Kollisionsmanagement wird ein wesentlich höherer Interaktionsgrad möglich, der weit über einen linear vorgegebenen Spielverlauf hinausgehen kann. Der Spie- ler ist durch eigene Handlungen in der Lage, Reaktionen auszulösen, die sich anschließend autonom und individuell weiterentwickeln. Gegenstände der Szene müssen nicht nur statische Kulisse sein, sondern können aktiv in das Spielgeschehen einbezogen und möglicherweise zur Lösung von Aufgaben verwendet werden. Durch die erhöhte Interaktivität erlaubt das Spiel, den angedachten Verlauf zu verlassen und experimentell die Szene zu erforschen. Eine Engine kann über weitere Fähigkeiten verfügen, die das Spiel für einen Benutzer glaub- hafter und interessanter machen. Dazu gehört unter anderem die Darstellung einer flächenmä- ßig begrenzten Landschaft, genannt Terrain. Eine solche Landschaft bietet eine gute Vorgabe für die Platzierung weiterer Szenenobjekte und gibt dem Benutzer eine Möglichkeit, sich in der Szene zu orientieren. Des Weiteren zählt die Unterstützung von Partikelsystemen zu die- sen nützlichen Fähigkeiten. Über ihren Einsatz lassen sich eine Vielzahl von Spezialeffekten, wie Feuer, Rauch, Staub oder Explosionen darstellen. Verwendet man Partikelsysteme in ei- ner Szene, kann man diese dadurch mit geringem Aufwand wesentlich dynamischer wirken lassen.

7 3. Bestehende Ansätze

Um einen Überblick über die bestehenden Ansätze und Techniken zu erlangen, wurden zu Game-Engines im Allgemeinen und zu den drei Schwerpunkt-Themen Recherchen durchge- führt. Auf viele dieser Ideen konnte bei der Entwicklung des Konzepts aufgesetzt werden. Grundlage der Recherchen waren Bücher, Artikel, Webseiten und Dokumentationen zu beste- henden Systemen, die im Einzelnen im jeweiligen Zusammenhang aufgeführt werden.

3.1. 3D-Game-Engines

Die wohl am weitesten ausgereiften Game-Engines sind die kommerziellen Engines. Die- se werden von großen Entwicklerteams in jahrelanger Arbeit entwickelt und erweitert und müssen hohen Ansprüchen gerecht werden. Um eine solche Engine nutzen zu können, muss man allerdings sehr hohe Lizenzgebühren an das Entwickler-Studio entrichten, weshalb die- se Möglichkeit in der Regel nur größeren, etablierten Unternehmen offen steht. Die mo- mentan bekanntesten Vertreter kommerzieller Engines sind z.B. die Unreal-Engine oder die Renderware-Engine. Da diese Technologien gut gehütet werden, sind Implementierungsde- tails meistens erst dann öffentlich zugänglich, wenn bereits die Nachfolgeversion der Engine auf dem Markt erhältlich ist. Eine für jedermann nutzbare Alternative bieten OpenSource-Engines, wie etwa OGRE, Cry- stal Space oder die Irrlicht-Engine. Aufgrund des OpenSource-Konzepts lassen sich verwen- dete Verfahren und Techniken zwar uneingeschränkt analysieren, allerdings sind diese oft so tief in das Konzept verwoben, dass ein genaues Gesamtverständnis desselben unabdingbar ist. In der Gemeinschaft der Nutzer und Entwickler ist oft sehr viel unterstützendes Material vorhanden: Von Anleitungen über Hilfsprogramme bis hin zu Quellcode-Beispielen.

3.2. Szenengraph

Bei der Recherche zu bestehenden Szenengraphen wurden Ansätze einbezogen, die sich in ihrer Absicht bei der Verwendung des Graphen unterscheiden. So konnten sowohl alternative Ansätze für die gleichen Problemstellungen als auch gemeinsame Lösungswege untersucht werden.

8 3. Bestehende Ansätze

3.2.1. Buch „3D Game Engine Design“ Als erste Quelle diente das Buch „3D Game Engine Design“ von David H. Eberly [4], in dem ein Ansatz für einen Szenengraph beschrieben wird. Demnach ist ein Szenengraph eine hierarchische Anordnung und Gruppierung von Objekten gemäß ihres Orts in der virtuellen Szene. Die verwendete Struktur ist hierbei ein Baum, dessen Blätter Geometrien darstellen und dessen interne Knoten als Gruppierungsmechanismen dienen. Die Blätter können Verti- ces1, Texturen und weitere Daten gemeinsam verwenden, um somit den Speicherverbrauch zu verringern. Eberly sieht in der Verwendung eines Szenengraphen vier Vorteile:

1. Es ist leicht möglich einen einzelnen Bereich der Szene als Teilbaum des Graphen zu modellieren und die Teilbäume zu einer komplexen Szene zusammenzufügen.

2. Nicht sichtbare Regionen der Szene können mit Culling-Mechanismen2 und Bounding- Volumes3 aufgrund der räumlichen Hierarchie schnell ausgeschlossen werden. Die Hier- archie eignet sich ebenfalls gut, um die Anzahl an Kollisionstests drastisch zu verrin- gern.

3. Viele Objekte, die es in Szenen zu modellieren gilt, bergen eine Hierarchie in sich (z.B. die Gelenke eines menschlichen Arms). Der Szenengraph eignet sich aufgrund seiner Struktur gut, um diese hierarchischen Anordnungen nachzubauen.

4. Die gesamte Szene lässt sich einfach speichern, indem man die Speichermethode der Wurzel aufruft, wodurch der Aufruf zum Speichern rekursiv bis zu den Blättern weiter- gereicht wird.

Veränderungen in der Szene werden durch Veränderungen in den Knoten des Graphen reprä- sentiert, allerdings müssen bei Änderung eines Knotens alle untergeordneten Knoten aktuali- siert werden. Nach Eberly gibt es vier Informationskategorien, die auf jeden Fall in Knoten ge- speichert werden sollen. Dazu gehören Transformationen, Bounding-Volumes, Render-States4 und Animation-States5. Es wird vorgeschlagen, die Transformationen aus Effizienzgründen als eine Kombination einer 3 × 3 - Matrix und eines Translations-Vektors abzuspeichern. Die Bounding-Volumes werden von unten nach oben durch Verschmelzung der Bounding- Volumes der Kinder aufgebaut. Render-States können auch in internen Knoten auftreten und wirken sich dann auf den gesamten Teilbaum aus. Animation-States beziehen sich auf zeitab- hängige Vorgänge, wie z.B. die zeitlich bedingte Änderung einer Transformation. Diese zeitli- chen Änderungen sollen durch Controller6 verwaltet werden, die in jedem Knoten gespeichert werden können.

1Knotenpunkte einer Geometrie 2Mechanismen zum Ausblenden nicht sichtbarer Geometrie 3Hüllkörper, die eine Geometrie umschließen 4Ein Render-State ist ein Status innerhalb einer Grafikbibliothek, der für die Dauer seiner Aktivierung auf alle Zeichenvorgänge der Bibliothek einwirkt. 5Ein Animation-State ist der zum einem festen Zeitpunkt betrachtete Status einer vorgegebenen Animation. 6Controller dienen der Verwaltung von Werten, die sich über die Zeit verändern sollen

9 3. Bestehende Ansätze

Eberly beschreibt die Aktualisierung und das Culling der Bounding-Volumes im Detail, geht aber auf andere Aspekte, wie die Vorsortierung der Render-States nicht genauer ein.

3.2.2. OpenSG Als Informationsquelle zu dem OpenSource-Szenengraphen OpenSG diente der „OpenSG Starter Guide“ von O. Abert [1]. OpenSG erweitert das Prinzip des Szenengraphen durch eine strikte Trennung des Knotens und seines Inhalts, dem so genannten „Node-Core“. Dadurch wird erreicht, dass die Knoten nur zur Verwaltung der Hierarchie dienen und nicht noch zu- sätzliche Daten beinhalten. Des Weiteren kann derselbe Node-Core von beliebig vielen Knoten verwendet werden, wodurch vermieden werden kann, dass gleiche Inhalte mehrfach abgespei- chert werden. Da OpenSG darauf ausgelegt ist, die Verwaltung einer Szene auf mehrere Rech- ner per Netzwerk zu verteilen, dienen viele seiner Konstrukte der Multithread-Sicherheit. Es existieren mehrere Arten von Node-Cores, die mit einem Knoten verknüpft werden können: • Group core: Dient der Gruppierung mehrerer Knoten durch das Speichern mehrerer Kindknoten

• Transform core: Speichert eine Transformation, die auf alle Kinder des Knotens ange- wendet wird. Die Transformation wird in Form einer Matrix übergeben.

• Material core: Einfache oder texturierte Materialien werden gespeichert und auf alle Kindknoten angewendet.

• Switch core: Eine Art Gruppenknoten, bei dem entweder genau eines, keines oder alle Kinder gerendert werden können.

• LOD core: abhängig von der Entfernung zum Betrachter (bzw. von einer Fehlerfunk- tion) wird ein Teilbaum des Graphen zum Rendern ausgewählt, wobei die möglichen Teilbäume jeweils dieselbe Geometrie mit unterschiedlichem Detailgrad darstellen. OpenSG erstellt vor dem Rendern einen Zeichenbaum, um die Darstellung in Bezug auf Ef- fizienz und Qualität zu optimieren. Dazu werden Render-States zwecks Vermeidung von Sta- tuswechseln geordnet, Geometrien gegen das View-Frustum getestet und transparente Objekte von hinten nach vorne sortiert.

3.2.3. VRML Die Virtual Reality Modeling Language ([3]) ist eine Modellierungssprache, mit der es mög- lich ist, dreidimensionale Szenen mit Interaktionsmöglichkeiten zu beschreiben. Die Daten- struktur beruht auf dem Szenengraph Open Inventor von SGI. VRML verwendet ebenfalls Standardknotentypen wie den Gruppen- oder den Transformationsknoten. Interessant ist der so genannte ShapeNode, der sowohl Geometrien als auch ihr Aussehen beschreibt. Dazu ver- weist er intern auf einen AppearanceNode und einen GeometryNode. Der AppearanceNode zeigt wiederum auf eine Reihe von weiteren Knoten, mit denen sich Material, Textur und

10 3. Bestehende Ansätze weitere Eigenschaften beschreiben lassen. Der GeometryNode kann auf eine Standardgeome- trie wie z.B. eine Box oder einen Zylinder verweisen, aber auch komplexere Beschreibungen, wie eine Menge von Linien, verwalten. VRML besitzt eine interne Kollisionsabfrage, die sich allerdings nur auf Kollisionen des Betrachters, bzw. seines Avatars, mit Objekten der Szene beschränkt. Des Weiteren ermöglicht die Modellierungssprache die Verwendung von Events, die durch einen besonderen Knotentyp, genannt Sensor, ausgelöst und über festgelegte Pfa- de an andere Knoten der Szene weitergeleitet werden, wo sie eine Reaktion verursachen. Die Sensoren können je nach Typ auf verschiedene Ereignisse reagieren. Um mit Events gezielt auf andere Knoten der Szene wirken zu können, gibt es einen weiteren Knotentyp, die Inter- polatoren. Ihre Aufgabe ist es eingehende Daten in einen anderen Wertebereich zu übersetzen und die neuen Daten wieder auszugeben. Es existieren Interpolatoren für Farben, Positionen, Orientierungen usw. Für Knotentypen, von denen es zur Laufzeit nur eine Instanz geben kann, gibt es in VRML so genannte Bindable Nodes. Zu ihnen zählen Knoten wie Viewpoint, Nebel oder Hintergrund. Im Rahmen der Recherche wurden eine Reihe weiterer Artikel zum Thema Szenengraph untersucht, die teilweise den bereits vorgestellten Ansätzen stark ähneln oder nur Teilaspekte der Szenengraphverwaltung behandeln und deshalb an dieser Stelle nicht explizit aufgeführt werden.

3.3. Physikalisch basierte Simulation

In Bezug auf die physikalisch basierte Simulation wurden gezielt Ansätze untersucht, die sich mit der Simulation von Festkörpern beschäftigen, da sich diese Methoden in Hinblick auf die Leistungsfähigkeit der heutigen Computersysteme am besten für den Einsatz in Echtzeit eignen.

3.3.1. Artikel von Chris Hecker Von 1996 bis 1997 veröffentlichte die Zeitschrift Game Developer eine vierteilige Serie von Chris Hecker [6], die sich mit physikalisch basierter Simulation beschäftigt. Die Serie be- zieht sich in erster Linie auf die Anwendung der Physik in Spielen und eignet sich deshalb gut als Informationsquelle. Aus Gründen der Verständlichkeit baute Hecker die Serie so auf, dass sich die ersten drei Artikel mit Dynamik und Kinematik von zweidimensionalen Fest- körpern beschäftigen und erst der vierte Artikel eine Überleitung in die dritte Dimension mit sich bringt. Der Zustand eines Festkörpers wird mit sechs Freiheitsgraden beschrieben, wobei drei Freiheitsgrade auf die Position des Massezentrums, bzw. Schwerpunkts entfallen und drei Freiheitsgrade für die Orientierung des Körpers im Raum verwendet werden. Während sich die Gleichungen der linearen Kinematik und Dynamik durch Erweiterung der Dimension der Vektoren direkt in die dreidimensionale Darstellung übertragen lassen, werden die Rotations- effekte in der dritten Dimension deutlich komplizierter. Hecker verwaltet die Orientierung eines Festkörpers als 3 × 3 - Matrix und spricht die Verwendung von Quaternionen nur in einer Fußnote an.

11 3. Bestehende Ansätze

Zur numerischen Integration von ODEs (Ordinary differential equation) setzt er das Ver- fahren von Euler ein: x(t0 + h) = x0 + hx˙(t0) Zum Ende des letzten Artikels stellt Hecker einen in Pseudo-Code verfassten Algorithmus für 3D-Dynamik vor. Dieser besteht aus zwei Phasen: einer Initialisierungs- und einer Simulati- onsphase, wobei letztere in einer Schleife mit jedem Zeitabschnitt wiederholt wird. Sämtliche physikalischen Größen, die im folgenden aufgeführt werden, beziehen sich auf das Massezen- trum, bzw. den Schwerpunkt des simulierten Festkörpers. Die Initialisierung umfasst folgende Schritte:

• Die Konstanten des Festkörpers definieren: I¯−1,M

• Die Startbedingungen definieren: r0, v0,A0,L0

• Die initialen physikalischen Hilfsgrößen ermitteln, welche die Berechnungen vereinfa- chen:

I0−1 = A0I¯−1A0T ω0 = I0−1 L0

Erläuterung:

I¯−1 = Inverse des Trägheitstensors7 im lokalen Koordinatensystems des Festkör- pers M = Gesamtmasse des Festkörpers r0 = Initiale Position des Schwerpunkts v0 = Initiale Geschwindigkeit des Schwerpunkts A0 = Initiale Orientierung des Festkörpers L0 = Initialer Drehimpuls des Festkörpers bezüglich des Schwerpunkts I0−1 = Initiale Inverse des Trägheitstensors im Weltkoordinatensystem ω0 = Initiale Winkelgeschwindigkeit des Festkörpers

Die Simulation führt die folgenden Aktionen durch:

• Berechnung der einzelnen Kräfte und der Positionen, an denen sie wirken: Fi, ri

n P n P • Berechnung der Gesamtkräfte und -drehmomente: FT = Fi, τT = ri × Fi • Integrieren der physikalischen Größen:

rn+1 = rn + hvn n n+1 n FT v = v + h M 7Der Trägheitstensors eines Festkörpers ist eine Matrix, welche die Beziehung zwischen dem Drehimpuls und der Drehgeschwindigkeit ausdrückt. Je größer die Werte der Matrix, desto größer muss das ausgeübte Dreh- moment sein, um einen Körper um sein Massezentrum zu drehen.

12 3. Bestehende Ansätze

An+1 = An + hω˜nAn n+1 n n L = L + hτT • Reorthogonalisieren von An+1

• Berechnung der physikalischen Hilfsgrößen:

In+1−1 = An+1I¯−1An+1T ωn+1 = In+1−1 Ln+1

Erläuterung:

ω˜ = Dieser Operator erzeugt aus der Winkelgeschwindigkeit eine 3 × 3 - Matrix. Multipliziert mit einer zweiten Matrix hat die Ergebnismatrix die gleiche Form, als ob für jede einzelne Spalte der zweiten Matrix das Kreuzprodukt mit der Win- kelgeschwindigkeit berechnet worden wäre.

Dieser Algorithmus stellt einen möglichen Ansatz zur Simulation von Bewegungen eines Fest- körpers dar. Nachdem die physikalischen Eigenschaften des Körpers initialisiert worden sind, werden sie in einer Schleife zu jedem neuen Zeitpunkt berechnet, wobei der Einfluss externer Kräfte und Drehmomente berücksichtigt wird.

3.3.2. SIGGRAPH Papers: „Physically based modeling“ Die SIGGRAPH 2001 Course Notes — Physically based modeling [2] von Andrew Witkin und David Baraff bieten einen umfassenden Überblick über das Thema und geben durch Quellcode-Fragmente praktische Lösungsansätze vor. Sie beschäftigen sich mit vielen Teil- aspekten, wie Differentialgleichungen oder physikalisch modellierten Partikelsystemen. Im Artikel zu Thema Differentialgleichungen werden neben dem Algorithmus von Euler noch weitere numerische Verfahren zur Integration von ODEs erklärt. Dazu zählen die Midpoint- Methode h !! x(t + h) = x(t ) + h f x + f(x ) 0 0 0 2 0 und das Runge-Kutta-Verfahren vierter Ordnung

k1 = hf(x0, t0)

k h! k = hf x + 1 , t + 2 0 2 0 2 k h! k = hf x + 2 , t + 3 0 2 0 2

k4 = hf (x0 + k3, t0 + h) 1 1 1 1 x(t + h) = x + k + k + k + k . 0 0 6 1 3 2 3 3 6 4

13 3. Bestehende Ansätze

Das Kernstück dieser Artikel bildet allerdings der von Baraff verfasste Artikel Rigid Body Simulation. Im ersten Teil werden sehr ausführlich die Grundlagen der Simulation von Fest- körpern beschrieben. Zunächst wird erläutert, in welchem Zusammenhang die Lösung von Integralgleichungen bei der Simulation eine Rolle spielt, und eine Möglichkeit angedeutet, wie der Integrator bei der Implementierung aufgebaut werden kann. Außerdem werden al- le physikalischen Größen, die einen Festkörpern ausmachen, detailliert beschrieben, wobei auch ihre Bedeutung im Gesamtkontext erläutert wird. Es werden Ansätze gegeben, wie ein Festkörper als Datenstruktur implementiert werden kann, und wie einige der zugehörigen Si- mulationsfunktionen aussehen. Im Gegensatz zu Hecker (s. 3.3.1) wird neben der Verwendung von Matrizen für die Orientierung auch deutlich auf die Verwendung von Quaternionen ein- gegangen. An Beispielen werden die vermittelten Informationen gegen Ende des ersten Teils verdeutlicht. Im zweiten Teil spricht Baraff in groben Zügen auch die Kollisionserkennung an, dazu ver- wendet er das Separating Plane-Verfahren, genaueres dazu in Abschnitt 3.4.3. Bei der Kolli- sionsbehandlung unterscheidet er zwischen zwei Fällen: kollidierender Kontakt, bei dem sich die Körper durch den Aufprall wieder voneinander trennen und ruhender Kontakt, bei dem die Körper in ständigem Kontakt bleiben. Ein Beispiel für den ruhenden Kontakt wäre ein Körper, der auf einem anderen liegt. Im Fall des kollidierenden Kontakts leitet er die Kräfte nach der Kollision im Sinne des Impulserhaltungssatzes her, indem zwischen den Körpern ein Impuls übertragen wird, der zu einer sofortigen Änderung der Geschwindigkeiten führt. Ins- gesamt bietet Baraff mit den Beschreibungen und Code-Fragmenten seines Artikels ein gutes Fundament für eine darauf aufsetzende Implementierung.

3.4. Kollisionserkennung

In Zusammenhang mit der Kollisionserkennung wurden Verfahren untersucht, die sich zum Teil stark in Präzision und Geschwindigkeit unterscheiden. Im Folgenden werden eine schnel- le, approximierende Methode und zwei rechenintensive, präzise Vorgehensweisen vorgestellt.

3.4.1. Bounding volumes Bei der Verwendung von Bounding Volumes werden Geometrien, die auf Kollisionen getestet werden sollen, durch primitive Hüllvolumen, wie Kugeln oder Quader umschlossen. Anstatt die umschlossenen Geometrien selbst auf Kollisionen untereinander zu testen, werden die Hüllvolumen auf Überschneidung geprüft. Aufgrund der Einfachheit dieser Volumen können deren Überschneidungstest sehr schnell ausgeführt werden. Existiert zwischen zwei Hüllvolu- men keine Überschneidung, gibt es auch definitiv keine Kollision zwischen den eingehüllten Geometrien — der umgekehrte Fall gilt allerdings nicht. Deshalb können Bounding Volumes entweder als grobe Approximation zur Kollisionserkennung verwendet werden oder als Vor- verarbeitungsschritt, der aus einer großen Anzahl von Geometrien schnell eine kleine Menge potentiell kollidierender Paare herausfiltern kann, die anschließend einem genaueren, aber re- chenintensiveren Test unterzogen werden können. Die Kugel ist das einfachste Hüllvolumen

14 3. Bestehende Ansätze

— der Test, ob sich zwei Kugeln schneiden, prüft lediglich ob der Abstand der Kugelzentren kleiner ist als die Summe beider Radien. Allerdings ist dieses Volumen für eine große An- zahl an Geometrien ungeeignet, besonders bei Objekten, deren Ausdehnung in eine bestimm- te Richtung wesentlich stärker ist als in alle anderen Richtungen, wie z.B. bei einem Stab. Eine genauere Approximation sind hingegen Boxen, bzw. Quader, die das Objekt umgeben. Es existieren zwei verschiedene Arten, solche Boxen zu verwenden, wobei der Unterschied in der Orientierung der Boxen liegt. Im Fall der Axis Aligned Bounding Boxes (AABB) er- strecken sich die Kanten der Quader entlang der Achsen des globalen Koordinatensystems, wohingegen bei den Oriented Bounding Boxes (OBB) jede Box die Achsen des lokalen Ob- jektkoordinatensystems verwendet. Die zweite Variante ist in der Berechnung aufwendiger, dafür in den meisten Fällen aber präziser.

3.4.2. Closest Feature Tracking Das Verfahren Closest Feature Tracking [8, 9] wurde bereits 1991 von Lin und Canny vor- gestellt. Es kann Kollisionen zwischen konvexen Polyhedra feststellen. Um das Verfahren zu verstehen, müssen zunächst einige Begriffe geklärt werden. Unter einem Feature versteht man bezüglich eines Polyhedron8 einen Eckpunkt, eine Kante oder eine Seitenfläche. Die Voronoi- Region eines Features ist eine Region, innerhalb der sich alle Punkte näher am jeweiligen Feature befinden, als an allen anderen Features. Betrachtet man zwei nicht kollidierende Polyhedra A und B (s. Abbildung 3.1) und sind a und b die am dichtesten zusammen liegenden Punkte zwischen den Features FA auf A und FB auf B und zugleich auch die am dichtesten zusammen liegenden Punkte zwischen den Polyhedra selbst, dann liegt a in der Voronoi-Region von FB (a ∈ V (FB)) und b in der Voronoi-Region von FA (b ∈ V (FA)). Das Closest Feature Tracking nutzt die Tatsache, dass auch der Um- kehrschluss gilt: Sind a und b die dichtesten Punkte zwischen den Features FA auf A und FB auf B und liegen die Punkte jeweils in der Voronoi-Region des gegenüberliegenden Features, dann sind es auch dichtesten Punkte zwischen A und B. Der Algorithmus geht zum Finden der am dichtesten zusammen liegenden Punkte in folgen- den Schritten vor:

1. Berechne die dichtesten Punkte zwischen dem aktuellen Feature-Paar.

2. Falls jeder Punkt in der Voronoi-Region des gegenüberliegenden Features liegt, wurden die dichtesten Punkte gefunden.

3. Ansonsten ein oder beide Features aktualisieren und wieder zu Schritt 1 springen.

Das Aktualisieren eines Features bedeutet, im Falle des Features FA und des darauf befind- lichen Punktes a erst zu testen, bezüglich welcher begrenzenden Ebene der Voronoi-Region V (FB) der Punkt a auf der falschen Seite liegt und dann als neues Feature FA das benachbarte Feature auf der richtigen Seite der Ebene zu wählen. Man nutzt auch bei diesem Verfahren die

8Ein geschlossener Körper, dessen Oberfläche aus Polygonen (ebenen Vielecken) zusammengesetzt ist

15 3. Bestehende Ansätze zeitliche Kohärenz9 aus, indem man die Features des vorangehenden Schrittes zwischenspei- chert und als Ausgangspunkt für den aktuellen Schritt verwendet.

Abbildung 3.1.: Zweidimensionales Beispiel des Closest-Feature-Verfahrens. Die beiden Fea- tures, die am dichtesten beieinander liegen, befinden sich jeweils in der Voronoi-Region des gegenüberliegenden Features.

3.4.3. Separating plane Das Separating Plane-Verfahren, welches ebenfalls im Artikel von Baraff beschrieben wird ([2]), eignet sich für die Erkennung von Kollisionen zwischen konvexen Polyhedra. Das Ver- fahren stützt sich auf folgende Tatsache: Existiert zwischen zwei Geometrien eine Ebene, bezüglich der sich beide Geometrien auf verschiedenen Seiten befinden, so kann eine Über- schneidung der Geometrien ausgeschlossen werden (s. Abbildung 3.2). Diese Ebene kann ein- fach dadurch überprüft werden, indem getestet wird, ob sich die Vertices beider Geometrien in verschiedenen Halbräumen der Ebene befinden. Durch die Bedingung, dass die Polyhedra konvex sein sollen, wird die Anzahl der möglichen Ebenen beschränkt, da jede mögliche Ebe- ne entweder eine Fläche einer der beiden Geometrien beinhaltet oder dadurch gebildet werden kann, dass man ihre Normale durch das Kreuzprodukt zweier Kanten der beiden Geometrien berechnet und annimmt, dass die Ebene selbst durch eine der beiden Kanten verläuft. Somit ist der Aufwand des Verfahrens abhängig von der Polygonanzahl der Kollisionspartner. Ist die Suche nach einer trennenden Ebene erfolglos, so wird eine Kollision angenommen. Die Kollisionszeit lässt sich dadurch ermitteln, dass man versucht, den letzten Zeitpunkt vor dem gegenseitigen Eindringen der Geometrien zu finden, zu dem noch eine trennende Ebene exis- tiert. Der Ort der Kollision liegt dann auf oder in unmittelbarer Nähe zur Ebene. Bei diesem Verfahren kann man außerdem die zeitliche Kohärenz nutzen, indem man nach dem Finden einer Ebene einen „Zeugen“, d.h. die Fläche oder die Kanten, aus denen die Ebene gebildet wurde, in Zusammenhang mit der Kollision der beiden Geometrien abspeichert. Sollen die beiden Geometrien im nächsten Zeitabschnitt erneut getestet werden, so kann zunächst aus den gespeicherten Zeugen wieder eine Ebene gebildet und getestet werden.

9Die zeitliche Kohärenz sagt aus, dass sich die Position und Orientierung der betrachteten Geometrien während eines Zeitabschnitts nicht drastisch verändert.

16 3. Bestehende Ansätze

Ist diese Ebene gültig, findet keine Kollision statt und die aufwendige Suche nach einer neuen Ebene braucht nicht mehr durchgeführt werden.

Abbildung 3.2.: Zweidimensionales Beispiel des Separating-Plane-Verfahrens. Die Vertices der beiden Geometrien liegen bezüglich der hervorgehobenen Ebene in ver- schiedenen Halbräumen.

17 4. Anforderungen

Um das Konzept der Engine zu erstellen, muss zunächst klar sein, welche Anforderungen sie erfüllen muss. Während bei einer Engine, die speziell für ein Spiel entwickelt wird, die Anforderungen direkt herleitbar sind, müssen bei einer universellen Engine möglichst viele Anwendungsfälle von vornherein bedacht werden. Gleichzeitig kann man sie auch so konzi- pieren, dass sie im Nachhinein leicht erweiterbar oder auf neue Bedürfnisse anzupassen ist. In dieser Arbeit wurden beide genannten Ansätze verfolgt, um eine solche Engine zu schaffen.

4.1. Engine-Design

Die Engine an sich muss so gestaltet werden, dass sie in sinnvoller Art und Weise die wesent- lichen Bestandteile, die zum Entwickeln eines Spiels benötigt werden, miteinander verknüpft und übersichtlich zur Verfügung stellt.

Dazu gehört ein Fenstersystem, das es ermöglicht, das Spiel auf einem Betriebssystem mit grafischer Benutzerschnittstelle auszuführen. Anwendungen laufen unter diesen Betriebssys- temen gewöhnlich auf Basis einer Nachrichtenschleife, innerhalb welcher Nachrichten vom Betriebssystem empfangen werden, die über festgelegte Ereignisse informieren. Die Erzeu- gung eines Fensters und eine sinnvolle Integration der Nachrichtenschleife müssen deshalb vom Fenstersystem der Engine erledigt werden.

Des Weiteren muss die Engine in der Lage sein, eine Szene zu verwalten und unter Aus- nutzung moderner Grafikhardware auf dem grafischen Ausgabegerät darzustellen. Das setzt komplexe Mechanismen zur Verwaltung von dreidimensionalen Geometrien und deren Ei- genschaften bei der Darstellung, z.B. ihrer Texturen, voraus. Um die Unterstützung der Gra- fikkarte zu erlangen, muss eine entsprechende Grafikbibliothek verwendet und dazu sinnvoll ins Gesamtkonzept eingefügt werden.

Die Engine sollte zudem dafür sorgen, dass Töne und Musik während des Spielverlaufs ausgegeben werden können. Da es sich um eine dreidimensionale Szene handelt, müssen die- se Töne auch räumlich positionierbar sein, so dass sie z.B. mit zunehmender Entfernung zum „Hörer“ leiser werden und ein Stereoeffekt bei der Ausgabe wahrnehmbar ist.

18 4. Anforderungen

Da sich ein Spiel durch seine Interaktivität auszeichnet, werden Eingabegeräte benötigt, mit denen der Spieler Einfluss auf das Geschehen nehmen kann. Das setzt in Bezug auf die Engi- ne eine Verwaltung von allen gängigen Eingabegeräten voraus, deren Daten auch zur Laufzeit ständig ausgewertet werden können.

Innerhalb eines Spiels existieren in der Regel ein Fülle an Spielobjekten, deren Bandbreite von statischen Szenengegenständen über eigenständig agierende Spielfiguren bis hin zur kom- plett steuerbaren Repräsentation des Spielers reichen kann. Für diese Spielobjekte muss die Engine Datenstrukturen bereitstellen und sie auch verwalten können. Die Spielobjekte müssen so erweiterbar sein, dass bei der Entwicklung eines Spiel neue Objekt-Typen mit individuellen Eigenschaften und Verhalten davon abgeleitet werden können. Da diese semantischen Objekte größtenteils auch grafisch und akustisch repräsentiert werden sollen, ist von Seiten der Engine eine Verknüpfung zu den entsprechenden Modulen bereitzustellen. Wie in den nachfolgenden Abschnitten (4.3, 4.4) beschrieben, sollen diese Objekte auch bezüglich ihrer Bewegungen gemäß den Gesetzen der Physik simuliert und auf Kollisionen untereinander getestet werden, wofür die Engine ebenfalls adäquate Mechanismen bereitstellen muss.

4.2. Szenengraph

Der Szenengraph soll eine Vielzahl an Aufgaben, insbesondere bei der grafischen Darstellung der Szene, erfüllen. Die wichtigste Aufgabe ist hierbei die hierarchische Verwaltung der Sze- ne. Dazu soll der Szenengraph über eine angemessene Datenstruktur zur Repräsentation von Knoten des Graphen verfügen. Diese Struktur muss Mechanismen zur rekursiven Traversie- rung bereitstellen und so gestaltet sein, dass sich ohne großen Aufwand spezialisierte Knoten- typen daraus generieren lassen. Der Szenengraph sollte von Vornherein einen Grundstock von elementaren Knotentypen wie z.B. Knoten zur Speicherung von Geometrien und Transforma- tionen oder zur Gruppierung anderer Knoten bereitstellen, um alleine mit der Funktionalität der Engine eine einfache Szene erstellen zu können.

Des Weiteren muss eine Möglichkeit bestehen, den Szenengraphen zur Laufzeit durch Hin- zufügen oder Entfernen von Knoten zu verändern. Da die Szene nicht nur aus statischen Geo- metrien besteht, benötigt man Zugriff auf die Inhalte gewisser Knoten, um z.B. Transforma- tionen verändern zu können. Der Zugriff auf diese Inhalte soll durch Spielobjekte erfolgen, weshalb für diese eine Anbindung an den Szenengraphen, bzw. auf einzelne Knoten, bereitge- stellt werden muss. Der Graph erfordert auch eine ständige Aktualisierung seiner Inhalte, um dem Betrachter immer seinen aktuellen Status zu zeigen. Das Rendern der im Graphen gespei- cherten Inhalte sollte so schnell erfolgen, dass auch bei zusätzlichen aufwendigen Berechnun- gen, wie denen der Kollisionserkennung, beim Betrachter der Eindruck einer Darstellung in Echtzeit aufkommt. Um diesen Eindruck zu erreichen, sollten keine Inhalte an die Grafikkarte übergeben werden, die für den Betrachter nicht sichtbar sind, und die zu zeichnenden Inhalte so vorsortiert werden, dass die Hardware-Architektur der Karte optimal ausgenutzt wird.

19 4. Anforderungen

4.3. Physikalisch basierte Simulation

Bei der physikalisch basierten Simulation geht es in erster Linie um Plausibilität für den Be- trachter. Für diesen sollten alle simulierten Bewegungen so aussehen, wie er sie in der Realität erwarten würde. Dazu müssen die Bewegungen aller Spielobjekte, die sich physikalisch plau- sibel verhalten sollen, durch Anwendung der Gesetze der Dynamik und Kinematik berechnet werden. Folglich wird eine spezialisierte Form von Spielobjekten benötigt, die über alle physikali- schen Größen verfügt, die zur Simulation nötig sind. Es müssen Funktionen existieren, die eine Simulation der Bewegungen dieser Objekte über einen Zeitabschnitt hinweg durchführen können und dabei deren Status aktualisieren. Zur Berechnung von Bewegungen werden Dif- ferentialgleichungen integriert. Hier sollte ein Integrator geschaffen werden, der hinreichend genau aber auch schnell genug ist. Des Weiteren müssen alle Mechanismen zur physikalisch basierten Simulation übersichtlich in das Gesamtkonzept der Engine eingebunden werden.

4.4. Kollisionsmanagement

An die Kollisionserkennung werden zwei Bedingungen gestellt: Zum einen sollte sie über eine entsprechend hohe Genauigkeit verfügen, um keine starken Durchdringungen von Geometrien zuzulassen, die dem Betrachter sofort auffallen. Zum anderen ergibt sich aus der angepeilten Echtzeitdarstellung die Schnelligkeit, mit der Kollisionen erkannt werden müssen. Um beide Bedingungen zu erfüllen, ist ein Verfahren nötig, dass zunächst eine grobe, aber schnelle Vor- auswahl an Geometrien treffen kann, die anschließend genauer überprüft werden sollen. Die Erkennung muss neben dem Prüfen, ob eine Kollision zwischen zwei Geometrien stattfindet, auch den Zeitpunkt und Ort der Kollision ermitteln können, da diese Zusatzinformationen für eine physikalisch basierte Behandlung der Kollision erforderlich sind. Die Aufgabe der Kollisionsbehandlung wird demnach sein, in angemessener Art mit den phy- sikalisch simulierten Spielobjekten zu korrespondieren, um deren physikalische Eigenschaften zum Zeitpunkt einer Kollision neu zu berechnen.

4.5. Tech-Demo

Für die anschauliche Präsentation der Ergebnisse eignet sich eine Tech-Demo. Diese soll den interaktiven Charakter eines Spiels haben und zugleich die Fähigkeiten der Engine hervorhe- ben. Allerdings benötigt die Engine neben den bisher geforderten Eigenschaften noch weitere Fähigkeiten, um eine entsprechende Qualität der Darstellung zu erreichen. So sollte die darge- stellte Szene für eine glaubhafte Wirkung immer in eine Umgebung eingebettet werden. Die einzige Ausnahme bildet ein Weltraumszenario, bei dem sich die Geometrien beliebig in drei Dimensionen anordnen lassen. Ansonsten hat man bei der Wahl einer Umgebung die Entschei- dung zwischen geschlossenen Räumen oder der Erzeugung einer Außenlandschaft, einem so genannten Terrain — natürlich sind auch Mischformen aus beiden Umgebungstypen möglich. Um eine Szene interessanter zu gestalten, eignet sich der Einsatz von Spezialeffekten. Die Be-

20 4. Anforderungen herrschung von Partikelsystemen ist daher eine Eigenschaft, deren Integration in die Engine in Hinblick auf die Entwicklung einer ansprechenden Tech-Demo unbedingt angestrebt werden soll.

21 5. Konzept

Im diesem Kapitel soll ein Konzept für eine 3D-Game-Engine vorgestellt und erklärt werden. Dabei wird in folgenden Schritten vorgegangen: Zunächst wird der grundsätzliche Aufbau der Engine gezeigt und in diesem Zusammenhang die einzelnen Module und deren Zusammen- hänge angesprochen. Den Schwerpunktthemen dieser Arbeit, also Szenengraph, physikalisch basierte Simulation und Kollisionsmanagement werden eigene Abschnitte gewidmet, in denen die Konzepte dieser Themen im Detail behandelt werden.

5.1. Engine-Design

Die Engine besteht aus mehreren Modulen (s. Abbildung 5.1), von denen jedes einen inhalt- lich abgekapselten Teil der Funktionalität zusammenfasst, die für die Erstellung eines Spiels benötigt werden. Die Modularisierung sorgt für Übersichtlichkeit, Erweiterbarkeit und eine gute Möglichkeit, einzelne Module zu modifizieren, ohne dass die anderen davon beeinflusst werden. Das Kern-Modul ist dabei der GameManager, da dieser neben einer Vielzahl weiterer Aufgaben, die im Folgenden erwähnt werden, auch für die Anbindung und Kommunikation der anderen Module sorgt. Bei der Initialisierung des GameManager werden durch diesen im- plizit auch die meisten anderen Module vorbereitet.

Zugleich wird auch das Modul WindowSystem angewiesen, mit den Mittel des gegenwär- tigen Betriebssystems ein Fenster zu erzeugen und für die Ausgabe von grafischen Inhalten vorzubereiten. Beim Start des GameManager wird auch die Nachrichtenschleife des Fensters betreten und bis zum Eintreffen einer Fensternachricht, die ein Schließen des Fensters fordert, ständig durchlaufen. Der GameManager klinkt sich dabei in diese Schleife ein und wird bei jedem Durchlauf benachrichtigt. Bei der Benachrichtigung führt er eine Reihe von Anweisun- gen aus, deren Ziel eine Aktualisierung des Spielzustands ist. Diese ständige Aktualisierung durch den GameManager wird im weiteren Verlauf auch als Update-Schleife des Spiels be- zeichnet.

Ein Spiel besteht in der Regel aus einer Vielzahl von Objekten, die sich über verschiedene Eigenschaften und Verhaltensweisen voneinander unterscheiden. Für diese Objekte existiert zwar oft eine grafische und akustische Repräsentation, aber im Wesentlichen beschreiben sie die Semantik eines Objekts. So kann z.B. ein spezifisches Objekt für den Spieler und ein Ob- jekt für einen Gegner vorhanden sein, die beide durch die gleiche Geometrie dargestellt wer- den, aber eine völlig unterschiedliche Bedeutung im Spiel haben. Solche Spielobjekte müssen

22 5. Konzept

Abbildung 5.1.: Modularer Aufbau der Game-Engine. auch in der Engine berücksichtigt werden, weshalb eine Klasse für ein allgemeines Game- Object (s. Abbildung 5.2) bereitgestellt wird. Von dieser rudimentären Klasse können nach Bedarf weitere Klassen für spezifische Spielobjekte abgeleitet werden, die über zusätzlich benötigte Attribute und Methoden verfügen. Der GameManager übernimmt die Verwaltung aller Spielobjekte, die ihm zu diesem Zweck übergeben werden.Während der Update-Schleife ruft er deshalb bei allen Objekten deren Methode Update() auf, die von abgeleiteten Klassen überschrieben werden kann, um ein individuelles Objektverhalten zu erzeugen. Der gewähl- te Ansatz, innerhalb der Engine allgemeine GameObjects zu definieren und zu verwalten und durch Ableitung individuelle Spielobjekte bei der Programmierung spezifischer Spiele auf Ba- sis der Engine zu ermöglichen, sorgt für eine hohe Flexibilität und Erweiterbarkeit, da so eine Bandbreite erlaubt wird, die von statischen Szenenobjekten bis hin zu dynamischen Spielfigu- ren mit künstlicher Intelligenz reichen kann.

Abbildung 5.2.: Der GameManager verwaltet Objekte der Klasse GameObject, von der wie- derum spezifische Spielobjekte abgeleitet werden können.

23 5. Konzept

Interaktivität ist eine Grundlage jedes Spiels und kommt nur durch Eingaben eines Spielers zustande. Das Modul InputManager (s. Abbildung 5.3) ermöglicht die Bereitstellung der Da- ten von Eingabegeräteklassen: Keyboard, Mouse und Gamepad. Während die Klassen Key- board und Mouse selbsterklärend sind, verwaltet die Klasse Gamepad sämtliche Arten von Joysticks, Gamepads und weiteren ähnlichen Eingabegeräten. Zur Laufzeit gewährleistet der InputManager eine ständige Aktualisierung der Eingabedaten. Der GameManager sorgt dafür, dass die Daten der Eingabegeräte auch allen von ihm verwalteten Spielobjekten zur Verfügung stehen. So kann jedes dieser Objekte abhängig von seiner Bedeutung individuell auf Eingaben reagieren. So könnte z.B. ein Objekt, welches die Kamera darstellt, durch direkte Eingaben des Spielers gesteuert werden, wodurch dieser den Eindruck erhält, sich selbst in der Szene zu bewegen. Genauso ließe sich aber auch ein Objekt steuern, dem die Kamera in einem gewis- sen Abstand folgt. Durch die Entscheidung, drei Sorten von Eingabegeräteklassen gleichzeitig zu behandeln, sind Steuerungskombinationen möglich, wie das in vielen Spielen übliche si- multane Steuern mit Tastatur und Maus und ein Wechsel zur Laufzeit auf ein alternatives Eingabegerät.

Abbildung 5.3.: Der InputManager und seine drei Geräteklassen

Um die grafischen Inhalte zum richtigen Zeitpunkt innerhalb der Update-Schleife des Spiels zu aktualisieren und auszugeben, steuert der GameManager auch das Modul GraphicsMa- nager, welches für die visuelle Repräsentation der Szene verantwortlich ist und im nächsten Abschnitt ausführlicher beschrieben wird. Somit können bei Bedarf auch die verwalteten Spie- lobjekte mit geometrischen Repräsentationen innerhalb der Szene verknüpft werden. Das Bin- deglied zwischen GameObjects und GraphicsManager sind die sogenannten ComponentCon- troller. Diese können zur Laufzeit gezielt auf verschiedene Arten von Knoten innerhalb des Szenengraphen zugreifen und dort Veränderungen vornehmen. Auf die ComponentController wird im Zusammenhang mit dem detaillierten Konzept des Szenengraphen (s. 5.2) genauer eingegangen.

Innerhalb eines Spiel spielt die Ausgabe von Geräuschen eine maßgebliche Rolle. Im Falle einer 3D-Engine erwartet man von Geräuschen außerdem, dass sie sich räumlich zuordnen lassen, d.h. dass der Spieler z.B. bei der Ausgabe eines Soundeffekts erkennen kann, ob sich

24 5. Konzept die Tonquelle links, rechts, nah oder fern von seiner eigenen Position befindet. Das Laden, Abspielen und die Simulation von räumlicher angeordneter Soundeffekte übernimmt das Mo- dul SoundManager. Da der Szenengraph bereits über eine räumliche Strukturierung verfügt, bietet es sich an, auch die Position von Tonquellen über einen speziellen Knotentyp in den Graph zu integrieren. Das Modul GraphicsManager kann dazu selbst Befehle für die Tonaus- gabe an den SoundManager schicken.

Das Modul OpenGLWrapper sorgt dafür, dass Zeichenoperationen des Moduls Graphics- Manager in Befehle der Grafikbibliothek OpenGL umgesetzt werden, die in dem dafür vorge- sehenen Zeichenbereich des WindowSystem dargestellt werden. Die Zeichenbefehle der Gra- fikbibliothek werden direkt an die Grafikhardware gesendet, die sie aufgrund ihrer speziellen Architektur optimal verarbeitet. Das Modul verfügt dazu über eine Menge an häufig benötig- ten Methoden, z.B. für das Zeichnen von Vertex- und Index-Puffern oder das Setzen diverser Render-States. Durch die Abkapselung der OpenGL-spezifischen Befehle in einem eigenen Modul bietet die Engine auch Erweiterbarkeit gegenüber anderen Grafikbibliotheken, wie Di- rect3D. Voraussetzung ist die Generierung eines äquivalentes Moduls für die entsprechende Bibliothek.

Über die Module PhysicalSimulation, CollisionDetection und eine abgeleitete Klasse der GameObjects, den RigidBody-Objekten, wird in die Engine eine physikalisch basierte Simu- lation von Festkörpern und ein System zur Kollisionserkennung und -behandlung integriert. Da die Beschreibung dieser Module und ihrer Einbindung in das Gesamtkonzept etwas um- fangreicher ausfällt, werden sie in zwei eigenen Abschnitten (5.3, 5.4) erläutert. Des Weiteren verfügt die Engine über eine große Menge an Hilfsklassen, die von mehreren Modulen verwendet werden. Dazu gehören unter anderem Klassen für Vektoren, Matrizen und Quaternionen.

Im Wesentlichen stellt sich die Update-Schleife der Engine (s. Abbildung 5.4), die im Game- Manager ausgeführt wird, wie folgt dar: Zuerst werden die Daten der Eingabegeräte ausgewer- tet, damit diese den GameObjects im folgenden Schritt zur Verfügung stehen. Anschließend werden alle GameObjects durchlaufen und ihr Verhalten simuliert. Innerhalb dieser Simu- lation werden im Falle von RigidBody-Objekten, die eine Spezialisierung von GameObjects sind, mittels des Moduls PhysicalSimulation physikalisch plausible Bewegungen berechnet. Neue Positionen, Orientierungen und weitere Veränderungen, die sich bei der Simulation des GameObject-Verhaltens ergeben können, werden über zugehörige ComponentController auf den Szenengraph übertragen. Die daraus resultierenden Veränderungen von Knoten des Sze- nengraphen lassen dessen BoundingVolumes ungültig werden, welche deshalb wieder aktuali- siert werden müssen. Diese Aktualisierung ist auch die Voraussetzungfür den nächsten Schritt, in dem die BoundingVolumes eingesetzt werden, um in der groben Kollisionserkennungspha- se die Anzahl der möglicherweise kollidierenden Paare von Geometrien stark einzuschrän- ken und die herausgefilterten Paare in einer Liste von Collision-Objekten einzutragen. Diese Liste wird in der feinen Kollisionserkennungsphase genauer mit Hilfe des Separating Pla- ne-Verfahrens (3.4.3) untersucht. Erkannte Kollisionen werden direkt durch die betroffenen RigidBody-Objekte behandelt, indem diese mit Hilfe des Impulserhaltungssatzes ihre physi-

25 5. Konzept kalischen Eigenschaften unmittelbar zum Kollisionszeitpunkt berechnen. Die neu berechneten Werte fließen erst im nächsten Zeitabschnitt in die Simulation ein. Dadurch wird vermieden, dass die Kollisionsbehandlung selbst wieder Kollisionen hervorruft. Ein solches Vorgehen ent- spricht durch den daraus resultierenden geringfügigen Verzögerungen nicht einer perfekten Si- mulation von Festkörperverhalten, eignet sich aber aufgrund der geringeren Verarbeitungszeit besser für eine Berechnung in Echtzeit. Außerdem wirkt die Kollisionsbehandlung auf den Be- trachter plausibel, da in der Realität keine absolut starren Körper existieren, deren Kräfte ohne Verzögerung bei einer Kollision übertragen werden könnten. Nach Abschluss der Erkennung muss der Szenengraph zum Rendern vorbereitet werden, was eine Reihe von Berechnungen umfasst. Im letzten Schritt werden seine Inhalte schließlich grafisch ausgegeben.

Abbildung 5.4.: Die wesentlichen Elemente der Update-Schleife im GameManager.

26 5. Konzept

5.2. Szenengraph

Das Modul GraphicsManager verfügt über ein Objekt der Klasse SceneGraph. Diese Klas- se ist in der Lage, einen Szenengraphen, bestehend aus Objekten der Klasse SceneNode (s. Abbildung 5.5) zu verwalten. Dazu verfügt die Klasse über alle benötigten Methoden zum Traversieren, Aktualisieren und Rendern des Graphen. Des Weiteren speichert sie über einen Zeiger ein SceneNode-Objekt, welches den Wurzelknoten repräsentiert.

Abbildung 5.5.: Die Klasse SceneGraph ist Teil des GraphicsManager-Moduls.

Die Knoten des Graphen werden durch die Klasse SceneNode dargestellt. Diese Klasse besitzt einen Zeiger auf einen Elternknoten und einen oder mehrere Zeiger auf Kindknoten, welche ebenfalls SceneNode-Objekte sind. In der Regel haben Knoten nur einen Kindknoten, die Ausnahme bilden hierbei Gruppenknoten und deren Ableitungen. Ebenfalls eine Ausnah- me ist der Wurzelknoten. Dieser verfügt als einziger Knoten über keinen Elternknoten, da er in der Hierarchie ganz oben angeordnet ist. Jeder Knoten speichert außerdem eine orientierte BoundingBox, welche seine Geometrie, bzw. die gesamte Geometrie seiner Kinder umschließt.

Abbildung 5.6.: Die Klasse SceneNode und ihre Ableitungen

27 5. Konzept

Von der Klasse SceneNode, die die Basisfunktionalität eines jeden Knoten des Szenengra- phen umfasst, werden eine Menge von spezialisierten Knoten (s. Abbildung 5.6) abgeleitet:

• TransformNode: Dieser Knoten speichert eine Transformation, die auf den gesamten un- tergeordneten Teilbaum angewendet wird. Die Transformation kann aus einer Rotation und einer Translation zusammengesetzt sein. Befinden sich mehrere TransformNodes untereinander, so werden deren Transformationen beim Rendern einer Geometrie, die sich im Graph unterhalb befindet, akkumuliert. Um diese Gesamttransformation nicht jedesmal neu berechnen zu müssen, wird sie neben der lokalen Transformation im Kno- ten gespeichert. • GroupNode: Da es sinnvoll ist, innerhalb eines Graphen Gruppierungen von Knoten anzulegen, z.B. wenn mehrere Knoten der gleichen Transformation unterliegen sollen, existiert ein Knotentyp, der im Gegensatz zum SceneNode mehrere Kinder verwalten kann. Auch vom Gruppenknoten gibt es eine Ableitung:

– CollisionSwitchNode: Bei dem zur feinen Kollisionserkennung verwendeten Ver- fahren (5.4) zeigt sich ein direkter Zusammenhang zwischen der Polygonanzahl ei- ner Geometrie und der Zeit, die zum Kollisionstest benötigt wird. Um trotz schnel- ler Kollisionserkennung eine hohe Auflösung bei der grafischen Ausgabe verwen- den zu können wird der CollisionSwitchNode verwendet. Dieser Gruppenknoten hat genau zwei Kinder: Ein Kind ist Wurzelknoten des Teilbaums, der zum detail- lierten Rendern verwendet wird, während das andere Kind zum niedrig aufgelösten Gegenstück für den Kollisionstest führt. – SkyboxNode: Eine Skybox ist ein großer Würfel, der die Szene vollkommen um- schließt und dessen Innenseiten texturiert sind. In der Regel zeigen die Texturen Bilder einer weit entfernten Umgebung, wie z.B. eine Landschaft oder einen Him- mel. Befindet sich die Kamera immer im Zentrum der Skybox, d.h. dass die Box in ihrer Position den Bewegungen der Kamera folgt, so kann der Betrachter die An- ordnung der Seitenflächen als Würfel nicht erkennen und unterliegt der Illusion, sich in der dargestellten Umgebung zu befinden. Der SkyboxNode ist ein Knoten des Szenengraphen, der die Verwaltung einer solchen Skybox übernimmt.

• ShapeNode: Bei grafischen Inhalten, die in einem Spiel ausgegeben werden, handelt es sich vorwiegend um Geometrien und deren Eigenschaften bei der Darstellung. Diese Eigenschaften können z.B. das Material oder die Textur sein, mit der die Geometrie gezeichnet wird. Der Knoten, der diese zum Rendern benötigten Daten beinhaltet, ist der ShapeNode. Er speichert zwei NodeComponents: Geometry, welche die geometri- schen Daten enthält, und Appearance, in der die Eigenschaften bei der Darstellung der Geometrie zu finden sind. NodeComponents sind Klassen, die komplexere Datensätze innerhalb von Knoten des Szenengraphen kapseln — sie werden im weiteren Verlauf dieses Abschnitts genauer erklärt. • LightNode: Dieser Knoten ermöglicht es, eine Lichtquelle in der hierarchischen Anord- nung des Graphen zu platzieren. Die Beleuchtung betrifft nur die Knoten, die mit dem

28 5. Konzept

LightNode in einer Gruppe angeordnet sind, d.h. alle Knoten die sich unter dem letz- ten Gruppenknoten befinden, der sich in der Hierarchie über dem jeweiligen LightNode befindet. So wird es möglich, nur einzelne Teilbäume dynamisch auszuleuchten.

• CameraNode: Um eine Szene nicht nur von einem festen Blickpunkt aus zu betrachten, muss eine Kamera in dieser Szene gesetzt werden. Aus deren Position und Orientierung ergibt sich das Bild, welches dem Spieler präsentiert wird. Der CameraNode macht die Kamera zum Teil des Szenengraphen und ermöglicht somit, sie wie eine Geometrie mit- tels TransformNodes beliebig zu platzieren und über ComponentController zu bewegen.

• EnvironmentNode: Dieser Knoten wird verwendet, um Eigenschaften der Umgebung zu verwalten, die für die ganze Szene gelten. Ein Beispiel dafür ist Nebel, der zwar bezüg- lich Farbe und Dichte variiert werden kann, dessen Einstellungen aber global gelten.

• SoundNode: Wie bereits im vorherigen Abschnitt (5.1) erwähnt, wird der Szenengraph auch verwendet, um Tonquellen über einen Knoten räumlich zu platzieren. Die akku- mulierte Transformation, die beim Traversieren des Graphen bis zu einem SoundNode ermittelt wurde, wird dem SoundManager übermittelt, damit dieser das entsprechende Geräusch korrekt ausgeben kann.

Einige der Knoten speichern in ihrem Datenbereich so komplexe Strukturen, dass es sinn- voll ist, diese logisch in Form von Klassen, den so genannten NodeComponents, abzukapseln. Einige solcher NodeComponents können sogar von mehreren Knoten gleichzeitig verwendet werden, so ist es beispielsweise möglich, dass zwei ShapeNodes auf die gleiche Geometrie verweisen, dieser aber unterschiedliche Materialien und Texturen zuordnen. Von der Klasse NodeComponent existieren wiederum Spezialisierungen, die nur von zugehörigen Knotenty- pen verwendet werden können. So verwendet der TransformNode die von NodeComponent abgeleitete Klasse Transformation, der SoundNode die Ableitung Sound und der ShapeNode die beiden Ableitungen Geometry und Appearance (s. Abbildung 5.7). Die Klasse Appearance beinhaltet wiederum drei NodeComponents: Material, Texture und Shader. Während Material und Texture „klassische“ Eigenschaften bei der Darstellung von Geometrien darstellen, soll die Klasse Shader den Einsatz von Pixel- und Vertexshadern möglich machen. In einem Spiel gibt es eine Fülle von Bewegungen. In der hier konzipierten Engine kom- men Bewegungen durch das Verhalten der GameObjects zustande, sei es durch Benutzer- Interaktion über Eingabegeräte, aufgrund physikalischer Gesetze oder durch andere Regeln, denen das jeweilige Spielobjekt unterliegt. Damit diese Änderungen in Position und Orientie- rung der Objekte auch sichtbar werden, muss es eine Möglichkeit geben, sie auf den Szenen- graphen zu übertragen. Gleiches gilt für Änderungen bezüglich anderer NodeComponents wie der Komponente Sound, um ein Geräusch abzuspielen. Als Schnittstelle zwischen den Game- Objects und den Knoten des Szenengraph dient deshalb die Klasse ComponentController. Sie verweist auf einen Knoten des Szenengraphen und auf die darin gespeicherte NodeCom- ponent. Für die verschiedenen NodeComponents existieren passende Ableitungen der Klasse ComponentController (s. Abbildung 5.8). Beim Erzeugen eines spezifischen GameObjects ist das Hinzufügen verschiedener Com- ponentController möglich, die wiederum mit Knoten des Szenengraphen verknüpft werden.

29 5. Konzept

Abbildung 5.7.: Die Klasse ShapeNode und ihre NodeComponents.

Abbildung 5.8.: Die Klasse ComponentController und ihre Ableitungen.

30 5. Konzept

Während der Update-Schleife im GameManager, innerhalb der auch die Simulation aller GameObjects stattfindet, können die Spielobjekte über die ComponentController gezielt Ein- fluss auf Daten nehmen, die in den Knoten des Szenengraphen gespeichert sind. Abbildung 5.9 verdeutlicht diesen Vorgang.

Abbildung 5.9.: Der beispielhafte Einsatz von ComponentControllern zur Manipulation des Szenengraphen.

Um den Szenengraph zu rendern, könnte man ihn einfach traversieren und bei jedem Auffin- den eines ShapeNodes, direkt dessen Geometrie mit den passenden Darstellungseigenschaften zur Ausgabe an den OpenGLWrapper übergeben. Das hätte allerdings eine ständige Änderung von Texturen, Materialien und anderen Render-States zur Folge, welche den Geschwindig- keitsvorteil der optimierten Grafikhardware verringern würde. Somit macht es Sinn, die Geo- metrien vor dem Zeichnen nach den Eigenschaften zu sortieren, deren Wechsel am aufwendig- sten ist. In diesem Konzept wird zusätzlich zum Szenengraphen ein Sortierbaum verwendet, der die Geometrien samt Eigenschaften auf jeder seiner Ebenen mit einem Sortierkriterium vergleicht und an den entsprechenden Kindknoten übergibt. In den Blättern werden Geome- trien gleicher Eigenschaften schließlich in Listen gespeichert. Zum Zeitpunkt des Renderns werden alle Blätter des Sortierbaums durchlaufen und ausgegeben, wobei die Render-States pro Liste nur ein Mal gesetzt werden müssen.

31 5. Konzept

5.3. Physikalisch basierte Simulation

Die in diesem Konzept entwickelte physikalisch basierte Simulation beschränkt sich auf die Bewegungen von unverformbaren Festkörpern, die über einen festen Masseschwerpunkt ver- fügen. Die Simulation ergibt sich durch das Zusammenspiel des GameManagers, des Moduls PhysicalSimulation und der von RigidBody abgeleiteten Klassen. RigidBody ist wiederum von der Klasse GameObject abgeleitet und erweitert diese um eine Reihe physikalischer Größen, die zur Simulation benötigt werden. Neben der von GameObject geerbten Position und Orien- tierung verfügt die Klasse RigidBody über die Konstanten Gesamtmasse M, Trägheitstensor −1 Ibody im lokalen Koordinatensystem und dessen Inverse Ibody, welche aufgrund der häufigen Verwendung direkt vorberechnet wird. Zusätzlich werden für die Simulation die Werte der Funktionen Linearimpuls P (t) und Drehimpuls L(t) benötigt. Dazu kommen noch die Hilfs- größen: lineare Geschwindigkeit v(t), Winkelgeschwindigkeit ω(t) und die Inverse des Träg- heitstensors im Weltkoordinatensystem I−1(t). Außerdem bietet die Klasse noch Platz für die Gesamtkraft F (t) und das Drehmoment τ(t). Jedes Mal, wenn der GameManager seine Spielobjekte durchläuft, weisen die auf RigidBody basierten Objekte das Modul PhysicalSimulation an, ihren Status durch Simulation über das Zeitintervall ∆t = t1 − t0 neu zu berechnen. PhysicalSimulation bestimmt für die Position, die Orientierung, den Linearimpuls und den Drehimpuls jedes Festkörpers zunächst den Wert der zugehörigen Ableitungsfunktion zum vorherigen Zeitpunkt t0 und berechnet dann mittels numerischer Euler-Integration die neuen Werte zum aktuellen Zeitpunkt t1. Daraus können auch die neuen physikalischen Hilfsgrößen ermittelt werden, wodurch der Festkörper wieder einen aktuellen Status erlangt. Nach einer Reihe weiterer objektspezifischer Berechnungen übermittelt das Spielobjekt, falls es durch eine Geometrie dargestellt wird, seine neue Position und Orientierung mittels des TransformationControllers an den Szenengraphen.

5.4. Kollisionsmanagement

Bei der Kollisionserkennung gibt es einen direkten Zusammenhang zwischen der Genauig- keit und der Geschwindigkeit des verwendeten Verfahrens: Je genauer eine Überprüfung auf Kollisionen ist, desto mehr Ausführungszeit benötigt sie. In diesem Konzept wird deshalb ein zweistufiges Verfahren verwendet, welches zunächst schnell eine große Anzahl an möglichen Kollisionen ausschließt und die verbleibenden Geometrie-Paare anschließend mit hoher Prä- zision auf eine Überschneidung testet.

Die erste Stufe des Verfahrens (s. Abbildung 5.10) profitiert von im Szenengraphen gespei- cherten Informationen, denn sie kann die bereits vorhandenen BoundingBoxes verwenden, um die Anzahl der potentiell kollidierenden Geometrien stark zu verringern. Da im Graphen bereits orientierte BoundingBoxes für das View Frustum Culling1 vorliegen, wurde von der Verwendung von achsenparallelen BoundingBoxes, die zudem die Konturen der umschlosse- nen Geometrien weniger genau approximieren, abgesehen. Um BoundingBoxes gegeneinander zu testen, traversiert man zunächst den Szenengraphen,

1Ausblenden von Geometrien, die sich nicht im Sichtbereich befinden

32 5. Konzept bis man auf einen Knoten trifft, der von einem RigidBody mittels eines ComponentControl- lers gesteuert wird. Dann sucht man einen weiteren Knoten, dessen BoundingBox mit der des gerade betrachteten Knotens überlappt. Der Test auf Überschneidung wird mit Hilfe des von Gottschalk, Lin und Manocha vorgestellten Separating Axis-Theorems [5] durchgeführt. Die- ses Theorem besagt, das sich zwei orientierte BoundingBoxes nicht berühren, wenn zwischen ihnen eine trennende Achse existiert. Es gibt insgesamt 15 Möglichkeiten, eine trennende Ach- se L~ zu bilden: jeweils die drei Achsen der beiden Boxen und alle Kreuzprodukte der Achsen beider Boxen. Von allen möglichen Achsen L~ ist diejenige eine trennende Achse, die folgende Bedingung erfüllt:

~ ~ X ~ ~ X ~ ~ T · L > aiAi · L + biBi · L i i T~ = Translation von A nach B ~ ~ Ai, Bi= Normierte Achsen der Boxen A und B ai, bi = Skalierungsfaktor, der die Ausdehnung einer Box entlang einer Achse angibt (von Zen- trum bis zum Rand)

Findet man einen solchen Knoten, testet man alle ShapeNodes, die sich in den unterge- ordneten Teilbäumen beider Knoten befinden, auf Überschneidung ihrer BoundingBoxes und trägt die potentiell kollidierenden Paare in eine Liste ein. Die Speicherung innerhalb der Liste erfolgt in Objekten der Klasse Collision. Die Klasse Collision dient der Aufnahme aller Daten, die in Zusammenhang mit einer Kollision von Interesse sind. Dazu gehören unter anderem die Geometrien der beiden Partner und, falls vorhanden, die mit den Geometrien korrespondie- renden RigidBody-Objekte. Nach Abschluss der ersten Stufe verfügt man über eine Liste von Geometrie-Paaren, die möglicherweise kollidieren.

Abbildung 5.10.: Die erste Stufe der Kollisionserkennung mit Hilfe des Szenengraphen

Um diese potentiell kollidierenden Paare genauer zu untersuchen und gegebenenfalls den

33 5. Konzept

Kollisionszeitpunkt und -ort zu ermitteln, wird das Separating Plane-Verfahren verwendet. Beim ersten Kontakt der BoundingBoxes zweier Geometrien muss dazu zunächst die trennen- de Ebene neu bestimmt werden. Das geschieht durch Erstellen und Testen der potentiellen Ebenen (s. 3.4.3). Wird eine Ebene gefunden, speichert man die zugrunde liegenden Flächen oder Kanten im zugehörigen Collision-Objekt und liefert als Ergebnis des Tests die Infor- mation zurück, dass die Geometrien nicht kollidieren. Beim Prüfen einer Kollision, die im vorherigen Schritt bereits vorhanden war, kann somit zunächst die Ebene verwendet werden, die sich aus den gespeicherten Daten erzeugen lässt. Aufgrund der zeitlichen Kohärenz reicht der Test dieser Ebene oft schon aus, um eine Kollision auszuschließen. Findet man keine Ebene, so wird eine Kollision angenommen und man versucht den Kolli- sionszeitpunkt mit einer vorgegebenen Genauigkeit einzugrenzen. Mittels eines binären Ver- fahrens wird der Status beider Geometrien zwischen dem vorherigen Zeitpunkt t0 und dem aktuellen Zeitpunkt t1 untersucht. Ziel ist es, den spätesten Zeitpunkt innerhalb des gesamten Intervalls zu finden, zu dem eine trennende Ebene vorhanden ist. Bei dem Verfahren verwendet man einen Testzeitpunkt, zu dem man den Status der beteiligten RigidBody-Objekte simuliert, um den Ebenentest durchzuführen, und einen Zeitabschnitt, den man nach jedem erfolgreichen Test zum Testzeitpunkt addiert, bzw. nach erfolglosem Test vom Testzeitpunkt subtrahiert und der anschließend halbiert wird, um die Genauigkeit der Suche zu erhöhen. So beginnt die Su- che zum Zeitpunkt t 1 und fährt bei Erfolg mit dem Zeitpunkt t 3 , bzw. bei Misserfolg mit dem 2 4 Zeitpunkt t 1 fort. 4

Abbildung 5.11.: Eingrenzung des Kollisionszeitpunkts durch Wiederholung der Simulation mit feineren Zeitintervallen Wenn der jeweils halbierte Zeitabschnitt nach einigen Wiederholungen einen festen Grenz- wert unterschreitet, ist der Kollisionszeitpunkt mit der gewünschten Genauigkeit gefunden worden. Der Ort der Kollision befindet sich auf der zugehörigen Ebene und kann schnell er- mittelt werden. Bei einem Zusammenstoß zwischen einer Fläche und einem Eckpunkt der

34 5. Konzept beiden Geometrien entspricht der Ort dem Eckpunkt, bei einer Kollision zweier Kanten ergibt sich der Ort aus dem Schnittpunkt der Kanten. Diese Informationen und auch die Normale am Kollisionspunkt werden wieder im zugehörigen Collision-Objekt für eine anschließende Kollisionsbehandlung vermerkt.

Die Behandlung wird von den beteiligten RigidBody-Objekten, die an einer Kollision be- teiligt sind, selbst übernommen. Dazu werden die physikalischen Größen des Objekts und die bei der Kollision ermittelten Daten verwendet, um mit Hilfe des Impulserhaltungssatzes die Kräfte zu berechnen, die unmittelbar nach dem Zusammenstoß wirken. Vorher wird al- lerdings die relative Geschwindigkeit der beiden Körper zueinander berechnet, um daraus zu erkennen, ob die Körper zusammenstoßen, ruhen oder sich bereits voneinander trennen. Die- ser vorherige Test ist wichtig, da im letzten Fall keine neuen Kräfte ermitteln werden müssen. Werden die Kräfte berechnet, so zeigen sie erst beim nächsten Durchlauf der Update-Schleife des Spiel eine Wirkung, nämlich dann, wenn die physikalisch basierte Simulation den Status des Festkörpers aktualisiert.

35 6. Implementierung

Bei der Implementierung der Engine wurden die Ansätze des Konzepts praktisch umgesetzt und durch zahlreiche weitere Konzepte, die für die Entwicklung eines Spiels benötigt werden, erweitert. Realisiert wurde die Umsetzung in der Programmiersprache C++ für das Betriebs- system XP. Die grafische Ausgabe stützt sich vorrangig auf die Grafik- bibliothek OpenGL, eine Anbindung des ebenfalls sehr verbreiteten Äquivalents ist nur in Ansätzen vorhanden. Für die Verwaltung von Texturen, Geräuschen und Eingabege- räten kommen die Bibliotheken DevIL, FMOD und DirectInput zum Einsatz. Die Engine ist in Form einer statischen Bibliothek gekapselt und wird für die Entwicklung einer konkreten Anwendung, bzw. eines Spiels, eingebunden. Da die Implementierung unter dem Arbeitstitel FuEngine erfolgte und um bei der Einbindung der Bibliothek zwischen Engine-Klassen und anwendungsspezifischen Klassen unterscheiden zu können, tragen alle Engine-Klassen das Präfix „Fu“. So heisst z.B. die Szenengraph-Klasse FuSceneGraph. Die Beschreibungen der Implementierung beschränken sich auf die wesentlichen Klassen und Abläufe.

6.1. Engine-Design

Hauptklasse der gesamten Engine ist die Klasse FuGameManager. Sie verfügt über drei we- sentliche Methoden für die Initialisierung, die Aktualisierung und das Beenden. Eine ähnliche Strukturierung lässt sich auch in vielen anderen Klassen der Engine wiederfinden. Die Metho- de Init() sorgt neben dem Erzeugen eines Fensters über die Klasse FuWindowSystem auch für die Initialisierung der meisten anderen Hauptmodule. Im Gegenzug dazu veranlasst die Me- thode Exit() ein Beenden und eine Speicherbereinigung der Hauptmodule. Nach abgeschlos- senen Initialisierungen bewirkt ein Aufruf der Methode Start() den Beginn der Nachrichten- schleife des Fenster und eine Einbettung der Engine in diese Schleife. Dadurch wird erreicht, dass in jedem Durchlauf der Nachrichtenschleife die Methode Update() des FuGameManager aufgerufen wird. Diese Methode bekommt als Parameter jeweils die Ausführungszeit überge- ben, die der vorherige Durchlauf der Schleife benötigt hat. Aufgrund des Wissens über die Ausführungszeit werden zeitlich basierte Simulationen, die von der Aktualisierungsrate wei- testgehend unabhängig sind, überhaupt erst möglich. Innerhalb der Update-Methode werden die bereits im Konzept angesprochenen Schritte (5.1) durchgeführt, bezüglich der Kollisions- erkennung und -behandlung ist allerdings ein großer Mehraufwand nötig — die Details wer- den im Abschnitt 6.4 erläutert. Um der Anwendung, die die Engine verwendet, die Möglich- keit zu bieten, innerhalb jedes Aktualisierungsschritts eigene Aktionen durchzuführen, wird dem FuGameManager beim Starten der Hauptschleife ein Zeiger auf eine Funktion der An-

36 6. Implementierung wendung übergeben, die dieser am Ende seiner Methode Update() aufruft. Neben der Klasse FuGameManager existieren noch eine Reihe weiterer Manager-Klassen, wie z.B. der FuTextureManager oder der FuSoundManager. Die Klassen dienen der Verwal- tung von Daten, die Art der Daten geht in der Regel aus dem Klassennamen hervor. Da von den Klassen jeweils nur eine Instanz vorhanden und diese von jeder Stelle aus aufrufbar sein soll, sind die Manager-Klassen als Singletons1 implementiert.

6.2. Szenengraph

Die gesamte Verwaltung und Darstellung der Szene wird von der Klasse FuGraphicsManager übernommen. Diese Klasse verteilt die Aufgaben auf einen Szenengraph und einen Rend- ergraph. Der Szenengraph, dessen Funktionalität in der Klasse FuSceneGraph zu finden ist, speichert alle Daten hierarchisch in Form von Knoten ab (s. 5.2). Als Basisklasse für alle Knoten dient dabei die Klasse FuSceneNode, welche Methoden für das Setzen und den Zugriff auf Eltern- und Kindknoten umfasst und des Weiteren ein Boun- dingBox-Objekt verwaltet. Es existieren innerhalb der Engine einige Ableitungen dieser Basis- klasse, mit denen spezielle Knotentypen beschrieben werden, die im Szenengraphen benötigt werden.

Der Szenengraph bekommt bei der Initialisierung, die im Gegensatz zu den meisten anderen Modulen nicht von der Klasse FuGameManager sondern von der Applikation vorgenommen wird, einen Zeiger auf einen Knoten übergeben, der den Wurzelknoten des Graphen darstellt. Ob die Knoten, die die Szene darstellen, bereits vor diesem Schritt erzeugt und dem Wurzel- knoten untergeordnet werden oder der Wurzelknoten erst zur Laufzeit dynamisch erweitert wird, ist völlig freigestellt. Die Methode Exit() der Szenengraph-Klasse führt im Gegenzug zum rekursiven Löschen aller Knoten.

Weiterhin verfügt die Klasse FuSceneGraph über Methoden zum Aktualisieren der Hüll- volumen aller Knoten. Diese Aktualisierung ist so konzipiert, dass die BoundingBox eines Elternknotens die des Kindknotens, bzw. bei einem Gruppenknoten die Boxen mehrerer Kin- der umfasst. Dazu beinhaltet die Klasse BoundingBox selbst eine Methode Merge(), die es erlaubt das gemeinsame Hüllvolumen mit einer weiteren Box zu berechnen. Bei der Verschmelzung der orientierten Boxen werden außerdem alle Transformationsknoten, bzw. Objekte der Klasse FuTransformNode, durchlaufen, um die globale Transformationsma- trix zu berechnen, mit der die Boxen transformiert werden müssen. Da diese akkumulierte Matrix auch in weiteren Berechnungsschritten nötig ist, wird sie neben der lokalen Transfor- mation im Transformationsknoten gespeichert. Die aktuelle Gültigkeit der gespeicherten glo- balen Matrix wird mittels einer booleschen Flag vermerkt, so dass die Matrix nur bei Bedarf neu berechnet werden muss. Für eine schnelle Kollisionserkennung speichert jeder Knoten des Szenengraphen ab, ob der Knoten selbst oder einer seiner Kindknoten auf mögliche Kol- lisionen getestet werden soll. Da sich diese Information zur Laufzeit ändern kann, wird ihre

1Ein Singleton ist ein Entwurfsmuster für eine Klasse, von der nur ein einzelnes globales Objekt bestehen soll, auf welches von zahlreichen Klassen aus zugegriffen werden kann.

37 6. Implementierung

Gültigkeit zusammen mit der Aktualisierung der BoundingBoxes auf den neusten Stand ge- bracht. Ausführlichere Informationen dazu werden im Abschnitt 6.4 gegeben.

Die Methode Update() der Klasse FuSceneGraph besteht im Grunde aus drei Schritten. Der erste Schritt traversiert den kompletten Graphen, wertet Änderungen aus und sorgt dafür, dass die geänderten Werte allen Knoten zu Verfügung stehen, die sie benötigen. Mitunter die wichtigsten Informationen, die sich laufend ändern, sind die Position und die Orientierung der Kamera. Sie werden bestimmt, indem die globale Transformationsmatrix des Kameraknotens, repräsentiert durch die Klasse FuCameraNode, berechnet wird. Wird die Inverse dieser Trans- formationsmatrix dem Renderer übergeben, kann dieser die Szene aus der Sicht der Kamera darstellen. Des Weiteren ist für die einzelnen Knoten, die die Geometrie des Terrains beinhal- ten, die Position der Kamera von großer Bedeutung. Diese Objekte der Klasse FuTerrainNode schränken nämlich die Anzahl der von ihnen gezeigten Vertices abhängig von der Entfernung zur Kamera ein. Auf das Terrain und das zugrunde liegende Level-of-Detail-Verfahren wird im Abschnitt 6.5 genauer eingegangen. Bei der rekursiven Traversierung jedes Knotens werden bereits ermittelte Daten durch ein FuRenderContainer-Objekt an die Kindknoten weiterge- reicht. Diese Klasse dient dazu, Daten die zum Rendern benötigt werden, abzukapseln und sie zwischen den Klassen FuSceneGraph und FuRenderGraph auszutauschen. Im ersten Update- Schritt werden sie allerdings nur für die Übergabe von Transformationsmatrizen zum Zwecke der Akkumulierung verwendet.

Nach der Aktualisierung des Szenengraphen und somit auch der Kamera-Transformation ist es im zweiten Schritt möglich, die Ebenen des Kamerafrustums in der Methode CalcFrus- tumPlanes() zu berechnen. Danach stehen die sechs Ebenen in Form von FuPlane-Objekten als Klassenvariablen in FuSceneGraph zur Verfügung.

Der dritte Schritt dient im Wesentlichen dazu, die Szeneninformation für das Rendern auf- zubereiten. So werden etwa im Fall von FuShapeNode-Objekten, also den Knoten die Geome- trie und deren Erscheinungsbild beinhalten, alle benötigten Information in einem FuRender- Container-Objekt verpackt und dem RenderGraph übergeben. Allerdings schränkt der dritte Schritt die Geometrien auf solche ein, die sich im Sichtfeld der Kamera befinden. Deshalb wird die BoundingBox jedes Szenengraph-Knotens vor einer weiteren Behandlung durch die Methode ViewFrustumTest() gegen das Kamera-Frustum getestet. Dabei werden die zuvor be- rechneten sechs Ebenen verwendet. Liegt die Box komplett außerhalb des Frustums, können der Knoten und alle seine untergeordneten Knoten bezüglich des Rendering ignoriert werden. Ausgenommen vom View-Frustum-Test sind die FuSoundNode-Objekte, da deren enthalte- ne Töne auch wahrgenommen werden sollen, wenn sie außerhalb des Sichtfelds auftreten. Die Knoten, die Terrain-Stücke repräsentieren, also die FuTerrainNode-Objekte, werden aus Geschwindigkeitsgründen nicht über den RenderGraph gezeichnet. Genaueres dazu wird im Abschnitt 6.5 erläutert.

Die grafische Ausgabe der vom Szenengraph aufbereiteten Geometrie-Daten wird von der Klasse FuRenderGraph gewährleistet. Schon bei der Übergabe der Daten mit Hilfe von Fu- RenderContainer-Objekten werden diese nach mehrstufigen Kriterien sortiert, um teure Sta-

38 6. Implementierung tuswechsel der Grafikbibliothek zu vermeiden (s. Abbildung 6.1). Zunächst prüft man bei jedem RenderContainer, ob Multitexturierung, einfache Texturierung oder keine Texturierung verwendet wird. Anschießend wird nach Texturen sortiert und im letzten Schritt nach Mate- rialien. Die Sortierung erfolgt mittels eines Baums aus FuRenderNode-Objekten. Jeder dieser Knoten speichert die Tiefe, auf der er sich im Baum befindet, und ein Sortierkriterium. So kann ein Knoten z.B. die Tiefe 2 und als Sortierkriterium einen Zeiger auf ein Texturobjekt erhalten, wodurch er beim Sortiervorgang aufgrund seiner Tiefe die Anweisung erhält, das Texturobjekt des übergebenen FuRenderContainer-Objekts auszulesen und den Wert mit dem eigenen Sortierkriterium zu vergleichen. Nur bei erfolgreichem Test wird der Knoten den Con- tainer an seine Kindknoten zu weiteren Sortierung übergeben. Die zugehörigen Kindknoten erhalten folglich nur Container mit der selben Textur. Während die internen Render-Knoten FuRenderContainer-Objekte an ihre Kindknoten weiterleiten, werden die Container in den Blattknoten des Sortierbaums in dynamischen Arrays, genauer gesagt Vektoren der Standard Template Library, abgelegt.

Abbildung 6.1.: Beispiel für die Funktionsweise der Klasse FuRenderGraph.

Zum Zeitpunkt des Rendering, bei dem bereits alle Container sortiert vorliegen, werden der Sortierbaum traversiert, alle darin befindlichen Arrays durchlaufen und die in den Containern gekapselten Daten zum Zeichnen an die Grafikbibliothek übergeben. Vor dem Durchlaufen einer Liste werden einmalig die Render-States gesetzt, nach denen zuvor sortiert wurde. Die Grafikbibliothek wird grundsätzlich nur über eine Wrapper-Klasse angesprochen, so dass es für die Engine-Klassen an sich keine Rolle spielt, welche Grafikbibliothek tatsächlich zum Zu- ge kommt. Wie bereits erwähnt, stützt sich die aktuelle Implementierung auf OpenGL, so dass alle Aufrufe der Klasse Fu3DAPIWrapper an die spezialisierte Klasse FuOpenGLWrapper weitergeleitet werden.

39 6. Implementierung

6.3. Physikalisch basierte Simulation

Die Physiksimulation erfolgt durch ein Zusammenspiel der Klasse FuRigidBody, bzw. de- ren Ableitungen, und der Klasse FuPhysicalSimulation. Jedes RigidBody-basierte Spielobjekt ruft in seiner überschriebenen Methode Update() die von FuRigidBody geerbte Methode Si- mulate() mit dem momentanen Zeitintervall als Parameter auf. Dadurch erreicht man einen Aufruf der Methode SimulateRigidBodyDynamics() von FuPhysicalSimulation, der wieder- um das Zeitintervall aber auch ein Zeiger auf das aufrufende RigidBody-Objekt übergeben wird. Somit erhält die Simulationsklasse Zugriff auf alle physikalischen Größen des betrach- teten Spielobjekts. Innerhalb der Simulation werden folgende Schritte abgearbeitet: Zunächst werden die phy- sikalischen Größen Position, Orientierung, linearer Impuls und Drehimpuls des FuRigidBo- dy-Objekts elementweise ausgelesen und in ein Array kopiert. Anschließend werden auch die Werte der linearen Geschwindigkeit und der Drehgeschwindigkeit, der Kraft und des Dreh- moments herangezogen, um ein zweites Array zu füllen. Aus dem ersten Array, welches dem Zustand des Körpers zum Zeitpunkt t0 entspricht, dem zweiten Array, welches die Ableitung der Werte des ersten Arrays zum Zeitpunkt t0 darstellt, und dem Zeitintervall t1 − t0 lässt sich mittels der Euler-Integration nun der Zustand des Körpers zum Zeitpunkt t1 berechnen. Dieser neue Zustand wird wieder in ein FuRigidBody-Objekt umgerechnet, wobei Position, Orientierung und die Impulse lediglich kopiert und die restlichen physikalischen Größen aus den neuen Werten berechnet werden. Wie im nachfolgenden Abschnitt beschrieben, benötigt die Kollisionserkennung die Möglich- keit, die Simulation eines Körpers wieder auf den vorherigen Zeitpunkt zurückzusetzen. Dazu verfügt die Klasse FuRigidBody über eine Methode GenerateBackup(), die den aktuellen Sta- tus des Körpers speichert und in einer Klassenvariable ablegt.

6.4. Kollisionsmanagement

Die Erkennung und Behandlung von Kollisionen werden durch eine umfangreiche Implemen- tierung realisiert. Zunächst müssen bei der Initialisierung des Spiels einige Vorbereitungen getroffen werden. Zum einen werden die Geometrien, gekapselt in der Klasse FuGeometry, die beim Laden von 3D-Modellen ausgelesen werden, mit einer Zusatzinformation versehen: Einem dynamischen Array aus allen Flächen der Geometrie. Da bei der ersten Phase der Kol- lisionserkennung die BoundingBoxes des Szenengraphen überprüft werden (s. 5.4) und nicht alle Knoten kollidieren oder eine Kollision verursachen können, erhalten Szenengraphknoten ein zusätzliches Attribut, welches den Kollisionstyp jedes Knotens beschreibt. Der Kollisions- typ kann fünf verschiedene Werte annehmen. Sagt er aus, dass der Knoten keine Kollision enthält, können er und seine untergeordneten Knoten beim Testen ignoriert werden. Ist der Kollisionstyp eine dynamische Kollision, so korrespondiert der Knoten mit einem bewegli- chen Spielobjekt, dessen Position und Orientierung sich in jedem Schritt ändern kann und so- mit in der Lage ist, aktiv eine Kollision zu verursachen. Der statische Kollisionstyp hingegen kann nur passiver Partner einer Kollision sein, die von einem Knoten mit dynamischem Kolli- sionstyp ausgelöst wird. Die beiden verbleibenden Kollisionstypen sagen aus, ob ein Knoten

40 6. Implementierung im untergeordneten Teilbaum einen dynamischen oder statischen Kollisionstyp besitzt. Eine Aktualisierung der Kollisionstypen im Szenengraphen erfolgt zusammen mit der Aktualisie- rung der BoundingBoxes.

6.4.1. Erkennung Durch Aufruf der Methode FindCollisions() der Klasse FuCollisionDetection startet die erste Phase der Kollisionserkennung. Der Szenengraph wird traversiert und nach Knoten des dyna- mischen Kollisionstyps durchsucht. Wird ein solcher Knoten gefunden, durchsucht die Me- thode FindCollisionsPartners() den Szenengraphen erneut, dieses Mal allerdings sowohl nach Knoten mit dynamischem Kollisionstyp als auch nach Knoten mit statischem Kollisionstyp. Für jeden gefundenen Knoten wird ein Test der BoundingBoxes beider Knoten durchgeführt. Nur bei einer Überschneidung der Boxen wird die Methode NoteCollisions() mit den beiden Knoten als Parameter aufgerufen. Diese Methode durchsucht die Teilbäume, die den beiden Knoten untergeordnet sind, gezielt nach Objekten vom Typ FuShapeNode, also den Knoten, in denen Geometrien gespeichert werden, und speichert potentiell kollidierende Paare von Geometrien in einem dynamischen Array ab. Zur Verwaltung von Kollisionsdaten existiert die Klasse FuCollision, die noch über zahlreiche weitere Attribute verfügt, die erst im Laufe der weiteren Kollisionserkennung gefüllt werden.

Nachdem nun alle Szenengraphknoten gefunden wurden, die kollidieren können, müssen die zu diesen Knoten gehörenden FuRigidBody-Objekte, bzw. Objekte der von FuRigidBo- dy abgeleiteten Klassen, ihnen zugeordnet werden. Dazu wird der Transformationscontroller jedes RigidBody-Objekts aus der Liste der Spielobjekte ausgelesen. Mittels des Transforma- tionscontrollers lässt sich der Szenengraphknoten bestimmen, der vom Controller verwendet wird. Stimmt dieser Knoten mit einem Knoten überein, der in einem FuCollision-Objekt des Kollisionsarrays enthalten ist, wird das RigidBody-Objekt ebenfalls als Attribut der gleichen Kollision vermerkt. Nach einer solchen Zuordnung befindet sich in jedem FuCollision-Objekt des Kollisionsarrays mindestens ein gültiger Zeiger auf ein RigidBody-Objekt.

Im Anschluss daran findet die zweite Erkennungsphase statt. Diese wird über die Methode ExactCollisionTest() der Klasse FuCollisionDetection aufgerufen und erhält als Parameter ein einzelnes FuCollision-Objekt und das aktuelle Zeitintervall. Die Methode liefert einen boole- schen Wert zurück, der Aufschluss darüber gibt, ob tatsächlich eine Kollision stattgefunden hat. Zu Beginn des Tests müssen Position und Orientierung beider Kollisionspartner ermittelt werden. Ist dem Partner ein RigidBody-Objekt zugeordnet, kann die benötigte Information direkt aus dessen Attributen ausgelesen werden. Andernfalls entspricht die Position dem Zen- trum der BoundingBox des gespeicherten Szenengraphknotens und die Orientierung wird aus den Achsen der Box berechnet.

Nun sind alle Informationen vorhanden, um mit der Methode FindSeparatingPlane() nach einer trennenden Ebene zwischen den Geometrien zu suchen. Innerhalb der Suchmethode wer- den zunächst alle Flächen der ersten Geometrie durchlaufen. Aus jeweils einer Fläche wird eine Ebene gebildet und diese Ebene mittels der Methode TestSeparatingPlane() überprüft. Ist

41 6. Implementierung der Test erfolgreich, liefert FindSeparatingPlane() den Wert true zurück. Andernfalls werden die Flächen der zweiten Geometrie untersucht und danach falls nötig Kantenpaare der beiden Geometrien. Im Falle der Überprüfung von zwei Kanten wird das Kreuzprodukt der beiden Kantenvektoren berechnet und als Normale der Ebene verwendet. Die Ebene selbst verläuft durch eine der Kanten. Lässt sich nach dem Testen aller möglichen Ebenen keine finden, die die beiden Geometrien trennt, so gibt FindSeparatingPlane() den Wert false zurück. Wie die trennende Ebene gefunden wird, erlaubt Rückschlüsse über die Art des Kontakts, der zwi- schen den beiden Körpern auftritt. Verläuft die Ebene durch eine Fläche der Geometrien, so wird ein Kontakt zwischen einer Fläche und einem Vertex angenommen, andernfalls wird ein Kontakt zwischen zwei Kanten vermutet.

Kann man sofort zum aktuellen Zeitpunkt t1 eine trennende Ebene ermitteln, so ist zwi- schen den Geometrien keine Kollision eingetreten. Ansonsten muss der Kollisionszeitpunkt mit einer festgelegten Genauigkeit eingegrenzt werden. Dazu startet eine Schleife, die solan- ge wiederholt wird, bis die zeitliche Genauigkeit erreicht ist. Vor dem ersten Durchlauf der Schleife wird das aktuelle Zeitintervall ∆t = t1 − t0 halbiert. Die Variable sampleTime, die den Testzeitpunkt darstellt, wird mit dem halbierten Zeitintervall gleichgesetzt. In der Schleife wird zunächst die Simulation der an der Kollision beteiligten RigidBody-Objekte auf den im Körper gespeicherten Status zum Zeitpunkt t0 zurückgesetzt (s. 6.3) und anschließend bis zum Testzeitpunkt fortgeführt. Der Status der Körper entspricht demnach im ersten Durchlauf dem Status zum Zeitpunkt t 1 . Das Zeitintervall selbst wird jeweils nach diesem Schritt halbiert, 2 um die Genauigkeit zu erhöhen. Zum neuen Testzeitpunkt wird nun erneut das Vorhandensein einer trennenden Ebene getestet.

Wurde bereits in einem vorherigen Schleifendurchlauf eine Ebene gefunden, wird diese aufgrund der angenommenen zeitlichen Kohärenz für den Test verwendet, ansonsten wird ei- ne komplett neue Suche durchgeführt. Ist der Test, bzw. die Suche erfolgreich, so muss die Kollision etwas später stattgefunden haben, weshalb der Testzeitpunkt um das Zeitintervall er- höht wird. Andernfalls wird der Testzeitpunkt um das Zeitintervall verringert. Nach Abbruch der Schleife kennt man nun den annähernd genauen Zeitpunkt, in dem zum letzten Mal eine trennende Ebene vorhanden war. Wurde während der Schleife allerdings keine Ebene gefun- den, so muss die Kollision in unmittelbarer zeitlicher Nähe zum Zeitpunkt t0 eingetreten sein, weshalb die Simulation der beteiligten RigidBody-Objekte auf diesen Zeitpunkt zurückgesetzt wird.

Nachdem der Kollisionszeitpunkt bekannt ist, können auch die restlichen, zur Kollisions- behandlung benötigten Informationen, wie Ort und Normale der Kollision, berechnet und im zugehörigen FuCollision-Objekt vermerkt werden. Da bezüglich der Kollisionen zeitliche Ko- härenz angenommen wird, verlieren FuCollision-Objekte nicht sofort nach der Kollisionsbe- handlung ihre Gültigkeit, sondern werden mindestens bis zum nächsten Update-Schritt des FuGameManager im Kollisionsarray gespeichert. Vor dem Einfügen einer neuen Kollision in das Array wird geprüft, ob nicht bereits ein FuCollision-Objekt mit dem selben Geometrie- Paar darin vorhanden ist. In der zweiten Phase der Erkennung können somit viele Kollisionen ohne aufwendige Suche einer Ebene ausgeschlossen werden.

42 6. Implementierung

Da die Berechnungszeit der zweiten Phase direkt von der Anzahl der Flächen der beiden zu testenden Objekte abhängt, gibt es die Möglichkeit, für eine Geometrie im Spiel ein hoch aufgelöstes Modell für die Darstellung zu verwenden und ein alternatives Modell mit wenigen Polygonen für die Kollisionserkennung. Dazu existiert der Szenengraphknoten FuCollision- SwitchNode, der von FuGroupNode abgeleitet ist und über genau zwei Kindknoten für die beiden Modelle verfügt. Dieser Knoten verzweigt beim Rendern des Szenengraphen zum er- sten Knoten und bei der Kollisionserkennung zum zweiten Knoten. Dieses Verfahren erlaubt es auch konkave Polyhedra darzustellen und den Kollisionstest mit einem Polyhedron oder mehreren zusammengesetzten konvexen Polyhedra durchzuführen.

6.4.2. Behandlung Nach der Kollisionserkennung befinden sich im Kollisionsarray alle Kollisionen, die zwischen den Zeitpunkten t0 und t1 aufgetreten sind. Allerdings ist pro RigidBody-Objekt nur die zu- erst eingetretene Kollision relevant. Deshalb werden aus allen Kollisionen nur die relevanten Exemplare ausgesucht und behandelt. Jede von FuRigidBody abgeleitete Klasse verfügt über eine Methode OnCollision(), die das jeweilige Objekt über die Kollision informiert und das FuCollision-Objekt als Parameter übergibt.

Des Weiteren erbt jede dieser Klassen von FuRigidBody die Methode RespondToColli- sion(), welche die konkrete Behandlung der Kollision durchführt. In dieser Methode wer- den zunächst die physikalischen Größen jedes Körpers auf den Stand zum Zeitpunkt t0 zu- rückgesetzt und bis zum ermittelten Kollisionszeitpunkt simuliert. Dann werden einige Zwi- schenergebnisse berechnet, die zur Ermittlung der neuen Kräfte nach der Impulsübertragung benötigt werden. Die Berechnung dieser Zwischenergebnisse ist abhängig davon, ob der Kol- lisionspartner selbst ein RigidBody-Objekt ist oder ein statisches Objekt, bei dem z.B. die Geschwindigkeit immer null beträgt und die Masse als unendlich groß angenommen wird. Eines dieser Zwischenergebnisse ist die relative Geschwindigkeit der Körper zueinander, d.h. die Geschwindigkeit mit der sich beide Körper am Kollisionspunkt in Richtung der Normalen der Kollision aufeinander zu bewegen. Anhand dieser Geschwindigkeit lässt sich bestimmen, wie mit den Körpern zu verfahren ist. Bei negativer relativer Geschwindigkeit bewegen sich die Körper voneinander weg und es darf keine Impulsübertragung durchgeführt werden, wo- hingegen bei positiver Geschwindigkeit die neuen Kräfte nach dem Zusammenstoß berechnet werden müssen. Ist die relative Geschwindigkeit allerdings nahezu null, so ruhen die Körper aufeinander. Eine korrekte Behandlung dieser Situation ist enorm komplex und aufwendig, weshalb in dieser Implementierung ein einfacher Trick ([7]) angewendet wird. Die beiden kol- lidierenden Geometrien werden in einem festen Abstand zueinander gesetzt, erst dann wird die Übertragung der Kräfte durchgeführt. Somit wird verhindert, dass z.B. ein Körper, der auf einem anderen ruht, durch den Einfluss der Gravitation langsam in dem anderen Körper versinkt. Bei der Impulsübertragung wird der neue lineare Impuls und der neue Drehimpuls des RigidBody-Objekts berechnet und daraus direkt die neue lineare Geschwindigkeit und die Winkelgeschwindigkeit. Diese physikalischen Größen fließen, wie in 5.4 erläutert, erst beim nächsten Aktualisierungsschritt in die Simulation des Körpers ein.

43 6. Implementierung

6.5. Weiteres

Im Zusammenhang mit der Implementierung sollen noch zwei weitere Gebiete beachtet wer- den, die zwar nicht den Schwerpunkt der Arbeit darstellen, denen aber dennoch ein großer Teil des Implementierungsaufwands gewidmet wurde. Es handelt sich um die Darstellung von Terrain und um Partikelsysteme, mit denen eine Vielzahl erstaunlicher Effekte möglich wird.

6.5.1. Terrain

Die Implementierung stützt sich auf das von Ulrich Thatcher [10] beschriebene Verfahren zur Verwaltung und Darstellung großer Terrains und erweitert es, um das Terrain ebenfalls in den Szenengraph einbinden zu können. Beim Laden des Terrains über die Methode LoadTer- rain() der Klasse FuTerrainManager wird dessen Höhenkarte, ein quadratisches Bild mit 256 Graustufen, eingelesen und in einem Array gespeichert. Anschließend wird ein quadratisches Raster von dreidimensionalen Vertices gebildet, wobei die Y-Komponente jedes Vertex durch Skalierung eines zugehörigen eingelesenen Höhenwerts gebildet wird. Das entstandene Ra- ster enthält nun die gesamte Geometrie des Terrains, kann aber aufgrund der großen Anzahl an Polygonen nicht in der kompletten Form gerendert werden. Stattdessen wird ein Quadtree aufgebaut, in dem Indizes2 auf die Terrain-Vertices in einer besonderen Art gespeichert und zum Rendern ausgewählt werden. Jeder Knoten des Quadtrees ist ein Quadrat, bestehend aus einem Zentrum, vier Eckpunkten und vier Kantenpunkten (s. Abbildung 6.2). Ein Knoten ent- hält selbst wieder vier Kindknoten.

Abbildung 6.2.: Ein Knoten des Terrain-Quadtrees mit einem Kindknoten (oben rechts).

2Ganzzahlen, welche die Position eines Vertex in einem Array angeben

44 6. Implementierung

Der Wurzelknoten des Quadtrees erstreckt sich über die gesamte Länge und Breite des Ter- rains. Seine Eckpunkte entsprechen den Eck-Vertices des gesamten Terrains, sein Zentrum dem Mittelpunkt-Vertex und seine Kantenpunkte den Vertices, die zwischen den Ecken des Terrains liegen. Innerhalb des FuTerrainNode-Objekts, welches für einen Quadtree-Knoten steht, werden allerdings nur die Indizes der entsprechenden Terrain-Vertices abgespeichert. Das Füllen der Quadtree-Knoten mittels der Methode InitNode wird rekursiv solange für al- le Kindknoten wiederholt, bis die Blattknoten im Zuge der Aufteilung eine Kantenlänge von drei Terrain-Vertices erreicht haben. Während der Initialisierung eines Knotens werden neben der Ermittlung der zugehörigen neun Indizes auch acht Fehlerwerte berechnet und im Knoten gespeichert. Vier dieser Fehlerwerte stellen den Höhenunterschied dar, der durch Weglassen eines der vier Kantenpunkte entsteht, d.h. den Unterschied in der Y-Komponente zwischen dem Terrain-Vertex, auf den der jeweilige Kantenpunkt verweist, und dem Terrain-Vertex, der durch Interpolation der beiden benachbarten Eckpunkte entsteht. Die anderen vier Fehlerwer- te repräsentieren den Höhenunterschied der sich beim Weglassen eines Kindknotens ergibt. Außerdem wird bei der Initialisierung auch die BoundingBox jedes Knotens erstellt. Da bei späteren Berechnungen zur Laufzeit eine Überprüfung von benachbarten Terrain-Knoten eine Rolle spielt, werden alle Knoten mit ihren Nachbarn gleicher Auflösung verknüpft.

Während des ersten Update-Schritts des Szenengraphen wird beginnend vom Wurzelknoten des Terrains der in den Szenengraph integrierte Quadtree rekursiv durchlaufen und für jeden der Knoten dessen Methode Subdivide() aufgerufen. Diese Methode prüft zunächst welche Kantenpunkte des aktuellen Knotens aktiviert werden sollen, was bedeutet, dass ein Kanten- punkt beim Rendern des Knotens gezeichnet und durch eine Kante mit dem Zentrum des Knotens verbunden werden soll. Um den Test durchzuführen, wird über den im Kantenpunkt gespeicherten Index der tatsächliche Terrain-Vertex ermittelt. Dann wird der Abstand dieses Vertex zur Kameraposition berechnet. Wenn das Produkt aus dem zum Kantenpunkt gehörigen Fehlerwert und einem bei der Initialisierung festgelegten Detailfaktor größer als der berechne- te Abstand ist, wird der Kantenpunkt aktiviert. Der Detailfaktor entscheidet darüber, wie hoch das Terrain aufgelöst wird — je höher der Faktor, desto mehr Dreiecke des Terrains werden gezeichnet.

Beim Aktivieren eines Kantenpunktes muss gewährleistet werden, dass dieser Punkt auch beim angrenzenden Nachbarknoten aktiviert ist. Andernfalls kann es passieren, dass ein Ter- rainknoten, dessen Kantenpunkt aktiviert ist, den tatsächlichen Höhenwert des Terrains dar- stellt, während der angrenzende Knoten den Wert an der gleichen Stelle aus seinen Eckpunkten interpoliert und der daraus resultierende Höhenunterschied so groß ist, dass er als Bruch bei der Darstellung sichtbar wird. Da jeder Knoten bei der Initialisierung Zeiger auf alle Nach- barknoten gespeichert hat, ist es einfach, einen Kantenpunkt des Nachbarn zu aktivieren, al- lerdings ist erst sicher, dass dieser angrenzende Knoten überhaupt gerendert wird, wenn der gesamte Pfad bis zu ihm im Quadtree aktiviert ist. Deshalb werden vom Nachbarknoten aus die Elternknoten solange aufwärts durchlaufen und jeweils die entsprechenden Kindknoten aktiviert, bis man auf einen Knoten stößt, bei dem diese Aktivierung bereits vollzogen wurde. Dieses Verfahren wird als Balancierung des Quadtrees bezeichnet. Nachdem die Überprüfung der Kantenpunkte innerhalb der Methode Subdivide() abgeschlossen ist, wird auf die nahezu

45 6. Implementierung gleiche Art getestet, welche der vier Kindknoten aktiviert werden sollen. Auch bei der Akti- vierung von Kindknoten muss der Quadtree erneut balanciert werden.

Beim dritten Update-Schritt des Szenengraphen werden nur noch die aktivierten Terrain- Knoten durchlaufen. Für jeden Knoten wird die Methode PrepareRendering() aufgerufen, die anhand der aktivierten Kantenpunkte entscheidet, welche Dreiecke für die Darstellung des Knotens gezeichnet werden sollen und die dafür benötigten Indizes an den FuTerrainManager übergibt. Dieser speichert alle Indizes in einem Puffer, der nach dem Rendern des Szenengra- phen dazu verwendet wird, das Terrain zu zeichnen.

Für die Texturierung des Terrains kommt ein Verfahren zum Zuge, bei dem mehrere Textur- schichten ineinander übergeblendet werden. Um festzulegen, an welcher Stelle der Landschaft welche Bodentextur zum Einsatz kommt, verwendet man eine Bilddatei in derselben Größe wie die Höhenkarte. Diese Datei enthält ein aus drei Farben bestehendes Bild, bei dem je- weils eine Farbe einer Bodentextur zugeordnet ist. Bei der Generierung des Terrains wird die Bilddatei pixelweise ausgewertet und mittels der Farbe der Anteil jeder Bodentextur pro Terrain-Vertex bestimmt. Des Weiteren berechnet man eine statische Beleuchtung des gesam- ten Terrains im Voraus und speichert sie in einer Textur ab. Diese so genannte Lightmap wird dann als weitere Texturschicht über das Terrain gelegt.

Abbildung 6.3.: Texturierung der Terrain-Geometrie.

6.5.2. Partikelsysteme Die FuEngine verwendet Partikelsysteme bei denen jedes Partikel durch einen Sprite, bzw. ein Billboard dargestellt wird. Ein Sprite ist ein texturiertes Polygon, welches immer so ro- tiert wird, dass seine Achsen der X- und Y-Achse der Kamera entsprechen. Das Laden und Darstellen von Sprites wird von der Klasse FuSpriteManager übernommen. Eine Menge von Sprites wird dabei in einem SpriteSet zusammengefasst. Das Laden eines Sets geschieht über die Methode LoadSpriteSet() der als Parameter der Pfad einer Bilddatei übergeben wird, in dem die einzelnen Sprites des Sets in tabellarischer Form angeordnet sind, und der Pfad einer Konfigurationsdatei, in der vermerkt ist, wie das Bild auszuwerten ist. Die Methode gibt ei- ne eindeutige ID zurück, mit der auf das SpriteSet im FuSpriteManager zugegriffen werden kann. Soll ein Sprite an einer bestimmten Position gezeichnet werden, so kann es mittels der

46 6. Implementierung

Methode AddSprite() zum Rendern vorgemerkt werden. Neben der Position und einem Ska- lierungsfaktor für die Größe des Sprite-Polygons erwartet die Methode die ID des SpriteSets und die Nummer des Sprites innerhalb des Sets. Nachdem alle Sprites hinzugefügt wurden, können sie mit der Methode Render() gezeichnet werden. Da die meisten Sprites über trans- parente Bereiche verfügen, werden sie in der Render-Methode abhängig von der Entfernung zur Kamera sortiert, wobei die Sortierung von hinten nach vorne erfolgt. So wird vermieden, dass der Z-Buffer das Zeichnen der hinteren Sprites unterdrückt.

Partikelsysteme werden in der Klasse FuParticleSystem verwaltet. Jedes Partikelsystem ver- fügt über eine Menge an Parametern, mit denen sich ihr Verhalten bestimmen lässt. Kernstück eines jeden Partikelsystems ist der Emitter. Er versendet eine Menge von Partikeln mit einer Geschwindigkeit in eine Richtung. Auf alle Partikel wirken außerdem externe Kräfte, die die Partikel zu Boden fallen oder aufsteigen lassen können. Alle Parameter des Systems werden über die Methode Init(), der eine ausgefüllte Initialisierungsstruktur übergeben wird, gesetzt. Über die Methode StartEmission() wird die Aussendung von Partikeln begonnen, über Stop- Emission() folglich wieder beendet. Die Methode EmitParticles() bekommt ein Zeitintervall übergeben und simuliert das gesamte System für die Dauer des Intervalls. Bei der Darstellung von Sprites an den Positionen der Partikel kann das Partikelsystem auf zwei verschiedene Ar- ten Gebrauch von einem im FuSpriteManager geladen SpriteSet machen. Zum einen gibt es die Möglichkeit, das ein Partikel alle Sprites eines Sets während seiner Lebenszeit der Reihe nach durchläuft, oder das Partikel wird bei der Erzeugung zufällig mit einem Sprite des Sets verknüpft und stellt während seiner Lebenszeit nur dieses Sprite dar. Die gewünschte Mög- lichkeit wird dem Partikelsystem bei der Initialisierung mitgeteilt.

Um Partikelsysteme im Szenengraphen einordnen zu können, existiert die von FuScene- Node abgeleitete Klasse FuParticleSystemNode. Diese Art von Knoten wird mit einem Parti- kelsystem verknüpft und im Szenengraphen eingesetzt. Die Position des Emitters wird beim Aktualisieren des Graphen immer wieder mit der räumlichen Position des Knotens abgegli- chen. Ein Partikelsystem kann auch einem Spielobjekt zugeordnet werden, welches dann zur Laufzeit über ein Objekt der Klasse FuParticleSystemController Zugriff auf das System er- langt, um z.B. dessen Emission zu einer beliebigen Zeit zu starten oder zu beenden.

47 6. Implementierung

Abbildung 6.4.: Beispiele für Partikelsysteme der FuEngine.

48 7. Ergebnisse

Die im Rahmen dieser Arbeit entwickelte 3D-Game-Engine erfüllt alle Anforderungen aus dem Kapitel 4 und verfügt darüber hinaus noch über eine Fülle weiterer Fähigkeiten, die bei der Entwicklung eines Spiels notwendig sind. Um die Möglichkeiten der FuEngine vorzu- führen und um zu zeigen, dass auf ihrer Basis die Implementierung eines Spiels möglich ist, wurde eine Anwendung zur Demonstration der Engine-Technologien, kurz Tech-Demo ge- nannt, entwickelt. Diese Applikation lässt ein interaktives Erforschen der Ergebnisse zu und hebt dabei insbesondere die in Zusammenhang mit der Implementierung erwähnten Eigen- schaften hervor. Das dargestellte Szenario orientiert sich dabei an einem Spiel, welches von einer Gruppe von Leuten als Freizeitprojekt unter dem Arbeitstitel „U-Boot-Crew“ entwickelt wird und neben den Eigenschaften der FuEngine bereits das gemeinsame Spielen über ein Netzwerk und die Simulation von Schwarmverhalten umfasst. Bilder der Tech-Demo befinden sich im Anhang A.

Eine Station dieser Applikation zeigt ein Dorf, welches aus mehreren Ruinen von Häusern und einem Turm besteht (s. Abbildung 7.1). Zur Evaluation der Leistungsfähigkeit der Engine bei der Ausgabe von Geometrien wurde diese Station als Testszene von einer festen Kamera- position aus betrachtet und die aktuelle Bildwiederholrate gemessen. Der Test wurde mehrfach wiederholt, wobei sich jeweils interne Einstellungen der Engine oder die Szenengeometrie än- derten. Die Evaluation wurde auf einem System durchgeführt, das mit einem AMD Athlon XP 2200+ Prozessor, 512 MB Arbeitsspeicher und einer GeForce 5900XT Grafikkarte ausgerüstet ist. Die Bildschirmauflösung, die sich bei jedem 3D-Spiel erheblich auf die Leistung auswirkt, betrug 1152 × 864 Pixel.

Zunächst wurde die Bildwiederholrate mit normalen Einstellungen gemessen, wobei sich ein durchschnittlicher Wert von 95 Bildern pro Sekunde ergab. Anschließend wurde der Test mit deaktiviertem View-Frustum-Culling durchgeführt. Das Er- gebnis betrug in etwa die Hälfte der vorherigen Bildwiederholrate. Der erhebliche Geschwin- digkeitsverlust lässt sich vor allem auf die große Menge an nicht sichtbaren Terrain-Knoten zurückführen, deren Übergabe an die Grafikkarte nicht verhindert wird, und es zeigt sich, dass das View-Frustum-Culling eine sehr effektive Optimierung darstellt. Nachfolgend wurde ein Test durchgeführt, welcher der Überprüfung des Sortierverfahrens diente, welches beim Zeichnen des Szenengraphen verwendet wird, um einen häufigen Wech- sel von Render-States zu vermeiden. Hier stellte sich überraschenderweise heraus, dass die Sortierung bezüglich der Testszene keinen messbaren Geschwindigkeitsvorteil mit sich bringt. Das liegt möglicherweise an einer verbesserten Architektur der relativ modernen Grafikkarte

49 7. Ergebnisse

Abbildung 7.1.: Testszene für die Evaluierung der Engine. oder daran, dass die Testszene nicht über genug verschiedene Texturen oder Materialien ver- fügt, um eine Auswirkung der Sortierung festzustellen. Die nächste Überprüfung sollte herausfinden, wie die Engine mit einer großen Anzahl von Geometrien, bzw. Polygonen umgeht. Um die Anzahl der Polygone zu erhöhen, wurden die Gebäude des Dorfes vervielfacht dargestellt. Bei einer Anzahl von ca. 60.000 gleichzeitig sichtbaren Polygonen kam nur noch die Hälfte der zu Beginn gemessenen Bildwiederholrate zustande. Dieses Ergebnis lässt sich bei der Verwendung der FuEngine als Richtwert für die zukünftige Gestaltung von Szenen verwenden. Da das Terrain im Vergleich zu den restlichen Bereichen der Engine sehr viel Berechnungs- zeit beansprucht, wurde die Auswirkung des Detailfaktors, der über die Auflösung des Terrains entscheidet (s. 6.5.1), untersucht. Hier zeigte sich, dass der Faktor ungefähr proportional zur Bildwiederholrate ist, d.h. dass ein Verdoppeln des Faktors eine Halbierung der Rate zu Folge hat. Über diesen Faktor hat man deshalb die Möglichkeit, die Auflösung des Terrains an die Leistungsfähigkeit verschiedener Systeme anzupassen.

Die physikalisch basierte Simulation erwies sich in jeder Ausführung der Tech-Demo als zuverlässig und stabil. Es konnten keine konkreten Nachteile aufgrund des verwendeten nu- merischen Euler-Integrationsverfahrens festgestellt werden. Zahlreiche Tests des Kollisionsmanagements zeigten, dass Kollisionen in der Regel korrekt erkannt und behandelt werden. Allerdings kommt es in seltenen Ausnahmefällen zu Situatio- nen, in denen sich die Kollisionspartner ineinander verkeilen und sich erst nach einer gewissen

50 7. Ergebnisse

Verzögerung wieder korrekt verhalten. Dieses Verhalten lässt sich auf die provisorische Be- handlung des ruhenden Kontakts (6.4.2) zurückführen und soll im Zuge von Erweiterungen (8) beseitigt werden. Die Geschwindigkeit der Kollisionserkennung ist von der Polygonanzahl der getesteten Geometrien abhängig, was aber aufgrund der verwendeten CollisionSwitchNo- des (5.2) nicht zur einer qualitativen Verschlechterung der dargestellten Geometrien führen muss. Dadurch ist sogar möglich, die Genauigkeit der Kollisionserkennung für die verschie- denen Geometrien einer Szene individuell festzulegen.

51 8. Fazit und Zukunftsaussichten

Die Entwicklung einer universellen 3D-Game-Engine ist ein Prozess, dessen Komplexität sich beliebig ausdehnen lässt. Es gibt immer zahlreiche Funktionen, um die man jede bestehende Engine erweitern könnte und es werden, besonders in Hinblick auf die explosionsartige Ent- wicklung im Bereich der Grafikhardware, ständig neue Techniken und Verfahren entwickelt, die eine Unterstützung durch die jeweilige Engine verdient hätten. Die Engine, die innerhalb dieser Arbeit entwickelt wurde, beschränkt sich deshalb nur auf einige Fähigkeiten, die aller- dings schon ausreichen, um darauf aufbauend ein Spiel zu entwickeln. Sie kann Spielobjekte mit unterschiedlichen Eigenschaften und Verhaltensweisen erzeugen und zur Laufzeit verwal- ten. Durch ihren Szenengraphen verfügt sie über die Möglichkeit, eine komplexe, hierarchi- sche Szene zu erzeugen, in Echtzeit darzustellen und mit den Spielobjekten zu verknüpfen. Die Physiksimulation der Engine erlaubt die plausible Darstellung der Bewegungen von Festkör- pern und ihr Kollisionsmanagement lässt das Ermitteln und Behandeln von Zusammenstößen zwischen Spielobjekten zu. Außerdem kann sie ein Terrain darstellen, über Partikelsysteme vielfältige Spezialeffekte erzeugen und beinhaltet noch eine Menge weiterer Techniken, die bei der Erstellung eines Spiels nützlich sind. Die Engine ist aber auch in ihrer Struktur so ausgelegt, dass Erweiterungen möglich sind.

Der Anzahl dieser Erweiterungen ist keine Grenze gesetzt — sinnvolle Zusätze wären unter anderem die Erweiterung um Pixel- und Vertexshader, Texturprojektion, Charakteranimation oder „Level-of-Detail“-Verfahren für die dargestellten Geometrien. Ebenso ist mit Sicherheit auch der Spielraum an möglichen Optimierungen noch nicht ausgereizt, besonders was die Verwaltung der Szene und die Kollsionserkennung betrifft. In Hinblick auf die Entwicklung des Spiels „U-Boot-Crew“ (s. 7) ist es wahrscheinlich, dass in Zukunft zumindest einige der angesprochenen Erweiterungen in die Engine einfließen und auch die bereits bestehenden Ver- fahren verbessert werden.

52 A. Bilder der Tech-Demo

Abbildung A.1.: Kollisionen zwischen einfachen Würfel-Geometrien.

53 A. Bilder der Tech-Demo

Abbildung A.2.: Kollisionen zwischen komplexen Geometrien.

54 A. Bilder der Tech-Demo

Abbildung A.3.: Partikelsysteme und Festkörpersimulation.

55 A. Bilder der Tech-Demo

Abbildung A.4.: Ansicht auf das versunkene Dorf.

56 Literaturverzeichnis

[1] Oliver Abert OpenSG Documentation Universität Koblenz, Juni 2004 http://www.oliver-abert.de/opensg/index.html

[2] David Baraff, Andrew Witkin Physically Based Modelling SIGGRAPH 2001 course notes

[3] Departamento de Informática VRML Interactive Tutorial Universität Minho -Portugal http://tom.di.uminho.pt/vrmltut/

[4] David H. Eberly 3D Game Engine Design Morgan Kaufmann Publishers, 2001

[5] S. Gottschalk, M. Lin, D. Manocha OBB-Tree: A Hierarchical Structure for Rapid Interference Detection SIGGRAPH 96 Conference Proceedings, Annual Conference Series ACM SIGGRAPH, Addison Wesley, August 1996

[6] Chris Hecker Behind the screen — Physics Game Developer, November 1996 - März 1997 http://www.d6.com/users/checker/pdfs/gdmphys3.pdf

[7] Ladislav Kavan Rigid Body Collision Response Charles University, Prag

[8] Ming Lin, John Canny Efficient collision detection for animation Proceedings of the Third Eurographics Workshop on Animation and Simula- tion, Cambridge, 1991

57 Literaturverzeichnis

[9] Brian Mirtich Efficient Algorithms for Two-Phase Collision Detection Mitsubishi Eletric Research Laboratory, Dezember 1997

[10] Ulrich Thatcher Continuous LOD Terrain Meshin Using Adaptive Quadtrees. Gamasutra, Februar 2000 http://www.gamasutra.com/features/20000228/ulrich_ 01.htm

58