NeHe - Lektion 34 - Schöne Landschaften mittels Height Mapping

Lektion 34



Willkomen zu einem weiteren aufregendem Tutorial! Der Code für dieses Tutorial wurde von Ben Humphrey geschrieben und basiert auf dem Framework aus Lektion 1. Von nun an, sollten Sie ein GL Experte sein {grins} und den Code in Ihren eigenen Basis-Code einzufügen, sollte ein Leichtes sein!

Dieses Tutorial bringt Ihnen bei, wie Sie ein cool aussehendes Terrain aus einer Height Map erzeugen. Für die unter Ihnen, die sich nichts unter einer Height Map vorstellen können, versuche ich eine grobe Erklärung abzugeben. Eine Height Map ist einfach... eine Ersetzung einer Oberfläche. Für die unter Ihnen, die sich immer noch den Kopf kratzen und sich selbst fragen "worüber redet der Kerl überhaup!?!"... Auf deutsch: unsere Height Map repräsentiert tiefe und hohe Punkte unserer Landschaft. Es liegt völlig bei Ihnen zu entscheiden, welche Schattierungen hohe Punkte und welche Schattierungen tiefe Punkte repräsentieren. Es ist auch wichtig zu erwähnen, dass Height Maps keine Bilder sein müssen... Sie können eine Height Map erzeugen, indem Sie einfach irgendwelche Daten verwenden. Sie können zum Beispiel einen Audio Stream verwenden um eine visuelle Height Map Repräsentation zu erzeugen. Wenn Sie immer noch verwirrt sind... lesesn Sie weiter... Sie werden es am Ende des Tutorials begriffen haben :)

#include <windows.h>                        // Header Datei für Windows
#include <stdio.h>                        // Header Datei für Standard Input/Output ( NEW )
#include <gl\gl.h>                        // Header Datei für die OpenGL32 Library
#include <gl\glu.h>                        // Header Datei für die GLu32 Library
#include <gl\glaux.h>                        // Header Datei für die Glaux Library

#pragma comment(lib, "opengl32.lib")                // Linke OpenGL32.lib
#pragma comment(lib, "glu32.lib")                // Linke Glu32.lib

Wir fangen damit an, einige wichtige Variablen zu definieren. MAP_SIZE ist die Dimension unserer Map. In diesem Tutorial wird die Map 1024x1024 groß sein. Die Schrittgröße STEP_SIZE ist die Größe eines jeden Quadrats, welches wir auf unserer Landschaft zeichnen. Indem wir die Schrittgröße verringern, wird unsere Landschaft detaillierter und weicher. Es ist wichtig zu erwähnen, dass, je kleiner die Schrittgröße, desto mehr Performance wird benötigt, insbesondere wenn Sie große Height Maps verwenden. HEIGHT_RATIO wird verwendet, um die Landschaft auf der Y-Achse zu skalieren. Je kleiner HEIGT_RATIO ist, desto flacher sind die Hügel. Je höher HEIGHT_RATIO, desto größer / sichtbarer sind die Hügel.

Später im Code werden Sie bRender entdecken. Wenn bRender auf True gesetzt ist (was es standardmäßig ist), werden wir solide Polygone zeichnen. Wenn wir bRender auf False setzen, wird die Landschaft als Drahtgittermodell gezeichnet.

#define        MAP_SIZE    1024                // Größe unsereR .RAW Height Map ( NEU )
#define        STEP_SIZE    16                // Breite und Höhe jedes Quadrats ( NEU )
#define        HEIGHT_RATIO    1.5f                // Verhältnis indem Y zu X und Z skaliert wird ( NEU )

HDC        hDC=NULL;                    // Privater GDI Device Context
HGLRC        hRC=NULL;                    // Permanenter Rendering Context
HWND        hWnd=NULL;                    // Enthält unser Fenster-Handle
HINSTANCE    hInstance;                    // Enthält die Instanz der Applikation


