Grafika 3D
 
  Zarejestruj się
::  Newsy  ::  Pliki  ::  Twoje Konto  ::  Forum  ::
Menu
· Strona główna
· Forum
· Linki
· Lista u?ytkowników
· O nas...
· Pliki
· Statystyki
· Twoje Konto
Tutoriale
· API
· Matematyka
· Teoria
· Direct3D
· OpenGL
· Techniki
Kto Jest Online
Aktualnie jest 36 gość(ci) i 0 użytkownik(ów) online.

Jesteś anonimowym użytkownikiem. Możesz się zarejestrować za darmo klikając tutaj
Tutoriale - Techniki - Tekst

Frustracja i problem z napisaniem czegokolwiek na ekranie przez mało doświadczonych użytkowników komputerowego świata 3D są znane na pewno każdemu, kto zaczynał się bawić w programowanie grafiki. O ile w 2D było to śmiesznie proste i nie było z tym jakiegoś wielkiego problemu to na scenie trójwymiarowej, na której wszystko się obraca i porusza we wszystkich możliwych kierunkach umiejscowienie prostego tekstu do dzisiaj dla wielu stanowi nie lada wyzwanie. Przez lata próbowano wypracować jakąś technikę, która zapobiegła by temu problemowi i jednocześnie umożliwiała skuteczne posługiwanie się nią, no ale odbywało się to z rożnymi skutkami. Głównym problemem zawsze była przenośność - bowiem na każdym systemie pisanie odbywa trochę inaczej i to stanowiło główną przeszkodę w ujednoliceniu pisania na ekranie. No ale z biegiem czasu w systemach 3D zaczęły rządzić tekstury...

Bitmapa ma te fajna właściwość, ze jej oglądanie dzisiaj jest możliwe pod niemal każdym systemem - wyobrażacie sobie, żeby używać jakiegoś systemu, który nie ma możliwości oglądania grafiki? W tym momencie na pewno Unixowcy by mnie zakrzyczeli, ale pewnie i tak nie czytają tej strony, więc załóżmy, ze każdy z nas kiedyś bitmapę widział i wie jak wygląda ;-). Po cóż nam ona będzie potrzebna? Zaraz sobie wszystko wyjaśnimy dokładniej, a teraz jeszcze pójdźmy trochę w przód, do naszego świata 3D. Jak wiemy w naszym świecie wszystko oglądamy jak na razie jako trójkąty, kawałki płaszczyzn. Potrafimy doskonale je animować, ustawiać i przedstawiać na naszej scenie a jak im jeszcze zapodać tekstury to już jest zupełny odlot i wszystko fajowo wygląda ;-). Tekstury są, jeśli najprościej na to spojrzeć po prostu bitmapami, które są nakładane na trójkąty, no i jeśli te trójkąty ułożyć w jakieś ładne bryły, ubrać w błyszczące tekstury to dostaniemy cały wirtualny świat.

Pewnie wielu od razu wpadnie na pomysł, że można stworzyć modele liter i je wyświetlać na ekranie - mówimy przecież o pisaniu tekstów dzisiaj. Pomysł w pierwszej chwili mógłby wypalić - w sumie zyskujemy sporo, bo możliwość pisania w dowolnym miejscu przestrzeni, obracania i uzyskiwania rożnych fajnych efektów, mając do dyspozycji pełen arsenał sztuczek dostępnych w 3D. Nam jednak nie do końca o to chodzi. Powiedzmy, ze mamy do napisania bardzo dużo tekstu, który po prostu ma być wyświetlany na ekranie przez jakiś czas. Załadowanie modeli liter i ich obróbka na scenie przede wszystkim zabierze nam czas jakże dla nas drogocenny oraz miejsce w pamięci karty grafiki (niemniej potrzebne)., więc nie do końca tedy droga. Nie mowie już nawet o samych modelach, które trzeba będzie stworzyć, przechować i odpowiednio układać na scenie z każdą klatką. Nie, nam to podejście zupełnie nie odpowiada i my wykombinujemy coś znacznie prostszego, co da nam zupełnie przyzwoite wyniki wydajnościowe a będzie na tyle uniwersalne, ze będzie się tym można posługiwać w miarę elastycznie a co najważniejsze, żeby dało się to stosować w miarę przenośnie.

Dzisiaj zatem połączymy przyjemne (czyli 3D) z pożytecznym (czyli bitmapami) i powiemy sobie o fontach bitmapowych. Cóż takiego to będzie... Otóż, skoro mamy do dysoozycji bitmapy wszelkiej maści, trójkąty, które nasza karta potrafi wyświetlać niesamowicie szybko więc możemy skorzystać z ich pomocy do napisania czegoś na ekranie. Sama idea jest bardzo prosta i banalna i nie trzeba tutaj wielkiej filozofii, żeby ja zrozumieć. Po prostu nałożymy sobie bitmapę na trójkąt i go odpowiednio wyświetlimy. Bitmapa taka będzie oczywiście zawierała jak nietrudno się domyślić literki, które pragniemy zobaczyć na naszym ekranie a trójkąty będą przekształcane w specjalny sposób, który spowoduje odpowiedni ich wygląd na ekranie. W ten bardzo prosty sposób dostaniemy do ręki jednocześnie bardzo potężne narzędzie, dzięki któremu nie tylko będziemy w stanie wyświetlać tekst na ekranie ale będziemy mieli dostęp do wszystkich sztuczek znanych nam ze świata 3D - a więc poprzez wszystkie przekształcenia, dzięki którym będziemy mogli osiągnąć rożne fajne efekty z napisami poczynając od pływania, latania, zanikania i co tylko będziemy w stanie wymyślić a kończąc na zaawansowanych operacjach z użyciem vertex i pixel shaderów - tutaj ogranicza nas tylko nasz własna wyobraźnia. No ale żeby zacząć całą zabawę trzeba na początku trochę się pomęczyć

