NeHe - Lektion 43 - Free Type Fonts in OpenGL

Lektion 43



So, hier es ein weiteres schnelles Tutorial, das Ihnen zeigt, wie man die FreeType Font Rendering Library in OpenGL verwendet. Indem wir die FreeType Library verwenden, können wir Anti-Aliased Text erzeugen, der besser als der Text aussieht, der Bitmap Fonts (wie in Lektion 13) benutzt. Unser Text wird auch noch ein paar andere Vorteile gegenüber von Bitmap Fonts hat - er wird einfach zu rotieren sein und wird ebenfalls mit den OpenGL Picking-Funktionen funktionieren.

Motivation

Hier habe ich den selben Text ausgedruckt, der einmal beide WGL Bitmap Fonts verwendet und Fonts, die mit FreeType gerendert sind (beide sind Arial Black Italic).


Das grundsätzliche Probleme beim benutzen von Bitmap Fonts ist, dass in OpenGL Bitmaps per Definition binäre Bilder sind. Das bedeutet, dass ein OpenGL Bitmap nur ein Bit an Informationen pro Pixel speichert. Wenn Sie an den Text heranzoomen, den Sie mittels WGL Bitmaps erstellt haben, wird das Ergebniss wie folgt aussehen:


Da Bitmaps binäre Bilder sind, gibt es keine grauen Pixel im obigen Bild was wiederum den Text klobig erscheinen lässt.

Glücklicherweise ist es ziemlich einfach, bessere Fonts zu machen, indem man die GNU FreeType Library verwendet. FreeType ist die gleiche Library, die Blizzard verwendet, um die Fonts in ihren Spielen zu rendern, sie muss also gut sein!

Hier eine Großaufnahme des Textes den ich mit der FreeType Library erzeugt habe:


Sie können sehen, dass es recht viele graue Pixel um die Ecken des Textes herum gibt; das ist typisch für ein anti-aliased Font, die grauen Pixel lassen den Font aus der Ferne weicher aussehen.

Das Programm erzeugen

Als erster Schritt müssen Sie sich eine Kopie der GNU FreeType Library besorgen. Gehen Sie zu http://gnuwin32.sourceforge.net/packages/freetype.htm und laden Sie die Binaries und die Entwickler Dateien herunter. Wenn Sie sie installieren, beachten Sie die Lizenzbedingungen, welche im Grunde aussagen, dass Sie irgendwo in der Dokumentation Credits geben müssen, wenn Sie FreeType in ihren eigenen Programmen verwenden.

Nun müssen wir MSVC einrichten, um FreeType zu verwenden. Erzeugen Sie also ein neues Projekt wie in Lektion 1, aber wenn Sie auf Projekt->Einstellungen->Link gehen, stellen Sie sicher, dass Sie die libfreetype.lib zu den Libraries zusammen mit opengl32.lib, glu32.lib und glaux.lib (falls benötigt) einfügen.

Als nächstes müssen wir auf Tools->Optionen->Verzeichnisse gehen und die FreeType Library Verzeichnisse hinzufügen. Unter "Zeige Verzeichnisse für" wählen Sie "Füge Dateien ein", dann Doppelklick auf die leere Zeile am Ende der Verzeichnis-Liste, nachdem Sie einen Doppelklick auf einen "..." Button gemacht haben, wird etwas erscheinen, wo Sie nach einem Verzeichnis suchen können. Auf diese Weise fügen Sie

C:\PROGRAM FILES\GNUWIN32\INCLUDE\FREETYPE2

und

C:\PROGRAM FILES\GNUWIN32\INCLUDE

in die Liste der Header Verzeichnisse ein.

Nun wählen Sie unter "Zeige Verzeichnisse für" "Library Dateien" aus und fügen diesmal

C:\PROGRAM FILES\GNUWIN32\LIB

hinzu.

Jetzt sollten Sie Programme, die FreeType verwenden, kompilieren können, aber diese werden nicht laufen können, solange diese keinen Zugriff auf freetype-6.dll haben. Zur Zeit haben Sie eine Kopie dieser DLL Datei in Ihrem GNUWIN32\BIN Verzeichnis und wenn Sie diese irgendwo hin kopieren, wo alle Programme diese sehen können (Programme\Microsoft Visual Studio\VC98\Bin ist eine gute Wahl), können Sie auch Programme laufen lassen, die FreeType verwenden. Aber denken Sie daran, dass wenn Sie ihr Programm weitergeben, dass Sie mit FreeType gemacht haben, dass Sie auch eine Kopie der DLL weitergeben müssen.