bool        keys[256];                    // Array das für die Tastatur Routine verwendet wird
bool        active=TRUE;                    // Fenster Aktiv Flag ist standardmäßig auf TRUE gesetzt
bool        fullscreen=TRUE;                // Fullscreen Flag ist standardmäßig auf TRUE gesetzt
bool        bRender = TRUE;                    // Polygon Flag ist standardmäßig auf TRUE gesetzt ( NEU )

Hier erstellen wir ein Byte-Array (g_HeightMap[]), das unsere Height Map Daten enthalten wird. Da wir eine .RAW-Datei lesen, die nur Werte zwischen 0 und 255 enthält, können wir die Werte als Höhen-Werte benutzen, mit 255 als höchsten Punkt und 0 als tiefsten Punkt. Wir erzeugen auch eine Variable namens scaleValue, um die gesamte Szene zu skalieren. Das gibt dem Benutzer die Möglichkeit heran und wegzuzoomen.

BYTE g_HeightMap[MAP_SIZE*MAP_SIZE];                // enthält die Height Map Daten ( NEU )

float scaleValue = 0.15f;                    // Skalierungswert für das Terrain ( NEU )

LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);        // Deklaration For WndProc

Der ReSizeGLScene() Code ist der selbe wie in Lektion 1 mit der Ausnahme, dass die entfernteste Distanz von 100.0f auf 500.0f geändert wurde.

GLvoid ReSizeGLScene(GLsizei width, GLsizei height)        // verändert die Größe und initialisiert das GL-Fenster
{
... SCHNITT ...
}

Der folgende Code lädt die .RAW DAtei. Nicht allzu komplex! Wir öffnen die Datei im binären Lesemodus. Wir überprüfen dann, ob die Datei gefunden wurden und das sie geöffnet werden konnte. Wenn es beim Öffnen der Datei irgend ein Problem gegeben haben, wird eine Fehlernachricht angezeigt.

// Lädt die .RAW Datei und speichert sie in pHeightMap
void LoadRawFile(LPSTR strName, int nSize, BYTE *pHeightMap)
{
    FILE *pFile = NULL;

    // öffne die Datei zum Lesen im binären Modus.
    pFile = fopen( strName, "rb" );

    // Überprüfe ob wir die Datei gefunden haben und ob wir sie öffnen konnten
    if ( pFile == NULL )
    {
        // Zeige Fehlernachricht an und stoppe die Funktion
        MessageBox(NULL, "Konnte die Height Map nicht finden!", "Error", MB_OK);
        return;
    }

Wenn wir bis hierher gekommen sind, können wir uns sicher sein, dass keine Probleme beim Öffnen der Datei aufgetreten sind, wir können die Daten jetzt lesen. Wir machen das mittels fread(). pHeightMap ist der Platz an dem die Daten gespeichert werden (Zeigt auf unser g_Heightmap Array). 1 ist die Anzahl der zu ladenden Elemente (1 Byte zur Zeit), nSize die maximale Anzahle an zu lesenden Elementen (die Bildgröße in Bytes - Breite des Bildes * Höhe des Bildes). Letztendlich ist pFile ein Zeiger auf unsere Datei-Struktur!

Nachdem wir die Daten eingelesen haben, überprüfen wir, ob irgendwelche Fehler aufgetreten sind. Wir speichern die Ergebnisse in result und überprüfen dann result. Wenn ein Fehler aufgetreten ist, zeigen wir eine Fehler-Nachricht an.

Als letztes schließen wir die Datei mit flcose(pFile) wieder.

    // Hier laden wir die .RAW Datei in unser pHeightMap Daten Array
    // Wir lesen nur '1' ein und die Größe ist (Breite * Höhe)
    fread( pHeightMap, 1, nSize, pFile );

    // Nachdem wir die Daten gelesen haben, ist es eine gute Idee, zu überprüfen, ob beim Lesen alles glatt verlief
    int result = ferror( pFile );

    // überprüfe, ob ein Fehler aufgetreten ist
    if (result)
    {
        MessageBox(NULL, "Daten konnten nicht geholt werden!!", "Error", MB_OK);
    }

    // Schließe die Datei
    fclose(pFile);
}

