Lekce 14 - Outline fonty

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>

Zdrojové kódy

Lekce 14

<<< Lekce 13 | Lekce 15 >>>