Tutorial Matrix2

Aus DGL Wiki
Wechseln zu: Navigation, Suche

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.

Info DGL.png 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. ;-)

Info DGL.png Modeller wie 3D Studio Max benutzen globale Koordinaten beim exportieren der Modelle. Dies hat den Vorteil, dass beim importieren einer ganzen 3DS Szene, die Objekte gleich an der richtigen Position stehen, und nicht erst ausgerichtet werden müssen.

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:

So stelle ich mir eine 2D-Matrix vor.


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.

Das passiert bei einer Translation um 3.5 Einheiten in X und -3 Einheiten in Z-Richtung.


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)

Das passiert bei einer Rotation um 45° mit der Y-Achse als Rotationsachse.

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.

Das passiert bei einer Skalierung. Wie ihr seht, sind die Seiten unterschiedlich skaliert worden (X-Achse: 80%, Z-Achse: 60%).


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;
Bevor glTranslate aufgerufen wurde.
Nachdem glTranslate aufgerufen wurde.
Nachdem glRotate aufgerufen wurde. Man sieht, dass die jeweils vorhergehende Veränderung bestehend bleibt, und die Basis für die nachfolgende Änderung bildet.


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;
Bevor glRotate aufgerufen wurde.
Nachdem glRotate aufgerufen wurde.
Nachdem glTranslate aufgerufen wurde. Man sieht, dass die vorhergehende Rotation die Verschieberichtung beeinflusst.

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.


Erst Rotiert
Erst Verschoben

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;
Bevor glScale aufgerufen wurde.
Nachdem glScale aufgerufen wurde.
Nachdem glRotate aufgerufen wurde. Man sieht, dass die Skalierung den Winkel nicht beeinflusst. Das Koordinatensystem wurde korrekt um 45° gedreht.

Der direkte Vergleich zeigt, dass die Skalierung keinen Effekt auf den Winkel hatte. Beide Koordinatensysteme sind gleich ausgerichtet:

Unskaliert
Skaliert


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;
Bevor glScale aufgerufen wurde.
Nachdem glScale aufgerufen wurde.
Nachdem glTranslate aufgerufen wurde.

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:

Unskaliert
Skaliert


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.