NeHe - Lektion 15 - Texturierte Fonts

Lektion 15



Nachdem ich die letzten beiden Tutorials über Bitmap und Outline Fonts hochgeladen habe, habe ich etlich Emails von Leuten bekommen, die sich gefragt haben, wie man Fonts texturiert. Sie können die automatische Texturkoordinaten Erzeugung nutzen. Diese generiert Texturkoordinaten für jedes Polygon des Fonts.

Eine kleine Anmerkung: dieser Code ist Windows-spezifisch. Er benutzt die wgl Funktionen von Windows um den Font zu erzeugen. Dafür hat Apple aber agl, was das Selbe machen sollte und X hat glx. Unglücklicherweise kann ich Ihnen nicht garantieren, dass der Code portabel ist. Wenn irgendwer einen Plattform unabhängigen Code zum Fonts zeichnen hat, schickt ihn mir und ich werde ein anderes Font-Tutorial schreiben.

Wir erzeugen unser texturiertes Font Demo basierend auf dem Code von Lektion 14. Wenn irgend ein Code in einem bestimmten Abschnitt des Programms geändert wurde, schreibe ich den Codeabschnitt nochmal komplett hier nieder, damit es einfacher ist, Änderungen zu verfolgen.

Der folgende Codeabschnitt ist ähnlich dem aus Lektion 14, aber diesmal inkludieren wir nicht die stdarg.h Datei.
 
#include    <windows.h>                    // Header Datei für Windows
#include    <math.h>                    // Header Datei für Windows Math Library
#include    <stdio.h>                    // Header Datei für Standard Input/Output
#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 neue Integer Variable namens texture[] hier ein. Sie wird dazu verwendet, um unsere Textur zu speichern. Die letzen drei Zeilen waren in Tutorial 14 und haben sich in diesem Tutorial nicht geändert.
 
GLuint    texture[1];                        // Eine Textur Map ( NEU )
GLuint    base;                            // Basis Display Liste für den Font Satz

GLfloat    rot;                            // wird benutzt um den Text rotieren zu lassen

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

Der folgende Codeabschnitt wurde minimal geändert. In diesem Tutorial werde ich den Wingdings Font verwenden, um einen Totenschädel mit gekreuzten Knochen anzuzeigen. Wenn Sie statt dessen Text anzeigen wollen, können Sie den Code aus Lektion 14 einfach so lassen oder Sie ändern den Font Ihren Wünschen entsprechend.

Einige von Ihnen haben sich gefragt, wie man den Wingdings Font benutzen kann, was ein weiterer Grund ist, warum ich keinen Standard Font verwende. Wingdings ist ein SYMBOL Font und benötigt eine Änderungen, um zu funktionieren. Es ist nicht so einfach, Windows mitzuteilen, Wingdings zu benutzen. Wenn Sie den Font Namen auf Wingdings ändern, werden Sie bemerken, dass der Font nicht ausgewählt wird. SIe müssen Windows mitteilen, dass der Font ein Symbol Font ist und kein Standard Zeichen Font. Mehr dazu später.
 
