NeHe - Lektion 42 - Mehrere Viewports

Lektion 42



Willkommen zu einem weiteren spaßgeladenem Tutorial. Diesmal werde ich Ihnen zeigen, wie Sie mehrere Viewports in einem einzelnen Fenster anzeigen können. Die Viewports passen sich korrekt in der Größe an, wenn man sich im Fenster-Modus befindet. Zwei der Fenster werden Beleuchttung verwenden. Eins der Fenster ist Ortho und drei im Perspetkiven Modus. Um dieses Tutorial spannend zu halten, werden Sie auch etwas über den Labyrinth-Code erfahren, der im Demo verwendet, wie man eine auf eine Textur rendert (schon wieder) und wie man die aktuellen Fenster-Masen ermittelt.

Wenn Sie dieses Tutorial erst einmal verstanden haben, ist es ein Leichtes, Split-Screen-Spiele oder 3D Applikationen mit mehreren Ansichten zu erstellen. Nachdem das gesagt wurde, tauchen wir nun in den Code ein!!!

Sie können entweder den letzten NeHeGL Code oder den IPicture Code als Basecode verwenden. Die erste Datei, in die wir ein Blick werfen müssen, ist NeHeGL.cpp. Drei Abschnitte haben sich im Code geändert. Ich werde nur die Codeabschnitte aufführen, die sich geändert haben.

Die erste und wichtigste Sache, die sich geändert hat, ist ReshapeGL(). Hier haben wir die Screen-Dimensionen initialisieren (unseren Main Viewport). Unsere gesamte Haupt-Viewport-Initialisierung wird nun in unserer Zeichnen-Schleife gemacht. Deshalb initialsieren wir hier nur das Hauptfenster.

void ReshapeGL (int width, int height)                                // Passe das Fenster an, wenn es bewegt oder in der Größe verändert wurde
{
    glViewport (0, 0, (GLsizei)(width), (GLsizei)(height));                    // Resette den aktuellen Viewport
}

Als nächstes fügen wir etwas Code ein, um die Fenster-Nachricht zum Löschen des Hintergrunds (WM_ERASEBKGND) zu überwachen. Wenn diese aufgerufen wird, fangen wir sie ab und geben 0 zurück. Das verhindert, dass der Hintergrund gelöscht wird und erlaubt es und, unser Hauptfenster in der Größe zu ändern ohne dieses nervende Flackern, was Sie normalerweise sehen würden. Wenn Sie nicht sicher sind, was ich meine, entfernen Sie: case WM_ERASEBKGND: und return 0; und Sie können es selbst vergleichen.