Ok, fangen wir endlich an, Code zu schreiben. Ich habe mich dazu entschieden, den Source aus Lektion 13 als Grundlage zu nehmen, laden Sie sich ihn also runter, wenn Sie ihn noch nicht haben. Kopieren Sie lesson13.cpp in Ihr Projektverzeichnis und fügen Sie die Datei zu Ihrem Projekt hinzu.

Nun erzeugen und fügen Sie zwei neue Dateien namens "freetype.cpp" und "freetype.h" hinzu. Wir werden all unseren FreeType spezifischen Code in diese Dateien packen und dann lesson13 etwas modifzieren, um die Funktionen, die wir geschrieben haben, zu demonstrieren. Wenn wir fertig sind, werden wir eine sehr einfache OpenGL FreeType Library erzeugt haben, die theoretisch in jedem OpenGL Project verwendet werden kann.

Wir fangen mit freetype.h an.

Natürlich müssen wir die FreeType und OpenGL Header inkludieren. Wir werden ebenfalls einige nützliche Dinge aus der Standard Template Library verwenden, inklusive der STL Exception Klassen, was es uns einfacher macht, nützliche Debugging Nachrichten zu erzeugen.

#ifndef FREE_NEHE_H
#define FREE_NEHE_H

// FreeType Header
#include <ft2build.h>
#include <freetype/freetype.h>
#include <freetype/ftglyph.h>
#include <freetype/ftoutln.h>
#include <freetype/fttrigon.h>

// OpenGL Header
#include <windows.h>                                        // (wird von den GL Headern benötigt)
#include <GL/gl.h>
#include <GL/glu.h>

// einige STL Header
#include <vector>
#include <string>

// Indem wir die STL Exception Library verwenden, erhöhen wir die
// Chancen, dass jemand, der unseren Code verwendet, alle Exceptions
// die wir werfen, korrekt abfängt.
#include <stdexcept>

// MSVC wird eine Menge unbrauchbarer Warnungen ausspucken, wenn Sie
// Vectors von Strings erzeugen, dieses Pragma schaltet das ab.
#pragma warning(disable: 4786)

Wir werden alle Informationen, die jeder Font benötigt, in einer Struktur speichern (das macht das Managen von mehreren Fonts etwas einfacher). Wie wir in Lektion 13 gelernt haben: wenn WGL einen Font erzeugt, erzeugt es einen Satz von aufeinanderfolgenden Display Listen. Das ist ganz nett, weil es bedeuted, dass Sie glCallLists verwenden können, um einen String, der aus Buchstaben besteht, mit nur einem Befehl ausgeben können. Wenn wir unseren Font erzeugen, werden wir die Dinge genau so initialisieren, was bedeutet, dass das list_base Feld die erste, von 128 aufeinanderfolgenden Display-Listen, speichern wird. Da wir Texturen benutzen werden, um unseren Text zu zeichnen, benötigen wir ebenfalls Speicherplatz für die entsprechenden 128 Texturen. Die letzte Information die wir benötigen ist die Höhe des von uns erzeugten Fonts, in Pixeln, (damit wir entsprechend neue Zeilen in unserer Print-Funktion berücksichtigen können).

// Wrappe alles in einem Namespace, So können wir gängige
// Funktionsnamen wie "print" benutzen, ohne uns Sorgen darum
// zu machen, irgendwelchen Code zu überlappen.
namespace freetype {

// Innerhalb dieses Namespaces, geben wir uns die Möglichkeit
// einfach "vector" zu schreiben, anstatt von "std::vector"
using std::vector;

// Das Selbe für String.
using std::string;

// Dies enthält alle relevanten Informationen über jeglichen
// FreeType Font, den wir erzeugen wollen.
struct font_data {
    float h;                                        // enthält die Höhe des Fonts.
    GLuint * textures;                                    // enthält die Textur Id's
    GLuint list_base;                                    // enthält die erste Display Listen Id

    // Die Init Function wird ein Font mit
    // der Höhe h aus der Datei fname erzeugen.
    void init(const char * fname, unsigned int h);

    // gebe alle Ressourcen, die mit dem Font verbunden sind, frei.
        void clean();
};

Als letztes benötigen wir einen Prototypen für unsere Print Funktion:

// Die Flaggschiff-Funktion der Library - Diese wird unseren Text
// an den Fenster-Koordinaten X, Y ausgeben und dabei den Font ft_font verwenden.
// Die aktuelle Modelview Matrix wird ebenso auf den Text angewandt.
void print(const font_data &ft_font, float x, float y, const char *fmt, ...);

}                                                // Schließe den Namespace

#endif

Und das ist das Ende der Header Datei! Es ist an der Zeit freetype.cpp zu öffnen.

// Inkludiere unsere Header Datei.
#include "freetype.h"