Cala nasza zabawę należy zacząć oczywiście od stworzenia bitmapy zawierającej nasze literki. Na sam początek wystarczy podstawowy zestaw znaków ascii zawierających się w interesującym nas przedziale - czy potrzebne nam jest wszystkie 256 znaków opisanych znakami ascii to już pozostaje do oceny i zależy od potrzeb sceny. Powstaje w tym momencie pytanie, w jakim rozmiarze powinny być te literki i jaką wielkość powinna posiadać bitmapa. Oczywiście idealnym rozwiązaniem jest wielkość odpowiadająca rzeczywistemu rozmiarowi wyświetlanych liter - czyli tak jak zobaczymy je na ekranie. Nie zapominajmy jednak, ze mamy do dyspozycji wszystkie sztuczki z arsenału 3D - wchodzi więc na przykład w grę skalowanie, filtrowanie i inne tego typu rzeczy, które mogą w znacznym stopniu zmniejszyć zapotrzebowanie na pamięć i inne zasoby naszego komputera. Dla ułatwienia załóżmy, że nasza bitmapa będzie zawierała obrazy znaków w oryginalnej wielkości, czyli w takiej, w jakiej maja się pojawić na ekranie.

Drugi problem, to taki, jak maja być ułożone obrazy znaków na naszej bitmapie - odpowiedź jest jak zwykle podyktowana najważniejszym kryterium, jakim powinniśmy się kierować przy programowaniu grafiki 3D - oczywiście aby jak najszybciej można było na niej określić współrzędne tekstury, potrzebne do wycięcia odpowiedniej litery. Czyli nie układamy liter w żadne kolka ani inne takie. Najprostszą i dosyć skuteczną metodą jest po prostu ustawienie znaków jeden za drugim - wtedy mamy bardzo długą i bardzo wąską bitmapę. Zauważając jednak posiadaczy starszych akceleratorów może się zdarzyć tak, że nasza bitmapa przekroczy 256 pikseli w swojej długości (jeśli litery będą dosyć duże). Wtedy można się pokusić o położenie tekstu na przykład w kilku liniach i napisać algorytm, który będzie ułatwiał ich odpowiednie odczytywanie.
Przykładowa bitmapa zawierająca kształty liter może wyglądać dla przykładu następująco:


Mając przygotowaną bitmapę można przystąpić do prób napisania czegoś na ekranie. O sposobie powiedzieliśmy już wcześniej dosyć ogólnie, czas więc na szczegóły. Otóż dla każdej literki, którą chcemy nasmarować na ekranie będziemy musieli stworzyć prostokąt, złożony z dwóch trójkątów. (chodzi oczywiście o bryły 3D). To będzie zapewne banał, nawet dla raczkujących adeptów sztuki pisania w 3D, w sumie to przy odpowiednim podejściu będą cztery odpowiednio ułożone wierzchołki. Ponieważ w najprostszej wersji nasz napis będzie się tylko pojawiał na ekranie, więc współrzędne wierzchołków definiujących prostokąt dla każdej litery mogą odpowiadać ich faktycznemu położeniu na ekranie. Jeśli mamy zamiar z napisami robić rożne dziwne rzeczy musimy sobie zdecydować sami, czy przy wyświetlaniu będziemy je odpowiednio przekształcać. Ponieważ dla nas napisy będą jak na razie pełnić role wyłącznie informacyjna skupimy się na metodach a nie na fajerwerkach. Oczywiście problemów nie da się uniknąć i musimy sobie o nich zawczasu powiedzieć, żeby być przygotowanym na najgorsze.

Po pierwsze pamiętajmy, ze czcionki rozróżniamy proporcjonalne i nieproporcjonalne. Proporcjonalne to takie, w których szerokość matrycy znaku jest różna w zależności od litery - dobrym przykładem są tutaj fonty znane z Windows. Weźmy dla przykładu litery "w" i "i" - widać od razu, ze "w" jest o wiele szersza litera niż "i" i zajmuje więcej miejsca na ekranie. W czcionce nieproporcjonalnej szerokość matrycy znaku jest dla każdego znaku taka sama - przykładem może być już prawie dzisiaj nie używany pod Windows tryb tekstowy, który jednak wiele lat był używany a wspomniani przeze mnie Unixowcy nadal wielbią go ponad wszystko ;) - tam każdy znak wyświetlany na ekranie ma taka sama szerokość. Trzeba uważać, bo i dzisiaj takie graficzne czcionki można zapewne spotkać. Oczywiście ładniej wyglądają i dzisiaj bezwzględnie największe zastosowanie maja fonty proporcjonalne, dlatego warto od razu na początku zabawy założyć, ze takich właśnie będziemy używać -oszczędzi nam to wiele problemów i frustracji w dalszej pracy. Oczywiście jeśli ktoś się uprze to może napisać sobie super uniwersalną bibliotekę, która obsłuży wszelkie przypadki.

