OpenGL.org 3Dsource.de: Infos rund um 3D-Computergrafik, OpenGL und VRML
space Telefon-Tarife space VDI - Arbeitskreis Fahrzeugtechnik space Beschallungsanlagen und Elektroakustik space Heise-News space

9 Transformationen

9.001 Ich bekomme meine Transformationen nicht zum laufen. Wo kann ich mehr über Matrizen lernen ?

Eine Erklärung der Matrizen- und Vektorrechung sowie der Linearen Algebra geht über den Rahmen dieser FAQ hinaus. Diese sind Themen der High School in den USA (und teilweise der Abiturstufe in Deutschland ?! zumindest im Studium kommts dran ;-) ).

Falls zwar die Grundlagen bekannt sind, man aber trotzdem durcheinander kommt (ein nicht so seltenes Problem selbst für die, die schon länger damit arbeiten), bietet sich auch Steve Baker's Grundlagen der Matrizenrechnung und sein Artikel über Eulerwinkel (jeweils in Englisch) an.

Delphi Code für Vektor- und Matrizenrechnung bietet Mike Lischke.

9.005 Wie sind die OpenGL Matrizen referenziert, Spalte-Zeile oder Zeile-Spalte ?

Für den Programmierer: OpenGL-Matrizen sind Felder mit 16 (4*4) Einträgen, die hintereinanderweg im Speicher abgelegt werden. Die Transformationselemente sind jeweils im 13., 14. und 15. Element gespeichert (da Felder meistens mit dem Index 0 starten, in den Feldern [12,13,14]).

Aufgrund dieser lineraren Speicherung ist der Unterschied zwischen Zeile-Spalte und Spalte-Zeile nur eine andere, hier also von der OpenGL Spezifikation getroffene Festlegung. Eine Multiplikation von links mit Spalte-Zeile Matrizen erzielt das gleiche Ergebnis wie eine Multiplikation von rechts mit Zeile-Spalte Matrizen. OpenGL nutzt allerdings die Spalte-Zeile Notation. Grundsätzlich kann man aber jede der beiden Varianten nutzen, solange es konsequent im Programm durchgezogen wird.

Die Festlegung der Spalte-Zeile-Notation durch die Spec hat schon zu unzähligen Diskussionen unter OpenGL-Programmiern geführt. Insbesondere weil man sich immer wieder in Erinnerung rufen muss, in welcher Reihenfolge die einzelnen Felder im Speicher abgelegt sind. Im Zweifel hilft aber die Spec, dort sind bei der Defintion der Matrizen alle Felder numeriert ;-)

9.010 Welche Einheit nutzt OpenGL für die Koordinaten ?

Die kurze Antwort: jede Einheit die man will.

Abhängig von den Geometriedaten der Szene erleichtert es die Programmierung, wenn man den Koordinaten eine beliebig gewählte Einheit (mm, m, km) zuordnet.

Es ist aber auch möglich, mehrere verschiedene Einheiten zu verwenden und diese mittels glScale() auf eine gemeinsame Basis zu normieren (z.B. ein Objekt in Metern, dessen Position in km).

Es ist Aufgabe des Programms, die Projection und ModelView Matrix so abzustimmen, das der Anwender die Szene aus einem geeigneten Blickwinkel und Entfernung betrachten kann. Auch hier wird sich die beste Einstellungen z.B. durch gezieltes Verändern der Werte für Blickwinkel und Clipping Planes finden lassen.

9.011 Wie werden die Koordinaten transformiert ? Wo liegen die Unterschiede zwischen den Koordinatensystemen ?

Die Modellkoordinaten (Objektkoordinaten) ergeben nach Multiplikation mit der ModelView Matrix die Augpunktkoordinaten.
Die Augpunktkoordinaten ergeben nach der Multiplikation mit der Projektionsmatrix die Clipping Koordinaten.
Die Clipping Koordinaten XYZ, dividiert durch die Clipping Koordinate W ergeben die normalisierten Device Koordinaten.
Die Device Koordinaten werden durch die Viewport Parameter gestreckt bzw. transformiert und ergeben dann die Bildschirmkoordinaten.

