NeHe - Lektion 17 - 2D Texturierte Fonts

Lektion 17



Dieses Tutorial kommt von NeHe & Giuseppe D'Agata...

Ich weiß, dass wahrscheinlich jeder genug von Fonts hat. Die Text Tutorials, die ich bisher gemacht habe, zeigen nicht nur Text an, sie zeigen 3D Text an, texturierten Text und können Variablen händeln. Aber was ist, wenn Sie Ihr Projekt auf eine andere Maschine portieren, die keine Bitmap oder Outline Fonts unterstützt?

Vielen Dank an Giuseppe D'Agata, dass wir ein weiteres Font Tutorial haben. Wenn Sie sich an das erste Font Tutorial erinnern, da habe ich erwähnt, Texturen als Buchstaben auf den Screen zu zeichnen. Normalerweise, wenn Sie Texturen verwenden, um Text auf den Screen zu bringen, starten Sie das Grafikbearbeitungsprogramm Ihrer Wahl, wählen einen Font aus und geben die Buchstaben oder den Satz ein, den Sie anzeigen lassen wollen. Sie speichern dann das Bitmap und laden es in Ihrem Programm als Textur. Nicht sehr effizient für ein Programm, dass viel Text benötigt oder wo sich der Text ständig ändert!

Dieses Programm benutzt nur EINE Textur, um jeden der 256 verschiedenen Zeichen auf dem Screen darzustellen. Behalten Sie im Hinterkopf, dass Ihr durchschnittliches Zeichen nur 16 Pixel breit und ungefähr 16 Pixel hoch ist. Wenn Sie die Standard 256x256 Textur nehmen, ist es recht einfach zu sehen, dass Sie 16 Buchstaben in der Breite unterkriegen und Sie können insgesamt 16 Reihen haben. Wenn Sie eine genauere Erklärung brauchen: Die Textur ist 256 Pixel breit, ein Zeichen ist 16 Pixel breit. 256 dividiert durch 16 ist 16 :)

Nun... Lassen Sie uns ein Demo für 2D texturierte Fonts machen! Dieses Programm erweitert den Code aus Lektion 1. Im ersten Abschnitt des Programms inkludieren wir die math und stdio Bibliotheken. Wir benötigen die Math Bibliothek, um unsere Buchstaben auf dem Bildschirm mittels SIN und COS zu bewegen und wir brauchen die stdio Bibliothek, um sicher zu gehen, dass die Bitmaps die wir verwenden wollen, tatsächlich existieren, bevor wir daraus Texturen machen.

#include <windows.h>                                // Header Datei für Windows
#include <math.h>                                // Header Datei fürWindows Math Library        ( HINZUGEFÜGT )
#include <stdio.h>                                // Header Datei für Standard Input/Output    ( HINZUGEFÜGT )
#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

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 standardmäßig auf TRUE gesetzt
bool    fullscreen=TRUE;                            // Fullscreen Flag ist standardmäßig auf TRUE gesetzt

Wir fügen eine Variable names base ein, die auf unsere Display Listen zeigen wird. Außerdem fügen wir texture[2] ein, um die zwei Texturen zu speichern, die wir erzeugen werden. Textur 1 wird die Font-Textur sein und Textur 2 wird eine Bump Textur sein, die dazu verwendet wird, um unser einfaches 3D Objekt zu erzeugen.

Wir fügen die Variable loop ein, welche zum Ausführen von Schleifen verwendet wird. Letztendlich fügen wir noch cnt1 und cnt2 ein, die wir zum Bewegen des Textes über den Screen verwenden werden und zum einfachen rotieren unserer 3D Objekte.

GLuint    base;                                    // Basis Display Liste für den Font
GLuint    texture[2];                                // Speicherplatz für unsere Font Textur
GLuint    loop;                                    // die Schleifen Variable

GLfloat    cnt1;                                    // erster Zähler zum bewegen des Textes und zur Färbung
GLfloat    cnt2;                                    // zweiter Zähler zum bewegen des Textes und zur Färbung

LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);                // Deklaration fürWndProc

Nun zum Textur-Lade-Code. Es ist genau der Selbe wie in den vorherigen Textur-Mapping Tutorials.

AUX_RGBImageRec *LoadBMP(char *Filename)                    // Lädt ein Bitmap
{
    FILE *File=NULL;                            // Datei Handle
    if (!Filename)                                // gehe sicher, dass ein Dateiname übergeben wurde
    {
        return NULL;                            // wenn nicht, gebe NULL zurück
    }
    File=fopen(Filename,"r");                        // überprüfe, ob die Datei existiert
    if (File)                                // Existiert die Datei?
    {
        fclose(File);                            // Schließe das Handle
        return auxDIBImageLoad(Filename);                // Lädt das Bitmap und gibt einen Zeiger zurück
    }
    return NULL;                                // Wenn das Laden fehl schlug, gebe NULL zurück
}