namespace freetype {

Wir verwenden Texturen um jeden einzelnes Zeichen unseres Fonts anzuzeigen. OpenGL Texturen benötigen Dimensionen zur Basis zwei, weshalb wir die Font Bitmaps, die von FreeType erzeugt werden, entsprechend anpassen müssen, um eine gültige Größe zu erhalten. Deshalb benötigen wir diese Funktion:

// Diese Funktion ermittelt die erste 2er-Basis >= der Int, den 
// wir übergeben.
inline int next_p2 (int a )
{
    int rval=1;
    // rval
    while(rval<a) rval
Die nächste Funktion, die wir benötigen, ist make_dlist, welche wirklich das Herz dieses Codes ist. Ihr wird ein FT_FACE-Objekt übergeben, welches FreeType dazu verwenden, Informationen über einen Font zu speichern und erzeugt eine Display-Liste für das Zeichen, welches wir ebenfalls übergeben.

// erzeuge eine Display-Liste für das entsprechende Zeichen.
void make_dlist ( FT_Face face, char ch, GLuint list_base, GLuint * tex_base ) {

    // Als erstes holen wir FreeType um unser Zeichen in ein
    // Bitmap zu rendern. Das benötigt ein paar FreeType Befehle:

    // Lade den Glyphen unseres Zeichens.
    if(FT_Load_Glyph( face, FT_Get_Char_Index( face, ch ), FT_LOAD_DEFAULT ))
        throw std::runtime_error("FT_Load_Glyph failed");

    // bewege die Frontseite des Glyphen in ein Glyph Objekt.
    FT_Glyph glyph;
    if(FT_Get_Glyph( face->glyph, &glyph ))
        throw std::runtime_error("FT_Get_Glyph failed");

    // konvertiere den Glyphen in ein Bitmap.
    FT_Glyph_To_Bitmap( &glyph, ft_render_mode_normal, 0, 1 );
    FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;

    // Diese Referenz wird es einfacher machen, auf das Bitmap zuzugreifen.
    FT_Bitmap& bitmap=bitmap_glyph->bitmap;

Nun, da wir ein Bitmap von FreeType haben erzeugen lassen, müssen wir es mit leeren Pixeln auffüllen, um es eine gültige Quelle für eine OpenGL Textur zu machen. Es ist wichtig daran zu denken, dass, während OpenGL den Begriff "Bitmap" dazu verwendet, um binäre Bilder zu bezeichnen, ein Bitmap in FreeType 8 Bits an Informationen pro Pixel abspeichert, so dass die Bitmaps von FreeType die Grautöne speichern kann, die wir benötigen, um Anti-Aliased Text zu erzeugen.

    // Benutze unsere Helfer-Funktion, um die Breiten der
    // Bitmap Daten zu ermitteln, die wir benötigen, um unsere
    // Texturen zu erzeugen.
    int width = next_p2( bitmap.width );
    int height = next_p2( bitmap.rows );

    // Alloziere Speicher für die Textur-Daten.
    GLubyte* expanded_data = new GLubyte[ 2 * width * height];

    // Hier füllen wir die Daten für das erweiterte Bitmap.
    // Beachten Sie, dass wir ein zwei Channel Bitmap verwenden (einen
    // Durchsichtigkeit und einen für Alpha), aber wir wenden beide
    // Durchsichtigkeit und Alpha auf den Wert an, den wir im
    // FreeType Bitmap finden. 
    // Wir verwenden den ?: Operator, um mitzuteilen, dass wir den Wert, den wir verwenden
    // entweder 0 sein wird, wenn wir in der Auffüll-Zone sind oder aber,
    // dass es ansonsten der Wert aus dem FreeType Bitmap sein wird.
    for(int j=0; j <height;j++) {
        for(int i=0; i <width; i++){
            expanded_data[2*(i+j*width)]= expanded_data[2*(i+j*width)+1] =
                (i>=bitmap.width || j>=bitmap.rows) ?
                0 : bitmap.buffer[i + bitmap.width*j];
        }
    }

Wenn wir mit dem Auffüllen fertig sind, können wir mit dem Erzeugen der OpenGL Textur weiter machen. Wir fügen einen Alpha-Channel ein, so dass die schwarzen Teile des Bitmaps transparent sein werden und so, dass die Ecken des Textes leicht durchsichtig sind (was sie so aussehen lassen sollte, dass sie sich dem Hintergrund anpassen).

    // Nun initialisieren wir einfach ein Paar Textur Parameter.
    glBindTexture( GL_TEXTURE_2D, tex_base[ch]);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

    // Hier erzeugen wir die eigentlich Textur, beachten Sie,
    // dass wir GL_LUMINANCE_ALPHA verwenden, um zu indizieren, dass
    // wir 2 Channel Daten verwenden.
    glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
        GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data );

    // Nachdem wir die Textur erzeugt haben, benötigen wir die erweiterten Daten nicht mehr.
    delete [] expanded_data;

Wir benutzen texturierte Quads um unseren Text zu zeichnen. Das bedeutet, dass es recht einfach wird, den Text zu rotieren und skalieren und außerdem erben die Fonts die Farbe der aktuellen OpenGL Farbe haben (von keiner, was wahr wäre, wenn wir PixMaps verwenden würden).

    // Nun erzeugen wir die Display Liste
    glNewList(list_base+ch,GL_COMPILE);

    glBindTexture(GL_TEXTURE_2D,tex_base[ch]);

    // als erstes müssen wir uns etwas nach oben bewegen, so dass
    // die Zeichen die richtige Menge Platz zwischen
    // und vor den Zeichen haben.
    glTranslatef(bitmap_glyph->left,0,0);

    // Nun bewegen wir uns etwas nach unten, für den Fall, dass
    // das Bitmap über die untere Linie geht
    // Das ist nur für Zeichen wir 'g' oder 'y' der Fall.
    glPushMatrix();
    glTranslatef(0,bitmap_glyph->top-bitmap.rows,0);

    // Nun müssen wir etwas korrigieren, da vieles unserer Textur 
    // einfach nur leerer aufgefüllter Platz ist.
    // Wir finden heraus, welche Teile der Textur vom eigentlichen
    // Zeichen verwendet werden und speichern die Informationen in den
    // Variablen x und y, dann zeichnen wir den Quad
    // Quad, wir werden nur die Teile der Textur referenzieren, welche
    // das eigentliche Zeichen enthält.
    float   x=(float)bitmap.width / (float)width,
    y=(float)bitmap.rows / (float)height;

    // Hier zeichnen wir die texturierten Quads.
    // Das Bitmap welches wir von FreeType erhalten haben, ist nicht ganz 
    // so ausgerichtet wie wir es gerne hätten,
    // aber wir linken die Textur auf den Quad
    // Auf diese Weise wird das Ergebniss korrekt ausgerichtet.
    glBegin(GL_QUADS);
    glTexCoord2d(0,0); glVertex2f(0,bitmap.rows);
    glTexCoord2d(0,y); glVertex2f(0,0);
    glTexCoord2d(x,y); glVertex2f(bitmap.width,0);
    glTexCoord2d(x,0); glVertex2f(bitmap.width,bitmap.rows);
    glEnd();
    glPopMatrix();
    glTranslatef(face->glyph->advance.x >> 6 ,0,0);

    // Inkrementiere die Raster Position so, als ob wir ein Bitmap Font wären.
    // (wird nur benötigt, wenn Sie die Text Länge berechnen wollen)
    // glBitmap(0,0,0,0,face->glyph->advance.x >> 6,0,NULL);

    // beende die Display List
    glEndList();
}

Die nächste Funktion die wir erzeugen werden, wird make_dlist verwenden, um ein paar Display-Listen zu dem korrespondierenden Font-File und der Pixel Höhe zu erzeugen.

FreeType verwendet TrueType Fonts, so dass Sie vielleicht selbst ein paar TrueType Font Dateien sich suchen und sie mit dieser Funktion verwenden. TrueType Font Dateien sind sehr gebräuchlich und es gibt eine Menge Seiten im Netz, wo Sie viele verschiedene TrueType Fonts kostenlos herunterladen können. Windows 98 hat TrueType für fast all seine Fonts verwendet, wenn Sie also einen alten Computer finden, auf dem noch Windows 98 läuft, können Sie alle Standard Fonts im Truetype Format dort im windows/fonts Verzeichnis bekommen.

void font_data::init(const char * fname, unsigned int h) {
    // Alloziere etwas Speicher um die Textur Ids zu speichern.
    textures = new GLuint[128];

    this->h=h;

    // erzeuge und initialisiere eine FreeType Font Library.
    FT_Library library;
    if (FT_Init_FreeType( &library ))
        throw std::runtime_error("FT_Init_FreeType failed");

    // Das Objekt in welchem FreeType die Informationen über den
    // Font speichert, wird ein "face" (eine Seite/Gesicht...) genannt.
    FT_Face face;

    // Hier laden wir die Font Informationen aus der Datei.
    // Von allen Möglichkeiten, wo der Code fehl schlagen könnte, ist die hier die wahrscheinlichste,
    // da FT_New_Face fehl schlagen wird, wenn die Font Datei nicht existiert oder irgendwie beschädigt ist.
    if (FT_New_Face( library, fname, 0, &face ))
        throw std::runtime_error("FT_New_Face failed (there is probably a problem with your font file)");

    // aus unerklärlichen Gründen ist die Masseinheit der Font Größe in Freetype in
    // 1/64tel Pixeln angegeben. Deshalb müssen wir, wenn wir einen Font
    // h Pixel hoch haben wollen, die Größe auf h*64 anpassen.
    // (h 
    FT_Set_Char_Size( face, h // hier fragen wir OpenGL an, Ressourcen für all die 
    // Texturen und Display-Listen die wir
    // erzeugen wollen, zu allozieren.  
    list_base=glGenLists(128);
    glGenTextures( 128, textures );

    // Hier erzeugen wir die einzelnen Fonts Display-Listen.
    for(unsigned char i=0;i// Nun, wo die Display Liste erzeugt wurde, benötigen wir die Face Informationen
// nicht mehr, weshalb wir die damit verbundenen Ressourcen wieder freigeben. FT_Done_Face(face); // Ditto für die Font Library. FT_Done_FreeType(library); }

Nun benötigen wir eine Funktion, um alle Display-Listen wieder zu löschen und die entsprechenden Texturen des Fonts.

void font_data::clean() {
    glDeleteLists(list_base,128);
    glDeleteTextures(128,textures);
    delete [] textures;
}

Hier sind zwei kleine Funktionen, die wir definieren, um sie in unserer Print-Funktion zu verwenden. Die Print-Funktion wird in Pixel-Koordinaten (auch Fenster-Koordinaten genannt), denken wollen, weshalb wir auf eine Projektionsmatrix umschalten müssen, damit wir alles in Pixel-Koordinaten angeben können.

Wir benutzen hier zwei sehr nützliche OpenGL Funktionen, glGet, um die Fenster-Dimensionen zu ermitteln und glPush / PopAttrib, um sicher zu stellen, den Matrix-Modus so wieder zu verlassen, wie wir ihn vorgefunden haben. Wenn Sie mit diesen Funktionen nicht vertraut sind, wäre Ihre Zeit sicherlich gut angelegt, wenn Sie sich diese in Ihrem bevorzugten OpenGL Referenz Handbuch nachschlagen würden.

// Eine ziemlich einfache Funktion, die eine Projektions Matrix
// pusht, um die Objekt-Welt-Koordinaten identisch 
// mit den Fenster-Koordinaten zu machen.
inline void pushScreenCoordinateMatrix() {
    glPushAttrib(GL_TRANSFORM_BIT);
    GLint   viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport);
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3]);
    glPopAttrib();
}

// Poppt die Projektions Matrix, ohne den aktuellen
// MatrixMode zu ändern.
inline void pop_projection_matrix() {
    glPushAttrib(GL_TRANSFORM_BIT);
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glPopAttrib();
}

Unsere Printing-Funktion sieht fast so aus wie die aus Lektion 13, aber es gibt ein paar wichtige Unterschiede. Die OpenGL Enable-Flags (Aktivierungs-Flags), die wir setzen, sind anders, was die Tatsache wiederspiegelt, dass wir 2 Channel Texturen verwenden anstatt Bitmaps. Wir bearbeiten auch die Textzeile etwas, die wir übergeben bekommen, um Zeilenumbrüche richtig zu händeln. Da wir solch gute Samariter sind, achten wir darauf, OpenGLs Matrix und Attribute Stack zu nutzen, um sicherzustellen, dass alle Funktion rückgängig gemacht werden, die an OpenGL's internem Status gemacht werden (damit vermeiden wir, dass jemand plötzlich herausfinden muss, der die Funktion benutzt, dass zum Beispiel die Modelview Matrix sich auf mysteriöse Weise geändert hat).

// fast das gleiche wie NeHe's glPrint Funktion, aber etwas modifiziert,
// damit sie auch mit FreeType Fonts funktioniert.
void print(const font_data &ft_font, float x, float y, const char *fmt, ...)  {

    // Wir wollen ein Koordinaten-System, wo die Entfernung in Fenster-Pixeln gemessen wird.
    pushScreenCoordinateMatrix();

    GLuint font=ft_font.list_base;
    // Wir machen die Höhe etwas größer. So wird es etwas Abstand zwischen den Zeilen geben.
    float h=ft_font.h/.63f;
    char    text[256];                                    // enthält unseren String
    va_list    ap;                                        // Zeiger auf die Argumentenliste

    if (fmt == NULL)                                    // Wenn da kein Text ist, 
        *text=0;                                    // mache nichts
    else {
        va_start(ap, fmt);                                // Parse den String für Variablen
        vsprintf(text, fmt, ap);                            // und konvertiere Symbole in die tatsächlichen Zahlen
        va_end(ap);                                    // Das Ergebniss wir in Text gespeichert
    }

    // hier kommt etwas Code, um den Text den wir bekommen haben, 
    // in mehrere Zeilen zu splitten.
    // Das könnte wesentlich eleganter gemacht werden, indem eine
    // Regular Expression Library benutzt werden würde, so wie sie unter
    // boost.org verfügbar ist (Ich habe das nun nur von Hand gemacht, um Komplikationen
    // in diesem Tutorial mit unnötigen Library-Abhängikeiten zu vermeiden).
    const char *start_line=text;
    vector<string> lines;
    for(const char *c=text;*c;c++) {
        if(*c=='\n') {
            string line;
            for(const char *n=start_line;n<c;n++) line.append(1,*n);
            lines.push_back(line);
            start_line=c+1;
        }
    }
    if(start_line) {
        string line;
        for(const char *n=start_line;n<c;n++) line.append(1,*n);
        lines.push_back(line);
    }

    glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT  | GL_ENABLE_BIT | GL_TRANSFORM_BIT);
    glMatrixMode(GL_MODELVIEW);
    glDisable(GL_LIGHTING);
    glEnable(GL_TEXTURE_2D);
    glDisable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glListBase(font);

Da wir texturierte Quads verwenden, wird jede Transformation, die wir auf die Modelview Matrix anwenden, bevor wir unseren glCallLists Aufruf machen, auf den Text selbst angewandt. Das bedeutet, dass man den Text rotieren oder skalieren kann (ein weiterer Vorteil gegenüber von WGL Bitmaps). Der einfachste Weg, um einen Vorteil aus dieser Tatsache zu haben, wäre es, die aktuelle Modelview Matrix allein zu lassen, so dass jede Transformation gemacht wird, bevor die Print-Funktion auf den Text angewand wird. Aber auf Grund der Art, wie wir die Modelview Matrix verwenden, um unsere Font Position zu setzen, wird das nicht funktionieren. Unsere nächstbeste Option ist es, eine Kopie der Modelview Matrix zu speichern, die uns übergeben wird und sie zwischen glTranslate und glCallLists anzuwenden. Das ist ziemlich einfach zu machen, da wir den Text aber mit einer speziellen Projektions Matrix zeichnen müssen, wird der Effekt der Modelview Matrix etwas anders sein, als man erwartet - alles wird als Skalierung von Pixeln interpretiert. Wir könnten das Problem komplett umgehen, indem wir nicht die Projektions-Matrix innerhalb von Print resetten. Das ist vielleicht eine gute Idee in einigen Situation - aber wenn Sie es machen, stellen Sie sicher, dass die Fonts auf eine annehmbare Größe skaliert wird (sie sollten ungefähr 32x32 sein und Sie wollen vielleicht etwa 0.01x0.01 haben).