Der Init Code ist ziemlich einfach. Wir setzen die Hintergrundfarbe auf Schwarz, setzen Depth Testing, Polygon Smoothing, etc. Nach all dem laden wir unsere .RAW Datei. Um das zu machen, übergeben wir den Dateinamen ("Date/Terrain.raw"), die Dimensionen der .RAW Datei (MAP_SIZE * MAP_SIZE) und letztlich unser HeightMap Array (g_HeightMap) an LoadRawFile(). Damit springen wir in den Lade-Code von oben hinein. Die .RAW Datei wird geladen und die Daten werden in unserem Heightmap Array (g_HeightMap) gespeichert.

int InitGL(GLvoid)                        // Der ganze Setup Kram für OpenGL kommt hier rein
{
    glShadeModel(GL_SMOOTH);                // aktiviert weiches Shading (Smooth Shading)
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);            // Schwarzer Hintergrund
    glClearDepth(1.0f);                    // Depth Buffer Setup
    glEnable(GL_DEPTH_TEST);                // aktiviert Depth Testing
    glDepthFunc(GL_LEQUAL);                    // Die Art des auszuführenden Depth Test
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);    // wirklich nette Perspektiven Berechnungen

    // Hier lesen wir die Height Map aus der .RAW Datei ein und speichern sie in unserem
    // g_HeightMap Array. Außerdem übergeben wir die Größe der .RAW Datei (1024).

    LoadRawFile("Data/Terrain.raw", MAP_SIZE * MAP_SIZE, g_HeightMap);    // ( NEU )

    return TRUE;                        // Initialisierung war OK
}

Damit indexieren wir unser Height Map Array. Wann immer wir mit Arrays arbeiten, wollen wir sicher gehen, dass wir nicht die Grenzen überschreiten. Um sicher zu gehen, dass das nicht passiert, benutzen wir %. % verhindert, dass unsere x / y Werte außerhalb MAX_SIZE - 1 liegen.

Wir stellen sicher, dass pHeightMap auf gültige Daten zeigt und geben ansonsten 0 zurück.

Ansonsten geben wir den gespeicherten Wert bei x, y in unserer Heigt Map zurück. Sie sollten nun wissen, dass wir Y mit der Breite unseres Bildes MAP_SIZE multiplizieren um uns durch die Daten zu bewegen. Mehr darüber weiter unten!

int Height(BYTE *pHeightMap, int X, int Y)            // Dies gibt die Höhe von einem Height Map Index zurück 
{
    int x = X % MAP_SIZE;                    // Überprüfe unseren X Wert
    int y = Y % MAP_SIZE;                    // Überprüfe unseren Y Wert

    if(!pHeightMap) return 0;                // Stelle sicher, dass unsere Daten gültig sind

Wir müssen das einzelne Array wie ein 2D Array behandeln. Wir können die folgende Gleichung benutzen: index = (x + (y * arrayWidth) ). Damit nehme wir an, dass wir es wie: pHeightMap[x][y] visualisieren, ansonsten ist es das Gegenteil: (y + (x * arrayWidth) ).

Nun wo wir den korrekten Index haben, geben wir die Höhe an diesem Index zurück (Daten bei x, y in unserem Array).

    return pHeightMap[x + (y * MAP_SIZE)];            // Index in unser Height Array und gebe die Höhe zurück
}