Der folgende Code hat sich auch nur sehr wenig verändert, im Vergleich zu vorherigen Tutorials. Wenn Sie sich nicht sicher sind, was die folgenden Zeilen machen, gehen Sie zurück und schauen Sie nach.

Beachten Sie, dass TextureImage[ ] 2 rgb Bilder speichern wird. Es ist sehr wichtig den Code, der für das Laden und speichern unserer Texturen zuständig ist, nochmals zu überprüfen. Eine falsche Zahl könnte ein Speicherleck oder einen Crash verursachen!

int LoadGLTextures()                                // Lade Bitmaps und kopvertiere in Texturen
{
    int Status=FALSE;                            // Status Indikator
    AUX_RGBImageRec *TextureImage[2];                    // erzeugt Speicherplatz für die Texturen

Die nächste Zeile ist die wichtigste Zeile, die Sie beachten müssen. Wenn Sie die 2 mit irgend einer anderen Zahl ersetzen, werden Sie mächtig Probleme bekommen. Überprüfen Sie es also lieber noch mal! Diese Zahl sollte mit der übereinstimmen, die Sie auch beim deklarieren von TextureImages[ ] verwendet haben.

Die zwei Texturen, die wir laden werden, sind font.bmp (unser Font) und bumps.bmp. Die zweite Textur kann mit irgend einer Textur Ihrer Wahl ersetzt werden. Ich war nicht gerade kreativ, weshalb die Textur vielleicht etwas eintönig ist.

    memset(TextureImage,0,sizeof(void *)*2);                // Setze den Pointer auf NULL

    if ((TextureImage[0]=LoadBMP("Data/Font.bmp")) &&            // Lade das Font Bitmap
        (TextureImage[1]=LoadBMP("Data/Bumps.bmp")))            // Lade das Texture Bitmap
    {
        Status=TRUE;                            // Setze den Status auf TRUE

Eine weitere wichtige Zeile, die Sie überprüfen sollten. Ich kann Ihnen gar nicht sagen, wieviele Emails ich erhalten habe, wo Leute micht gefragt haben: "warum sehe ich nur eine Textur oder warum sind meine Texturen alle weiß?!". Normalerweise ist diese Zeile das Problem. Wenn Sie die 2 mit einer 1 ersetzen würden, würde nur eine Textur erzeugt werden und die zweite würde weiß erscheinen. Wenn Sie die 2 mit einer 3 ersetzen würden, würde Ihr Programm wahrscheinlich abstürzen!

Sie sollten glGenTextures() nur einmal aufrufen. Nach glGenTextures() sollten Sie alle Texturen generieren. Ich habe Leute gesehen, die eine glGenTextures() Zeile vor jede Textur gesetzt haben, die sie erzeugt haben. Normalerweise hat das zur Folge, dass alle zuvor erzeugten Texturen überschrieben werden. Es ist eine gute Idee zu entscheiden, wieviele Texturen Sie erzeugen müssen, glGenTextures() einmal aufzurufen und dann alle Texturen zu erzeugen. Es ist nicht gerade schlau, glGenTextures() in einer Schleife zu plazieren, es sei denn, Sie haben einen Grund dafür.

        glGenTextures(2, &texture[0]);                    // erzeuge zwei Texturen

        for (loop=0; loop// durchlaufe alle Texturen
        {
            // erzeuge alle Texturen
            glBindTexture(GL_TEXTURE_2D, texture[loop]);
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
            glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
        }
    }

Die folgenden Codezeilen überprüfen, ob die Bitmapdaten die wir geladen haben um die Texturen zu erzeugen, im Speicher liegen. Falls ja, geben wir den Speicher frei. Beachten Sie, dass wir beide rgb Image-Records überprüfen und freigeben. Wenn wir 3 verschiedene Bilder zum Erzeugen der Texturen verwendet hätte, würden wir alle 3 rgb Image-Records überprüfen und freigeben.

    for (loop=0; loop// wenn Textur existiert
            {
                if (TextureImage[loop]->data)            // Wenn Textur-Image existiert
                {
                    free(TextureImage[loop]->data);        // gebe Textur-Image Speicher frei
                }
                free(TextureImage[loop]);            // gebe die Image-Struktur frei
            }
        }
    return Status;                                // gebe Status zurück
}

Nun erzeugen wir unseren eigentlichen Font. Ich werde bei diesem Codeabschnitt etwas mehr ins Detail gehen. Es ist nicht wirklich komplex, aber es gibt etwas Mathematik, die verstanden werden muss und ich weiß, dass nicht jeder Mathe gerade liebt.

GLvoid BuildFont(GLvoid)                            // erzeuge unsere Font Display Liste
{

Die folgenden zwei Variablen werden verwendet, um die Position von jedem Buchstaben innerhalb der Font Textur zu speichern. cx enthält die Position von links nach rechts innerhalb der Textur und cy enthält die Position von oben nach unten.

    float    cx;                                // enthält unsere X Buchstabenkoordinate
    float    cy;                                // enthält unsere Y Buchstabenkoordinate

Als nächstes teilen wir OpenGL mit, dass wir 256 Display Listen erzeugen wollen. Die Variable base wird auf die erste Display Liste zeigen. Die zweite Display Liste ist dann base+1, die dritte base+2, etc.

Die folgende zweite Codezeile wählt unsere Font Textur aus (texture[0]).

    base=glGenLists(256);                            // erzeuge 256 Display Listen
    glBindTexture(GL_TEXTURE_2D, texture[0]);                // wähle unsere Font Textur aus

Nun starten wir unsere Schleife. Die Schleife erzeugt alle 256 Buchstaben, wobei jeder Buchstabe in einer eigenen Display Liste gespeichert wird.

    for (loop=0; loop// durchlaufe alle 256 Listen
    {

Die erste Zeile sieht etwas verwirrend aus. Das % Symbol stellt den Teilerrest dar, nachdem loop durch 16 dividiert wurde. cx lässt uns von links nach rechts durch die Font Textur bewegen. Sie werden später bemerken, dass wir im Code cy von 1 subtrahieren, um uns von oben nach unten, anstatt von unten nach oben zu bewegen. Das % Symbol ist ziemlich schwer zu erklären, aber ich werde es trotzdem versuchen.

Worum wir uns nur kümmern wollen ist (loop%16), das /16.0f konvertiert das Ergebnis lediglich in Texturkoordinaten. Wenn loop gleich 16 wäre... wäre cx dem Restergebnis von 16/16 sein, was gleich 0 ist, aber cy wäre gleich 16/16 was 1 ist. Deshalb bewegen wir uns um die Höhe des Buchstabens herunter und wir würden uns gar nicht nach rechts bewegen. Wenn nun loop gleich 17 wäre, wäre cx gleich 17/16 was 1.0625 wäre. Der Teilerrest .0625 ist also gleich 1/16tel. Das würde bedeuten, dass wir uns um ein Zeichen nach rechts bewegen würden. cy wäre immer noch gleich 1, da wir nur die Zahlen links vom Dezimalzeichen betrachten. 18/16 würde 2 ergeben, was uns zwei Zeichen nach rechts bewegen würde und immer noch ein Zeichen runter. Wenn loop gleich 32 ist, wäre cx wieder gleich 0, da es keinen Teilerrest gibt, wenn Sie 32 durch 16 dividieren, aber cy wäre gleich 2. Da die Zahl vor dem Dezimalzeichen gleich 2 wäre, würden wir uns 2 Zeichen vom oberen Rand der Textur nach unten bewegen. Verstanden?

        cx=float(loop%16)/16.0f;                    // X Position des aktuellen Zeichens
        cy=float(loop/16)/16.0f;                    // Y Position des aktuellen Zeichens

Hui :) Ok. So, nun erzeugen wir unseren 2D Font, indem wir einen einzelnes Zeichen aus unserer Font Textur abhängig vom cx und cy Wert auswählen. In der folgenden Zeile, addieren wir loop zu dem Wert von base, weil wenn wir das nicht machen würden, würde jedes Zeichen in der ersten Display Liste gespeichert werden. Das wollen wir aber garantiert nicht, deshalb addieren wir loop zu base, so dass jedes Zeichen in der nächst verfügbaren Display Liste gespeichert wird.

        glNewList(base+loop,GL_COMPILE);                // fange an, Liste zu erzeugen

Nun, da wir die Display Liste ausgewählt haben, die wir erzeugen wollen, erzeugen wir unsere Zeichen. Das geschieht durch's zeichnen eines Quad, den wir dann mit einem einzelnen Zeichen aus der Font Textur texturieren.

            glBegin(GL_QUADS);                    // benutze einen Quad für jedes Zeichen

cx und cy werden einen winzigen Fließkommawert zwischen 0.0f und 1.0f enthalten. Wenn beide, cx und cy, gleich 0 wären, wäre die erste folgende Codezeile eigentlich: glTexCoord2f(0.0f,1-0.0f-0.0625f). Denken Sie daran, dass 0.0625 exakt 1/16 unserer Textur entspricht oder die Breite / Höhe eines Zeichens. Die untenstehende Texturkoordinate wäre der obere linke Punkt unserer Textur.

Beachten Sie, dass wir glVertex2i(x,y) anstatt von glVertex3f(x,y,z) verwenden. Unser Font ist ein 2D Font, weshalb wir den Z-Wert nicht benötigen. Da wir einen Ortho Screen verwenden, müssen wir uns nicht in den Screen hinein bewegen. Alles was Sie machen müssen, um auf einen Ortho Screen zu zeichnen, ist das angeben der X und Y Koordinate. Da unser Screen in Pixel von 0 bis 639 und von 0 bis 479 verläuft, müssen wir weder Fließkommazahlen noch negative Werte verwenden :)

So wie wir unseren Ortho Screen setzen, wird (0,0) in der unteren linken Ecke unseres Screens sein (640,480) wird die obere rechte Ecke des Screens sein. 0 ist die linke Seite auf der X-Achse und 639 die rechte Seite des Screens auf der X-Achse. 0 ist der untere Rand des Screens auf der Y-Achse und 479 der obere auf der Y-Achse. Grundsätzlich sind wir also die negative Koordinaten losgeworden. Das ist auch ganz nett für Leute, denen die Perspektive egal ist und lieber mit Pixel als mit Einheiten arbeiten :)

                glTexCoord2f(cx,1-cy-0.0625f);            // Texturkoordinate (unten links)
                glVertex2i(0,0);                // Vertexkoordinate (unten links)

Die nächste Texturkoordinate ist nun 1/16tel nach rechts von unserer letzten Texturkoordinate aus gesehen (exakt ein Zeichen breit). So, dass das der untere rechte Texturpunkt wäre.

                glTexCoord2f(cx+0.0625f,1-cy-0.0625f);        // Texturkoordinate (unten rechts)
                glVertex2i(16,0);                // Vertexkoordinate  (unten rechts)

Die dritte Texturkoordinate bleibt ganz rechts von unserem Zeichen, aber bewegt 1/16tel unserer Textur nach oben (genau die Höhe eines Zeichens). Das wird der obere recht Punkt eines einzelnen Zeichens.

                glTexCoord2f(cx+0.0625f,1-cy);            // Texturkoordinate (oben rechts)
                glVertex2i(16,16);                // Vertexkoordinate  (oben rechts)

Dann bewegen wir uns nach links, um unsere letzte Texturkoordinate oben links unseres Buchstaben zu setzen.

                glTexCoord2f(cx,1-cy);                // Texturkoordinate (oben links)
                glVertex2i(0,16);                // Vertexkoordinate   (oben links)
            glEnd();                        // fertig mit dem Erzeugen unseres Quad (Buchstaben)

Dann bewegen wir uns noch 10 Pixel nach rechts, um uns rechts von unserer Textur zu plazieren. Wenn wir uns nicht bewegen, würden die Buchstaben übereinander gezeichnet werden. Da unser Font so schmall ist, wollen wir uns nicht 16 Pixel nach rechts bewegen. Wenn wir das machen würden, wären große Freiräume zwischen den einzelnen Zeichen. Wenn wir uns nur um 10 Pixel bewegen, treten diese Zwischenräume nicht auf.

            glTranslated(10,0,0);                    // bewege sich rechts neben den Buchstaben
        glEndList();                            // fertig mit dem erzeugen der Display Liste
    }                                    // durchlaufe so lange bis alle 256 erzeugt wurden
}

