Lekcja 17. Czcionki z bitmapy
Autor: Stanisław Michał 'black_maq' Warych
Oryginał: 2D Texture Mapped Fonts (Jeff 'NeHe' Molofee & Ben Humphrey)
Źródła: http://nehe.gamedev.net/data/lessons/vc/lesson17.zip

Lekcja powstała dzięki NeHe & Giuseppe D'Agata...

Wszyscy pewnie mają już dość czcionek. Lekcje, które wcześniej zrobiłem nie tylko wyświetlały tekst, wyświetlały także tekst 3D, oteksturowane czcionki i mogły wyświetlać zmienne. Ale co jeżeli chcesz przenieść swój projekt na maszynę, która nie wspiera ani czcionek rastrowych, ani konturowych?

Dzięki Giuseppe D'Agata mamy kolejną lekcję o czcionkach. Pewnie pytasz co jeszcze może być na ten temat. Jeśli pamiętasz to przy pierwszej lekcji wspomniałem o używaniu tekstur przy rysowaniu liter na ekranie. Zazwyczaj kiedy używasz tekstur to odpalasz jakiś program garficzny, wybierasz czcionkę i piszesz tekst jaki chcesz póżniej wyświetlić. Następnie zapisujesz bitmapę i ładujesz ją do swojego programu jako teksturę. Niezbyt skuteczne dla programu, który potrzebuje dużo tekstu lub tekst się zmienia!

Ten program używa zaledwie JEDNEJ tekstury aby wyświetlić którykolwiek z 256 znaków na ekranie. Twój znak ma zaledwie 16 pikseli wysokości i 16 szerokości. Jeśli weźmiemy standardową teksturę 256x256 to zauważysz, że będziemy mieli 16 kolumn i 16 wierszy, czyli razem będziemy mieli 256 znaków, inaczej znak ma 16 pikseli szerokosci (wysokosci), więc 256 / 16 = 16 :).

Więc...Stwórzmy demonstrację czcionek ładowanych z tekstury! Program bazuje na kodzie z lekcji pierwszej. W pierwszej części programu dołączamy nagłówki math oraz stdio. Potrzebujemy math do poruszania tekstu po ekranie za pomocą sinusa i cosinusa, a stdio potrzebujemy aby upewnić się, że tekstura, z której chcemy zrobić czcionkę istnieje.

#include <windows.h>         // Nagłówek windows
#include <math.h>         // Nagłówek dla biblioteki math    ( NOWE )
#include <stdio.h>         // Nagłówek wejścia/wyjścia ( NOWE )
#include <gl\gl.h>         // Nagłówek dla biblioteki OpenGL32
#include <gl\glu.h>         // Nagłówek dla biblioteki glu32
#include <gl\glaux.h>         // Nagłówek dla biblioteki Glaux
HDC        hDC=NULL;         // Kontekst urządzenia
HGLRC        hRC=NULL;         // Kontekst rysujący
HWND        hWnd=NULL;         // Uchwyt okna
HINSTANCE    hInstance;         // Instancja aplikacji
bool    keys[256];         // Tablica do obługi klawiatury
bool    active=TRUE;         // Aktywność okna ustawiona na TRUE
bool    fullscreen=TRUE;         // Tryb pełnoekranowy ustawiony na TRUE

Stworzymy zmienną base do przetrzymywania list wyświetlania oraz zmienną texture[2] do przechowywania dwóch tekstur, które stworzymy. Pierwsza tekstura będzie teksturą z czcionką, a druga będzie zdarzeniową teksturą użytą do stworzenia prostego obiektu 3D.

Dodamy zmienną loop, którą będziemy wykorzystywać przy pętlach. Na końcu utworzymy dwa liczniki cnt1 i cnt2 do poruszania tekstu po ekranie i do obracania naszego obiektu 3D.

GLuint    base;         // Listy wyświetlania dla czcionki
GLuint    texture[2];         // Przechowuje tekstury
GLuint    loop;         // Zmienna pętel
GLfloat    cnt1;         // Licznik do poruszania i kolorowania
GLfloat    cnt2;         // Licznik do poruszania i kolorowania
LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);         // Deklaracja WndProc