Po drugie - skoro już wiemy jakiej czcionki będziemy używać należy zatroszczyć się o teksturę ze wzorami liter. Tutaj mamy znowu kilka opcji, o których trzeba powiedzieć. Po pierwsze rozmiar tekstury - oczywiście dbamy aby był jak najmniejszy i możliwie kwadratowy no i w rozmiarach będących wielokrotnościami liczby dwa - to zapewni kompatybilność z jak największą ilością dostępnych na rynku akceleratorów. Jeśli tekstura wydaje nam się za duża (albo taka jest w istocie) to można podzielić nasze wzorce na kilka mniejszych kawałków, tylko wtedy oczywiście komplikuje nam się sprawa z wyszukaniem poszczególnych znaków.

Po trzecie - rozmiar. Na początku powiedziałem, ze dobrze będzie wygenerować znaki w oryginalnym rozmiarze, takim jak pojawia się na ekranie (bez macierzy perspektywy!). Ponieważ teksty z reguły nie są ogromne więc zadanie mamy nieco ułatwione tutaj, bo poszczególne znaki będą dosyć małe i istnieje duże prawdopodobieństwo że zmieszczą się wszystkie na jednej bitmapie. Nie zapominajmy również, ze mamy do dyspozycji takie narzędzia jak skalowanie i filtrowanie tekstur w 3D. Możemy więc w pewnych, rozsądnych granicach manipulować nawet jednym fontem tak aby osiągnąć różnorakie rozmiary - oczywiście w granicach rozsądku, które trzeba znaleźć sobie doświadczalnie - nie ma tutaj niestety uniwersalnego rozwiązania jak na razie (no chyba, ze ktoś z was wymyśli ;)).

Po czwarte i najważniejsze - trzeba znaleźć sobie współrzędne mapowania na teksturze, dla poszczególnych liter - sposobów jest mnóstwo i każdy ma jakieś zalety i wady. Można na przykład sobie ręcznie składać taką teksturę w programie do rysowania i ręcznie określać co i jak dla każdej litery, ale sposobu nie polecam - męczący i stwarza spore możliwości pomyłek. Co prawda mamy możliwości dopieszczenia samej tekstury w dowolnym programie graficznym, ale nie zrekompensuje to wysiłku włożonego w określenie współrzędnych. Można także programowo generować obrazy fontów (pisać i wrzucać do bitmapy) - to wiele łatwiejsze, mniej pracy kosztuje a i pomyłek można mniej narobić, choć kontrola nad rysowaniem fontu raczej znikoma. Na sam początek omówimy sobie właśnie drugi sposób, ponieważ pod Windows będziemy mogli w zupełnie banalny sposób sobie taką teksturę wygenerować, a nawet stworzyć sobie do tego celu wygodny edytor! Ale wszystko po kolei.

Mając już przygotowana bitmapę, określone współrzędne mapowania i potrzebne rozmiary możemy przystąpić do pisania po ekranie. Tutaj także mamy wielkie pole do popisu, ponieważ metod można wymyślać do woli jakie komu wygodne. My powiemy sobie o ogólnikach, które każdy będzie mógł dostosować do własnych potrzeb. Generalną zasadą, jaką będziemy się kierować przy rysowaniu liter będzie taka, ze są one zawsze widoczne. Ponieważ większość gier i silników nie używa obecnie sprzętowego z-bufora, który jest zbyto powolny więc dobra rada na sam początek - napisy rysujemy zawsze na samym końcu sceny, chyba ze istnieją jakieś specjalne okoliczności, które temu zaprzeczają. Jeśli używany jest z-bufor, wtedy najlepiej przed narysowaniem liter jest go wyłączyć, narysować litery i z powrotem włączyć. Po drugie - sposób rysowania liter - ponieważ są to dosyć charakterystyczne elementy, więc trzeba je rysować oczywiście z włączonymi opcjami alfa-blendingu, żeby nam się nagle nie zrobiło na ekranie mroczno od prostokątów, które będą zawierały poszczególne litery. I tutaj również należy pamiętać o wydajności urządzenia renderującego - najlepiej stosować różne sztuczki związane z prekalkulowanymi stanami urządzenia, listami wyświetlania itp. - ale to wszystko zależy od zastosowanego przez nas API graficznego. Najlepiej je oczywiście rysować w towarzystwie innych obiektów przezroczystych, pod warunkiem, ze będą one rysowane na końcu. Miałem także w tym miejscu wspomnieć o odpowiednim ustawianiu macierzy, aby napisy zawsze były ustawione do nas przodem ani nie były skalowane przez macierz perspektywy. Ale to nie jest konieczne, ponieważ dzięki naszym graficznym API wystarczy odpowiednio skonstruować strukturę wyświetlanego wierzchołka a API załatwi resztę. Wszystko okaże się na przykładzie.

