Lekce 43 - FreeType Fonty v OpenGL

Použitím knihovny FreeType Font rendering library můžete snadno vypisovat vyhlazené znaky, které vypadají mnohem lépe než písmena u bitmapových fontů z lekce 13. Náš text bude mít ale i jiné výhody - bezproblémová rotace, dobrá spolupráce s OpenGL vybíracími (picking) funkcemi a víceřádkové řetězce.

Verze ve slovenštině...

Motivace: Tady máte ukázky bitmapového fontu vytvořeného pomocí WGL a FreeType fontu. Oba jsou Arial Black Kurzíva.

FreeType font a WGL font

Základní problém s použitím bitmapových fontů je, že OpenGL bitmapy jsou binárními obrázky. To znamená, že si OpenGL pamatuje pouze 1 bit na 1 pixel. Zoomujeme-li na text vytvořený pomocí WGL, výsledek vypadá přibližně takto:

Zvětšený WGL font

Protože jsou bitmapy binární obrázky, nejsou okolo nich šedé pixely a to znamená, že vypadají hůř. Naštěstí je velmi jednoduché pomocí GNU FreeType knihovny vytvořit dobře vypadající fonty. Tuto knihovnu používá i Blizzard ve svých hrách, takže musí být opravdu dobrá :-))). Opět ukázka zvětšeného textu, tentokrát s knihovnou FreeType:

Zvětšený FreeType font

Jak můžeme vidět, okolo okrajů se nachází spousta šedých pixelů, které jsou typické pro vyhlazené (anti-aliasované) fonty. Šedé pixely vylepšují znaky při pohledu z dálky.

Knihovnu GNU FreeType si můžete stáhnout na adrese http://gnuwin32.sourceforge.net/packages/freetype.htm. Konkrétně se jedná o binární a vývojářské soubory. Při instalaci si určitě všimnete licenční podmínky. Hovoří se v ní, že při použití ve vlastních programech, musíte někde v dokumentaci uvést kredit.

Po instalaci potřebujeme nastavit MSVC, aby umělo používat FreeType. V menu Project - Settings - Link se ujistěte, že jste spolu s opengl32.lib, glu32.lib, glaux.lib a podobnými přidali do Object/libraries i libfreetype.lib.

Dále potřebujeme v Tools - Options - Directories přidat cesty k hlavičkových souborům. Pod Show Directories for vybereme Include Files a poklikáme na prázdný řádek dole v seznamu. Objeví se tlačítko se třemi tečkami, které použijeme pro výběr adresáře. Takto přidáme:

C:\PROGRAM FILES\GNUWIN32\INCLUDE\FREETYPE32

a

C:\PROGRAM FILES\GNUWIN\INCLUDE

Pod Show Directories For vybereme Library Files a přidáme

C:\PROGRAM FILES\GNUWIN32\LIB

Na tomto místě bychom měli být schopni kompilovat programy, které používají FreeType, ale nepůjdou spustit bez dynamické knihovny freetype-6.dll. Kopii tohoto souboru naleznete v GNUWIN32\BIN. Je třeba ji umístit do adresáře, ve kterém systém při spouštění programů knihovny hledá (např. C:\PROGRAM FILES\MICROSOFT\VISUAL STUDIO\VC98\BIN nebo C:\WINDOWS\SYSTEM pro WIN9x, C:\WINNT\SYSTEM32 pro WIN NT/2000/XP). Překl.: Osobně doporučuji podobné DLL knihovny výhradně vkládat do adresáře, ve kterém se nachází spoustěný exe soubor, protože až budete váš program někomu kopírovat, nikdy na ně nezapomenete (Tak ten tvůj program mi nešel spustit!).

OK, tak teď už konečně můžeme začít programovat. Jako základ vezmeme lekci 13. Zkopírujeme soubor lesson13.cpp a přidáme ho do projektu. Stejně tak zkopírujeme dva nové soubory freetype.h a freetype.cpp, do kterých budeme přidávat všechen FreeTypový kód. Až skončíme, budeme mít jednoduchou FreeType knihovnu, kterou budeme moci využít i v jiných OpenGL programech.