Die Modellkoordinaten sind dabei entscheidend für das endgültige Aussehen des Objekts, die mittels glVertex*() oder glVertexPointer() an die OpenGL übergeben werden. Sie repräsentieren also die Form/Geometrie des zu zeichnenden Objekts.

Viele Programmierer arbeiten auch direkt im Weltkoordinatensystem. Definiert man z.B. einen Würfel mit der Kantenlänge 1.0 und transformiert (dreht/verschiebt) dieses Objekt dann an eine bestimmte Position, so werden die Modellkoordinaten der Eckpunkte durch Multiplikation mit der ModelView Matrix in Weltkoordinaten umgerechnet. Obwohl die Form des Würfels erhalten bleibt, sind seine Koordinaten danach nicht mehr so leicht der gezeichneten Form zuzuordnen.
OpenGL kennt diese errechneten Weltkoordinaten im eigentlichen Sinne zwar nicht, diese Modellvorstellung erleichtert dem Programmierer das Verständnis allerdings erheblich.

Die Augpunktkoordinaten werden im nächsten Schritt berechnet. Nachdem alle Modellkoordinaten in Weltkoordinaten umgerechnet wurden, erfolgt die Transformation dieser Weltkoordinaten in die Augpunktkoordinaten. Dieses wird durch eine weitere Matrizenmultiplikation mit den Parametern des Viewing Volumens durchgeführt. Mit anderen Worten: alle Koordinaten werden so relativ zum Augpunkt (Entfernung, Richtung) dargestellt.

Mit diesem Ergebnis ist es nun möglich, die Clipping Koordinaten, also die Grenzen des Viewing Volumens aus Sicht der Kamera zu berechnen. Die Clipping Koordinaten liegen im Bereich von -Wc to Wc mit W als homogener Anteil der Grenzkoordinaten. Alle Koordinaten ausserhalb dieses Wertebereiches werden geclippt, also von den nachfolgenden Transformationen (einschl. Anzeige) ausgeschlossen.

Die Perspektivtransformation (also die Zentral- oder Parallelprojektion) wird auf die Clipping Koordinaten angewendet, um daraus die normalisierten Device Koordinaten im Bereich von -1 bis +1 für alle Achsen zu erhalten.

Der abschliessende Schritt ist die Berechnung der Bildschirmkoordinaten. Diese werden durch Berücksichtigung der Viewport Grenzen mittels weiteren Transformationen aus den Device Koordinaten gewonnen. Vorgegebene Parameter lassen sich durch die Funktionen glViewport() und glDepthRange() ändern. Es ist so z.B. möglich, drei verschiedene Ansichten eines Objektes gleichzeitig in drei Viewports darzustellen.

Weitere Informationen enthält die OpenGL Spezifikation oder (in deutsch) meine 3D-Technik Seite.

9.020 Wie kann ich gezielt nur ein einzelnes Objekt der Szene transformieren ? Kann ich jedem Objekt seine eigenen Transformationen zuordnen ?

Die OpenGL arbeitet mit verschiedenen Matrix Stacks, um genau diese Anforderungen zu erfüllen. Für Modelltransformationen steht der ModelView Stack zur Verfügung.

Eine typische OpenGL Anwendung ruft zunächst den ModelView Matrix Stack mit glMatrixMode(GL_MODELVIEW) auf und lädt dann die Ansichtstransformation z.B. über gluLookAt(). Diese Funktion wird auch hier angesprochen.

Um jedes Objekt mit einer eigenen, von den anderen unabhängigen Transformation zu positionieren, müssen diese Aufrufe durch die Befehle glPushMatrix() und glPopMatrix() eingeschlossen sein. Als Beispiel:

    glPushMatrix();
      glRotatef(90., 1., 0., 0.);
      gluCylinder(quad,1,1,2,36,12);
    glPopMatrix();

Der obenstehende Code zeichnet einen Zylinder, der 90 Grad um die X-Achse gedreht wurde. Der Ausgangszustand wird durch den Aufruf von glPopMatrix() wieder hergestellt. Vergleichbar lassen sich auch weitere Objekte positionieren.

9.030 Wie kann ich 2D-Steuerungselemente über meine 3D-Ansicht plazieren ?