Teraz kod ładujący teksturę. Jest dokładnie taki sam jak w poprzedniej lekcji o oteksturowanych czcionkach.

AUX_RGBImageRec *LoadBMP(char *Filename)         // Ładuje obraz bitmapy
{
    FILE *File=NULL;         // Uchwyt pliku
    if (!Filename)         // Upewniamy się, że nazwa została podana
    {
        return NULL;         // Jeśli nie, zwracamy NULL
    }
    File=fopen(Filename,"r");         // Sprawdzamy czy plik istnieje
    if (File)         // Istnieje?
    {
        fclose(File);         // Zamykamy uchwyt
        return auxDIBImageLoad(Filename);         // Ładujemy bitmapę i zwracamy wskaźnik
    }
    return NULL;         // Jeśli się nie udało zwracamy NULL
}

Następny kawałek kodu jest lekko zmieniony w stosunku do poprzedniego. Jeśli nie jesteś powien co robią poszczególne linijki to wróć się i przypomnij sobie.

Zapamiętaj, że TextureImage[] przechowuje zapisy dwóch obrazów RGB. bardzo ważne jest dwukrotne sprawdzenie kodu powiązanego z łądowaniem i przechowywaniem tekstur. Jeden zły numer może spowodować w naszej pamięci ruinę!

int LoadGLTextures()         // Ładuje bitmapę i konwertuje na teksturę
{
    int Status=FALSE;         // Wskaźnik statusu
    AUX_RGBImageRec *TextureImage[2];         // Tworzy miejsce przechowywania tekstur

Następna linia jest bardzo ważną linią przy sprawdzaniu. Jeśli zastąpiłbyś liczę 2 jakąkolwiek inną, pojawiłby się główny problem. Sprawdź dwa razy ! Ten numer powinien być taki sam jaki użyłeś przy TextureImages[].

Dwie tekstury jakie zamierzamy załadować to font.bmp (basza czcionka) i bumps.bmp. Druga tekstura może być zastąpiona jaką chcesz teksturą. Tekstura, którą stworzyłem nie jest zbyt kreatywna.

    memset(TextureImage,0,sizeof(void *)*2);         // Ustawiamy wskaźnik na NULL
    if ((TextureImage[0]=LoadBMP("Data/Font.bmp")) &&         // Ładujemy teksturę z czcionką
        (TextureImage[1]=LoadBMP("Data/Bumps.bmp")))         // Ładujemy drugą teksturę
    {
        Status=TRUE;         // Ustawiamy Status na TRUE

Kolejna ważna linia do podwójnego sprawdzenia. Nie mogę określić jak wiele e-maili dostałem od ludzi pytających <span class="i">"Dlaczego widzę tylko jedną teksturę albo dlaczego są białe ?!". Zazwyczaj ta linia jest problemem. Jeśli zastąpiłeś 2 przez 1 to tylko jedna tekstura będzie stworzona, druga będzie cała biała. Jeśli zastąpisz 2 przez 3 Twój program się posypie!

Powinieneś wywołać glGenTextures() tylko raz. Po glGenTextures() powinienieś wygenerować wszystkie swoje tekstury. Widziałem ludzi dających glGenTextures() przed stworzeniem każdej tekstury. Zazwyczaj powodowało to nadpisanie wcześniej utworzonej tekstury przez nową. Dobrą rzeczą jest wiedzieć ile tekstur chcesz wygenerować, wywołać raz glGenTextures() i wtedy generować wszystkie tekstury. Nie jest mądrą rzeczą wstawianie glGenTextures() w pętlę dopóki neie masz powodu aby to zrobić.

        glGenTextures(2, &texture[0]);         // Tworzy dwie tekstury
        for (loop=0; loop<2; loop++)         // Pętla przez wszystkie tekstury
        {
        // Generuje tekstury
            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);
        }
    }

Następne linijki sprawdzą czy dane bitmap zostały załadowane w celu wygenerowania tekstury i czy dalej zajmują miejsce. Jeżeli są to zwalniamy je. Zauważ, że zwalniamy obydwie obrazki. Jeśli użyliśmy 3 obrazków do zbudowania teksutry to musi zwolnić 3 obrazy.

    for (loop=0; loop<2; loop++)
        {
     if (TextureImage[loop])         // Jeśli tekstura istnieje
            {
                if (TextureImage[loop]->data)         // Jeśli istnieje jej obraz
                {
                    free(TextureImage[loop]->data);         // To zwalniamy go
                }
                free(TextureImage[loop]);         // I zwalniamy strukturę tekstury
            }
        }
    return Status;         // Zwraca Status
}

