Tutorial Matrix2
Inhaltsverzeichnis
Matrizen und Matrixmanipulationen
Vorwort
Willkommen bei meinem zweiten Tutorial.
In letzter Zeit traten im Forum verstärkt fragen wie "Wie positioniere ich Objekte richtig?" und "Ich komm mit den Matrizen net klar. Könnt ihr helfen?" auf. Dies war Anlass für mich ein weiteres Tutorial zum Thema Matrizen zu schreiben. Ich werde versuchen viel mit Bildern und Gleichnissen zu arbeiten. Wer aber das ganze Angebot zum Thema "Matrix" nutzen will sollte ruhig auch einen Blick auf Phobeus Matrixtutorial (Tutorial_lektion3) und auf den Wikiartikel zum Thema Matrix werfen.
Matrizen in OpenGL
OpenGL kennt drei Matrizen. Wer OpenGL programmieren will sollte diese auch kennen:
- GL_MODELVIEW - Die Modelviewmatrix
- Die Modelviewmatrix sollte immer dann aktiviert sein, wenn ihr an die Grafikkarte Vertexdaten senden wollt. Diese Matrix wird auf die Position der Vertices, die mit glVertex gesendet werden angewandt. Ebenso beeinflusst sie die Ausrichtung der Normalen, die mit Hilfe von glNormal gesetzt werden. Kurz, alles was mit den Objekten eurer Welt zu tun hat, erfordert die Aktivierung dieser Matrix.
- GL_PROJECTION - Die Projektionsmatrix
- Diese Matrix wird selten gebraucht. Sie bestimmt, wie am Ende alles angezeigt wird. Sie bestimmt quasi die Eigenschaften der "Kamera".
- GL_TEXTURE - Die Texturmatrix
- Diese Matrix wird auf die Texturkoordinaten angewandt, mit der Folge, dass sich die Texturen auf den Oberflächen bewegen.
Diese Matrizen können per glMatrixMode aktiviert werden. Dabei kann immer nur eine Matrix aktiv sein. Wenn ihr z.B. glMatrixMode(GL_MODELVIEW) aufgerufen habt, dann sagt man "Die Modelviewmatrix ist die aktuelle Matrix". Alle Matrixmanipulationen (wir kommen gleich dazu was das ist) wirken sich nur auf die aktuelle Matrix aus.
Kurze Matrixtheorie
Was sind Matrizen?
Matrizen sind aufgebaut wie Tabellen. Das tolle daran ist - man kann damit rechnen.
"Gaaaaanz toll! Jetzt rechnen die schon mit Tabellen..."
So schlimm ist das gar nicht. Denn das Rechnen übernimmt OpenGL für uns. Ihr müsst nur wissen, dass wann immer ihr einen Befehl wie glTranslate, glRotate oder glScale verwendet, ihr eine Matrix mit der aktuellen Matrix multipliziert.
Wenn ihr wissen wollt, wie man Matrizen miteinander multipliziert, dann lest euch einmal den Artikel "Matrix" durch und auch das dort verlinkte PDF "CompGeoScript". |
Wie sehen Matrizen aus?
In "echt" sehen Matrizen wie Tabellen voller Zahlen aus.
Will man sich eine Matrix grafisch vorstellen, sollte man sich ein 3 dimensionales Gitter vorstellen. Dieses Gitter ist das Koordinatensystem der Matrix. Wenn ihr mit glTranslate, glRotate oder glScale die Matrix manipuliert, dann verändert ihr die Position des Koordinatenursprungs (glTranslate), die Ausrichtung der Achsen (glRotate) oder die Maße (glScale). Im dritten Teil des Tutorials gehe ich auf die Auswirkungen dieser Befehle noch einmal genauer ein.
Die drei OpenGL Matrizen
GL_PROJECTION
Ich möchte zuerst einmal die Projektionsmatrix vorstellen. Diese Matrix wird meist nur am Rande erwähnt, macht aber einen verdammt wichtigen Job: Sie projeziert den Inhalt der Welt auf den Bildschirm.
Wenn ihr der Projektionsmatrix per glLoadMatrix eine Matrix zuweist, die nur Nullen enthält, werdet ihr feststellen, dass ihr nichts seht. Ihr habt damit nämlich die Projektionsmatrix ausgeschaltet und da wir ja was sehen wollen, unterlassen wir das in Zukunft lieber ;).
Aber was kann man dann mit der Projektionsmatrix machen (,außer sie nicht auszuschalten ;-) )?
Bei der Vorstellung der einzelnen Matrizen erwähnte ich bereits die "Kamera". Um gleich mal eins festzustellen: Ein Objekt "Kamera" existiert nicht. Das einzige, was man von der "Kamera" mitbekommt, ist die Ausgabe. Und wie wir gerade gelernt haben ist welcher Teil für die Ausgabe zuständig? Richtig! Die Projektionsmatrix. Sie ist unsere "Kamera".
Kommt jetzt aber bitte nicht auf die Idee, eure Projektionsmatrix per glTranslate und glRotate irgendwohin zu bewegen... Das ist nämlich gar nicht die Aufgabe der Projektionsmatrix. Ihre Aufgabe ist "das Abbild für die Ausgabe zu manipulieren".
Im Ausgangszustand (glLoadIdentity wurde für die Projektionsmatrix aufgerufen), gibt die Projektionsmatrix das Bild der Welt unverändert aus. Wenn man glTranslate auf die Projektionsmatrix anwendet, dann verschiebt man nicht die Welt, sondern sagt der Projektionsmatrix, dass sie das gesehene Bild verschieben soll.
Befehle wie gluPerspective und glOrtho sind typische Befehle zum einstellen der Projektionsmatrix. Diese Befehle bestimmen nämlich in welcher Form die Ausgabe gemacht wird. Entweder perspektivisch verzerrt (gluPerspective) oder durch Parallelprojektion (glOrtho).
Wenn man die Projektionsmatrix manipuliert, ist das vergleichbar mit dem Austauschen des Objektivs bei einer Kamera. Man könnt sogar über die Projektionsmatrix einen "Fischauge" Effekt erzeugen... (Aber fragt mich bitte nicht, wie die Matrix dazu aussieht... ;-) Falls ihrs wisst, einfach im Forum posten. Ich wäre interessiert.)
GL_MODELVIEW
Wie bereits erwähnt ist die Modelviewmatrix die wichtigste Matrix in der OpenGL Welt, denn sie wirkt sich auf Ausrichtung und Position aller Primitive und damit Objekte in der OpenGL Welt aus. Ohne Daten in der Modelviewmatrix, kann die Projektionsmatrix auch nichts ausgeben.
Wenn ihr Vertexdaten an die Grafikkarte übermittelt solltet ihr die Modelviewmatrix aktiviert haben. Positionsangaben wie z.B. glVertex3f(1,0,1); beziehen sich auf das aktuelle Koordinatensystem. Das Koordinatensystem ist quasi der Teil der Matrix mit dem ihr arbeitet, und den ihr euch vorstellen könnt (/müßt.).
Wie ihr bereits wisst kann man mit den Befehlen glTranslate, glRotate und glScale das Koordinatensystem manipulieren. Dadurch seid ihr in der Lage beim "Bauen" von Objekten mit "lokalen Koordinaten" zu arbeiten.
lokale und globale Koordinaten
Worin unterscheiden sich lokale und globale Koordinaten?
Um diese Frage zu klären bedarf es eines kleinen Beispiels:
Stellt euch vor, ihr wollt zwei Würfel zeichnen. Der eine soll um den Punkt (3,4,5) mit einer Kantenlänge von 1 erstellt werden, der andere soll um den Punkt (1,2,3) mit Kantenlänge 2 und dazu 30° um die Y-Achse gedreht werden.
Wenn man mit globalen Koordinaten arbeitet lässt man die Matrix so wie sie ist (verzichtet also auf die 3 genannten Befehle) und schreibt die Koordinaten der Eckpunkte per Hand hin:
glBegin(GL_QUADS);
//Vorderseite erster Würfel
glVertex3f(2,5,6);
glVertex3f(2,3,6);
glVertex3f(4,3,6);
glVertex3f(4,5,6);
//Nächste Seite
[...]
//Vorderseite zweiter Würfel
glVertex3f(0,3,4);
glVertex3f(0,1,4);
glVertex3f(2,1,4);
glVertex3f(2,3,4);
[...]
glEnd;
Bei lokalen Koordinaten bewegt man zuerst den Koordinatenursprung an die Stelle an die der Würfel entstehen soll, richtet dann wenn nötig die Achsen entsprechend aus, und kann dann lokale Koordinaten übergeben. Lokal bedeutet hier relativ zum aktuellen Koordinatensystem.
Aber was soll das für einen Vorteil haben?
Dem einen oder anderen ist vielleicht aufgefallen, dass wir zweimal das selbe Objekt zeichnen sollen. Der einzige Unterschied besteht in der Position, Ausrichtung und Größe/Skalierung. Um Arbeit zu sparen, erstellt man deshalb ein Unterprogramm welches einen Einheitswürfel (Kantenlänge 1) rendert und ruft vor dem Zeichnen einfach die passende Kombination aus glTranslate, glRotate und glScale auf:
procedure ZeichneWuerfel;
begin
glBegin(GL_QUADS);
glVertex3f(-0.5, 0.5, 0.5);
glVertex3f(-0.5,-0.5, 0.5);
glVertex3f( 0.5,-0.5, 0.5);
glVertex3f( 0.5, 0.5, 0.5);
[...]
glEnd;
end;
begin
glPushMatrix;
glTranslatef(3,4,5);
ZeichneWuerfel;
glPopMatrix;
glPushMatrix;
glTranslatef(1,2,3);
glRotatef(30,0,1,0);
glScalef(2,2,2);
ZeichneWuerfel;
glPopMatrix;
end;
Wie ihr seht, haben lokale Koordinaten den Vorteil, dass man wiederverwendbaren Code schreiben kann. Bei globalen Koordinaten muss für jedes Objekt eigener Code geschrieben werden (und wenn sich das Objekt bewegt dann wieder...).
Dreimal dürft ihr raten, welche Methode häufiger verwendet wird. ;-)
GL_TEXTURE
Dieser Matrix sollte endlich mehr Aufmerksamkeit geschenkt werden. Sie kann nämlich mit den Texturen in der Szene einiges anfangen...
Aber der Reihe nach:
Wie ihr vielleicht schon im Einsteigertutorial 4 - Texturen gelesen habt, sind Texturkoordinaten immer so, dass glTexCoord2f(0,0) die obere linke Ecke und glTexCoord2f(1,1) die untere Rechte Ecke der Textur betrifft. Wie die Textur außerhalb dieses Bereichs aussieht bestimmt ihr mittels glTexParameter und dem Parametern GL_TEXTURE_WRAP_S und GL_TEXTURE_WRAP_T.
Nun stellt euch einmal vor, ihr habt 2 Quads die beide die selbe Textur bekommen sollen. Einziger Unterschied ist, dass bei dem einen Quad der Inhalt der Textur um 20° in Uhrzeigerrichtung gedreht ist. Blöd, oder? Das heißt ja jetzt, dass ihr in eurem Zeichenprogramm eine weitere Textur erstellen müßt, um das zweite Quad zu texturieren oder die Texturkoordinaten per Hand ändern müsst.
STOP! Ihr hab es sicherlich schon mitbekommen: Dieses Szenario kann leicht mit der Texturmatrix gelößt werden. Und zwar so...
procedure drawTexturedQuad;
begin
glBegin(GL_QUADS);
glTexCoord(0,0); glVertex3f(-1, 1,0);
glTexCoord(0,1); glVertex3f(-1,-1,0);
glTexCoord(1,1); glVertex3f( 1,-1,0);
glTexCoord(1,0); glVertex3f( 1, 1,0);
glEnd;
end;
begin
glMatrixMode(GL_MODELVIEW);
glBindTexture(GL_TEXTURE_2D, texID);
glTranslatef(-2,0,0);
drawTexturedQuad;
glMatrixMode(GL_TEXTURE);
glRotatef(20,0,0,1);
glMatrixMode(GL_MODELVIEW);
glTranslatef(4,0,0);
drawTexturedQuad;
end;
Was ist hier geschehn?
Durch die vorherige Aktivierung der Texturmatrix wirkt sich das nachfolgende glRotate nicht auf die Modelle der Szene sondern auf die Texturkoordinaten aus. Auf gut deutsch: OpenGL dreht vor dem rendern des zweiten Quads die Textur um 20°.
Wenn ihr beim rotieren der Texturmatrix, bei jedem Renderdurchlauf den Winkel um ein Stück erhöht passiert was?... Richtig! Eure Textur dreht sich auf eurem Quad... Das is dann quasi eure erste Animation... Und das ohne ein Objekt zu bewegen... Cool, oder?
Auswirkungen von Matrixmanipulationen - oder "Wie positioniere ich meine Objekte richtig?"
In diesem Teil des Tutorials möchte ich euch zeigen wie die 3 Befehle glTranslate, glRotate und glScale eure aktuelle Matrix verändern. Ich bleibe hierbei im 2 dimensionalen Raum, da sonst die Skizzen zu unübersichtlich werden. (Die Befehle sind dabei natürlich die selben wie für 3D Manipulationen. Ich ignoriere nur einfach immer die Y-Angaben.)
Zuersteinmal zum warm werden:
glTranslate
glTranslate(a,b,c) verschiebt den Koordinatenursprung des aktuellen Koordinatensystems um a Einheiten in die Richtung der X-Achse, um b Einheiten in die Richtung der Y-Achse und um c Einheiten in die Richtung der Z-Achse.
glRotate
glRotate(w,x,y,z) rotiert das Koordinatensystem um w Grad um die Rotationsachse welche durch (0,0,0) und (x,y,z) verläuft. glRotate richtet also die Achsen neu aus. (Die Richtung der Achsen ist z.B. wichtig für glTranslate)
glScale
glScale(x,y,z) skaliert die Maße der Koordinatenachsen (Bestimmt also wie lange "eine Einheit" wirklich ist). Wenn x,y oder z größer als 1 ist, werden die Maßeinheiten der entsprechenden Achse gestreckt, wenn die Werte kleiner 1 sind gestaucht. (Wenn die Werte gleich 0 sind verschwindet die Szene auf nimmer wieder sehn(, da hilft dann nur noch glLoadIdentity um die Matrix wieder nutzbar zu machen), sind die Werte gleich 1 verändert sich nichts.)
x,y und z muss man sich als Prozentangaben vorstellen. Will man die Szene in X-Richtung um das doppelte (also 200%) vergrößern/strecken muss x gleich 2 sein. Will man die Y-Richtung auf ein Zehntel (also 10%) verkürzen/stauchen muss y gleich 0.1 sein.
Kombination - Translate vor Rotate
Wird Translate vor Rotate aufgerufen, bewegt sich der Koordinatenursprung zuerst an den Zielpunkt (über glTranslate angegeben). Anschließend werden die Achsen neu ausgerichtet (durch glRotate).
Folgender Code wird demonstriert:
glTranslatef(3.5,0,-3);
glRotatef(45, 0, 1, 0);
ZeichneKoordinatensystem;
|
Wird der Winkel von glRotate bei jedem Renderdurchlauf erhöht, dreht sich ein anschließend gezeichnetes Objekt um die durch glTranslate angegeben Position.
Kombination - Rotate vor Translate
Wird Rotate vor Translate aufgerufen, werden zuerst die Achsen neu ausgerichtet und dann der Koordinatenursprung bezüglich dieser neuen Achsen verschoben.
Folgender Code wird demonstriert:
glRotatef(45, 0, 1, 0);
glTranslatef(3.5,0,-3);
ZeichneKoordinatensystem;
|
Wird der Winkel von glRotate bei jedem Renderdurchlauf erhöht, bewegt sich ein nach glTranslate gezeichnetes Objekt auf einer Kreisbahn um den Koordinatenursprung wie er beim Aufruf von glRotate aktuell war. Der Radius der Kreisbahn wird durch glTranslate bestimmt.
Wenn man die beiden Ergebnisse direkt vergleicht sieht man, dass die Reihenfolge von Bedeutung für das Ergebnis ist. Das die Unterschiede so "klein" ausfallen liegt nur daran, dass bei rotate nicht mit größeren Winkeln rotiert wurde.
Kombination - Scale vor Rotate
Die Rotation wird nur von der Richung, nicht aber der Länge der Achsen beeinflusst. Der Winkel um den gedreht wird, bleibt von der Skalierung unberührt (45° bleiben 45°. Die Achsen zeigen anschließend jeweils in die gleiche Richtung)
Folgender Code wird demonstriert:
glScalef(0.8, 1, 0.6);
glRotatef(45, 0, 1, 0);
ZeichneKoordinatensystem;
|
Der direkte Vergleich zeigt, dass die Skalierung keinen Effekt auf den Winkel hatte. Beide Koordinatensysteme sind gleich ausgerichtet:
Kombination - Scale vor Translate
Die Verschiebung ist Abhängig von der Ausrichtung der Achsen (glRotate) und der Maße der Achsen (glScale). Aus diesem Grunde beeinflusst glScale die nachfolgenden Translationen.
Folgender Code wird demonstriert:
glScalef(0.8, 1, 0.6);
glTranslatef(3.5,0,-3);
ZeichneKoordinatensystem;
|
Der direkte Vergleich zeigt, dass sich die Skalierung auf die Verschiebung auswirkt. Denn obwohl der Verschiebung in die gleiche Richtung und um den selben Wert erfolgt, sind die Resultate nicht identisch. Dies liegt daran, dass glScale die Maße verändert hat. Die Verschiebung in X-Richtung erreicht dadurch nur 80% und in Z-Richtung sogar nur 60% der unskalierten Transformation:
Nachwort
7 Stunden sind vorbei und das Tutorial ist auf eine ordentliche Länge angewachsen. Ich hoffe ich konnte euch die OpenGL Matrizen etwas näher bringen.
Wer jetzt denkt "Bilder sind mir zu statisch!", dem möchte ich noch diese Programme ans Herz legen:
Name | Hersteller | Beschreibung |
---|---|---|
Matrix2 - Skizzenrenderer | Flash | Dieses Programm habe ich geschrieben um die Skizzen für dieses Tutorial zu erstellen. Es wurde so modifiziert, dass man beliebige Kombinationen von glTranslate, glRotate und glScale anzeigen kann. |
Matrix Control | Lithander | Das Programm erlaubt euch zu beobachten, wie die Matrizen sich bei den einzelnen Befehlen verändern und wie die Ausgabe aussieht. Besonderheit an diesem Programm ist, dass ihr die Matrizen auch per Hand manipulieren könnt. |
Sollten immer noch Unklarheiten bestehen bleibt das Forum natürlich auch weiterhin die Anlaufstelle Nummer1. Dies betrifft auch euer Feedback zum Tutorial.
MfG
- Flash (Kevin Fleischer)
|
||
Vorhergehendes Tutorial: Tutorial_2D |
Nächstes Tutorial: - |
|
Schreibt was ihr zu diesem Tutorial denkt ins Feedbackforum von DelphiGL.com. Lob, Verbesserungsvorschläge, Hinweise und Tutorialwünsche sind stets willkommen. |