Grundsätzlich muss hierfür eine 2D-Projektion für die Elemente genutzt werden. Diese Steuerungselemente können entweder in Overlay Planes oder aber als normale Objekte in die Szene gezeichnet werden. Der Nachteil der zweiten Lösung ist, dass so auch alle Steuerungselemente in jedem Frame neu berechnet und gezeichnet werden müssen. Nutzt man dagegen die Overlay Planes (also eine Art Zeichenfolie über der Szene), wird ein Neuzeichnen der Steuerungselemente nur notwendig, wenn sich deren Darstellung geändert hat.

Um die angesprochene 2D-Projektion zu realisieren, muss die Projektionsmatrix entsprechend angepasst werden. Normalerweise geschieht das, indem die Fenstergrösse als Parameter genutzt wird:

    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    gluOrtho2D (0, window width, 0, window height);

gluOrtho2D() setzt die Grenzen der Z-Achse auf -1 und +1, das muss beim Aufruf der glVertex2*()-Funktionen beachtet werden. Ansonsten werden die Objekte durch zNear bzw. zFar geclippt.

Normalerweise ist die ModelView Matrix als Einheitsmatrix (Identity Matrix) definiert, wenn 2D-Elemente gezeichnet werden. Es kann aber auch sinvoll sein, einen anderen Weg zu nehmen (z.B. wenn identische Elemente an anderen Positionen gezeichnet werden sollen).

Um eine exakte Positionierung und Darstellung zu erreichen, kann eine kleine Transformation wie aus dem nächsten Beispiel helfen:

    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    glTranslatef (0.375, 0.375, 0.);

Um die Steuerungselemente über einer 3D-Szene mit aktiviertem Tiefentest im Vordergrund zu plazieren, muss dieser Tiefentest (Z-Buffer Test) zuvor ausgeschaltet werden. Das geht z.B. über glDisable(GL_DEPTH_TEST) oder glDepthFunc (GL_ALWAYS). Abhängig von dem Programm kann es auch reichen, einfach den Z-Buffer vor dem Zeichnen der 2D-Elemente zu löschen. Nicht zuletzt geht es auch, die 2D-Elemente mit einer sehr kleinen Z-Koordinate zu definieren.

Sobald die 2D-Projektion wie beschrieben realisiert wurde, lassen sich die Steuerungselemente wie normale Objekte der Szene mit Angabe der XY Bildschirmkoordinaten (0,0 oben links) zeichnen.

9.040 Wie kann ich die OpenGL Matrizentransformationen umgehen und selbst berechnete 2D-Daten direkt rasterisieren ?

Es gibt keine Funktion, um die OpenGL Matrixtransformationen direkt zu deaktivieren. Man kann die Transformationsmatrizen aber neutralisieren, indem man sie durch den Aufruf von glLoadIdentity() auf die Einheitsmatrix zurücksetzt. Die OpenGL Implementationen sollten die Funktionslosigkeit dieser Operation erkennen und auf die Matrizenmultiplikation verzichten.

Mehr Informationen, um OpenGL nur als Rasterisationsbibliothek zu nutzen, findet man in der OpenGL Game Developers FAQ.

9.050 Was sind die Vor- und Nachteile von absoluten im Vergleich zu relativen Koordinaten ?

Einige OpenGL Programme sollen das gleiche Objekt an verschiedenen Koordinaten in der Szene darstellen. Dieses kann man auf zwei Wegen realisieren:

1. Wenn man "absolute Koordinaten" verwendet, müssen mehrere Kopien des benötigten Objekts angelegt werden. Darüber hinaus muss die Positionierung dadurch erfolgen, dass die jeweiligen Koordinaten entsprechend modifiziert werden. Vorteil dieser Lösung ist es, dass keine zusätzlichen Modelltransformationen erfolgen müssen.

2. Wenn man "relative Koordinaten" verwendet, muss man das Objekt nur einmal im Speicher halten und kann es mittels glPush(), glPop() mit jeweils geänderten Modelltransformationen an anderen Positionen so oft wie benötigt zeichnen.

