Často kladená otázka týkající se OpenGL zní: "Jak zobrazit text?". Vždycky jde namapovat texturu textu. Bohužel nad ním máte velmi malou kontrolu. A pokud nejste dobří v blendingu, většinou skončíte smixováním s ostatními obrázky. Pokud byste chtěli znát lehčí cestu k výstupu textu na jakékoli místo s libovolnou barvou nebo fontem, potom je tato lekce určitě pro vás. Bitmapové fonty jsou 2D písma, které nemohou být rotovány. Vždy je uvidíte zepředu.
Možná si řeknete co je tak těžkého na výstupu textu. Můžete spustit grafický editor, vepsat text do obrázku, nahrát ho jako texturu, zapnout blending a poté namapovat na polygon. Ale tím uberete čas procesoru. V závislosti na typu filtrování může výsledek vypadat rozmazaně nebo jako poskládaný z kostiček. Pokud by měl alfa kanál, skončí smíchaný s objekty na obrazovce. Jistě víte kolik různých fontů je dostupných v systému. V tomto tutoriálu se naučíte jak je používat. Nejen, že bitmapové fonty vypadají stokrát lépe než text na textuře, ale můžete je jednoduše měnit za běhu programu. Není třeba dělat texturu pro každé slovo nebo nápis, který chcete vypsat. Stačí jen jeden příkaz. Snažil jsem se vytvořit tuto funkci co nejjednodušší. Všechno co musíte udělat je napsat glPrint("Hello, world!"). Podle dlouhého úvodu můžete říci, že jsem s tímto tutoriálem dost spokojený. Trvalo mi přibližně hodinu a půl napsat tento program. Proč tak dlouho? Protože ve skutečnosti nejsou dostupné žádné informace o používání bitmapových fontů, pokud samozřejmě nemáte rádi MFC. Abych udržel vše jednoduché, rozhodl jsem se, že by bylo pěkné napsat jej v k pochopení jednoduchém C kódu.
Malá poznámka: Tento kód je specifický pro Windows. Používá wgl funkce Windows pro vytvoření fontu. Apple má pravděpodobně agl podporu, která by měla dělat tu samou věc a X má glx. Naneštěstí nemohu zaručit, že tento kód je přenositelný. Pokud má někdo na platformě nezávislý kód pro kreslení fontů na obrazovku, pošlete mi jej a já napíši jiný tutoriál o fontech:
Začneme typickým kódem z lekce 1. Přidáme hlavičkový soubor stdio.h pro vstupně výstupní operace, stdarg.h pro rozbor textu a konvertování proměnných do textu a konečně math.h, takže můžeme pohybovat textem po obrazovce s použitím funkcí SIN a COS.
#include <windows.h>// Hlavičkový soubor pro Windows
#include <math.h>// Hlavičkový soubor pro matematickou knihovnu
#include <stdio.h>// Hlavičkový soubor pro standartní 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
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
Přidáme 3 nové proměnné. V base uložíme číslo prvního vytvořeného display listu. Každý znak bude potřebovat vlastní, takže jich bude relativně dost. Znaku 'A' přiřadíme číslo 65, 'B' 66, 'C' 67 atd. Lehce usoudíte, že 'A' bude uloženo v base + 65 ('A' je 65 znak Ascii tabulky). Dále přidáme 2 čítače, které použijeme k pohybu textu po obrazovce s použitím sinů a kosinů. Budou sloužit i ke generování barvy znaků (více dále).
GLuint base;// Číslo základního display listu znaků
GLfloat cnt1;// Pro pohyb a barvu textu
GLfloat cnt2;// Pro pohyb a barvu textu
Následující funkce vytvoří font - asi nejtěžší část. "HFONT font" řekne Windows, že budeme manipulovat s fonty Windows. Vytvořením 96 display listů definujeme base. Po skončení této operace v ní bude uloženo číslo prvního listu.
GLvoid BuildFont(GLvoid)// Vytvoření fontu
{
HFONT font;// Proměnná fontu
base = glGenLists(96);// 96 znaků
Vytvoříme font. První parametr specifikuje velikost. Všimněte si, že je to záporné číslo. Vložením znaménka mínus řekneme Windows, aby našlo písmo podle výšky ZNAKU. Pokud bychom použili kladné, hledalo by se podle výšky BUŇKY.
font = CreateFont(-24,// Výška
Určíme šířku buňky. Zadáním nuly Windows použije implicitní hodnotu. Konkrétní hodnotou vytvoříme font širší.
0,// Šířka
Úhel escapement natočí font. Není to zrovna nejlepší vlastnost. Kdybyste nepoužili 0, 90, 180 nebo 270 stupňů, font by se pravděpodobně ořezal rámečkem.
0,// Úhel escapement
0,// Úhel orientace
Tučnost fontu je užitečný parametr. Lze použít čísla 0 až 1000 nebo některou z předdefinovaných hodnot. FW_DONTCARE (0), FW_NORMAL (400), FW_BOLD (700) a FW_BLACK (900). Je jich samozřejmě více, ale myslím si, že tyto čtyři bohatě stačí (popř. použijte nápovědu MSDN). Čím větší hodnotu použijete, tím bude tučnější.
FW_BOLD,// Tučnost
FALSE,// Kurzíva
FALSE,// Podtržení
FALSE,// Přeškrtnutí
Znaková sada popisuje typ znaků, které chcete použít. Např. CHINESEBIG5_CHARSET, GREEK_CHARSET, RUSSIAN_CHARSET, DEFAULT_CHARSET atd. ANSI je jediná, kterou používám, nicméně DEFAULT by koneckonců mohlo pracovat také. (Pokud rádi používáte fonty Webdings nebo Wingdings použijte SYMBOL_CHARSET.).
ANSI_CHARSET,// Znaková sada
Přesnost výstupu říká Windows jakou znakovou sadu použít, mají-li dvě stejná jména. Je-li více možných fontů OUT_TT_PRECIS vybere TRUETYPE verzi, která vypadá mnohem lépe - především, když se zvětší. Zadat můžete také OUT_TT_ONLY_PRECIS, která vždy použije TrueType font.
OUT_TT_PRECIS,// Přesnost výstupu (TrueType)
Přesnost ořezání je typ ořezání, který se použije, když se font dostane ven z ořezávacího regionu.
CLIP_DEFAULT_PRECIS,// Přesnost ořezání
Do výstupní kvality můžete zadat PROOF, DRAFT, NONANTIALIASED, DEFAULT nebo ANTIALIASED (méně hranaté).
ANTIALIASED_QUALITY,// Výstupní kvalita
Nastavíme rodinu a pitch. Do pitch lze zadat DEFAULT_PITCH, FIXED_PITCH a VARIABLE_PITCH. Do rodiny FF_DECORATIVE, FF_MODERN, FF_ROMAN, FF_SCRIPT, FF_SWISS, FF_DONTCARE. Zkuste si s nimi pohrát.
FF_DONTCARE | DEFAULT_PITCH,// Rodina a pitch
Nakonec zadáme jméno fontu. Spusťte MS Word nebo jiný textový editor a najděte si jméno písma, které se vám líbí.
"Courier New");// Jméno fontu
Vybereme font do DC (device context - kontext zařízení) a vytvoříme 96 display listů počínajíce 32 (v Ascii tabulce jsou před 32 netištitelné znaky, 32 - mezera). Můžete sestavit všech 256 zadáním čísla 256 do glGenList() (výše - na začátku této funkce). Ujistěte se, že smažete všech 256 listů po skončení programu (funkce KillFont(GLvoid)) a samozřejmě musíte napsat v následujícím příkazu místo 32 -> 0 a místo 96 -> 255 (viz. další lekce o fontech).
SelectObject(hDC, font);// Výběr fontu do DC
wglUseFontBitmaps(hDC, 32, 96, base);// Vytvoří 96 znaků, počínaje 32 v Ascii
}
Následující kód je krásně jednoduchý. Smaže 96 vytvořených display listů z paměti počínaje prvním, který je určen v "base". Nejsem si jistý, jestli by to Windows udělali automaticky. Jeden řádek za jistotu stojí.
GLvoid KillFont(GLvoid)// Smaže font
{
glDeleteLists(base, 96);// Smaže všech 96 znaků (display listů)
}
A teď přichází na řadu funkce, kvůli níž je napsána tato lekce. Volá se úplně stejně jako klasická printf("Hello, world!"); s tím rozdílem, že na začátek přidáte gl a před závorkou uberete f :-]
GLvoid glPrint(const char *fmt, ...)// Klon printf() pro OpenGL
{
První řádek alokuje paměť pro 256 znaků. Jakýsi řetězec, který nakonec vypíšeme na obrazovku. Druhou proměnnou tvoří ukazatel do argumentů funkce, který jsme při volání zadali s řetězcem kód této lekce. ( printf("%d %d", i, j) - to znáte, ne?)
char text[256];// Ukládá řetězec
va_list ap;// Pointer do argumentů funkce
Další dva řádky zkoušejí, jestli byl zadán text. Pokud ne fmt ukazuje na nic (NULL) a tudíž se nic nevypíše.
if (fmt == NULL)// Byl předán text?
return;// Konec
Následující kód konvertuje veškeré symboly (%d, %f...) v řetězci na konkrétní hodnoty. Po úpravě bude vše uloženo v text.
va_start(ap, fmt);// Rozbor řetězce
vsprintf(text, fmt, ap);// Zamění symboly za konkrétní čísla
va_end(ap);// Výsledek je uložen v text
Příkaz glListBase(base-32) je na vysvětlení trochu obtížnější. Řekněme, že vykreslujeme znak 'A', který je reprezentován 65 (v Ascii). Bez glListBase(base-32) OpenGL neví, kde má najít tento znak. Mohlo by vyhledat display list 65, ale pokud by se base rovnalo 1000, tak by 'A' bylo uloženo v display listu 1065. Takže nastavením base na počáteční bod, OpenGL bude vědět, odkud vzít ten správný display list. Odečítáme 32, protože jsme nevytvořili prvních 32 display listů, tudíž je přeskočíme.
glPushAttrib(GL_LIST_BIT);// Uloží současný stav display listů
glListBase(base - 32);// Nastaví základní znak na 32
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ů
}
Jediná změna v inicializačním kódu je příkaz volající BuildFont().
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
BuildFont();// Vytvoří font
return TRUE;
}
Přejdeme k vykreslování. Po obvyklých inicializacích se přesuneme o 1 jednotku do obrazovky. Bitmapové fonty pracují lépe při použití kolmé (ortho) projekce než při perspektivní. Nicméně kolmá vypadá hůře, tudíž provedeme translaci do obrazovky. Po přesunu o 1 jednotku dovnitř, budeme moci umístit text kamkoli od -0.5 do +0.5 na ose x. Po přesunu o 10 bychom mohli vykreslovat na pozice od -5.0 do +5.0. Nikdy neměňte velikost textu a naprosto nikdy nepoužívejte změnu měřítka glScale(x,y,z). Chcete-li mít font větší či menší musíte na to myslet při vytváření.
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,-1.0f);// Přesun o 1 do obrazovky
Dále nastavíme barvu textu. V tomto případě používám dva čítače. Červená složka se určuje podle kosinu prvního čítače. Hodnoty se mění od -1.0 do +1.0. Zelená složku vypočítáme podle sinu druhého čítače. Rozsahy jsou stejné jako v předchozím případě. K modré barvě jsou použity oba čítače s kosinem. Hodnoty náležejí od 0.5 do 1.5, tedy výsledek operace nebude nikdy 0 a text bude vždy viditelný.
// Pulzování barev závislé na pozici
glColor3f(1.0f*float(cos(cnt1)),1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)));
K určení pozice použijeme nový příkaz. Střed zůstal na 0.0, ale asi jste si všimli, že schází pozice osy z. Po přesunu o jednotku do obrazovky je levý nejvzdálenější viditelný bod -0.5 a pravý +0.5. Protože se vždy text vykresluje zleva doprava, přesuneme o 0.45 doleva. Tím bude vycentrován na střed. Použitá matematika vykonává stejnou funkci jako při nastavování barvy. Na ose x se text pohybuje od -0.5 do -0.4 (odečetli jsme 0.45). Tím udržíme text vždy viditelný. Na ose y se hranice nacházejí na -0.35 a +0.35.
glRasterPos2f(-0.45f+0.05f*float(cos(cnt1)), 0.32f*float(sin(cnt2)));// Pozice textu
Vypíšeme text. Tuto funkci jsem navrhl jako super snadnou a uživatelsky příjemnou. Vypadá jako volání printf() ze stdio.h zkřížené s OpenGL. Text se vykreslí na pozici, kam jsme přesunuli před chvílí. Podřetězec %7.2f oznamuje vypisování obsahu proměnné. Sedmička určuje, maximální délku čísla a dvojka upřesňuje počet desetinných míst. f značí desetinné číslo (float). Je mi samozřejmě jasné, že pokud ovládáte jazyk C, tak je to pro vás hračka. Konvence jsou stejné jako u klasické printf(). Pokud to bude nutné můžete se podívat do nápovědy MSDN.
glPrint("Active OpenGL Text With NeHe - %7.2f", cnt1);// Výpis textu
Nakonec zbývá inkrementování čítače, aby se měnila pozice a barva.
cnt1+=0.051f;
cnt2+=0.005f;
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
}
Hotovo... Na internetu jsem hledal podobný tutoriál, ale nic jsem nenašel. Možná jsem první, který píše na podobné téma. Vše je možné. Užijte si výpis textu a šťastné kódování.
napsal: Jeff Molofee - NeHe <nehe (zavináč) connect.ab.ca>
přeložil: Michal Turek - Woq <WOQ (zavináč) seznam.cz>