arkandos.de

Geschrieben von Yoschi am Oct 13, 02:16 PM in ,

Wie vieleicht schon Phoebus von DelphiGL muss auch ich zuerst euer komplettes Weltbild zerstören:
Nicht ihr (sprich die Kamera) bewegt euch, sondern die Welt um euch herum !!

Schock ! Diese Tatsache macht einem besonders am Anfang oder als ehemaliger D3D Programmierer das Leben schwer, da man bei allen “Kamerabewegungen” in openGL erst umdenken muss. Des Lösungs Rätsel (s.o.) ist aber einfach, die Welt immer genau in die entgegengesetzte Richtung zu bewegen.

Die Ganze Kamerabewegung lässt sich in 3 Schritten erklären: Drehung, Bewegung und eine Kombination aus beidem. Perfekte Formkontrolle ;) ( <—Schon wieder ein Insider)

Stufe 1: Kamera drehen

Fangen wir mit dem einfachsten an: Wir wollen uns umsehen können. Dafür verwenden wir einfach die Funktion, die wir auch sonst in openGL immer verwenden, um etwas zu drehen: glRotate. Um die ganze Szene zu drehen, rufen wir glRotate in unserer Zeichenroutine auf, bevor wir anfangen, irgendetwas zu zeichnen oder objektspezifische Rotationen/Translationen auszuführen. Wichtig dabei ist, dass man immer zuerst um die X-, dann um die Y- und zum Schluss um die Z-Achse dreht, da es ansonsten zu “komischen” Ergebnissen kommt. Wer Lust hat, kann es ja mal ausprobieren ;)

C++:
  1. //Speichre Drehung um {x, y, z} Achse
  2. float cameraRotation[3] = {0, 0, 0};
  3. void update() {
  4.   //clear, etc ...
  5.   //Wenn a oder s gedrückt wurde, drehe um die y - Achse
  6.   //Alle Werte müssen invertiert (*-1) werden, da wir die Szene und nicht die Kamera drehen
  7.   if(keyPressed('a')) cameraRotation[1] -= 10;
  8.   if(keyPressed('d')) cameraRotation[1] += 10;
  9.   if(keyPressed('w')) cameraRotation[0] -= 10;
  10.   if(keyPressed('s')) cameraRotation[0] += 10;
  11.   //Drehung Anwenden
  12.   glLoadIdentity(); //zurücksetzen
  13.   glRotatef(cameraRotation[0], 1, 0, 0);
  14.   glRotatef(cameraRotation[1], 0, 1, 0);
  15.   glRotatef(cameraRotation[2], 0, 0, 1);
  16.   //Szene Zeichnen
  17.   draw();
  18. }

Natürlich können wir die Welt auch in Abhängigkeit zur Position der Maus drehen, wie es normalerweise in Spielen üblich ist.

Stufe 2: Kamera bewegen

Auch das Bewegen für sich ist nicht schwierig, da uns openGL auch dafür mit glTranslate einen passenden Befehl bietet. Genau wie bei der Rotation rufen wir also einfach für dem Zeichen der Szene einmal glTranslatef auf. Natürlich müssen auch hier wieder alle Werte invertiert werden.

Achtung dabei bei der Z-Achse: Diese zeigt bei openGL bekanntlich aus dem Bildschirm heraus. Wollen wir uns nach vorne bewegen, müssen wir die Szene nach hinten verschieben (negativ). Allerdings zeigt die Z-Achse in die andere Richtung, damit haben wir - * - => +. Um sich nach vorne zu bewegen, muss man glTranslatef also mit einem positiven Z-Parameter aufrufen.

C++:
  1. //Speichre Translations - Parameter {x, y, z}
  2. float cameraPosition[3] = {0, 0, 0};
  3. void update() {
  4.   //clear, etc ...
  5.   //"nach vorne" entspricht eine bewegung entlang der z - Achse
  6.   if(keyPressed(KEY_UP)) cameraPosition[2] += 0.3;
  7.   if(keyPressed(KEY_DOWN)) cameraPosition[2] -= 0.3;
  8.   if(keyPressed(KEY_LEFT)) cameraPosition[0] -= 0.3;
  9.   if(keyPressed(KEY_RIGHT)) cameraPosition[0] += 0.3;
  10.   //bewegung anwenden
  11.   glLoadIdentity(); //zurücksetzen
  12.   glTranslatef(cameraPosition[0], cameraPosition[1], cameraPosition[2]);
  13.   draw();
  14. }