Der folgende Codeabschnitt ist der selbe Code, den wir in den anderen Font Tutorials verwendet haben, um die Display Listen freizugeben, bevor wir das Programm beenden. Alle 256 Display Listen, beginnend bei base, werden gelöscht. (es ist sinnvoll das zu machen!).

GLvoid KillFont(GLvoid)                                // lösche den Font aus dem Speicher
{
    glDeleteLists(base,256);                        // lösche alle 256 Display Listen
}

Im nächsten Codeabschnitt wird unser Text gezeichnet. Alles ist ziemlich neu, weshalb ich versuche, jede Zeile im Detail zu erklären. Nur eine kleine Anmerkung: Es kann ziemlich viel zu diesem Code hinzugefügt werden, so wie die Unterstützung von Variablen, Buchstabengröße-Anpassung und viele Überprüfungen, um die Dinge wieder herzustellen wie sie waren, bevor wir uns entschieden haben, etwas auszugeben.

glPrint() werden drei Parameter übergeben. Der erste ist die X-Position (die Position von links nach rechts). Als nächstes kommt die Y-Position (von oben nach unten... 0 ganz unten, größere Zahlen weiter oben). Dann haben wir unseren eigentlichen String (den Text, den wir ausgeben wollen) und letzlich eine Variable namens set. Wenn Sie sich die Bitmap angeschaut haben, die Giuseppe D'Agata gemacht hat, werden Sie festgestellt haben, dass es zwei verschieden Zeichensätze gibt. Die erste Zeichensatz ist der normale und der zweite Zeichensatz ist kursiv. Wenn set gleich 0 ist, wird der erste Zeichensatz ausgewählt. Wenn set gleich 1 oder größer ist, wird der zweite Zeichensatz gewählt.