Hier setzen wir die Farbe für einen Vertex, basierend auf dem Höhen Index. Um ihn dunkler zu machen, beginne ich mit -0.15f. Wir bekommen den Anteil der Farbe zwischen 0.0f und 1.0f indem wir die Höhe durch 256.0f dividieren. Wenn dort keine Daten vorhanden sind, kehrt die Funktion zurück, ohne eine Farbe zu setzen. Wenn alles in Ordnung ist, setzen wir die Farbe auf eine bläuliche Schattierung, in dem wir glColor3f(0.0f, fColor, 0.0f) verwenden. Versuchen Sie fColor zu den roten oder grünen Punkten zu bewegen, um die Farbe der Landschaft zu verändern.

void SetVertexColor(BYTE *pHeightMap, int x, int y)        // Dies setzt die Farbwerte für einen bestimmten Index
{                                // Abhängig vom Height Index
    if(!pHeightMap) return;                    // Stelle sicher, dass unsere Height Daten gültig sind

    float fColor = -0.15f + (Height(pHeightMap, x, y ) / 256.0f);

    // verbinde diese blaue Schattierung mit dem aktuellen Vertex
    glColor3f(0.0f, 0.0f, fColor );
}

Dies ist der Code, der unsere Landschaft eigentlich zeichnet. X und Y werden benutzt, um durch die Height Map Daten zu gehen. x, y und z werden verwendet, um die Quadrate zu rendern, aus denen unsere Landschaft besteht.

Wie immer überprüfen wir, ob unsere Height Map (pHeightMap) Daten enthält. Wenn nicht, kehren wir zurück, ohne etwas zu machen.

void RenderHeightMap(BYTE pHeightMap[])                // Damit rendern wir die Height Map als Quadrate
{
    int X = 0, Y = 0;                    // erzeuge einige Variablen, um das Array zu durchlaufen.
    int x, y, z;                        // erzeuge einige Variablen zur besseren Lesbarkeit

    if(!pHeightMap) return;                    // Stelle sicher, dass unsere Height Daten gültig sind

Da wir zwischen Linien und Quadraten hin und herwechseln können, überprüfen wir unseren Render-Status mit dem folgenden Code. Wenn bRender = True dann wollen wir Polygone rendern, ansonsten rendern wir Linien.

    if(bRender)                        // was wir rendern wollen
        glBegin( GL_QUADS );                // Rendere Polygons
    else
        glBegin( GL_LINES );                // Rendere Linien statt dessen


Als nächstes müssen wir unsere eigentliches Terrain aus der Height Map zeichnen. Um das zu machen, durchwandern wir einfach das Array der Höhen Daten und picken einige Höhen heraus um unsere Punkte zu zeichnen. Wenn wir sehen könnten, wie das geschieht, würde als erstes die erste Reihe (Y) gezeichnet werden, dann die Zeilen. BEachten Sie, dass wir STEP_SIZE haben. Das bestimmt wie detailiert unsere Height Map definiert ist. Je höher STEP_SIZE ist, umso klobiger würde unser Terrain aussehen und je kleiner der Wert wäre, umso runder (weicher) wird es. Wenn wir STEP_SIZE auf 1 setzen, würde es einen Vertex für jeden Pixel in unserer Height Map erzeugen. Ich habe 16 als eine gute Größe gewählt. Alles was wesentlich weniger ist, würde wahnsinnig langsam sein. Selbstverständlich können Sie die Nummer erhöhen, wenn Sie Beleuchtung einschalten. Die Vertex Beleuchtung würde die kantigen Formen etwas besser aussehen lassen. Anstatt Beleuchtung zu benutzen, verbinden wir einfach einen Farbwert mit jedem Polygon, um das Tutorial einfach zu halten. Je höher das Polygon ist, umso heller ist die Farbe.

    for ( X = 0; X <(MAP_SIZE-STEP_SIZE); X += STEP_SIZE )
        for ( Y = 0; Y <(MAP_SIZE-STEP_SIZE); Y += STEP_SIZE )
        {
            // ermittle die (X, Y, Z) Werte für den unteren linken Vertex
            x = X;
            y = Height(pHeightMap, X, Y );
            z = Y;

            // Setze den Farbwert des aktuellen Vertex
            SetVertexColor(pHeightMap, x, z);

            glVertex3i(x, y, z);            // Sende diesen Vertex an OpenGL zum rendern

            // ermittle die (X, Y, Z) Werte für den oberen linken Vertex
            x = X;
            y = Height(pHeightMap, X, Y + STEP_SIZE );
            z = Y + STEP_SIZE ;

            // Setze den Farbwert des aktuellen Vertex
            SetVertexColor(pHeightMap, x, z);

            glVertex3i(x, y, z);            // Sende diesen Vertex an OpenGL zum rendern

            // ermittle die (X, Y, Z) Werte für den oberen rechten Vertex
            x = X + STEP_SIZE;
            y = Height(pHeightMap, X + STEP_SIZE, Y + STEP_SIZE );
            z = Y + STEP_SIZE ;

            // Setze den Farbwert des aktuellen Vertex
            SetVertexColor(pHeightMap, x, z);

            glVertex3i(x, y, z);            // Sende diesen Vertex an OpenGL zum rendern

            // ermittle die (X, Y, Z) Werte für den unteren rechten Vertex
            x = X + STEP_SIZE;
            y = Height(pHeightMap, X + STEP_SIZE, Y );
            z = Y;

            // Setze den Farbwert des aktuellen Vertex
            SetVertexColor(pHeightMap, x, z);

            glVertex3i(x, y, z);            // Sende diesen Vertex an OpenGL zum rendern
        }
    glEnd();

Nachdem wir fertig sind, setzen wir die Farbe zurück auf helles Weiß mit einem Alpha-Wert von 1.0f. Wenn andere Objekte auf dem Screen waren, wollen wir diese nicht BLAU zeigen :)

    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);            // Resette die Farbe
}