    float modelview_matrix[16];
    glGetFloatv(GL_MODELVIEW_MATRIX, modelview_matrix);

    // Hier wird der Text angezeigt.
    // Für jede Textzeile resetten wir die Modelview Matrix, 
    // So dass die Textzeilen an der korrekten Position starten.
    // Beachten Sie, dass wir die Matrix resetten müssen, anstatt nur auf h runter zu translatieren.
    // Das kommt daher, weil jedes Zeichen, dass gezeichnet wird
    // die aktuelle Matrix ändert, so dass das nächste Zeichen
    // direkt dahinter gezeichnet wird.  
    for(int i=0;i<lines.size();i++) {
        glPushMatrix();
        glLoadIdentity();
        glTranslatef(x,y-h*i,0);
        glMultMatrixf(modelview_matrix);

    // Der auskommentierte Raster-Positions-Kram kann nützlich sein, wenn Sie
    // wissen müssen, wie lang der Text ist, den Sie erzeugen.
    // Wenn Sie sich dazu entscheiden, ihn zu benutzen, stellen Sie sicher, dass Sie auch den glBitmap Befehl wieder in
    // make_dlist() verwenden und dieser nicht auskommentiert bleibt.
        // glRasterPos2f(0,0);
        glCallLists(lines[i].length(), GL_UNSIGNED_BYTE, lines[i].c_str());
        // float rpos[4];
        // glGetFloatv(GL_CURRENT_RASTER_POSITION ,rpos);
        // float len=x-rpos[0]; (nimmt an, dass keine Rotation gemacht wurde)

        glPopMatrix();
    }