GLvoid glPrint(GLint x, GLint y, char *string, int set)                // Wo die Textausgabe geschieht
{

Als erstes stellen wir sicher wir, dass set entweder 0 oder 1 ist. Wenn set größer als 1 ist, setzen wir es auf 1.

    if (set>1)                                // Ist set größer als eins?
    {
        set=1;                                // wenn ja, setze Set auf eins
    }

Nun wählen wir unsere Font Textur. Wir machen das nur für den Fall, dass eine andere Textur ausgewählt wurde, bevor wir etwas auf dem Screen ausgeben.

    glBindTexture(GL_TEXTURE_2D, texture[0]);                // wähle unsere Font Textur

Nun deaktivieren wir Depth Testing. Der Grund warum ich das mache, ist, dass Blending dann so schön arbeitet. Wenn Sie Depth Testing nicht deaktivieren, wird der Text vielleicht hinter irgendwas verschwinden oder das Blending sieht nicht richtig aus. Wenn Sie nicht vorhaben den Text auf dem Screen zu blenden (so, dass schwarzer Zwischenraum nicht um die Buchstaben erscheint), können Sie Depth Testing angeschaltet lassen.

    glDisable(GL_DEPTH_TEST);                        // deaktiviere Depth Testing

Die nächsten paar Zeilen sind SEHR wichtig! Wir wählen unsere Projection Matrix. Direkt danach, benutzen wir einen Befehl namens glPushMatrix(). glPushMatrix speichert die aktuelle Matrix (projection). Ähnlich der Memory Taste bei einem Taschenrechner.

    glMatrixMode(GL_PROJECTION);                        // Select The Projection Matrix
    glPushMatrix();                                // Store The Projection Matrix

Nun, da unsere Projection Matrix gespeichert wurde, resetten wir die Matrix und setzen unseren Ortho Screen. Die erste und die dritte Zahl (0) repräsentieren unten links auf dem Bildschirm. Wir könnten die linke Seite des Screens auf -640 setzen, wenn wir wollten, aber warum sollten wir mit negativen Werten arbeiten, wenn wir es nicht müssen. Der zweite und vierte Parameter repräsentiert die obere rechte Ecke des Screens. Es wäre klug, diese Werte auf die Werte Ihrer aktuellen Auflösung zu setzen. Es gibt keine Tiefe, weshalb wir die Z-Werte auf -1 & 1 setzen.

    glLoadIdentity();                            // Resette die Projection Matrix
    glOrtho(0,640,0,480,-1,1);                        // Setze Ortho Screen

Nun wählen wir unsere Modelview Matrix aus und speichern die aktuellen Eigenschaften, indem wir glPushMatrix() verwenden. Wir resetten dann die Modelview Matrix, so dass wir mit ihr arbeiten können und unsere Ortho Ansicht verwenden können.

    glMatrixMode(GL_MODELVIEW);                        // Select The Modelview Matrix
    glPushMatrix();                                // Store The Modelview Matrix
    glLoadIdentity();                            // Reset The Modelview Matrix

Mit unseren gespeicherten Eigenschaften und unseren gesetzten Ortho Screen, können wir nun unseren Text zeichnen. Wir beginnen damit, dass wir uns an die Position des Screens bewegen, wo wir den Text zeichnen wollen. Wir benutzen glTranslated() anstatt von glTranslatef(), da wir mit Pixeln arbeiten, so dass Fließkommawerte nicht wichtig sind. Demnach können Sie auch kein halbes Pixel habe :)

    glTranslated(x,y,0);                            // Positioniere den Text (0,0 - unten links)