Teraz budujemy naszą czcionkę. Opowiem o tej części kodu z pewnymi szczegółami. To nie jest takie skomplikowane, ale jest trochę matematyki do zrozumienia i wiem, że matematyka nie jest tym co wszyscy uwielbiają.

GLvoid BuildFont(GLvoid)         // Buduje listy wyświetlania czcionki
{

Te dwie zmienne będą używane do przechowywania pozycji każdej litery w teksturze. cx będzie pozycją od lewej do prawej w teksturze, natomiast cy pozycją z góry na dół.

    float    cx;         // Przechowuje współrzędne szerokości
    float    cy;         // Przechowuje współrzędne wysokości

Następnie powiemy OpenGL, że chcemy zbudować 256 list wyświetlania. Zmienna base jest wskaźnikiem na pierwszą listę. Drugą listą jest base+1, trzecią base+2 itd.

Druga linijka kodu poniżej wybiera teksture naszej czcionki (texture[0]).

    base=glGenLists(256);         // Tworzy 256 list wyświetlania
    glBindTexture(GL_TEXTURE_2D, texture[0]);         // Wybiera teksturę czcionki

Teraz rozpoczniemy naszą pętlę. Pętla zbuduje wszystkie 256 znaków, przechowując każdy znak w jego własnej liście.

    for (loop=0; loop<256; loop++)         // Pętla przez wszystkie 256 znaków
    {

Pierwsza linia poniżej pewnie wygląda trochę zagadkowo. Symbol % oznacza resztę dzielenia przez 16. cx przemieszcze nas po teksturze od lewej do prawej. Zauważysz później w kodzie, że odjęliśmy cy od 1 żeby przemieszczać nas z góry na dół zamiast od dołu do góry. Symbol % jest dosyć trudny do wytłumaczenia ale spróbuję

Jesteśmy zainteresowani tym kawałkiem (loop%16) /16.0f po porstu konwertuje wynik na współrzędne tekstury. Więc jeśli loop było równe 16... cx mogłoby być resztą z 16/16 więc byłoby równe 0, a cy 16/16 czyli równe 1. Więc przemieścilibyśmy się w dół o jedną literę, ale nie ruszylibyśmy się w prawo. I tak dla kolejnych przypadków, dla loop = 17 cx = 0.0625 cy = 0.0625, dla loop=18 cx = 0.125 cy = 0.0625, dla loop = 32 cx = 0 cy = 0.125. Czyli prościej mówiać dzięki wykorzystując resztę z dzielenia lecimy od lewego brzego, przelatujemy linijkę i zmieniamy ją itd. Proste, no nie?

        cx=float(loop%16)/16.0f;         // Pozycja X znaku
        cy=float(loop/16)/16.0f;         // Pozycja Y znaku

Uff :) OK. Więc teraz budujemy naszą czcionkę wybierając pojedyncze znaki z naszej tekstury w zależności od cx i cy. W linii poniżej dodamy do base wartość loop, jeśli tego nie zrobimy to każda literaz będzie wygenerowana w pierwszej liście wyświetlania. Nie chcemy, aby to się stało więc dodając loop do base, każdy kolejny znak jest przetrzymywany w kolejnej liście wyświetlania.

        glNewList(base+loop,GL_COMPILE);         // Budujemy listę

Teraz, gdy wybraliśmy, którą listę wyświetlania chcemy utworzyć, tworzymy nasz znak. Wystarczy narysować kwadrat i oteksturować go fragmentem z naszą czcionką z naszej tekstury.

            glBegin(GL_QUADS);         // Używamy kwadratów dla każdego znaku

cx i cy powinny przechowywać bardzo małe wartości od 0.0f do 1.0f. Jeśli cx i cy są równe 0 to pierwsza linijka kodu poniżej powinna wyglądać tak: glTexCoord2f(0.0f,1-0.0f-0.0625f). Pamiętaj, że 0.0625 to 1/16 (jedna szesnasta) naszej tekstury, lub wysokość/szerokośc naszej litery. Współrzędne tekstury poniżej powinny być jej lewym dolnym rogiem.

Zauważ, że używamy glVertex2i(x,y) zamiast glVertex3f(x,y,z). Nasza czcionka jest czcionką 2D, więc nie potrzebujemy wartości z. Ponieważ używamy rzutowania ortograficznego, nie musimy przekładać tego na ekran. Jedyne co musisz zrobić aby rysować na "ortograficznym ekranie" jest podanie współrzędnych x, y. Ponieważ nasz ekran jest pikselami od 0 do 639 i od 0 do 479, nie musimy używać zmiennych typu float ani wartości ujemnych :).