    glPopAttrib();

    pop_projection_matrix();
}

}                                                // Schließe den Namespace

Die Library ist nun fertig. Öffnen Sie Lektion13.cpp und wir werden daran ein paar kleinere Änderungen vornehmen, um die Funktionen zu demonstrieren, die wir gerade geschrieben haben.

Unter den vorhandenen Headern fügen Sie den freetype.h Header ein.

#include "freetype.h"                                        // Header für unsere kleine Font Library.

Und wenn wir schon hier sind, erzeugen wir noch gleich ein globales font_data Objekt.

// Dies enthält all die Informationen für unseren Font, den wir erzeugen werden.
freetype::font_data our_font;

Nun müssen wir uns um das erzeugen und zerstören der Ressourcen unseres Fonts kümmern. Deshalb fügen Sie die folgende Zeile am Ende von InitGL ein:

our_font.init("Test.ttf", 16);                                    // erzeuge den FreeType Font

Und fügen Sie diese Zeile am Anfang von KillGLWindow ein, um den Font wieder zu zerstören, wenn wir fertig sind.

our_font.clean();

Nun müssen wir die Funktion DrawGLScene modifizieren, so dass sie unsere Print-Funktion verwendet. Das hätte eine ganz einfache Zeile sein können, die "hello world" ausgibt, aber ich war etwas kreativer, da ich Ihnen Rotation und Skalierung zeigen wollte.