Wie ihr seht, fehlt in diesem Code die Rotation wieder. Wer sie einfach mal stehen lässt, wird feststellen, dass sich die Kamera nicht so bewegt, wie man sich das vorstellt.

Stufe 3: Die Kombination

Wenn man die Translation vor der Rotation ausführt, funktioniert theoretisch die Bewegung in Sichtrichtung (mehr oder weniger). Allerdings möchten wir die Szene nicht um den Ursprung, sondern um die Kameraposition drehen. Wie ihr euch denken könnt, ist es so genau falsch ;)

Spontane Lösung: Rotation und Translation einfach vertauschen, also die Translation nach der Rotation ausführen. Dabei stimmt jetzt die Rotation, die Bewegung sieht allerdings so aus, als würden wir uns auf einem Karussell befinden.

Trotzdem muss die Drehung zuerst geschehen. Dabei müssen wir es aber schaffen, uns dennoch nach vorne (dem richtigen Vorne) zu bewegen. Leider bleibt uns dafür nichts anderes übrig, als die Drehung selbst zu berechnen bzw. sie wieder umzukehren. In die Schule haben wir das schon oft für Rotationen um eine Achse gemacht, als wir Sinus- und Cosinuswerte für einen Winkel berechnet haben. Um 3 Achsen zu drehen funktioniert theoretisch genauso — Man muss nur wissen, welche Kombination aus Sinus und Kosinus mit welchen Winkeln man braucht.

Ab da befinden wir uns nicht mehr in der 8. Klasse, sondern in der Oberstufe oder im Studium. Einfache Sinus- und Kosinusfunktionen reichen nicht mehr. Wir brauchen schwerere Geschütze: Drehmatrizen !

Ein Drehmatrix ist Einer, der mutlipliziert mit einem Vektor den gedrehten Punkt ergibt. Auch openGL verwendet solche Matrizen, allerdings mit einer Zeile und Spalte mehr, um auch gleich noch die Translation mit zu speichern. Für uns reichen allerdings “ganz normale” 3×3 Matrizen.

Die Drehmatrizen für einen bestimmten Winkel um eine Achse sehen so aus:

Um die X – Achse:

     [ 1     0      0   ]
Rx = [ 0 cos(a) -sin(a) ]
     [ 0 sin(a)  cos(a) ]

Um die Y – Achse:

     [  cos(b) 0 sin(b) ]
Ry = [      0  1     0  ]
     [ -sin(b) 0 cos(b) ]

Um die Z – Achse:

     [ cos(y) -sin(y) 0 ]
Rz = [ sin(y)  cos(y) 0 ]
     [     0       0  1 ]

Alles was wir jetzt noch machen müssen ist eigendlich “nur”, diese 3 Matrizen miteinander zu mutliplizieren ! Auch hier müssen wir wieder beachten, dass wir alles andersrum drehen/verschieben müssen. Wir müssen also nicht Rx * Ry * Rz, sondern Rz * Ry * Rx berechnen (Für Matrizen gilt das Kommutativgesetz nicht). Wer mag, kann das gern selber machen, wer nicht, muss mir jetzt einfach mal vertrauen, dass ich hier keinen Schmarn erzähle:

          [  cos(b) sin(a)*sin(b) cos(a)*sin(b) ]
Ry * Rx = [      0         cos(a)       -sin(a) ]
          [ -sin(b) sin(a)*cos(b) cos(a)*cos(b) ]

               [ cos(b)*cos(y) -sin(y) sin(b)*cos(y) ]
Rz * Ry * Rx = [ cos(b)*sin(y)  cos(y) sin(b)*sin(y) ] * Rx = 
               [       -sin(y)      0         cos(b) ]

[ cos(b)*cos(y) cos(a)*-sin(y)+sin(a)*sin(b)*cos(y) -sin(a)*-sin(y)+cos(a)*sin(b)*cos(y) ]
[ cos(b)*sin(y)  cos(a)*cos(y)+sin(a)*sin(b)*sin(y)  -sin(a)*cos(y)+cos(a)*sin(b)*sin(y) ] = 
[       -sin(b)                       sin(a)*cos(b)                        cos(a)*cos(b) ]

[ cos(b)*cos(y) sin(a)*sin(b)*cos(y)-cos(a)*sin(y) sin(a)*sin(y)+cos(a)*sin(b)*cos(y) ]
[ cos(b)*sin(y) cos(a)*cos(y)+sin(a)*sin(b)*sin(y) cos(a)*sin(b)*sin(y)-sin(a)*cos(y) ]
[       -sin(b)                      sin(a)*cos(b)                      cos(a)*cos(b) ]

