Beruflich Dokumente
Kultur Dokumente
Untersuchung Des Funktionsumfangs Und Der Leistungsfähigkeit Der Grafik-Engine Ogre3D
Untersuchung Des Funktionsumfangs Und Der Leistungsfähigkeit Der Grafik-Engine Ogre3D
Bachelor of Science
vorgelegt dem
von
Lars Bilke
Die Arbeit wurde bisher in gleicher oder ähnlicher Form keiner anderen Prüfungsbehörde
vorgelegt und auch nicht veröentlicht.
2
Inhaltsverzeichnis
Abbildungsverzeichnis 6
1 Einführung 7
1.1 Einführung 3D-Engines . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.1.1 Begrisklärung 3D-Engine . . . . . . . . . . . . . . . . . . . . . . 8
1.1.2 Begriserklärung Hardware-Abstraktion . . . . . . . . . . . . . . 8
1.1.3 Geschichtliches . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.1.4 Unterschiede zwischen freien und kommerziellen 3D-Engines . . . 9
1.2 Freie 3D-Engines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.2.1 Irrlicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.2.2 Nebula Device 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2.3 Panda3D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.2.4 Vergleichende Betrachtungen zu OGRE 3D . . . . . . . . . . . . . 13
1.3 Kommerzielle 3D-Engines . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.3.1 Torque Game Engine . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.3.2 CryEngine 1/2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.3.3 Unreal-Engine 2/3 . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.3.4 Source-Engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3
3.1.5 Mesh-Objekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.1.6 Entity-Objekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.1.7 SceneNode-Objekt . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.1.8 Materials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.1.9 Overlays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.2 Einrichten einer Entwicklungsumgebung und Erstellen eines OGRE 3D-
Projekts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.3 Eine Hello World-Anwendung . . . . . . . . . . . . . . . . . . . . . . . 32
3.4 FrameListener . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.5 Ein interaktives Beispiel: Kamera- und Charaktersteuerung . . . . . . . . 39
3.5.1 Kamerasteuerung . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.5.2 Charaktersteuerung . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5 Schatten in OGRE 3D 63
5.1 Allgemeines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
5.1.1 Schatten in OGRE 3D aktivieren . . . . . . . . . . . . . . . . . . 63
5.1.2 Modulative Schatten . . . . . . . . . . . . . . . . . . . . . . . . . 64
5.1.3 Additive Light Masking . . . . . . . . . . . . . . . . . . . . . . . 64
5.2 Stencil-Schatten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
5.2.1 Das HelloWorld-Beispiel mit Stencil-Schatten . . . . . . . . . . . 65
5.3 Textur-basierte Schatten . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
6 Animation in OGRE 3D 67
6.1 Skeletale Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
4
6.2 Vertex-Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.2.1 Morph-Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.2.2 Pose-Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.2.3 Kombination von skeletaler und Vertex-Animation . . . . . . . . . 71
6.3 Szenenknoten-Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
6.4 Animation von numerischen Werten . . . . . . . . . . . . . . . . . . . . . 72
8 Ausblick 81
A Quellcode 82
A.1 HelloWorld-Quellcode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
A.2 Eine interaktive Anwendung-Quellcode . . . . . . . . . . . . . . . . . . . 85
A.3 Compositor: Weichzeichner-Materialskript . . . . . . . . . . . . . . . . . 90
A.4 Compositor: Weichzeichner-Pixelshader . . . . . . . . . . . . . . . . . . . 91
A.5 Beispielshader: Vertexshader . . . . . . . . . . . . . . . . . . . . . . . . . 92
A.6 Beispielshader: Pixelshader . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Literaturverzeichnis 95
5
Abbildungsverzeichnis
1.1 Die Komponenten einer Spiel-Engine (in Anlehnung an [Mot03], Seite 495) 10
1.2 Screenshot von dem noch nicht veröentlichten Nebula Device Spiel Dra-
kensang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3 Disneys Toontown Online . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4 Freie Engines im Vergleich . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.5 Marble Blast Ultra auf der XBox 360 . . . . . . . . . . . . . . . . . . . . 15
1.6 Screenshot aus einer Crysis-Demonstration . . . . . . . . . . . . . . . . . 16
1.7 Normal Mapping in der Unreal Engine 3 . . . . . . . . . . . . . . . . . . 17
1.8 Lebensecht wirkende Charaktere im Spiel Half-Life 2 . . . . . . . . . . . 19
6
1 Einführung
Diese Arbeit beleuchtet den Funktionsumfang und die Leistungsfähigkeit der Grak-
Engine OGRE 3D, im folgenden nur mit OGRE bezeichnet. Nach einem Vergleich mit
weiteren 3D-Engines, sowohl freien als auch kommerziellen, und der Auistung der Leis-
tungsmerkmale von OGRE erfolgt eine Einführung in die Programmierung mit OGRE.
Darauf folgend werden wichtige Bereiche der Engine, wie z.B. das Materialsystem und
Animationen, näher beleuchtet und mit Beispielen verständlicht. Abschlieÿend werden
einige Werkzeuge betrachtet, die direkt mit OGRE arbeiten oder Daten mit der Engine
austauschen können.
7
1.1 Einführung 3D-Engines
Eine 3D-Engine 3
ist ein Kernbestandteil von Computerspielen oder anderen interaktiven
Anwendungen mit in Echtzeit berechneten Graken. Synonyme sind z.B. Grak-Engine,
Rendering-Engine oder fälschlicherweise Spiel-Engine
, deren Begri eigentlich eine höhe-
re Funktionalität (z.B. Physik und Kollisionserkennung, Sound, Scripting und Künstliche
Intelligenz) als der der 3D-Engines assoziiert. 3D-Engines kümmern sich um die Darstel-
lung möglichst realitätsnaher 3D-Grak. Sie verfügen meist über einen Szenengraphen,
der eine objektorientierte Repräsentation der 3D-Welt darstellt, das Design der Anwen-
dung vereinfacht und die Grakberechnungen von komplexen 3D-Welten optimiert.
1.1.3 Geschichtliches
Der Begri 3D-Engine erschien erstmals Mitte der 90er Jahre im Zusammenhang mit
3D-Spielen. Sogenannte First-Person Shooter 5 (FPS) wie Doom oder Quake von
id Software waren die ersten sehr populären 3D-Spiele. Um nicht alles von Grund auf
neuzuprogrammieren, lizenzierten Spieleentwickler die Kernkomponenten der Software
8
und entwickelten dazu eigene Graken, Charaktere und Welten, also Spielinhalte6 .
Spätere Spiele wie Quake III oder Unreal wurden bereits mit dem Hintergedanken
entwickelt, die 3D-Engine später an weitere Entwicklungsstudios verkaufen zu können,
was zur Folge hatte, dass die Engine und die Inhalte separat entwickelt wurden. Die
Lizenzierung der 3D-Engines wurde eine weitere Einnahmequelle für Spieleentwickler.
Jedoch beinhalten die lizenzierten Engines meist weitere Funktionalitäten auÿer der
Grakerzeugung, denn es handelt sich meistens um komplette Spiel-Engines. Auÿerdem
machen wiederverwendbare Engines die Programmierung von Spielfortsetzungen leichter
und kosteneektiver.
Auch vor 1990 gab es schon so etwas wie Spiel- oder Grak-Engines wie z.B. Lucas
Arts SCUMM-System und Incentive Softwares Freescape-Engine. Im Gegensatz
zu modernen Engines wurden diese Systeme nie von anderen Entwicklern lizenziert.
[Wik06], Suchbegri game engine
Der gröÿte Unterschied von freien und kommerziellen 3D-Engines ist die Verfügbar-
keit und Stabilität von Werkzeugen, die Inhalte nahtlos aus 7
DCC
-Anwendungen in
die Engine importieren, möglichst Echtzeit-Feedback bieten und die Benutzung auch
von Nichtprogrammierern bewältigt werden kann. Kommerzielle Engines bieten meist
ein reichhaltiges Angebot solcher Werkzeuge. Im Gegensatz zu den freien Engines, die
meist nur die Engine an sich bieten. Weitere Werkzeuge werden meist aus Eigenini-
tiative einzelner Enthusiasten heraus programmiert mit teilweise durchaus beachtlichen
Ergebnissen. Trotzdem erreichen diese Werkzeuge meist nie den Funktionsumfang und
vorallem die Stabilität kommerzieller Produkte. Beim Importieren von Inhalten ist bei
einer freien Engine meist mehr Handarbeit zu verrichten.
Zu den meisten kommerziellen Engines werden keine oziellen Angaben zum Preis für
die Lizenzierung gemacht. Entwickeler, die die Engines benutzen, verpichten sich in
einem NDA8 darüber keine Auskunft zu geben. Man geht aber davon aus, dass High-
End Engines wie z.B. die Unreal Engine 3(siehe auch Kapitel 1.3.3 auf Seite 17) für
etwa 500.000 Dollar zu lizenzieren sind und somit wirklich nur für einige sehr groÿe
Enwticklungsstudios mit einem Budget von mehreren Millionen Dollar pro Spiel in Frage
kommen. Deshalb gibt es für Hobby- und kleinere Entwickler oder für den universitären
Bereich nur die Alternative der freien Engines. Die einzige kommerzielle Engine, die
preislich auch für diese Zielgruppe in Frage kommt, ist die Torque Game Engine
(siehe
6 engl.:
game assets
7 DCC - digital content creation, dt.: Erzeugung digitaler Inhalte
8 NDA - Non-Disclosure-Agreement, dt.: Vertraulichkeitsvereinbarung
9
auch Kapitel 1.3.1 auf Seite 15) mit einem Preis von 100 US$ pro Arbeitsplatz.
Abbildung 1.1: Die Komponenten einer Spiel-Engine (in Anlehnung an [Mot03], Seite
495)
1.2.1 Irrlicht
Irrlicht
ist eine Multiplattform OpenSource 3D-Engine, die in C++ geschrieben wur-
de aber auch für .NET Sprachen erhältlich ist9 . Sie benutzt entweder OpenGL oder
10
die DirectX-API. Irrlicht unterstützt PerVertex-
10
und PerPixel-Lighting Lightmap-
11
,
ping Bump-
12
, Normal-Mapping
und 13
sowohl durch die Fixed-Function-Pipeline14
als
Shader
auch durch 15
bis Shadermodell 3.0 in HLSL
16
und GLSL
17
. Ein hierarchicher
Szenenmanager ermöglicht die Verwaltung der Szene in Octrees
18
als auch durch Oc-
clusion Culling19
. 3D-Modelle können aus diversen 3D-Modelling-Anwendungen wie 3ds
Max, Blender, Milkshape und Maya importiert werden. Die Animation von 3D-Modellen
erfolgt über Morphing und skeletale Animation (siehe Kapitel 6 auf Seite 67). Die Engine
stellt auÿerdem ein Lua-Scripting-Interface bereit. [Dev06]
irrEdit ist ein frei erhältlicher Szeneneditor für Irrlicht, der sich allerdings noch in der
Entwicklung bendet und bisher erst in Version 0.4 vorliegt20 .
Für Irrlicht sind zahlreiche Tutorials und eine gute Dokumentation verfügbar, die den
Einstieg in die Irrlicht-Programmierung sehr erleichtert. Auÿerdem beschäftigt sich eine
rege Community mit Irrlicht.
Kommerzielle Projekte, die mit Irrlicht realisiert wurden, sind nicht bekannt. [Dev06]
und [Irr06]
Nebula Device 2 ist eine OpenSource 3D-Engine in C++ von Radon Labs21 aus Berlin.
Sie ist eine moderne Engine, die ihre komplette Rendering-Pipeline durch Shader reali-
siert und dafür DirectX verwendet. Bisher kann man sie nur unter Windows verwenden,
Portierungen nach Linux und Max OSX sind in Planung. Sie besitzt Skriptinterfaces für
Python, Lua und Ruby.
11
cy n3d-Format importiert werden. Exporter für Maya und 3ds Max sind kommerziell
erhältlich. Die Engine unterstützt Keyframe- und skeletale Animation, Morphing und
Animationsüberblendung. Das Rendering erfolgt über Shader bis Shadermodell 3.0. Par-
tikelsysteme werden ebenfalls unterstützt.
1.2.3 Panda3D
Abbildung 1.2: Screenshot von dem noch
nicht veröentlichten Nebu- Panda3D
22
ist eine 3D-Engine für SGI,
la Device Spiel Drakensang Linux, Sun und Windows. Sie wurde ur-
sprünglich von Walt Disney Imagineering
VR Studio für das Online-Multiplayerspiel
Toontown Online (siehe Abbildung 1.3
auf Seite 13) entwickelt. Die Engine ist in
C++ geschrieben und verfügt über ein Python Skriptinterface. Panda3D ist jetzt als
Open Source Software unter http://www.panda3d.org verfügbar.
Das Rendering erfolgt wahlweise über OpenGL oder DirectX. Ein Python Kommando-
zeilenfenster ermöglicht die Änderung von Python Skripten während der Ausführung
der Panda3D-Anwendung. 3D-Modelle können aus 3ds Max und Maya in das Panda3D-
eigene Egg-Format exportiert werden. Die Szeneninhalte werden über einen Szenengra-
phen verwaltet und mit einem Szeneneditor platziert. Die Engine unterstützt Vertexbe-
leuchtung, animierte Texturen, Partikelsysteme und Nebel oder Shader in 23
Cg
. Anima-
tionen erfolgen durch inverse Kinematik oder skeletale Animation. Auÿerdem beinhaltet
die Engine ein einfaches Physik- und Künstliche Intelligenz-System.
Bisher kam die Engine nur in Toontown und in verschiedenen studentischen Projekten
des Carnegie Mellon Entertainment Technology Center in Pittsburgh, USA zum Einsatz.
Das liegt vielleicht daran, dass die Engine mit der vorhanden Dokumentation nur in Py-
thon programmiert werden kann und somit der Quellcode immer öentlich sichtbar und
12
veränderbar bleibt. Dies schlieÿt die Engine zur Verwendung in kommerziellen Projekten
praktisch aus. Es ist zwar möglich die Engine direkt in C++ zu programmieren, jedoch
gibt es dafür keine Dokumentation und Tutorials. [Dev06] und [Pan06]
13
Die Engine ist unter der LGPL24 lizensiert. Dies ermöglicht die freie Verwendung der
Engine sowohl in OpenSource als auch in kommerziellen Projekten. Im Gegensatz zur
GPL25 müssen Projekte nicht als OpenSource veröentlicht werden, sondern können,
soweit die Engine nicht verändert wurde, in einer eigenen Lizenz erscheinen.
14
1.3 Kommerzielle 3D-Engines
Die Torque Engine enthält mehrere Werkzeuge zur Inhalteerstellung, wie einen GUI-
Editor, einen Welteneditor zum Platzieren von Objekten und Modizieren von Szenen-
eigenschaften, einen Generator zur Erzeugung von Höhentexturen als Grundlage für
Gelände, einen Geländetextureditor, einen Editor zur Modizierung der Geländeform
und einen Modelleditor.
Hauptkritikpunkt der Engine ist die unzureichende Dokumentation. Garage Games hat
dieses Problem erkannt und unter http://tdn.garagegames.com das Torque Developer
15
Network ins Leben gerufen, in der Honung, dass die Benutzer der Engine über das
Wiki-System eine vollständigere Dokumentation erschaen.
Die Torque Engine kann über die Torque Shader Enginezur Unterstützung von mo-
dernen Shadern erweitert werden. Jedoch wird nur das Shadermodell 2.0 unterstützt.
Es gibt mehrere Starter Kits, die die Entwicklung bestimmter Spieletypen erleichtern
sollen, wie z.B. das Racing Starter Kit für Rennspiele.
Bekanntestes Spiel, das die Torque Engine nutzt, ist Marble Blast Ultra welches für
den PC als auch für die XBox verfügbar ist (siehe Abbildung 1.5 auf Seite 15).
GarageGames hat bereits mit Torque Xdie nächste Version der Engine angekündigt.
Torque X baut auf dem neuen XNA Framework 27
von Microsoft auf, eines Frameworks
zur Entwicklung von Spielen und anderen interaktiven Anwendungen. Torque X kann
mit dem neuen XNA Game Studio Express28
, welches auf der frei erhältlichen Entwick-
lungsumgebung Visual C# Express
29
aufsetzt, benutzt werden, um mit dem gleichen
Programmcode Anwendungen sowohl für den PC mit Windows XP als auch für Micro-
softs Spielekonsole XBox 360 zu entwickeln. [Dev06] und [Gar06]
16
sowie Bewegungsunschärfe und extrem detaillierte 3D-Modelle. Erste persönliche Ein-
drucke der Engine von einer Multiplayer-Demonstration auf der Games Convention 2006
in Leipzig belegen die Möglichkeit dieser Technologien in Echtzeit (siehe Abbildung 1.6
auf Seite 16). [Dev06]
Die Unreal-Engine32
ist eine der popu-
lärsten Spiele-Engines und sie kam in der
ersten Version 1998 mit dem gleichnami-
gen Spiel heraus und wird von Epic Ga-
mes entwickelt. Die Engine unterscheidet
sich zu den meisten anderen Engines dar-
in, dass man mit der Lizenz nicht nur
die Engine sondern gleich ein komplettes
Spiel mit Quellcode und allen Inhalten er-
wirbt. Das hat den Vorteil dass man sehr
schnell einen eigenen Prototyp entwickeln
kann, aber den Nachteil, dass je mehr sich
das zu entwickelnde Spiel von einem klas-
(a) Hochaufgelöstes (b) Niedrigaufgelöstes sischen First Person Shooter (FPS) un-
Modell bestehend aus Modell bestehend aus
2 Mio. Dreiecken ca. 5000 Dreiecken terscheidet, desto schwieriger es wird die
Engine den eigenen Bedürfnissen anzu-
passen. Die Unreal-Engine kann über die
UnrealScript-Skriptsprache gesteuert wer-
den.
17
UnrealED alles um einen Spiellevel zu erstellen, inklusive der Festlegung der Spiellogik
über Skripte. Es gibt weitere Editoren für Partikelsysteme, Benutzerinterfaces, Zwischen-
sequenzen in Spielgrak (sogenannte cutscenes) und für Modellanimationen. UnrealED
ist z.B. in dem Spiel Unreal Tournament 2004 enthalten und ermöglicht es, neue Karten
für das Spiel zu erstellen.
Die Version 2.5 erweitert die Engine um Direct3D 9 und OpenGL 2.x Unterstützung.
Somit werden auch moderne Graktechnologien wie HDRR34 , Normal und Bump Map-
ping, weiche Schatten und Per Pixel Lighting möglich. Nun werden auch Microsofts
XBox, 64-Bit Windows und Linux von der Engine unterstützt.
Die Unreal-Engine 3 bendet sich noch in der Entwicklung und wird für PCs, Micro-
softs XBox 360 und Sonys Playstation 3 erältlich sein. Die Engine läuft mit Direct3D
9 und 1035 und OpenGL 2.x. Sie unterstützt Shadermodell 2, 3 und 4 inklusive der neu-
en Geometry Shader 36
und verfügt über eine komplette 64- oder 128-Bit High Dynamic
Range Rendering (HDRR) - Pipeline.
Die Editoren werden weiterhin in ihrer Funktionalität ausgebaut. UnrealCascade ermög-
licht das Editieren komplexer Partikelsystem, mit UnrealPhAT editiert man die Skelette
und physikalischen Eigenschaften der 3D-Modelle, AnimTree erlaubt das Editieren und
Mischen mehrerer einzelner Animationen zu komplexen Bewegungen, ein visueller Ma-
terialeditor erlaubt die Erstellung komplexer Materialien ohne direkt Shader program-
mieren zu müssen und UnrealED, der leistungsstarke Level-Editor, der alle anderen
Werkzeugen in einer Oberäche vereint.
Die Engine verfügt über ein Werkzeug zur automatischen Erstellung von Normal Maps.
Dabei wird aus einem extrem hoch aufgelösten (bis 8 Mio. Dreiecke) Modell eines Cha-
rakters eine hochaufgelöste Normal Map (2048 x 2048 Pixel) erstellt und in der Engine
mit einem niedrig aufgelösten (bis 12000 Dreiecke) Mesh gerendert (siehe Abbildung 1.7
auf Seite 17). [Wik06], Suchbegri unreal engine und [Unr06]
34 HDRR - High Dynamic Range Rendering, Licht wird mit einer höheren Werteumfang berechnet, was
z.B. Überstrahleekte ermöglicht
35 Direct3D 10 wird erst mit der Veröentlichung von Windows Vista anfang 2007 erhältlich sein
36 neue Stufe im Renderingprozess, zwischen Vertex- und Pixel-Shader angeordnet, ermöglicht er z.B.
die prozedurale Erzeugung von 3D-Geometrie direkt auf der Grakkarte
18
1.3.4 Source-Engine
Die Source Engine wurde bis 2005 von Valve Software für das Spiel Half-Life 2 entwi-
ckelt. Sie läuft unter Direct3D 9 und unterstützt HLSL bis Shadermodell 2.0, ist aber bis
zu Direct3D 6 abwärtskompatibel mit älterer Grakhardware. Zu den Leistungsmerkma-
len zählen weiterhin Bump und Environment Mapping, dynamische Lichter, projektive
Schatten und vorberechnete Light Maps, HDR-Beleuchtung, Wasser mit Brechungseek-
ten und komplexe Partikelsysteme. Valve hebt besonders die Charakteranimationsmög-
lichkeiten der Engine hervor. So werden Charaktere über ein skeletales System animiert.
Im Gesichtsbereich kommt ein simuliertes Muskelsystem zum Einsatz, das die Charaktere
lebensnaher erscheinen lassen soll (siehe Abbildung 1.8 auf Seite 19). Auch die Lippen
der Charaktere können zumindest grob auf mehrere Sprachen synchronisiert werden.
19
2 Leistungsumfang von OGRE 3D
OGRE ist eine szenenorientierte, exible in C++ geschriebene 3D-Engine ,die eine ein-
fache und intuitive Möglichkeit zur Nutzung moderner Grakhardware zur Verfügung
stellt. Die Klassenbibliothek abstrahiert alle Details der darunterliegenden Systembiblio-
theken wie Direct3D oder OpenGL und ist damit wirklich plattformunabhängig.
• OGRE ist komplett objekt-orientiert gestaltet und bietet eine einfache Schnittstelle
zum Rendering von dreidimensionalen Szenen unabhängig von der verwendeten
Grak-API-Implementierung.
• Eine exible Plugin-Architektur ermöglicht die Erweiterung der Engine ohne er-
neute Kompilierung.
• Konsistentes Design und Dokumentation aller Klassen.
• Unterstützung von ZIP-Archiven.
20
2.3 Material- und Shader-Unterstützung
2.4 3D-Geometrien
• Mehrere Exporter für 3D-Modelling-Anwendungen, wie z.B. 3DS Max, Maya, Blen-
der und Milkshape3D, exportieren in das OGRE-eigene Mesh-Format .mesh (siehe
Kapitel 7.5, Seite 76).
• Manuell oder automatisch erzeugte Mesh-LODs sorgen für eine erhöhte Perfor-
mance.
4 LOD - Level of Detail, dt.: Detailgrad
21
2.5 Animation
2.6 Szenenverwaltung
• Das integrierte Szenenmanagement ist sehr exibel gestaltet und nicht an einen
bestimmten Szenentyp gebunden. Man kann vordenierte Klassen benutzen, wie
z.B. BSP oder Octree, oder eigene Unterklassen schreiben, um volle Kontrolle
über die Szenenverwaltung zu bekommen und diese an die spezielle Anwendung
anpassen.
• Ein hierarchischer Szenengraph verwaltet alle szenenrelevanten Objekte und sorgt
für eine inhaltliche wie räumliche Beziehung der Objekte untereinander.
2.7 Spezialeekte
22
• Transparente Objekte werden automatisch verwaltet. Man muss sich nicht um die
Renderreihenfolge kümmern.
• Verschiedene Schatten-Render-Methoden wie modulative, additive, stencil- und
texturbasierte Schatten werden unterstützt und werden wenn möglich direkt auf
der Grakhardware berechnet.
23
3 Einführung in die OGRE
3D-Programmierung
3.1 Kernobjekte
Die in Abbildung 3.1 auf Seite 24 dargestellten Objekte bilden die Basis für jede OGRE-
Anwendung:
Abbildung 3.1: Die OGRE-Kernobjekte und ihre Beziehungen untereinander (in Anleh-
nung an [The06b])
24
3.1.1 Root-Objekt
Das Root -Objekt stellt den Einstiegspunkt in das OGRE-System dar. Dieses Objekt
muss als allererstes OGRE-Objekt erstellt werden und als letztes zerstört werden.
Über das Root-Objekt lässt sich das Rendersystem kongurieren. Die Methode show-
ConfigDialog() prüft alle Renderoptionen, wie verfügbare Renderer (OpenGL oder Di-
rectX), Auösungen, Farbtiefen, Vollbildschirm- und Antialiasing-Optionen. Auÿerdem
speichert die Methode die vorgenommenen Einstellungen in einer Kongurationsdatei
zur späteren Initialisierung des Render-Systems.
Das Root-Objekt stellt Zugri zu wichtigen Objekten des OGRE-Systems, wie dem
Szenen-, dem Mesh- und Texturmanager, bereit.
3.1.2 RenderSytem-Objekt
Das RenderSystem -Objekt ist eine abstrakte Klasse, die das Interface zur darunter-
liegenden Render-API deniert. Es ist für das Senden der Renderoperationen an die
verwendete API und für das Setzen aller Renderoptionen verantwortlich. Die Klasse ist
abstrakt, weil die komplette Implementation spezisch für die jeweilige Render-API ist.
Es gibt API-spezische Unterklassen für jede Render-API, z.B. D3DRenderSystem für
Direct3D. Nachdem das System durch Root::initialise() initialisiert wurde, ist das
RenderSystem-Objekt für die gewählte Render-API über die Root::getRenderSystem()-
Methode verfügbar.
Normalerweise ist es nicht nötig direkt auf das RenderSystem-Objekt zuzugreifen. Al-
les was zum Rendern von Objekten und dem Einstellen der Renderoptionen benötigt
wird, ist über den Szenenmanager sowie über Materialien und anderen szenenorientier-
ten Klassen verfügbar. Der Szenenmanager sorgt dafür, dass die benötigte Methoden des
RenderSystem-Objekt zum richtigen Zeitpunkt aufgerufen werden.
Man benötigt das RenderSystem-Objekt jedoch um mehrere separate Renderfenster zu
erstellen oder um andere fortgeschrittene Möglichkeiten des Rendersystems zu nutzen.
25
3.1.3 SceneManager-Objekt
Neben dem Root-Objekt ist das SceneManager -Objekt der wichtigste Teil des OGRE-
Systems für den Anwendungsprogrammierer. Der Szenenmanager kennt die Inhalte der
Szene, die gerendert werden sollen. Er ist verantwortlich für die Organisation der Inhalte,
wie Kameras, bewegliche Objekte (Entities, siehe Kapitel 3.1.5 auf Seite 28), Lichtquellen
und Materialien (Oberächeneigenschaften von Objekten) sowie für das Verwalten der
Weltgeometrie, die eine statische Geometrie zur Repräsentation der unbeweglichen Teile
der Szene darstellt.
Es ist nicht nötig, in der Anwendung Listen von Objektverweisen zu führen. Der Sze-
nenmanager führt alle erstellten Objekte durch zugewiesene eindeutige Namen. Der
Zugang zu Objekten geschieht durch verschiedene SceneManager-Methoden wie z.B.
getCamera() und getEntity(), und dem eindeutigen Namen.
Der Szenenmanager sendet die Szene an das RenderSystem-Objekt wenn die Szene ge-
rendert werden soll.
Die meiste Interaktion mit dem Szenenmanager geschieht während der Erstellung der
Szene. SceneManager-Methodenaufrufe sorgen dafür, dass sich die Szene mit Inhalten
füllt. Es werden 3D-Geometrien geladen, mit Materialien versehen, in der Szene platziert
sowie Kameras und Lichtquellen deniert. Die spätere (dynamische) Veränderung der
Szene wird über FrameListener-Objekte (siehe Kapitel 3.4 auf Seite 37) vollzogen.
Die OGRE-Anwendung muss nicht wissen welche Unterklassen verfügbar sind. Die An-
wendung ruft nur Root::createSceneManager() mit dem Typ der Szene als Parameter
auf (z.B. ST_GENERIC, ST_INTERIOR) und das OGRE-System nutzt automatisch
die beste verfügbare SceneManager-Unterklasse oder den Standard-Szenemanager wenn
keine Spezialisierung verfügbar ist. Dies ermöglicht es erst später einen optimierten Sze-
nenmanager zu erstellen.
26
3.1.4 ResourceManager-Objekt
DieResourceManager -Klasse ist eine Basisklasse weitere Klassen, die zum Verwalten von
Ressourcen benutzt werden. Ressourcen sind Daten, die geladen werden müssen wie z.B.
Texturen und 3D-Geometrien (Meshes). Es gibt Unterklassen von der ResourceManager-
Klasse, um jeden Ressourcentyp zu verwalten, wie z.B. den TextureManager um Textu-
ren zu laden und den MeshManager um 3D-Geometrien zu laden.
Ressourcemanager sorgen dafür, dass Ressourcen nur einmal geladen werden und verwal-
ten die benötigten Speicherbereiche. Auÿerdem werden die angeforderten Ressourcen in
mehreren zuvor spezizierten Verzeichnissen oder komprimierten Archiven (zip-Dateien)
gesucht.
3.1.5 Mesh-Objekt
Ein Mesh -Objekt repräsentiert die 3D-Geometrie eines Modells und ist normalerwei-
se relativ klein gegenüber der Weltgeometrie. Mesh-Objekte repräsentieren bewegliche
Objekte und werden nicht zur Repräsentation der Weltgeometrie genutzt.
Mesh-Objekte sind eine Art von Ressourcen und werden durch den MeshManager Res-
sourcenmanager verwaltet. Sie werden normalerweise aus dem OGRE-spezischen .mesh-
Format geladen. Mesh-Dateien werden meist aus 3D-Modelling-Anwendungen wie z.B.
3ds Max (siehe Kapitel 7.5.1 auf Seite 77) exportiert.
Man kann Mesh-Objekte auch manuell erzeugen, indem man die MeshManager::create-
27
Manual()-Methode aufruft. Auÿerdem gibt es Methoden, um z.B. Ebenen generisch zu
erzeugen (siehe Kapitel 5.2.1 auf Seite 65).
Mesh-Objekte bilden die Basis für individuelle bewegliche Objekt in der Szene, wel-
che Entities (siehe Kapitel 3.1.5 auf Seite 28) genannt werden. Mesh-Objekte können
auÿerdem über skeletale Animation (siehe Kapitel 5.3 auf Seite 67) animiert werden.
Mesh-Objekte setzen sich aus einem oder mehreren SubMesh -Objekten zusammen. Jedes
repräsentiert einen Teil des Meshes, der ein individuelles Material benutzt. Wenn ein
Mesh nur ein Material benutzt, so sollte es auch nur aus einem SubMesh bestehen.
3.1.6 Entity-Objekt
Ein Entity ist eine Instanz eines beweglichen Objektes in der Szene. Es kann alles Mög-
liche darstellen. Es wird nur die Annahme gemacht, dass es keine feste Position in der
Szene hat.
Entities basieren auf 3D-Geometrien, die durch das Mesh-Objekt repräsentiert werden.
Mehrere Entities können auf dem selben Mesh-Objekt basieren, wenn man mehrere Ko-
pien eines Meshes in der Szene darstellen will. Auch hier wird wieder die Mesh-Ressource
nur einmal geladen auch wenn mehrere Entities mit dem Mesh-Objekt assoziiert sind.
Entities sind nicht Bestandteil der Szene solange sie nicht an einem Szenenknoten (Sce-
neNode, siehe Kapitel 3.1.6 auf Seite 29) angehängt werden. Durch das Anhängen an
Szenenknoten werden komplexe hierarchische Beziehungen zwischen den Positionen und
Orientierungen von Entities ermöglicht. Man verändert die Positionen von Knoten, um
indirekt die Positionen der Entities zu verändern.
Zu einem Mesh können auÿerdem mehrere Materialien (siehe Kapitel 3.1.7 auf Seite
29) deniert sein. So ist es möglich, verschiedenen Teilen des Meshes auch verschiedene
Materialien zuzuordnen. Jedes von einem Mesh erzeugte Entity benutzt automatisch die
vorgegebenen Materialien. Man kann dies aber für jedes Entity individuell ändern, um
mehrere Entities mit verschiedenen Materialien aber basierend auf demselben Mesh zu
erzeugen.
Wenn ein Entity basierend auf einem Mesh erzeugt wird, so ist es aus einem oder mehre-
ren SubEntity-Objekten zusammengesetzt, wobei jedes zu einem SubMesh-Objekt korre-
28
spondiert. Man greift auf das SubEntity über die Entity::getSubEntity()-Methode zu.
Jetzt kann man über den Aufruf von setMaterialName() das Material des SubEntity-
Objektes ändern. Dadurch kann man Entities basierend auf ein und demselben Mesh
individuell gestalten.
3.1.7 SceneNode-Objekt
Die Szene wird durch den Szenenmanager in einer baumbasierten Struktur verwal-
tet. Jeder Knoten entspricht einem SceneNode
-Objekt. Der Wurzelknoten wird über
SceneManager::getRootSceneNode() bereitgestellt. An jeden Knoten können weite-
re Knoten als Kindknoten über SceneNode::createChildSceneNode() angefügt wer-
den.
An jeden Knoten können Entities oder andere szenenrelevante Objekte wie Kameras oder
Lichtquellen angefügt werden. Szenenknoten haben eine Position, eine Skalierung und
eine Orientierung in der Szene. Ändert man z.B. die Position eines Knotens so ändert
sich auch die Position der Kindknoten relativ zum aktuellen Knoten. Dies beeinusst
natürlich auch die Position der angefügten Entities.
3.1.8 Materials
Das Material -Objekt kontrolliert, wie Objekte in der Szene gerendert werden. Es spezi-
ziert, welche Oberächen-Eigenschaften Objekte haben, wie z.B. diuse Farbe, Glanz-
farbe, Glanzstärke, wie viele Texturschichten vorhanden sind, welche Bilder als Texturen
dienen und wie sie miteinander kombiniert werden, welche Spezialeekte hinzukommen,
z.B. Umgebungsabbildung (Environment Mapping), wie die Texturen geltert werden
usw.
Materialien können entweder durch den Programmierer im Quellcode durch Aufrufen von
SceneManager::createMaterial() und Einstellen der verschiedenen Parameter erzeugt
werden oder durch das Spezizieren eines Skriptes welches erst zur Laufzeit geladen wird
(siehe Kapitel 4.1 auf Seite 44). Der groÿe Vorteil von Skripten liegt in der nicht mehr
notwendigen Neukompilierung des Quellcodes bei Änderung eines Skripts, da es ja erst
zur Laufzeit geladen wird. So erhält man ein schnelles Feedback auf Änderungen der
Materialskripts und auch Nicht-Programmierer können Einuss auf das Aussehen der
Szenenobjekte nehmen, da die Materialskripts leicht lesbar und verständlich sind.
Alles was die Erscheinung und das Rendern eines Objektes mit Ausnahme der Form
betrit wird in einem Material deniert.
29
Der Szenenmanager verwaltet die Hauptliste aller in einer Szene verfügbaren Materialien.
Zur Liste können Materialien durch den Aufruf von SceneManager::createMaterial
oder durch das Laden eines Meshes (was zur Folge hat, das auch die zugehörigen Ma-
terialien geladen werden) hinzugefügt werden. Die Eigenschaften von neuen Materialien
werden auf die von OGRE denierten Standardeigenschaften gesetzt oder durch das
jeweilige Materialskript festgelegt.
3.1.9 Overlays
Overlays sind 2- oder 3-dimensionale Elemente, die über die normale Szene gerendert
werden, um Benutzerinterfaces, Menüs oder Statistiken anzuzeigen. 2D-Elemente werden
für eben genannte Objekte und 3D-Elemente werden z.B. für Cockpits oder andere 3D-
Objekte genutzt, die über die normalen Szene gerendert werden.
Wenn man ein 2D Element als ein Overlay erzeugen will, ruft man OverlayManager::-
createOverlayElement() auf. Der Typ des zu erzeugenden Elements wird durch eine
zu übergebende Zeichenkette deniert. So erzeugt der Aufruf Overlay::Manager::get-
Singleton().createOverlayElement(Panel, new2DPanel) eine ache rechtecki-
ge Fläche mit dem eindeutigen Namen new2DPanel, die weitere Overlay-Elemente
beinhalten kann.
Um einen Container (wie z.B. das Panel) zu einem Overlay hinzuzufügen, ruft man
Overlay::add2D() auf. Wenn man Kind-Elemente zu einem Container hinzufügen will,
ruft man OverlayContainer::addChild() auf. Kind-Elemente können OverlayElements
30
oder OverlayContainer sein. Die Position eines Kind-Elements ist relativ zur linken obe-
ren Ecke seines Eltern-Elements.
In diesem Abschnitt wird die Einrichtung und die Kompilierung eines OGRE-Projekts
innerhalb der Visual Studio Entwicklungsumgebung von Microsoft erläutert. Die Pro-
jekteinrichtung erfolgt in anderen Entwicklungsumgebungen ähnlich.
Eine im Funktionsumfang reduzierte aber kostenlose Version von Microsoft Visual Studio
mit dem Namen Visual C++ Express steht kostenlos auf der Microsoft Homepage zum
Download1 . Diese Version ist für die Entwicklung mit OGRE bereits geeignet. Weil
Visual C++ Express auf die Erstellung von .net-Anwendungen ausgelegt ist benötigt
man zusätzlich das Win32-SDK2 , denn OGRE-Anwendungen laufen nicht innerhalb der
.net-Runtime sondern sind klassische Win32-Anwendungen.
Man installiert Visual C++ und das Win32-SDK. Das Win32-SDK muss in die Ent-
wicklungsumgebung integriert werden. Man wählt dazu im Visual C++ den Menüpunkt
Extras/Optionen aus und markiert im linken Fensterbereich Projekte und Projekt-
mappen/VC++ Verzeichnisse. Unter Verzeichnisse anzeigen für selektiert man den
Punkt Includedateien und fügt über das entsprechende Symbol das Verzeichnis <Pfad
zum Win32-SDK>/Include hinzu. Anschlieÿend trägt man bei Ausführbare Dateien
<Pfad zum Win32-SDK>/bin
das Verzeichnis und bei Bibliotheksdateien das Ver-
<Pfad zum Win32-SDK>/lib
zeichnis ein.
corewin_express.vsprops
Jetzt önet man die Datei im Verzeichnis <Pfad zu Vi-
sual C++>/VC/VCProjectDefaults und tauscht die Zeile AdditionalDependencies
=kernel32.lib gegen AdditionalDependencies=kernel32.lib user32.lib
gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib
oleaut32.lib uuid.lib aus. Anschlieÿen löscht man in der Datei AppSettings.htm
die Kommentare in den Zeilen 441 bis 444.
Für die kommerzielle Version Visual Studio sind die obengenannten Schritte nicht nö-
tig.
1 http://www.microsoft.com/germany/msdn/vstudio/express/default.mspx
2 erhältlich unter: http://www.microsoft.com/downloads/details.aspx?FamilyId=
A55B6B43-E24F-4EA3-A93E-40C0EC4F68E5\&displaylang=en
31
Nun lädt man das OGRE-SDK herunter 3 und installiert es. Um die Installation zu prü-
fen, önet man in Visual C++ die Projektmappe Samples_vc8 aus dem Ordner <Pfad
zum OGRE-SDK>/samples . Dieses Projekt enthält einige OGRE-Beispielprogramme.
Klickt man nun im Menü auf Erstellen/Projektmappe erstellen erzeugt Visual C++
alle Beispiele und legt sie unter <Pfad zum OGRE-SDK>/bin/debug ab.
Ein eigenes OGRE-Projekt erstellt man indem man über Projekt/Neu und Neue
Win32-Anwendung ein neues Projekt erzeugt. Man muss dem Projekt noch die OGRE-
Verzeichnisse bekannt geben. Dazu fügt man in den Projekteigenschaften unter Kon-
gurationseigenschaften bei C++/Allgemein dem Punkt Zusätzliche Include-Verzeich-
nisse den Eintrag <Pfad zum OGRE-SDK>/include hinzu. Genau so ergänzt man
unter Linker/Allgemein die Zusätzliche Bibliotheksverzeichnisse um <Pfad zum
OGRE-SDK>/lib , sowie unter Linker/Eingabe die Zusätzliche Abhängigkeiten um
den Eintrag OgreMain_d.lib. Abschlieÿend muss man noch sicherstellen, dass das er-
<Pfad zum OGRE-SDK>/bin/debug
stellte Programm die Dateien aus dem Verzeichnis
beziehungsweise <Pfad zum OGRE-SDK>/bin/release ndet oder man kopiert die Da-
teien aus diesen Verzeichnissen in die entsprechenden Verzeichnisse der eben erstellten
Anwendung. [Sch06]
Man erstellt ein neues OGRE-Projekt gemäÿ den Schritten in Kapitel 3.1.9 auf Seite 31.
Zuerst bindet man die OGRE-Engine über einen #include-Befehl ein:
# include < Ogre .h >
Es werden auÿerdem weitere Header für den Fehlerdialog, für EventListener und KeyE-
vents benötigt. Eine ausführliche Erklärung dieser erfolgt später.
# include < OgreErrorDialog .h >
# include < OgreEventListener .h >
# include < OgreKeyEvent .h >
32
Innerhalb der main()-Funktion wird der Programmcode in einen try/catch-Block aus-
geführt. Der Code im catch-Block erzeugt im Fehlerfall ein Fenster (createErrorDia-
log()), das die Fehlermeldung (ermittelt über getFullDescription()) anzeigt und vom
PlattformManager erzeugt wird, der alle betriebssystemabhängigen Elemente, wie z.B.
das eben erzeugte Dialogfenster, kapselt. Als allererstes erzeugt man im try-Block ein
Root-Objekt:
Root * mRoot = new Root ( " Plugins . cfg " , " Ogre . cfg " , " ogre . log " );
Dieses Objekt stellt den Kern des OGRE-Systems dar und ist Ausgangspunkt für alles
weitere (siehe Kapitel 3.1, Seite 24). Das erste Argument gibt den Pfad zu einer Kon-
gurationsdatei an, die OGRE mitteilt welche Plugins genutzt werden sollen. Plugins
sind z.B. das DirectX- und auch das OpenGL-Rendersystem und verschiedene Szenen-
manager. Eine beispielhafte Plugin.cfg
-Datei sieht folgendermaÿen aus:
# Define plugin folder
PluginFolder =.
# Define plugins
Plugin = RenderSystem_Direct3D9
Plugin = RenderSystem_GL
Plugin = Plugin_ParticleFX
Plugin = Plugin_BSPSceneManager
Plugin = Plugin_OctreeSceneManager
Plugin = Plugin_CgProgramManager
Der zweite Parameter FileSystem gibt an, dass es sich beim ersten Parameter um ein
Verzeichnis handelt. Alternativ kann man auch externe Daten wie 3D-Modelle, Materia-
lien und Texturen in ZIP-Archive packen. Dann würde der zweite Parameter Zip sein.
33
Man muss noch 3 weitere Pfade für dieses Beispiel angeben, siehe dazu den Quellcode auf
Seite 82. Nachdem dem Ressourcenmanager alle Verzeichnisse bekanntgegeben wurden,
initialisiert man den Datenbestand mit:
ResourceGroupManager :: getSingleton (). initialiseAllResourceGroups ();
Jetzt erzeugt man ausgehend von einem dem OGRE-SDK beiliegenden Mesh ein kon-
kretes Objekt, das man in der Szene platzieren kann:
Entity * mOgreEntity = mSceneManager - > createEntity ( " OgreEntity1 " ,
" ogrehead . mesh " );
Mit dieser Zeile wurde ein Entity mit dem eindeutigen Namen OgreEntity1 aus der
Mesh-Datei ogrehead.mesh erzeugt. Man kann über den Namen jederzeit einen Ver-
weis auf das entsprechende OGRE-Objekt bekommen, z.B. über mSceneManager->get-
MovableObject(<Name>). Um die Darstellung des Objekts mit der passenden Textur
kümmert sich OGRE automatisch, wenn die .mesh-Datei den zugehörigen Materialna-
men enthält und der Ressourcenmanager diesen bereits in einer eingelesenen Material-
datei entdeckt hat und ihm auch alle benötigten Texturen zur Verfügung stehen. Bisher
ist das Entity aber noch nicht sichtbar. Es muss erst an einen Szenenknoten gehängt
werden. Dazu erzeugt man ein SceneNode-Objekt mit einem eindeutigen Namen und
einer Position in der Szene und verbindet anschlieÿend den Szenenknoten und das zuvor
erstellte Entity:
SceneNode * mOgreSceneNode = mSceneManager - > getRootSceneNode () - >
createChildSceneNode ( " Ogre1Node " , Vector3 (0 ,0 ,0));
mOgreSceneNode - > attachObject ( mOgreEntity );
Der neue Szenenknoten ist ein Kindknoten des bereits existierenden Wurzelknotens
(RootSceneNode). Neue Szenenknoten können entweder an den Wurzelknoten oder
an bestehende Szenenknoten anghängt werden um hierarchische Beziehungen der Kno-
ten zu erzeugen. Jetzt bendet sich das Objekt zwar in der Szene aber es ist aufgrund
einer fehlenden Beleuchtung noch unsichtbar. Das Hintergrundlicht (AmbientLight)
setzt man auf einen geringen Grauwert:
mSceneManager - > setAmbientLight ( ColourValue (0.1 ,0.1 ,0.1 ,1));
Und man erzeugt ein Licht mit der Position (-80, 80, 200) und dem Namen Light1:
Light * mLight = mSceneManager - > createLight ( " Light1 " );
mLight - > setPosition ( -80 , 80 , 200);
34
Es fehlt nur noch eine Kamera, die die Szene aufnimmt:
Camera * mCamera = mSceneManager - > createCamera ( " Camera1 " );
Man stellt sie an der Position (40, 40, 100) auf und lässt sie auf den Koordinatenursprung
schauen:
mCamera - > setPosition ( Vector3 (40 ,40 ,100));
mCamera - > lookAt (0 ,0 ,0);
Das vom Standpunkt der Kamera aus erzeugte Bild muss nun in einem Viewport zur
Anzeige gebracht werden:
Viewport * mViewport = mWindow - > addViewport ( mCamera );
Um die Anwendung auf Tastendruck beenden zu können, benötigt man noch die selbst-
geschriebene Klasse Keyboard:
Keyboard * mKeyboard = new Keyboard ();
Diese Klasse vereint die Interfaces sogenannter FrameListener (siehe auch Kapitel 3.4
auf Seite 37 und KeyListener, deren Methoden wie z.B. FrameListener::frameEnded()
werden nach jedem gezeichneten Bild oder KeyListener::keyClicked bei jeder ge-
drückten Taste aufgerufen. Die Klasse muss beim Root-Objekt registriert werden:
mRoot - > addFrameListener ( mKeyboard );
Auÿerdem muss ein EventProcessor initialisiert werden, der die Ereignisse von gedrück-
ten Tasten an die Keyboard-Klasse weiterleitet:
EventProcessor * mEventProcessor = new EventProcessor ();
mEventProcessor - > addKeyListener ( mKeyboard );
mEventProcessor - > initialise ( mWindow );
mEventProcessor - > startProcessingEvents
Im Anschluss erfolgt die Denition der Keyboard-Klasse. Sie wird von den OGRE-
Klassen FrameListener und KeyListener abgeleitet:
35
class Keyboard : public KeyListener , public FrameListener
{
// Öffentliche Methoden
public :
Keyboard ();
virtual void keyClicked ( KeyEvent * e );
virtual void keyPressed ( KeyEvent * e );
virtual void keyReleased ( KeyEvent * e );
virtual void frameEnded ( const FrameEvent & evt );
// private Variablen
private :
bool EscWasPressed ; // wurde ESC - Taste gedrückt
}
In den beiden anderen Keyboard-Methoden geschieht nichts. Sie müssen aber überschrie-
ben werden:
void Keyboard :: keyPressed ( KeyEvent * e ) {}
void Keyboard :: keyReleased ( KeyEvent * e ) {}
36
Abbildung 3.2: Das HelloWorld-Renderfenster
Man sollte nun nach dem Starten des Programms und der Wahl der OGRE-Optionen
im Kongurationsdialog ein Renderfenster ähnlich der Abbildung 3.2 auf Seite 37 sehen.
Der vollständige Quellcode ist auf Seite 82 zu nden. [Sch06]
3.4 FrameListener
Die Schleife läuft solange, bis entweder eine frameStarted()- oder eine frameEnded()-
Methode false zurückgibt (z.B. als Reaktion auf einen Tastendruck). Die beiden Me-
thoden sind in der Klasse FrameListener virtuell, d.h. man muss immer eigene Klas-
sen davon ableiten, die eine konkrete Implementierung für diese Methoden enthalten.
Konkrete FrameListener müssen über Root::addFrameListener() beim Root-Objekt
registriert werden. Folgender Quellcodeausschnitt zeigt einen konkreten FrameListener,
dem ein Verweis auf eine Animation als Konstruktorparameter übergeben wird und der
37
am Anfang jedes Bildes den Fortschritt der Animation um die seit dem letzten Aufruf
von frameStarted() vergangene Zeitspanne erhöht (siehe auch Kapitel 5.3 Animation
in OGRE 3D auf Seite 67):
class AnimListener : public FrameListener
{
protected :
public :
// Der Konstruktor mit Verweis auf eine Animation
AnimListener ( AnimationState * animState )
{
mAnimState = animState ;
}
protected :
// Der Verweis auf die Animation
AnimationState * mAnimState ;
};
Im Programm muss nun eine konkrete FrameListener-Instanz erzeugt und beim Root-
Objekt registriert werden:
// Zeiger auf eine Animation , muss noch initialisiert werden
AnimationState * animState ;
...
38
3.5 Ein interaktives Beispiel: Kamera- und
Charaktersteuerung
Die Verschiebung der Kamera erfolgt über die Pfeiltasten und die Drehung über die
Tasten W, A, S und D. Der Charakter wird über die Pfeiltasten auf dem Nummernblock
bewegt.
3.5.1 Kamerasteuerung
Zur Kamerasteuerung werden einige neue Variablen benötigt, die am Anfang der Quell-
codedatei als globale Variablen deklariert werden:
SceneNode * mCameraNode ; // Szenenknoten der Kamera
Vector3 mTranslateVector = Vector3 :: ZERO ; // Verschiebungsvektor
Radian mRotX = Radian (0); // Rotation um die X - Achse
Radian mRotY = Radian (0); // Rotation um die Y - Achse
const Real mMoveScale = 150; // Geschw . der Bewegung
const Degree mRotScale = Degree (60); // Geschw . der Rotation
Innerhalb der main()-Methode wird die Kamera an einen Szenenknoten angefügt und auf
die Startposition (40, 40, 100) gesetzt. Abschlieÿend wird die Kamerablickrichtung
auf den Koordinatenursprung gesetzt:
Camera * mCamera = mSceneManager - > createCamera ( " Camera1 " );
mCamera - > setNearClipDistance (5);
// Kamera anhängen
mCameraNode - > attachObject ( mCamera );
39
// Blickrichtung auf Koordinatenursprung
mCameraNode - > setDirection ( - mCameraNode - > getPosition ());
// Rotation um X - Achse
if (e - > getKey () == KC_A )
mRotX += mRotScale ;
if (e - > getKey () == KC_D )
mRotX -= mRotScale ;
// Rotation um Y - Achse
if (e - > getKey () == KC_W )
mRotY -= mRotScale ;
if (e - > getKey () == KC_S )
mRotY += mRotScale ;
}
40
mTranslateVector . z += mMoveScale ;
if (e - > getKey () == KC_DOWN )
mTranslateVector . z -= mMoveScale ;
// rotx
if (e - > getKey () == KC_W )
mRotY += mRotScale ;
if (e - > getKey () == KC_S )
mRotY -= mRotScale ;
...
}
Abschlieÿend wird der Kameraszenenknoten und damit auch die Kamera entsprechend
verschoben. Dies geschieht einmal pro Bild in der Keyboard::frameStarted()-Methode.
Zuerst wird die Rotation um die X- und Y-Achse zugewiesen:
bool Keyboard :: frameStarted ( const FrameEvent & evt )
{
mCameraNode - > yaw ( mRotX * evt . timeSinceLastFrame );
mCameraNode - > pitch ( mRotY * evt . timeSinceLastFrame );
Nun wird die Kamera entsprechend ihrer Blickrichtung um den aus mTranslateVector
bekannten Betrag verschoben, wobei die ganze Verschiebung durch die vergangene Zeit
seit dem letzten Bild (evt.timeSinceLastFrame) skaliert wird:
mCameraNode - > translate ( mCameraNode - > getOrientation () *
mTranslateVector * evt . timeSinceLastFrame );
return true ;
}
3.5.2 Charaktersteuerung
Zur Steuerung des Charakters werden ebenfalls einige globale Variablen benötigt:
SceneNode * mOgreSceneNode ; // Der Charakter - Szenenknoten
AnimationState * mAnimWalk ; // Die Lauf - Animation
AnimationState * mAnimIdle ; // Die Warte - Animation
Vector3 mRobotTranslate = Vector3 :: ZERO ; // Verschiebungsvektor
Radian mRobotRot = Radian (0); // Rotationsvariable
In der main()-Methode wird nun statt dem Ogre-Kopf der Roboter-Charakter aus der
Datei robot.meshgeladen:
41
Entity * mOgreEntity = mSceneManager - > createEntity ( " OgreEntity1 " ,
" robot . mesh " );
Die beiden Animationen sollen als Schleife durchlaufen werden und die Warte-Animation
wird aktiviert:
mAnimWalk - > setLoop ( true );
mAnimIdle - > setLoop ( true );
mAnimIdle - > setEnabled ( true );
Die Steuerung des Roboters wird in der keyPressed()-Methode ähnlich der Kamera-
steuerung realisiert. Zusätzlich wird bei jedem Tastendruck jeweils die Warte-Animation
deaktiviert und die Lauf-Animation aktiviert:
if (e - > getKey ()== KC_NUMPAD8 )
{
// Verschiebungsvektor setzen
mRobotTranslate . x += mMoveScale / 4;
Beim Loslassen der Taste müssen die Änderungen am Verschiebungsvektor und an der
Rotationsvariablen zurückgesetzt werden und jeweils wieder die Lauf-Animation deak-
42
tiviert und die Warte-Animation aktiviert werden:
if (e - > getKey ()== KC_NUMPAD8 )
{
mRobotTranslate . x -= mMoveScale / 4;
mAnimIdle - > setEnabled ( true );
mAnimWalk - > setEnabled ( false );
}
...
Jetzt muss man nur noch in der frameStarted()-Methode die Animationen um die
jeweils seit dem letzten Bild vergangene Zeit weiterlaufen lassen und den Roboter den
gemachten Eingaben entsprechend rotieren und bewegen:
// Animation updaten
mAnimIdle - > addTime ( evt . timeSinceLastFrame );
mAnimWalk - > addTime ( evt . timeSinceLastFrame );
// Roboter rotieren
mOgreSceneNode - > yaw ( mRobotRot * evt . timeSinceLastFrame );
// Roboter verschieben
mOgreSceneNode - > translate ( mOgreSceneNode - > getOrientation () *
mRobotTranslate * evt . timeSinceLastFrame );
Als Ergebnis erhält man eine interaktive Anwendung mit Kamera- und Charaktersteue-
rung wie in Abbildung 3.3 auf Seite 43 zu sehen ist. Der komplette Quellcode ist in
Anhang A.2 auf Seite 85 zu nden.
43
4 Das OGRE 3D-Skriptsystem
Materialskripte sind die wohl mächtigsten OGRE-Skripte, bestimmen sie doch die kom-
plette Darstellung der zu rendernden Objekte. Die Skripte werden in einem C++ ähnli-
chen Pseudoformat geschrieben. Es können mehrere Materialien in einem Skript deniert
werden jedoch müssen alle Skripte einen global eindeutigen Namen haben. Materialien
können von anderen bereits denierten Materialien mit dem Doppelpunkt : kopiert
werden. Dabei überschreibt man im neuen Material nur die veränderten Eigenschaften
(ähnelt der Vererbung in C++). Jedes Material enthällt mindestens eine Technique,
welche wiederum mindestens einen Pass enthält.
4.1.1 Techniques
Eine Technique stellt eine Möglichkeit zur Verfügung ein Objekt zu rendern. Eine einfa-
che Materialdeniton enthält also nur eine Technique. Da die heutige Grakkarten sich
stark in ihrer Leistungsfähigkeit unterscheiden, kann man das nur tun, wenn man sicher
ist, dass sich das Material von allen Grakkarten auch darstellen lässt. Aufgrund des-
sen ist es ratsam, weitere Techniques mit einfacheren Anforderungen an die Hardware
in einem Material zu denieren. OGRE wählt dann automatisch die beste Technique,
die von der Hardware unterstützt wird. Auÿerdem kann man Techniques eine Distanz
zuweisen, sodass weiter entfernte Objekte mit einfacheren Techniques gerendert werden,
um eine bessere Performance zu erzielen.
44
Techniques können über die Eigenschaft scheme einem Renderschema zugeordnet wer-
den. Durch die Denition von z.B. den 3 Schemas Hoch, Mittel und Niedrig kann
man die Anwendung schnell an die vorhandene Hardware anpassen. Wobei bei der Ein-
stellung Hoch alle Objekte mit den bestmöglichen Techniques gerendert werden (, die
mit der Eigenschaft scheme Hoch deniert sind) und bei Niedrig auf weniger perfor-
mancehungrige Techniques zurückgegrien wird.
Die Eigenschaft lod_index weist eine Technique einer Entfernung zu, die im Materi-
al über die Eigenschaft lod_distance deniert wurde. Dies ermöglicht eine weniger
rechenintensive Technique für weiter enfternte Objekte.
4.1.2 Passes
Ein Pass ist ein Renderdurchlauf für ein Objekt mit festgelegten Rendereinstellungen.
Eine Technique kann bis zu 16 Passes beinhalten. Je mehr Passes, desto aufwendiger wird
die Berechnung. Passes enthalten Attribute, Textureinheiten und eventuell Verweise auf
Vertex- und Pixelshader (siehe Kapitel 4.1.3 auf Seite 47).
Hier folgt eine auszugsweise Beschreibung der möglichen Attribute in einem Pass. Für
ausführliche Informationen siehe [The06b].
Allgemeine Attribute
Das Attribut scene_blend legt fest, wie der Pass mit bereits gerenderten Inhalten der
Szene überblendet wird.
Über depth_check bestimmt man, ob der Tiefenbuer1 vor dem Rendern überprüft wird.
depth_write bestimmt, ob das aktuelle Objekt in den Tiefenbuer schreibt. Overlay-
Elemente, wie z.B. Menüs oder Buttons, sollten nicht in den Tiefenbuer schreiben.
cull_hardware bestimmt, ob die Hardware Flächen aufgrund der Anordnung ihrer Ver-
tizes vom Rendering ausschlieÿt. Sind die Vertizes von der Kamera aus gesehen im
Uhrzeigersinn angeordnet und ist das Atribut auf clockwise gesetzt, so werden sie vom
Rendering ausgeschlossen. Ist das Attribut auf none gesetzt, so werden die Flächen von
beiden Seiten gezeichnet, was z.B. bei durchsichtigen Objekten sinnvoll ist. Das Attri-
but polygon_mode bestimmt, wie das Objekt gerendert wird. solid rendert das Objekt
normal, wireframe rendert nur die Auÿenlinien der Flächen (als Drahtgitter) und point
rendert nur die Punkte des Polygons.
Das Attribut iteration bestimmt, ob der Pass mehrfach gerendert werden soll. Auÿer-
dem kann mit dem Setzen des Attributes auf once_per_light der Pass für jedes Licht
durchlaufen werden.
1 DerTiefenbuer (auch Z-Buer genannt) enthält die Tiefeninformation bereits gerenderter Objekte.
Liegt das aktuelle Objekt hinter einem bereits gezeichneten wird es nicht gerendert.
45
Attribute für die Fixed-Function Pipeline
Folgende Attribute haben nur Auswirkungen auf das Renderergebniss solange die Fixed-
Function Pipeline von OGRE benutzt wird. Bei Verwendung Shadern haben diese keine
Auswirkungen.
Die Attribute ambient2 , diffuse3 , specular4 und emissive5 beschreiben die entspre-
chenden Farben des Materials.
Über das Attribut lighting aktiviert und deaktiviert die dynamische Beleuchtung.
Über shading legt man auÿerdem fest, ob das Objekt flat-6 , gouraud-7 oder phong-
8
schattiert wird.
Textureinheiten
Das Attribut texture bestimmt über einen Dateinamen, welche Textur verwendet wird
und um welchen Texturtyp es sich dabei handelt. Unterstützte Typen sind 1d (eine Tex-
tur, die nur einen Pixel hoch ist, nützlich in Shadern, wenn man eine aufwendige Funk-
tion vor der Laufzeit in eine Textur kodiert, eine sogenannte Look-Up Tabelle), 2d (eine
normale Textur), 3d (eine Volumentextur, also mehrere 2D-Texturen, die über dreidi-
mensionale Texturkoordinaten in Shadern adressiert werden, z.B. nützlich für animierte
Texturen) und cubic(zusammengesetzte Textur aus 6 zweidimensialen Texturen, die auf
die Innenseiten eines Würfels projiziert werden, wird z.B. für Umgebungs-Mapping ver-
wendet).
tex_adress_mode steuert wie die Textur wiederholt wird. Mögliche Werte sind wrap
(Textur wird wiederholt), clamp (Ende der Textur wird breitgezogen), mirror (Textur
wird an den Rändern gespiegelt) und border (Textur wird an den Ränder auf eine mit
tex_border_colour denierte Farbe gesetzt).
Wie eine Textur aus bestimmten Betrachtungswinkeln und beim Übergang zweier MIP-
Stufen geltert wird, bestimmt das Attribut filtering. Mögliche Werte in qualitativ
aufsteigender Reihenfolge sind none(es wird keine Filterung vorgenommen und auch
kein MIP-Mapping angewendet), bilinear(Textur wird über einen 2x2-Filter interpo-
liert und es wird eine MIP-Stufe ausgewählt, aber nicht zwischen zwei Stufen überblen-
det), trilinear(wie bilinear, nur das hier zwei MIP-Stufen überblendet werden) und
2 Die ambiente Farbe bestimmt die Grundfarbe des Objekts ohne Beleuchtung.
3 Die diuse Farbe bestimmt die Farbe des Objekts, die durch einfallendes Licht reektiert wird.
4 Die spekulare Farbe bestimmt die Glanzfarbe des Objekts.
5 Die emissive Farbe bestimmt die Farbe mit der das Objekt unabhängig von Lichtquellen oder am-
bienten Farbeinstellungen gezeichnet wird.
6 Jede Objektäche wird mit einer Farbe gezeichnet. Diese wird nicht interpoliert.
7 Die Farbe jedes Vertex wird linear über die Fläche interpoliert.
8 Die Vertexnormalen werden über die Fläche interpoliert und somit die Farbe pro Pixel berechnet.
Dies ergibt ein realistischeres Ergebniss bei höherem Berechnungsaufwand.
46
anisotropic(wie trilinear, nur das hier der wirkliche Anstieg der Fläche zur Filterung
herangezogen wird und nicht nur ein einfacher 2x2-Filter verwendet wird).
Für weitere Attribute, insbesondere zum Animieren der Texturen, siehe [The06b].
Ein Shader ist ein kleines Programm, um die nalen Oberächeneigenschaften eines 3D-
Modells oder Bildes zu berechnen. Aufgrund ihrer Natur sind sie sehr gut für parallele
Berechnung geeignet. Mehrere parallele Prozessoren auf aktuellen Grakkarten berech-
nen somit mehrere Dreiecke und die resultierenden Pixel gleichzeitig. Shader sind in
speziellen Programmiersprachen geschrieben und ersetzen grundlegende Funktionen der
traditionellen Fixed-Function-Pipeline wie z.B. Transformationen, Beleuchtung und Tex-
turoperationen. Dabei gibt es auf der Grakkarte mehrere Vertex-Einheiten, die Vertex-
Shader (wird für jedes Vertex des zu rendernden Objekts einmal ausgeführt) und mehrere
Pixel-Einheiten, die Pixel-Shader (wird für jedes auf dem Bildschirm sichtbare Pixel ein-
mal ausgeführt.
OGRE bietet volle Unterstützung für Vertex- und Pixelshader bis zum aktuellen Sha-
dermodell 3.0. Somit sind alle modernen Beleuchtungs-(z.B. PerPixel-Lighting, Bump-,
Normal- und Parallax Mapping) und Materialsimulationen(z.B. Wasser- und Glasmate-
rialien) und Eekte (z.B. High Dynamic Range Rendering, Nebel und Tiefenunschärfe)
in OGRE möglich. Die Shader können in Assembler, DirectX9 HLSL, OpenGL GLSL
oder in Cg vorliegen.
Shader müssen erst in einem Skript deklariert werden, bevor sie von einem Materi-
al benutzt werden können. Dies kann entweder in dem entsprechenden Materialskript
direkt oder in einer .program-Datei geschehen. Die Deklaration umfasst einen eindeu-
tigen Namen, den Dateinamen des Shaderquellcodes und für welches Shadermodell der
Shader kompiliert werden soll. Folgendes Beispiel deklariert einen HLSL Vertexshader
mit dem Namen PerPixelLighting.Vertex_Shader, der aus der Datei PerPixelLight-
ning.Vertex_Shader.source geladen und für das Shadermodell 2.0 kompiliert wird:
vertex_program PerPixelLighting . Vertex_Shader hlsl
{
source PerPixelLightning . Vertex_Shader . source
target vs_2_0
entry_point vs_main
default_params
{
param_named_auto lightPosition light_position_object_space 0
47
param_named_auto eyePosition camera_position_object_space
param_named_auto worldViewProj worldviewproj_matrix
param_named shininess float 10
}
}
Innerhalb eines Passes in einem Materialskript kann man Vertex- und Pixelshader, die
zuvor in einem Skript deklariert wurden, referenzieren:
vertex_program_ref PerPixelLighting . Vertex_Shader
{
param_named_auto worldViewProj worldviewproj_matrix
param_named shininess float 20
}
48
Die abschlieÿende 0 gibt an, dass die Position der dem Objekt nächsten Lichtquelle
übergeben wird. Höhere Werte beziehen sich dann auf weiter entfernte Lichtquellen.
Wenn man Stencil-Schatten benutzt, so sollte man nur skeletale Animation einsetzen,
denn jede andere Geometriedeformation hat keine Auswirkung auf den berechneten
Schatten. Man kann dann nur akzeptieren, dass der Schatten nicht korrekt zur geren-
derten Geometrie passt oder auf Schattenberechnung ganz verzichten (es sei denn, man
realisiert die Schattenberechnung ebenfalls in einem Shader, was aber ein sehr komplexes
Gebiet ist).
Im folgenden Beispiel wird ein einfacher Shader erarbeitet, der ein 3D-Modell durch
diuses Licht einer Punktlichtquelle beleuchtet und das Ergebnis mit einer Textur ver-
sieht. Der Shader wird in HLSL für das Shadermodell 2.0 geschrieben. Da die Licht-
berechnung im Vertexshader geschieht spricht man auch von PerVertex-Lighting. Das
genauere PerPixel-Lighting würde es erfordern, dass die Lichtberechnungen im Pixels-
hader ausgeführt werden, was aber zu einer schlechteren Performance führt.
Der Vertex-Shader
Zuerst deniert man, welche Informationen des zu rendernden Objektes dem Shader als
Eingabe dienen. Für den Beispielshader benötigen wir die Position, die Normale und die
Texturkoordinate des aktuellen Vertex;
struct VS_INPUT
{
float4 Pos : POSITION ;
float3 Normal : NORMAL ;
float2 Txr : TEXCOORD0 ;
};
49
Die Ausgabe des Vertexshaders ist zugleich Eingabe des Pixelshaders und umfasst die
(transformierte) Position, die Texturkoordinaten und die berechnete Lichtintensität:
struct VS_OUTPUT
{
float4 Pos : POSITION ;
float2 Txr : TEXCOORD0 ;
float4 Color : COLOR0 ;
};
Auÿerdem müssen folgende Variablen deklariert werden, deren Werte von der Anwen-
dung (in diesem Falle das OGRE-System) bereitgestellt werden müssen:
float4x4 matViewProjection ; // Transformationsmatrix
float4 viewPosition ; // Die Position des Betrachters
float4 LightAmbient ; // Das ambiente Licht
float4 LightPos ; // Position der Lichtquelle
float4 LightColor ; // Farbe der Lichtquelle
Für die Lichtberechnung wird der Übersichtlichkeit wegen eine Funktion deniert, die
aus der Vertex-Position und -Normalen und der Lichtposition und -farbe die Intensität
des reektierten Lichtes auf dem 3D-Modell berechnet:
float4 Light_PointDiffuse ( float3 VertPos , float3 VertNorm ,
float3 LightPos , float4 LightColor )
{
// Berechne die Richtung aus der das Licht auf den Vertex trifft und
// normalisiere diesen Vektor
float3 LightDir = normalize ( LightPos - VertPos );
50
// Leite Texturkoordinaten unverändert weiter
Out . Txr = In . Txr ;
return Out ;
}
Der Pixel-Shader
Der Pixelshader benötigt als Eingabe nur die Texturkoordinaten und die berechnete
Lichtintensität. Da ein Vertex eher selten genau einem Pixel auf dem Bildschirm ent-
spricht, werden die berechneten Werten zwischen verschiedenen Vertizes interpoliert.
struct PS_INPUT
{
float2 Txr : TEXCOORD0 ;
float4 Color : COLOR0 ;
};
Die Abbildung 4.1 auf Seite 52 zeigt den Shader in RenderMonkey von ATI (siehe auch
Kapitel 7.7 auf Seite 79). Der komplette zusammenhängende Quellcode ist in Listing
51
Abbildung 4.1: Der PerVertex-Lighting Beispielshader
A.5 auf Seite 92 zu nden. Für weitere Informationen zur Shaderprogrammierung siehe
[SL04], [Ros04], [Fer04] und [Eng05].
4.2 Compositor-Skripte
Um einen PostProcessing-Eekt zu erzeugen, wird erst die gesamte Szene in eine Textur
gerendert. Diese wird über einen Pixelshader auf einen Viereck gerendert, das den gan-
zen Bildschirm umfasst. Das Ergebniss dieses Vorganges kann entweder das endgültige
auf dem Bildschirm erscheinende Bild oder aber wieder eine Zwischentextur sein, die
durch weitere Compositor-Passes bearbeitet wird.
Mehrere Compositors können in einem sogenannten CompositorChain hintereinander
angeordnet werden, d.h. der nächste Compositor erhält als Eingabe die Ausgabe des vor-
herigen Compositors. Diese Kette wird automatisch von OGRE angelegt und die Reihen-
folge richtet sich nach der Reihenfolge der CompositorManager::getSingleton().add-
Compositor()-Aufrufe. Die Hauptkomponenten von einem Compositor sind Techniques,
Target Passes und Passes. Das Skript ist wieder in einem Pseudo-C++-Dialekt geschrie-
ben und jeder Compositor muss einen global eindeutigen Namen zugewiesen bekom-
men.
52
4.2.1 Techniques
Die erstere Zeile deniert ein RenderTarget, das 512 mal 512 Pixel groÿ ist und bei dem
jeder Pixel aus jeweils 8 Bit für Alpha-, Rot-, Grün- und Blauwert zusammengesetzt ist.
Die zweite Zeile gibt ein RenderTarget an, das so groÿ wie das Renderfenster ist und bei
dem die Pixel in einem speziellen Flieÿkommaformat gespeichert sind, welches z.B. für
HDR-Rendering benötigt wird.
Ein Target Pass ist ein Renderdurchlauf, der entweder in eine Render-Textur oder die
nale Ausgabe rendert. Das Element target <Name> rendert in eine Textur, während
das Element target_output <Name> die nale Ausgabe erzeugt. Die Inhalte beider
Elemente sind gleich. Der Compositor kann jedoch nur ein target_output-Element
aber mehrere target-Elemente haben. Folgende Attribute können in diesen Elementen
vorkommen:
Über input wird denniert, ob die Textur vor dem Rendering mit Inhalt gefüllt wird.
Der Wert previous sorgt dafür, dass die Textur mit der Original-Renderszene, falls es
der erste Compositor in der Kette ist, oder mit der Ausgabe des vorherigen Compositors
gefüllt wird. Der Wert none belässt die Textur unbefüllt, so dass diese vollständig durch
den aktuellen Pass gefüllt werden muss.
Das Attribut only_initial on sorgt dafür, dass dieser Pass nur einmal bei Aktivieren
des Compositors gerendert wird. Dies ist nützlich, wenn dieser Pass nur statische Inhalte
enthält.
Das Attribut material_scheme teilt diesen Pass ähnlich dem gleichlautenden Attribut
in Materialskripten einer Qualitätseinstellung zu.
53
4.2.3 Compositor Pass
Ein Compositor Pass ist ein in einem Target Pass durchgeführtes Rendering. Es gibt
folgende Pass-Typen:
• clear: Dieser Pass setzt den Inhalt eines Buers einer Zieltextur auf einen be-
stimmten festen Wert. Dies könnte z.B. den Farbbuer auf eine bestimmte Farbe
oder den Tiefenbuer auf einen bestimmten Wert setzen.
• stencil: Dieser Pass konguriert den Stencilbuer und die zugehörigen Stencil-
funktionen für nachfolgende Passes.
• render_scene: Dieser Pass rendert die komplette Szene in eine Textur.
• render_quad: Dies rendert ein bildschirmumfassendes Viereck durch ein deniertes
Materialskript in eine Textur. Das eigentliche PostProcessing wird durch diesen
Pass gerendert.
Das Attribut material bestimmt das Material für einen render_quad-Pass. Man be-
nutzt Pixelshader in diesem Material um PostProcessing-Eekte zu rendern und übergibt
Texturen an das Material über das Attribut input. Für weitere Informationen zu den
Buer- und Stenciloperationen siehe [The06b].
Folgendes Beispiel zeigt den Aufbau eines PostProcessing-Eekts, das einen Weichzeich-
ner zweimal über die gerenderte Szene laufen lässt (siehe Abbildung 4.2 auf Seite 55):
compositor SimpleBlur
{
technique
{
texture rt target_width target_height PF_R8G8B8
texture rt2 target_width target_height PF_R8G8B8
target rt2
{
input none
pass render_quad
{
material ScreenSpace . SimpleBlur
input 0 rt
}
}
54
target_output
{
input none
pass render_quad
{
material ScreenSpace . SimpleBlur
input 0 rt2
}
}
}
}
Für das zugehörige Materialskript und den Pixelshader siehe Listing A.3 auf Seite 90.
4.3 Particle-Skripte
55
Nachfolgend werden einige ausgewählte Eigenschaften erläutert (für eine vollständige
Eigenschaftenliste siehe [The06b]):
• quota - Setzt die maximale Anzahl an Partikeln. Wenn dieses Limit erreicht wird,
so werden für eine Weile keinen neuen Partikel emittiert bis einige zerstört wurden
(weil sie z.B. ihre Lebensdauer überschritten haben).
• material - Legt das Material für alle Partikel des Systems fest. Jedoch kann jedes
Partikel sein Aussehen über sein Farbattribut ändern.
• particle_width und particle_height - Bestimmt die Breite und Höhe eines
Partikels in Weltkoordinaten.
• cull_each - Ist standardmäÿig auf false gesetzt, so dass das Partikelsystem über
seinen umgebenden Hüllquader9 auf Sichtbarkeit geprüft wird. Dies ist vorteilhaft
bei kleinen lokalen Partikelsystemen. Bei Systemen, die jedoch einen groÿen Be-
reich abdecken, kann es zu einer besseren Performance führen, wenn jedes Partikel
auf Sichtbarkeit geprüft wird.
• local_space - Normalerweise bewegen sich Partikel nachdem sie emittiert wur-
den unabhängig vom Emitter im Weltkoordinatensystem. Sollen sich die Partikel
jedoch in Abhängigkeit zum Emitter bewegen (im lokalen Raum des Emitters) so
wird diese Eigenschaft auf true gesetzt.
• billboard_type - Partikel werden über sogenannte Billboards gerendert. Das sind
Rechtecke, die aus 2 Dreiecken bestehen, die rotiert werden, um in eine bestimmmte
Richtung zu zeigen. Normalerweise sind diese Rechtecke direkt der Kamera zuge-
wandt (Parameter point). Dies ist für kugelförmige Partikel, wie z.B. Lichteekte,
gut geeignet. Bei andere Eekte, wie z.B. Laserfeuer, erzielt man bessere Ergebnis-
se mit Partikeln, die in eine eigene Richtung zeigen (Parameter oriented_self).
Der Parameter oriented_common richtet die Partikel an einem gegebenen Vektor
(Eigenschaft common_direction) aus.
4.3.1 Partikelemitter
• angle - Bestimmt den maximalen Winkel in Grad, indem die Partikel abweichend
zur Emitter-Richtung erzeugt werden.
• colour - Bestimmt eine Farbe, in der alle Partikel gerendert werden.
56
• colour_range_start und colour_range_end - Legt einen Farbbereich fest, in-
nerhalb dessen Partikel zufällig gefärbt werden.
• direction - Legt die Richtung des Emitters relativ zu seinem Szenenknoten fest.
• emission_rate - Deniert, wie viele Partikel pro Sekunde emittiert werden.
• position - Bestimmt die Position des Emitters relativ zu seinem Szenenknoten.
• velocity - Legt eine feste Geschwindigkeit für alle emittierten Partikel fest.
• time_to_live - Deniert eine Anzahl von Sekunden, die der Partikel lebt bevor
er zerstört wird.
• duration - Deniert eine Anzahl von Sekunden, die der Emitter aktiv ist. Standard
ist hier 0 für unbegrenzte Aktivität.
• repeat_delay - Falls eine duration ungleich 0 angegeben wurde, deniert dieser
Wert die Zeitdauer in Sekunden bis der Emitter wieder aktiv wird.
4.3.2 Partikelaektoren
Aektoren nehmen auf verschiedene Weise Einuss auf ein Partikelsystem. Es gibt fol-
gende Aektoren in OGRE:
• LinearForce - Dieser Aektor addiert eine lineare Kraft, wie z.B. Wind und Gra-
vitation, zu allen Partikeln. Er wird deniert durch einen force_vector und ob
die Kraft addiert oder der Durchschnitt zwischen Kraft und dem Bewegungsvektor
gebildet wird (force_application average|add).
• Scaler - Skaliert Partikel um einen bestimmten Wert (rate).
• Rotator - Rotiert die Partikel, indem die Partikeltextur rotiert wird. Wird durch
Anfangs- und Endrotationsgrade (rotation_range_start und rotation_range_-
end) und durch Rotationsgeschwindigkeiten (rotation_speed_range_start und
rotation_speed_range_end) deniert.
• ColourFader - Dieser Aektor verändert die Farbe eines Partikels um einen be-
stimmten Wert pro Sekunde.
57
4.3.3 Ein einfaches Beispiel-Partikelskript
Folgendes Particle-Skript deniert das in Abbildung 4.3 auf Seite 59 gezeigte Partikel-
system:
PurpleFountain
{
// Grundeinstellungen
material Examples / Flare2
particle_width 20
particle_height 20
cull_each false
quota 10000
billboard_type oriented_self
// Der Partikelemitter
emitter Point
{
angle 15
emission_rate 75
time_to_live 3
direction 0 1 0
velocity_min 250
velocity_max 300
colour_range_start 1 0 0
colour_range_end 0 0 1
}
// Gravitation
affector LinearForce
{
force_vector 0 -100 0
force_application add
}
Für die Erstellung von Particle-Skripten empehlt sich der ParticleEditor (siehe Kapitel
7.4 auf Seite 76), der sofortiges optisches Feedback liefert und intuitiv zu bedienen ist.
58
4.4 Overlay-Skripte
Overlays werden nach allen anderen Objekten gerendert und verdecken damit darun-
terliegende Bereiche. Sie eignen sich für Menüs und Buttons oder sonstigen relevanten
Anzeigen wie z.B. einer Punkteanzeige oder eines Raumschi-Cockpits in Spielen. Man
kann Overlays direkt im Quellcode denieren oder in einem .overlay-Skript, welches die
Overlay-Dention in einer Pseudo-C++-Sprache enthält.
Overlay-Skripte enthalten die Denition von einem oder mehreren Overlays, die global
eindeutige Namen haben müssen. Jedes Overlay besteht aus der Eigenschaft zorder
und ein oder mehreren Elementen. zorder bestimmt die Reihenfolge, in der die Overlays
gerendert werden. Ein Overlay mit höherem Wert überdeckt ein Overlay mit niedrigerem
Wert.
59
4.4.1 Elemente zu Overlays hinzufügen
Ein Overlay kann Elemente (ein 2D-Element, das keine weiteren Kindelemente hat)
und Container (enthält selber wieder Elemente und Container) beinhalten. Ein Element
oder Container setzt sich aus einem Typen und einem eindeutigen Namen zusammen. In
OGRE vordenierte Typen sind Panel, BorderPanel, TextArea und TextBox. Elemente
oder Container können folgende Eigenschaften enthalten:
4.4.2 Standard-Overlay-Elemente
• Panel - Eine rechteckiger Bereich mit einem Hintergrundmaterial, der weitere Ele-
mente enthalten kann
• BorderPanel - Wie Panel, nur das ein separates Material für einen Begrenzungs-
rahmen gesetzt werden kann
• TextArea - Wird genutzt, um Text zu rendern
• TextBox - Wie TextArea, nur das man Text eingeben kann
60
// Der Name des Overlays
Overlays / BeispielOverlay
{
zorder 200
Diese Font-Dention ist vom Typ type image und die Eigenschaft source <Dateiname>
legt die Schriftart-Textur fest. Danach folgt eine Reihe von Zeichendenitionen in der
folgenden Form: glyph <Zeichen> <u1> <v1> <u2> <v2>, wobei die vier Koordinaten
das Rechteck beschreiben, in dem sich das Zeichen in der angegebenen Textur bendet.
61
Solche Koordinaten von Hand zu erzeugen ist sehr zeitaufwendig. Ein gutes Werkzeug für
die Generierung der Texturen als auch der Koordinaten ist der BitmapFontBuilder10 .
Diese Art der Font-Denition ist vom Typ type truetype und hier legt die Eigen-
schaft source <Dateiname> die zugrundeliegende TrueType-Schriftart fest. Die Eigen-
schaft size legt die Gröÿe der Zeichen in der generierten Textur fest und die Eigen-
schaft resolution bestimmt die endgültige Gröÿe der Zeichen, wenn sie gerendert wer-
den. Folgendes Skript erzeugt eine Schriftart mit dem Namen Schriftart1 aus der Datei
Schrift1.ttf
, die nun von Overlay-Elementen zur Textausgabe genutzt werden kann:
Schriftart1
{
type truetype
source Schrift1 . ttf
size 16
resolution 96
}
62
5 Schatten in OGRE 3D
5.1 Allgemeines
Schatten stellen eines der anspruchvollsten Gebiete der 3D-Programmierung dar. Es gibt
sehr verschiedene Ansätze zur Schattenberechnung, jedoch ist keiner perfekt und sie ha-
ben alle ihre spezischen Vor- und Nachteile. Trotzdem sind Schatten für die Glaub-
würdigkeit der dargestellten Szene wichtig, weil sie räumliche Beziehungen zwischen den
Objekten verdeutlichen. OGRE stellt mehrere Schattenimplementationen bereit.
Schatten sind standardmäÿig deaktiviert. Man aktiviert sie mit dem Aufruf der Scene-
Manager::setShadowTechnique()-Methode und der gewünschten Schattenart als Pa-
rameter:
mSceneMgr - > setShadowTechnique ( SHADOWTYPE_STENCIL_ADDITIVE );
63
5.1.2 Modulative Schatten
Modulative Schatten dunkeln im Schatten liegende Bereiche einer zuvor mit voller Lichtin-
tensität gerenderten Szene mit einer fest eingestellten Farbe ab. Erst wird die Szene mit
allen Schatten erhaltenden und werfenden Objekten gerendert. Danach dunkelt ein mo-
dulativer Rendering-Durchlauf pro Lichtquelle die im Schatten liegenden Bereiche ab.
Zum Schluss werden alle Objekte gerendert, die keine Schatten erhalten sollen. Dieses
Schattenmodell ist nicht sehr wirklichkeitsnah, da es bei mehreren Lichtquellen zu unna-
türlich dunklen Schatten kommen kann. Jedoch sind modulative Schatten mit statischen
in Texturen vorberechneten Schatten kompatibel.
Beim Additive Light Masking wird die Szene in mehreren Durchläufen gerendert. Dabei
stellt jeder Durchlauf die Lichtberechnung einer Lichtquelle dar. Im Schatten liegende
Bereiche werden dabei ausmaskiert. Jeder Durchlauf addiert seine Berechnungsergebnis-
se zum vorigen Durchlauf. Diese Technik führt zu sehr realistischen Ergebnissen aber zu
Lasten der Performance aufgrund mehrerer Render-Durchläufe.
5.2 Stencil-Schatten
• Finde alle Kanten, die zur Silhouette des Schatten werfenden Objektes gehören
• Expandiere alle gefundenen Kanten in entgegengesetzter Richtung zur Lichtquelle
• Schlieÿe das erzeugte Volumen mit einer Front- und einer Rückäche
64
5.2.1 Das HelloWorld-Beispiel mit Stencil-Schatten
Nun wird das HelloWorld-Beispiel aus Kapitel 3.2 auf Seite 32 um Stencil-Schattenbe-
rechnung erweitert. Dabei soll der OGRE-Kopf einen Schatten auf eine darunterliegende
Ebene werfen. Um Stencil-Schatten in OGRE zu benutzen, muss zunächst die Schatten-
art über folgenden Funktionsaufruf gesetzt werde:
mSceneManager - > setShadowTechnique ( SHADOWTYPE_STENCIL_MODULATIVE );
Dieser Methodenaufruf muss vor dem Laden jeglicher 3D-Modelle geschehen. Da die
Schattenfarbe standardmäÿig auf Schwarz gesetzt ist, kann man den Schattenwurf etwas
abschwächen:
mSceneManager - > setShadowColour ( ColourValue (0.5 , 0.5 , 0.5));
Nun wird die Ebene deniert, auf die der Schatten fällt:
// Ein Ebenen - Mesh generisch über den MeshManager erstellen
MeshManager :: getSingleton (). createPlane ( " plane . mesh " , " Custom " ,
Plane ( Vector3 :: UNIT_Z , Vector3 :: ZERO ) , 250 , 250 , 10 , 10);
65
// Ebene ausrichten und etwas nach unten verschieben
mGroundNode - > setOrientation ( Quaternion ( Radian ( - Math :: HALF_PI ) ,
Vector3 :: UNIT_X ));
mGroundNode - > translate (0 , -50 , 0);
Hier wird ein Ebenen-Mesh (plane.mesh) generisch über den MeshManager. erstellt.
Danach kann man wie gewohnt ein Entity mit dem Mesh laden. Das Ergebniss ist in
Abbildung 5.1 auf Seite 65 zu sehen.
Textur-basierte Schatten werden berechnet, indem getestet wird, ob ein Objekt von der
Lichtquelle aus gesehen sichtbar ist. Dieser Test geschieht durch einen Vergleich der
Tiefe des Objekts (von der Lichtquelle aus gesehen) mit einer Tiefentextur. Die Tiefen-
textur wird erstellt, indem die Szene von der Lichtquelle aus gerendert wird, dabei aber
nicht der Farbwert, sondern die Tiefeninformation gespeichert wird. Das Ergebniss ist
die sogenannte Schatten-Map. Diese Technik ist ungenauer als die Stencil-Technik, aber
in vielen Fällen performanter. Auÿerdem können die Schatten-Maps weiter bearbeitet
werden, um z.B. weiche Schatten zu erzeugen (dabei werden mehrere Schatten-Maps
in unterschiedlichen Auösungen berechnet und daraus ein nale Schatten-Map durch
Überblendung berechnet), was mit Stencil-Schatten nicht möglich ist. Ein wichtiger Fak-
tor für die Genauigkeit der Schatten ist die Auösung der Schatten-Map (Abbildung 5.2
auf Seite 66 zeigt die Auswirkung der Auösung der Schatten-Map auf das Renderer-
gebniss in der Beispielanwendung aus dem Kapitel 5.2). Je kleiner die Schatten-Map
desto ungenauer und weicher werden die Schatten. In OGRE wird diese Schattenart
folgenderweise aktiviert:
mSceneManager - > setShadowTechnique ( SHADOWTYPE_TEXTURE_MODULATIVE );
(a) Schatten-Map mit einer Auösung (b) Schatten-Map mit einer Auösung
von 1024 x 1024 Pixeln von 128 x 128 Pixeln
66
6 Animation in OGRE 3D
Unter skeletaler Animation versteht man die Deformation von 3D-Modellen mithilfe ei-
ner skeletalen Struktur (auch Bones, dt. Knochen, genannt). Dabei werden die Knochen
des Modells animiert, was zur Folge hat, dass auch die entsprechend zugewiesenen Verti-
zes bewegt werden. Jeder Vertex wird einem oder mehreren Knochen in einer bestimmten
Wichtung zugewiesen. Die skeletale Struktur wird bei der Erstellung des 3D-Modells in
einem 3D-Modelling-Programm, wie z.b. 3ds Max oder Maya, festgelegt und über einen
Exporter in das OGRE-.mesh- und -.skeleton-Format exportiert (siehe Kapitel 7.5 auf
Seite 76). Die Animationen werden in der .skeleton-Datei gespeichert, die automatisch
mit geladen wird, wenn eine .mesh-Datei geladen wird, zu der ebenfalls ein Skelett de-
niert und exportiert wurde. Folgende Leistungsmerkmale werden von OGRE dabei
unterstützt:
Skeletale Animation kann entweder vom Hauptprozessor oder in Vertexshadern von der
Grakkarte berechnet werden. Wobei letzteres aufgrund der Leistungsfähigkeit heutiger
Grakkarten empfehlenswert ist.
67
mation Walk:
// Variablen
AnimationState * mAnimationState ; // Zur Ansteuerung der aktuellen
// Animation
Entity * mEntity ; // Das Roboter - Entity
SceneNode * mNode ; // Der Roboter - Szenenknoten
// mAnimationState verweist nun auf die " Walk " - Animation des Roboters
mAnimationState = mEntity - > getAnimationState ( " Walk " );
Um eine Animation jedoch wirklich ablaufen zu lassen, muss noch die seit dem letzten
Bild vergangene Zeit addiert werden. Dies geschieht am besten in der frameStarted-
Methode einer OGRE::FrameListener-Klasse (siehe Kapitel 3.4 auf Seite 37):
mAnimationState - > addTime ( evt . timeSinceLastFrame );
Übergibt man der Methode negative Werte, so sind auch rückwärts ablaufende Anima-
tionen möglich.
Will man nun eine andere Animation startet, so beendet man die aktuelle, verweist auf
die neue Animation und startet diese:
mAnimationState - > setEnabled ( false );
mAnimationState = mEntity - > getAnimationState ( " Die " );
mAnimationState - > setLoop ( true );
mAnimationState - > setEnabled ( true );
Um eine weitere Animation gleichzeitig ablaufen zu lassen, holt man sich einen weite-
ren Verweis auf die zweite Animation und aktiviert diese. Über AnimationState::set-
Weight() kann die Gewichtung der Animation zu anderen gleichzeitig aktiven Anima-
tionen deniert werden:
AnimationState * mAnimationState2 ;
mAnimationState2 = mEntity - > getAnimationState ( " Idle " );
68
// Verringert die Gewichtung der zweiten Animation
mAnimationState2 - > setWeight (0.5);
mAnimationState2 - > setLoop ( true );
mAnimationState2 - > setEnabled ( true );
Für die Anwendung von skeletaler Animation im Kontext einer kompletten OGRE-
Anwendung siehe das interaktive Beispiel in Kapitel 3.5 auf Seite 39 sowie den zugehö-
rigen Quelltext im Anhang A.2 auf Seite 85.
6.2 Vertex-Animation
Bei der Vertex-Animation werden Informationen zur direkten Bewegung der zu einem
3D-Modell gehörenden Vertizes gespeichert. Diese Informationen sind direkt in der.mesh-
Datei enthalten. Vertex-Animation unterscheidet man nochmal in Morph- und Pose-
Animation, wobei Morph-Animation eine vereinfachte Form der Pose-Animation ist.
6.2.1 Morph-Animation
Bei der Morph-Animation werden zu jedem Schlüsselbild die absoluten Positionen al-
ler Vertizes eines 3D-Modells gespeichert. Man interpoliert nun linear zwischen einem
Schlüsselbild und dem ursprünglichen Erscheinungsbild des 3D-Modells und erzeugt so-
mit die Animationen. Es ist nicht möglich mehrere Schlüsselbilder zu überblenden. Diese
Art der Animation wird bei Objekten genutzt, die nicht durch skeletale Animation ani-
miert werden können, weil sich z.B. die Geometrie zu stark verformen soll.
6.2.2 Pose-Animation
Sowohl Morph- als auch Pose-Animationen können entweder vom Hauptprozessor oder
von einem Vertexshader berechnet werden. Man sollte jedoch bei der Pose-Animation
69
beachten, dass mit der Anzahl der eingesetzten Posen auch die Performance deutlich
sinken kann.
Pose-Animation wird in OGRE ähnlich verwendet wie skeletale Animation. Man benötigt
eine AnimationState-Variable, die auf eine Pose-Animation verweist:
// Die Animationsvariable
AnimationState * mAnimState ;
Nun muss der Animation noch die vergangene Zeit seit dem letzten Bild in der frameStarted()-
Methode eines FrameListener (siehe Kapitel 3.4 auf Seite 37) hinzugefügt werden:
mAnimState - > addTime ( evt . timeSinceLastFrame );
Die Abbildung 6.1 auf Seite 70 zeigt den sprechenden Charakter, den dieses Beispiel
erzeugt.
70
6.2.3 Kombination von skeletaler und Vertex-Animation
Skeletale und Vertex-Animation können gleichzeitig auf einem 3D-Modell benutzt wer-
den. Zuerst wird das Modell über die Vertex-Animation deformiert und erst danach wird
noch die skeletale Animation durchgeführt. Die Animation erfolgt immer auf Submesh-
Basis, d.h. man kann z.B. einen Charakter als einen Körper- und einen Kopf-Submesh
denieren und die skeletale Animation auf den Kopf- und Körper und die Pose-Animation
nur auf den Kopf anwenden. [The06b]
6.3 Szenenknoten-Animation
71
// Letztes Schlüsselbild an letzter Position (10)
key = track - > createNodeKeyFrame (10);
key - > setTranslate ( Vector3 (0 , 80 , -30));
// Animation starten
mCamAnimState - > setEnabled ( true );
Neben den bisher besprochenen Animationsarten kann man auÿerdem jeden numeri-
schen Wert animieren, dessen Klasse das Interface AnimableObject implementiert. Im-
plementiert eine Klasse dieses Interface, so stehen verschiedenen Methoden bereit, um
numerische Daten der Klasse mit dem generischen Animationssystem von OGRE zu
verbinden.
72
7 OGRE 3D-Tools und Exporter
7.1 XML-Konverter
Die DTD2 der XML-kodierten .mesh- und .skeleton-Dateien ndet man im OGRE-
Quellcodeverzeichnis3 unter /Tools/XMLConverter/docs
.
Der XML-Konverter ist nicht im OGRE-SDK enthalten sondern muss separat herunter-
geladen werden4 .
Gibt man als Option -i an, so wird man durch die möglichen Optionen geleitet.
Optionen sind z.B. die Generierung von Detailstufen und ihre zugehörigen Entfernungen,
die Generierung von Tangentenvektoren für Normal-Mapping usw. Weitere Optionen
73
(a) Original-Mesh (b) Details reduziert um 20 Prozent
Abbildung 7.1: Auswirkung der Detailreduktion des XML-Konverters auf die Texturpro-
jektion
7.2 MeshUpgrader
74
Einuss auf die Performance haben und man wird in der OGRE-Log-Datei auf die
Benutzung von älteren .mesh-Dateien hingewiesen und sollte dementsprechend auf die
neue Version upgraden.
Auch hier wird man wieder mit der Option -i durch alle möglichen Optionen geleitet,
die denen des XML-Konverters entsprechen.
7.3 MeshViewer
75
7.4 ParticleEditor
7.5 Mesh-Exporter
76
SoftImage XSI, Blender6 und 3ds Max verfügbar, wobei im Folgenden auf die beiden
letzteren näher eingegangen wird.
Der 3ds Max-Exporter für OGRE erlaubt das Exportieren beliebiger 3D-Modelle, egal
mit welcher Modellierungsart sie erstellt wurden. Es können also auch Modelle, die als
NURBS7 oder Subdivision Surfaces erstellt wurden, exportiert werden. Diese werden
vor dem Exportieren implizit in editierbare Netze, also Listen von Dreiecken, konver-
tiert. Es werden auch die Texturkoordinaten und eine Materialdenition exportiert. Der
Exporter erstellt eine XML-Datei des Modells im OGRE-spezischen Format. Optional
kann man diese gleich durch den OGRE XML-Konverter in eine binäre .mesh-Datei um-
wandeln lassen, inklusive dessen Einstellungsmöglichkeiten bezüglich der Erzeugung von
LODs, Tangentenvektoren usw. (siehe Kapitel 7.1 auf Seite 73). Auÿerdem lassen sich
auch Modelle inklusive ihrer Animationen exportieren. Unterstützte Animationsarten
und -einstellungen werden in Kapitel 6 auf Seite 67 erläutert.
7.5.2 Blender-Exporter
7.6 oFusion
oFusion8 ist eine Erweiterung zu Autodesks 3ds Max, die es ermöglicht, die erstellte Szene
inklusive aller erstellten Modelle, deren Materialien und Ausleuchtung innerhalb von
3ds Max in einem echten OGRE-Renderfenster zu betrachten. Man erhält ein sofortiges
77
optisches Feedback auf alle Änderungen der Szene und somit entfällt das häuge Testen
der veränderten Szene in der eigentlichen OGRE-Anwendung. oFusion erlaubt auch dem
Nichtprogrammierer hochwertige Inhalte für OGRE-Anwendungen in seiner bevorzugten
Software zu erstellen ohne sich um OGRE-spezische Details kümmern zu müssen.
Wichtigster Bestandteil ist das oFusion-Ansichtsfenster, welches sich nahtlos in 3ds Max
integriert (siehe Abbildung 7.4 auf Seite 78). Es werden 3D-Modelle, Lichter, Kameras,
Animationen, Materialien und Shader in der Echtzeit-Ansicht unterstützt.
Die in 3ds Max erstellten Inhalte können in für OGRE-geeignete Formate exportiert
werden. 3D-Modelle werden in das .mesh-, Materialien in das .-material-, Shader in das
.shader- und .program- und Skelettanimation in das .skeleton-Format exportiert.
Noch komfortabler exportiert man Inhalte von 3ds Max in die OGRE-Anwendung durch
den oFusion Scene Loader und dem dazugehörigen .osm-Format. Dabei wird die kom-
plette Szene in eine Datei exportiert, die von der OGRE-Anwendung durch einen Funk-
tionsaufruf geladen wird. Somit wird die Szene genauso, wie man sie in 3ds Max erstellt
hat, mit wenig Aufwand in die OGRE-Anwendung importiert.
oFusion ist für nicht kommerzielle Benutzung kostenlos. Zur kommerziellen Nutzung
werden keine öentlichen Preisinformationen zur Verfügung gestellt.
78
7.7 RenderMonkey-Shader Exporter
Mit dem Shader Exporter kann man nun den Shader in das OGRE-Shaderformat expor-
tieren. Der Shader-Quellcode wird dabei in .source-Dateien und die Shader-Deklaration
in .program-Dateien gespeichert. Auÿerdem wird eine Materialdenition in einer .material-
Datei erstellt. Diese Materialdenition kann man nun 3D-Modellen innerhalb der OGRE-
Anwendung mit Entity::setMaterialName() zuweisen (siehe Abbildung 7.6 auf Seite
80).
79
(a) Shader in RenderMonkey (b) Shader innerhalb einer OGRE-Anwendung
Abbildung 7.6: Ein einfacher Specular Lighting Shader mit einer Holztextur
Der Exporter liegt in Version 2 vor und bietet neben schlankeren erzeugten Materialde-
nitionen eine Vorschau in einem OGRE-Renderfenster.
80
8 Ausblick
Die Leistungsfähigkeit moderner Grakprozessoren wird weiter steigen und der Bedarf
an interaktiven 3D-Anwendungen weiter wachsen. Bisher hat es OGRE, dank seiner en-
gagierten Entwickler und der lebendigen Community, sehr gut geschat mit der techni-
schen Entwicklung Schritt zu halten. Ein Open Source Projekt steht und fällt mit seiner
Community und auf die kommt es in Zukunft an, um OGRE weiterzuentwickeln und
auf neue Technologien abzustimmen. Wichtig wäre es, auf weitere Kooperationen mit
professionellen Entwicklern zu setzen, um OGRE auch als Markenzeichen in der breiten
Öentlichkeit zu etablieren. Der deutsche Entwickler des Adventures Ankh, Deck13,
hat bereits einen Nachfolger, Ankh - das Herz des Osiris, wieder auf OGRE-Basis
angekündigt. Auÿerdem ist gerade ein Buch, das sich mit der OGRE-Programmierung
beschäftigt, erschienen (siehe [Jun06]).
81
A Quellcode
A.1 HelloWorld-Quellcode
82
38
39 void Keyboard :: keyReleased ( KeyEvent * e ) {}
40
41 bool Keyboard :: frameEnded ( const FrameEvent & evt )
42 {
43 return ! mEscWasPressed ;
44 }
45
46 int main ( int argc , char * argv [])
47 {
48 try
49 {
50 Root * mRoot = new Root ( " Plugins . cfg " , " Ogre . cfg " , " ogre . log " );
51
52 if (! mRoot - > showConfigDialog ())
53 return 1;
54
55 RenderWindow * mWindow = mRoot - > initialise ( true , " HelloWorld " );
56
57 ResourceGroupManager :: getSingleton (). addResourceLocation
58 ( " < Pfad zum OGRE - SDK >\\ media \\ materials \\ scripts " , " FileSystem " );
59 ResourceGroupManager :: getSingleton (). addResourceLocation
60 ( " < Pfad zum OGRE - SDK >\\ media \\ materials \\ programs " , " FileSystem " );
61 ResourceGroupManager :: getSingleton (). addResourceLocation
62 ( " < Pfad zum OGRE - SDK >\\ media \\ materials \\ textures " , " FileSystem " );
63 ResourceGroupManager :: getSingleton (). addResourceLocation
64 ( " < Pfad zum OGRE - SDK >\\ media \\ models " ," FileSystem " );
65 ResourceGroupManager :: getSingleton (). initialiseAllResourceGroups ();
66
67 SceneManager * mSceneManager = mRoot - > createSceneManager ( ST_GENERIC );
68
69 Entity * mOgreEntity = mSceneManager - > createEntity ( " OgreEntity1 " ,
70 " ogrehead . mesh " );
71
72 SceneNode * mOgreSceneNode = mSceneManager - > getRootSceneNode () - >
73 createChildSceneNode ( " Ogre1Node " , Vector3 (0 ,0 ,0));
74 mOgreSceneNode - > attachObject ( mOgreEntity );
75
76 mSceneManager - > setAmbientLight ( ColourValue (0.1 ,0.1 ,0.1 ,1));
77
78 Light * mLight = mSceneManager - > createLight ( " Light1 " );
79 mLight - > setPosition ( -80 , 80 , 200);
80
81 Camera * mCamera = mSceneManager - > createCamera ( " Camera1 " );
82 mCamera - > setNearClipDistance (5);
83 mCamera - > setPosition ( Vector3 (40 ,40 ,100));
84 mCamera - > lookAt (0 ,0 ,0);
85
86 Viewport * mViewport = mWindow - > addViewport ( mCamera );
87 mCamera - > setAspectRatio ( Real ( mViewport - > getActualWidth ()) /
88 Real ( mViewport - > getActualHeight ()));
89
83
90 Keyboard * mKeyboard = new Keyboard ();
91 mRoot - > addFrameListener ( mKeyboard );
92 EventProcessor * mEventProcessor = new EventProcessor ();
93 mEventProcessor - > addKeyListener ( mKeyboard );
94 mEventProcessor - > initialise ( mWindow );
95 mEventProcessor - > startProcessingEvents ();
96
97 mRoot - > startRendering ();
98 delete mRoot ;
99 }
100 catch ( Exception & e )
101 {
102 ErrorDialog * errorDlg =
103 PlatformManager :: getSingleton (). createErrorDialog ();
104 errorDlg - > display ( e . getFullDescription ());
105 delete errorDlg ;
106 return 1;
107 }
108 return 0;
109 }
84
A.2 Eine interaktive Anwendung-Quellcode
85
49 void Keyboard :: keyPressed ( KeyEvent * e )
50 {
51 if (e - > getKey () == KC_UP )
52 mTranslateVector . z -= mMoveScale ;
53 if (e - > getKey () == KC_DOWN )
54 mTranslateVector . z += mMoveScale ;
55
56 if (e - > getKey () == KC_LEFT )
57 mTranslateVector . x -= mMoveScale ;
58 if (e - > getKey () == KC_RIGHT )
59 mTranslateVector . x += mMoveScale ;
60
61 if (e - > getKey () == KC_PGUP )
62 mTranslateVector . y += mMoveScale ;
63 if (e - > getKey () == KC_PGDOWN )
64 mTranslateVector . y -= mMoveScale ;
65
66 if (e - > getKey () == KC_W )
67 mRotY -= mRotScale ;
68 if (e - > getKey () == KC_S )
69 mRotY += mRotScale ;
70
71 if (e - > getKey () == KC_A )
72 mRotX += mRotScale ;
73 if (e - > getKey () == KC_D )
74 mRotX -= mRotScale ;
75
76 if (e - > getKey ()== KC_NUMPAD8 )
77 {
78 mRobotTranslate . x += mMoveScale / 4;
79 mAnimIdle - > setEnabled ( false );
80 mAnimWalk - > setEnabled ( true );
81 }
82 if (e - > getKey ()== KC_NUMPAD2 )
83 {
84 mRobotTranslate . x -= mMoveScale / 4;
85 mAnimIdle - > setEnabled ( false );
86 mAnimWalk - > setEnabled ( true );
87 }
88 if (e - > getKey ()== KC_NUMPAD4 )
89 {
90 mRobotRot += mRotScale ;
91 mAnimIdle - > setEnabled ( false );
92 mAnimWalk - > setEnabled ( true );
93 }
94 if (e - > getKey ()== KC_NUMPAD6 )
95 {
96 mRobotRot -= mRotScale ;
97 mAnimIdle - > setEnabled ( false );
98 mAnimWalk - > setEnabled ( true );
99 }
100 }
86
101
102 void Keyboard :: keyReleased ( KeyEvent * e )
103 {
104 if (e - > getKey () == KC_UP )
105 mTranslateVector . z += mMoveScale ;
106 if (e - > getKey () == KC_DOWN )
107 mTranslateVector . z -= mMoveScale ;
108
109 if (e - > getKey () == KC_LEFT )
110 mTranslateVector . x += mMoveScale ;
111 if (e - > getKey () == KC_RIGHT )
112 mTranslateVector . x -= mMoveScale ;
113
114 if (e - > getKey () == KC_PGUP )
115 mTranslateVector . y -= mMoveScale ;
116 if (e - > getKey () == KC_PGDOWN )
117 mTranslateVector . y += mMoveScale ;
118
119 if (e - > getKey () == KC_W )
120 mRotY += mRotScale ;
121 if (e - > getKey () == KC_S )
122 mRotY -= mRotScale ;
123
124 if (e - > getKey () == KC_A )
125 mRotX -= mRotScale ;
126 if (e - > getKey () == KC_D )
127 mRotX += mRotScale ;
128
129 if (e - > getKey ()== KC_NUMPAD8 )
130 {
131 mRobotTranslate . x -= mMoveScale / 4;
132 mAnimIdle - > setEnabled ( true );
133 mAnimWalk - > setEnabled ( false );
134 }
135 if (e - > getKey ()== KC_NUMPAD2 )
136 {
137 mRobotTranslate . x += mMoveScale / 4;
138 mAnimIdle - > setEnabled ( true );
139 mAnimWalk - > setEnabled ( false );
140 }
141 if (e - > getKey ()== KC_NUMPAD4 )
142 {
143 mRobotRot -= mRotScale ;
144 mAnimIdle - > setEnabled ( true );
145 mAnimWalk - > setEnabled ( false );
146 }
147 if (e - > getKey ()== KC_NUMPAD6 )
148 {
149 mRobotRot += mRotScale ;
150 mAnimIdle - > setEnabled ( true );
151 mAnimWalk - > setEnabled ( false );
152 }
87
153 }
154
155 bool Keyboard :: frameStarted ( const FrameEvent & evt )
156 {
157 mAnimIdle - > addTime ( evt . timeSinceLastFrame );
158 mAnimWalk - > addTime ( evt . timeSinceLastFrame );
159
160 mOgreSceneNode - > yaw ( mRobotRot * evt . timeSinceLastFrame );
161 mOgreSceneNode - > translate ( mOgreSceneNode - > getOrientation () *
162 mRobotTranslate * evt . timeSinceLastFrame );
163
164 mCameraNode - > yaw ( mRotX * evt . timeSinceLastFrame );
165 mCameraNode - > pitch ( mRotY * evt . timeSinceLastFrame );
166 mCameraNode - > translate ( mCameraNode - > getOrientation () *
167 mTranslateVector * evt . timeSinceLastFrame );
168
169 return true ;
170 }
171
172 bool Keyboard :: frameEnded ( const FrameEvent & evt )
173 {
174 return ! mEscWasPressed ;
175 }
176
177 int main ( int argc , char * argv [])
178 {
179 try
180 {
181 Root * mRoot = new Root ( " Plugins . cfg " , " Ogre . cfg " , " ogre . log " );
182
183 if (! mRoot - > showConfigDialog ())
184 return 1;
185
186 RenderWindow * mWindow = mRoot - > initialise ( true ,
187 " Interaktive Anwendung " );
188
189 ResourceGroupManager :: getSingleton (). addResourceLocation
190 ( " C :\\ Programmieren \\ OgreSDK \\ media \\ materials \\ scripts " ,
191 " FileSystem " );
192 ResourceGroupManager :: getSingleton (). addResourceLocation
193 ( " C :\\ Programmieren \\ OgreSDK \\ media \\ materials \\ programs " ,
194 " FileSystem " );
195 ResourceGroupManager :: getSingleton (). addResourceLocation
196 ( " C :\\ Programmieren \\ OgreSDK \\ media \\ materials \\ textures " ,
197 " FileSystem " );
198 ResourceGroupManager :: getSingleton (). addResourceLocation
199 ( " C :\\ Programmieren \\ OgreSDK \\ media \\ models " ," FileSystem " );
200 ResourceGroupManager :: getSingleton (). initialiseAllResourceGroups ();
201
202 SceneManager * mSceneManager =
203 mRoot - > createSceneManager ( ST_GENERIC );
204
88
205 Entity * mOgreEntity = mSceneManager - > createEntity ( " OgreEntity1 " ,
206 " robot . mesh " );
207
208 mOgreSceneNode = mSceneManager - > getRootSceneNode () - >
209 createChildSceneNode ( " Ogre1Node " , Vector3 (0 ,0 ,0));
210 mOgreSceneNode - > attachObject ( mOgreEntity );
211 mOgreSceneNode - > setScale (0.5 , 0.5 , 0.5);
212
213 mAnimWalk = mOgreEntity - > getAnimationState ( " Walk " );
214 mAnimWalk - > setLoop ( true );
215 mAnimIdle = mOgreEntity - > getAnimationState ( " Idle " );
216 mAnimIdle - > setLoop ( true );
217 mAnimIdle - > setEnabled ( true );
218
219 mSceneManager - > setAmbientLight ( ColourValue (0.1 ,0.1 ,0.1 ,1));
220
221 Light * mLight = mSceneManager - > createLight ( " Light1 " );
222 mLight - > setPosition ( -80 , 80 , 200);
223
224 Camera * mCamera = mSceneManager - > createCamera ( " Camera1 " );
225 mCamera - > setNearClipDistance (5);
226 mCameraNode = mSceneManager - > getRootSceneNode () - >
227 createChildSceneNode ( " CameraNode " , Vector3 (0 , 0 , 0));
228 mCameraNode - > attachObject ( mCamera );
229 mCameraNode - > translate (40 , 40 , 100);
230 mCameraNode - > setDirection ( - mCameraNode - > getPosition ());
231
232 Viewport * mViewport = mWindow - > addViewport ( mCamera );
233 mCamera - > setAspectRatio ( Real ( mViewport - > getActualWidth ()) /
234 Real ( mViewport - > getActualHeight ()));
235
236 Keyboard * mKeyboard = new Keyboard ();
237 mRoot - > addFrameListener ( mKeyboard );
238 EventProcessor * mEventProcessor = new EventProcessor ();
239 mEventProcessor - > addKeyListener ( mKeyboard );
240 mEventProcessor - > initialise ( mWindow );
241 mEventProcessor - > startProcessingEvents ();
242
243 mRoot - > startRendering ();
244 delete mRoot ;
245 }
246 catch ( Exception & e )
247 {
248 ErrorDialog * errorDlg =
249 PlatformManager :: getSingleton (). createErrorDialog ();
250 errorDlg - > display ( e . getFullDescription ());
251 delete errorDlg ;
252 return 1;
253 }
254 return 0;
255 }
89
A.3 Compositor: Weichzeichner-Materialskript
90
A.4 Compositor: Weichzeichner-Pixelshader
91
A.5 Beispielshader: Vertexshader
92
49
50 // Das ambiente Licht definiert den Grundanteil der Lichtintensität
51 float4 Color = LightAmbient ;
52
53 // Berechne Lichtintensität durch die zuvor definierte Funktion
54 Color += Light_PointDiffuse ( In . Pos , In . Normal ,
55 LightPos , LightColor );
56
57 // Gib berechnete Farbe an den Pixelshader weiter
58 Out . Color = Color ;
59
60 return Out ;
61 }
93
B Inhalt der Begleit-CD
94
Literaturverzeichnis
95
[Val06] ValveSoftware.com: Engine Licensing Information Sheet. URL: http:
//www.valvesoftware.com, September 2006.
[Wik06] Wikipedia, Online-Enzyklopädie: Diverse Artikel. URL: http://www.
wikipedia.org, September 2006.
96