Die folgende Zeile wählt den Font aus, den wir benutzen wollen. Wenn wir den zweiten Font benutzen wollen, addieren wir 128 zur aktuellen Base Display Liste (128 ist die Hälfte unserer 256 Zeichen). Indem wir 128 addieren, überspringen wir die ersten 128 Zeichen.

    glListBase(base-32+(128*set));                        // Wähle den Zeichensatz (0 oder 1)

Nun müssen wir nur noch die Buchstaben auf den Screen zeichnen. Wir machen das genauso wie in den anderen Font Tutorials. Wir benutzen glCallLists(). strlen(string) ist die Länge unseres Strings (wieviele Zeichen wir zeichnen wollen), GL_UNSIGNED_BYTE bedeutet, dass jedes Zeichen durch ein unsigned Byte repräsentiert wird (ein Byte ist ein Wert zwischen 0 und 255). Letzlich enthält string den eigentlichen Text, den wir auf dem Screen ausgeben wollen.

    glCallLists(strlen(string),GL_UNSIGNED_BYTE,string);            // gebe den Text auf dem Screen aus

Alles was wir jetzt machen müssen, ist unsere perspektivische Sicht wieder herstellen. Wir wählen die Projektions Matrix aus und benutzen glPopMatrix, um die Einstellungen wieder herzustellen, die wir vorher mit glPushMatrix() gespeichert haben. Es ist wichtig, dass Sie die Dinge in umgekehrter Reihenfolge wieder herstellen, wie Sie sie abgespeichert haben.

    glMatrixMode(GL_PROJECTION);                        // wähle unsere Projection Matrix aus
    glPopMatrix();                                // stelle unsere alteProjection Matrix wieder her