GLvoid BuildFont(GLvoid)                    // erzeuge unseren Bitmap Font
{
    GLYPHMETRICSFLOAT    gmf[256];            // Address Buffer für Font Speicherplatz
    HFONT    font;                        // Windows Font ID

    base = glGenLists(256);                    // Speicherplatz für 256 Buchstaben
    font = CreateFont(    -12,                // Höhe unseres Font
                0,                // Breite unseres Font 
                0,                // Angle Of Escapement
                0,                // Orientation Angle
                FW_BOLD,            // Font Dicke
                FALSE,                // Italic
                FALSE,                // Underline
                FALSE,                // Strikeout

Dies ist die magische Zeile. Anstatt ANSI_CHARSET zu benutzen, wie wir es in Tutorial 14 gemacht haben, benutzen wir SYMBOL_CHARSET. Damit teilen wir Windows mit, dass der Font den wir erzeugen, kein typischer Font bestehent aus Buchstaben ist. Ein Symbol Font besteht normalerweise aus kleinen Bildern (Symbolen). Wenn Sie vergessen, diese Zeile zu ändern, Wingdings, Webdings und andere Symbol Fonts die Sie versuchen zu verwenden, werden nicht funktionieren.
 
                SYMBOL_CHARSET,            // Character Set Identifier ( Modifiziert )

Die nächsten Zeilen haben sich nicht verändert.
 
                OUT_TT_PRECIS,            // Output Precision
                CLIP_DEFAULT_PRECIS,        // Clipping Precision
                ANTIALIASED_QUALITY,        // Ausgabe Qualität
                FF_DONTCARE|DEFAULT_PITCH,    // Familie And Pitch

Nun, da wir den Symbol Zeichensatz gewählt haben, können wir den Wingdings Font auswählen!
 
                "Wingdings");            // Font Name ( Modifiziert )

Die restlichen Codezeilen haben sich nicht geändert.
 
    SelectObject(hDC, font);                // wähle den erzeugten Font aus

    wglUseFontOutlines(    hDC,                // wähle aktuellen DC
                0,                // Anfangsbuchstaben
                255,                // Anzahl der zu erzeugenden Display Listen
                base,                // Anfangs Display Liste

Wir erlauben größere Abweichungen. Das bedeutet, dass OpenGL nicht versucht so genau wie möglich zu sein. Wenn Sie den Wert auf 0.0f setzen, werden Sie bemerken, dass Sie Probleme bei der Texturierung wirklich gekrümmter Flächen haben werden. Wenn Sie etwas mehr Abweichungen erlauben, werden die meisten Probleme verschwinden.
 
                0.1f,                // Abweichung vom wahren Umriss

Die nächsten drei Codezeilen bleiben die selben.
 
                0.2f,                // Font Tiefe auf der Z-Ebene
                WGL_FONT_POLYGONS,        // benutze Polygone, keine Linien
                gmf);                // Addresse des Buffer der die Daten empfängt
}

Genau vor ReSizeGLScene() fügen wir folgenden Codeabschnitt ein, um unsere Textur zu laden. Sie werden den Code vielleicht aus voherherigen Tutorials wiedererkennen. Wir erzeugen Speicherplat für das Bitmap. Wir laden das Bitmap. Wir teilen OpenGL mit, 1 Textur zu erzeugen und wir speichern die Textur in texture[0].

Ich werde eine Mipmapped Textur erzeugen, weil diese besser aussieht. Der Name der Textur ist lights.bmp.
 
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
}

int LoadGLTextures()                        // Lade Bitmaps und konvertiere in Texturen
{
    int Status=FALSE;                    // Status Indikator

    AUX_RGBImageRec *TextureImage[1];            // erzeuge Speicherplatz für die Textur

    memset(TextureImage,0,sizeof(void *)*1);        // Setze den Zeiger auf NULL

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

        glGenTextures(1, &texture[0]);            // erzeuge die Textur

        // erzeuge lineare Mipmapped Textur
        glBindTexture(GL_TEXTURE_2D, texture[0]);
        gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);

Die nächsten vier Codezeilen erzeugen automatisch die Texturkoordinaten für jedes Objekt, dass wir in der Szene zeichnen. Der glTexGen Befehl ist ziemlich mächtig udn komplex und all die Mathematik dahinter zu erklären wäre ein eigenes Tutorial. Alles was Sie wissen müssen, ist, dass GL_S und GL_T Texturkoordinaten sind. Standardmäßig werden diese initialisiert um die aktuelle X Position auf dem Screen und die aktuelle Y Position auf dem Screen und kommen zusammen mit einem Textur-Vertex. Sie werden bemerken, dass die Objekte nicht auf der Z-Ebene texturiert sind... es erscheinen nur Streifen. Die Vorder- und Rückseite sind allerdings texturiert und das ist alles, was zählt. X (GL_S)sorgt sich um das mapping der Textur von links nach rechts und Y (GL_T) deckt das Mapping der Texur von oben nach unten ab.

