Die Fähigkeiten der freien 3D-Engine Ogre ziehen sogar professionelle Spiele- und Echzeitgrafik-Hersteller an. Zudem kümmert sich eine aktive Gemeinde um den leistungsstarken 3D-Motor und leistet vorbildlichen Support. Grund genug also, um einen Blick unter die Haube zu werfen.
Moderne Computerspiele zaubern atemberaubende Bilder auf den Bildschirm. Die Hauptarbeit verrichtet dabei die so genannte Engine. In der Regel kümmert sie sich um eine möglichst schnelle Berechnung der Szene, abstrahiert von der zugrunde liegenden Hardware und erleichtert so den Programmierern das Leben. Die Entwicklung einer Engine erfordert viele Mannjahre Arbeit, was sich letztlich auch im Preis widerspiegelt. Das aktuelle Programmgerüst des Actionspiels Unreal Tournament ist beispielsweise ab 350000 Dollar zu haben und somit für Endkunden oder kleinere Unternehmen schlichtweg unbezahlbar – an die Entwicklung freier Spiele damit ist gar nicht zu denken.
Mittlerweile gibt es jedoch zahlreiche freie Engines unterschiedlichster Ausprägung. Eine von ihnen ist Ogre (Object-Oriented Graphics Rendering Engine), die Dank LGPL-Lizenz sogar kommerzielle Projekte verwenden dürfen [1]. Gegenüber anderen freien Engines punktet Ogre unter anderem mit der Unterstützung von Shader-Programmen, mit einem Partikelsystem und einem modularen Aufbau über Plugins.
Obwohl Ogre hauptsächlich Spiele antreibt, ist die Engine recht universell ausgelegt. Sie ist daher auch für andere Projekte im 3D-Echtzeitbereich bestens geeignet. Das derzeit wohl bekannteste Anwendungsbeispiel ist das kommerzielle Adventurespiel Ankh [2].
Spezialgebiet: Grafik
Ogre versteht sich vorwiegend als Grafik-Engine. Andere Aufgaben, zum Beispiel die Ausgabe von Tönen, müssen zusätzliche Softwarekomponenten übernehmen. Einen Überblick über die Architektur gibt Abbildung 1. Zum Ausgleich läuft die 3D-Engine plattformübergreifend unter Windows, Mac OS X und Linux. Tipps zur Übersetzung der Engine gibt der Kasten “Installation”.
Beim Entwurf von Ogre achteten die Entwickler nicht nur auf konsequente Objektorientierung, sondern auch auf möglichst einfache Benutzbarkeit. Wie das in diesem Artikel entwickelte Beispiel zeigt, genügen bereits wenige Zeilen Quellcode, um einen Roboter auf den Schirm zu zaubern. Diese Schlichtheit erleichtert nicht nur den Einstieg, das System qualifiziert sich damit auch für Prototyping-Aufgaben.
Leider ist Ogre etwas wählerisch bei den Programmiersprachen. So wurde die 3D-Engine vollständig in und für C++ geschrieben. Weitere Bindungen existieren derzeit nur für Python und Lua. Eine Umsetzung für Java steckt noch in den Kinderschuhen [3].
Einblick in die Arbeitsweisen von Ogre erhält man am besten anhand eines kleinen Beispiels. Ziel soll im Folgenden die Darstellung des bereits angesprochenen Roboters sein. Voraussetzung sind C++-Kenntnisse sowie grundlegendes Wissen über 3D-Grafiken. Hilfreich, aber nicht zwingend ist die Beherrschung von Entwurfsmustern (Design Patterns). Ogre setzt häufig Singletons (es gibt in der Anwendung nur ein Objekt einer Klasse) und Listener (ein Objekt wird von einem anderen benachrichtigt) ein.
Ausgangspunkt für die Ogre-Programmierung ist Listing 1. Die Datei »beispiel.cpp« lässt sich am einfachsten per
gcc -o beispiel `pkg-config --cflags OGRE` `pkg-config --libs OGRE` beispiel.cpp
übersetzen. Damit entfällt die Sorge um Flags und einzubindende Pfade. Viel kann das Programm jetzt allerdings noch nicht. Der leere »try«-Block nimmt später die eigentliche Funktionalität auf. Wenn dort etwas schief läuft, gibt Ogre eine Ausnahme-Warnung aus. Der anschließende »catch«-Block fängt sie ein und bemüht zunächst den »PlatformManager«. Dieser kapselt alle plattformabhängigen Dienste, beispielsweise das in diesem Fall angeforderte Dialogfenster für Fehlermeldungen. Anschließend visualisiert »display()« die mit der Ausnahme übermittelte Fehlermeldung. Unter Linux entspricht dies in der Regel einer Textausgabe auf der Kommandozeile.