// bearbeite Fenster Nachrichten Callbacks
LRESULT CALLBACK WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    DWORD        tickCount;                                // enthält aktuellen Tick Zähler
    __int64        timer;                                    // wird für den Tick Zähler verwendet

    // ermittle den Fenster Context
    GL_Window* window = (GL_Window*)(GetWindowLong (hWnd, GWL_USERDATA));

    switch (uMsg)                                        // werte die Fenster Nachrichten aus
    {
        case WM_ERASEBKGND:                                // überprüfe, ob Windows versucht den Hintergrund zu löschen
            return 0;                                // gebe 0 zurück (verhindert ein Flackern während der Größenveränderung eines Fensters)

In WinMain müssen wir den Fenstertitel ändern und die Auflösung rauf auf 1024x768 setzen. Wenn Ihr Monitor aus irgendwelchen Gründen nicht 1024x768 unterstüzt, können Sie eine niedrigere Auflösung wählen und auf etwas Details verzichten.

// Programm Einstiegspunkt (WinMain)
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    Application            application;                        // Applikations Struktur
    GL_Window            window;                            // Fenster Struktur
    Keys                keys;                            // Key Struktur
    BOOL                isMessagePumpActive;                    // Message Pumpe aktiv?
    MSG                msg;                            // Fenster Nachrichten Struktur

    // Fülle Applikations-Daten
    application.className = "OpenGL";                            // Applikations Klassen Name
    application.hInstance = hInstance;                            // Applikations Instanz

    // Fülle Window
    ZeroMemory (&window, sizeof (GL_Window));                        // stelle sicher, dass der Speicher mit Nullen gefüllt ist
    window.keys            = &keys;                        // Fenster Key Struktur
    window.init.application        = &application;                        // Fenster Applikation

    // Fenstertitel
    window.init.title        = "Lesson 42: Multiple Viewports... 2003 NeHe Productions... Building Maze!";

    window.init.width        = 1024;                            // Fenster Breite
    window.init.height        = 768;                            // Fenster Höhe
    window.init.bitsPerPixel    = 32;                            // Bits Pro Pixel
    window.init.isFullScreen    = TRUE;                            // Fullscreen? (Setze auf TRUE)

Nun ist es an der Zeit lesson42.cpp (den Hauptcode) zu modifizieren...

Wir fangen damit an, die Standard Header und Library-Dateien zu inkludieren.

#include <windows.h>                                        // Header Datei für Windows
#include <gl\gl.h>                                        // Header Datei für die OpenGL32 Library
#include <gl\glu.h>                                        // Header Datei für die GLu32 Library

#include "NeHeGL.h"                                        // Header Datei für NeHeGL

#pragma comment( lib, "opengl32.lib" )                                // suche nach OpenGL32.lib während des Linkens
#pragma comment( lib, "glu32.lib" )                                // suche nach GLu32.lib während des Linkens

GL_Window*    g_window;                                    // Window Struktur
Keys*        g_keys;                                        // Tastatur

Wir initialisieren nun alle globale Variablen, die wir während des Programms verwenden wollen.

mx und my verwenden wir, um zu wissen, in welchem Raum wir innerhalb des Labyrinths sind. Jeder Raum ist durch eine Mauer getrennt (weshalb Räume zwei verschiedene Einheiten sind).

width und height werden dazu verwendet um unsere Textur zu erstellen. Sie sind ebenfalls die Breite und Höhe des Labyrinths. Der Grund, warum Labyrinth und Textur die selbe Größe haben sollen, ist, weil somit jeder Pixel, der im Labyrinth gezeichnet wird, ein Pixel in der Textur ist. Ich möchte Breite und Höhe auf 256 setzen, obwohl es dann etwas länger dauert, das Labyrinth zu erzeugen.

Wenn Ihre Grafikkarte größere Texturen händeln kann, versuchen Sie die Werte um eine Potenz zu zwei zu erhöhen (256, 512, 1024). Passen Sie auf, dass Sie die Werte nicht allzu sehr erhöhen. Wenn das Hauptfenster 1024 Pixel breit ist und jeder Viewport halb so groß wie das Hauptfenster ist, so sollten Sie ihre Textur höchstens so breit machen: Höhe des Fensters / 2. Wenn Sie ihre Textur 1024 Pixel breit machen, aber Ihr Viewport nur 512 Pixel breit ist, wird jeder zweite Pixel überlappt, da es nicht genügend Platz für alle Pixel der Textur im Viewport gibt. Das Selbe gilt für die Höhe. Sie sollte: Höhe des Fensters / 2 sein. Natürlich müssen Sie auf eine Potenz zu 2 runden.

// benutzerdefinierte Variablen
int    mx,my;                                            // Generelle Schleifen (werden zum Suchen verwendet)

const    width    = 128;                                        // Labyrinth Breite (muss eine Potenz von 2 sein)
const    height    = 128;                                        // Labyrinth Höhe (muss eine Potenz von 2 sein)

done wird dazu benutzt, um zu verfolgen, ob das Labyrinth beendet wurde. Mehr darüber später.

sp wird dazu verwendet, um zu verfolgen, ob die Leertaste gedrückt wird. Indem die Leertaste gedrückt wird, wird das Labyrinth resettet un das Programm fängt an, ein neues Labyrinth zu zeichnen. Wenn wir nicht überprüfen würden, ob die Leertaste gedrückt ist, würde das Labyrinth mehrere Male restettet werden, während der Zeit, wo die Leertaste gedrückt wird. Diese Variable stellt sicher, dass das Labyrinth nur einmal resettet wird.

BOOL    done;                                            // Flag welches uns wissen lässt, wann das Labyrinth fertig ist
BOOL    sp;                                            // Leertaste gedrückt?

r[4] wird vier zufällige Rotwerte enthalten, g[4] vier zufällige Grünwerte und b[4] 4 zufällige Blauwerte. Diese Werte werden dazu verwendet, jedem Viewport eine andere Farbe zuzuweisen. Die erste Viewport-Farbe wird r[0],g[0],b[0] sein. Achten Sie darauf, dass jede Farbe ein Byte-Wert ist und kein Fliesskommawert, wie es die meisten von Ihnen gewohnt ist. Der Grund, warum ich ein Byte verwende, ist, dass es einfacher ist, einen zufälliger Wert zwischen 0 und 255 zu erzeugen, als einen Wert zwischen 0.0f und 1.0f.

tex_data zeigt auf unsere Texturdaten.

BYTE    r[4], g[4], b[4];                                    // zufällige Farben (4 Rot, 4 Grün, 4 Blau)
BYTE    *tex_data;                                        // enthält unsere Texturdaten

xrot, yrot und zrot werden dazu verwendet, um unsere 3D Objekte rotieren zu lassen.

Zu letzt initialisieren wir ein Quadric Objekt, so dass wir einen Zylinder und Sphere mittels gluCylinder und gluSphere zeichnen können. Wesentlich einfacher, als die Objekte manuell zu zeichnen.

GLfloat    xrot, yrot, zrot;                                    // werden för die Rotation der Objekte verwendet

GLUquadricObj *quadric;                                        // das Quadric Objekt

Der folgende Codeteil wird ein Pixel in unserer Textur am Ort dmx, dmy auf ein helles Weiß setzen. tex_data ist der Zeiger auf die Texturdaten. Jeder Pixel besteht aus 3 Bytes (1 für Rot, 1 für Grün und 1 für blau). Der Offset für Rot ist 0. Und der Ort des Pixels, den wir modifizieren wollen, ist dmx (die X-Position) plus dmy (die Y-Position) multipliziert mit der Breite unserer Textur und das Endergebniss multipliziert mit 3 (3 Bytes pro Pixel).

Die erste folgende Zeile setzt die rote (0) Farbe auf 255. Die zweite Zeile setzt die Grün (1) Farbe auf 255 und die letzte Zeile setzt die blau (2) Farbe auf 255. Das Endresultat ist ein heller weißer Pixel an dmx, dmy.

void UpdateTex(int dmx, int dmy)                                // akutalisiere Pixel dmx, dmy auf der Textur
{
    tex_data[0+((dmx+(width*dmy))*3)]=255;                            // Setze roten Pixel Wert auf volle Helligkeit
    tex_data[1+((dmx+(width*dmy))*3)]=255;                            // Setze grünen Pixel Wert auf volle Helligkeit
    tex_data[2+((dmx+(width*dmy))*3)]=255;                            // Setze blauen Pixel Wert auf volle Helligkeit
}

Reset erledigt ein paar Dinge. Es wird unsere Textur gelöscht, ein paar zufällige Farben jedem Viewport zugewiesen, alle Wände im Labyrinth resettet und ein zufälliger Startpunkt für die Labyrinth-Erzeugung erzeugt.

Die erste Codezeile übernimmt das Löschen. tex_data zeigt auf unsere Textur-Daten. Wir müssen width (Breite unserer Textur) multipliziert mit height (Höhe unserer Textur) multipliziert mal 3 (rot, grün, blau) löschen. Durch das Löschen setzen wir in diesem Speicherbereich alle Bytes auf 0. Wenn alle 3 Farbwerte auf 0 gesetzt werden, ist unsere Textur komplett schwarz!

void Reset (void)                                        // Resettet das Labyrinth, Farben, Anfangspunkt, etc
{
    ZeroMemory(tex_data, width * height *3);                        // Fülle den Texturspeicher mit nullen

Nun müssen wir eine zufällige Farbe jedem Viewport zuweisen. Für diejenigen, die es noch nicht wirklich wissen, random ist nicht wirklich so zufällig! Wenn Sie ein einfache Programm erzeugen, welches 10 zufällige Ziffern ausgibt, würden Sie jedes Mal, wenn Sie das Programm laufen lassen würden, die selben Zahlen erhalten. Damit ein wenig mehr Zufall im Spiel ist, können wir einen zufälligen Startpunkt angeben. Auch hier gilt, wenn Sie 1 angeben, würden wir immer wieder die selben Zahlen erhalten. Wenn wir allerdings srand auf den aktuellen Tick-Zähler setzen (was irgend eine Zahl sein kann), erhalten wir jedes Mal andere Zahlen.

Wir haben 4 Viewports, weshalb wir eine Schleife benötigen, die von 0 bis 3 läuft. Wir weisen jedem Farbwert (rot, grün, blau) einen zufälligen Wert zwischen 128 und 255 zu. Der Grund warum ich 128 addiere, ist der, dass ich helle Farben möchte. Mit einem minimal Wert von 0 und einem maximal Wert von 255, wäre 128 gerade mal 50% Helligkeit.

    srand(GetTickCount());                                    // versuche etwas mehr Zufälligkeit ins Spiel zu bringen

    for (int loop=0; loop// Schleife in der wir 4 zufällige Farbwerte zuweisen
    {
        r[loop]=rand()%128+128;                                // wähle einen zufälligen Rotwert (hell)
        g[loop]=rand()%128+128;                                // wähle einen zufälligen Grünwert (hell)
        b[loop]=rand()%128+128;                                // wähle einen zufälligen Blauwert (hell)
    }

Als nächstes weisen wir einen zufälligen Anfangspunkt zu. Wir müssen in einem Raum starten. Jeder zweiter Pixel in der Textur ist ein Raum. Um sicherzugehen, dass wir in einem Raum anfangen und nicht einer Wand, wählen wir eine zufällige Zahl zwischen 0 der Hälfte der Breite der Textur und multiplizieren sie mit 2. Auf diese Weise können wir Nummern wie 0, 2, 4, 6, 8, etc. erhalten, was bedeutet, dass wir immer einen zufälligen Raum erhalten und niemals auf einer Wand landen, welche 1, 3, 5, 7, 9, etc. sein würde.

    mx=int(rand()%(width/2))*2;                                // wähle eine neue zufällige X Position aus
    my=int(rand()%(height/2))*2;                                // wähle eine neue zufällige Y Position aus
}

Die erste Zeile der Initialisierung ist sehr wichtig. Sie alloziert genügend Speicher, um unsere Textur zu enthalten (width*height*3). Wenn Sie kein Speicher allozieren, wird ihr System aller Wahrscheinlichkeit nach abstürzen!

BOOL Initialize (GL_Window* window, Keys* keys)                            // Jeglich GL & Benutzer Initialisierung kommt hier hin
{
    tex_data=new BYTE[width*height*3];                            // alloziere Speicher für unsere Textur

    g_window    = window;                                // Fenster Werte
    g_keys        = keys;                                    // Tasten Werte

Direkt nachdem wir den Speicher für unsere Textur alloziert haben, rufen wir Reset( ) auf. Reset wird unsere Textur löschen, unsere Farben setzen und einen zufälligen Startpunkt für's Labyrinth erzeugen.

Wenn erst einmal alles resettet wurde, müssen wir unsere Anfangstextur initialisieren. Die ersten 2 Texturparameter CLAMP (schneidet) unsere Textur in der Spanne von [0,1] (ab). Das verhindert Artifakte wenn wir ein einzelnes Bild auf ein Objekt mappen. Um zu sehen, warum das wichtig ist, versuchen Sie mal die beiden Codezeilen zu entfernen. Ohne das abschneiden werden Sie ein dünne Linie am oberen Rand und an der rechten Seite der Textur sehen. Die Linien erscheinen, weil die lineare Filterung versucht, die gesamte Textur zu glätten, inklusive der Kanten. Wenn Pixel zu nah am Rand gezeichnet werden, erscheint eine Linie auf der gegenüberliegenden Seite der Textur.

Wir werden lineare Filterung verwenden, damit alles etwas weicher aussieht. Es liegt an Ihnen was für eine Filterung Sie verwenden. Wenn es zu langsam läuft, versuchen Sie als Filter GL_NEAREST zu verwenden.

Zu letzt erzeugen wir eine RGB 2D Textur mit tex_data (der Alpha Kanal wird nicht verwendet).

    Reset();                                        // rufe Reset auf, um unsere Anfangstextur zu erzeugen, etc.

    // Anfang der Benutzer Initialisierung
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, tex_data);

Wir setzen unsere Löschfarbe auf schwarz und löschen Depth auf 1.0f. Wir setzen unsere Depth-Funktion auf 'kleiner als oder gleich' und aktivieren dann Depth-Testing.

Die Aktivierung von GL_COLOR_MATERIAL lässt Sie Objekte mittels glColor einfärben, wenn die Beleuchtung aktiviert ist. Diese Methode wird 'Color Tracking' genannt und häufig Performance-fressenden glMaterial-Aufrufen vorgezogen. Ich habe viele E-Mails erhalten, wie man die Farbe eines Objektes ändert... hoffentlich ist diese Information nützlich! Für diejenigen, die mir gemailt haben und mich gefragt haben, warum Texturen in ihrem Projekt eigenartige Färbungen mit der aktuellen glColor( ) hatten... stellen Sie sicher, dass Sie nicht GL_COLOR_MATERIAL aktiviert haben!

* Dank an James Trotter für die korrekte Erklärung wie GL_COLOR_MATERIAL funtkioniert. Ich hatte gesagt, es lässt Sie ihre Texturen einfärben... Allerdings lässt es Sie tatsächlich ihre Objekte einfärben.

Zu letzt aktivieren wir 2D Texturmapping.

    glClearColor (0.0f, 0.0f, 0.0f, 0.0f);                            // schwarzer Hintergrund
    glClearDepth (1.0f);                                    // Depth Buffer Setup

    glDepthFunc (GL_LEQUAL);                                // die Art des Depth Testing
    glEnable (GL_DEPTH_TEST);                                // aktiviere Depth Testing

    glEnable(GL_COLOR_MATERIAL);                                // aktiviere Color Material (erlaubt es uns Texturen einzufärben)

    glEnable(GL_TEXTURE_2D);                                // aktiviere Textur-Mapping

Der folgende Code erzeugt einen Zeiger auf unser Quadric Objekt. Wenn wir den Zeiger erst einmal haben, setzen wir die Normalenvektoren auf 'smooth' und wir verlangen Texturkoordinaten. Dadurch wird die Beleuchtung korrekt funktionieren und unsere Textur wird automatisch auf jedes Quadric Objekt gemapped!

    quadric=gluNewQuadric();                                // erzeuge einen Zeiger auf das Quadric Objekt
    gluQuadricNormals(quadric, GLU_SMOOTH);                            // erzeuge weiche (smooth) Normalenvektoren
    gluQuadricTexture(quadric, GL_TRUE);                            // erzeuge Texturkoordinaten

Light 0 ist aktiviert, allerdings wird es nichts machen, bis wir die Beleuchtung einschalten. Light 0 ist, für diejenigen die es noch nicht wissen, ein vordefiniertes Licht, welches direkt auf den Screen zeigt. Ist ganz praktisch, wenn Sie das Licht nicht selbst initialisieren wollen.

    glEnable(GL_LIGHT0);                                    // aktiviere Light0 (Standard GL Light)

    return TRUE;                                        // gebeTRUE zurück (Initialisierung war erfolgreich)
}

Wann immer Sie Speicher allozieren, ist es wichtig, dass Sie ihn auch wieder freigeben. Die folgende Codezeile gibt den Speicher wieder frei, wenn Sie zwischen Fullscreen / Fenster-Modus hin und herschalten oder wenn das Programm beendet wird.

void Deinitialize (void)                                    // Jegliche Benutzer DeInitialization kommt hier hin
{
    delete [] tex_data;                                    // Lösche unsere Texturdaten (gebe Speicher frei)
}

Update( ) ist der Ort wo die meiste Labyrinth-Erzeugung vorgenommen wird, zusammen mit der Überwachung der Tastendrücken, Rotation, etc.

Wir müssen eine Variable namens dir initialisieren, welche wir dazu verwenden, um uns zufällig nach oben, rechts, runter oder links zu bewegen.

Wir schauen, ob die Leertaste gedrückt wurde. Wenn ja, und sie nicht gedrückt bleibt, resetten wir das Labyrinth. Wenn die Taste nicht mehr gedrückt wird, setzen wir sp auf FALSE, so dass unser Programm weiß, dass sie nicht länger mehr gedrückt wird.

void Update (float milliseconds)                                // führe Bewegungsaktualisierung hier aus
{
    int    dir;                                        // wird die aktuelle Richtung enthalten

    if (g_keys->keyDown [VK_ESCAPE])                            // wurde ESC gedrückt?
        TerminateApplication (g_window);                        // Beende das Programm

    if (g_keys->keyDown [VK_F1])                                // wurde F1 gedrückt?
        ToggleFullscreen (g_window);                            // wechsel Fullscreen-Modus

    if (g_keys->keyDown [' '] && !sp)                            // Überprüfe, ob die Leertaste gedrückt wurde
    {
        sp=TRUE;                                    // wenn ja, setze sp auf TRUE (Leertaste gedrückt)
        Reset();                                    // wenn ja, rufe Reset auf und starte ein neues Labyrinth
    }

    if (!g_keys->keyDown [' '])                                // überprüfe, ob die Leertaste wieder losgelassen wurde
        sp=FALSE;                                    // wenn ja, setze sp auf FALSE (Leertaste wurde losgelassen)

xrot, yrot und zrot werden um die Anzahl der vergangenen Millisekunden, multipliziert mit einer kleinen Fließkommazahl, inkrementiert. Das erlaubt es uns, die Objekte auf der X-Achse, Y-Achse und Z-Achse zu rotieren. Jede Variable wird um ein andere Zahl inkrementiert, was die Rotation etwas netter anzuschauen macht.

    xrot+=(float)(milliseconds)*0.02f;                            // inkrementiere Rotation auf der X-Achse
    yrot+=(float)(milliseconds)*0.03f;                            // inkrementiere Rotation auf der Y-Achse
    zrot+=(float)(milliseconds)*0.015f;                            // inkrementiere Rotation auf der Z-Achse

Der folgende Code überprüft, ob wir fertig mit dem Zeichnen des Labyrinths sind. Wir fangen damit an, done auf TRUE zu setzen. Wir iterieren dann durch jeden Raum, um zu sehen, ob irgend ein Raum noch nicht besucht wurde und wenn ein Raum noch nicht besucht wurde, setzen wir done auf FALSE.

Wenn tex_data[((x+(width*y))*3)] gleich null ist, wissen wir, dass ein Raum noch nicht besucht wurde und wir noch kein Pixel darin gezeichnet haben. Wenn dort ein Pixel wäre, wäre der Wert gleich 255. Wir überprüfen nur den Rotwert des Pixels, da wir wissen, dass dieser entweder gleich 0 (leer) oder gleich 255 (aktualisiert) ist.

    done=TRUE;                                        // Setze  done auf True
    for (int x=0; x<width; x+=2)                                // iteriere durch alle Räume
    {
        for (int y=0; y<height; y+=2)                            // auf der X und Y-Achse
        {
            if (tex_data[((x+(width*y))*3)]==0)                    // Wenn der aktuelle Textur-Pixel (Raum) leer ist
                done=FALSE;                            // müssen wir done auf False setzen (weil wir noch nicht fertig sind)
        }
    }

Nachdem wir alle Räume überprüft haben und done dann immer noch gleich TRUE ist, wissen wir, dass das Labyrinth komplett ist. SetWindowsText wird den Titel des Fensters ändern. Wir ändern den Titel so, dass er "Maze Complete!" (Labyrinth komplett!) anzeigt. Wir pausieren dann für 5000 Millisekunden, so dass die betrachtende Person Zeit hat, den Titel zu lesen (oder wenn wir im Fullscreen sind, dass man sieht, dass die Animation gestoppt wurde). Nach 5000 Millisekunden ändern wir den Titel wieder in "Building Maze!" und resetten das Labyrinth (und beginnen den gesamten Prozess von vorne).

    if (done)                                        // wenn done gleich True ist, gab es keine unbesuchten Räume
    {
        // Zeige eine Nachricht im Fenstertitel an, pausiere etwas und erzeuge ein neues Labyrinth!
        SetWindowText(g_window->hWnd,"Lesson 42: Multiple Viewports... 2003 NeHe Productions... Maze Complete!");
        Sleep(5000);
        SetWindowText(g_window->hWnd,"Lesson 42: Multiple Viewports... 2003 NeHe Productions... Building Maze!");
        Reset();
    }

Der folgende Code sieht vielleicht etwas verwirrend aus, ist aber nicht wirklich schwierig zu verstehen. Wir überprüfen, ob der Raum rechts vom aktuellen Raum bereits besucht wurde oder ob unsere aktuelle Position an der äußersten rechten Grenze des Labyrinths ist (wenn es also keine Räume mehr rechts von uns gibt). Wir überprüfen, ob der Raum links von uns bereits besucht wurde oder ob wir an der äußersten linken Grenze des Labyrinths sind (es gibt keine Räume mehr links von uns). Wir überprüfen, ob dre Raum unter uns bereits besucht wurde oder ob wir am untersten Rand sind (es gibt keine Räume mehr unter uns) und zu letzt überprüfen wir, ob der Raum über uns bereits besucht wurde oder ob wir am obersten Rand sind (keine Räume über uns).

Wenn der rote Pixelwert eines Raums gleich 255 ist, wissen wir, dass der Raum bereits besucht wurde (der er mit UpdateTex aktualisiert wurde). Wenn mx (aktuelle X Position) kleiner als 2 ist, wissen wir, dass wir fast am äußersten linken Rand des Screens sind und wir nicht weiter nach links gehen können.

Wenn wir gefangen wurden oder wir in der Nähe einer Grenze sind, geben wir mx und my einen zufälligen Wert. Wir überprüfen dann, ob dieser Wert an der Position bereits besucht wurde. Wenn nicht, holen wir neue zufällige mx, my Werte, bis wir eine Zelle finden, die bereits besucht wurde. Wir wollen, dass die neuen Zweige aus den alten Pfaden hervorgehen, weshalb wir nach einem alten Pfad suchen, von dem wir aus weitermachen können.

Um den Code bei einem Minimum zu belassen, überprüfe ich nicht, ob mx-2 kleiner 0 ist. Wenn Sie 100% Fehlerüberprüfung haben wollen, können Sie diesen Codeabschnitt modizfizieren, um zu verhindern, dass Speicher überprüft wird, der nicht zur aktuellen Textur gehört.

    // Überprüfe, dass wir nicht gefangen sind (wir uns nirgends mehr hinbewegen können)
    if (((mx>(width-4) || tex_data[(((mx+2)+(width*my))*3)]==255)) && ((mx(height-4) || tex_data[((mx+(width*(my+2)))*3)]==255)) && ((my// wenn wir gefangen sind
        {
            mx=int(rand()%(width/2))*2;                        // ermittle eine neue zufällige X Position
            my=int(rand()%(height/2))*2;                        // ermittle eine neue zufällige Y Position
        }
        while (tex_data[((mx+(width*my))*3)]==0);                    // ermittle ständig neue zufällige Positionen, bis wir
    }                                            // eine finden, die bereits markiert wurde (sicherer Anfangspunkt)

Die folgende erste Zeile weist dir einen zufälligen Wert zwischen 0 und 3 zu. Wir werden diesen Wert verwenden, um unseren Labyrinth mitzuteilen, ob nach rechts, oben, links oder unten gezeichnet werden soll.

Nachdem wir eine zufällige Richtung erhalten haben, überprüfen wir, ob der Wert von dir gleich 0 (nach rechts bewegen) ist. Wenn dem so ist und wir noch nicht am äußersten rechten Rand des Labyrinths sind, reissen wir die Mauer zwischen den beiden Räumen mit UpdateTex(mx+1,my) ein und bewegen uns dann in den neuen Raum, indem wir mx um 2 inkrementieren.

    dir=int(rand()%4);                                    // Wähle eine zufällige Richtung

    if ((dir==0) && (mx// Wenn die Richtung gleich 0 (rechts) ist und wir nicht am äußersten rechten Rand sind
    {
        if (tex_data[(((mx+2)+(width*my))*3)]==0)                    // und der Raum rechts von uns, noch nicht besucht wurde
        {
            UpdateTex(mx+1,my);                            // aktualisiere die Textur
            mx+=2;                                    // und bewege uns nach rechts (ein Raum nach rechts)
        }
    }

Wenn der Wert von dir gleich 1 (runter) ist und wir noch nicht am untersten Rand sind, überprüfen wir, ob der Raum bereits besucht wurde. Wenn er noch nicht besucht wurde, reißen wir die Wand zwischen den zwei Räumen (dem aktuellen Raum und dem Raum darunter) ein und bewegen uns in den neuen Raum, indem wir my um 2 inkrementieren.

    if ((dir==1) && (my// Wenn die Richtung gleich 1 (runter) ist und wir noch nicht ganz unten sind
    {
        if (tex_data[((mx+(width*(my+2)))*3)]==0)                    // und wenn der Raum unter uns noch nicht besucht wurde
        {
            UpdateTex(mx,my+1);                            // aktualisiere unsere Textur
            my+=2;                                    // und bewege uns nach unten (ein Raum unter uns)
        }
    }

Wenn der Wert von dir gleich 2 (links) ist und wir noch nicht am äußersten linken Rand sind, überprüfen wir, ob der Raum links von uns bereits besucht wurde. Wenn er noch nicht besucht wurde, reissen wir die Wand zwischen den beiden Räumen ein (aktueller Raum und der linke Raum) und bewegen uns in den neuen Raum, indem wir mx um 2 dekrementieren.

    if ((dir==2) && (mx>=2))                                // Wenn die Richtung 2 (links) ist und wir noch nicht am äußersten linken Rand sind
    {
        if (tex_data[(((mx-2)+(width*my))*3)]==0)                    // und wenn der Raum zur Linken noch nicht besucht wurde
        {
            UpdateTex(mx-1,my);                            // aktualisiere die Textur
            mx-=2;                                    // bewege nach links (Raum zur Linken)
        }
    }

Wenn der Wert von dir gleich 3 (oben) ist und wir nicht am obersten Rand sind, überprüfen wir, ob der Raum über uns bereits besucht wurde. Wenn er noch nicht besucht wurde, reissen wir die Wand zwischen den beiden Räumen (aktueller Raum und der darüber) ein und bewegen uns dann in den neuen Raum, indem wir my um 2 dekrementieren.

    if ((dir==3) && (my>=2))                                // Wenn die Richtung gleich 3 (oben) ist und wir noch nicht ganz oben sind
    {
        if (tex_data[((mx+(width*(my-2)))*3)]==0)                    // und wenn der Raum über uns noch nicht besucht wurde
        {
            UpdateTex(mx,my-1);                            // aktualisiere die Textur
            my-=2;                                    // bewege nach oben (Raum über uns)
        }
    }

Nachdem wir uns in den neuen Raum bewegt haben, müssen wir diesen als besucht markieren. Wir machen das, indem wir UpdateTex( ) mit der aktuellen mx, my Position aufrufen.

    UpdateTex(mx,my);                                    // akktualisiere aktuellen Raum
}

Wir fangen diesen Codeabschnitt mit etwas neuem an... Wir müssen wissen, wie groß das aktuelle Fenster ist, um die Viewports richtig in ihrer Größe anzupassen. Um die aktuelle Fenster Breite und Höhe zu ermitteln, müssen wir den linken wert des Fensters ermitteln, den rechten Wert des fensters, den oberen Wert und den unteren Wert des Fensters. Nachdem wir diese Werte haben, können wir die Breite berechnen, indem wir den linken Wert des Fensters vom rechten Wert subtrahieren. Wir können die Höhe ermitteln, indem wir den oberen Wert vom unteren wert des Fensters subtrahieren.

Wir können den linken, rechten, oberen und unteren Wert mittels RECT ermitteln. RECT enthält die Koordinaten eines Rechtecks. Die linke, rechte, obere und untere Koordinate, um genau zu sien.

Um die Koordinaten unseres Screens zu ermitteln, verwenden wir GetClientRect( ). Der erste Parameter, den wir übergeben, ist unser aktuelles Fenster-Handle. Der zweite Parameter, ist die Struktur, die die zurückgelieferten Informationen enthalten wird (rect).

void Draw (void)                                        // unsere Zeichnen Routine
{
    RECT    rect;                                        // enthält die Koordinaten eines Rechtecks

    GetClientRect(g_window->hWnd, &rect);                            // ermittle Fenster Dimensionen
    int window_width=rect.right-rect.left;                            // berechne die Breite (rechte Seite - linke Seite)
    int window_height=rect.bottom-rect.top;                            // berechne die Höhe (unten - oben)

Wir müssen die Textur in jedem Frame aktualisieren und wir wollen sie aktualisieren, bevor wir die texturierte Szene zeichnen. Der schnellste Weg, eine Textur zu aktualisieren, ist der Befehl glTexSubImage2D( ). glTexSubImage2D mappt alles oder Teile einer Textur aus dem Speicher auf ein Objekt auf dem Screen. Im folgeden Code teilen wir ihm mit, dass wir eine 2D Textur verwenden. Das Detaillevel ist 0, wir wollen keinen x (0) oder y (0) Offset haben. Wir wollen die gesamte Breite der Textur und die gesamte Höhe verwenden. Die Daten sind im GL_RGB Format und vom Typen GL_UNSIGNED_BYTE. tex_date enthält die Daten, die wir mappen wollen.

Das ist eine schnelle Möglichkeite eine Textur zu aktualisieren, ohne sie neu zu erzeugen. Es ist ebenfalls wichtig anzumerken, dass dieser Befehl keine Textur ERZEUGT. Sie müssen eine Textur erzeugen, bevor Sie diesen Befehl verwenden können, um sie zu aktualisieren!

    // aktualisiere unsere Textur... Das ist der Schlüssel der Programm-Geschwindigkeit... Viel schneller als die Textur jedes Mal neu zu erzeugen
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, tex_data);

Diese Codezeile ist sehr wichtig. Sie wird den gesamten Screen löschen. Da wir den Screen nur löschen wollen BEVOR ALLE 4 Viewports gezeichet werden und bevor jeder Viewport gezeichnet wird, müssen wir vor der Hauptschleife löschen, die dann die 4 Viewports zeichnet! Beachten Sie auch, dass wir zur Zeit nicht den Depth Buffer löschen. Dieser wird später, bevor jede Szene gezeichnet wird, gelöscht! Es ist SEHR wichtig, dass Sie den Screen einmal löschen, und dann den Depth Buffer löschen, bevor Sie jeden Viewport zeichnen.

    glClear (GL_COLOR_BUFFER_BIT);                                // lösche den Screen

Nun zu Haupt-Zeichnen-Schleifen. Wir wollen 4 verschiedene Viewports zeichnen, weshalb wir eine Schleife von 0 bis 3 durchlaufen.

Als erstes setzen wir die Farbe des aktuellen Viewports, mittels glColor3ub(r,g,b). Das mag neu für Sie sein. Es ist das Selbe wir glColor3f(r,g,b), allerdings werden unsigned Bytes anstatt von Fließkommawerten verwendet. Erinnern Sie sich daran, dass ich vorher gesagt habe, es sei leichter zufällige Werte zwischen 0 und 255 als Farbe zuzuweisen. Da wir nun so große Werte für jede Farbe haben, ist dies der Befehl, den wir benötigen, um die Farben richtig zu setzen.

glColor3f(0.5f,0.5f,0.5f) ist 50% Helligkeit für rot, grün und blau. glColor3ub(127,127,127) ist ebenfalls 50% Helligkeit für rot, grün und blau.

Wenn loop gleich 0 ist, würden wir r[0],g[0],b[0] auswählen. Wenn loop gleich 1 ist, würden wir r[1],g[1],b[1] auswählen. Auf diese Wiese hat jede Szene seine eigene zufällige Farbe.

    for (int loop=0; loop// Schleife um unsere 4 Ansichten zu zeichnen
    {
        glColor3ub(r[loop],g[loop],b[loop]);                        // weise Farbe der aktuelle Sicht zu

Bevor wir irgend etwas zeichnen können, müssen wir den aktuellen Viewport setzen. Wenn loop gleich 0 ist, zeichnen wir den ersten Viewport. Wir wollen diesen Viewport auf der linken Hälfte des Screens (0) haben und zwar in der oberen Ecke (window_height/2). Wir wollen, dass die Breite des Viewports die Hälfte des Hauptfensters (window_width/2) ist und die Höhe soll gleich der Hälfte der Höhe des Hauptfensters (window_height/2) sein.

Wenn das Hauptfenster 1024x768 ist, hätten wir einen Viewport bei 0,384 mit einer Breite von 512 und einer Höhe von 384.

Dieser Viewport würde wie folgt aussehen:



Nachdem wir den Viewport gesetzt haben, wählen wir die Projektions-Matrix aus, resetten diese und setzen dann unsere 2D Orhto Sicht. Wir wollen, dass die Ortho Sicht den gesamten Viewport erfasst. Deshalb übergeben wir einen links-Wert von 0 und einen rechts-Wert von window_width/2 (selbe Breite wie der Viewport selbst). Wir weisen ebenso einen unteren Wert von window_height/2 zu und einen oberen Wert von 0. Dies gibt uns die gesamte Höhe des Viewports.

Oben links unserer Ortho-Ansicht wird 0,0 sein. Unten rechts unserer Ortho-Ansicht wird window_width/2, window_height/2 sein.

        if (loop==0)                                    // wenn wir die erste Szene zeichnen
        {
            // setze den Viewport nach oben links. Er wird die Hälfte der Screen Breite und Höhe einnehmen
            glViewport (0, window_height/2, window_width/2, window_height/2);
            glMatrixMode (GL_PROJECTION);                        // wähle die Projektions Matrix aus
            glLoadIdentity ();                            // Resette die Projektions Matrix
            // Setze Ortho Modus, der 1/4 des Screens ausfüllt (Größe eines Viewports)
            gluOrtho2D(0, window_width/2, window_height/2, 0);
        }

Wenn loop gleich 1 ist, zeichnen wir den zweiten Viewport. Er wird auf der rechten Hälfte des Screens erscheinen, in der oberen Hälfte (des Hauptfensters). Die Breite und Höhe wird die selbe sein, wie beim ersten Viewport. Der einzige Unterschied ist der erste Parameter von glViewport( ) welcher gleich window_width/2 ist. Damit teilen wir unserem Programm mit, dass wir den Viewport von der Hälfte des Hauptfensters aus starten wollen.

Der zweite Viewport würde wie folgt aussehen:



Erneut wählen wir die Projektions-Matrix aus und resetten sie, aber diesmal setzen wir eine perspektivische Sicht, mit einem 45 Grad Sichtfeld und einem Near-Value von 0.1f und einem Far-Value von 500.0f.

        if (loop==1)                                    // wenn wir die zweite Szene zeichnen
        {
            // Setze den Viewport nach oben rechts. Er wird die Hälfte der Screen-Breite und -Höhe einnehmen
            glViewport (window_width/2, window_height/2, window_width/2, window_height/2);
            glMatrixMode (GL_PROJECTION);                        // wähle die Projektions-Matrix aus
            glLoadIdentity ();                            // Resette die Projektions-Matrix
            // Setze den perspektivischen Modus, damit er 1/4 des Screens abdeckt (Größe des Viewports)
            gluPerspective( 45.0, (GLfloat)(width)/(GLfloat)(height), 0.1f, 500.0 );
        }

Wenn loop gleich 2 ist, zeichnen wir den dritten Viewport. Er wird unten rechts im Hauptfenster erscheinen. Die Breite und Höhe wird die selbe sein, wie beim ersten und zweiten Viewport. Der einzige Unterschied zum zweiten Viewport ist der zweite Parameter von glViewport( ), welcher nun gleich 0 ist. Damit teilen wir unserem Programm mit, dass der Viewport in der unteren rechte Hälfte des Hauptfensters gezeichnet wird.

Der dritte Viewport würde wie folgt aussehen:



Wir setzen eine perspektivische Sicht genauso wie wir es für den zweiten Viewport gemacht haben.

        if (loop==2)                                    // wenn wir die dritte Szene zeichnen
        {
            // setzen wir den Viewport nach unten rechts.  Er wird die Hälfte des Screens und der Höhe einnehmen
            glViewport (window_width/2, 0, window_width/2, window_height/2);
            glMatrixMode (GL_PROJECTION);                        // wähle die Projektions Matrix aus
            glLoadIdentity ();                            // Resette die Projektions Matrix
            // Setze Perspektivischen Modus, damit 1/4 des Screens ausgefüllt ist (Größe eines Viewports)
            gluPerspective( 45.0, (GLfloat)(width)/(GLfloat)(height), 0.1f, 500.0 );
        }

Wenn loop gleich 3 ist, zeichnen wir den letzten Viewport (viewport 4). Er wird unten links im Hauptfenster dargestellt. Die Breite und Höhe wir die selbe sein wie der erste, zweite und dritte Viewport. Der einzige Unterschied ist der erste Parameter von glViewport( ), welcher nun 0 ist. Damit teilen wir dem Programm mit, dass wir den Viewport unten links im Hauptfenster haben wollen.

Der vierte Viewport würde wie folgt aussehen:



Wir setzen eine perspektivische Sicht genauso wie wir es für den zweiten Viewport gemacht haben.

        if (loop==3)                                    // wenn wir die vierte Szene zeichnen
        {
            // setze den Viewport nach unten links.  Er wird die Hälfte der Bildschirm Breite und Höhe einnehmen
            glViewport (0, 0, window_width/2, window_height/2);
            glMatrixMode (GL_PROJECTION);                        // wähle die Projektions Matrix aus
            glLoadIdentity ();                            // Resette die Projektions Matrix
            // Setze Perspektivischen Modus, damit 1/4 des Screens ausgefüllt ist (Größe eines Viewports)
            gluPerspective( 45.0, (GLfloat)(width)/(GLfloat)(height), 0.1f, 500.0 );
        }

Der folgende Code wählt die Modelview Matrix aus, resettet sie und löscht dann den Depth Buffer. Wir löschen den Depth Buffer für jeden gezeichneten Viewport. Beachten Sie, dass wir nicht die Screen Farbe löschen. Nur den Depth Buffer! Wenn Sie den Depth Buffer nicht löschen, werden Sie Objektteile verschwinden sehen, etc. Defintiv nicht schön!

        glMatrixMode (GL_MODELVIEW);                            // wähle die Modelview Matrix aus
        glLoadIdentity ();                                // Resette die Modelview Matrix

        glClear (GL_DEPTH_BUFFER_BIT);                            // lösche Depth Buffer

Das erste Bild das wir zeichnen, ist ein flacher 2D texturierter Quad. Der Quad wird im Ortho Modus gezeichnet und den gesamten Viewport ausfüllen. Da wir den Ortho-Modus verwenden, gibt es keine 3te Dimension, weshalb wir auch nicht auf der Z-Achse translatieren müssen.

Erinnern Sie sich daran, dass die obere linke Ecke des ersten Viewports 0,0 ist und unten rechts ist windod_width/2, window_height/2. Was bedeutet, dass unsere obere rechte Ecke des Quads bei window_width/2,0 ist. Die obere linke Ecke ist bei 0,0, die untere linke bei 0, window_height/2 und die untere rechte bei window_width/2, window_height/2. Beachten Sie, dass wir im Ortho-Modus mit Pixeln anstatt mit Einheiten arbeiten können (abhängig davon, wie der Viewport initialisiert wurde).

        if (loop==0)                                    // Zeichnen wir das erste Bild? (Original Textur... Ortho)
        {
            glBegin(GL_QUADS);                            // fange an einen einzelnen Quad zu zeichnen
                // Wir füllen den gesamten 1/4 Abschnitt mit einem einzelnen texturierten Quad.
                glTexCoord2f(1.0f, 0.0f); glVertex2i(window_width/2, 0              );
                glTexCoord2f(0.0f, 0.0f); glVertex2i(0,              0              );
                glTexCoord2f(0.0f, 1.0f); glVertex2i(0,              window_height/2);
                glTexCoord2f(1.0f, 1.0f); glVertex2i(window_width/2, window_height/2);
            glEnd();                                // fertig mit dem Zeichnen des texturierten Quads
        }

Das zweite Bild das wir zeichnen, ist eine weiche Sphere mit Beleuchtung. Der zweite Viewport ist perspektivisch, weshalb wir als erstes uns 14 Einheiten in den Screen hinein bewegen müssen. Wir rotieren dann unser Objekt auf der X-Achse, Y-Achse und Z-Achse.

Wir aktivieren Beleuchtung, zeichnen unsere Sphere und deaktivieren Beleuchtung. Die Sphere hat einen Radius von 4 Einheiten mit 32 Slices und 32 Stacks. Wenn Sie etwas herumspielen möchten, versuchen Sie die Anzahl der Stacks oder Slices niedriger anzusetzen. Indem Sie die Anzahl der Stacks / Slices reduzieren, reduzieren Sie die 'Weichheit' der Sphere.

Texturkoordinaten werden automatisch erzeugt!

        if (loop==1)                                    // zeichnen wir das zweite Bild?  (3D Texturierte Sphere... Perspectivisch)
        {
            glTranslatef(0.0f,0.0f,-14.0f);                        // bewege 14 Einheiten in den Screen hinein

            glRotatef(xrot,1.0f,0.0f,0.0f);                        // Rotiere um xrot auf der X-Achse
            glRotatef(yrot,0.0f,1.0f,0.0f);                        // Rotiere um yrot auf der Y-Achse
            glRotatef(zrot,0.0f,0.0f,1.0f);                        // Rotiere um zrot auf der Z-Achse

            glEnable(GL_LIGHTING);                            // aktiviere Beleuchtung
            gluSphere(quadric,4.0f,32,32);                        // zeichne eine Sphere
            glDisable(GL_LIGHTING);                            // deaktiviere Beleuchtung
        }

Das dritte Bild wird genauso gezeichnet wie das erste Bild, aber es wird mit Perspektive gezeichnet. Es wird um einen Winkel geneigt und rotiert (oh ja!).

Wir bewegen uns 2 Einheiten in den Screen hinein und neigen den Quad nach hinten um 45 Grad. Das lässt die obere Hälfte des Quads weiter weg von uns erscheinen und die untere Hälfte näher zu uns!

Wir rotieren dann auf der Z-Achse, damit der Quad rotiert und zeichnen den Quad. Wir müssen die Texturkoordinaten manuell setzen.

        if (loop==2)                                    // Zeichnen wir das dritte Bild?  (Textur geneigt um einen Winkel... Perspektivisch)
        {
            glTranslatef(0.0f,0.0f,-2.0f);                        // bewege 2 Einheiten in den Screen hinein
            glRotatef(-45.0f,1.0f,0.0f,0.0f);                    // kippe den Quad um 45 Grad nach hinten.
            glRotatef(zrot/1.5f,0.0f,0.0f,1.0f);                    // Rotiere um zrot/1.5 auf der Z-Achse

            glBegin(GL_QUADS);                            // fange an einen einzelnen Quad zu zeichnen
                glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, 0.0f);
                glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, 0.0f);
                glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f);
                glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 0.0f);
            glEnd();                                // fertig mit dem Zeichnen des texturierten Quads
        }

Wenn wir das 4te Bild zeichnen, bewegen wir uns 7 Einheiten in den Screen hinein. Wir rotieren dann das Objekt auf der X-Achse, Y-Achse und Z-Achse.

Wir aktivieren Beleuchtung, damit das Objekt eine nette Schattierung erhält und translatieren dann -2 Einheiten auf der Z-Achse. Der Grund warum wir das machen, ist der, dass das Objekt um seinen Mittelpunkt und nicht um eins seiner Enden rotieren soll. Der Zylinder ist 1.5 Einheiten breit an einem Ende, 1.5 Einheiten breit am anderen Ende, er hat eine Länge von 4 Einheiten und besteht aus 32 Scheiben (gestappelte Felder) und 16 Stacks (längliche Felder).

Damit wir auch wirklich um den Mittelpunkt rotieren, müssen wir um die Hälfte der Länge translatieren. Die Hälfte von 4 ist 2!

Nachdem wir translatiert, rotiertund noch mehr translatiert haben, zeichnen wir den Zylinder und deaktivieren dann die Beleuchtung.

        if (loop==3)                                    // Zeichnen wir das vierte Bild?  (3D texturierter Zylinder... Perspektivisch)
        {
            glTranslatef(0.0f,0.0f,-7.0f);                        // bewege 7 Einheiten in den Screen hinein
            glRotatef(-xrot/2,1.0f,0.0f,0.0f);                    // Rotiere um -xrot/2 auf der X-Achse
            glRotatef(-yrot/2,0.0f,1.0f,0.0f);                    // Rotiere um -yrot/2 auf der Y-Achse
            glRotatef(-zrot/2,0.0f,0.0f,1.0f);                    // Rotiere um -zrot/2 auf der Z-Achse

            glEnable(GL_LIGHTING);                            // aktiviere Beleuchtung
            glTranslatef(0.0f,0.0f,-2.0f);                        // Translatiere -2 auf der Z-Achse (damit der Zylinder um seinen Mittelpunkt und nicht um eines seiner Enden rotiert)
            gluCylinder(quadric,1.5f,1.5f,4.0f,32,16);                // Zeichne einen Zylinder
            glDisable(GL_LIGHTING);                            // Deaktiviere Beleuchtung
        }
    }

Als letztes müssen wir die Rendering Pipeline flushen.

    glFlush ();                                        // Flushe die GL Rendering Pipeline
}

Hoffentlich hat dieses Tutorial alle Fragen beantwortet, die Sie über multiple Viewports hatten. Der Code ist nicht allzu schwer zu verstehene. Der Code ist fast identisch mit dem Standard Basecode. Das einzige was sich wirklich geändert hat, ist das Viewport-Setup, welches nun in der Haupt-Zeichnen-Schleife gemacht wird, der Screen wird gelöscht bevor die Viewports gezeichnet werden und der Depth Buffer wird selbst gelöscht.

Sie können den Code benutzen, um verschiedene Bilder anzuzeigen, die alle in ihrem eigenen Viewport laufen oder Sie können den Code verwenden, um verschiedene Ansichten eines bestimmten Objekts anzuzeigen. Was Sie mit diesem Code machen, liegt an Ihnen.

Ich hoffe, Ihr habt das Tutorial genossen.... Wenn Sie irgendwelche Fehler im Code finden oder Sie denken, dieses Tutorial sogar besser machen könnten, lassen Sie es mich wissen.

Jeff Molofee (NeHe)

* DOWNLOAD Visual C++ Code für diese Lektion.

* DOWNLOAD Borland C++ Builder 6 Code für diese Lektion. ( Conversion by Le Thanh Cong )
* DOWNLOAD Code Warrior 5.3 Code für diese Lektion. ( Conversion by Scott Lupton )
* DOWNLOAD Delphi Code für diese Lektion. ( Conversion by Michal Tucek )
* DOWNLOAD Dev C++ Code für diese Lektion. ( Conversion by Victor Andrée )
* DOWNLOAD Euphoria Code für diese Lektion. ( Conversion by Evan Marshall )
* DOWNLOAD Linux/SDL Code für diese Lektion. ( Conversion by Evik )
* DOWNLOAD Mac OS X/Cocoa Code für diese Lektion. ( Conversion by Brian Holley )
* DOWNLOAD Python Code für diese Lektion. ( Conversion by Brian Leair )
* DOWNLOAD Visual Studio .NET Code für diese Lektion. ( Conversion by Joachim Rohde )


* DOWNLOAD Lektion 42 - Multi Window Code für diese Lektion by Marcel Laverdet



Deutsche Übersetzung: Joachim Rohde
Der original Text ist hier zu finden.
Die original OpenGL Tutorials stammen von NeHe's Seite.