int DrawGLScene(GLvoid)                                        // Hier kommt der ganze Zeichnen-Kram hin
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);                    // löscht Screen und Depth Buffer
    glLoadIdentity();                                    // Resettet die aktuelle Modelview Matrix
    glTranslatef(0.0f,0.0f,-1.0f);                                // bewege eine Einheit in den Screen hinein

    // Blauer Text
    glColor3ub(0,0,0xff);

    // Positioniere den WGL Text auf dem Screen
    glRasterPos2f(-0.40f, 0.35f);
     glPrint("Active WGL Bitmap Text With NeHe - %7.2f", cnt1);    // gebe GL Text auf dem Screen aus

    // Hier geben wir etwas Text aus, unter der Benutzung unseres Freetype Fonts
    // Der einzig wirklich wichtige Befehl ist der eigentliche Print() Aufruf,
    // Aber um die Resultate etwas interessanter zu gestalten,
    // Habe ich etwas Code für Rotation und Skalierung des Textes eingefügt.

    // Roter Text
    glColor3ub(0xff,0,0);

    glPushMatrix();
    glLoadIdentity();
    glRotatef(cnt1,0,0,1);
    glScalef(1,.8+.3*cos(cnt1/5),1);
    glTranslatef(-180,0,0);
    freetype::print(our_font, 320, 200, "Active FreeType Text - %7.2f", cnt1);
    glPopMatrix();

    // Um die Fähigkeit von Print zu testen, wie sie mit neuen Zeilen umgeht, fügen Sie die folgende Kommentarzeile in den Code ein.
    // freetype::print(our_font, 320, 200, "Here\nthere\nbe\n\nnewlines\n.", cnt1);

    cnt1+=0.051f;                                        // Inkrementiere den ersten Zähler
    cnt2+=0.005f;                                        // Inkrementiere den zweiten Counter
    return TRUE;                                        // Alles verlief OK
}

