Transformationen mit OpenGL unter C++

OpenGL - Keine TransformationIm vorigen Tutorial haben wir schon eine einfache Transformation durchgeführt, bei der wir unseren Würfel rotiert und etwas verschoben haben. In diesem Tutorial werden wir uns jetzt etwas genauer damit beschäftigen und auch lernen wie man die verschiedensten Transformationen richtig verbindet. Damit wir uns das Ganze auch grafisch vorstellen können werden wir nebenbei auch ein kleines Programm schreiben, mit dem wir uns die verschiedenen Möglichkeiten auch vorstellen können.

1. Ein Programm zur Veranschaulichung

Da wir jetzt schon etwas besser mit dem Umgang von OpenGL sind, werden wir nur mehr die relevanten Codeausschnitte betrachten und nicht mehr den ganzen Quellcode, den es wieder wie gewohnt am Ende des Tutorials zum Downloaden gibt.
Der Screenshot auf der rechten Seite zeigt das Demoprogramm noch ohne der Anwendung einer Transformation. Um dieses Ausgabe zu erhalten nehmen wir einfach wieder unser bewährtes Grundgerüst für ein OpenGL Programm und setzen die Projektion mit einem Aufruf von 'glOrtho' wie folgt fest:

glOrtho( -5.3, 5.3, -4, 4, 0.5, 1.5 );

Dabei dürfen wir aber nicht vergessen zuerst den richtigen Matrizenstack zu wählen. Das wir jetzt wieder eine orthogonale Projektion verwenden liegt daran, dass wir uns alles im Zweidimensionalen viel einfacher vorstellen können als im Dreidimensionalen. Das Prinzip bleibt jedoch auch bei Transformationen mit drei Dimensionen gleich.
Jetzt brauchen wir uns nur noch die Koordinatenachsen mit zwei Linien zeichnen und dazu noch ein Quadrat hineinrendern. Der dafür zuständige Codeauschnitt schaut dann in etwa so aus:

//--------------------------------
// Ein Koordinatensystem zeichnen:
//--------------------------------

glBegin( GL_LINES );

  glColor3f(1,0,0);
    glVertex3f(8,0,-1.1);
    glVertex3f(-8,0,-1.1);

  glColor3f(0,0,1);
    glVertex3f(0,8,-1.1);
    glVertex3f(0,-8,-1.1);

glEnd();

//----------------------
// Ein Quadrat zeichnen:
//----------------------

glBegin( GL_QUADS );

  glColor3f(0.3, 0.9, 0.3);
    glVertex3f( 1.5,  0.5,  -1);
    glVertex3f(-1.5,  0.5,  -1);
  glColor3f(0.9, 0.3, 0.3);
    glVertex3f(-1.5, -0.5,  -1);
    glVertex3f( 1.5, -0.5,  -1);

glEnd();

1.1. Der richtige Matrizenstack

Bevor wir irgendeine Transformation durchführen sollten wir nicht vergessen den richtigen Matrizenstack. Der Stack 'GL_PROJECTION' ist nämlich wirklich ausschließlich für die Projektion zuständig. Sämtliche Transformationen kommen auf einen eigenen Stack (GL_MODELVIEW) den wir mit folgendem Aufruf aktivieren:
glMatrixMode( GL_MODELVIEW );

2. Die verschiedenen Transformationen

Für uns gibt es drei wichtige Arten der Transformation mit denen wir alle Objekte völlig frei im Raum platzieren und skalieren können:

2.1. Translation

OpenGL - Translation Wohl eine der wichtigste Transformation ist die Translation oder zu deutsch auch Verschiebung. Schließlich würden wir uns sehr schwer tun ein Spiel zu programmieren, wenn wir keine Möglichkeit hätten die verschiedensten Objekte auch beliebig im Raum zu verschieben. OpenGL bietet uns dafür eine Funktion die wir noch aus dem vorigen Tutorial kennen sollten und mit 'glTranslatef' benannt ist. Die bereits bekannte Signatur schaut wie folgt aus:

void glTranslatef( GLfloat x, GLfloat y, GLfloat z );

Wie wir bereits wissen können wir damit ein Objekt in die verschiedenen Richtungen im Koordinatensystem verschieben. Die Verschiebung erfolgt dabei auf jeder Achse in positive Richtung um den jeweiligen Wert. Dabei sollten wir uns nocheinmal zumindest dunkel daran erinnern können, wie das Koordinatensystem in OpenGL üblicherweise ausgerichtet ist:

  • X-Achse: wachsende Werte von links nach rechts.
  • Y-Achse: wachsende Werte von unten nach oben.
  • Z-Achse: wachsende Werte aus dem Bildschirm heraus.

In dem Bild auf der rechten Seite können wir eine einfache Translation in y-Richtung erkennen.

2.2. Rotation

OpenGL - Rotation Da wir die Objekte in unseren Spielen aber nicht nur an ihre Bestimmungsorte verschieben wollen sonder auch verdrehen wollen, beschäftigen wir uns als nächstes mit der Rotation, die eine Transformation beschreibt bei der ein Objekt um eine bestimmte Achse gedreht (=rotiert) wird. Auch dafür haben wir bereits im vorigen Tutorial die Funktion 'glRotatef' kennengelernt, die genau das tut. Zur Wiederholung schauen wir uns noch einmal die Signatur an:

void glRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z );

Mit (x,y,z) geben wir die Achse an, um die rotiert werden soll und mit angle geben wir den Winkel im Gradmaß an, um den das Objekt gedreht werden soll. Damit wir auch wissen in welche Richtung sich das Objekt drehen wird begeben wir uns im Gedanken am Besten an die Position im Koordinatensystem die wir mit (x,y,z) festgelegt haben. Von dieser Position aus denken wir uns eine Gerade in den Ursprung. Jetzt ist es eigentlich ganz einfach zu erkennen in welche Richtung das Objekt rotieren wird:

  • angle < 0: Rotation im Uhrzeigersinn.
  • angle > 0: Rotation gegen den Uhrzeigersinn.

2.3. Skalierung

Neben den beiden bis jetzt besprochenen Transformationen. Auch hier können wir wieder die einheitliche Signatur und Bennennung der Funktionen von OpenGL erkennen:

void glScalef( GLfloat x, GLfloat y, GLfloat z );

3. Kombinieren von Transformationen

Wenn wir jetzt ein bisschen mit den verschiedensten Kombinationen von Transformationen herumspielen werden wir sehr schnell herausfinden, dass die Reihenfolge in der wir die Transformationen anwenden nicht egal ist. Es steckt natürlich auch ein System dahinter mit dem wir auch leicht die richtige Reihenfolge herausfinden können. Wichtig ist dabei, dass wir im Hinterkopf behalten, dass OpenGL die Transformationen als Matrizen auf einen Stack legt. Da es nun einmal in der Natur eines Stacks liegt das man das am Schluss hinzugefügte Element als erstes bearbeitet. Deshalb wird auch die zuletzt gemachte Transformation als erste angewendet. Wer bereits mit DirectX gearbeitet hat sollte hier besonders aufpassen, da man sonst gerne genau die entgegengesetzte Reihenfolge verwendet.

Damit das ganze ein wenig klarer wird schauen wir uns jetzt einfach noch ein paar Beispiele von Kombinationen an:

3.1. Rotation vor Translation

OpenGL - Rotation vor Translation

glRotatef(50,0,0,1);
glTranslatef(3,0,0);

Wie bereites erwähnt müssen wir alle Transformationen von hinten nach vorne abarbeiten. Hier wird also zuerst eine Translation um 3 Einheiten in x-Richtung durchgeführt. Anschließend werden die neuen Eckpunkte des Rechtecks um 50 Grad um die z-Achse rotiert. In unserem Fall steht die z-Achse normal auf die Bildebenen, weshalb die Rotation leicht zu erkennen ist.