Sposób w jaki ustawimy nasz ekran to taki, że punkt (0,0) będzie lewym dolnym rogiem ekranu, a (640,480) będzie prawym górnym. 0 jest lewą częścią ekranu na osi X, a 639 prawą na osi X. 0 jest na osi Y dolną częścią ekranu, a 479 jest górną częścią ekranu na osi Y. W prosty sposób nie mamy ujemnych wartości :). To jest także przydatne dla ludzi, którzy nie dbają o perspektywę i wolą pracować z pikselami niż z jednostkami :).

                glTexCoord2f(cx,1-cy-0.0625f);         // Współrzędna tekstury (lewa dolna)
                glVertex2i(0,0);         // Współrzędna wierzchołka (lewy dolny)

Następna współrzędna tekstury to 1/16 na prawo od ostatniej współrzędnej (szerokość jednego znaku). Czyli to będzie dolny prawy punkt tekstury.

                glTexCoord2f(cx+0.0625f,1-cy-0.0625f);         // Współrzędna tekstury (prawa dolna)
                glVertex2i(16,0);         // Współrzędna wierzchołka (prawy dolny)

Trzecią współrzędną ma taką samą prawą współrzędną jak poprzednia, ale przenosimy ją w górę. To będzie prawy górny punkt.

                glTexCoord2f(cx+0.0625f,1-cy);         // Współrzędna tekstury (prawa górna)
                glVertex2i(16,16);         // Współrzędna wierzchołka (prawy górny)

Na końcu przenosimy się w lewo aby ustalić ostatnią współrzędna tekstury w lewym górnym rogu.

                glTexCoord2f(cx,1-cy);         // Współrzędna tekstury (lewa górna)
                glVertex2i(0,16);         // Współrzędna wierzchołka (lewy górny)
            glEnd();         // Koniec budowania naszego kwadratu (znaku)

Na koniec przenosimy się na prawo. Jeśli tego nie zrobimy to litery będą rysowane jedna na drugiej. Ponieważ nasza czcionka bardzo wąska nie będziemy się przenosić 16 piskeli w prawo. Gdybyśmy tak zrobili to byłyby duże przerwy między literami. Przeniesienie tylko o 10 pikseli jest w sam raz.

            glTranslated(10,0,0);         // Przenosimy się na prawą stronę znaku
        glEndList();         // Koniec budowania listy
    }         // Pętla do czasu zbudowania 256 znaków
}

Ta część kodu jest taka sama jaką użyliśmy wcześniej do zwalniania listy wyświetlania zanim nasz program się wyłączy.

Wszystkie 256 znaków zaczynająć od base zostanie skasowane. (przydatna rzecz!)

GLvoid KillFont(GLvoid)         // Usuwa czcionkę z pamięci
{
    glDeleteLists(base,256);         // Usuwa wszystkie 256 list wyświetlania
}

Następna część kodu to rysowanie naszego napisu. Wszystko jest zupełnie nowe, więc postaram się wytłumaczyć każdą linijkę bardzo szczegółowo. Mała uwaga: więcej rzeczy może zostać dodanych do tego kodu, jak np. wyświetlanie zmiennych, wielkość znaków, przerwy i wiele innych.