A na jakiej zasadzie będzie się następnie odbywało pisanie na ekranie? Nic bardziej banalnego - mając tablicę ze współrzędnymi mapowania i teksturę, będziemy tworzyć odpowiednią liczbę trójkątów tworzących prostokąty, umieszczać je w odpowiednich miejscach na ekranie, nakładać na nich teksturę z odpowiednimi współrzędnymi mapowania i voila - napis gotowy.

Żeby jednak rozpocząć całą zabawę będziemy potrzebować na samym początku bitmapę i współrzędne. Do tego celu posłuży nam mały program, którego kompletny kod znajdziecie w dziale z przykładami, natomiast my omówimy sobie tylko pewne elementy. Opisując ten przykład zakładam u was pewną wiedzę na temat wykorzystania bitmap w API Windows, wierzę, że wiecie także coś o kontekstach kompatybilnych i zapisywaniu bitmap do pliku. Jeśli będzie okazja na pewno omówimy sobie te tematy w odpowiednich do tego celu działach. Mając zaś taki kod możecie się pokusić o napisanie na przykład fajnego edytora, w którym będzie można podglądać wygląd czcionki, tekstury, regulować wielkości, kolory, dodawać efekty specjalne itd... możliwości jest całe mnóstwo - może nawet ogłosimy mały konkurs? ;). Na sam początek wystarczy nam możliwość ustalenia rodzaju czcionki i jej cech charakterystycznych - wielkości, koloru, pochylenia, pogrubienia i podobnych - wszystko to, co tak naprawdę możemy znaleźć w porządnym edytorze tekstu. Program w wyniku swojego działania wygeneruje nam dwa pliki - bitmapę z obrazami naszych znaków oraz plik zawierający sporo linii, z których każda będzie zawierać cztery liczby float - współrzędne mapowania dla naszych liter. A najbardziej interesujący nas kawałek wygląda mniej więcej tak:
for( unsigned char c = 32; c < 128; c++ )
{
  str[0] = c;
  GetTextExtentPoint32( hDC, str, 1, &size );
  if( (DWORD)( x + size.cx + 1 ) > 512 )
  {
    x = 0;
    y += size.cy + 1;
  }
  ExtTextOut( hDC, x + 0, y + 0, ETO_OPAQUE, NULL, str, 1, NULL );

  fTexCoords[c-32][0] = ( (FLOAT) ( x + 0 ) ) / 512;
  fTexCoords[c-32][1] = ( (FLOAT) ( y + 0 ) ) / 512;
  fTexCoords[c-32][2] = ( (FLOAT) ( x + 0 + size.cx ) ) / 512;
  fTexCoords[c-32][3] = ( (FLOAT) ( y + 0 + size.cy ) ) / 512;

  x += size.cx+1;
}
Jak widać nic strasznego się tutaj nie dzieje i tak proste także jest w istocie. Zwykła pętla for, parę prostych przeliczeń i dwie funkcje API. A jak to wszystko działa? W naszej pętli bierzemy kolejne znaki ASCII, począwszy tego, który ma kod 32 (jest to spacja). Zmienna o nazwie str będzie nam potrzebna do udawania łańcucha znaków, ponieważ użyjemy do wyświetlania napisów (liter) funkcji API, które posługują się tylko łańcuchami znakowymi. Tak więc na samym początku wypełniamy nasz łańcuch i określamy nasz pierwszy znak (bo i tak tylko jego jednego będziemy wyświetlać). Następnie mamy wywołanie dosyć kosmicznie wyglądającej funkcji API o nazwie GetTextExtentPoint32(). Jak mówi dokumentacja, funkcja ta oblicza wysokość oraz szerokość podanego jako jej argument tekstu. Pierwszy parametr jaki pobiera to jednak nie łańcuch znaków, ale jak nietrudno się domyśleć uchwyt kontekstu urządzenia. Kiedyś w lekcji o API poświęconej grafice była mowa o kontekście i o funkcjach API dotyczących grafiki - i wiemy doskonale, że wszystkie funkcje graficzne, do których ta niewątpliwie się zalicza posługują się uchwytem w swoich działaniach. Następnym parametrem jest nasz tekst, który pragniemy przeanalizować. Powiązany z tym parametr trzeci, to ilość znaków w naszym łańcuchu, który określa ile znaków od początku łańcucha pragniemy niejako zmierzyć tą funkcją. U nas widać, że interesuje nas w zasadzie tylko jeden znak - pierwszy w łańcuchu - to wyjaśnia oczywiście podstawienie w pierwszej linii w ciele pętli. Ostatnim argumentem jest adres struktury typu SIZE, która przechowa dla nas jakże interesujące dane - czyli szerokość i wysokość badanego tekstu (tak naprawdę to jednej litery). A mając te wielkości będziemy mogli policzyć coś, co nas bardziej interesuje.
Dodać należy na koniec bardzo ważną rzecz. Otóż wspomniana funkcja API mierzy oczywiście wymiary tekstu, który jest napisany aktualnie ustawionym dla danego urządzenia (do którego kontekstu mamy uchwyt) fontem. Więc jeśli mamy zamiar coś mierzyć, to należy oczywiście pamiętać o wcześniejszym ustawieniu wszystkich potrzebnych rzeczy a w szczególności właśnie fontu.
if( (DWORD)( x + size.cx + 1 ) > 512 )
{
  x = 0;
  y += size.cy + 1;
}
Ponieważ nasza tekstura nie jest nieskończenie wielka i nie możemy sobie po niej hasać dowoli i bez ograniczeń trzeba coś wymyślić, żeby w razie czego nie wyjść poza nią i nie pogubić liter, których potem możemy szukać bezskutecznie gdzieś w przestrzeni. Ten prosty warunek pozwala w razie przekroczenia rozmiaru tekstury, kiedy wpisywać będziemy na nią kolejne znaki przenieść się jakby do kolejnej linii - po prostu w miejsce tekstury położone poniżej, tworząc jakby nową linię. Oczywiście uniwersalnej metody tutaj nie ma i należy iść na kompromis pomiędzy rozmiarem czcionki i wielkością tekstury. Fonty możemy zrobić naprawdę duże, ale czy jest sens robienia ich większych niż ekran? W naszym przykładzie założyłem, że nasza tekstura będzie miała rozmiar 512 wzdłuż - pomieści więc znaki fontu nawet dosyć sporego rozmiaru. Zmienna x przechowuje podczas pisania fontu na bitmapie aktualną pozycję danej litery i jeśli okazuje się, że jeśli następna przeznaczona do napisania już nie mieści się na teksturze to należy ją przenieść do następnej linii. Dobrym i bardzo fajnym narzędziem byłby tutaj właśnie edytor do fontów, który pozwalałby na pisanie, ustalanie rozmiarów tekstury i kontrolowanie wizualne jak wykorzystywania jest tekstura w stosunku do fontów - więc jeśli komuś będzie się nudziło to zapraszam - dział z plikami już czeka ;). Oczywiście może się zdarzyć tak, że nasz font będzie tak duży, że nie zmieści się na założonym rozmiarze tekstury - wtedy trzeba albo zwiększyć rozmiar tekstury albo podzielić znaki pomiędzy małe kawałki tekstur - do wyboru do koloru.
ExtTextOut( hDC, x + 0, y + 0, ETO_OPAQUE, NULL, str, 1, NULL );
Skoro ustalimy, w jakim miejscu bitmapy mamy wstawić znak, więc czas na kolejną funkcję API, czyli wypisanie naszego znaku na teksturze. Funkcja ExtTextOut() jak każda "graficzna" jako pierwszy argument przyjmuje kontekst urządzenia - dzięki niemu dowie się przecież jakim fontem ma napisać literę. Następnie podajemy współrzędne - zwykle są one podawane dla okna, w którym rysujemy, ale u nas jest to oczywiście bitmapa. Tutaj widać jak bardzo Windows nam ułatwia życie - dla niego jest wszystko jedno, gdzie piszemy, czy na ekranie, czy na drukarce czy bitmapie - odpowiednio kombinując możemy wszystko a sposób wywołania funkcji jest cały czas taki sam. Czwarty i piąty parametr są ze sobą blisko spokrewnione - Wartość ETO_OPAQUE mówi o tym, że jakiś prostokąt ma być wypełniony kolorem tła. Prostokąt ten jest określany za pomocą parametru nr. pięć. Otóż dla różnych dziwnych efektów możemy nakazać omawianej funkcji wyświetlić interesujący nas tekst w danym jako jeden z jej parametrów prostokącie. Jeśli na przykład napis nie mieści się nam w nim bo jest za duży, to możemy ten napis obciąć do tego prostokąta. Ale ponieważ u nas parametr piąty jest pusty więc żadne kombinacje z takim prostokątem nie wchodzą w grę. Szósty parametr i siódmy to znowu nierozłączna para - nasz tekst i ilość znaków, jaką z niego wypisujemy - działanie jest dokładnie takie samo jak w przypadku poprzednio omawianej funkcji. Ostatni parametr jest dla nas zupełnie nieistotny, więc też nie będziemy się nim przejmować.
fTexCoords[c-32][0] = ( (FLOAT) ( x + 0 ) ) / 512;
fTexCoords[c-32][1] = ( (FLOAT) ( y + 0 ) ) / 512;
fTexCoords[c-32][2] = ( (FLOAT) ( x + 0 + size.cx ) ) / 512;
fTexCoords[c-32][3] = ( (FLOAT) ( y + 0 + size.cy ) ) / 512;