Nun wählen wir die Modelview Matrix aus und machen das Selbe. Wir benutzen glPopMatrix() um unsere Modelview Matrix wieder herzustellen, bevor wir unsere Ortho Anzeige setzen.

    glMatrixMode(GL_MODELVIEW);                        // wähle die Modelview Matrix aus
    glPopMatrix();                                // Stelle die alte Projection Matrix wieder her

Letztlich aktivieren wir Depth Testing. Wenn Sie Depth Testing weiter oben nicht deaktiviert haben, brauchen Sie diese Zeile nicht.

    glEnable(GL_DEPTH_TEST);                        // aktiviere Depth Testing
}

In ReSizeGLScene() hat sich nichts geändert, weshalb wir gleich zu InitGL() kommen.

int InitGL(GLvoid)                                // Der ganze Setup Kram für OpenGL kommt hier rein
{

Wir springen zu unserem Textur-Erzeugungs-Code. Wenn das Erzeugen der Textur aus irgend einem Grund fehl schlug, geben wir FALSE zurück. Das lässt unser Programm wissen, dass ein Fehler aufgetreten ist und lässt unser Programm würdig beenden.

    if (!LoadGLTextures())                            // springe zur Textur-Lade-Routine
    {
        return FALSE;                            // wenn Textur nicht geladen wurde, geben FALSE zurück
    }

Wenn es keine Fehler gab, springen wir zu unserem Font-Erzeugungs-Code. Es kann eigentlich nichts schief gehen, wenn wir den Font erzeugen, weshalb wir uns hier auch nicht mit Fehlerprüfungen aufhalten.

    BuildFont();                                // erzeuge Font

Nun zum normalen GL Setup. Wir löschen den Hintergrund auf schwarz, löschen Depth auf 1.0. Wir wählen einen Depth-Testing-Modus mit Blending Modus. Wir aktivieren Smooth Shading und aktivieren dann noch 2D Textur-Mapping.

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);                    // löscht den Hintergrund auf schwarz
    glClearDepth(1.0);                            // aktiviert Löschung des Depth Buffer
    glDepthFunc(GL_LEQUAL);                            // Die Art des auszuführenden Depth Test
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);                    // wähle die Art desBlending aus
    glShadeModel(GL_SMOOTH);                        // aktiviert weiches Shading (Smooth Shading)
    glEnable(GL_TEXTURE_2D);                        // aktiviere 2D Textur Mapping
    return TRUE;                                // Initialisierung war OK

}

Der folgende Codeabschnitt erzeugt unsere Szene. Wir zeichnen das 3D Objekt als erstes und den Text zuletzt, so dass der Text über dem 3D Objekt erscheint, anstatt dass das 3D Objekt den Text verdeckt. Der Grund, warum ich ein 3D Objekt eingefügt habe, ist der, dass sowohl Perspektiven als auch der Ortho-Modus zur selben Zeit verwendet werden kann.

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();                            // Resettet die Modelview Matrix

Wir wählen unsere bumps.bmp Textur aus, so dass wir unser einfaches kleines 3D Objekt erzeugen können. Wir bewegen uns 5 Einheiten in den Screen hinein, so dass wir das Objekt auch sehen können. Wir rotieren auf der Z-Achse um 45 Grad. Das wird unser Quad um 45 Grad mit dem Uhrzeigersinn drehen und lässt unseren Quad mehr wie einen Diamenten als wir ein Quadrat aussehen.

    glBindTexture(GL_TEXTURE_2D, texture[1]);                // wähle unsere zweite Textur
    glTranslatef(0.0f,0.0f,-5.0f);                        // Gehe 5 Einheiten in den Screen hinein
    glRotatef(45.0f,0.0f,0.0f,1.0f);                    // Rotiere auf der Z-Achse um 45 Grad (im Uhrzeigersinn)

Nachdem wir die 45 Grad Drehung gemacht haben, rotieren wir das Objekt sowohl auf der X-Achse als auch auf der Y-Achse, basierend auf der Variable cnt1 mal 30. Das lässt unser Objekt rotieren, als ob der Diamant sich auf einem Punkt dreht.

    glRotatef(cnt1*30.0f,1.0f,1.0f,0.0f);                    // Rotiere auf der X & Y Achse um cnt1 (von links nach rechts) 