Als letztes müssen wir noch etwas Exception-Handling Code einfügen. Gehen Sie zur WinMain und fügen Sie ein try { .. } statement am Anfang der Funktion ein.

    MSG    msg;                                        // Windows Nachrichten Struktur
    BOOL    done=FALSE;                                    // Bool Variable um die Schleife zu beenden

    try {                                            // benutze Exception Handling

Dann fügen Sie am Ende der Funktion ein catch {} Befehl ein.

    // Shutdown
    KillGLWindow();                                        // Kill das Fenster

    // Catche jegliche Exception, die geworfen wurde
    } catch (std::exception &e) {
        MessageBox(NULL,e.what(),"CAUGHT AN EXCEPTION",MB_OK | MB_ICONINFORMATION);
    }

    return (msg.wParam);                                    // beende das Programm
}

Wenn jetzt jemal eine Exception auftreten sollte, wird uns eine kleine Message Box angezeigt, die uns mitteilt, was passiert ist. Beachten Sie, dass Exception-Handling Ihren Code verlangsamen kann, wenn Sie also eine Release Version Ihres Programmes kompilieren, möchten Sie vielleicht unter Project->Settings->C/C++, unter der Kategorie "C++ Language" Exception Handling ausschalten.

So, dass war's! Kompilieren Sie das Programm und Sie sollten ein paar nette FreeType gerenderte Texte sehen, die sich unterhalb des original Bitmap-Text aus Lektion 13 bewegen.

Allgemeine Anmerkungen