Für die unter Ihnen, die gluLookAt() noch nicht verwendet haben, die Funktion positioniert die Kameraposition, Ihre Sicht und Ihren Vektor der nach oben zeigt. Hier setzen wir die Kamera etwas außerhalb, damit wir eine gute Sicht auf das Terrain haben. Um solch hohe Zahlen zu vermeiden, würden wir die Terrain Vertices durch eine Skalierungskonstante dividieren, wie wir es später bei glScalef() machen.

Die Werte bei gluLookAt() sind wie folgt: Die ersten drei Zahlen repräsentieren die Position der Kamera. So das die ersten drei Werte die Kamera 212 Einheiten auf der X-Achse, 60 Einheiten auf Y-Achse und 194 Einheiten auf der Z-Achse von unserem Mittelpunkt weg bewegen. Die nächsten 3 Werte repräsentieren wo unsere Kamera hinzeigt. In diesem Tutorial werden Sie bemerken, wenn Sie die Demo laufen lassen, dass wir etwas nach links schauen. Wir schauen auch etwas hinunter auf die Landschaft. 186 liegt links von 212 weshalb wir nach links schauen und 55 ist kleiner als 60, was uns den Eindruck vermittelt, dass wir höher als die Landschaft liegen und wir mit einer leichten Neigung auf diese herunterschauen. Der Wert 171 sagt wie weit das Objekt von der Kamera ist. Die letzten drei Werte teilen OpenGL mit, welche Richtung nach oben zeigt. Unsere Berge wandern auf der Y-Achse nach oben, weshalb wir den Wert auf der Y-Achse auf 1 setzen. Die anderen beiden Werte setzen wir auf 0.

gluLookAt kann anfangs recht einschüchternd wirken. Wenn Sie die grobe Erklärung eben gelesen haben, mag es sein, dass Sie immer noch irritiert sind. Mein Ratschlage ist es, dass Sie etwas mit den Werten herumspielen. Ändern Sie die Kameraposition. Wenn Sie beispielsweise die Y-Position der Kamera auf sagen wir 120 ändern, würden Sie mehr den oberen Teil der Landschaft sehen, da Sie ganz hinunter auf 55 sehen.