Začneme vytvářením freetype.h. Samozřejmě musíme nadefinovat hlavičky FreeType a OpenGL. Také přidáme pár užitečných částí ze Standard Template Library (STL). Konkrétně se jedná o třídy vyjímek, které nám zjednoduší vytváření pěkných debugových zpráv. Použití STL zvyšuje šanci, že někdo jiný, kdo používá náš kód, úspěšně zachytí všechny poslané vyjímky.

#ifndef FREE_NEHE_H

#define FREE_NEHE_H

#include <windows.h>

// FreeType hlavičky

#include <ft2build.h>

#include <freetype/freetype.h>

#include <freetype/ftglyph.h>

#include <freetype/ftoutln.h>

#include <freetype/fttrigon.h>

// OpenGL hlavičky

#include <GL/gl.h>

#include <GL/glu.h>

// STL hlavičky

#include <vector>

#include <string>

// STL vyjímky

#include <stdexcept>

Následující pragma MSVC zabrání, aby oznamovalo zbytečná varování o vektorech řetězců.

#pragma warning(disable: 4786)

Všechny informace, které každý font potřebuje, dáme do jedné struktury (toto ulehčí práci s více písmy). Jak jsme se naučili v lekci 13, když WGL vytváří font, generuje sadu display listů s postupně se zvyšujícím ID. To je šikovné, protože díky tomu můžeme pro vypsání celého řetězce použít jediný příkaz glCallLists(). V naší knihovně nastavíme všechno úplně stejně, což znamená, že pole list_base bude ukládat prvních 128 display listů jednotlivých znaků. Protože se chystáme pro vykreslování použít textury, potřebujeme také uložit 128 asociovaných textur. Poslední, co uděláme, je deklarování proměnné označující výšku vytvářeného fontu v pixelech, která nám umožní vypisovat i zalomení řádků označené \n v řetězci.

namespace freetype// Proti vícenásobnému použití stejných identifikátorů

{

using std::vector;// Možnost psát jen vector namísto std::vector

using std::string;// To samé pro string

struct font_data// Zapouzdření všeho do struktury

{

float h;// Výška fontu

GLuint* textures;// ID textur

GLuint list_base;// ID prvního display listu

void init(const char* fname, unsigned int h);// Vytvoření písma s výškou h ze souboru fname

void clean();// Uvolnění všech prostředků spojených s fontem

};

Funkce print() vykreslí zadaný text na souřadnicích x, y. Modelview matice bude také aplikovaná na text.

void print(const font_data &ft_font, float x, float y, const char *fmt, ...);// Vykreslí text

}

#endif

To je konec hlavičkového souboru freetype.h, teď otevřeme freetype.cpp.

#include "freetype.h"// Vloží freetype.h

namespace freetype