Wir deaktivieren Blending (wir wollen das 3D Objekt solide erscheinen lassen) und setzen die Farbe auf ein helles weiß. Wir zeichnen dann einen einfachen texturierten Quad.

    glDisable(GL_BLEND);                            // deaktiviere Blending bevor wir in 3D zeichnen
    glColor3f(1.0f,1.0f,1.0f);                        // helles weiß
    glBegin(GL_QUADS);                            // zeichne unseren ersten texturieren Quad
        glTexCoord2d(0.0f,0.0f);                    // erste Texturkoordinate
        glVertex2f(-1.0f, 1.0f);                    // erster Vertex
        glTexCoord2d(1.0f,0.0f);                    // zweite Texturkoordinate
        glVertex2f( 1.0f, 1.0f);                    // zweiter Vertex
        glTexCoord2d(1.0f,1.0f);                    // dritte Texturkoordinate
        glVertex2f( 1.0f,-1.0f);                    // dritter Vertex
        glTexCoord2d(0.0f,1.0f);                    // vierte Texturkoordinate
        glVertex2f(-1.0f,-1.0f);                    // vierter Vertex
    glEnd();                                // fertig mit dem Zeichnen des ersten Quads

Unverzüglich nachdem wir den ersten Quad gezeichnet haben, rotieren wir um 90 Grad, sowohl auf der X Achse als auch auf der Y-Achse. Wir zeichnen dann einen weiteren Quad. Der zweite Quad schneidet direkt in der Mitte den ersten Quad, was ein nett aussehende Form ergibt.

    glRotatef(90.0f,1.0f,1.0f,0.0f);                    // Rotiere auf der X & Y Achse um 90 Grad (von links nach rechts)
    glBegin(GL_QUADS);                            // zeichne unseren zweiten texturieren Quad
        glTexCoord2d(0.0f,0.0f);                    // erste Texturkoordinate
        glVertex2f(-1.0f, 1.0f);                    // erster Vertex
        glTexCoord2d(1.0f,0.0f);                    // zweite Texturkoordinate
        glVertex2f( 1.0f, 1.0f);                    // zweiter Vertex
        glTexCoord2d(1.0f,1.0f);                    // dritte Texturkoordinate
        glVertex2f( 1.0f,-1.0f);                    // dritter Vertex
        glTexCoord2d(0.0f,1.0f);                    // vierte Texturkoordinate
        glVertex2f(-1.0f,-1.0f);                    // vierter Vertex
    glEnd();                                // fertig mit dem Zeichnen des zweiten Quads

Nachdem beide texturierten Quads gezeichnet wurden, aktivieren wir Blending und zeichnen unseren Text.

    glEnable(GL_BLEND);                            // aktiviere Blending
    glLoadIdentity();                            // Resette die View

Wir benutzen den selben Code zur Färbung, wie in den anderen Text Tutorials. Die Farbe ändert sich sobald der Text sich über den Screen bewegt.

    // Pulsierende Farben basierend auf der Text Position
    glColor3f(1.0f*float(cos(cnt1)),1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)));

Dann zeichnen wir unseren Text. Wir benutzen immer noch glPrint(). Der erste Parameter ist die X-Position. Der zweite Parameter ist die Y-Position. Der dritte Parameter ("NeHe") ist der Text, der ausgegeben werden soll und der letzte Parameter ist der zu verwendende Zeichensatz (0 - normal, 1 - kursiv).

Wie Sie wahrscheinlich erraten können, bewegen wir den Text auf dem Screen mittels COS und SIN hin und her, zusammen mit den Zählern cnt1 und cnt2. Wenn Sie nicht verstehen, was SIN und COS machen, gehen Sie zurück zu den vorherigen Text-Tutorials und lesen Sie es dort nach.

    glPrint(int((280+250*cos(cnt1))),int(235+200*sin(cnt2)),"NeHe",0);    // Gebe GL Text auf dem Screen aus

    glColor3f(1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)),1.0f*float(cos(cnt1)));
    glPrint(int((280+230*cos(cnt2))),int(235+200*sin(cnt1)),"OpenGL",1);    // Gebe GL Text auf dem Screen aus

Wir setzen die Farbe auf ein dunkles Blau und geben den Namen des Autors am unteren Rand des Screens aus. Dann geben wir seinen Namen erneut aus und benutzen dabei helle weiße Buchstaben. Die weißen Buchstaben sind etwas nach rechts versetzt zu den blauen Buchstaben. Dadurch sieht es aus wie ein Schatten. (wenn Blending nicht aktiviert wurde, funktioniert der Effekt nicht).

    glColor3f(0.0f,0.0f,1.0f);                        // Setze Farbe auf blau
    glPrint(int(240+200*cos((cnt2+cnt1)/5)),2,"Giuseppe D'Agata",0);    // zeichne Text auf den Screen

    glColor3f(1.0f,1.0f,1.0f);                        // Setze Farbe auf weiß
    glPrint(int(242+200*cos((cnt2+cnt1)/5)),2,"Giuseppe D'Agata",0);    // Zeichne Text auf den Screen 