So eine Kombination werden wir verwenden um ein Objekt in einem bestimmten Radius um einen Punkt rotieren zu lassen. Wir könnten zum Beispiel in einem Modell der Planeten die Erde um die Sonne bewegen lassen.

Anders ausgedrückt können wir auch sagen dass eine Translation am Schluss den Mittelpunkt für vorangehende Transformationen verschiebt.

3.2. Translation vor Rotation

OpenGL - Translation vor Rotation

glTranslatef(3,0,0);
glRotatef(50,0,0,1);

Hier wird der Transformationsmittelpunkt nicht verschoben sondern gleich eine Rotation um den eigenen Mittelpunkt durchgeführt und das Objekt anschließend aus dem Ursprung heraus verschoben.

So eine Kombination würden wir zum Beispiel verwenden um bei unserer Planetensimulation dir Erde um die eigene Achse rotieren zu lassen und anschließend aus der Sonne hinaus auf ihren Bestimmungsort zu verschieben.

3.3. Skalierung vor Translation

OpenGL - Skalierung vor Translation

glScalef(0.5,2,1);
glTranslatef(3,0,0);

Bei dieser Kombination wir das Rechteck zuerst wieder verschoben und nachher die Koordinaten aller Vertices wieder skaliert. In x-Richtung werden somit durch den Skalierungsfaktor von 0.5 alle Koordinaten halbiert und durch den Faktor 2 in y-Richtung verdoppelt. Die z-Koordinaten bleiben durch die Wahl des Faktors mit 1 unbeinflusst.

Diese Reihenfolge werden wir zum Beispiel verwenden um nach dem positionieren aller Objekte die gesamte Welt zu skalieren, da nicht nur die Größen der Objekte sondern auch ihre Positionen skaliert werden.

3.4. Translation vor Skalierung

OpenGL - Translation vor Skalierung

glTranslatef(3,0,0);
glScalef(0.5,2,1);

Diese weitere Kombination von Translation und Skalierung benötigen wir um die Größe eines Objekts vor dem verschieben zu verändern. Dabei bleibt die Position des Objekts unverändert. Einzig und allein die Größe wird verändert.

Damit können wir zum Beispiel bestimmte Objekte in verschiedenen Größen verwenden. Das könnten wir unter anderem auch zum erstellen verschieden großer Bäume verwenden oder um bei unserem Beispiel mit dem Planetensystem zu bleiben eine Kugel, die wir für die verschiedensten Planeten entsprechend skalieren.

3.5. Skalierung vor Rotation

OpenGL - Skalierung vor Rotation

glScalef(0.5,2,1);
glRotatef(50,0,0,1);

Eher selten anzutreffen ist diese Kombination, bei der ein rotiertes Objekt nich skaliert wird. Verwenden können wir dies zum Beispiel um eine Objekt um eine andere als eine der drei vorgegebene Achsen zu skalieren, oder wie bereits früher erwähnt um nach allen durchgeführten Transformationen die gesamte virtuelle Welt noch zu skalieren.

3.6. Rotation vor Skalierung

OpenGL - Rotation vor Skalierung

glRotatef(50,0,0,1);
glScalef(0.5,2,1);

Hier haben wir wieder eine Fall den wir vielleich öfter antreffen werden, da hier zuerst die Größe des Objekts durch eine Skalierung verändern und anschließend das in der Größe veränderte Objekt noch rotieren.

In unserem Planetensystem wäre das eine auf die entsprechende Größe gebrachte Kugel, die um die eigene Achse rotiert.

Anschließend könnten wir sie auch mit einer vorne angefügten Kombination aus einer Rotation gefolgt von einer Translation an die richtige Stelle bringen.

3.7. Transformieren um alternativen Punkt oder Achse