{

Pro vykreslení znaků budeme používat textury, které musí samozřejmě mít rozměry mocniny čísla 2. Následující funkce vrátí první mocninu dvojky, která se rovná nebo je větší než předané číslo.

inline int next_p2(int a)// Vrátí následující mocninu čísla 2

{

int rval = 1;// Nastaví bit vpravo do jedničky

while(rval < a)// Dokud je nalezená mocnina menší než minimum

{

rval <<= 1;// Získání další mocniny (rotace bitů doleva, rychlejší způsob, jak napsat rval *= 2)

}

return rval;// Vrácení výsledku

}

Další funkce, kterou budeme potřebovat, je srdcem tohoto kódu. Make_dlist() vytvoří display list podle poslaného znaku, parametr FT_Face představuje objekt, který FreeType používá pro uchování informací o fontu. Funkci se dále předává ID základního display listu a ukazatel na texturu.

void make_dlist(FT_Face face, char ch, GLuint list_base, GLuint* tex_base)// Vytvoří display list pro daný znak

{

Na začátku požádáme FreeType o vykreslení daného znaku do bitmapy.

if(FT_Load_Glyph(face, FT_Get_Char_Index(face, ch), FT_LOAD_DEFAULT))// Načte glyph znaku

{

throw std::runtime_error("FT_Load_Glyph failed");

}

FT_Glyph glyph;// Glyph objekt

if(FT_Get_Glyph(face->glyph, &glyph))// Přesun glyphu do glyph objektu

{

throw std::runtime_error("FT_Get_Glyph failed");

}

FT_Glyph_To_Bitmap(&glyph, ft_render_mode_normal, 0, 1);// Konvertování glyphu na bitmapu

FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;

FT_Bitmap& bitmap = bitmap_glyph->bitmap;// Reference ulehčí přístup k bitmapě

Teď, když máme pomocí FreeType vytvořenu bitmapu, potřebujeme do ní doplnit prázdné pixely, abychom ji mohli použít v OpenGL. Je důležité zapamatovat si, že zatímco OpenGL používá termín bitmapa k označení binárních obrázků, ve FreeType bitmapa ukládá 8 bitů informace na pixel, takže může ukládat i šedé složky, které jsou potřebné pro vyhlazený text.

int width = next_p2(bitmap.width);// Velikost textury - mocnina čísla 2

int height = next_p2(bitmap.rows);

GLubyte* expanded_data = new GLubyte[2 * width * height];// Alokace paměti

Všimněte si, že používáme dvoukanálovou bitmapu, první kanál pro zářivost a druhý pro alfu. Oba přiřadíme hodnotě, kterou jsme nalezli ve FreeType bitmapě. Ternální operátor ? : použijeme pro určení nulové hodnoty, pokud se nacházíme v okrajové zóně (zvětšení pro mocninu 2), v ostatních případech platí hodnota převzatá z FreeType bitmapy.

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];

}

}

Jsme hotovi, takže se můžeme pustit do vytváření OpenGL textury. Protože zahrneme alfa kanál, černé části bitapy budou vždy průhledné a okraje textu budou plynule průsvitné. Text by měl vypadat správně na jakémkoli podkladě. Jak už jsem napsal, texturu vytváříme ze složek luminance a alfa.

// Nastavení parametrů textury

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);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data);

delete [] expanded_data;// Uvolnění paměti bitmapy

Na vykreslení znaku použijeme otexturované čtyřúhelníky. To znamená, že bude jednoduché otáčet, zvětšovat i zmenšovat text a dokonce bude od OpenGL dědit barvu.

glNewList(list_base + ch, GL_COMPILE);// Vytvoření display listu

glBindTexture(GL_TEXTURE_2D, tex_base[ch]);

Nejdřív pohneme kamerou trochu doprava, aby byl znak vycentrovaný mezi minulým a následujícím. Uložíme matici a pokud pracujeme se znakem typu g nebo y, posuneme se trochu dolů.

glTranslatef(bitmap_glyph->left, 0, 0);// Vycentrování znaku mezi minulým a následujícím

glPushMatrix();

glTranslatef(0, bitmap_glyph->top - bitmap.rows, 0);// Posun o trochu dolů

Musíme počítat s faktem, že mnoho textur je na okraji vyplněných prázdným místem. Zjistíme, jaká část textury je znakem používána a tuto hodnotu uložíme do pomocných proměnných x a y, kterou před kreslením předáme funkci glTexCoord2d().

float x = (float)bitmap.width / (float)width;

float y = (float)bitmap.rows / (float)height;

Na tomto místě vykreslíme otexturovaný obdélník. Bitmapa, kterou jsme získali pomocí FreeType není orientovaná přesně tak, jak by měla, ale to nám nevadí, protože můžeme explicitně určit polohu pro správné zarovnání.

glBegin(GL_QUADS);// Vykreslení znaku

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);

Inkrementujeme pozici v rastru stejně, jako bychom pracovali s bitmapovým fontem. To je nutné pouze, pokud bychom chtěli spočítat aktuální délku textu. Proto jsem řádek zakomentoval.