Im Allgemeinen kann ein häufiges Ändern des OpenGL Status -dazu gehört auch die ModelView Matrix- einen negativen Einfluss auf die Gesamtperformance des Programms ausüben. OpenGL kann die Geometrie schneller berechnen, wenn nicht jedes Objekt eine spezielle Matrixtransformation beansprucht.

Wie auch immer, man sollte die aufwendigen Berechnungen dem eingesparten Speicherplatz gegenüberstellen. Nimmt man einen aufwendig modellierten Türgriff mit 200 bis 300 Dreiecken als Beispiel und will hiermit ein Haus mit etwa 50 Türen nachbilden, benötigt man bei Verzicht auf eine einzige Display-List mit dem Objekt "Türgriff" und entsprechend vielen Modelltransformation den Speicherplatz für etwa 10...15 Tausend Dreiecken.

Wie in vielen Fällen der Programmierung muss man auch hier in jedem Einzelfall das richtige Mass zwischen Speicherbedarf und Rechenaufwand bestimmen.

9.060 Wie kann ich mehr als eine Ansicht meiner Szene gleichzeitig darstellen ?

Innerhalb eines Fensters lassen sich zwei oder mehr Ansichten gleichzeitig darstellen, wenn man den Aufruf von glViewport() entsprechend anpasst. Zunächst definiert man z.B. den ersten Viewport in der linken Fensterhälfte und zeichnet seine Ansicht. Dann legt man den Viewport neu auf die rechte Fensterhälfte fest und zeichnet seine zweite Ansicht.

Einige Befehle nehmen allerdings keine Rücksicht auf den glViewport() Aufruf. Dazu gehören auch SwapBuffers und glClear(). SwapBuffers() wechselt immer das gesamte Frame, dagegen lässt sich glClear() in Verbindung mit einem unveränderlichen Rahmen (glScissor) auf einen rechteckigen Bereich einschränken.

Falls das Programm die unterschiedlichen Ansichten in verschiedenen Fenstern darstellen soll, kann für jedes Fenster ein eigener Rendering Context angelegt werden. Zur Aktualisierung des Fensters muss dann der jeweilige Context mit MakeCurrent aktiviert werden. Falls ein gemeinsamer Context genutzt werden kann, ist der Viewport wie beschrieben festzulegen und umzuschalten. Im Falle verschiedener Contexts ist das im Regelfall nicht erforderlich.

9.070 Wie kann ich meine Objekte relativ zu einem gemeinsamen Koordinatensystem transformieren, also nicht um das jeweils eigene Koordinatensystem der Objekte ?

Wenn man ein Objekt um seine Y-Achse dreht, werden auch die X- und Z-Achse ihre räumliche Position ändern. Eine weitere Drehung um eine dieser Achsen bezieht sich dann auf die neue Position der gewählten Achse und nicht auf deren ursprüngliche Lage. Es ist aber oftmals wünschenswert, alle Rotationen auf ein festes Koordinatensystem beziehen zu können, um nicht von den Änderungen des Modellkoordinatensystems des Objekts abhängig zu sein.

Die OpenGL Game Developers FAQ enthält Informationen über die Nutzung von Quaternations zur Speicherung der Rotationen, was in manchen Fällen hilfreich sein kann.

Das Grundproblem liegt darin, dass die OpenGL Matrixoperationen nacheinander mit dem aktuellen Matrixstack multipliziert werden. Deshalb wirken sich diese Transformationen unmittelbar auf den auf das jeweilige Modellkoordinatensystem aus. Um die Transformationen auf das feste Weltkoordinatensystem wirken zu lassen, muss die Reihenfolge der Matrixmultiplikationen geändert werden. Allerdings bietet OpenGL keinen direkten Zugriff auf die Reihenfolge der Multiplikationen, so dass man diese Berechnungen manuell durchführen muss. Eine Möglichkeit im Programm besteht darin, nach jedem Frame den aktuellen Matrixstack abzufragen, um so den Wert der aktuellen ModelView Matrix zu ermitteln. Im nächsten Frame wären dann die neuen Transformationen nach einem Aufruf von glLoadIdentity() durchzuführen und als letzter Schritt mit der gespeicherten ModelView Matrix aus dem letzten Frame mittels glMultMatrix() zu multiplizieren.