glPrint() zawiera trzy parametry. Pierwszą jest pozycja x na ekranie. Następnym jest pozycja y. następnie jest string (tekst, który chcemy wyświetlić), i ostatecznie zmienna nazwana set. Jeżeli patrzyłeś na bitmapę zrobioną przez Giuseppe D'Agata zauważysz, że są tam dwa różne typy znaków. Pierwszy jest normalny, a drugi z kursywą. Jeśli set jest 0 to pierwszy typ jest wybrany. Jeśli typ jest 1 lub wyższy to wybrany jest drugi typ czcionki.

GLvoid glPrint(GLint x, GLint y, char *string, int set)         // Where The Printing Happens
{

Pierwsza rzecz jaką robimy to upewniamy się, że set jest równe o lub 1, jeżeli jest wyższe niż 1 to ustawiamy, że jest równe 1.

    if (set>1)         // Set jest większe od 1?
    {
        set=1;         // Jeśli tak robimy set równe 1
    }

Teraz wybieramy teksturę czcionki. Robimy to w przypadku, gdy wcześniej wybraliśmy inną teksturą przy rysowaniu czegoś innego.

    glBindTexture(GL_TEXTURE_2D, texture[0]);         // Wybieramy teksturę czcionki

Następnie wyłączamy testowanie głębi (ang. depth testing). Powodem dla którego to robie jest to, że blending dziła wtedy ładniej. Jeśli nie wyłączysz depth testing telst może się kończyć wchodząc za "coś" albo blending może źle wyglądać. Jeśli nie zamierzasz mieszać tekst z ekranem (więc czarne przestrzenie wokół liter będą wokół naszych liter) możesz zostawić depth testing włączony.

    glDisable(GL_DEPTH_TEST);         // Wyłącza depth testing

Następnych kilka linijek jest bardzo ważnych. Wybieramy macierz projekcji. Zaraz po tym używamy polecenia glPushMatrix(). glPushMatrix() przechowuje aktualną macierz (projekcji). Coś jak przycisk "pamięć" na kalkulatorze.

    glMatrixMode(GL_PROJECTION);         // Wybiera macierz projekcji
    glPushMatrix();         // Przechowuje macierz projekcji

Teraz, gdy nasza macierz jest przechowywana, resetujemy macierz i włączamy rzutowanie ortograficzne. pierwszy i trzeci parametr (0) ustawia lewy dolny róg ekranu. Mądrym rozwiązaniem jest ustawienie tego na taką rozdzielczość jaką aktualnie masz. Nie ma głębi więc ustalamy wartość z na -1 i 1.

    glLoadIdentity();         // Resetuje macierz projekcji
    glOrtho(0,640,0,480,-1,1);         // Ustawia rzutowanie ortograficzne

Teraz wybieramy macierz modelu i przechowujemy używając glPushMatrix(). Następnie zresetujemy macierz więc możemy pracować używając rzutowania ortograficznego.

    glMatrixMode(GL_MODELVIEW);         // Wybiera macierz modelu
    glPushMatrix();         // Przechowuje macierz
    glLoadIdentity();         // Resetuje macierz

Z zachowanymi ustawieniami perspektywy i ustawionym rzutowaniem ortograficznym możemy rysować nasz tekst. Zaczniemy od ustalenia pozycji na ekranie, gdzie chcemy rysować. Użyjemy glTranslated() zamiast glTranslatef() ponieważ pracujemy z pikselami, więc nie potrzebujemy float. Przecież nie można mieć połowy piksela :D

    glTranslated(x,y,0);         // Pozycja tekstu (0,0 - lewy dolny róg)

Linijka poniżej wybierze odpowiednią czcionkę w zależności jaką wartość ma parametr set. Jeśli chcemy używać drugiej czcionki to dodamy 128 do aktualnej listy wyświetlania (128 do połowa ze wszystkich 256 znaków). Dodając 128 przeskakujemy pierwsze 128.

    glListBase(base-32+(128*set));         // Wybieramy typ czcionki

Teraz jedyne co nam pozostało to narysować litery na ekranie. Zrobimy to dokłądnie tak samo jak robiliśmy w innych lekcjach o czcionkach. Użyjemy glCallLists(). strlen(string) jest długością naszego tekstu (jak wiele znaków chcemy narysować) , GL_UNSIGNED_BYTE zonacza, że każdy znak jest reprezentowany przez unsigned byte (bajt jest wartością od 0 do 255). W końcu string przechowuje tekst jaki chcemy wyświetlić na ekranie.

    glCallLists(strlen(string),GL_UNSIGNED_BYTE,string);         // Wypisz tekst na ekranie

Teraz wszystko co musimy zrobić to przywrócić nasz widok z perspektywy. Wybieramy macierz projekcji i używamy glPopMatrix() do przywrócenia ustawień poprzedio zapisanych za pomocą glPushMatrix(). Przywróćenie ustawień jest ważne, w przeciwnym razie będziesz je gromadził.

    glMatrixMode(GL_PROJECTION);         // Wybiera macierz projekcji
    glPopMatrix();         // Przywraca starą macierz

Teraz wybieramy macierz modelu i robimy to samo. Używamy glPopMatrix() aby przywrócić poprzednie ustawienia.

    glMatrixMode(GL_MODELVIEW);         // Wybiera macierz modelu
    glPopMatrix();         // Przywraca macierz

Ostatecznie włączamy depth testing. Jeśli wcześniej nie wyłączyłeś depth testing to ta linijka jest zbędna.

    glEnable(GL_DEPTH_TEST);         // Włącza depth testing
}