Ich bin mir nicht sicher, ob es Ihnen hilft, aber ich werde eins meiner oft kritisierten Real Life "Beispiel" Erklärung geben :) Sagen wir, Sie sind 6 Fuß und ein bißchen groß. Nehmen wir ebenso an, dass Ihre Augen sich bei der 6 Fuß Marke befinden (Ihre Augen repräsentieren die Kamera - 6 Fuß sind 6 Einheiten auf der Y-Achse). Wenn Sie nun vor einer Mauer stehen, die nur 2 Fuß hoch ist (2 Einheiten auf der Y-Achse), würde Sie auf die Mauer HINUNTER schauen und könnten den oberen Teil der Mauer sehen. Wenn die Mauer 8 Fuß hoch wäre, würden Sie nach OBEN schauen und Sie könnten NICHT auf den oberen Teil der Mauer schauen. Die Sicht würde sich abhängig davon, ob Sie rauf oder runter schauen würden, verändern (ob Sie nun größer oder kleiner als das Objekt sind, das Sie betrachten). Ich hoffe das macht wenigstens etwas Sinn!

int DrawGLScene(GLvoid)                        // Hier kommt der ganze Zeichnen-Kram hin
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // Lösche den Bildschirm und den Depth-Buffer
    glLoadIdentity();                    // Resette die Matrix

    //       Position     Sicht        Vektor der nach oben zeigt
    gluLookAt(212, 60, 194,  186, 55, 171,  0, 1, 0);    // dies bestimmt die Kamera Position und Sicht

Das wird unser Terrain herunterskalieren, so dass es etwas einfacher zu betrachten und nicht so groß ist. Wir können den scaleValue verändern, indem wir die Pfeil nach oben oder Pfeil nach unten Taste drücken. Sie werden bemerken, dass wir den Y scaleValue mit HEIGHT_RATIO multiplizieren. Das geschieht, damit das Terrain höher und detaillierter aussieht.

    glScalef(scaleValue, scaleValue * HEIGHT_RATIO, scaleValue);

Wenn wir die g_HeightMap Daten an unsere RenderHeightMap() Funktion übergeben, wird diese das Terrain als Quads zeichnen. Wenn Sie irgendwie Gebruach von dieser Funktion machen, wäre es eine gute Idee, einen (X, Y) Parameter einzuügen, um dort zu zeichnen oder benutzen Sie OpenGL's Matrix-Operationen (glTranslatef(), glRotatef(), etc) um das Land exakt dort zu positionieren, wo Sie es wollen.

    RenderHeightMap(g_HeightMap);                // Rendere die Height Map

    return TRUE;                        // Weiter geht's
}

Der KillGLWindow() Code ist der selbe wir in Lektion 1.

GLvoid KillGLWindow(GLvoid)                    // Entferne das Fenster korrekt
{
}

Der CreateGLWindow() Code ist der selbe wie in Lektion 1.

BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
}

Die einzige Änderung in WndProc() ist das Hinzufügen von WM_LBUTTONDOWN. Dadurch wird überprüft, ob die linke Maustaste gedrückt wurde. Falls ja, wechselt der Rendering-Status zwischen Polygon-Modus und Linien-Modus oder vom Linien-Modus zum Polygon-Modus.