Dabei sollte man beachten, dass die Abfrage der ModelView Matrix die Performance des Programms negativ beeinflussen kann, da die OpenGL Pipeline meist nur für den steuernden Zugriff, nicht aber für eine Abfrage optimiert ist. Um den Einfluss zu ermitteln, sollte man einen (selbst definierten) Benchmark zu Rate ziehen, da die tatsächlichen Auswirkungen immer von der genutzten OpenGL Implementation abhängig sind.

9.080 Was sind die Vor- und Nachteile von glFrustum() im Vergleich zu gluPerspective() ?

glFrustum() und gluPerspective() sind zwei Befehle zur Darstellung einer Szene als Zentralprojektion. Dabei stellt glFrustum() die flexiblere Möglichkeit zur Festlegung einer Zentralprojektion dar, hiermit lassen sich auch unsymmetrische Grenzen des Viewing Volumes zu den Achsen des Weltkoordinatensystems festlegen. Dagegen arbeitet gluPerspective() immer symmetrisch zu den Achsen und stellt somit nur einen Sonderfall von glFrustum() dar. Einen Performanceunterschied zwischen diesen Befehlen gibt es nicht, gluPerspective() ist in bestimmten Anwendungsfällen aber leichter zu nutzen.

Da glFrustum() die allgemeinere Definition einer Zentralprojektion ermöglicht, gibt es auch Situationen, in denen auf den Aufruf von gluPerspective() verzichtet werden muss. Beispiele hierfür sind projektierte Schatten, verteilte Rendering Contexts und die Stereo-Darstellung von Szenen.

Verteilte Rendering Contexts benötigen mehrere unsymmetrische Projektionen zum Rendern von verschiedenen Bereichen einer grösseren Szene. Das Gesamtergebnis wird dann in einem einzigen Bild zu der endgültigen Darstellung der Szene zusammengefügt. Ein Grund für diese Anwendung kann darin liegen, dass die gesamte Szene grösser als der maximal zulässige Viewport ist.

Eine Stereo-Darstellung lässt sich realisieren, in dem man die Szene aus zwei voneinander leicht abweichenden Blickwinkeln zeichnet. Da der mittlere Blickwinkel entlang der Blickachse laufen muss, sind diese Abweichungen symmetrisch zu den Achsen festzulegen. Beide Blickachsen laufen dabei im Zentrum der Szene zusammen.

9.085 Mit welche Parametern muss ich glFrustum() aufrufen, um das gleiche Ergebnis wie mit gluPerspective zu erhalten ?

Das Blickfeld (field of view, fov) von glFrustum() ist wie folgt definiert:

    fov*0.5 = arctan ((oben-unten)*0.5 / zNear)

Da bei sich symmetrischer Darstellung mittels gluPerspective() "oben" und "unten" nur im Vorzeichen unterscheiden, gilt:

    oben  = tan(fov*0.5) * zNear
    unten = -oben

Die Werte für die linke und rechte Grenze sind einfache Funktionen des Verhältnisses (aspect) von der oberen zur unteren Grenze:

    links  = aspect * unten
    rechts = aspect * oben

Anmerkung: fov muss in Radiant angegeben werden, wenn man die Standard-C-Bibliothek zur Berechnung nutzt. Falls die Angabe nur in Grad vorliegt, kann man oben wie folgt berechnen:

    oben = tan(fov*3.14159/180.0) * zNear
Der Rest bleibt gleich.

Die OpenGL Reference Manual enthält die von beiden Funktionen erzeugten Matrizen.

9.090 Wie zeichne ich einen bildschirmfüllenden Würfel ?

Diese Frage meint meistens "Wie zeichne ich ein Rechteck, dass den gesamten Viewport ausfüllt ?". Hierfür gibt es mehrere Möglichkeiten.

Die einem am ehesten einfallende Lösung ist wohl, die Farbe mittels glColor*() festzulegen, die Projection und ModelView Matrix jeweils mit glLoadIdentity() zurückzusetzen und dann ein Rechteck der Kantenlänge "1" mit glRectf() oder GL_QUADS zu zeichnen. Die Z-Werte des Rechtecks und für zNear, zFar sollten dabei im Bereich von -1 bis +1 liegen.