x += size.cx+1;
No i po tych wszystkich kosmicznych zabiegach, żeby coś narysować na naszej bitmapie czas przyszedł na określenie współrzędnych - czyli trzeba zapamiętać, gdzie na bitmapie leżą poszczególne znaki, żeby program renderujący mógł sobie bez problemu wyciąć z tekstury odpowiedni kawałek, nałożyć na trójkąt i wyświetlić nam piękną literę na ekranie. Zmienna fTexCoords to tablica zawierzająca czwórki liczb typu float (jak na współrzędne przystało) dla tylu znaków, ile sobie pragniemy zapamiętać na naszej bitmapie. Każdy kolejny znak będzie określony przez cztery liczby - dwie pary współrzędnych określających prostokąt na bitmapie. Wyliczenie współrzędnych jest jak widać banalnie proste - z prostych proporcji określamy w którym miejscu bitmapy leżą poszczególne punkty. Przypomnijmy, że zmienne x i y oznaczają aktualny punkt, od którego zaczynamy rysowanie na bitmapie a struktura size zawiera rozmiary tekstu wyliczone za pomocą funkcji GetTextExtentPoint32(). Oczywiście dla innego rozmiaru tekstury inne będą wartości przez które należy dzielić. Należy oczywiście także pamiętać, że nie wolno nam już potem zmienić niczego na wygenerowanej w taki sposób bitmapie czy we współrzędnych - bo po prostu nam się wszystko rozjedzie i fonty być może i się wyświetlą, ale nie koniecznie w sensowny i zrozumiały sposób.

