Bitmapové fonty nestačí? Potřebujete kontrolovat pozici textu i na ose z? Chtěli byste fonty s hloubkou? Pokud zní vaše odpověď ano, pak jsou 3D fonty nejlepší řešení. Můžete s nimi pohybovat na ose z a tím měnit jejich velikost, otáčet je, prostě dělat vše, co nemůžete s obyčejnými. Jsou nejlepší volbou ke hrám a demům.
Tato lekce je volnými pokračováním té minulé (13). Tehdy jsme se naučili používat bitmapové fonty. 3D písma se vytvářejí velmi podobně. Nicméně... vypadají stokrát lépe. Můžete je zvětšovat, pohybovat s nimi ve 3D, mají hloubku. Při osvětlení vypadají opravdu efektně. Stejně jako v minulé lekci je kód specifický pro Windows. Pokud by měl někdo na platformě nezávislý kód, sem s ním a já napíšu nový tutoriál. Rozšíříme typický kód první lekce.
#include <windows.h>// Hlavičkový soubor pro Windows
#include <math.h>// Hlavičkový soubor pro matematickou knihovnu
#include <stdio.h>// Hlavičkový soubor pro standardní vstup/výstup
#include <stdarg.h>// Hlavičkový soubor pro funkce s proměnným počtem parametrů
#include <gl\gl.h>// Hlavičkový soubor pro OpenGL32 knihovnu
#include <gl\glu.h>// Hlavičkový soubor pro Glu32 knihovnu
#include <gl\glaux.h>// Hlavičkový soubor pro Glaux knihovnu
HDC hDC = NULL;// Privátní GDI Device Context
HGLRC hRC = NULL;// Trvalý Rendering Context
HWND hWnd = NULL;// Obsahuje Handle našeho okna
HINSTANCE hInstance;// Obsahuje instanci aplikace
Base si pamatujete z 13. lekce jako ukazatel na první z display listů ascii znaků, rot slouží k pohybu, rotaci a vybarvování textu.
GLuint base;// Číslo základního display listu znaků
GLfloat rot;// Pro pohyb, rotaci a barvu textu
bool keys[256];// Pole pro ukládání vstupu z klávesnice
bool active = TRUE;// Ponese informaci o tom, zda je okno aktivní
bool fullscreen = TRUE;// Ponese informaci o tom, zda je program ve fullscreenu
GLYPHMETRICSFLOAT gmf[256] ukládá informace o velikosti a orientaci každého z 256 display listů fontu. Dále v lekci vám ukážu, jak zjistit šířku jednotlivých znaků a tím velmi snadno a přesně vycentrovat text na obrazovce.
GLYPHMETRICSFLOAT gmf[256];// Ukládá informace o fontu
Skoro celý kód následující funkce byl použit již ve 13. lekci, takže pokud mu moc nerozumíte, víte, kde hledat informace.
GLvoid BuildFont(GLvoid)// Vytvoření fontu
{
HFONT font;// Proměnná fontu
base = glGenLists(256);// 256 znaků
font = CreateFont(-24,// Výška
0,// Šířka
0,// Úhel escapement
0,// Úhel orientace
FW_BOLD,// Tučnost
FALSE,// Kurzíva
FALSE,// Podtržení
FALSE,// Přeškrtnutí
ANSI_CHARSET,// Znaková sada
OUT_TT_PRECIS,// Přesnost výstupu (TrueType)
CLIP_DEFAULT_PRECIS,// Přesnost ořezání
ANTIALIASED_QUALITY,// Výstupní kvalita
FF_DONTCARE|DEFAULT_PITCH,// Rodina a pitch
"Courier New");// Jméno fontu
SelectObject(hDC, font);// Výběr fontu do DC
Pomocí funkce wglUseFontOutlines() vytvoříme 3D font. V parametrech předáme DC, první znak, počet display listů, které se budou vytvářet a ukazatel na paměť, kam se budou vytvořené display listy ukládat.
wglUseFontOutlines(hDC,// Vybere DC
0,// Počáteční znak
255,// Koncový znak
base,// Adresa prvního znaku
Nastavíme úroveň odchylek, která určuje jak hranatě bude vypadat. Potom určíme šířku nebo spíše hloubku na ose z. 0.0f by byl plochý 2D font. Čím větší číslo přiřadíme, tím bude hlubší. Parametr WGL_FONT_POLYGONS říká, že má OpenGL vytvořit pevné (celistvé) znaky s použitím polygonů. Při použití WGL_FONT_LINES se vytvoří z linek (podobné drátěnému modelu). Je důležité poznamenat, že by se v tomto případě negenerovaly normálové vektory, takže světlo nebude vypadat dobře. Poslední parametr ukazuje na buffer pro uložení informací o display listech.
0.0f,// Hranatost
0.2f,// Hloubka v ose z
WGL_FONT_POLYGONS,// Polygony ne drátěný model
gmf);// Adresa bufferu pro uložení informací.
}
V následují funkci se maže 256 display listů fontu počínaje prvním, který je definován v base. Nejsem si jistý, jestli by to Windows udělaly automaticky. Jeden řádek za jistotu stojí. Funkce se volá při skončení programu.
GLvoid KillFont(GLvoid)// Smaže font
{
glDeleteLists(base, 256);// Smaže všech 256 znaků
}
Tento kód zavoláte vždy, když budete potřebovat vypsat nějaký text. Řetězec je uložen ve "fmt".
GLvoid glPrint(const char *fmt, ...)// Klon printf() pro OpenGL
{
Proměnnou "length" použijeme ke zjištění délky textu. Pole "text" ukládá konečný řetězec pro vykreslení. Třetí proměnná je ukazatel do parametrů funkce (pokud bychom zavolali funkci s nějakou proměnnou, "ap" na ni bude ukazovat.
float length=0;// Délka znaku
char text[256];// Konečný řetězec
va_list ap;// Ukazatel do argumentů funkce
if (fmt == NULL)// Pokud nebyl předán řetězec
return;// Konec
Následující kód konvertuje veškeré symboly v řetězci (%d, %f ap.) na znaky, které reprezentují číselné hodnoty v proměnných. Poupravovaný text se uloží do řetězce text.
va_start(ap, fmt);// Rozbor řetězce pro proměnné
vsprintf(text, fmt, ap);// Zamění symboly za čísla
va_end(ap);// Výsledek je nyní uložen v text
Text by šel vycentrovat manuálně, ale následující metoda je určitě lepší. V každém průchodu cyklem přičteme k délce řetězce šířku aktuální znaku, kterou najdeme v gmf[text[loop]].gmfCellIncX. gmf ukládá informace o každém znaku (display listu), tedy například i výšku znaku, uloženou pod gmfCellIncY. Tuto techniku lze použít při vertikálním vykreslování.
for (unsigned int loop=0;loop<(strlen(text));loop++)// Zjistí počet znaků textu
{
length+=gmf[text[loop]].gmfCellIncX;// Inkrementace o šířku znaku
}
K vycentrování textu posuneme počátek doleva o polovinu délky řetězce.
glTranslatef(-length/2,0.0f,0.0f);// Zarovnání na střed
Nastavíme GL_LIST_BIT a tím zamezíme působení jiných display listů, použitých v programu na glListBase(). Předešlým příkazem určíme, kde má OpenGL hledat správné display listy jednotlivých znaků.
glPushAttrib(GL_LIST_BIT);// Uloží současný stav display listů
glListBase(base);// Nastaví první display list na base
Zavoláme funkci glCallLists(), která najednou zobrazuje více display listů. strlen(text) vrátí počet znaků v řetězci a tím i počet k zobrazení. Dále potřebujeme znát typ předávaného parametru (poslední). Ani teď nebudeme vkládat více než 256 znaků, takže použijeme GL_UNSIGNED_BYTE (byte může nabývat hodnot 0-255, což je přesně to, co potřebujeme). V posledním parametru předáme text. Každý display list ví, kde je pravá hrana toho předchozího, čímž zamezíme nakupení znaků na sebe, na jedno místo. Před začátkem kreslení následující znaku se přesune o tuto hodnotu doprava (glTranslatef()). Nakonec nastavíme GL_LIST_BIT zpět na hodnotu mající před voláním glListBase().
glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);// Vykreslí display listy
glPopAttrib();// Obnoví původní stav display listů
}
Provedeme pár drobných změn v inicializačním kódu. Řádka BuildFont() ze 13. lekce zůstala na stejném místě, ale přibyl nový kód pro použití světel. Light0 je předdefinován na většině grafických karet. Také jsem přidal glEnable(GL_COLOR_MATERIAL). Ke změně barvy písma potřebujeme zapnout vybarvování materiálů, protože i znaky jsou 3D objekty. Pokud vykreslujete vlastní objekty a nějaký text, musíte před funkcí glPrint() zavolat glEnable(GL_COLOR_MATERIAL) a po vykreslení textu glDisable(GL_COLOR_MATERIAL), jinak by se změnila barva i vámi vykreslovaného objektu.
int InitGL(GLvoid)// Všechna nastavení OpenGL
{
glShadeModel(GL_SMOOTH);// Povolí jemné stínování
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);// Černé pozadí
glClearDepth(1.0f);// Nastavení hloubkového bufferu
glEnable(GL_DEPTH_TEST);// Povolí hloubkové testování
glDepthFunc(GL_LEQUAL);// Typ hloubkového testování
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Nejlepší perspektivní korekce
glEnable(GL_LIGHT0);// Zapne implicitní světlo
glEnable(GL_LIGHTING);// Zapne světla
glEnable(GL_COLOR_MATERIAL);// Zapne vybarvování materiálů
BuildFont();// Vytvoří font
return TRUE;
}
Přesuneme se 10 jednotek do obrazovky. Outline fonty vypadají skvěle v perspektivním módu. Když jsou umístěny hlouběji, zmenšují se. Pomocí funkce glScalef(x,y,z) můžeme také měnit měřítka os. Pokud bychom například chtěli vykreslit font dvakrát vyšší, použijeme glScalef(1.0f,2.0f,1.0f).
int DrawGLScene(GLvoid)// Vykreslování
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Smaže obrazovku a hloubkový buffer
glLoadIdentity();// Reset matice
glTranslatef(0.0f,0.0f,-10.0f);// Přesun do obrazovky
glRotatef(rot,1.0f,0.0f,0.0f);// Rotace na ose x
glRotatef(rot*1.5f,0.0f,1.0f,0.0f);// Rotace na ose y
glRotatef(rot*1.4f,0.0f,0.0f,1.0f);// Rotace na ose z
Jako obyčejně jsem použil pro změnu barev "jednoduché" matematiky. (Pozn. překladatele: tahle věta se mi povedla :)
// Pulzování barev závislé na pozici a rotaci
glColor3f(1.0f*float(cos(rot/20.0f)),1.0f*float(sin(rot/25.0f)),1.0f-0.5f*float(cos(rot/17.0f)));
glPrint("NeHe - %3.2f",rot/50);// Výpis textu
rot+=0.5f;// Inkrementace čítače
return TRUE;
}
Poslední kód, který se provede před opuštěním programu je smazání fontu voláním KillFont().
//Konec funkce KillGLWindow(GLvoid)
if(!UnregisterClass("OpenGL",hInstance))
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL;
}
KillFont();//Smazání fontu
}
Po dočtení této lekce byste měli být schopni používat 3D fonty. Stejně jako jsem psal ve 13. lekci, ani tentokrát jsem na internetu nenašel podobný článek. Možná jsem opravdu první, kdo píše o tomto tématu.
napsal: Jeff Molofee - NeHe <nehe (zavináč) connect.ab.ca>
přeložil: Michal Turek - Woq <WOQ (zavináč) seznam.cz>