Dritte Dimension

Beispiel

Visual Studio Projekt herunterladen

Unser erstes 3D-Projekt arbeitet mit einer Klasse CCube, die einen Würfel selbständig zeichnet und rotiert. In der Datei main.cpp gibt es einige Veränderungen gegenüber den bisherigen Beispielen, da wir mit einem Tiefenbuffer (z-Buffer) arbeiten und eine perspektivische Projektion verwenden.

Der Tiefenpuffer funktioniert so: Für jeden Bildpunkt (Pixel) unseres Ausgabefensters wird nicht nur die Farbe gespeichert, sondern auch der Abstand von der Kamera. Wird ein Pixel neu gezeichnet, prüft die Hardware, ob der neue Pixel vor, auf oder hinter einem an dieser Stelle davor gezeichneten Pixel liegt. Liegt unser neuer Bildpunkt hinter einem schon gezeichneten, wir er übergangen.

Eine Alternative zum Tiefenpuffer ist der sogenannte Painter's Algorithm. Dabei werden alle Objekte vor dem Zeichnen sortiert. Objekte, die weiter hinten liegen, werden zuerst gezeichnet, Objekte weiter vorne anschließend darüber. Der Painter's Algorithm hat Probleme mit Objekten, die sich durchschneiden, ermöglicht aber das korrekte Darstellen von transparenten Objekten.

main.cpp

In der main Funktion initialisieren wir den GLUT Display Mode zusätzlich mit dem Parameter GLUT_DEPTH.

    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);

Weiter unten schalten wir den Tiefentest an, damit der Tiefenpuffer auch verwendet wird:

    glEnable(GL_DEPTH_TEST);

Zusätzlich zum display- und idle-Callback verwenden wir in diesem Beispiel auch die Tastatureingaben. Durch die Zeile

    glutKeyboardFunc(keyboard);

wird unsere Funktion keyboard jedes Mal aufgerufen, wenn eine Taste gedrückt wurde. Die keyboard-Funktion selbst schließt das GLUT Fenster und beendet unsere Anwendung. Das ist notwendig, da wir mit dem Befehl

    glutFullScreen();

in den Vollbildmodus schalten und kein Button zum Schließen des Fensters mehr angezeigt wird.

Sehen wir uns noch an, wie die perspektivische Transformation in der transform-Funktion eingerichtet wird.

void transform(unsigned int width, unsigned int height)
{
    // OpenGL Transformationen initialisieren...
    // Ausgabefenstergröße setzen
    glViewport(0, 0, (GLint)width, (GLint)height);

    // Transformationen für 3D-Koordinaten bearbeiten
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // Kameraposition setzen
    gluLookAt(0.0, 0.0, 4.0,  // von wo
              0.0, 0.0, 0.0,  // nach wo
              0.0, 1.0, 0.0); // wo ist oben?

    // Perspektivische Projektion bearbeiten
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    // Verhältnis Breite zu Höhe (aspect) berechnen
    GLdouble aspect = (GLdouble)width / (GLdouble)height;
   
    // Kameraperspektive setzen
    gluPerspective(53,           // Öffnungswinkel vertikal (field of view)
                   aspect,       // aspect ratio setzen
                   0.001, 10.0); // nahe und ferne Begrenzungsebene setzen
}

Der Funktion wird die aktuelle Breite und Höhe unseres Fensters in den Parametern width und height übergeben. Zu den einzelnen Befehlen:

Die Funktion glViewport erlaubt uns, wenn gewünscht, nur in einen Teil des Ausgabefensters zu ziechnen. In unserem Fall möchten wir das ganze Fenster nutzen.

Danach setzen wir die Kameraposition mit gluLookAt. In unserem Fall bewegen wir die Kamera an die Position (0, 0, 4), richten sie auf den Ursprung (0, 0, 0) und geben weiterhin an, dass die positive y-Achse im Kamerabild nach oben zeigen soll. Der letzte Parameter bestimmt also die Drehung (roll) unserer Kamera.

Für die Kameratransformation arbeiten wir mit Welt- bzw. Objektkoordinaten (also mit der Modelview-Matrix), da sowohl unsere Objekte als auch die Kamera zusammen in diesem Koordinatensystem existieren.

Jetzt fehlt uns noch der Öffnungswinkel der Kamera, bzw. die Eigenschaften der Kameralinse. Diese setzen wir (in der Projektionsmatrix) über die Funtkion gluPerspective. In unserem Fall wählen wir einen vertikalen Öffnungswinkel von 53° mit quadratischen Pixeln (steuerbar durch den aspect Parameter).

Durch nahe und ferne Begrenzungsebenen (near and far plane) wird der minimale und maximale Abstand festgelegt, den zu zeichnende Pixelvon der Kamera haben dürfen. Beide Werte müssen positiv sein. Warum das Ganze?

a) Um die perspektivische Transformation eines Vektors zu erhalten, muss an einer bestimmten Stelle durch seine z-Koordinate geteilt werden. Wäre z Null, läge der Vektor also genau auf oder über der Kamera, müsste der Punkt ins unendliche transformiert werden. Das ist unmöglich. Wäre z kleiner als Null, ergäbe die Transformation einen gespiegelten (x,y)-Wert und Objekte hinter der Kamera würden sichtbar. Aus beiden Gründen besteht die Notwendigkeit für eine nahe, positive Begrenzungsebene.

b) Der Tiefenpuffer hat eine endliche Genauigkeit. Durch die Begrenzung der Gemoetrie auf einen weitesten Abstand von der Kamera kann der Tiefenpuffer in höherer Genauigkeit genutzt werden. Als Faustregel gilt für beide Werte also: Sie sollten so nah wie möglich an den wahren Objektgrenzen unserer Szene liegen.

CCube

Sehen wir uns die Datei cube.h genauer an. Die Klasse für unseren Würfel besitzt drei geschützte Attribute, die nur intern vom Würfel selbst angesprochen werden können:

    unsigned int m_divisions;       // Unterteilungen der Achsen
    float        m_rotation;        // Rotation des Würfels in Grad
    float        m_rotationTime;    // Rotationszeit des Würfels

m_divisions gibt an, wie oft der Würfel bei der Anzeige unterteilt wird, m_rotation ist die akuelle Rotation des Würfels um die z-Achse in Grad und m_rotationTime die Zeit, in der sich der Würfel ein Mal um die eigene Achse drehen soll.

Von Außen kann durch die öffentliche Funktion

    void update(float time);

indirekt auf den Rotationsparameter zugegriffen werden. Die update-Funktion wird von der display-Funktion in main.cpp mit einer angenommenen Bildwiederholrate von einer sechzigstel Sekunde aufgerufen. Wir werden noch bessere Methoden kennenlernen, die Zeit einer Animation oder Simulation mit der tatsächlich vergangenen Zeit zu füttern.

Die Implementierung der update-Funtkion In der Date cube.cpp sorgt dafür, dass die Rotation innerhalb von m_rotationTime genau 360° durchläuft:

    m_rotation += 360.0f * time / m_rotationTime;

Die render-Funtkion enthält zwei ineinander verschachtelte for-Schleifen, um ein Gitter von Linien zu erzeugen. Dabei werden zunächst zwei Koordinaten a und b zwischen -1 und 1 auf den Würfelflächen berechnet. Anschließend werden jeweils für die x- y- und z-Achsen des Würfels Linien von einer Würfelseite zur gegenüberliegenden gezogen.

Viel Spaß bei eigenen Formen...

zurück