Mając tak wygenerowany obraz i tablicę ze współrzędnymi nie zwlekając zapisujemy więc do odpowiednich plików i możemy odpalić naszego dzielnego renderera aby zaaplikować mu nowy i dosyć niezbędny trzeba przyznać feature, jakim będą niewątpliwie napisy.
Wczytanie zarówno bitmapy jak i pliku ze współrzędnymi nie powinno stanowić dla was jakiegoś wielkiego wyzwania, tak więc zostawimy to może do waszych przemyśleń. My zajmiemy się zaś o wiele ciekawszą zabawą a mianowicie wyświetlaniem napisów na ekranie. Zarówno w OpenGL i jak i w Direct3D sprawa odbędzie się w sposób niemalże identyczny. Tak więc na pewno wczytamy sobie naszą teksturę, najlepiej jeśli od razu będzie zawierać kanał alfa (można przerobić ją dla przykładu w photoshopie) i zapisać w formacie, który taką informację może przechować (np. *.tga), albo jakoś inaczej kombinować - w każdym razie ostateczny cel to tekstura z kanałem alfa w pamięci. Druga sprawa - współrzędne mapowania. Dla każdej wczytanej tekstury z fontami należy mieć dostępną oczywiście tablicę ze współrzędnymi - to są jednak absolutne podstawy i mam nadzieję, że nie muszę was tego uczyć. Załatwiwszy te dwie sprawy pozostaje potem już tylko kilka rzeczy, które potrafimy robić zupełnie doskonale - czyli stworzenie odpowiedniego bufora na wierzchołki, odpowiednie dobranie współrzędnych wierzchołków, wypełnienie bufora odpowiednimi wartościami, nałożenie na trójkąty odpowiedniej tekstury i jazda na ekran. Wszystko niby proste, prawda? Ale kryje się tutaj jeszcze parę problemów, o których trzeba powiedzieć:

1. Bufor wierzchołków - wiadomo nie od dzisiaj, że aby go stworzyć musimy znać dwie rzeczy - rozmiar struktury określającej wierzchołek oraz ilość wierzchołków jaką mamy zamiar w nim umieścić. Z pierwszą rzeczą nie ma raczej wielkiego problemu - jeśli ma to być prosty napis to wiadomo, że będą nam potrzebne współrzędne wierzchołka oraz współrzędne mapowania tekstury. Jeśli zapragniemy jakiś efektów specjalnych to oczywiście odpowiednio dodamy co trzeba. Gorzej, choć nie tak źle będzie z rozmiarem - dla każdej litery będzie potrzeba dwóch trójkątów, które w sumie utworzą prostokąt, na który zostanie naciągnięta odpowiednia tekstura. Rozmiar bufora w ostatecznym rozrachunku będzie więc wynosił przy zastosowaniu najbardziej ekonomicznego wariantu budowania prostokątów 4 * ilość liter w napisie - należy o tym oczywiście pamiętać, o ile nie mamy zamiaru w inny sposób tworzyć i renderować trójkątów.