GL_TEXTURE_GEN_MODE lässt uns den Modus des Textur-Mappings auswählen, den wir auf der S und T Textur-Koordinate benutzen wollen. Sie haben 3 Möglichkeiten:

GL_EYE_LINEAR - Die Textur ist am Screen fixiert. Sie bewegt sich niemals. Das Objekt wird mit jedem Abschnitt der Textur gemapped, wenn das Objekt über die Textur bewegt.

GL_OBJECT_LINEAR - Dies ist der Modus den wir verwenden. Die Textur ist am Objekt fixiert und wird somit mit über den Screen bewegt.

GL_SPHERE_MAP - Jedermann's Liebling. Erzeugt ein metallisch reflektierenes Objekt.

Es ist wichtig anzumerken, dass ich eine Menge Code auslasse. Wir sollten GL_OBJECT_PLANE ebefalls setzen, aber standardmäßig ist es auf den Parmeter gesetzt, den wir haben wollen. Kaufen Sie sich ein gutes Buch, wenn Sie daran interessiert sind, mehr zu lernen oder schauen Sie in der MSDN nach.
 
        // Textur-Umriss ist am Objekt befestigt
        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        // Textur-Umriss ist am Objekt befestigt
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glEnable(GL_TEXTURE_GEN_S);            // Auto Textur Erzeugung
        glEnable(GL_TEXTURE_GEN_T);            // Auto Textur Erzeugung
    }

    if (TextureImage[0])                    // Wenn Textur existiert
    {
        if (TextureImage[0]->data)            // Wenn Textur-Image existiert
        {
            free(TextureImage[0]->data);        // gebe Textur-Image Speicher frei
        }

        free(TextureImage[0]);                // gebe die Image-Struktur frei
    }

    return Status;                        // gebe Status zurück
}

Es gibt einige neue Zeilen am Ende des InitGL() Codes. BuildFont() wurde unter den Textur-Lade-Code geschoben. Die Zeile glEnable(GL_COLOR_MATERIAL) wurde entfernt. Wenn Sie vorhaben die Textur zu färben, indem Sie glColor3f(r,g,b) verwenden, fügen Sie die Zeile glEnable(GL_COLOR_MATERIAL) wieder in diesem Codeabschnitt ein.
 
int InitGL(GLvoid)                        // Der ganze Setup Kram für OpenGL kommt hier rein
{
    if (!LoadGLTextures())                    // Rufe Textur Lade Routine auf 
    {
        return FALSE;                    // wenn Textur nicht geladen wurde, gebe FALSE zurück
    }
    BuildFont();                        // erzeuge den Font

    glShadeModel(GL_SMOOTH);                // aktiviere Smooth Shading
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);            // schwarzer Hintergrund
    glClearDepth(1.0f);                    // Depth Buffer Setup
    glEnable(GL_DEPTH_TEST);                // aktiviere Depth Testing
    glDepthFunc(GL_LEQUAL);                    // Die Art des auszuführenden Depth Test
    glEnable(GL_LIGHT0);                    // schnelle und schmutzige Beleuchtung (nimmt an, dass Light0 existiert)
    glEnable(GL_LIGHTING);                    // aktiviere Beleuchtung
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);    // wirklich nette Perspektiven Berechnungen

Aktiviere 2D Textur Mapping und wähle Textur eins aus. Das wird die Textur eins auf jedes 3D Objekt mappen, dass wir in der Szene zeichnen. Wenn Sie etwas mehr Kontrolle haben wollen, können Sie Textur-Mapping selber aktivieren und deaktivieren.
 
    glEnable(GL_TEXTURE_2D);                // aktiviere Textur Mapping
    glBindTexture(GL_TEXTURE_2D, texture[0]);        // wähle Textur aus
    return TRUE;                        // Initialisierung war OK
}

Der ReSize code hat sich nicht geändert, aber unser DrawGLScene code hat es.
 
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 View