Hier ist ein Beispiel für diese Lösung:

    glMatrixMode (GL_MODELVIEW);
    glPushMatrix ();
    glLoadIdentity ();
    glMatrixMode (GL_PROJECTION);
    glPushMatrix ();
    glLoadIdentity ();
    glBegin (GL_QUADS);
      glVertex3i (-1, -1, -1);
      glVertex3i (1, -1, -1);
      glVertex3i (1, 1, -1);
      glVertex3i (-1, 1, -1);
    glEnd ();
    glPopMatrix ();
    glMatrixMode (GL_MODELVIEW);
    glPopMatrix ();

Falls es das Programm erfordert, kann man das Rechteck auch direkt am hinteren Rand des Viewing Volumens zeichnen, also als Z-Wert +1 festlegen.

Es kann sinvoll sein, bestimmte Buffer (und damit zusätzliche Berechnungen) beim Zeichnen eines Vollbild-Rechtecks abzuschalten. Das kann den Color Buffer und den Tiefenbuffer betreffen (Z-Buffer Test auf GL_ALWAYS setzen), so dass das Rechteck alle vorhergehenden Farbwerte im Frame Buffer überschreibt. Auch der Stencil Buffer kann helfen (Bereich wird als Schablone definiert und damit nicht mehr mit anderen Werten überschrieben).

9.100 Wie kann ich die Bildschirmkoordinaten für einen einen bestimmten Punkt in meinem Modellkoordinatensystem ermitteln ?

Hierfür kann man die GLU Funktion gluProject() nutzen, falls die Daten nur für wenige Vertices benötigt werden. Für eine grössere Anzahl an Vertices sollte man aber auf den Feedback Mechanismus von OpenGL ausweichen.

Beim Aufruf von gluProject() muss man als Parameter (mittels glGet* ermittelt) die aktuelle Projection und ModelView Matrix, den Viewport und die Modellkoordinaten angeben. Die Bildschirmkoordinaten werden als X,Y,Z zurückgegeben (0 <= Z <= 1).

9.110 Wie kann ich die Modellkoordinaten für einen bestimmten Punkt auf meinem Bildschirm ermitteln ?

Die GLU bietet hierfür die gluUnProject() Funktion.

Es muss zunächst der Tiefenbuffer an der Stelle X,Y des Bildschirms ausgelesen werden. Das geht z.B. so:

    GLdouble z;
    glReadPixels (x, y, 1, 1, GL_DEPTH_COMPONENT, GL_DOUBLE, &z);

Man sollte beachten, dass die OpenGL Koordinaten x und y (0,0) in der oberen, linken Ecke des Bildschirms definiert sind.

Auch hier muss der Funktion neben der interessierenden X,Y,Z Koordinate als Parameter (mittels glGet* ermittelt) die aktuelle Projection und ModelView Matrix und den Viewport übergeben.

9.120 Wie kann ich die Koordinaten eines Punktes nach erfolgter Modelltransformation ermitteln ?

Es ist oft nützlich, die Weltkoordinaten eines Vertex (berechnet aus den Modellkoordinaten und der zugehörigen Modelltransformation) zu kennen. Diese erhält man, indem Modellkoordinaten und ModelView Matrix miteinander multipliziert werden. Die Matrizenmultiplikation muss man allerdings von Hand durchführen.

9.130 Wie berechne ich die Entfernung vom Augpunkt zu einem anderen Punkt im Weltkoordinatensystem ?

Zuerst werden die Koordinaten des Punktes in das Augpunktkoordinatensystem umgerechnet (Koordinaten mit der Modelview-Matrix multiplizieren). Das funktioniert nur, wenn die Objekttransformationen korrekt im Modellview-Stack durchgeführt wurden. Dann wird der Abstand des Punktes zum Koordinatenursprung ermittelt (L2=X2+Y2+Z2).

Die Modelview-Matrix erhält; man über:

		GLfloat m[16];
		glGetFloatv (GL_MODELVIEW_MATRIX, m);
Wie bei jeder OpenGL-Funktion muss auch hier ein aktivierter Context vorliegen, damit glGet*() arbeitet.

9.140 Wie kann ich ein festes Höhe/Breite-Verhältnis meines Fensters auch bei Grössenänderung einhalten ?