// glBitmap(0, 0, 0, 0, face->glyph->advance.x >> 6, 0, NULL);

glEndList();// Ukončíme display list

}

Další funkce, kterou se chystáme vytvořit, bude používat make_dlist() pro vytvoření množiny display listů odpovídajících danému souboru s fontem a výšce v pixelech. FreeType používá truetype fonty, takže budeme potřebovat nějaký .ttf soubor s fontem. Truetypová písma jsou velmi běžná, existuje spousta míst na internetu, kde si je můžete stáhnout. Jednodušší bude ale podívat se do adresáře windows/fonts.

void font_data::init(const char * fname, unsigned int h)// Vytvoření fontu

{

textures = new GLuint[128];// Paměť pro ID textur

this->h = h;

FT_Library library;// Vytvoření FreeType

if (FT_Init_FreeType(&library))// Inicializace FreeType

{

throw std::runtime_error("FT_Init_FreeType failed");

}

FT_Face face;// Objekt pro informace o fontu

Na tomto místě se pokusíme načíst ze souboru data fontu. Ze všech míst, kde se kód může zaseknout je právě toto nejčastější, protože soubor např. nemusí existovat nebo může být nějakým způsobem poškozen.

if (FT_New_Face(library, fname, 0, &face))// Načtení fontu ze souboru

{

throw std::runtime_error("FT_New_Face failed (there is probably a problem with your font file)");

}

Z nějakým nevysvětlitelných důvodů měří FreeType velikost písma v 1/64-nách pixelů. Proto, abychom měli font vysoký h pixelů, musíme předávat velikost násobenou číslem 64. H << 6 je jen rychlejší způsob psaní této operace.

FT_Set_Char_Size(face, h << 6, h << 6, 96, 96);

Překl.: Anglické znakové sadě stačí pouze 128 znaků, ale čeština obsahuje navíc háčky a čárky, takže pokud je chcete používat, musíte upravit kód.

list_base = glGenLists(128);// 128 display listů a textur

glGenTextures(128, textures);

for(unsigned char i = 0; i < 128; i++)// Vytvoření display listů znaků

{

make_dlist(face, i, list_base, textures);

}

Protože všechna data máme uložena v display listech a texturách, můžeme uvolnit použité zdroje FreeType.

FT_Done_Face(face);// Uvolnění zdrojů

FT_Done_FreeType(library);

}

Vytvoříme clean() funkci, která uvolní všechny prostředky spojené s display listy a texturami.

void font_data::clean()

{

glDeleteLists(list_base, 128);

glDeleteTextures(128, textures);

delete [] textures;

}

Následují dvě pomocné funkce pro print(), která bude chtít operovat ne v OpenGL jednotkách, ale v pixelových souřadnicích okna. Nula se bude nacházet v levém horním rohu.

inline void pushScreenCoordinateMatrix()// Přepne do pravoúhlé projekce

{

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();

}

inline void pop_projection_matrix()// Obnoví perspektivu

{

glPushAttrib(GL_TRANSFORM_BIT);

glMatrixMode(GL_PROJECTION);

glPopMatrix();

glPopAttrib();

}

Nová funkce print() vypadá velmi podobně jako ta z lekce 13, ale je tu pár rozdílů. Nastavíme jiné OpenGL flagy, protože používáme pouze dvoukanálové textury namísto bitmap. Abychom zajistili přechody na nové řádky, přidáme několik extra výpočtů a samozřejmě nesmíme zapomenout na zajištění toho, aby všechna nastavení byla po výstupu z funkce ve stejné stavu jako před vstupem.

void print(const font_data &ft_font, float x, float y, const char *fmt, ...)// Rendering textu