Hier kommt unsere erste Änderung. Anstatt das Objekt in der Mitte des Screens zu behalten, lassen wir es auf dem Screen rotieren, in dem wir COS und SIN verwenden (keine wirkliche Überraschung). Wir bewegen uns 3 Einheiten in den SCreen hinein (-3.0f). Auf der X-Achse schwingen wir von -1.1 links bis +1.1 rechts. Wir werden die Variable rot benutzen, um das Schwingen von links nach rechts zu kontrollieren. Wir schwingen von +0.8 oben nach -0.8 unten. Wir werden die Variable rot ebenfalls für diese Schwingbewegung verwenden.
 
    // Positioniere den Text
    glTranslatef(1.1f*float(cos(rot/16.0f)),0.8f*float(sin(rot/20.0f)),-3.0f);

Nun machen wir unsere normale Rotation. Das lässt das Symbol auf der X, Y und Z-Achse rotieren.
 
    glRotatef(rot,1.0f,0.0f,0.0f);                // Rotiere auf der X-Achse
    glRotatef(rot*1.2f,0.0f,1.0f,0.0f);            // Rotiere auf der Y-Achse
    glRotatef(rot*1.4f,0.0f,0.0f,1.0f);            // Rotiere auf der Z-Achse

Wir bewegen uns etwas nach links, runter und zum Betrachter hin, um das Symbol auf jeder Achse zu zentrieren. Ansonsten sieht es nicht so aus, als ob es um die eigene Achse rotieren würde. -0.35 ist nur eine Zahl, die funktioniert. Ich musste etwas mit den Zahlen herumspielen, da ich mir nicht sicher war, wie breit der Font ist und das bei jedem Font variieren kann. Warum die Fonts nicht um einen zentralen Punkt gebildet werden, weiß ich nicht.
 
    glTranslatef(-0.35f,-0.35f,0.1f);            // zentriere auf der X, Y, Z Achse

Zum Schluss zeichnen wir unseren Totenschädel mit den gekreuzten Knochen und inkrementieren dann die rot Variable, um unser Symbol rotieren zu lassen und auf dem Screen zu bewegen. Wenn Sie nicht herausfinden können, wie ich an das Totenschädel Symbol mit dem Buchstaben 'N' gekommen bin, machen Sie folgendes: Öffnen Sie Microsoft Word oder Wordpad. Gehen Sie ins Font Menü. Wählen Sie den Wingdings Font aus. Geben Sie ein großes 'N' ein. Ein Totenschädel mit gekreuzten Knochen erscheint.
 
    glPrint("N");                        // zeichne einen Totenkopf mit gekreuzten Knochen
    rot+=0.1f;                        // inkrementiere die Rotations Variable
    return TRUE;                        // Weiter geht's
}

Die letzte zu erledigende Sache ist, KillFont() am Ende von KillGLWindow() einzufügen, so wie ich's unten zeige. Es ist wichtig diese Zeile einzufügen. Sie räumt auf, bevor wir das Programm beenden.
 
    if (!UnregisterClass("OpenGL",hInstance))        // Können wir unsere Klasse de-registrieren
    {
        MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        hInstance=NULL;                    // Setze hInstance auf NULL
    }

    KillFont();                        // Zerstöre den Font
}

Selbst wenn ich nicht immer bis ins kleinste Detail gegangen bin, sollten Sie jetzt ein ganz gutes Verständnis haben, wie Sie OpenGL die Texturkoordinaten für Sie berechnen lassen können. Sie sollten keine Probleme haben, ihre Fonts selbst zu texturieren, oder ein anderes Objekt. Und indem Sie nur zwei Codezeilen ändern, können Sie Sphere Mapping aktivieren, was ein recht cooler Effekt.

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 Euphoria Code für diese Lektion. ( Conversion by Evan Marshall )
* DOWNLOAD GLut Code für diese Lektion. ( Conversion by David Phillip Oster )
* DOWNLOAD Java Code für diese Lektion. ( Conversion by Jeff Kirby )
* DOWNLOAD LCC Win32 Code für diese Lektion. ( Conversion by Robert Wishlaw )
* DOWNLOAD Mac OS Code für diese Lektion. ( Conversion by David Phillip Oster )
* DOWNLOAD MASM Code für diese Lektion. ( Conversion by Greg Helps )
* 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.