Lekcja powstała dzięki NeHe & Giuseppe D'Agata...
Wszyscy pewnie mają już dość czcionek. Lekcje, które wcześniej zrobiłem nie tylko wyświetlały tekst, wyświetlały także tekst 3D, oteksturowane czcionki i mogły wyświetlać zmienne. Ale co jeżeli chcesz przenieść swój projekt na maszynę, która nie wspiera ani czcionek rastrowych, ani konturowych?
Dzięki Giuseppe D'Agata mamy kolejną lekcję o czcionkach. Pewnie pytasz co jeszcze może być na ten temat. Jeśli pamiętasz to przy pierwszej lekcji wspomniałem o używaniu tekstur przy rysowaniu liter na ekranie. Zazwyczaj kiedy używasz tekstur to odpalasz jakiś program garficzny, wybierasz czcionkę i piszesz tekst jaki chcesz póżniej wyświetlić. Następnie zapisujesz bitmapę i ładujesz ją do swojego programu jako teksturę. Niezbyt skuteczne dla programu, który potrzebuje dużo tekstu lub tekst się zmienia!
Ten program używa zaledwie JEDNEJ tekstury aby wyświetlić którykolwiek z 256 znaków na ekranie. Twój znak ma zaledwie 16 pikseli wysokości i 16 szerokości. Jeśli weźmiemy standardową teksturę 256x256 to zauważysz, że będziemy mieli 16 kolumn i 16 wierszy, czyli razem będziemy mieli 256 znaków, inaczej znak ma 16 pikseli szerokosci (wysokosci), więc 256 / 16 = 16 :).
Więc...Stwórzmy demonstrację czcionek ładowanych z tekstury! Program bazuje na kodzie z lekcji pierwszej. W pierwszej części programu dołączamy nagłówki math oraz stdio. Potrzebujemy math do poruszania tekstu po ekranie za pomocą sinusa i cosinusa, a stdio potrzebujemy aby upewnić się, że tekstura, z której chcemy zrobić czcionkę istnieje.
Stworzymy zmienną base do przetrzymywania list wyświetlania oraz zmienną texture[2] do przechowywania dwóch tekstur, które stworzymy. Pierwsza tekstura będzie teksturą z czcionką, a druga będzie zdarzeniową teksturą użytą do stworzenia prostego obiektu 3D.
Dodamy zmienną loop, którą będziemy wykorzystywać przy pętlach. Na końcu utworzymy dwa liczniki cnt1 i cnt2 do poruszania tekstu po ekranie i do obracania naszego obiektu 3D.
Teraz kod ładujący teksturę. Jest dokładnie taki sam jak w poprzedniej lekcji o oteksturowanych czcionkach.
Następny kawałek kodu jest lekko zmieniony w stosunku do poprzedniego. Jeśli nie jesteś powien co robią poszczególne linijki to wróć się i przypomnij sobie.
Zapamiętaj, że TextureImage[] przechowuje zapisy dwóch obrazów RGB. bardzo ważne jest dwukrotne sprawdzenie kodu powiązanego z łądowaniem i przechowywaniem tekstur. Jeden zły numer może spowodować w naszej pamięci ruinę!
Następna linia jest bardzo ważną linią przy sprawdzaniu. Jeśli zastąpiłbyś liczę 2 jakąkolwiek inną, pojawiłby się główny problem. Sprawdź dwa razy ! Ten numer powinien być taki sam jaki użyłeś przy TextureImages[].
Dwie tekstury jakie zamierzamy załadować to font.bmp (basza czcionka) i bumps.bmp. Druga tekstura może być zastąpiona jaką chcesz teksturą. Tekstura, którą stworzyłem nie jest zbyt kreatywna.
Kolejna ważna linia do podwójnego sprawdzenia. Nie mogę określić jak wiele e-maili dostałem od ludzi pytających <span class="i">"Dlaczego widzę tylko jedną teksturę albo dlaczego są białe ?!". Zazwyczaj ta linia jest problemem. Jeśli zastąpiłeś 2 przez 1 to tylko jedna tekstura będzie stworzona, druga będzie cała biała. Jeśli zastąpisz 2 przez 3 Twój program się posypie!
Powinieneś wywołać glGenTextures() tylko raz. Po glGenTextures() powinienieś wygenerować wszystkie swoje tekstury. Widziałem ludzi dających glGenTextures() przed stworzeniem każdej tekstury. Zazwyczaj powodowało to nadpisanie wcześniej utworzonej tekstury przez nową. Dobrą rzeczą jest wiedzieć ile tekstur chcesz wygenerować, wywołać raz glGenTextures() i wtedy generować wszystkie tekstury. Nie jest mądrą rzeczą wstawianie glGenTextures() w pętlę dopóki neie masz powodu aby to zrobić.
Następne linijki sprawdzą czy dane bitmap zostały załadowane w celu wygenerowania tekstury i czy dalej zajmują miejsce. Jeżeli są to zwalniamy je. Zauważ, że zwalniamy obydwie obrazki. Jeśli użyliśmy 3 obrazków do zbudowania teksutry to musi zwolnić 3 obrazy.
Teraz budujemy naszą czcionkę. Opowiem o tej części kodu z pewnymi szczegółami. To nie jest takie skomplikowane, ale jest trochę matematyki do zrozumienia i wiem, że matematyka nie jest tym co wszyscy uwielbiają.
Te dwie zmienne będą używane do przechowywania pozycji każdej litery w teksturze. cx będzie pozycją od lewej do prawej w teksturze, natomiast cy pozycją z góry na dół.
Następnie powiemy OpenGL, że chcemy zbudować 256 list wyświetlania. Zmienna base jest wskaźnikiem na pierwszą listę. Drugą listą jest base+1, trzecią base+2 itd.
Druga linijka kodu poniżej wybiera teksture naszej czcionki (texture[0]).
Teraz rozpoczniemy naszą pętlę. Pętla zbuduje wszystkie 256 znaków, przechowując każdy znak w jego własnej liście.
Pierwsza linia poniżej pewnie wygląda trochę zagadkowo. Symbol % oznacza resztę dzielenia przez 16. cx przemieszcze nas po teksturze od lewej do prawej. Zauważysz później w kodzie, że odjęliśmy cy od 1 żeby przemieszczać nas z góry na dół zamiast od dołu do góry. Symbol % jest dosyć trudny do wytłumaczenia ale spróbuję
Jesteśmy zainteresowani tym kawałkiem (loop%16) /16.0f po porstu konwertuje wynik na współrzędne tekstury. Więc jeśli loop było równe 16... cx mogłoby być resztą z 16/16 więc byłoby równe 0, a cy 16/16 czyli równe 1. Więc przemieścilibyśmy się w dół o jedną literę, ale nie ruszylibyśmy się w prawo. I tak dla kolejnych przypadków, dla loop = 17 cx = 0.0625 cy = 0.0625, dla loop=18 cx = 0.125 cy = 0.0625, dla loop = 32 cx = 0 cy = 0.125. Czyli prościej mówiać dzięki wykorzystując resztę z dzielenia lecimy od lewego brzego, przelatujemy linijkę i zmieniamy ją itd. Proste, no nie?
Uff :) OK. Więc teraz budujemy naszą czcionkę wybierając pojedyncze znaki z naszej tekstury w zależności od cx i cy. W linii poniżej dodamy do base wartość loop, jeśli tego nie zrobimy to każda literaz będzie wygenerowana w pierwszej liście wyświetlania. Nie chcemy, aby to się stało więc dodając loop do base, każdy kolejny znak jest przetrzymywany w kolejnej liście wyświetlania.
Teraz, gdy wybraliśmy, którą listę wyświetlania chcemy utworzyć, tworzymy nasz znak. Wystarczy narysować kwadrat i oteksturować go fragmentem z naszą czcionką z naszej tekstury.
cx i cy powinny przechowywać bardzo małe wartości od 0.0f do 1.0f. Jeśli cx i cy są równe 0 to pierwsza linijka kodu poniżej powinna wyglądać tak: glTexCoord2f(0.0f,1-0.0f-0.0625f). Pamiętaj, że 0.0625 to 1/16 (jedna szesnasta) naszej tekstury, lub wysokość/szerokośc naszej litery. Współrzędne tekstury poniżej powinny być jej lewym dolnym rogiem.
Zauważ, że używamy glVertex2i(x,y) zamiast glVertex3f(x,y,z). Nasza czcionka jest czcionką 2D, więc nie potrzebujemy wartości z. Ponieważ używamy rzutowania ortograficznego, nie musimy przekładać tego na ekran. Jedyne co musisz zrobić aby rysować na "ortograficznym ekranie" jest podanie współrzędnych x, y. Ponieważ nasz ekran jest pikselami od 0 do 639 i od 0 do 479, nie musimy używać zmiennych typu float ani wartości ujemnych :).
Sposób w jaki ustawimy nasz ekran to taki, że punkt (0,0) będzie lewym dolnym rogiem ekranu, a (640,480) będzie prawym górnym. 0 jest lewą częścią ekranu na osi X, a 639 prawą na osi X. 0 jest na osi Y dolną częścią ekranu, a 479 jest górną częścią ekranu na osi Y. W prosty sposób nie mamy ujemnych wartości :). To jest także przydatne dla ludzi, którzy nie dbają o perspektywę i wolą pracować z pikselami niż z jednostkami :).
Następna współrzędna tekstury to 1/16 na prawo od ostatniej współrzędnej (szerokość jednego znaku). Czyli to będzie dolny prawy punkt tekstury.
Trzecią współrzędną ma taką samą prawą współrzędną jak poprzednia, ale przenosimy ją w górę. To będzie prawy górny punkt.
Na końcu przenosimy się w lewo aby ustalić ostatnią współrzędna tekstury w lewym górnym rogu.
Na koniec przenosimy się na prawo. Jeśli tego nie zrobimy to litery będą rysowane jedna na drugiej. Ponieważ nasza czcionka bardzo wąska nie będziemy się przenosić 16 piskeli w prawo. Gdybyśmy tak zrobili to byłyby duże przerwy między literami. Przeniesienie tylko o 10 pikseli jest w sam raz.
Ta część kodu jest taka sama jaką użyliśmy wcześniej do zwalniania listy wyświetlania zanim nasz program się wyłączy.
Wszystkie 256 znaków zaczynająć od base zostanie skasowane. (przydatna rzecz!)
Następna część kodu to rysowanie naszego napisu. Wszystko jest zupełnie nowe, więc postaram się wytłumaczyć każdą linijkę bardzo szczegółowo. Mała uwaga: więcej rzeczy może zostać dodanych do tego kodu, jak np. wyświetlanie zmiennych, wielkość znaków, przerwy i wiele innych.
glPrint() zawiera trzy parametry. Pierwszą jest pozycja x na ekranie. Następnym jest pozycja y. następnie jest string (tekst, który chcemy wyświetlić), i ostatecznie zmienna nazwana set. Jeżeli patrzyłeś na bitmapę zrobioną przez Giuseppe D'Agata zauważysz, że są tam dwa różne typy znaków. Pierwszy jest normalny, a drugi z kursywą. Jeśli set jest 0 to pierwszy typ jest wybrany. Jeśli typ jest 1 lub wyższy to wybrany jest drugi typ czcionki.
Pierwsza rzecz jaką robimy to upewniamy się, że set jest równe o lub 1, jeżeli jest wyższe niż 1 to ustawiamy, że jest równe 1.
Teraz wybieramy teksturę czcionki. Robimy to w przypadku, gdy wcześniej wybraliśmy inną teksturą przy rysowaniu czegoś innego.
Następnie wyłączamy testowanie głębi (ang. depth testing). Powodem dla którego to robie jest to, że blending dziła wtedy ładniej. Jeśli nie wyłączysz depth testing telst może się kończyć wchodząc za "coś" albo blending może źle wyglądać. Jeśli nie zamierzasz mieszać tekst z ekranem (więc czarne przestrzenie wokół liter będą wokół naszych liter) możesz zostawić depth testing włączony.
Następnych kilka linijek jest bardzo ważnych. Wybieramy macierz projekcji. Zaraz po tym używamy polecenia glPushMatrix(). glPushMatrix() przechowuje aktualną macierz (projekcji). Coś jak przycisk "pamięć" na kalkulatorze.
Teraz, gdy nasza macierz jest przechowywana, resetujemy macierz i włączamy rzutowanie ortograficzne. pierwszy i trzeci parametr (0) ustawia lewy dolny róg ekranu. Mądrym rozwiązaniem jest ustawienie tego na taką rozdzielczość jaką aktualnie masz. Nie ma głębi więc ustalamy wartość z na -1 i 1.
Teraz wybieramy macierz modelu i przechowujemy używając glPushMatrix(). Następnie zresetujemy macierz więc możemy pracować używając rzutowania ortograficznego.
Z zachowanymi ustawieniami perspektywy i ustawionym rzutowaniem ortograficznym możemy rysować nasz tekst. Zaczniemy od ustalenia pozycji na ekranie, gdzie chcemy rysować. Użyjemy glTranslated() zamiast glTranslatef() ponieważ pracujemy z pikselami, więc nie potrzebujemy float. Przecież nie można mieć połowy piksela :D
Linijka poniżej wybierze odpowiednią czcionkę w zależności jaką wartość ma parametr set. Jeśli chcemy używać drugiej czcionki to dodamy 128 do aktualnej listy wyświetlania (128 do połowa ze wszystkich 256 znaków). Dodając 128 przeskakujemy pierwsze 128.
Teraz jedyne co nam pozostało to narysować litery na ekranie. Zrobimy to dokłądnie tak samo jak robiliśmy w innych lekcjach o czcionkach. Użyjemy glCallLists(). strlen(string) jest długością naszego tekstu (jak wiele znaków chcemy narysować) , GL_UNSIGNED_BYTE zonacza, że każdy znak jest reprezentowany przez unsigned byte (bajt jest wartością od 0 do 255). W końcu string przechowuje tekst jaki chcemy wyświetlić na ekranie.
Teraz wszystko co musimy zrobić to przywrócić nasz widok z perspektywy. Wybieramy macierz projekcji i używamy glPopMatrix() do przywrócenia ustawień poprzedio zapisanych za pomocą glPushMatrix(). Przywróćenie ustawień jest ważne, w przeciwnym razie będziesz je gromadził.
Teraz wybieramy macierz modelu i robimy to samo. Używamy glPopMatrix() aby przywrócić poprzednie ustawienia.
Ostatecznie włączamy depth testing. Jeśli wcześniej nie wyłączyłeś depth testing to ta linijka jest zbędna.
W ResizeGLScene() nic się nie zmieniło więc przechodzimy do InitGL().
Przechodzimy do kodu budującego teksturę. Jeśli budowanie się nie uda z nieznanych powodów, zwracamy FALSE.
Dzięki temu nasz program będzie wiedział, że wystąpił błąd i łaskawie się wyłączy.
Jeśli nie było błędów to budujemy naszą czcionkę. Nie bardzo coś może źle pójść więc nie sprawdzamy błędów.
Teraz przejdziemy do normalnych ustawień OpenGL. Ustawiamy kolor tła na czarny, czyszczenie bufora głębi. Wybieramy rodzaj testowania głębi. Włączamy smooth shading i włączamy 2D texture mapping.
Kod poniżej utworzy naszą scenę. Najpierw narysujemy nasz obiekt 3D, a następnie nasz tekst tak żeby pojawiał się na obiekcie inaczej obiekt przysłaniałby tekst. Powodem dla którego dodałem obiekt 3D jest pokazanie, że rzutowanie ortograficzne i perspektywa mogą być używa jednocześnie [tłum. bardzo przydatne :D].
Wybraliśmy naszą teksturę bumps.bmp więc możemy zbudować nasz obiekt 3D. Przenosimy się 5 jednostek w ekran żeby zobaczyć nasz obiekt. Obracamy się względem osi Z o 45 stopni. Nasz kwadrat będzie obracał zgodnie z ruchem wskazówek zegara i będzie bardziej wyglądał jak diament niż kwadrat.
Następnie obracamy obiektem względem osi X i Y bazując na zmiennej cnt1. To powoduje, że obiekt wiruje i tworzy efekt wirowania w miejscu.
Wyłączamy blending (chcemy aby obiekt był widoczny jako bryła) i ustawiamy kolor na jasny biały. Chcemy narysować oteksturowany, pojedynczy kwadrat.
Po narysowaniu pierwszego obracamy o 90 stopnie względem dwóch osi X i Y, i rysujemy następny. Drugi przecina pierwszy w połowie, tworząc ładny kształt.
Po narysowaniu dwóch kwadratów włączamy blending i rysujemy tekst.
Użyjemy kodu kolorującego z innej lekcji o czcionkach. Kolor zmienia się stopniowo jak tekst porusza w poprzek ekranu.
Następnie rysujemy nasz tekst. Użyjemy glPrint(). Pierwszym parametrem jest pozycja x, drugim pozycja y, a trzecim jest tekst jaki chcemy wyświetlić, natomiast ostatni parametr to typ czcionki jakiego chcemy użyć (0 - normalny, 1 - italic).
Jak zepwne się domyślasz będziemy przemieszczać tekst po ekranie za pomocą sinusa i cosinusa oraz dwóch liczników cnt1 i cnt2. Jeśli nie rozumiesz co robi sinus i cosinus wróć się do poprzedniej lekcji o tekście.
Ustawiamy kolor na ciemny niebieski i pisze imię autora na dole ekranu. Następnie ponownie piszemy jego imię używając jasnych białych liter. Białę litery są lekko na prawo w stosunku do niebieskich. TO tworzy efekt cienia. (Jeśli blending nie został włączony to efekt nie będzie widoczny).
Ostatnia rzecz jaką zrobimy to zwiększenie liczników o różne wartości. Dzięki temu tekst się porusza, a obiekt wiruje.
Kod w KillGLWindow(), CreateGLWindow() i WndProc() się nie zmienił więc go pomijamy.
Tytuł naszego okna się zmienił.
Ostatnią rzeczą do zrobienia jest dodanie KillFont() na koniec KillGLWindow() tak jak to zrobiłem poniżej. Dodanie tej linii jest bardzo ważne. CZyści pamięć zanim zakończymy program.
Myślę, że mogę teraz oficjalnie powiedzieć, że moja strona uczy jak wyświetlać tekst na ekranie na każdy możliwy sposób :D. Uważam to jest całkiem dobra lekcja. Kod może być użyty na każdym komputerze z OpenGL [tłum. co ? przecież tu jest WinApi !!! LoL], jest prosty w użyciu i ten sposób jest bardzo mało procesożerny :D (zużywa mało pamięci obliczeniowej).
Chciałbym podziękować Giuseppe D'Agata za originalną wersję tej lekcji. Trochę zmodyfikowałem kod, ale prawdopodobnie bez tego nie zrobiłbym tej lekcji. Jego wersja kodu ma trochę więej opcji takich jak np. ropzstaw znaków itd. Ale ja za to zrobiłem to super obiektem 3D :D.
Mam nadzieję, że wszystkim podobałą się lekcje. Jeśli macie pytania to pisać do mnie albo Giuseppe D'Agata.