Oto krótki tutorial, który pokaże Ci jak używać drugiej wersji biblioteki renderującej czcionki FreeType w OpenGL. Dzięki bibliotece FreeType możemy stworzyć antyaliasowany tekst, który wygląda lepiej niż bitmapowe czcionki (lekcja 13). Nasz tekst będzie posiadać dodatkową przewagę nad czcionkami bitmapowymi - będzie prosty do obrócenia i będzie świetnie działać z wybieraniem (picking) OpenGL (więcej o wybieraniu w lekcji 32).
Pierwsze co musisz zrobić to zdobyć kopię biblioteki GNU FreeType. Udaj się na stronę http://gnuwin32.sourceforge.net/packages/freetype.htm i ściągnij binarki oraz pliki dla deweloperów (jeżeli używasz Linuksa to zajrzyj do repozytorium swojej dystrybucji). Podczas instalacji zwróć uwagę na warunki licencji, która mówi, że jeżeli używasz FreeType w swoich programach to jesteś zobowiązany wspomnieć o nich w swojej dokumentacji.
Stwórz nowy projekt, taki jak w lekcji 1., ale nie zapomnij dodać do bibliotek libfreetype.lib (lub też libfreetype.a).
Jeżeli korzystasz z Linuksa to przejdź do następnego paragrafu.
Teraz potrzebujesz dodać do listy katalogów z plikami nagłówkowymi katalogi: C:\Program Files\GNUWIN32\include\freetype2 oraz C:\Program Files\GNUWIN32\include
Potrzebujesz również dodać katalog C:\Program Files\GNUWIN32\lib do listy katalogów z bibliotekami.
W tym momencie jesteśmy wstanie skompilować programy używające FreeType, ale nie będą działać dopóki nie mają dostępu do freetype-6.dll. Masz kopię tej DLL'ki w katalogu GNUWIN32\bin, jeżeli skopiujesz ją tam, gdzie każdy Twój program będzie mieć dostęp, to będziesz mógł uruchomić programy używające FreeType. Pamiętaj, że jeżeli będziesz chciał wydać program używający FreeType, to będziesz musiał dodać do niego kopię freetype-6.dll.
Dodaj do projektu dwa nowe pliki - "freetype.cpp" i "freetype.h". Umieścimy cały kod związany z FreeType w tych plikach i lekko zmodyfikujemy kod z lekcji 13 by zademonstrować funkcje, które napisaliśmy. Kiedy skończymy, otrzymamy bardzo prostą bibliotekę łączącą OpenGL z FreeType, która teoretycznie mogłaby być użyta w dowolnym projekcie używającym OpenGL.
Oczywiście potrzebujemy nagłówków FreeType i OpenGL. Dodamy jeszcze pewne użyteczne częsci STL'a, włączając w to klasy wyjątków z STL'a, które ułatwią nam wyłapywanie błędów dzięki ładnym komunikatom.
Wszystkie informacje jakie czcionka potrzebuje umieścimy w jednej strukturze (to trochę ułatwi zarządzanie wieloma czcionkami). Tak jak nauczyliśmy się w lekcji 13, gdy WGL stwarza czcionkę to generuje zbiór następujących po sobie list wyświetlania. To jest piękne, ponieważ oznacza, że możesz używać glCallLists do wypisania łańcucha znaków z pomocą tylko jednej komendy. Kiedy utworzymy naszą czcionkę, będziemy ustawiać wszystko w ten sam sposób, co oznacza, że pole list_base będzie składować pierwsze 128 kolejnych list wyświetlania. Ponieważ zamierzamy używać tekstur do rysowania tekstu, będziemy potrzebować miejsca na 128 powiązanych tekstur. Ostatnią informacją jaką potrzebujemy jest wysokość, w pikselach, stworzonej przez nas czcionki (to umożliwi tworzenie nowych linii w naszej funkcji rysującej).
Ostatnią rzeczą jaka jest nam potrzebna to prototyp funkcji wypisującej:
To wszystko w pliku nagłówkowym!
Wykorzystujemy tekstury do wyświetlania znaków w naszej czcionce. Tekstury w OpenGL muszą mieć wymiary będące potęgami dwójki, dlatego dodamy pewien margines do bitmap czcionek utworzonych przez FreeType, by były właściwego rozmiaru. Do tego potrzebujemy tej funkcji:
Następna potrzeba nam funkcja to make_dlist, będąca sercem naszej biblioteki. Jako argument bierze FT_Face, który jest obiektem używanym przez FreeType do składowania informacji o czcionce. Funkcja tworzy odpowiednią listę wyświetlania do podanego znaku.
Teraz, gdy mamy już bitmapę utworzoną przez FreeType, potrzebujemy dodać margines, żeby móc utworzyć teksturę OpenGL. Należy pamiętać, że w kontekście OpenGL wyrażenie "bitmapa" onacza binarny obraz (org. - binary images), w kontekście FreeType bitmapa przechowuje 8 bitów informacji na piksel, dlatego bitmapy FreeType'a są wstanie przechowywać odcienie szarości, których potrzebujemy do utworzenia antyaliasowanego tekstu.
Wraz ze zrobionym marginesem, możemy przejść do utworzenia tekstury OpenGL. Dodajemy kanał alfa by czarne części bitmapy były przezroczyste i by krawędzie tekstu były odrobinę półprzezroczyste (co powinno sprawić, że będą dobrze wyglądać na każdym tle).
Używamy oteksturowanych quadów to rysowania naszego tekstu. Znaczy to, że łatwo będzie obracać i skalować tekst, dodatkowo czcionki będą mogły przybierać aktualny kolor OpenGL (przy używaniu czcionek bitmapowych nie działa nic z wymienionych).
Następna funkcja, jaką zamierzamy napisać będzie używać make_clist do utworzenia zbioru list wyświetlania odpowiadających danemu plikowi czcionki i jej wysokości.
FreeType używa czcionek trutype, więc znajdź sobie jakieś pliki czcionek truetype, żeby nakarmić tą funkcję. Pliki czcionek trutype są bardzo powszechne, jest mnóstwo stron, z których możesz ściągnąć duże ilości najróżniejszych czcionek truetype za darmo.
Teraz potrzebna jest nam funkcje czyszcząca listy wyświetlania i powiązane z czcionką tekstury.
Tutaj są dwie małe funkcje, które napiszemy w oczekiwaniu na naszą funkcję wypisującą. Funkcja wypisująca będzie chciała myśleć w koordynatach okna, więc będziemy musieli zmienić macierz rzutowania na taką, która będzie zapewni wyrażanie się w koordynatach okna.
Używamy dwóch bardzo użytecznych funkcji OpenGL - glGet do pobrania rozmiarów okna oraz glPush/PopAttrib by mieć pewność, że po wszystkim zostawimy ustawioną poprzednio macierz. Jeżeli nie jesteś obeznany z tymi funkcjami, to dobrze by było, żebyś poczytał o nich.
Nasza funkcja wypisująca wygląda dość podobnie do tej z lekcji 13, ale jest parę ważnych zmian. Flagi stanu włączenia (enable flags) OpenGL, które ustawiliśmy są inne, co odzwierciedla fakt, iż używamy tutaj dwukanałowych tekstur zamiast bitmap. Dodatkowo zajmujemy się obsługą nowych linii tekstu. Ponieważ jesteśmy dobrymi samarytanami, ostrożnie obchodzimy się ze stosami macierzy i atrybutów OpenGL, żeby być pewnym, że funkcja odkręci wszystkie zmiany w zastanym stanie OpenGL jakie zrobi (dzięki temu osoba używająca funkcji nie powie pewnego dnia - Hmm... Macierz modelu w jakiś tajemniczy sposób uległa zmianie.).
Ponieważ używamy oteksturowanych quadów, to każda zmiana macierzy modelu przed wywołaniem naszego glCallLists będzie zastosowana na tekście. Znaczy to, że jest możliwość obracania lub skalowania tekstu (pewna przewaga nad używaniem bitmap WGL). Najbardziej naturalny sposób na wykorzystanie tej zalety to zostawienie macierzy modelu w spokoju, zatem pozostawienie stanu transformacji przed wywołaniem funkcji wypisującej będzie miało wpływ na tekst. Jednak ze względu na używanie macierzy modelu do ustalania pozycji czcionki, nie będzie to działać. Naszą następną najlepszą opcją jest zachowanie kopii wprowadzonej macierzy modelu i zastosowaniem jej między glTranslate a glCallLists. Jest to wystarczająco proste do zrobienia, ale ponieważ rysujemy tekst używając specjalnej macierzy projekcji uzyskane efekty mogą się różnić od oczekiwanych - wszystko będzie zinterpretowane w skali pikseli. Moglibyśmy dać sobie radę z tym problemem, gdybyśmy nie resetowali macierzy projekcji wewnątrz funkcji print. W niektórych sytuacjach jest to dość dobry pomysł, jeżeli spróbujesz osiągnąć to w ten sposób nie zapomnij o przeskalowaniu czcionek do właściwych wymiarów (często są około 32x32 a Ty prawdopodobnie będziesz chciał coś rzędu 0.01x.0.01).
Biblioteka jest już ukończona. Otwórz lesson13.cpp, bo wprowadzimy parę drobnych zmian, żeby zaprezentować właśnie napisane funkcje.
Pod innymi nagłówkami dodaj nagłówek freetype.h.
No i skoro tu już jesteśmy, stwórzmy globalny obiekt font_data.
Teraz musimy przetestować tworzenie i usuwanie zasobów dla naszej czcionki. Dodaj następującą linię na koniec InitGL:
Oraz dodaj tą linię na początek KillGLWindow, żeby usunąć czcionkę gdy już skończymy.
Potrzebujemy zmodyfikować funkcję DrawGLScene, żeby używała funkcji wypisującej. Mogło to być tak proste jak dodanie pojedynczej komendy "hello world" na końcu funkcji, ale zechciałem być trochę bardziej kreatywny i pokazać działanie obrotów i skalowania.
Ostatnią rzeczą do zrobienia jest dopisanie obsługi wyjątków. Idź do WinMain (bądź main) i dopisz blok try{...} na początku funkcji.
Teraz dodaj na końcu funkcji blok catch(){...}.
Jeżeli kiedykolwiek trafimy na wyjątek to otrzymamy małe okienko dialogowe wyjaśniające co się stało. Zauważ, że obsługa wyjątków może spowolnić Twój kod, więc jeżeli już będziesz wydawał ostateczną wersję swojego programu to możesz wyłączyć obsługę wyjątków.
To wszystko! Skompiluj program, powinieneś zobaczyć ładny tekst wyrenderowany przez FreeType, poruszający się pod oryginalnym bitmapowym tekstem z lekcji 13.
Jest wiele ulepszeń, które mógłbyś dodać do tej biblioteki. Na przykład używanie obiektów czcionek bezpośrednio jest trochę niezgrabne, więc mógłbyś zrobić jakiś manager czcionek. Mógłbyś również, tak jak w samym OpenGL, stworzyć stos czcionek, który pozwoliłby Ci uniknąć posługiwania się referencjami do obiektów czcionek każdego razu, gdy wywołujesz funkcję print. (To są wszystkie rzeczy jakie aktualnie robię we własnym kodzie, ale zdecydowałem się pozostawić tutorial prostym.) Może również chciałeś zrobić wersję funkcji wypisującej, która wycentrowuje tekst, żeby to zrobić możesz potrzebować technik opisanych niżej.
Mam tekst obracający się wokół jego środka. Jednakże, by osiągnąć taki efekt dla dowolnego tekstu, musisz wiedzieć dokładną długość linii tekstu - to może być odrobinę skomplikowane. Jedynym sposobem, żeby otrzymać długość tekstu jest umieszczenie komend glBitmap w listach wyświetlania czcionek, po kolei modyfikować pozycję rasteryzacji tak jak macierz modelu (pozostawiłem niezbędny kod, ale zakomentowany). Wtedy możesz ustawić pozycję rasteryzacji na x, y zanim wywołasz glCallLists i użyć glGet, żeby otrzymać pozycję rasteryzacji po narysowaniu tekstu - różnica między pozycjami rasteryzacji to długość tekstu w pikselach.
[T Takie potrzaskane metody wynikają z używania list wyświetlania. Według mnie najlepiej we własnej obsłudze FreeType'a pozbyć się list wyświetlania. T]Pamiętaj, że czcionki FreeType używają znacznie więcej pamięci niż bitmapowe czcionki (to jest ich przewaga - zajmują znacznie mniej). Jeżeli z jakiś powodów potrzebujesz oszczędzać pamięć karty graficznej, możesz skorzystać z kodu z lekcji 13.
Inną korzyścią używania oteksturowanych quadów do rysowania tekstu są właśnie te quady. Inaczej niż bitmapy, quady świetnie współpracują z funkcjami wybierania OpenGL (zobacz lekcję 32). Ułatwia to tworzenie tekstu, który reaguje na trzymanie kursora myszy nad nim lub na kliknięcie w niego. (Zrobienie bitmapowych czcionek WGL, które będą dobrze współgrać z funkcjami wybierania jest możliwe, znów należy użyć pozycji rasteryzacji do zdobycia długości tekstu w pikselach.)
No i wreszcie są gotowe biblioteki do obsługi czcionek w OpenGL, poniżej znajdują się linki do ich stron. W zależności od Twoich celów i kompilatora, może będziesz chciał używać jedną z nich zamiast tego kodu (jest ich znacznie więcej, ale generalnie dołączyłem te, z którymi miałem jakieś doświadczenia).
GLTT Ta biblioteka jest stara i wygląda na to, że już nie jest rozwijana, ale zbierałą pewne bardzo pozytywne opinie. Bazuje na FreeType1. Myślę, że potrzebowałbyś starych źródeł FreeType1, by skompilować ją. Możesz ściągnąć ją z http://www.opengl.org/developers/faqs/technical/fonts.htm
OGLFT Fajna biblioteka bazująca na FreeType2. Wygląda na przeznaczoną dla Linuksowych maszyn. http://oglft.sourceforge.net/
FTGL Trzecia biblioteka bazująca na FreeType, pisana z myślą o OS X, ale działa również z innymi systemami. http://homepages.paradise.net.nz/henryj/code/#FTGL
FNT Biblioteka nie bazująca na FreeType, będąca częścią PLIB. Zdaje się mieć przyjemny interfejs, używa własnego formatu czcionek. http://plib.sourceforge.net/fnt