Puuh ! Der Aufwand hat sich aber gelohnt: Die einzelnen Spalten der Matrix entsprechen jetzt der Richtung, in die wir uns bewegen wollen. Entlang der X Achse nehmen wir also die 1. Spalte, entlang der Z Achse die 3. Spalte. Die einzelnen Zeilen der jeweiligen Spalten sind die Formeln, in der wir die richtigen Winkel einsetzen. Als Ergebnis erhalten wir einen gedrehten Vektor, um den wir dann verschieben können.

Damit können wir jetzt Drehung und Bewegung kombinieren. Normalerweise möchte man nur, dass man sich um die Y-Achse drehen kann. Die X-Achse braucht man nur für Weltraumshooter, etc. und die Z Achse nur, wenn man “Rollen” können muss. Je nachdem, wie stark die “Dreh-Freiheit” sein muss, braucht man auch nur eine entsprechende Matrix verwenden (Ry, Ry*Rx oder die Komplette), wodurch man sich (relativ teure) Multiplikationen und Sinus/Kosinusberechnungen spart.

C++:
  1. inline double rad(double deg) {
  2.   return deg / 180.0 * M_PI;
  3. }
  4. float cameraRotation[3] = {0, 0, 0};
  5. float cameraPosition[3] = {0, 0, 0};
  6. void update() {
  7.   //clear, etc ...
  8.   //X - und Y Drehung je nach Mausposition
  9.   cameraRotation[1] = (float)mouseX / screenWidth * 360 - 180;
  10.   cameraRotation[0] = (float)mouseY / screenHeight * 360 - 180;
  11.   //Drehung um die Z - Achse per Tasten
  12.   if(keyPressed('q')) cameraRotation[2] -= 10;
  13.   if(keyPressed('e')) cameraRotation[2] += 10;
  14.   //Sinus - und Kosinusberechnungen. Wie immer müssen alle Winkel umgedreht werden
  15.   double s1 = sin(rad(-cameraRotation[0])), s2 = sin(rad(-cameraRotation[1])), s3 = sin(rad(-cameraRotation[2]));
  16.   double c1 = cos(rad(-cameraRotation[0])), c2 = cos(rad(-cameraRotation[1])), c3 = cos(rad(-cameraRotation[2]));
  17.   //Bewegung ! Die 0.3 Sind der Speed - Faktor
  18.   float dx = 0, dy = 0, dz = 0;
  19.   if(keyPressed(KEY_UP)) dz += 0.3;
  20.   if(keyPressed(KEY_DOWN)) dz -= 0.3;
  21.   if(keyPressed(KEY_LEFT)) dx += 0.3;
  22.   if(keyPressed(KEY_RIGHT)) dx -= 0.3;
  23.   if(keyPressed('s')) dy += 0.3;
  24.   if(keyPressed('w')) dy -= 0.3;
  25.   //Berechnen wie im Matrix angegeben
  26.   cameraPosition[0] += (dx * (c2*c3)) + (dy * (s1*s2*c3-c1*s3)) + (dz * (s1*s3+c1*s2*c3));
  27.   cameraPosition[1] += (dx * (c2*s3)) + (dy * (c1*c3+s1*s2*s3)) + (dz * (c1*s2*s3-s1*c3));
  28.   cameraPosition[2] += (dx * (-s2)) + (dy * (s1*c2)) + (dz * (c1*c2));
  29.   //cameraRotation und -Position anwenden
  30.   glLoadIdentity(); //zurücksetzen
  31.   glRotatef(cameraRotation[0], 1, 0, 0);
  32.   glRotatef(cameraRotation[1], 0, 1, 0);
  33.   glRotatef(cameraRotation[2], 0, 0, 1);
  34.   glTranslatef(cameraPosition[0], cameraPosition[1], cameraPosition[2]);
  35.   //und ... Szene Zeichnen !
  36.   draw();
  37. }

Fertig !

Der letzte Schritt war etwas viel Mathe … ich hoffe, ich habe euch nicht verschreckt und ihr habt bis zum Ende durchgehalten. Wenn ihr es geschafft habt, könnt ihr nun endlich mit einer virtuellen Kamera durch eure 3D – Welt fliegen.

Zum Schluss bitte ich noch noch um Feedback, falls etwas unklar oder gar falsch ist.
Einfach in die Kommentare unten reinschreiben :)

Viel Spaß mit openGL, Yoschi

Quellen, Links:

Zurück zu | Vor zu

Kommentare