{

pushScreenCoordinateMatrix();// Souřadná soustava v pixelech

GLuint font = ft_font.list_base;

float h = ft_font.h / 0.63f;// Větší mezera mezi řádky

char text[256];// Výsledný řetězec

va_list ap;// Ukazatel na argumenty funkce

if (fmt == NULL)// Byl předán text?

{

*text = 0;// Nic nedělat

}

else

{

va_start(ap, fmt);// Analýza řetězce na proměnné

vsprintf(text, fmt, ap);// Konvertování symbolů na čísla

va_end(ap);// Výsledky jsou uloženy do text

}

Následující kód rozdělí daný text na sadu řádků. Velmi jednoduše by se to dalo provést pomocí regulárních výrazů, jedna taková knihovna je dostupná např. na boost.org, ale my nic takového používat nebudeme - jednoduše se snažím udržet kód bez zbytečných závislostí na méně nutných knihovnách.

// Rozdělení řetězce na jednotlivé řádky

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);

}

Zálohujeme všechny OpenGL parametry a potom je nastavíme na nutné hodnoty.

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);

Všechny transformace, které provedeme na modelview matici před voláním této funkce, se projeví i na textu samotném. To znamená, že při výpisu textu máme možnost rotovat nebo měnit jeho velikost. Nejpřirozenější cestou by bylo ponechat původní matici, tak jak byla, ale to nebude pracovat, protože chceme mít kontrolu nad pozicí textu. Další možností by bylo vytvořit kopii matice a mezi glTranslatef() a glCallLists() ji aplikovat, nicméně měřítko projekční matice teď už není v OpenGL jednotkách, ale v pixelech, takže bychom získali trošku odlišný efekt, než by někdo mohl očekávat. Přes toto bychom se také mohli dostat neresetován projekční matice uvnitř print(). To je v některých situacích docela dobrý nápad, ale pokud to budete zkoušet, zajistěte, že fonty budou mít odpovídající velikost (jsou nastaveny na 32x32, ale vy pravděpodobně budete potřebovat něco kolem 0,01x0,01). Zkuste uhodnout, kterou cestou jdeme my :-)

float modelview_matrix[16];

glGetFloatv(GL_MODELVIEW_MATRIX, modelview_matrix);

Zobrazování textu se děje právě na tomto místě. Pro každý řádek resetujeme matici, aby začínal na správné pozici. Všimněte si, že místo posunu dolů o výšku h, ji raději rovnou resetujeme. To proto, že se při vykreslení každého znaku posouváme doprava na pozici znaku za ním.

for(int i = 0; i < lines.size(); i++)// Prochází jednotlivé řádky textu

{

glPushMatrix();// Záloha matice

glLoadIdentity();// Resetování matice

glTranslatef(x, y - h*i, 0);// Přesun na odpovídající pozici

glMultMatrixf(modelview_matrix);

Pokud byste potřebovali zjistit délku textu, který vytváříte odkomentujete následující řádky. Pokud se tak rozhodnete, musíte odkomentovat i příkaz glBitmap() v make_dlist().

// glRasterPos2f(0, 0);

glCallLists(lines[i].length(), GL_UNSIGNED_BYTE, lines[i].c_str());// Vykreslí řádek textu

// float rpos[4];

// glGetFloatv(GL_CURRENT_RASTER_POSITION, rpos);

// float len = x - rpos[0];

glPopMatrix();// Obnovení matice

}

glPopAttrib();// Obnovení OpenGL flagů

pop_projection_matrix();// Obnovení perspektivy

}

}// Konec namespace

Knihovna je teď kompletní. Abychom mohli vidět výsledek, otevřeme soubor lesson13.cpp a provedeme v něm několik menších změn. Za includování hlavičkových souborů vložte i freetype.h.

#include "freetype.h"// Vložení freetype

A když už jsme tu, deklarujeme i globální objekt font_data.

freetype::font_data our_font;// Informace pro vytvářený font

Dále potřebujeme font inicializovat...

// InitGL()

our_font.init("test.TTF", 16);// Vytvoření fontu

... a při skončení programu odstranit.

// KillGLWindow()

our_font.clean();

Do funkce DrawGLScene() doplníme výpis FreeType fontu, který bude navíc rotovat a měnit svou velikost.