LRESULT CALLBACK WndProc(    HWND    hWnd,            // Handle für dieses Fenster
                UINT    uMsg,            // Nachricht für dieses Fenster
                WPARAM    wParam,            // Weitere Nachrichten Informationen
                LPARAM    lParam)            // Weitere Nachrichten Informationen
{
    switch (uMsg)                        // Überprüfe auf Fenster-Nachrichten
    {
        case WM_ACTIVATE:                // Wenn Aktivierungs-Nachricht reinkommt
        {
            if (!HIWORD(wParam))            // Überprüfe Minimierungs-Status
            {
                active=TRUE;            // Programm ist aktiv
            }
            else
            {
                active=FALSE;            // Programm ist nicht länger aktiv
            }

            return 0;                // Kehre zurück zur Nachrichten-Schleife
        }

        case WM_SYSCOMMAND:                // hereinkommende System Befehle
        {
            switch (wParam)                // Überprüfe auf System Aufrufe
            {
                case SC_SCREENSAVE:        // versucht der Screensaver sich zu starten?
                case SC_MONITORPOWER:        // versucht der Monitor in den Energiesparmodus zu gehen?
                return 0;            // verhindere das
            }
            break;                    // beende
        }

        case WM_CLOSE:                    // Haben wir eine Nachricht zum Schließen erhalten?
        {
            PostQuitMessage(0);            // Sende eine Beenden-Nachricht
            return 0;                // Springe zurück
        }

        case WM_LBUTTONDOWN:                // haben wir einen linken Mausklick erhalten?
        {
            bRender = !bRender;            // Ändere Render-Status zwischen Gefüllt/Drahtgittermodell
            return 0;                // Springe zurück
        }

        case WM_KEYDOWN:                // Wird eine Taste gedrückt?
        {
            keys[wParam] = TRUE;            // Wenn ja, markiere sie mit TRUE
            return 0;                // Springe zurück
        }

        case WM_KEYUP:                    // Wurde eine Taste losgelassen?
        {
            keys[wParam] = FALSE;            // Wenn ja, markiere sie mit FALSE
            return 0;                // Springe zurück
        }

        case WM_SIZE:                    // ändere die Größe des Fenster
        {
            ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));    // LoWord=Breite, HiWord=Höhe
            return 0;                // Springe zurück
        }
    }

    // Übergebe alle nicht bearbeiteten Nachrichten an DefWindowProc
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

Keine großartigen Änderungen in diesem Codeabschnitt. Die einzige Änderung ist der Titel des Fensters. Alles andere bleibt beim selben, bis wir auf Tastendrücke überprüfen.