Abbildung 1: Unter Linux benutzt Ogre die OpenGL-Bibliothek: Eine Anwendung ruft eine Ogre-Funktion auf, die das gewünschte Verhalten in OpenGL-Anweisungen umsetzt. Diese werden an die gleichnamige Bibliothek weitergeleitet, die wiederum mit der Grafikkarte kommuniziert. Den Sound übernimmt SDL.
Um keine Verwirrung zu stiften, stecken übrigens alle Ogre-Klassen im Namespace »Ogre«. Da es in diesem Beispiel keine Doppeldeutigkeiten gibt, befreit die dritte Zeile von der ansonsten geltenden Präfixpflicht.
Die erste Aufgabe auf dem Weg zu einer eigenen Ogre-Anwendung besteht darin, ein Root-Objekt anzulegen:
Root* myroot = new Root("plugins.cfg", "ogre.cfg","ogre.log");
Dieses Root-Objekt ist das Kernobjekt in Ogre und somit der Einstiegspunkt für alle weiteren Aktivitäten. Von ihm darf immer nur ein Objekt pro Anwendung existieren, das man zudem immer erst am Ende des Programms zerstören sollte.

Abbildung 2: Das grundlegende Objekt der Ogre-Engine ist Root. Es kontrolliert den Szenen-Manager und das Render Window.
Das Root-Objekt erwartet bei seiner Erzeugung die Übergabe von drei Dateinamen. In »plugins.cfg« sind alle Pfade zu den Plugins abgelegt, die die künftige Anwendung benötigt. Standardmäßig sieht sie aus wie in Listing 2. Dank dieses Konzepts lädt Ogre keine unnötigen oder unpassenden Funktionen. Die nächste Datei »ogre.cfg« enthält alle übrigen Konfigurationsdaten, beispielsweise die zu verwendende Fenstergröße. Sämtliche Texte, die Ogre auf der Konsole ausgibt, finden zusätzlich ihren Weg in eine Logdatei. Ihren Namen gibt der dritte Parameter an. Nur die Datei »plugins.cfg« muss beim Programmstart bereits existieren.
Fensterln
Als Nächstes ist ein Fenster einzurichten, das den Roboter anzeigt. Das erledigt etwa der folgende Zweizeiler:
if(!myroot->restoreConfig()) if (!myroot->showConfigDialog()) return 1; RenderWindow* mywindow = myroot->initialise(true,"LinuxMagazin Beispiel");
Er versucht zunächst eine bestehende Konfiguration aus der Datei »ogre.cfg« zu lesen. Falls die nicht existiert, zeigt das Programm einen Konfigurationsdialog an. Alle dort vorgenommenen Einstellungen wandern in eine neue »ogre.cfg«, die schließlich die alte ersetzt. Auf diese Weise merkt sich die Anwendung ihre letzten Einstellungen.
Die zweite Zeile erzeugt ein neues Fenster, das sofort angezeigt wird (»true«), den Titel »LinuxMagazin Beispiel« trägt und auf den zuvor eingelesenen Konfigurationsdaten basiert. Das Ausgabefenster sollte immer zu einem möglichst frühen Zeitpunkt entstehen, da Ogre beziehungsweise die darunter liegende SDL sonst leicht einmal abstürzt.
Haut und Knochen
Ein leeres Fenster ist natürlich unansehnlich. Also muss endlich der Roboter her. Er lässt sich zwar mit den entsprechenden Ogre-Funktionen aus einfachen geometrischen Elementen zusammensetzen. Dieses Vorgehen ist jedoch recht mühsam und daher nur für dynamische, also sich ständig ändernde Objekte zu empfehlen. Bequemer ist der Weg über ein 3D-Programm wie Blender. In ihm entwirft der Anwender komfortabel einen Roboter, den er anschließend in das Ogre-eigene Format exportiert. Auf der Ogre-Homepage steht zu diesem Zweck eine Reihe von Plugins für die gängigsten 3D-Programme bereit.
Für dieses Beispiel ist jedoch noch nicht einmal Blender erforderlich, Ogre hat vorgesorgt. Es genügt bereits ein Blick in das Unterverzeichnis »ogrenew/Samples/Media«. Dort tummeln sich einige fertige Beispielobjekte im passenden Format. Das Skelett (Mesh) des Roboters liegt unter »models«, seine Haut als Textur im Verzeichnis »material/textures«. Skripte in »material/scripts« und »material/program« steuern, in welcher Weise Ogre die dort befindlichen Bilder auf das Skelett klebt. Normalerweise erzeugen die erwähnten Export-Plugins alle genannten Dateien.
|
Listing 1: |
|---|
01 #include <Ogre.h>
02 #include <OgreErrorDialog.h>
03
04 using namespace Ogre;
05
06 int main()
07 {
08 try
09 {
10 }
11 catch (Exception& e)
12 {
13 ErrorDialog *dlg = PlatformManager::getSingleton().createErrorDialog();
14 dlg->display(e.getFullDescription());
15 delete dlg;
16 return 1;
17 }
18 return 0;
19 }
|
|
Listing 2: |
|---|
01 #Pfad zu den Plugins 02 PluginFolder=/usr/local/lib/OGRE 03 #Plugins 04 Plugin=RenderSystem_GL.so 05 Plugin=Plugin_ParticleFX.so 06 Plugin=Plugin_BSPSceneManager.so 07 Plugin=Plugin_OctreeSceneManager.so 08 Plugin=Plugin_CgProgramManager.so |
|
Installation |
|---|
|
Ogre unter Linux zu installieren ist nicht ganz so einfach, wie es die Homepage behauptet. Voraussetzungen für den Betrieb sind zunächst die installierten Bibliotheken Zziplib ab Version 0.12 und DevIl ab Version 1.6.5 sowie das Cg-Toolkit von der Nvidia-Website. Wegen einiger Bugs darf DevIl nicht in Version 1.6.6 vorliegen. Das SDK von Nvidia gibt es nur in einer bereits fertigen Variante, der zudem ein Installationsskript fehlt. Es genügt aber, das Archiv auf der obersten Ebene des Verzeichnisbaums zu entpacken. Die Ogre-Autoren empfehlen die Version 1.1, die auf den Nvidia-Seiten etwas versteckt zu haben ist. Um Ogre zu kompilieren, ist zudem folgende Software erforderlich: Automake 1.9.5, Autoconf 2.59a, Make 3.80, Libtool 1.5.6, Pkg-config 0.17.2, GCC 3.3.5, CPP 3.3.5 und Freetype2 (2.1.x). Insbesondere unter Suse ist Slang-devel nicht zu vergessen. Bei GCC-Compilern vor Version 3 ist zusätzlich noch die Stlport-Klassenbibliothek notwendig. Plattform: OpenGLMit den genannten Versionen verlief der Ogre-Test erfolgreich, daher sollten am besten sie oder neuere vorliegen. Außerdem ist die SDL ab Version 1.2 erforderlich. Zwar lässt sich Ogre auch in einem Modus ohne diese Grafikbibliothek betreiben, dann fehlen allerdings ihre Funktionen für die Steuerung von Eingabegeräten oder einer Soundausgabe (»configure« mit Parameter »–with-gl-support=GLX –with-platform=GLX«). Optional sind die Pakete Glademm2 ab Version 1.3.4 und Gtkmm2 ab Version 1.3.26. Nach ihrer Installation zeigen die Beispielanwendungen einen GTK-basierten Konfigurationsdialog an. Sind alle Vorbereitungen abgeschlossen, übersetzt man Ogre im »ogrenew«-Verzeichnis mit den Befehlen: ./bootstrap ./configure make make install Wer vor allem das »bootstrap«-Skript vergisst, riskiert eine nicht funktionierende Installation. |
Ressourcen auch gepackt
In die eigene Ogre-Anwendung gelangen Modelle und Texturen über den so genannten Ressourcen-Manager. Wie sein Name schon andeutet, verwaltet er sämtliche Daten aus externen Quellen auf effiziente und Speicherplatz sparende Art. Zunächst teilt der Programmierer ihm mit, wo die Ressourcen liegen. Am einfachsten ist es, gleich alle Dateien in einem Verzeichnis anzumelden, wie in Listing 3 zu sehen ist.
Der zweite Parameter von »addResourceLocation()« gibt den Typ der Ressource an. Im Beispiel geht es um ein Verzeichnis (»FileSystem«). Eine interessante Alternative hierzu ist »ZIP«. Insbesondere bei der Auslieferung von fertigen Anwendungen ist oftmals ein komprimiertes Archiv wünschenswert, das sämtliche Ressourcen enthält.
|
Listing 3: Ressourcen |
|---|
01 ResourceGroupManager::getSingleton().addResourceLocation("Pfad zu
Ogre/ogrenew/Samples/Media/materials/programs", "FileSystem");
02 ResourceGroupManager::getSingleton().addResourceLocation("Pfad zu
Ogre/ogrenew/Samples/Media/materials/scripts", "FileSystem");
03 ResourceGroupManager::getSingleton().addResourceLocation("Pfad zu
Ogre/ogrenew/Samples/Media/materials/textures", "FileSystem");
04 ResourceGroupManager::getSingleton().addResourceLocation("Pfad zu
Ogre/ogrenew/Samples/Media/models", "FileSystem");
|
Ogre trägt diesem Wunsch durch die direkte Unterstützung von Zip-Archiven Rechnung. In diesem Fall erwartet »addResourceLocation« als ersten Parameter den Namen einer Datei, die alle oder einen Teil der Ressourcen enthält. In dem Unterverzeichnis »Media/Packs« liegen hierzu einige Beispielpakete. Abschließend sind die Ressourcen nur noch zu initialisieren:
ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
Wie unschwer am Namen der Methode zu erkennen ist, bietet auch der Ressource-Manager ein Singleton-Interface (siehe oben).
Mehr nach links, bitte
Um nun den Roboter auf den Schirm zu bringen, kommt der Szenenmanager ins Spiel. Bildlich gesprochen repräsentiert er ein leeres Fernsehstudio, in dem sich die einzelnen 3D-Objekte zu einer hübschen Szene anordnen lassen. In diesem Beispiel geht es zwar nur um einen einzigen Roboter, aber auch der möchte irgendwo im Raum platziert sein. Zunächst ist der Szenenmanager beim Root-Objekt anzufordern:
SceneManager* myscenemanager = myroot->getSceneManager(ST_GENERIC);
Ebenso wie wohl die meisten TV-Sender mehrere Studios besitzen, dürfen auch in Ogre mehrere Szenenmanager gleichzeitig existieren. Außerdem gibt es sie auch in unterschiedlichen Ausprägungen. So sind einige Szenenmanager auf die Arbeit im Außengelände spezialisiert, während andere besonders schnell mit Innenräumen hantieren. Im Beispiel kommt der recht universelle Standard-Szenenmanager zum Einsatz.
Zusammenbauen
Um den Roboter richtig in Szene zu setzen, sind zwei Schritte notwendig. Zunächst muss der Regisseur aus allen Einzelressourcen ein konkretes Exemplar des Roboters konstruieren, die so genannte Entität (Entity):
Entity *robent = myscenemanager->createEntity("Roboter1", "robot.mesh");
Hier erzeugt der Szenenmanager aus dem Skelet »robot.mesh« eine Entität mit dem Namen »Roboter1«. Dabei zieht er automatisch den Ressourcen-Manager zu Rate. Über den frei wählbaren Namen »Roboter1« ist die Entität bei späteren Operationen leichter zu identifizieren. Im zweiten Schritt bekommt der Roboter eine Position innerhalb der Szene:
SceneNode* robnode = myscenemanager->getRootSceneNode()->createChildSceneNode("Roboter1Node", Vector3(0,0,0));
Diese Anweisung strotzt zwar nur so vor Knoten (Nodes), das hat aber einen guten Grund: Ogre kann einem 3D-Objekt nicht direkt eine Position zuweisen. Stattdessen erzeugt man einen Ankerpunkt innerhalb der Szene, an den dann das Objekt gebunden wird. Wer den Roboter später verschieben möchte, muss folglich seinem Knoten andere Koordinaten zuweisen. In diesem Fall heißt der Knoten »Roboter1Node« und steht an Position (0,0,0).

Abbildung 3: Mit nur wenigen Zeilen lädt Ogre ein fertiges Robotermodell und rendert es vor schwarzem Hintergrund.
Die »Vector3«-Klasse kapselt einen Punkt im Raum mit der Form (x,y,z). In Ogre ist das Koordinatensystem so angelegt, dass die x-Achse von links nach rechts, die y-Achse von unten nach oben und die z-Achse aus dem Bildschirm heraus (die negativen Koordinaten entsprechend in den Bildschirm hinein) laufen.
Hierarchische Modelle
Bleibt nur noch zu klären, was »getRootSceneNode()« in der Szene-Anweisung bewirkt. Wer bereits eine 3D-Modellierungssoftware oder ein CAD-Programm benutzt hat, kennt das Konzept: Ogre ordnet alle Scene Nodes in einer Hierarchie an. Verschiebt sich ein Knoten, wandern automatisch alle ihm untergeordneten Knoten mit – und die damit verbundenen Entitäten. Ordnet der Programmierer seine 3D-Objekte clever in dieser Hierarchie an, genügt ein Befehl, um eine Armada von Robotern um 100 Einheiten nach links zu verschieben. »getRootSceneNode()« liefert also den virtuellen Wurzelknoten einer Szene.
Da in der Szene nun ein Knoten existiert, kann der Programmierer anschließend die vorher erzeugte Entität »Roboter1« daran binden:
robnode->attachObject(robent);
Jetzt steht der einfachen Animation des Robotermodells nichts mehr im Weg.
Bitte lächeln
Der Roboter existiert, erscheint aber immer noch nicht auf dem Bildschirm. Es fehlt noch eine virtuelle Kamera, die das bunte Treiben auf der Studiobühne aufzeichnet:
Camera* mycamera = myscenemanager->createCamera("MeineKamera1");
Wie im Fernsehstudio darf es selbstverständlich mehrere Kameras geben, die aus verschiedenen Blickwinkeln filmen. Das erspart das umständliche Wechseln der Kameraposition. Das folgende Beispiel stellt »mycamera« an der Position (150,150,100) auf und richtet sie auf das Geschehen an Position (0,0,0):
mycamera->setPosition(Vector3(150,150,100)); mycamera->lookAt(Vector3(0,0,0));
Die Kamera nimmt jetzt eine Szene auf, aber wohin soll sie ihre Bilder projizieren? Leider kann der Programmierer sie nicht direkt an ein Fenster binden. Stattdessen muss er den Weg über ein so genanntes Viewport, auf Deutsch etwa Sichtfeld, gehen.
Bildausschnitt
Ein Viewport repräsentiert genau jenen Bereich, der später im Fenster erscheint. Das Beispiel erstellt zunächst ein Viewport für das Fenster, weist Schwarz als Hintergrundfarbe zu und passt schließlich noch das Seitenverhältnis (Aspect Ratio) der Kamera an das gewählte Sichtfeld an:
Viewport* myviewport = mywindow-> addViewport(mycamera); myviewport->setBackgroundColour(ColourValue (0.0f,0.0f,0.0f)); mCamera->setAspectRatio(Real(mViewport-> getActualWidth()) / Real(mViewport-> getActualHeight()));
Damit nimmt die Kamera ein Bild mit den Abmessungen des Viewport auf. Abschließend ist auf der Bühne nur noch das Licht anzuknipsen, in diesem Fall weißes Umgebungslicht:
myscenemanager->setAmbientLight(ColourValue(1,1,1));
Der Startschuss für die Hauptschleife fällt dann mit der Anweisung:
myroot->startRendering(); delete myroot; // Aufräumen nicht vergessen
Das Ergebnis des Beispiels sollte so aussehen wie in Abbildung 3.
Wird das Programm nun kompiliert und ausgeführt, erscheint der Roboter. Allerdings für immer oder bis zum nächsten »kill«-Befehl, denn Ogre beziehungsweise die darunter liegende SDL fangen gemeinerweise alle Tastatureingaben ab. Um diesen Missstand zu beheben, bietet sich eine Funktion des Root-Objekts an. Bei ihm darf sich eine vom Frame Listener abgeleitete Klasse registrieren. Kurz bevor ein neues Bild gezeichnet wird, ruft das Root-Objekt immer die Methode »frameStarted()« auf. In ihr lässt sich prüfen, ob eine Taste gedrückt ist. Liefert »frameStarted()« den Wert »false«, zieht Root kontrolliert die Notbremse (Listing 4).
|
Listing 4: |
|---|
01 class MyFrameListener : public Ogre::FrameListener
02 {
03 public:
04 MyFrameListener(Ogre::InputReader* ir);
05 virtual bool frameStarted(const Ogre::FrameEvent& evt);
06 private:
07 Ogre::InputReader* mInputReader;
08 };
09
10 MyFrameListener::MyFrameListener(InputReader* ir) : mInputReader(ir) {}
11
12 bool MyFrameListener::frameStarted(const FrameEvent& evt)
13 {
14 mInputReader->capture();
15 if (mInputReader->isKeyDown(KC_ESCAPE)) return false;
16 return true;
17 }
|
Den Input Reader bekommt der Frame Listener bei seinem Aufruf übergeben. Per »mInputReader->capture();« schießt er eine Momentaufnahme von der Tastatur (Zeile 14). Anschließend ist nur noch zu prüfen, ob [Esc] gedrückt war. Im Hauptprogramm ist der Input Reader über den »PlatformManager« erreichbar:
InputReader* myir = PlatformManager:: getSingleton().createInputReader(); myir->initialise(mywindow,true,false);
In der Schreibweise von »initialise« zeigt sich übrigens auch die durchgehende Verwendung des britischen Englisch für Variablen- und Funktionsnamen im Ogre-API.
Abschließend ist ein Objekt vom Typ »MyFrameListener« zu erzeugen und bei Root zu registrieren:
MyFrameListener* myfl = new MyFrameListener(myir); myroot->addFrameListener(myfl);
Diese oberen vier Zeilen müssen in der Hauptschleife vor »myroot->startRendering()« stehen. Die hier gezeigte Methode der Tastaturabfrage bezeichnet Ogre als ungepuffert (unbuffered). Bei der gepufferten Variante benachrichtigt Root den registrierten »FrameListener«, sobald der Benutzer eine Taste drückt:
class Keyboard : public Ogre::KeyListener, public Ogre::FrameListener
{
public: virtual void keyPressed(Ogre::KeyEvent *e);
};
void Keyboard::keyPressed(KeyEvent *e) { /* Tastendruck verarbeiten*/ }
Fazit
Diese Einführung konnte nur an der Oberfläche aller Möglichkeiten der Ogre-Engine kratzen. Eine Screenshot-Galerie auf der Homepage zeigt, welche erstklassigen Bilder der Anwender der freien Software entlocken kann (siehe Abbildungen 4 und 5). Bei aller Euphorie sollte jedoch klar sein, dass Ogre nicht in der obersten Liga der kommerziellen 3D-Engines mitspielt – zumindest noch nicht. Die eingangs erwähnte Unreal Engine kann das freie Pendant bislang nicht ersetzen
Weiterführende Hilfe bietet die rege und aktive Community. Im Wiki und in den Foren ist auf fast jede Frage eine Antwort zu finden. Besonders die sehr gut gemachten Tutorials sollten Interessierte einmal durchgearbeitet haben. (ofr)
|
Infos |
|---|
|
[1] Ogre: [http://www.ogre3d.org] [2] Deck 13, Hersteller von Ankh: [http://www.deck13.com] [3] Ogre4J: [http://ogre4j.org] |