int DrawGLScene(GLvoid)// Všechno vykreslování

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Smazání bufferů

glLoadIdentity();// Reset matice

glTranslatef(0.0f, 0.0f, -1.0f);// Posun o jednotku do obrazovky

glColor3ub(0, 0, 0xff);// Modrý text

// Vykreslení WGL textu

glRasterPos2f(-0.40f, 0.35f);

glPrint("Active WGL Bitmap Text With NeHe - %7.2f", cnt1);

// Vykreslení FreeType fontu

glColor3ub(0xff,0,0);// Červený text

glPushMatrix();

glLoadIdentity();

glRotatef(cnt1, 0, 0, 1);

glScalef(1, 0.8 + 0.3 * cos(cnt1 / 5), 1);

glTranslatef(-180, 0, 0);

freetype::print(our_font, 320, 240, "Active FreeType Text - %7.2f", cnt1);

glPopMatrix();

Chcete-li otestovat i přechody na nové řádky, odstraňte komentář.

// freetype::print(our_font, 320, 200, "Here\nthere\nbe\n\nnewlines\n.", cnt1);

cnt1 += 0.051f;// Zvětšení hodnot v čítačích

cnt2 += 0.005f;

return TRUE;// Vše OK

}

Nakonec musíme přidat kód pro odchytávání vyjímek. Přejdeme do WinMain() a na začátku vyhledáme sekci try { }.

// WinMain()

MSG msg;// Struktura zprávy

BOOL done = FALSE;// Proměnná pro ukončení cyklu

try// Sekce, ve které se budou zachytávat vyjímky

{

Konec funkce modifikujeme přidáním catch { }, které vypíše text vyjímky.

KillGLWindow();// Zrušení okna

}

catch (std::exception &e)// Ošetření vyjímek

{

MessageBox(NULL, e.what(), "CAUGHT AN EXCEPTION", MB_OK | MB_ICONINFORMATION);

}

Tak a teď, když v programu nastane vyjímka, se zobrazí text oznamující uživateli, co se stalo. Pozor, tento kód může zpomalit váš program, takže se možná při kompilování konečné verze bude hodit vypnutí odchytávání vyjímek (Project->Settings->C/C++, "C++ Language").

Zkompilujte program. Po spuštění byste měli vidět pěkný text renderovaný pomocí FreeType, který se pohybuje okolo originálního textu z lekce 13.

Obecné poznámky

Pravděpodobně budete chtít právě vytvořenou FreeType knihovnu ještě dále vylepšit. Konkrétně se může jednat např. o zarovnávání textu na střed. K tomu budete potřebovat nějakým způsobem zjistit jeho délku. Jedním způsobem může být vložení příkazu glBitmap() do display listu, který bude modifikovat pozici v rastru. Prakticky všechno už je v kódu připraveno, stačí odkomentářovat příslušné příkazy.

FreeType fonty zabírají také mnohem více místa než obyčejný WGL bitmapový font. Pokud z nějakého důvodu potřebujete šetřit texturovací paměť, zkuste vytvořit jednu texturu, která bude obsahovat matici všech znaků, stejnou jaká je v lekci 13.

Na rozdíl od bitmap obdélníky s namapovanou texturou reprezentující text dobře spolupracují s OpenGL picking funkcemi (lekce 32), což velmi usnadňuje zjištění, jestli někdo na text klikl myší nebo přes něj přejel.

Nakonec uvádím odkazy na několik knihoven fontů pro OpenGL. Záleží pouze na vás, jestli je budete chtít použít místo tohoto kódu.

napsal: Sven Olsen <sven (zavináč) sccs.swarthmore.edu>
do slovenštiny přeložil: Pavel Hradský - PcMaster<pcmaster (zavináč) stonline.sk>
do češtiny přeložil: Michal Turek - Woq <WOQ (zavináč) seznam.cz>

Zdrojové kódy

Lekce 43

<<< Lekce 42 | Lekce 44 >>>