Das hängt zunächst davon ab, wie die Projection Matrix im Programm festgelegt wird. In jedem Fall braucht man die aktuelle Höhe und Breite des Fensters. Wie man diese ermittelt, hängt von der genutzten API ab. GLUT übergibt diese z.B. als Parameter an den Resize Callback.

Das folgende gilt, wenn der Viewport das gesamte Fenster ausfüllt. Trifft das nicht zu, ist fensterBreite/fensterHoehe durch viewportBreite/viewportHoehe zu ersetzen.

Falls gluPerspective() zur Festlegung der Projektion genutzt wird, muss der zweite Parameter dieser Funktion (Verhältnis zwischen Breite und Höhe des Fensters) neu berechnet werden. Das geht wie folgt:

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(fov, (float)fensterBreite/(float)fensterHoehe, zNear, zFar);

Falls glFrustum() genutzt werden soll, ist lässt sich ein verzerrungsfreies Bild auch nach Grössenänderung wie folgt realisieren:

    float cx     = 0; /* hier gewünschten Mittelpunkt der Szene in X festlegen */
                      /* cx bezieht sich auf die vordere Clippingflaeche zNear */
    float halbB  = fensterBreite*0.5;
    float aspect = (float)fensterBreite/(float)fensterHoehe;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(cx-halbB*aspect, cx+halbB*aspect, unten, oben, zNear, zFar);

glOrtho() und gluOrtho2D() werden wie glFrustum() behandelt.

9.150 Wie kann ich mit OpenGL ein linksorientiertes Koordinatensystem nutzen ?

OpenGL besitzt keinen Schalter, um einfach zwischen rechts- und linksorientierten Koordinatensystemen umzuschalten. Man kann diese Funktion aber nachbilden, indem man die Z-Achse spiegelt. Als Beispiel:

    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    glScalef (1., 1., -1.);
    /* ab hier wie ueblich weiter */

9.160 Wie muss ich ein Objekt transformieren, so dass es einem bestimmten Punkt meiner Szene folgt ?

Hierzu muss eine Matrix definiert werden, die das Modellkoordinatensystem des betreffenden Objekts in ein anderes Koordinatensystem transformiert, dass in die gewünschte Richtung zeigt. Dieser Beispielcode zeigt, wie man eine derartige Matrix berechnen kann.

Um ein Objekt so darzustellen, dass es immer auf den Betrachter zeigt, muss es lediglich direkt im Weltkoordinatensystem (ModelView Matrix als Einheitsmatrix) gezeichnet werden.

9.162 Wie kann ich ein Objekt mit vorgegebenem Gier-, Nick- und Wankwinkel transformieren ?

Als Vorwort: In der Originalfrage ist von yaw, pitch and roll die Rede. Die gewählte Übersetzung stammt aus der Kfz-Technik, wobei

  • der Gierwinkel (yaw) eine Drehung um die Senkrechte (positiv nach oben zeigende Achse),
  • der Nickwinkel (pitch) eine Drehung die quer zur Fahrzeuglängsachse liegende Achse
  • und der Wankwinkel (roll) eine Drehung um die Fahrzeuglängsachse
bezeichnet.
In der Kfz-Technik wird zwar auch ein rechtwinkliges und rechtsorientiertes Koordinatensystem verwendet, allerdings liegen die Achsen nicht so, wie man es als OpenGL-Programmierer erwarten würde (die X-Achse ist der Fahrzeuglängsachse zugeordnet, Z zeigt nach oben).

Daher hier die Zuordnung der Winkel auf das Koordinatensystem der OpenGL:

  • Gierwinkel -> Rotation um die Y-Achse
  • Nickwinkel -> Rotation um die X-Achse
  • Wankwinkel -> Rotation um die Z-Achse

Nun zur Frage:
Der obere linke Teil (3x3 Felder) der Transformationsmatrix enthält die Werte für die X,Y,Z-Achse des Modellkoordinatensystems nach der Transformation. Diesen Teilbereich kann man also auch durch selbst berechnen und dann entsprechend mit den eigenen Werten auffüllen.