2. Położenie na ekranie - wydawać by się mogło że to będzie stanowiło nie lada wyzwanie, ale jeśli się temu bliżej przyjrzeć nie będzie aż tak źle. W tym przypadku potrzebna będzie trochę znajomość manipulacji macierzami ale przecież to potrafimy doskonale robić, więc nie mamy się czym martwić ;). A dodatkowo w zależności od zastosowanego API graficznego będziemy mieć inne możliwości, które znacznie sprawę nam ułatwią. Doskonale wiemy, jak wygląda napis na ekranie a przynajmniej jak powinien wyglądać. Nie jest to model 3D w kojarzącym nam się znaczeniu. Jest on zawsze widoczny przodem, nie jest jakoś specjalnie przekształcony - bo nie takie ma on zadanie. Napis przeważnie pełni rolę informacyjną więc powinien być dobrze widoczny. Aby tak przedstawić na scenie trójkąty zawierające teksturę z poszczególnymi literami będzie potrzebny prosty i bezbolesny zabieg. Kiedy renderujemy scenę zawierającą prawdziwe modele 3D, które szaleją po scenie wiemy, że w najprostszym przypadku przechodzą one przez 3 macierze, które umożliwiają spojrzenie na nie z odpowiedniego kąta i odpowiednie ich poruszanie po scenie. Dla napisów nie będzie to oczywiście konieczne - one nie będą się ani obracać, nie muszą być przekształcone przez macierz projekcji aby uwypuklić ich przestrzeń ani nie muszą być oglądane pod odpowiednim kątem. Jedyne co trzeba zrobić to ustawić odpowiednie trójkąty w odpowiednim miejscu. Wymagany przez nas efekt osiągniemy ustawiając dla renderingu liter odpowiednio macierze przekształceń. Ponieważ jak wspomniałem nie będziemy uwydatniać przestrzenności płaskich przecież liter więc tym razem odpada nam tworzenie macierzy perspektywy. Wiemy także z lekcji teoretycznych, że istnieją różne rzuty, które mają za zadanie odpowiednio przekształcić współrzędne wierzchołków aby po wyświetleniu dały konkretny widok. W kręgu naszych zainteresowań będzie dzisiaj leżał rzut prostokątny, który tak naprawdę ma za zadanie ułatwić rzutowanie brył na płaszczyzny (jak w rysunku technicznym) a nie powodować, że bryły na rysunku czy ekranie wydają się przestrzenne - dla naszych liter więc idealnie. Tak więc pierwszy krok w naszej funkcji renderującej to stworzenie macierzy projekcji, która będzie tak naprawdę macierzą reprezentującą rzut prostokątny a więc mający niewiele wspólnego z perspektywą. Jedną uwagę trzeba przy tym dodać jeśli chodzi o płaszczyzny obcinania - macierz projekcji zawsze definiuje stożek widzenia, w którym są widoczne obiekty na scenie. Jest on ograniczony sześcioma płaszczyznami, jak łatwo można sobie wyobrazić. W przypadku pisania napisów i rzutowania prostokątnego dobrze będzie założyć, że płaszczyzny te będą niejako stanowiły krawędzie naszego okna, jednym słowem mówiąc ich współrzędne będą takie same jak wymiary okna, w którym będziemy wyświetlać obraz. A to wszystko dlatego, aby sobie ułatwić sprawę. Jeśli zdefiniujemy sobie stożek widzenia, tak że jego współrzędne będą jednocześnie współrzędnymi okna to bardzo łatwo będzie nam manipulować położeniem liter na ekranie. Po prostu wystarczy wtedy podać rzeczywiste położenie litery w oknie w pikselach i tak samo zostanie umieszczona ona w stożku a w zasadzie w sześcianie widzenia (jeśli chodzi o rzutowanie prostokątne).

3. Wyświetlanie - uzbrojeni w odpowiednią macierz rzutowania będziemy mogli przystąpić do malowania na ekranie. Pozostaje kwestia wielkości trójkątów, jakie będą widziane na ekranie - dzięki rzutowaniu prostokątnemu i odpowiednio ustawionym płaszczyznom obcinania stożka (sześcianu) widzenia będziemy widzieć wierzchołki dokładnie tam, gdzie współrzędne pikseli. Dla przykładu wierzchołek o współrzędnych (10, 10) zostanie umieszczony w oknie Windows dziesięć pikseli od lewego i górnego rogu ekranu, wierzchołek o współrzędnych ujemnych będzie znajdował się poza oknem, tak samo jak ten, który posiada współrzędne większe niż rozmiary okna. No ale to chyba oczywiste. A zatem jak wykombinować jak ma być duży trójkąt na ekranie? Najprościej byłoby mieć po prostu rozmiar w pikselach każdej litery i odpowiednio dla niej tworzyć takie trójkąty. Ale załóżmy, że mamy do dyspozycji tylko współrzędne mapowania na teksturze oraz że znamy rozmiary naszej bitmapy - czy da się coś z tego wycisnąć? Ależ oczywiście!, przypomnijmy sobie tylko w jaki sposób obliczaliśmy współrzędne mapowania. Z prostej proporcji wyliczaliśmy w jakim miejscu na teksturze względem całej tekstury leżą poszczególne litery - ten sam manewr zastosujemy tutaj, tylko że w drugą stronę!
float    w = ( tx2 - tx1 ) * m_dwTexWidth * m_fAspectX;
float    h = ( ty2 - ty1 ) * m_dwTexHeight * m_fAspectY;
Proste, ale takie rozwiązania są najpiękniejsze ;). No ale co do czego. Zmienne tx2 i tx1 to odpowiednio współrzędne prawej i lewej strony na teksturze, które odczytamy z tablicy zawierającej czwórki liczb float, stanowiące o literze. Odejmując je od siebie odczytamy jaką część szerokości tekstury zajmuje dana litera. Mnożąc to przez szerokość tekstury dostaniemy fizycznie w pikselach jak duża jest litera na teksturze i na ekranie. Dodatkowe mnożenie przez zmienną m_fAspectX pozwala na stosowanie nie tylko kwadratowych tekstur ale w zasadzie o dowolnym stosunku szerokości do wysokości tekstury. Nie muszę chyba dodawać, że w przypadku wysokości liter robimy dokładnie to samo a współczynniki m_fAspectY wyliczamy odpowiednio w zależności od rozmiarów tekstury. Jeśli zaś planujemy używanie tylko kwadratowych tekstur to ten element możemy zupełnie pominąć.