int WINAPI WinMain(    HINSTANCE    hInstance,        // Instanz
            HINSTANCE    hPrevInstance,        // vorherige Instanz
            LPSTR        lpCmdLine,        // Kommandozeilen Parameter
            int        nCmdShow)        // Fenster Anzeige Status
{
    MSG        msg;                    // Windows Nachrichten Struktur
    BOOL    done=FALSE;                    // Bool Variable um die Schleife zu beenden

    // Frage den Benutzer, in welchen Modus er starten will
    if (MessageBox(NULL,"Wollen Sie im Vollbildmodus starten?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
    {
        fullscreen=FALSE;                // Fenster-Modus
    }

    // erzeuge unser OpenGL Fenster
    if (!CreateGLWindow("NeHe & Ben Humphrey's Height Map Tutorial", 640, 480, 16, fullscreen))
    {
        return 0;                    // Beende, wenn Fenster nicht erzeugt wurde
    }

    while(!done)                        // Schleife die so lange läuft, bis done=TRUE
    {
        if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))    // Wartet eine Nachricht?
        {
            if (msg.message==WM_QUIT)        // Haben wir eine Nachricht zum beenden erhalten?
            {
                done=TRUE;            // Wenn ja done=TRUE
            }
            else                    // Wenn nicht, bearbeite die Fenster-Nachrichten
            {
                TranslateMessage(&msg);        // Übersetze die Nachricht
                DispatchMessage(&msg);        // bearbeite die Nachricht
            }
        }
        else                        // Wenn da keine Nachrichten sind
        {
            // Zeichne die Szene. Schau nach der ESC-Taste und Beendigungs-Nachrichten von DrawGLScene()
            if ((active && !DrawGLScene()) || keys[VK_ESCAPE])    // Acktiv? Wurde Signal zum Beenden gesendet?
            {
                done=TRUE;            // ESC oder DrawGLScene signalisiert uns, dass beendet werden soll
            }
            else if (active)            // es ist noch nicht Zeit, zum beenden, aktualisiere den Screen
            {
                SwapBuffers(hDC);        // Swap Buffers (Double Buffering)
            }

            if (keys[VK_F1])            // Wurde F1 gedrückt?
            {
                keys[VK_F1]=FALSE;        // Wenn ja, setze Taste auf FALSE
                KillGLWindow();            // Kill unser aktuelles Fenster
                fullscreen=!fullscreen;        // Wechsel zwischen Fullscreen und Fester-Modus
                // Erzeuge unser OpenGL Fenster erneut
                if (!CreateGLWindow("NeHe & Ben Humphrey's Height Map Tutorial", 640, 480, 16, fullscreen))
                {
                    return 0;        // Beenden, wenn das Fenster nicht erzeugt wurde
                }
            }

Der folgende Code lässt Sie scaleValue inkrementieren und dekrementieren. Indem Sie die Pfeil-Hoch-Taste drücken, wird scaleValue inkrementiert, was die Landschaft größer macht. Indem Sie die Pfeil-Runter-Taste drücken, dekrementieren Sie scaleValue und die Landschaft wird kleiner.

            if (keys[VK_UP])            // wurde die Pfeil-Hoch-Taste gedrückt?
                scaleValue += 0.001f;        // Inkrementiere den Skalierungswert um heran zu zoomen

            if (keys[VK_DOWN])            // Wurde die Pfeil-Runter-Taste gedrückt?
                scaleValue -= 0.001f;        // dekrementiere den Skalierungswert um raus zu zoomen
        }
    }

    // Shutdown
    KillGLWindow();                        // Kill das Fenster
    return (msg.wParam);                    // Beende das Programm
}

Das ist alles um schöne Height Mapped Landschaften zu erstellen. Ich hoffe, Sie wissen Ben's Arbeit zu schätzen. Wie immer, wenn Sie Fehler in dem Tutorial oder Code finden, mailen Sie mir bitte und ich werde versuchen, dass Problem zu korrigieren / das Tutorial verbessern.

Wenn Sie den Code verstanden haben, spielen Sie etwas herum damit. Etwas was Sie versuchen könnten, wäre das Hinzufügen eines kleinen Balls, der über die Oberfläche rollt. Sie wissen bereits die Höhe jeder Sektion der Landschaft, weshalb das Hinzufügen des Balls kein Problem sein sollte. Ander Dinge zum ausprobieren: Erzeugen Sie die Height Map manuell, machen Sie eine scrollende Landschaft, fügen Sie Farben zur Landschaft hinzu, um Spitzen mit Schnee / Wasser / etc. zu repräsentieren, fügen Sie Texturen hinzu, benutzen Sie einen Plasma-Effekt um die Landschaft kontinuierlich zu ändern. Die Möglichkeiten sind endlos :)

Ich hoffe, Sie haben das Tutorial genossen. Sie können Ben's Seite unter http://www.GameTutorials.com besuchen.

Ben Humphrey (DigiBen)

Jeff Molofee (NeHe)

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

* DOWNLOAD Borland C++ Builder 6 Code für diese Lektion. ( Conversion by Christian Kindahl )
* 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 Dan )
* DOWNLOAD JoGL Code für diese Lektion. ( Conversion by Abdul Bezrati )
* DOWNLOAD LCC Win32 Code für diese Lektion. ( Conversion by Robert Wishlaw )
* DOWNLOAD Linux/GLX Code für diese Lektion. ( Conversion by Patrick Schubert )
* DOWNLOAD Mac OS X/Cocoa Code für diese Lektion. ( Conversion by Bryan Blackburn )
* DOWNLOAD Visual Studio .NET Code für diese Lektion. ( Conversion by Grant James )



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