Wie wir vielleicht schon aus den Beispielen erkennen können ist es ganz einfache eine Transformation durchzuführen und dabei einen anderen Mittelpunkt als die Objektmitte zu verwenden. Der "Trick" besteht einfach darin zuerst das Objekt so zu verschieben, dass sich der gewünschte Mittelpunkt im Ursprung des Koordinatensystems befindet. Dannach wird einfach die gewünschte Transformation durchgeführt und dann wieder durch eine weitere Translation mit den negierten Werten der ersten Translation ebendiese rückgängig gemacht. Da die Transformationen wie bereits erwähnt in umgekehrter Reihenfolge durchgeführt werden müssen wir die erste Translation am Schluss hinschreiben und uns dann nach vorne arbeiten.

3.8. Zusammenfassung

Aus den ganzen Beispielbildern wird uns eigentlich recht schnell klar, dass die Reihenfolge der Transformationen eine sehr wichtige Rolle spielt. Es ist jedoch gar nicht einmal so schwer die richtige Reihenfolge zu erkennen da man meistens in der gleichen Reihung vorgeht: Ganz am Schluss erfolgt eine mögliche Skalierung. Dannach kommt nur dann eine Translation wenn wir den Mittelpunkt der Skalierung verschieben wollen. Davor kommen die Rotationen. Auch hier müssen wir wieder eine Translation einschieben wenn wir den Drehmittelpunkt ändern wollen. Und als aller erstes kommt dann noch die Translation an den gewünschten Platz. Diese Reihenfolge, die vor allem für jemanden der schon mit DirectX zu tun gehabt hat sehr verkehrt wirkt, hat einen simplen Grund, und zwar dass durch den Matrizenstack alle Transformation in entgegengesetzter Reihenfolge ausgeführt werden. Die Transformation die wir als letztes angeordnet haben wird also als erste durchgeführt. Wir können uns das jetzt entweder so merken und immer von hinten nach vorne denken oder wir stellen uns das in der richtigen Reihenfolge vor. Nur werden dann nicht die Koordinaten transformiert sondern das lokale Koordinatensystem in dem alle folgenden Zeichenoperationen durchgeführt werden.

4. Das Demoprogramm

Das bereits erwähnte Demoprogramm mit dem ich auch die Screenshots erstellt habe gibt es weiter unten als Quellcode zum Downloaden. Die dabei verwendete Verarbeitung von Tastatureingaben werden wir erst in einem weiteren Tutorial besprechen. Der Code sollte aber trotzdem nicht allzu schwer zum Verstehen sein. Einfach gleich wie alle bisherigen Programme kompilieren und ausführen. Ein Druck auf die Taste [T] schaltet zur nächsten Transformation um.

5. Ausblick

In diesem Tutorial haben wir bei weitem kein einfaches Thema behandelt und der eine oder andere wird sich vielleicht denken, wie soll ich das jemals verstehen ;-) Aber keine Sorge ich kann aus eigener Erfahrung sagen mit ein bisschen Übung und durch herumprobieren versteht man das sehr schnell. Nach ein paar eigenen Experimenten können wir dann gleich mit dem nächsten Tutorial weiter machen um endlich auch mit dem Benutzer per Tastatur oder Maus interagieren zu können.

Nächstes Tutorial: Tastatur und Maus mit der SDL - C++ OpenGL Tutorials

6. Quellcode

Hier gibt es noch den vollständigen Quellcode des in diesem Tutorial erwähnten Demoprogramms zum Downloaden:

icon Quellcode - C++ OpenGL Tutorial 04 (5.5 kB 2009-02-03 13:45:19)

Und falls einer noch immer nicht die in den Tutorials verwendete Klasse zum Initialisieren von OpenGL hat, gibt es sie hier noch einmal zum Downloaden:

icon Klasse zum Initialisieren von OpenGL (3.42 kB 2008-07-13 00:52:46)


Wünsche, Anregungen und Kritik bitte über das Kontaktformular oder direkt an Diese E-Mail-Adresse ist gegen Spambots geschützt! Sie müssen JavaScript aktivieren, damit Sie sie sehen können. an mich schicken.

Aktualisiert ( Montag, 07. November 2011 um 00:01 Uhr )