W ResizeGLScene() nic się nie zmieniło więc przechodzimy do InitGL().

int InitGL(GLvoid)         // Ustawienia OpenGL
{

Przechodzimy do kodu budującego teksturę. Jeśli budowanie się nie uda z nieznanych powodów, zwracamy FALSE.

Dzięki temu nasz program będzie wiedział, że wystąpił błąd i łaskawie się wyłączy.

    if (!LoadGLTextures())         // Przechodzimy do ładowania tekstury
    {
        return FALSE;         // Jeśli był błąd, zwracamy FALSE
    }

Jeśli nie było błędów to budujemy naszą czcionkę. Nie bardzo coś może źle pójść więc nie sprawdzamy błędów.

    BuildFont();         // Budujemy czcionkę

Teraz przejdziemy do normalnych ustawień OpenGL. Ustawiamy kolor tła na czarny, czyszczenie bufora głębi. Wybieramy rodzaj testowania głębi. Włączamy smooth shading i włączamy 2D texture mapping.

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);         // Ustawia kolor tła na czarny
    glClearDepth(1.0);         // Włącza czyszczenie bufora głębi
    glDepthFunc(GL_LEQUAL);         // Typ głębi
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);         // Wybiera tyb blendingu
    glShadeModel(GL_SMOOTH);         // Włącza smooth shading
    glEnable(GL_TEXTURE_2D);         // Włącza 2D Texture Mapping
    return TRUE;         // Inicjalizacja poszła OK
}

Kod poniżej utworzy naszą scenę. Najpierw narysujemy nasz obiekt 3D, a następnie nasz tekst tak żeby pojawiał się na obiekcie inaczej obiekt przysłaniałby tekst. Powodem dla którego dodałem obiekt 3D jest pokazanie, że rzutowanie ortograficzne i perspektywa mogą być używa jednocześnie [tłum. bardzo przydatne :D].

int DrawGLScene(GLvoid)         // Tutaj rysujemy
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);         // Czyści ekran i bufor głębi
    glLoadIdentity();         // Resetuje macierz

Wybraliśmy naszą teksturę bumps.bmp więc możemy zbudować nasz obiekt 3D. Przenosimy się 5 jednostek w ekran żeby zobaczyć nasz obiekt. Obracamy się względem osi Z o 45 stopni. Nasz kwadrat będzie obracał zgodnie z ruchem wskazówek zegara i będzie bardziej wyglądał jak diament niż kwadrat.

    glBindTexture(GL_TEXTURE_2D, texture[1]);         // Wybiera naszą drugą teksturę
    glTranslatef(0.0f,0.0f,-5.0f);         // Przenosimy się w ekran o 5 jednostek
    glRotatef(45.0f,0.0f,0.0f,1.0f);         // Obraca względem osi Z o 45 stopni

Następnie obracamy obiektem względem osi X i Y bazując na zmiennej cnt1. To powoduje, że obiekt wiruje i tworzy efekt wirowania w miejscu.

    glRotatef(cnt1*30.0f,1.0f,1.0f,0.0f);         // Obrót względem osi X i Y