No i cóż, tak uzbrojeni we wszystkie potrzebne dane możemy przystąpić do pisania. Załóżmy, że mamy funkcję piszącą, która dostaje jako parametr łańcuch znaków do wyświetlenia. Mamy przygotowaną tablicę ze współrzędnymi mapowania i dane dotyczące tekstury, takie jak wysokość i szerokość. Lecąc teraz w pętli po łańcuchu znaków wyszukujemy dla niego odpowiedni zestaw współrzędnych mapowania w tablicy, na ich podstawie wyliczamy rzeczywistą wielkość liter w pikselach a co za tym idzie i trójkątów, tworzymy odpowiednio duże trójkąty bazując na tych wyliczonych danych, wypełniamy bufor wierzchołków no i na koniec ustawiamy wszystko co trzeba jeśli chodzi o atrybuty renderingu, czyli przeźroczystość, tekstury itd. No i po wywołaniu naszej funkcji rysującej powinniśmy dostać piękny napis na ekranie.
Oczywiście podana metoda ma także wady - napisy zatem nie mogą być przede wszystkim wieloliniowe - tutaj trzeba pokombinować, ale funkcję wyświetlającą wielolinone wiersze można napisać naprawdę bez trudu. Bufory wierzchołków także nie powinny być zbyt duże na jedną linię, ponieważ może to niekorzystnie wpływać na szybkość renderingu, no ale ilość liter w jednej linii nigdy nie powinna powodować jakiś niekorzystnych wymiarów bufora wierzchołków (przypominam *4 ilość liter przy najbardziej ekonomicznych prymitywach) - tak wynika z mojego doświadczenia. Można także optymalizować na różne sposoby niektóre operacje - na przykład przygotować wcześniej, obok współrzędnych mapowania także faktyczne położenie liter na teksturze w pikselach, żeby szybciej i łatwiej tworzyć odpowiedniej wielkości trójkąty. Sposobów optymalizacji można tutaj wymyślać naprawdę sporo i trudno o jakiś bardzo uniwersalny, no ale to nie jest oczywiście temat na ten artykuł.

Chciałbym na koniec, jeśli chodzi o technikę macierzy z rzutowaniem prostokątnym dodać jeszcze kilka słów na temat techniki renderingu w Direct3D. Otóż API to posiada pewną interesującą cechę, która skutecznie eliminuje potrzebę używania macierzy projekcji. Do wypełnienia bufora wierzchołków można użyć tzw. wierzchołków nie przetransformowanych. Są to wierzchołki, które nie przechodzą przez taśmociąg geometrii i nie są przekształcane przez Direct3D za pomocą dostarczonych przez nas macierzy - to zadanie należ do nas. Jeśli byśmy chcieli zmusić takie wierzchołki do ruchu sami musielibyśmy napisać procedury do mnożenia wierzchołków przez macierze i sami robić wszystkie przekształcenia. Nam jednak na tym zupełnie nie zależy a staje się to wręcz dla nas błogosławieństwem. Okazuje się bowiem, że wyświetlenie wierzchołków nie przetransformowanych daje dokładnie taki sam efekt jak zastosowanie macierzy z projekcją prostokątną! Jeśli takiemu wierzchołkowi ustawimy współrzędne (0, 0) to pojawi się on w lewym górnym rogu okna, jeśli (10, 10) to będzie położony 10 pikseli od góry i od lewej krawędzi okna -czyli dokładnie tak, jak tego chcieliśmy. Tak więc do przygotowania napisów w Direct3D możemy z czystym sumieniem używać wierzchołków nie transformowanych i nie trudzić się o ustawianie odpowiednich macierzy. Niestety, w OpenGL nie jestem zbyt biegły więc nie wiem czy taka możliwość istnieje.

Dwa przykłady, które dołączyłem do tutorialu, a które znajdują się oczywiście w projekcie prezentują wykorzystanie omówionych technik zarówno w OpenGL jak i Direct3D, co prezentują przedstawione poniżej ekrany. Technika ma jedną, bardzo ważną zaletę - jest całkowicie przenośna pomiędzy wszelakimi API graficznymi do tworzenia grafiki 3D i pomiędzy różnymi systemami, co powinno nas tylko cieszyć. Oczywiście kody przykładów nie są w żaden sposób optymalizowane, bo i nie ma takiej potrzeby. Ale posiadają tę jedną zaletę, że przedstawiają bardzo szeroki zakres zagadnień, które do tej pory omawialiśmy i jeśli dokładnie sobie przykłady przeanalizujecie to będzie okazja sobie przypomnieć sporo rzeczy. Poniższe rysunki pokazują wyniki działania przykładowych programów na dowód, że to nie tylko czcze gadanie. No i mam nadzieję, że od dzisiaj pisanie napisów nie będzie stanowiło już dla was problemów ;).




Kod źródłowy (glText.zip)

Kod źródłowy (dxText.zip)

©Copyright by Robal   



Tutoriale - Techniki
Nasze newsy s� w RSS: backend.php
PHP-Nuke Copyright © 2005 by Francisco Burzi. This is free software, and you may redistribute it under the GPL. PHP-Nuke comes with absolutely no warranty, for details, see the license.
Tworzenie strony: 0.04 sekund

:: Layout strony został stworzony przez www.nukemods.com w oparciu o styl phpbb2 Helius, którego autorem jest Cyberalien ::