Es gibt ein ganze Zahl an Verbesserungmöglichkeiten, die Sie vielleicht an dieser Library vornehmen wollen. Zum einen ist das direkte benutzen der Font Daten-Objekte etwas aufwändig, weshalb Sie vielleicht einen Standard Cache für Fonts erzeugen wollen, um das Font-Ressourcen-Management vor dem Benutzer zu verstecken. Vielleicht wollen Sie auch die Hilfe von der OpenGL Library selber in Anspruch nehmen und einen Font Stack erzeugen, womit Sie ein übergeben von Referenzen auf Font Objekte, jedes Mal wenn Sie die Print Funktion aufrufen, vermeiden. (Das sind alles Dinge, die ich zur Zeit in meinem eigenen Code mache, aber sie der Einfachheit halber aus diesem Tutorial rausgelassen habe). Sie wollen vielleicht auch eine Version von Print machen, die den Text zentriert - dafür werden Sie wahrscheinlich die Technicken verwenden müssen, die folgend diskutiert werden.

Zur Zeit lasse ich den Text um sein Zentrum rotieren. Wie dem auch sei, um einen Effekt wie diesen für jeden willkürlichen Text zu erhalten, müssten Sie eine Möglichkeit kennen, die Sie die genaue Länge der Textzeile wissen lässt - das kann etwas trickreich sein. Eine Möglichkeit um die Länge des Textes herauszufinden, ist, glBitmap Befehle in die Font Display Listen einzufügen, um die Raster Position sowie die Modelview Matrix zu modifizieren (ich habe die benötigte Zeile im Code gelassen, aber sie ist auskommentiert). Dann können Sie die Raster Position auf x,y setzen bevor Sie glCallLists verwenden und glGet benutzen, um die Raster Position herauszufinden, nachdem der Text gezeichnet wurde - die Differenz der Raster Positionen gibt Ihnen die Länge des Textes in Pixeln.

Sie sollten daran denken, dass FreeType Fonts wesentlich mehr Speicher einnehmen als WGL's Bitmap Fonts (das ist ein Vorteil von binären Bilder, sie nehmen nur sehr wenig Platz ein). Wenn Sie aus irgend einem Grund an Texturspeicher sparen müssen, sollten Sie lieber bei dem Code aus Lektion 13 bleiben.

Ein weiterer interessanter Vorteil beim benutzen von texturierten Quads um Fonts darzustellen, ist, dass Quads, anders als Bitmaps, mit den OpenGL Picking-Funktionen funktionieren (siehe Lektion 32 ). Das macht einem das Leben wesentlich einfacher, wenn Sie Text erzeugen wollen, der darauf reagiert, wenn jemand mit der Maus darüber fährt oder darauf klickt. (Um WGL Fonts mit Picking zu verwenden ist möglich, wieder ist der Truck, Raster Koordinaten zu verwenden, um die Länge des Textes in Pixeln heraus zu finden).

Und zum Schluss kommen hier noch ein paar Links zu OpenGL Font Libraries. Abhängig von Ihren Zielen und vom Compiler möchten Sie vielleicht eine von diesen, anstatt meines Codes verwenden (es gibt noch viel mehr, ich habe allerdings nur die aufgeführt mit denen ich selbe etwas Erfahrung gemacht habe).

GLTT Diese Library ist eine alte Library die scheinbar nicht mehr weiterentwickelt wird, aber die ein paar sehr positive Resonanzen bekommen hat. Basiert auf FreeType1. Ich denke, Sie müsste eine Kopie der alten FreeType1 Source Distribution finden, um ihn mit MSVC6 kompilieren zu können. Download ist unter http://www.opengl.org/developers/faqs/technical/fonts.htm verfügbar.

OGLFT Eine nette FreeType2 basierte Font Library, es benötigt allerdings etwas Arbeit, um sie unter MSVC zu kompilieren (hauptsächlich typische for-Schleifen Scope Probleme). Scheint für Linux Maschinen gemacht worden zu sein... http://oglft.sourceforge.net.

FTGL Eine weitere dritte auf FreeType basierte Font Library, diese wurde eindeutig für OS X entwickelt. http://homepages.paradise.net.nz/henryj/code/#FTGL.

FNT Eine non-FreeType Library die Teil von PLIB ist. Scheint ein nettes Interface zu haben, benutzt sein eigenes Font Format, kompiliert unter MSVC6 mit nur wenig Aufwand... http://plib.sourceforge.net/fnt.

Sven Olsen

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

* DOWNLOAD Borland C++ Builder 6 Code für diese Lektion. ( Conversion by Galileo Sjodin )
* DOWNLOAD Code Warrior 5.3 Code für diese Lektion. ( Conversion by Scott Lupton )
* DOWNLOAD Dev C++ Code für diese Lektion. ( Conversion by mt-Wudan )
* DOWNLOAD Linux/SDL Code für diese Lektion. ( Conversion by Aaron Graves )
* DOWNLOAD Python Code für diese Lektion. ( Conversion by Brian Leair )
* DOWNLOAD Visual Studio .NET Code für diese Lektion. ( Conversion by Joachim Rohde )



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