Dazu berechnet man die Drehung um die jeweilige Achse.
Wenn der Parameter der neuen Transformation der Wankwinkel ist, wird das Koordinatensystem um die Z-Achse gedreht (glRotate, Winkelangabe in Grad). Das gleiche Prinzip kommt bei den anderen Eingangswinkeln zur Anwendung.
Gemäss der originalen FAQ werden die so erhaltenen Werte für X,Y,Z einfach in den linmken oberen 3x3 Bereich einer 4x4 Matrix hineinkopiert, mittels glMultMatrix() zur Gesamttransformation hinzugefügt und das wars. Naja, mir schien diese Art der Erklärung doch etwas zu schnell zu gehen, daher mach ich es mal etwas ausführlicher:

Aus der Spec kann man die Rotationsmatrizen Die so erhaltenen transform is a pitch or yaw. Then simply construct your transformation matrix by inserting the new local X, Y, and Z axes into the upper left 3x3 portion of an identity matrix. This matrix can be passed as a parameter to glMultMatrix().

Further rotations should be computed around the new local axes. This will inevitably require rotation about an arbitrary axis, which can be confusing to inexperienced 3D programmers. This is a basic concept in linear algebra.

Many programmers apply all three transformations -- yaw, pitch, and roll -- at once as successive glRotate() calls about the X, Y, and Z axes. This has the disadvantage of creating gimbal lock, in which the result depends on the order of glRotate() calls.

9.170 Wie kann ich einen Spiegeleffekt zeichnen ?

Das geht, indem man die Szene zweimal zeichnet. Einmal unverändert, einmal spiegelbildlich. Dieses Beispiel demonstriert die Technik.

Ist die Spiegelebene an einer Achse ausgerichtet (z.B. verankert in der YZ-Ebene), kann die Szene mittels glTranslate(), glScale() gespiegelt werden. Skaliert man mit "-1.0" entlang der Achse, die dem Normalenvektor des Spiegels entspricht und verschiebt sie auf die doppelte Distanz "Spiegel-Koordinatenursprung", wird die Szene reflektiert. Mittels glPush()/glPop() lässt sich danach der Ausgangszustand leicht wiederherstellen.

Dann kann man den Tiefenbuffer mit glClear(GL_DEPTH_BUFFER_BIT) auf Null setzen und den Spiegel selbst zeichnen. Ein idealer Spiegel wird ohne weitere Angaben gezeichnet. Da in der Praxis aber ein Teil des Lichtes absorbiert wird, kann die gespiegelte Ansicht über einen leicht geschwärzten Spiegel realisiert werden. Das geht z.B. mittels Blending, einem Alpha-Wert von 0.05 und der Funktion glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA).

Zum Schluss wird die originale, noch nicht gespiegelte Szene gezeichnet. Da der Color Buffer bereits komplett durch den vorhergehenden Schritt mit Farbwerten gefüllt ist, müssen alle Bereiche die nicht gespiegelt dargestellt sein dürfen, durch die unveränderte Ansicht überdeckt, also neu gezeichnet werden.

9.180 Wie kann ich eine eigene perspektivische Verzerrung nutzen ?

OpenGL multipliziert die Objektkoordinaten erst mit der ModelView Matrix, dann mit der Projection Matrix, um so die Clipping Koordinaten zu berechnen. Danach erfolgt eine Divison abhängig von der festgelegten Projektion, um daraus die normalisierten Device Koordinaten zu erhalten. Diese Division bewirkt, dass im Falle einer Zentralprojektion die entfernteren Objekte kleiner als die im Vordergrund wirken. Realisiert wird das dadurch, dass die XYZ Clipping Koordinaten durch ihre homogene Koordinate W geteilt werden:

    Yndc = Ycc/Wcc
    Zndc = Zcc/Wcc

Um nun eine eigene Perspektive zu definieren, muss man zunächst die homogene Clipping Koordinate W ermitteln. Das geht über den Feedback Buffer oder mittels glGetFloatv(GL_CURRENT_RASTER_POSITION). In beiden Fällen erhält man XYZ als Device und W als Clipping Koordinate.

Seite durchsuchen nach:

Fragen oder Ideen an:
Thomas.Kern@3Dsource.de
Linux-Counter
(C) Thomas Kern