Wyłączamy blending (chcemy aby obiekt był widoczny jako bryła) i ustawiamy kolor na jasny biały. Chcemy narysować oteksturowany, pojedynczy kwadrat.

    glDisable(GL_BLEND);         // Wyłączamy blending
    glColor3f(1.0f,1.0f,1.0f);         // Jasny biały
    glBegin(GL_QUADS);         // Rysujemy pierwszy oteksturowany kwadrat
        glTexCoord2d(0.0f,0.0f);         // Pierwsza współrzędna tekstury
        glVertex2f(-1.0f, 1.0f);         // Pierwszy wierzchołek
        glTexCoord2d(1.0f,0.0f);         // Druga współrzędna tekstury
        glVertex2f( 1.0f, 1.0f);         // Drugi wierzchołek
        glTexCoord2d(1.0f,1.0f);         // Trzecia współrzędna tekstury
        glVertex2f( 1.0f,-1.0f);         // Trzeci wierzchołek
        glTexCoord2d(0.0f,1.0f);         // Czwarta współrzędna tekstury
        glVertex2f(-1.0f,-1.0f);         // Czwarty wierzchołek
    glEnd();         // Koniec rysowania

Po narysowaniu pierwszego obracamy o 90 stopnie względem dwóch osi X i Y, i rysujemy następny. Drugi przecina pierwszy w połowie, tworząc ładny kształt.

    glRotatef(90.0f,1.0f,1.0f,0.0f);         // Obrót względem osi X i Y o 90 stopni
    glColor3f(1.0f,1.0f,1.0f);         // Jasny biały
    glBegin(GL_QUADS);         // Rysujemy drugi oteksturowany kwadrat
        glTexCoord2d(0.0f,0.0f);         // Pierwsza współrzędna tekstury
        glVertex2f(-1.0f, 1.0f);         // Pierwszy wierzchołek
        glTexCoord2d(1.0f,0.0f);         // Druga współrzędna tekstury
        glVertex2f( 1.0f, 1.0f);         // Drugi wierzchołek
        glTexCoord2d(1.0f,1.0f);         // Trzecia współrzędna tekstury
        glVertex2f( 1.0f,-1.0f);         // Trzeci wierzchołek
        glTexCoord2d(0.0f,1.0f);         // Czwarta współrzędna tekstury
        glVertex2f(-1.0f,-1.0f);         // Czwarty wierzchołek
    glEnd();         // Koniec rysowania

Po narysowaniu dwóch kwadratów włączamy blending i rysujemy tekst.

    glEnable(GL_BLEND);         // Włącza blending
    glLoadIdentity();         // Resetuje widok

Użyjemy kodu kolorującego z innej lekcji o czcionkach. Kolor zmienia się stopniowo jak tekst porusza w poprzek ekranu.

        // Pulsujące kolory w zależności od pozycji tekstu
    glColor3f(1.0f*float(cos(cnt1)),1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)));

Następnie rysujemy nasz tekst. Użyjemy glPrint(). Pierwszym parametrem jest pozycja x, drugim pozycja y, a trzecim jest tekst jaki chcemy wyświetlić, natomiast ostatni parametr to typ czcionki jakiego chcemy użyć (0 - normalny, 1 - italic).

Jak zepwne się domyślasz będziemy przemieszczać tekst po ekranie za pomocą sinusa i cosinusa oraz dwóch liczników cnt1 i cnt2. Jeśli nie rozumiesz co robi sinus i cosinus wróć się do poprzedniej lekcji o tekście.

    glPrint(int((280+250*cos(cnt1))),int(235+200*sin(cnt2)),"NeHe",0);         // Wyświetla tekst na ekranie
    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);         // Wyświetla tekst na ekranie

Ustawiamy kolor na ciemny niebieski i pisze imię autora na dole ekranu. Następnie ponownie piszemy jego imię używając jasnych białych liter. Białę litery są lekko na prawo w stosunku do niebieskich. TO tworzy efekt cienia. (Jeśli blending nie został włączony to efekt nie będzie widoczny).

    glColor3f(0.0f,0.0f,1.0f);         // Ustawiamy kolor na niebieski
    glPrint(int(240+200*cos((cnt2+cnt1)/5)),2,"Giuseppe D'Agata",0);         // Rysujemy tekst na ekranie
    glColor3f(1.0f,1.0f,1.0f);         // Ustawiamt kolor na biały
    glPrint(int(242+200*cos((cnt2+cnt1)/5)),2,"Giuseppe D'Agata",0);         // I znowu wyświetlamy tekst