Als letztes inkrementieren wir noch unsere beiden Zähler, um verschiedene Werte. Dadurch wird der Text bewegt und das 3D Objekt rotiert.

    cnt1+=0.01f;                                // inkrementiere den ersten Zähler
    cnt2+=0.0081f;                                // inkrementiere den zweiten Zähler
    return TRUE;                                // alles war OK
}

Der Code von KillGLWindow(), CreateGLWindow() und WndProc() hat sich nicht geändert, weshalb wir diesen überspringen.

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,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
    {
        fullscreen=FALSE;                        // Fenster-Modus
    }

Der Titel unseres Fensters hat sich geändert.

    // erzeuge unser OpenGL Fenster
    if (!CreateGLWindow("NeHe & Giuseppe D'Agata's 2D Font 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])    // aktiv? soll beendet werden?
            {
                done=TRUE;                    // ESC oder DrawGLScene Signalisiert, dass beendet werden soll
            }
            else                            // Es ist noch nicht Zeit zum beenden, zeichne Screen neu
            {
                SwapBuffers(hDC);                // Swap Buffers (Double Buffering)
            }
        }
    }

    // Shutdown

Als letztes muss noch KillFont() am Ende von KillGLWindow() eingefügt werden, genauso wie ich es hier gemacht habe. Es ist wichtig, diese Zeile einzufügen. Sie räumt noch auf, bevor das Programm beendet wird.

    if (!UnregisterClass("OpenGL",hInstance))                // Können wir die Klasse de-registrieren?
    {
        MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        hInstance=NULL;                            // Setze hInstance aufNULL
    }

    KillFont();                                // zerstöre denFont
}

Ich denke, ich kann nun offiziell sagen, dass meine Seite jede mögliche Art lehrt, wie man Text auf dem Bildschirm ausgibt {grins}. Alles in allem denke ich, dass das ein ziemlich gutes Tutorial ist. Der Code kann auf jedem Computer ausgeführt werden, auf dem auch OpenGL läuft, er ist einfach zu benutzen und Text auf diese Weise auf dem Bildschirm auszugeben, benötigt nur wenig Rechenleistung.

Ich möchte Giuseppe D'Agata für die original Version dieses Tutorials danken. Ich habe sie ziemlich stark modifiziert und dem neuen Base Code angepasst, aber wenn er den Code nicht an mich geschickt hätte, hätte ich wahrscheinliich niemals dieses Tutorial geschrieben. Seine Version des Codes hatte ein paar mehr Optionen, so wie z.B. das plazieren der Buchstaben, etc., aber ich habe daraus die extrem coolen 3D Objekte gemacht {grins}.

Ich hoffe, dass jeder dieses Tutorial genossen hat. Wenn Sie Fragen haben, mailen Sie Giuseppe D'Agata oder mir.

Giuseppe D'Agata

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 C# Code für diese Lektion. ( Conversion by Brian Holley )
* DOWNLOAD Code Warrior 5.3 Code für diese Lektion. ( Conversion by Scott Lupton )
* DOWNLOAD Cygwin Code für diese Lektion. ( Conversion by Stephan Ferraro )
* DOWNLOAD Delphi Code für diese Lektion. ( Conversion by Michal Tucek )
* DOWNLOAD Dev C++ Code für diese Lektion. ( Conversion by Dan )
* DOWNLOAD Euphoria Code für diese Lektion. ( Conversion by Evan Marshall )
* DOWNLOAD Game GLUT Code für diese Lektion. ( Conversion by Milikas Anastasios )
* DOWNLOAD Irix / GLUT Code für diese Lektion. ( Conversion by Rob Fletcher )
* DOWNLOAD Java Code für diese Lektion. ( Conversion by Jeff Kirby )
* DOWNLOAD JoGL Code für diese Lektion. ( Conversion by Nicholas Campbell )
* DOWNLOAD LCC Win32 Code für diese Lektion. ( Conversion by Robert Wishlaw )
* DOWNLOAD Linux/GLX Code für diese Lektion. ( Conversion by Mihael Vrbanec )
* DOWNLOAD Linux/SDL Code für diese Lektion. ( Conversion by Ti Leggett )
* DOWNLOAD LWJGL Code für diese Lektion. ( Conversion by Mark Bernard )
* DOWNLOAD Mac OS Code für diese Lektion. ( Conversion by Anthony Parker )
* DOWNLOAD Mac OS X/Cocoa Code für diese Lektion. ( Conversion by Bryan Blackburn )
* DOWNLOAD MASM Code für diese Lektion. ( Conversion by Greg Helps )
* DOWNLOAD Visual C++ / OpenIL Code für diese Lektion. ( Conversion by Denton Woods )
* DOWNLOAD Pelles C Code für diese Lektion. ( Conversion by Pelle Orinius )
* DOWNLOAD Visual Basic Code für diese Lektion. ( Conversion by Ross Dawson )
* 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.