Ostatnia rzecz jaką zrobimy to zwiększenie liczników o różne wartości. Dzięki temu tekst się porusza, a obiekt wiruje.

    cnt1+=0.01f;         // Zwiększamy pierwszy licznik
    cnt2+=0.0081f;         // Zwiększamy drugi licznik
    return TRUE;         // Wszystko poszło OK
}

Kod w KillGLWindow(), CreateGLWindow() i WndProc() się nie zmienił więc go pomijamy.

int WINAPI WinMain(    HINSTANCE    hInstance,         // Instancja
            HINSTANCE    hPrevInstance,         // Poprzednia instancja
            LPSTR        lpCmdLine,         // Parametry komendy
            int        nCmdShow)         // Status okna
{
    MSG        msg;         // Struktura komunikatów okna
    BOOL    done=FALSE;         // Zmienna typu BOOL do obsługi pętli
        // Pytamy usera jaki tryb woli?
    if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
    {
        fullscreen=FALSE;         // Tryb okienkowy
    }

Tytuł naszego okna się zmienił.

        // Tworzy nasze okno
    if (!CreateGLWindow("NeHe & Giuseppe D'Agata's 2D Font Tutorial",640,480,16,fullscreen))
    {
        return 0;         // Zamykamy jeśli okno nie zostało utworzone
    }
    while(!done)         // Dooki done=FALSE
    {
        if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))         // Mamy komunikat?
        {
            if (msg.message==WM_QUIT)         // Otrzymaliśmy komunikat o wyjściu?
            {
                done=TRUE;         // Jeśli tak, done=TRUE
            }
            else         // Jeśli nie, to przekazujemy komunikaty
            {
                TranslateMessage(&msg);         // Przetwarzamy komunikat
                DispatchMessage(&msg);         // Wysyłamy komunikat
            }
        }
        else         // Jeśli nie ma komunikatów
        {
        // Rysuje scenę. Czeka na klawisz ESC lub komunikat o końcu
            if ((active && !DrawGLScene()) || keys[VK_ESCAPE])         // Nie aktywne? Komunikat o końcu?
            {
                done=TRUE;         // ESC lub DrawGLScene poinformowało o końcu
            }
            else         // Jeszcze nie koniec, aktualizuj ekran
            {
                SwapBuffers(hDC);         // Przełącza bufory (podwójne buforowanie)
            }
        }
    }
        // Zamykamy

Ostatnią rzeczą do zrobienia jest dodanie KillFont() na koniec KillGLWindow() tak jak to zrobiłem poniżej. Dodanie tej linii jest bardzo ważne. CZyści pamięć zanim zakończymy program.

    if (!UnregisterClass("OpenGL",hInstance))         // Odrejestrowujemy klasę
    {
        MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        hInstance=NULL;         // Ustawia instancję na NULL
    }
    KillFont();         // Niszczy czcionkę
}

Myślę, że mogę teraz oficjalnie powiedzieć, że moja strona uczy jak wyświetlać tekst na ekranie na każdy możliwy sposób :D. Uważam to jest całkiem dobra lekcja. Kod może być użyty na każdym komputerze z OpenGL [tłum. co ? przecież tu jest WinApi !!! LoL], jest prosty w użyciu i ten sposób jest bardzo mało procesożerny :D (zużywa mało pamięci obliczeniowej).

Chciałbym podziękować Giuseppe D'Agata za originalną wersję tej lekcji. Trochę zmodyfikowałem kod, ale prawdopodobnie bez tego nie zrobiłbym tej lekcji. Jego wersja kodu ma trochę więej opcji takich jak np. ropzstaw znaków itd. Ale ja za to zrobiłem to super obiektem 3D :D.

Mam nadzieję, że wszystkim podobałą się lekcje. Jeśli macie pytania to pisać do mnie albo Giuseppe D'Agata.