Praca (część teoretyczna)

Transkrypt

Praca (część teoretyczna)
WYDZIAŁ ELEKTROTECHNIKI, AUTOMATYKI,
INFORMATYKI I INŻYNIERII BIOMEDYCZNEJ
KATEDRA INFORMATYKI STOSOWANEJ
Praca dyplomowa magisterska
Renderowanie szczegółowego terenu z użyciem teselacji
Detailed terrain rendering using tessellation
Autor:
Piotr Martynowicz
Kierunek studiów:
Informatyka
Opiekun pracy:
dr inż. Grzegorz Rogus
Kraków, 2014
Oświadczam, świadomy odpowiedzialności karnej za poświadczenie nieprawdy, że
niniejszą pracę dyplomową wykonałem) osobiście i samodzielnie i że nie korzystałem ze
źródeł innych niż wymienione w pracy.
……………………………………………………………………
Pracę dedykuję moim Rodzicom
– jako podziękowanie za trud
włożony w zapewnienie mi
odpowiedniej edukacji.
2.1. Przedmowa
5
1 SPIS TREŚCI
1
SPIS TREŚCI ....................................................................................................................................... 5
2
WSTĘP .............................................................................................................................................. 7
2.1
2.2
3
PRZEDMOWA ..................................................................................................................................... 7
CEL PRACY ......................................................................................................................................... 8
WYMAGANIA I TECHNOLOGIE ........................................................................................................ 11
3.1
DECYZJE .......................................................................................................................................... 11
3.2
C++ ............................................................................................................................................... 11
3.3
DIRECTX 11.0 .................................................................................................................................. 12
3.4
VISUAL STUDIO 2012 ........................................................................................................................ 12
3.5
ASSIMP ........................................................................................................................................... 16
3.6
INNE BIBLIOTEKI ................................................................................................................................ 16
3.6.1 stb_image ................................................................................................................................ 16
3.6.2 SimpleJSON .............................................................................................................................. 16
3.6.3 WinAPI ..................................................................................................................................... 17
3.6.4 DirectXMath............................................................................................................................. 17
3.6.5 Boost ........................................................................................................................................ 17
3.7
AUTORSKIE ROZWIĄZANIA ................................................................................................................... 17
4
ZAGADNIENIA TEORETYCZNE .......................................................................................................... 19
4.1
TESELACJA ....................................................................................................................................... 19
4.1.1 Zasada działania ...................................................................................................................... 19
4.1.2 Etapy potoku renderującego.................................................................................................... 20
4.1.3 Zalety i wady ............................................................................................................................ 24
4.2
TESELACJA PHONGA .......................................................................................................................... 27
4.3
HEIGHTMAPA ................................................................................................................................... 29
4.4
MAPA OBIEKTÓW TERENU .................................................................................................................. 31
4.5
DISPLACEMENT MAPPING ................................................................................................................... 34
4.6
OŚWIETLENIE ................................................................................................................................... 36
4.6.1 Podstawowe informacje .......................................................................................................... 36
4.6.2 Użyte wzory ............................................................................................................................. 37
4.6.3 Typy świateł ............................................................................................................................. 39
4.6.4 Metody cieniowania pikseli ..................................................................................................... 39
4.7
NORMAL MAPPING............................................................................................................................ 40
4.8
FRUSTUM CULLING ............................................................................................................................ 41
4.8.1 Bounding volume ..................................................................................................................... 41
4.8.2 Frustum .................................................................................................................................... 42
4.8.3 Zasada działania ...................................................................................................................... 43
4.9
INSTANCING ..................................................................................................................................... 44
4.10
SKYBOX ........................................................................................................................................... 44
5
IMPLEMENTACJA ............................................................................................................................ 47
5.1
OBSŁUGA APLIKACJI ........................................................................................................................... 47
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
6
2.1. Przedmowa
5.2
ŁADOWANIE MODELI I TWORZENIE MATERIAŁÓW ..................................................................................... 48
5.2.1 Elastyczny system ładowania modeli ....................................................................................... 48
5.2.2 Pliki materiałów ....................................................................................................................... 49
5.2.3 Ładowanie modeli .................................................................................................................... 54
5.3
TEREN ............................................................................................................................................. 54
5.3.1 Wykorzystanie teselacji do renderowania terenu .................................................................... 54
5.3.2 Automatyczne teksturowanie terenu ....................................................................................... 56
5.4
WODA ............................................................................................................................................ 58
5.5
OBIEKTY .......................................................................................................................................... 60
5.6
NIEBO ............................................................................................................................................. 65
5.7
UŻYTE MODELE ................................................................................................................................. 66
5.8
WYDAJNOŚĆ .................................................................................................................................... 66
6
PODSUMOWANIE ........................................................................................................................... 69
6.1
6.2
REALIZACJA CELÓW ............................................................................................................................ 69
MOŻLIWE USPRAWNIENIA I PERSPEKTYWY ROZWOJU ................................................................................ 69
7
PRZYPISY ........................................................................................................................................ 71
8
BIBLIOGRAFIA ................................................................................................................................. 73
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
2.1. Przedmowa
7
2 WSTĘP
2.1 PRZEDMOWA
Podstawą współczesnej trójwymiarowej grafiki czasu rzeczywistego jest siatka
(ang. mesh) złożona z prymitywów (ang. primitives), najczęściej – trójkątów, na którą
„nałożony” jest materiał definiujący kolor powierzchni. Liczba trójkątów bezpośrednio
przekłada się na stopień szczegółowości modelu – im więcej jest trójkątów i im są one
mniejsze, tym wierniej oddają modelowany kształt, tym gładsza/bardziej zaokrąglona jest
powierzchnia i tym lepiej model wygląda.
Niestety liczba trójkątów jest również wprost proporcjonalna [1] do ilości pracy,
jaką karta graficzna musi wykonać, i odwrotnie proporcjonalna do wydajności aplikacji
(liczba FPS). Dlatego od początku grafiki czasu rzeczywistego programiści skupiali się na
szukaniu sposobów „oszukiwania”, przybliżania i upraszczania efektów graficznych,
jakie chcieli uzyskać. Przykładowo bardzo prosta (ang. low poly – mało wielokątów)
siatka może wyglądać bardzo ładnie i udawać znacznie dokładniejszy model, jeśli
oświetlenie będziemy obliczać osobno dla każdego piksela, jaki model zajmuje na ekranie
(ang. per pixel lighting), zamiast dla każdego wierzchołka siatki (ang. per vertex
lighting).
Jednak nawet takie metody mają swoje ograniczenia, których nie da się
przeskoczyć (np. na brzegach modelu zawsze będzie widać kanciaste kształty przy mało
dokładnej siatce modelu). Dlatego równolegle z technikami i sztuczkami wymyślanymi
przez programistów twórcy sprzętu bezustannie pracują nad zwiększeniem mocy
obliczeniowej kart graficznych. Czasami poprzez bezpośrednie zwiększanie prędkości
przetwarzania danych, a czasami poprzez wymyślanie nowych sposobów wykorzystania
istniejących rozwiązań i omijanie tzw. wąskich gardeł (ang. bottleneck).
Jednym z takich rozwiązań jest sprzętowa teselacja (ang. tessellation). Głównym
wąskim gardłem współczesnych kart graficznych nie jest prędkość przetwarzania dużej
liczby wierzchołków/trójkątów, ale samo przesyłanie danych modelu z procesora
komputera do karty graficznej (ang. bandwidth). Pomysł jest taki, że żeby zamiast grafik
3D tworzył bardzo szczegółową siatkę modelu (której przesłanie z CPU do GPU zajęłoby
dużo czasu), stworzył on uproszczoną wersję modelu (o znacznie niższej liczbie
trójkątów) oraz np. displacement mapę (pojęcie to zostanie szczegółowo wyjaśnione w
dalszej części pracy). Uproszczona siatka modelu może zostać znacznie szybciej
przesłana do karty graficznej, która to, na podstawie displacement mapy lub innych
wytycznych, sama wygeneruje dodatkowe wierzchołki siatki, omijając tym samym
wąskie gardło. Okazuje się bowiem, że, paradoksalnie, przesyłanie uproszczonego
modelu i generowanie dodatkowych wierzchołków co każdą klatkę animacji jest znacznie
szybsze niż przesyłanie do karty graficznej gotowego modelu.
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
8
2.2. Cel pracy
Technika ta ma wiele zalet, które zostaną opisane w dalszej części pracy, jednak ma
też kilka wymagań, które trzeba spełnić, aby móc jej używać. Po pierwsze nie każdy
sprzęt (w tym konsole) ją obsługuje, po drugie wymaga innego, specjalnego
przygotowania modeli, więc użycie jej to poważna decyzja dla twórców aplikacji, a po
trzecie wreszcie nieumiejętne zastosowanie teselacji może znacząco pogorszyć
wydajność aplikacji nie poprawiając (znacząco) jej wyglądu.
Z tych powodów, pomimo istnienia technologii na rynku już od paru lat, niewiele
gier ją wykorzystuje (teselacja, jak i cała grafika czasu rzeczywistego, może być
oczywiście wykorzystywana w innych celach, np. do różnorakich symulacji, ale odsetek
innych zastosowań jest znacznie mniejszy niż w przypadku gier komputerowych i autor
nie posiada danych dot. jej zastosowania). Jednak wraz z pojawieniem się na rynku
konsol obsługujących teselację (PlayStation 4 i Xbox One) i zwiększaniem się udziału w
rynku kart graficznych dla komputerów domowych obsługujących tę technologię można
mieć nadzieję, że coraz więcej gier będzie używać teselacji, umożliwiając graczom
oglądanie pięknych szczegółowych scenerii i modeli.
2.2 CEL PRACY
Celem niniejszej pracy jest implementacja i prezentacja efektów zastosowania
teselacji do renderowania szczegółowego terenu. Aplikacja renderuje duży otwarty teren
(wyspę tropikalną, cały teren ma powierzchnię 1 km2) i umożliwia użytkownikowi
swobodne poruszanie się po całym jego obszarze. Wyspa jest pokryta roślinnością i
otoczona wodą. Jednak wszystkie te elementy stanowią jedynie pretekst do pokazania
możliwości teselacji i jej zastosowania w kilku różnych aspektach renderowania terenu.
Przez „teren” autor rozumie zarówno sam ląd (wyspę), jak i różne obiekty
znajdujące się na niej (rośliny, trawę, kamienie) czy wodę otaczającą wyspę. Wszystkie te
elementy bardzo mocno wykorzystują teselację, dając zarówno ładny wizualnie efekt, jak
i dobrą wydajność.
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
2.2. Cel pracy
9
Rysunek 1. Widok z kamery po uruchomieniu aplikacji.
Jedynym wyjątkiem jest trawa, która nie jest teselowana (dokładniej – ma zawsze
współczynnik teselacji równy 1). Wynika to z tego prostego powodu, że pokrycie tak
dużego obszaru modelami kępek trawy wymaga wyrenderowania dziesiątek albo nawet
setek tysięcy kopii (sic!) tego modelu w każdej klatce. Dlatego model powinien być
bardzo prosty i mieć minimalną liczbę geometrii. Jednak gdyby pokryć trawą mniejszy
obszar, można by użyć teselacji również na niej, umożliwiając np. ładną animację
pochylania się kłosów trawy na wietrze.
W następnym rozdziale pracy zostaną omówione użyte technologie i biblioteki.
Rozdział czwarty przedstawia i opisuje zagadnienia teoretyczne związane z pracą. Przede
wszystkim wyjaśnia, czym jest i jak działa teselacja, oraz przedstawia jej zalety i wady, a
także wyjaśnia znaczenie innych pojęć i technik użytych w pracy. Rozdział piąty opisuje,
jak te zagadnienia zostały zaimplementowane w pracy oraz jakie rozwiązania autor
zastosował w aplikacji. W ostatnim rozdziale dokonano podsumowania, a także wskazano
możliwości poprawy i rozwoju projektu.
Ponadto – w branży gier komputerowych wiele pojęć, które zostały wymyślone
przez anglojęzycznych twórców, zostało przyjętych do języka polskiego w oryginalnej
wersji i jest powszechnie używanych i rozumianych. Autor pracy nie uznał za celowe
podejmowania prób ich tłumaczenia i tworzenia polskich odpowiedników, które i tak nie
byłyby zrozumiałe w środowisku.
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
10
Renderowanie szczegółowego terenu z użyciem teselacji
2.2. Cel pracy
Piotr Martynowicz
3.1. Decyzje
11
3 WYMAGANIA I TECHNOLOGIE
3.1 DECYZJE
Przed rozpoczęciem pracy bardzo ważne jest określenie zakresu pracy i wymagań
oraz wybranie odpowiednich technologii i narzędzi. Dobre przygotowanie pozwoli
oszczędzić zbędnej pracy, skupić się na istocie zagadnienia i wykonać pracę efektywnie.
Cel pracy został przedstawiony w poprzednim rozdziale. Aplikacja ma renderować
przykładowy otwarty teren z modelami rozmieszczonymi na nim. Praca nie zakładała
korzystania z żadnego gotowego silnika graficznego. Zamiast tego, autor stworzył własny
silnik graficzny od zera, który potrafi wczytać modele 3D zapisane w popularnych
formatach, wraz z teksturami, załadować przygotowane specjalnie shadery, wczytać
heightmapę i wygenerować teren wyspy, i wreszcie całość wyrenderować. Autor uznał,
że celowanie w platformy Windows i komputery PC oraz laptopy będzie wystarczające
na potrzeby tej pracy.
Autor zdecydował się na język C++, środowisko programistyczne Visual Studio
2012 i bibliotekę DirectX 11.0. Do wczytywania modeli 3D użyto biblioteki Assimp, a do
wczytywania tekstur –stb_image. Program używa także plików w formacie JSON, które
są wczytywane za pomocą biblioteki SimpleJSON. Do operacji na macierzach i
wektorach wykorzystano DirectXMath. Aplikacja posiada również własny system
logujący i wyświetlający błędy i informacje diagnostyczne.
3.2 C++
Podstawowym wyborem jest kwestia języka programistycznego. Na rynku
dostępnych jest wiele opcji, jednak nie wszystkie nadają się do implementacji silnika
graficznego. Podstawowym kryterium wyboru jest szybkość działania i kontrola nad
operacjami, jakie program faktycznie wykonuje podczas działania. Tutaj numerem jeden
jest język C++, którego kod jest kompilowany do kodu maszynowego (brak maszyny
wirtualnej), w przeciwieństwie do paru innych popularnych języków, takich jak Java czy
C#. To po prostu daje szybciej wykonujący się program oraz większy determinizm
działania (np. brak włączającego się w losowych momentach Garbage Collectora). Choć
C++ jest językiem wysokopoziomowym, daje on bezpośredni dostęp do pamięci
(wskaźniki) i kontrolę nad jej użyciem (brak Garbage Collectora). W tym sensie jest on
„mniej wysokopoziomowy” niż wspomniane C# i Java, ale przy programowaniu grafiki
jest to pożądana cecha.
Innym ważnym czynnikiem jest dostępność bibliotek graficznych dla danego
języka programistycznego (bo bez nich nie da się stworzyć silnika graficznego). Wybrany
DirectX został stworzony dla języków C/C++, więc to kryterium jest spełnione. Warto
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
12
3.3. DirectX 11.0
dodać, że istnieje open-source’owa wersja biblioteki – SharpDX – dla platformy .NET. W
przypadku Javy nie jest już tak prosto. Poza tym DirectX jest dostępny jedynie na
platformy Microsoftu, więc zamiast używać Javy, można równie dobrze użyć C#.
Jeszcze jednym ważnym argumentem jest popularność języka i dostępność
dokumentacji, przykładów, tutoriali i dodatkowych bibliotek dla danego języka – czasem
sam brak odpowiedniego wsparcia technicznego może uniemożliwić rozwijanie projektu.
Język C++ jest jednym z najczęściej używanych, więc wybranie go pod tym względem
nie może być złą decyzją.
3.3 DIRECTX 11.0
Jak wspomniano wcześniej – nie da się stworzyć własnej aplikacji graficznej bez
użycia jakiejś biblioteki graficznej. [2] Dla grafiki 3D na rynku istnieją 2 dominujące
rozwiązania: DirectX i OpenGL (technicznie rzecz biorąc DirectX to kilka bibliotek do
różnych zadań, część odpowiedzialna za grafikę nazywa się Direct3D, jednak nazwa
DirectX jest często używana na określenie samej części graficznej). Ta pierwsza to
produkt firmy Microsoft i jest przeznaczona wyłącznie na platformy tej firmy (systemy
Windows oraz konsole Xbox), jej kod jest zamknięty. Ta druga jest biblioteką opensource rozwijaną pod kierunkiem Khronos Group i jest dostępna na bardzo wiele platform
(PC – wszystkie systemy operacyjne, urządzenia przenośne, konsole). Obu natomiast
można używać za darmo, obie oferują analogiczne rozwiązania, mają bardzo duże
możliwość i podobną wydajność. Dlatego wybór tutaj, o ile nie jest ograniczony jakimiś
odgórnymi wymaganiami (np. konkretna platforma, polityka firmy), jest dowolny – obie
opcje są dobre. Dlatego autor, w celu nauczenia się nowej technologii, zdecydował się na
DirectX.
Natomiast ważna tutaj jest wersja biblioteki. Teselacja bezpośrednio narzuca wersję
11.0 lub wyższą, gdyż dopiero od tej wersji DirectX wspiera tę technikę. Ta wersja
bezpośrednio pociąga za sobą wersję systemu Windows (7 lub wyższa), co wynika z
polityki Microsoftu. To również daje konkretne ograniczenia, jeśli chodzi o karty
graficzne. DirectX 11.0 jest obsługiwany przez karty Ati od serii HD 5xxx oraz przez
karty Nvidia od serii GTX 4xx. Obecnie większość (jeśli nie wszystkie) nowych
komputerów sprzedawanych w sklepach posiada sprzęt i system (o ile jest to Windows)
obsługujących teselację, jednak bardzo wielu ludzi posiada komputery kupione
wcześniej, które nie spełniają tych wymagań. To oznacza, że nie każdy może skorzystać z
teselacji, jednak nowe technologie mają to do siebie, że wymagają nowego sprzętu.
3.4 VISUAL STUDIO 2012
Wybór środowiska programistycznego (IDE) ma mniejsze znaczenie, aczkolwiek
dobre środowisko może znacznie ułatwić i przyspieszyć proces tworzenia. Visual Studio
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
3.4. Visual Studio 2012
13
jest również produktem firmy Microsoft i jest jednym z najbardziej popularnych IDE. Jest
to bardzo rozbudowane narzędzie, które posiada swój własny kompilator języka C++ (o
wysokim stopniu zgodności ze standardem), bogaty i konfigurowalny edytor z
podświetlaniem składni i wieloma narzędziami ułatwiającymi tworzenie dużych
projektów programistycznych, wiele wtyczek rozszerzających jeszcze jego możliwości
oraz zaawansowany debugger.
Co ważne, od wersji 2012 VS posiada również wbudowany graficzny debugger do
debugowania aplikacji używających DirectX. Pozwala on na debugowanie kodu
shaderów (rysunek 2), podgląd listy wywołań API wysłanych do karty graficznej,
sprawdzanie stanu i zawartości buforów (rysunki 3 i 4), tekstur i zasobów używanych
przez aplikację itd… Na chwilę obecną nie działa on idealnie (autor podczas tworzenia
pracy znalazł przypadki, w których debugger np. nie potrafił debugować shaderów albo
wyświetlał nieprawidłowe wartości), ale i tak okazał się pomocny. Jeśli w przyszłości
narzędzie zostanie poprawione i zawsze będzie działać poprawnie, to będzie nieocenione
przy tworzeniu aplikacji graficznych – różnica między pisaniem shaderów w ciemno i
zgadywaniem, co faktycznie w danym momencie robi karta graficzna, a możliwością
faktycznego sprawdzenia stanu buforów i zmiennych to niebo i ziemia.
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
14
3.4. Visual Studio 2012
Rysunek 2. Debugowanie shaderów w Visual Studio 2012.
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
3.4. Visual Studio 2012
15
Rysunek 3. Sprawdzanie zawartości buforów w Visual Studio 2012.
Rysunek 4. Graficzny podgląd buforów w Visual Studio 2012.
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
16
3.5. Assimp
3.5 ASSIMP
Na rynku dostępnych jest bardzo wiele różnych formatów zapisu modeli i animacji
3D i większość jest bardzo rozbudowana, skomplikowana i – niestety – bardzo słabo
udokumentowana. To sprawia, że własnoręczne zaimplementowanie obsługi choćby
jednego z tych formatów jest pracochłonne i trudne. Na szczęście istnieją gotowe
biblioteki umożliwiające wczytywanie modeli 3D w popularnych formatach. Jedną z nich
jest Assimp (Asset Import Library). Jest to open-source’owa biblioteka rozwijana przez
kilku programistów jako zajęcie w wolnym czasie. Niemniej jednak jest to dobra
biblioteka, która obsługuje bardzo wiele formatów i oszczędza mnóstwo pracy każdemu,
kto chce móc używać tych formatów w swoim projekcie.
Co znaczy, że Assimp umożliwia „wczytywanie” modeli zapisanych w różnych
formatach? Otóż sposób przechowywania danych o modelu, ich format w pamięci, jest
bezpośrednio zależny od konkretnej aplikacji 3D – nie ma jednego ustalonego standardu.
Dlatego przyjęte jest tutaj następujące rozwiązanie: Assimp wczytuje dane z każdego
wspieranego formatu do jednego, zaprojektowanego przez twórców biblioteki formatu
(opisanego na oficjalnej stronie), a następnie aplikacja używająca Assimpa konwertuje te
dane do swojego własnego formatu. Teoretycznie mogłaby ona używać po prostu formatu
Assimpa, ale zwykle z wielu powodów byłoby to niepraktyczne i niewygodne. Oznacza
to oczywiście dodatkowy czas pracy procesora, jaki trzeba poświęcić na dodatkową
konwersję danych w pamięci, ale jest to bardzo niska cena za możliwość łatwego
skorzystania z popularnych formatów 3D.
3.6 INNE BIBLIOTEKI
3.6.1
STB_IMAGE
Wczytywanie obrazów 2D stanowi podobne wyzwanie, co w przypadku modeli 3D
– mnogość formatów i ich wariantów przysparza niemałych problemów przy
samodzielnej implementacji. Dlatego aplikacja korzysta z gotowej biblioteki: stb_image.
Została ona stworzona przez Seana Barretta i udostępniona w formie pojedynczego pliku
źródłowego. Obsługuje podstawowe formaty graficzne i jest bardzo prosta w użyciu, co
było wymaganiem autora pracy przy wyborze biblioteki.
3.6.2
SIMPLEJSON
Opis modelu 3D to nie tylko dane jego siatki – to również informacja o materiałach
używanych przy jego renderowaniu: shadery, tekstury, sposób próbkowania itd…
Informacje te można zawrzeć w kodzie programu, ale jest to bardzo nieelastyczne i mało
praktyczne rozwiązanie. Dlatego autor zdecydował się na inne rozwiązanie – każdemu
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
3.7. Autorskie rozwiązania
17
plikowi z modelem 3D towarzyszy dodatkowy plik z danymi o materiałach. Pliki te
przechowują dane w formacie stworzonym przez autora pracy i używają notacji JSON.
Do ich wczytywania używana jest open-source’owa biblioteka SimpleJSON autorstwa
Mike’a Anchora.
3.6.3
WINAPI
Do stworzenia okna, w którym ma działać aplikacja, można użyć czystego WinAPI,
jego opakowanej w klasy wersji – MFC lub którejś z bibliotek do tworzenia interface’ów –
Qt, GTK(+), wxWidgets… Z racji braku interface’u graficznego (aplikacja wymaga
jedynie pustego okna, w którym renderuje obraz) wybrano WinAPI, gdyż daje
bezpośrednią kontrolę nad działaniem aplikacji, gwarantuje szybkość działania (inne
biblioteki mają własne narzuty) oraz nie wymaga zaprzęgania do pracy dodatkowej
biblioteki (w szczególności wielkiej – jak Qt). Zresztą każda biblioteka na platformie
Windows gdzieś w swojej implementacji i tak musi użyć WinAPI.
3.6.4
DIRECTXMATH
Nie da się programować grafiki 3D bez macierzy i wektorów. Są one podstawą
każdej aplikacji 3D i dlatego powstał szereg bibliotek do obsługi operacji na nich. Autor
zdecydował się na DirectXMath autorstwa Microsoftu. Biblioteka ta stawia na szybkość i
wykorzystanie operacji SIMD. To ostatnie wymaga specjalnego sposobu korzystania z
biblioteki (osobne typy do przechowywania danych i do wykonywania operacji
matematycznych).
3.6.5
BOOST
Program wykorzystuje również bibliotekę (a raczej ich kolekcję) boost, która
rozszerza możliwości języka C++. Jest ona udostępniona na własnej, open-source’owej,
licencji. Boost ma opinię biblioteki o wysokiej jakości i może być stawiana za wzór
nowoczesnego projektowania i programowania w C++. Zakres dziedzin
implementowanych w booście jest bardzo szeroki.
3.7 AUTORSKIE ROZWIĄZANIA
Język C++ nie posiada wbudowanych typów wektorowych – to znaczy typów
danych przechowujących więcej niż jedną liczbę (np. pary, trójki, czwórki). A ponieważ
tego typu dane są bardzo często używane w aplikacja graficznych (pozycje, wektory
prędkości, wymiary), aplikacja definiuje własne typy – Vector2/3/4 – oraz definiuje
szereg operacji na nich. Są to szablony klas (więc obsługują różne typy, również
zdefiniowane przez użytkownika) i posiadają kilka funkcji, które ułatwiają posługiwanie
się nimi: np. do tej samej liczby można odnosić się poprzez zmienna[0], zmienna.x
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
18
3.7. Autorskie rozwiązania
oraz zmienna.width albo konstruktory przyjmujące dowolne typy skalarne i wektorowe,
które obsługują odpowiednią składnię.
Aplikacja korzysta również z autorskiego zaawansowanego loggera. Tradycyjny
logger to narzędzie, które zapisuje informacje o działaniu aplikacji przez cały czas jej
wykonywania i umieszcza je w pliku (zazwyczaj oznaczonym datą i godziną). Jednak
logger użyty w pracy potrafi więcej rzeczy. Po pierwsze zapewnia obsługę błędów
poprzez asserty – sprawdza stan odpowiednich zmiennych podczas wykonywania
programu i jeśli wykryje błąd, zgłasza to użytkownikowi aplikacji poprzez stosowny
komunikat. Wiadomość o błędzie zawiera różne ważne informacje ułatwiające
zlokalizowanie błędu, a ponadto sam assert umożliwia debugowanie kodu w
odpowiednim miejscu. Po drugie logger zapisuje wszystkie informacje z działania
aplikacji i dzieli je na 4 grupy: Debug, Info, Warning i Error. Informacje są również
dzielone wg tzw. tagów. Po trzecie narzędzie to zapisuje wszystkie dane do specjalnego
pliku HTML w formie strony z tabelą z danymi oraz panelem umożliwiającymi włączanie
i wyłączanie kanałów, tagów, plików źródłowych itd… dzięki czemu użytkownik może
przeglądać i analizować logi w przeglądarce WWW w łatwy i przejrzysty sposób. Logger
ma opcje kompilowania tylko wywołań dla wybranych kanałów, dzięki czemu np. kanał
Debug nie jest w ogóle kompilowany w wersji Release, co jest bardzo ważne dla
wydajności aplikacji.
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.1. Teselacja
19
4 ZAGADNIENIA TEORETYCZNE
4.1 TESELACJA
4.1.1
ZASADA DZIAŁANIA
Jak wspomniano w przedmowie pracy, celem teselacji jest zredukowanie ilości
informacji przesyłanych z CPU do GPU, gdyż przesył ten jest wąskim gardłem sprzętu w
wypadku aplikacji graficznych. Aby to było możliwe, aplikacja wczytuje z dysku i co
klatkę przesyła do karty graficznej uproszczoną siatkę modelu (o mniejsze liczbie
wierzchołków), a karta graficzna sama generuje dodatkowe wierzchołki, uzyskując
szczegółowy model. Oczywiście „uproszczona” i „szczegółowy” to pojęcia względne,
zależne jedynie od założeń i potrzeb projektu, jednak aby zastosowanie teselacji dawało
wymierną korzyść, szczegółowa siatka powinna mieć przynajmniej kilka razy więcej
wierzchołków niż wyjściowa – uproszczona.
Ponadto, teselacja sama w sobie to jedynie podział linii, trójkątów lub quadów (ang.
quad = quadrilateral – czworokąt) na mniejsze części. Zteselowanie siatki nie wnosi
niczego nowego – model dalej wygląda dokładnie tak samo, co prezentuje rysunek 5:
Rysunek 5. Teselacja fragmentu siatki i przesunięcie wierzchołków.
Pierwszy z lewej rysunek ilustruje fragment siatki modelu przed teselacją.
Czerwone punkty to oryginalne wierzchołki modelu. Środkowy rysunek przedstawia
efekt samej teselacji – wiele nowych wierzchołków (zielone punkty), które jednak w
żaden sposób nie zmieniają kształtu modelu. Widać, że jego krawędź wciąż ma duże ostre
załamania i nie prezentuje się dobrze (zakładając, że siatka przybliża jakiś okrągły kształt.
Dopiero rysunek z prawej pokazuje, o co naprawdę chodzi w teselacji – sam podział na
mniejsze części niczego nie daje, ale odpowiednie przesunięcie nowopowstałych
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
20
4.1. Teselacja
wierzchołków pozwala uzyskać bardziej szczegółowy kształt. W tym wypadku
wierzchołki zostały ułożone wg krzywej, dzięki czemu nowa krawędź modelu jest
znacznie bardziej gładka i okrągła, i lepiej przybliża zaokrągloną krawędź rzeczywistego
obiektu.
Teselacji używa się nie tylko w celu wygładzenia krawędzi. Można ją też
wykorzystać do dodania nowych detali do powierzchni, np. nierówności dla kory drzewa,
małych kamyczków na powierzchni kamiennej ścieżki itd… Rysunek 6 ilustruje ten
przypadek. Górny obrazek przedstawia gładką powierzchnię uproszczonego modelu, a
dolny – powierzchnię szczegółowego modelu z dodanymi wypukłościami.
Rysunek 6. Teselacja płaskiej powierzchni i dodanie nierówności.
Jak widać, cała korzyść z zastosowania tej techniki, to odpowiednie przesunięcie
nowowygenerowanych wierzchołków siatki. Można je albo układać wg pewnych
matematycznych reguł (np. różnego rodzaju krzywe), wczytywać ich pozycje z wcześniej
przygotowanego pliku (displacement mapping) czy choćby tworzyć losowe przesunięcia
(ang. noise – szum), gdyż w niektórych przypadkach dowolne nierówności wyglądają
dobrze i są pożądane. Każde podejście ma swoje zalety i wady, i niektóre zostaną opisane
w dalszej części pracy.
4.1.2
ETAPY POTOKU RENDERUJĄCEGO
Technika teselacji wymaga specjalnego wsparcia ze strony sprzętu oraz biblioteki
graficznej. Dlatego DirectX w wersji 11.0 wprowadza 3 nowe etapy do pipeline’u (ang.
rendering pipeline - potok renderujący): Hull Shader, Tessellator (teselator) i Domain
Shader:
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.1. Teselacja
21
Rysunek 7. Potok renderujący w DirectX 11. [3]
Hull Shader jest programowalnym etapem (tzn. programista pisze kod - shader który steruje jego wykonaniem). Jak widać na obrazku 7, Hull Shader występuje po
Vertex Shaderze, dlatego HS na wejściu otrzymuje dane zwrócone przez VS. Hull Shader
jest podzielony na 2 części: Constant HS i Main HS. Obie części mają dostęp do
informacji o prymitywie. Constant HS jest wykonywany raz dla każdego prymitywu i
jego głównym zadaniem jest wyliczenie i zwrócenie współczynnika teselacji (ang.
Tessellation Factor), który określa stopień podziału prymitywu na mniejsze części przez
teselator. Main HS jest wykonywany raz dla każdego punktu kontrolnego (ang. Control
Point). Umożliwia on wykonanie na nich operacji w oparciu o informację o całym
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
22
4.1. Teselacja
prymitywie i powinien zwrócić na wyjściu pozycję punktu kontrolnego. Współczynniki
teselacji są kierowane do teselatora, a punkty kontrolne do Domain Shadera.
Teselator nie jest programowalnym etapem - działa automatycznie i na podstawie
dostarczonych mu danych dzieli prymitywy na mniejsze części, tworząc nowe
wierzchołki. DirectX definiuje 3 sposoby podziału: integer, even-fractional i oddfractional. integer dzieli dany element na równe części (współczynnik teselacji oznacza
właśnie liczbę tych części), co oznacza, że wartość wyrażona ułamkiem jest zaokrąglana
(w górę) do najbliższej wartości całkowitej. To w efekcie sprawia, że nawet przy płynnej
zmianie współczynnika teselacji użytkownik widzi nagłe przeskoki (nagłe pojawianie
się/znikanie wierzchołków), co nie wygląda ładnie. Problem ten rozwiązują 2 pozostałe
sposoby podziału. W ich wypadku element nie jest dzielony na równe części - jedna z
części jest mniejsza (wg wartości ułamkowej) i płynna zmiana współczynnika teselacji
powoduje płynną zmianę wyglądu siatki. even i odd wskazują, przy jakich wartościach
liczba części jest zwiększana - parzystych lub nieparzystych.
Hull Shader zwraca 2 typy współczynników teselacji: dla krawędzi i dla wnętrza
prymitywu. Te pierwsze są definiowane dla każdej krawędzi (3 dla trójkąta i 4 dla quada)
i określają, na ile części należy podzielić daną krawędź (każda może mieć inny
współczynnik). Te drugie są definiowane w zależności od prymitywu (1 dla trójkąta i 2
dla quada: poziomo i pionowo) i definiują, na ile części podzielić wnętrze prymitywu.
Należy zwrócić tu uwagę na 2 rzeczy. Po pierwsze "wnętrze" nie jest precyzyjnym
określeniem i jest to tylko wskazówka dla teselatora określająca, czy prymityw powinien
być bardziej, czy mniej dokładnie podzielony. [4] Po drugie nie dla każdych wartości
podział jest możliwy i w takim wypadku teselator dokona najbliższego możliwego
podziału.
Domain Shader również jest programowalnym etapem. Ten etap jest wykonywany
raz dla każdego wierzchołka wygenerowanego przez teselator i ma dostęp do informacji o
prymitywie. Domain Shader ma 2 główne zadania. Po pierwsze musi, na podstawie
dostarczonych mu punktów kontrolnych i współrzędnych UV, wyliczyć, poprzez
interpolację, faktyczne pozycje nowych wierzchołków siatki. Po drugie musi je
przekształcić do przestrzeni projekcji, aby DirectX mógł wyliczyć ich prawidłowe
pozycje na ekranie. To zadanie Domain Shader przejmuje od Vertex Shadera (który musi
je wykonać, jeśli teselacja nie jest aktywna). Dane wyjściowe z Domain Shadera są
przekazywane do Geometry Shadera (jeśli jest aktywny) lub Pixel Shadera.
Teselator dzieli dany element na mniejsze części, uzyskując nowe wierzchołki
siatki, jednak nie ustawia im sam pozycji. To zadanie leży po stronie programisty.
Teselator daje jedynie informację, gdzie w dzielonej figurze powinien się znaleźć nowy
wierzchołek (wg otrzymanych wytycznych), jednak tylko od programisty zależy jego
ostateczna pozycja. W Domain Shaderze programista otrzymuje pozycje wierzchołków
figury (trójkąta lub quada) i współrzędne nowego wierzchołka w obrębie tej figury
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.1. Teselacja
23
(teselacja w DirectX obsługuje jeszcze izolinie (ang. isolines), ale one wychodzą poza
zakres tej pracy).
Dla quadów współrzędne podawane są jako dwie liczby, tradycyjnie oznaczane
jako UV. Rysunek 8 obrazuje sytuację:
Rysunek 8. Obliczanie pozycji nowego wierzchołka w quadzie.
Jak widać, quad ma 4 wierzchołki (v1 - v4), a współrzędne UV biegną wzdłuż
boków figury. Pozycje wierzchołków v1 - v4 to pozycje w świecie/scenie i szukana jest
pozycja nowego wierzchołka również w świecie/scenie. Natomiast współrzędne UV
dotyczą przestrzeni samej figury. Tak naprawdę każda z tych współrzędnych mówi, w
jakiej odległości między dwoma bokami figury znajduje się szukany punkt, czyli jest to
problem interpolacji liniowej. Dlatego, aby obliczyć pozycję nowoutworzonego
wierzchołka, należy wykonać 2 kroki. Najpierw należy dokonać interpolacji liniowej na
bokach (v1, v2) i (v4, v3) i wyznaczyć pozycje punktów pomocniczych. Następnie należy
dokonać trzeciej interpolacji - pomiędzy pozycjami punktów pomocniczych - uzyskując
szukaną pozycję nowego wierzchołka.
W przypadku trójkątów taki format współrzędnych oczywiście nie ma
zastosowania, dlatego używa się współrzędnych barycentrycznych UVW:
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
24
4.1. Teselacja
Rysunek 9. Obliczanie pozycji nowego wierzchołka w trójkącie.
Powyższe metody (dla quadów i trójkątów) dotyczą wyliczania pozycji nowych
wierzchołków, jednak można przy ich pomocy również znaleźć wektory normalne czy
współrzędne tekstur wierzchołków. Przy czym te metody pozwalają jedynie znaleźć
pozycje wierzchołków w obrębie teselowanych figur, dlatego po ich wyznaczeniu należy
dokonać jakiegoś przesunięcia nowych wierzchołków (wg krzywej, displacement
mappingu itd...).
4.1.3
ZALETY I WADY
Zalety:
 Oszczędzanie pamięci i mniejsza ilość danych przesyłanych z CPU do GPU.

Możliwość wykonywania części obliczeń na uproszczonych modelach.

Automatyczny, ciągły i adaptacyjny LOD.
Wady:

Konieczność specjalnego przygotowania assetów.

Ograniczenia sprzętowe.

W zależności od użycia może pogorszyć wydajność zamiast ją poprawić.
Pierwszą i główną zaletą teselacji (a zarazem jej istotą) jest ograniczenie rozmiaru
danych modeli trzymanych w pamięci. Siatki wczytywane z plików i wysyłane do karty
graficznej są uproszczone, więc zawierają (znacznie) mniej geometrii. Jak wcześniej już
napisano, przesył danych na linii CPU – GPU jest wąskim gardłem aplikacji 3D, dlatego
zmniejszenie rozmiaru przesyłanych znacząco wpływa na poprawę wydajności aplikacji.
Ponadto uproszczone siatki modeli zajmują znacznie mniej miejsca w pamięci (zarówno
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.1. Teselacja
25
komputera, jak i karty graficznej), co nie jest tak kluczowym czynnikiem, jak wydajność,
ale też jest zaletą.
Drugim atutem tej techniki jest możliwość wykonywania niektórych obliczeń na
uproszczonych siatkach, zamiast – na szczegółowych. Operacje takie jak animacja na
kościach, morph target animation, dynamika ciał miękkich czy generalnie dowolne
operacje wykonywane na wierzchołkach modelu mogą być wykonane znacznie
mniejszym kosztem, jeśli operuje się na siatce mającej wiele razy mniej wierzchołków.
Dotyczy to zarówno obliczeń wykonywanych na CPU przed wysłaniem modelu do karty,
jak i tych wykonywanych na GPU w Vertex Shaderze (gdzie siatka jest wciąż jeszcze
uproszczona).
Trzecią zaletą teselacji jest możliwość uzyskania automatycznego, ciągłego i
adaptacyjnego poziomu szczegółowości (ang. Level of Detail – LOD). Level of Detail to
technika pozwalająca oszczędzić niepotrzebnej pracy karcie graficznej. Idea jest prosta –
modele, które są blisko kamery, powinny być bardziej szczegółowe niż te, które są daleko
od kamery, przez co są bardzo małe i słabo widoczne. Przykładowo jeśli kamera
znajdowała się bardzo blisko modelu drzewa, to powinno być widać każdą jego gałązkę i
liść, ale jeśli kamera znajdowała się bardzo daleko, to powinno być widać jedynie z
grubsza przypominający drzewo kształt. Tym bardziej, że z daleka prawdopodobnie
bardzo dużo modeli drzew mieściło się w kadrze (np. las) i wyrenderowanie wszystkich
przy maksymalnym poziomie szczegółowości zabiłoby wydajność.
Tradycyjny LOD zakładał przygotowanie przez grafika kilku wersji tego samego
modelu (kilku osobnych siatek), od najbardziej do najmniej szczegółowego i
podmienianie ich w locie przez aplikację w zależności od odległości od kamery. Tzn. jeśli
obiekt był bardzo blisko kamery – używany był model najbardziej szczegółowy. Gdy,
podczas oddalania się kamery od obiektu, odległość przekraczała pewien zdefiniowany
próg, aplikacja podmieniała siatkę modelu na następną w kolejności (uproszczoną).
Oczywiście przy ponownym zbliżeniu aplikacja wracała do bardziej szczegółowego
modelu.
Problem z tym podejściem był taki, że grafik miał więcej pracy, bo musiał
przygotować kilka wersji modelu, a ponadto przeskoki między kolejnymi wersjami
modelu były nagłe i jeśli było to widoczne podczas działania aplikacji, to dawało to zły
efekt. Tutaj teselacja daje popis swoich możliwości. Po pierwsze jest automatyczna –
grafik przygotowuje tylko jedną wersję modelu (maksymalnie uproszczoną) i np.
displacement mapy, a aplikacja sama, na bieżąco, generuje odpowiednie wersje LOD
modelu. Po drugie jest to ciągły efekt – nie ma nagłych przeskoków pomiędzy kolejnymi
wersjami LOD – manipulując współczynnikiem teselacji (który jest liczbą
zmiennoprzecinkową), można płynnie przechodzić od maksymalnie uproszczonego
modelu do maksymalnie szczegółowego. Po trzecie, wreszcie, teselacja ta jest
adaptacyjna – to znaczy, że sama się dostosowuje do sytuacji w zależności od
parametrów, np. odległości od kamery, kąta, pod jakim kamera patrzy na daną
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
26
4.1. Teselacja
powierzchnię czy stopnia różnorodności powierzchni w danym miejscu modelu. Drugi
wymieniony parametr oznacza, że jeśli dana powierzchnia jest obrócona do kamery
bokiem, to stanowi krawędź modelu na ekranie i powinna być mocniej teselowana (aby
uzyskać gładką, okrągłą linię) niż środkowe fragmenty modelu, co dobrze widać na
rysunku 10. I – tak, możliwe jest zastosowanie różnego współczynnika teselacji dla
różnych quadów/trójkątów tego samego modelu. Teselacja w zależności od stopnia
różnorodności powierzchni w danym miejscu modelu została zaimplementowana w pracy
i zostanie opisana w jej dalszej części.
Rysunek 10. Adaptacyjna teselacja w zależności od kąta patrzenia kamery. [5]
Jeśli chodzi o wady – zastosowanie tej techniki w aplikacji nie ma wad jako takich
– nie utrudnia zastosowania innych technik, niczego nie uniemożliwia i nie pogarsza.
Natomiast jej użycie powoduje problemy niezwiązane z działaniem aplikacji. Po pierwsze
teselacja wymaga odpowiedniego przygotowania assetów (siatek i tekstur). Modele trzeba
tak wykonać, aby siatki miały jak najmniej wierzchołków, ale jednocześnie dobrze się
teselowały i w efekcie dawały oryginalną, szczegółową siatkę (którą zapewne grafik
najpierw stworzył). Poza samą siatką grafik musi stworzyć specjalnie displacement mapy,
które to umożliwią. Poza tym, aby grafik mógł zobaczyć, jak faktycznie jego model
będzie wyglądał w grze, musi go za każdym razem umieścić w grze i ją uruchomić albo
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.2. Teselacja Phonga
27
programiści muszą mu przygotować specjalny edytor, który renderuje w ten sam sposób,
co gra.
Drugim dużym minusem zastosowania tej techniki są jej wymagania sprzętowe.
Ograniczenie grona potencjalnych graczy tylko do posiadaczy najnowszego i drogiego
sprzętu zwykle jest bardzo niepożądane i zalety samej teselacji nie są wystarczającym
uzasadnieniem – o ile aplikacja nie ma wysokich wymagań sprzętowych z innych
powodów (zastosowanie kilku innych wymagających technik, próba zachęcenia do gry
samą grafiką, marketingowa decyzja o tworzeniu gry na najnowszy sprzęt), to raczej nie
powinno się decydować na użycie teselacji.
Trzecią rzeczą, o której należy wspomnieć, jest fakt, że teselacja może zarówno
poprawić wydajność, jak i ją pogorszyć, w zależności od tego, jak się jej używa. Jeśli
stosuje się ją, aby móc używać prostych siatek modeli, które dzięki teselacji i tak będą
wyglądać ładnie, albo, jak w przypadku tej pracy, renderuje się duży teren i używa się
teselacji do maksymalnego zmniejszenia rozmiaru siatki – to poprawa wydajności
aplikacji będzie bardzo znacząca. Jednak jeśli będzie się używać teselacji do modeli,
które już są dosyć szczegółowe, tylko po to, aby uzyskać bardzo drobne detale, aby w ten
sposób zachwycić graczy, albo używa się jej do takich ekstrawagancji, jak realistyczne
włosy, gdzie renderowany jest każdy pojedynczy włos, to nawet przy niskim
współczynniku teselacji bardzo łatwo o zabicie wydajności nawet na mocnych kartach
graficznych. To jednak nie jest wada per se – czasem takie rzeczy są zamierzone, a chęć
zaimponowania niektórym graczom (entuzjastom) usprawiedliwia nawet najwyższe
wymagania sprzętowe i niski FPS. Poza tym, technicznie rzecz biorąc, teselacja wciąż
tutaj daje ogromną poprawę wydajności – bo wyrenderowanie tych samych rzeczy bez
niej byłoby w ogóle niemożliwe (w czasie rzeczywistym). Autor jedynie sygnalizuje to
jako potencjalny problem, bo czasem parę dodatkowych szczegółów, których nawet nie
widać, może niepotrzebnie obniżać ogólną wydajność aplikacji.
4.2 TESELACJA PHONGA
Gdy na siatce dokonano już teselacji i utworzone zostały nowe wierzchołki, należy
je, jak już wcześniej wspomniano, odpowiednio przemieścić. Jednym z zastosowań
teselacji jest wygładzanie krawędzi okrągłych obiektów w celu uzyskania lepszego
estetycznie efektu. Jednak żeby móc znaleźć odpowiednie pozycje dla nowych
wierzchołków, trzeba się kierować jakimiś wytycznymi.
Jednym z algorytmów zaokrąglania powierzchni siatki jest teselacja Phonga (nie
mylić z cieniowaniem Phonga opisanym w dalszej części pracy). Jest to prosta metoda
bazująca na wektorach normalnych oryginalnych wierzchołków trójkąta. Cel jej użycia
ilustruje rysunek 11:
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
28
4.2. Teselacja Phonga
Rysunek 11. Efekt działania teselacji Phonga. [6]
Z lewej strony widać oryginalny model, z prawej model po zastosowaniu teselacji.
Jak widać na zaznaczonych fragmentach – model uzyskał ładne, okrągłe kształty na
brzegach. Ostra, łamana linia brzegowa modelu widoczna z lewej strony jest
charakterystyczna praktycznie dla całej grafiki komputerowej (przynajmniej w grach
komputerowych) – jest ona skutkiem zbyt niskiej dokładności siatki modelu i jest
odwiecznym problemem programistów zajmujących się grami. Z jednej strony chciałoby
się użyć jak najdokładniejszej siatki, aby modele zawsze miały okrągłe kształty i dobrze
się prezentowały pod każdym kątem, ale z drugiej strony wydajność renderowania takich
modeli zawsze stanowiła barierę nie do pokonania. Efektem jest kompromis pomiędzy
jakością, a wydajnością, który jednak nie zawsze daje zadowalające rezultaty. Jak widać,
teselacja Phonga rozwiązuje ten problem, nie obniżając przy tym znacznie wydajności
aplikacji.
Rysunek 12. Zasada działania teselacji Phonga dla pojedynczego trójkąta. [6]
Rysunek 12 ilustruje główną ideę tej metody – wektory normalne (czarne linie)
wyznaczają nachylenie wierzchołków trójkąta, definiując tym samym kształt powierzchni
jego wnętrza. Intuicyjnie – jeśli normalne wychylone są na zewnątrz, to powierzchnia jest
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.3. Heightmapa
29
wypukła i nowe wierzchołki teselowanego trójkąta będą przesuwane ku górze (w tym
konkretnym przypadku), jeśli normalne skierowane są do wewnątrz, to powierzchnia jest
wklęsła i nowe wierzchołki będą przesuwane w dół.
Rysunek 13. Ilustracja algorytmu teselacji Phonga. [6]
Punkt p to pozycja wierzchołka otrzymana po teselacji sprzętowej (zawiera się w
płaszczyźnie trójkąta). Obiekty zaznaczone przerywanymi liniami to płaszczyzny styczne
wyznaczone przez wektory normalne trójkąta. u, v i w to współrzędne barycentryczne z
teselacji sprzętowej (to dzięki nim został wyznaczony punkt p). W pierwszym kroku
algorytmu należy zrzutować punkt p na każdą z 3 płaszczyzn stycznych. W drugim kroku
wykonywana jest interpolacja otrzymanych 3 punktów przy pomocy współrzędnych
barycentrycznych u, v i w. To w efekcie daje punkt p*, czyli szukaną pozycję nowego
wierzchołka siatki. Te dwa kroki wykonywane są dla każdego nowego wierzchołka siatki.
Algorytm, jak widać, jest bardzo prosty (a kod implementujący go jeszcze
prostszy), ale to jest właśnie jego wielka zaleta – ten kod jest wykonywany dla setek
tysięcy wierzchołków powstałych w wyniku teselacji modeli i im jest prostszy, tym
szybciej się wykonuje, co ma kluczowe znaczenie.
4.3 HEIGHTMAPA
Heightmapa (z angielskiego height map - mapa wysokości) to sposób zapisu terenu
jako siatki równo rozłożonych punktów, dla których określamy jedynie wysokość.
Pozostałe składowe ich pozycji, szerokość i długość, są wyliczane na podstawie ich
pozycji w tablicy i wymiarów terenu. Heightmapy, przynajmniej w grafice
komputerowej, tradycyjnie zapisuje się w plikach graficznych, np. bitmapach. Każdy
piksel takiego obrazu określa wysokość danego punktu heightmapy. Zwykle koduje się ją
powtarzając 3 razy tę samą wartość dla każdego kanału (R, G i B), otrzymując obraz w
odcieniach szarości. Rysunek 14 przedstawia przykładowe heightmapy:
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
30
4.3. Heightmapa
A
B
Rysunek 14. Przykładowe heightmapy.
Aplikacja używająca heightmapy najpierw wczytuje obraz z pliku, aby otrzymać
dwuwymiarową tablicę pikseli. Ponadto gdzieś (w kodzie, osobnym pliku lub w inny
sposób) muszą zostać zdefiniowane wymiary terenu (jednostka jest kwestią umowną).
Następnie aplikacja generuje prostokątną siatkę wierzchołków, której wymiary mogą, ale
nie muszą, odpowiadać rozmiarom obrazu. W tym kroku aplikacja ustawia szerokość i
długość dla każdego wierzchołka - pierwszy wierzchołek z brzegu ma szerokość/długość
równą 0, a ostatni - równą odpowiedniemu wymiarowi terenu. Wszystkie wierzchołki
pośrodku mają pozycje liniowo interpolowane.
W kolejnym kroku program iteruje po wszystkich wierzchołkach siatki i ustawia im
wysokości, próbkując odpowiednie piksele obrazu - dla skrajnych wierzchołków
próbkowane będą skrajne piksele, a pośrodku - znowu wg interpolacji liniowej
(oczywiście kroki te można połączyć i wykonać w jednej pętli). Po spróbkowaniu
odpowiedniego piksela odczytywany jest dowolny kanał (gdyż wszystkie mają tę samą
wartość) i ta wartość jest normalizowana do przedziału
(o ile była inna). Następnie
wartość ta jest mnożona przez wysokość terenu i zapisywana jako wysokość danego
wierzchołka. W praktyce oznacza to, że białe piksele w obrazie oznaczają najwyżej
położone punkty w terenie, a czarne - najniżej. W ten sposób dla wszystkich
wierzchołków otrzymywana jest pełna informacja o ich pozycjach. Jeśli rozdzielczość
siatki odpowiada wymiarom obrazu (ile na ile wierzchołków vs ile na ile pikseli), to
każdemu wierzchołkowi odpowiada jeden i tylko jeden piksel.
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.4. Mapa obiektów terenu
31
A
B
Rysunek 15. Tereny wygenerowane z heightmap z rysunku 14.
Na rysunku 15 widać tereny wygenerowane z heightmap z rysunku 14. Teren B
wygenerowano w 2 wersjach: z lewej strony teren ma wymiary
,
natomiast z prawej –
. To pokazuje, jaką różnicę robią inne proporcje
terenu dla tej samej heightmapy.
Oczywiście poza wyliczeniem pozycji wierzchołków w heightmapie całą siatkę
można przesunąć, obrócić i przeskalować (gdyby była taka potrzeba) - albo w ramach
generowania siatki, albo już później - przy renderowaniu na karcie graficznej. Podany
sposób wyliczenia pozycji wierzchołków również nie jest jedynym możliwym - zamiast
powielać wartość wysokości w każdym kanale można je wykorzystać do zapisania jednej,
dokładniejszej wartości (rozdzielając informację na bity wszystkich kanałów). Również
inne aspekty można ulepszać i zmieniać wg potrzeb - autor przedstawił jedynie
tradycyjne podejście.
4.4 MAPA OBIEKTÓW TERENU
„Mapa obiektów terenu” to nie jest oficjalne pojęcie – jest to coś, co autor pracy
wymyślił i zastosował w swojej pracy, aby móc łatwo rozmieszczać duże ilości obiektów
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
32
4.4. Mapa obiektów terenu
(drzew, skał itd…) w otwartym terenie. Tradycyjnie albo teren powstaje od razu razem z
obiektami, które są jego integralną częścią, albo po stworzeniu terenu grafik lub level
designer rozmieszczają ręcznie wszystkie obiekty. Ręczne ustawianie (pozycji, skali i
rotacji) oczywiście dają pełną kontrolę i możliwość stworzenia lepiej wyglądającego
terenu i układów dopasowanych do konkretnych potrzeb.
Jednak przy terenie o powierzchni 1 km2 takie podejście wymagałoby mnóstwa
pracy, a biorąc pod uwagę, że celem aplikacji jest demonstracja możliwości teselacji,
byłby to daremny trud. Ponadto zastosowane podejście umożliwia bardzo łatwą, wygodną
i szybką edycję ustawienia obiektów, co nie jest możliwe przy ręcznym ustawianiu
każdego obiektu.
Mapa obiektów zajmuje się obszarami obiektów danego typu, a nie pojedynczymi
obiektami. Pomysł polega na użyciu obrazu, w którym kolorami zamalowane są obszary
terenu, które mają być pokryte obiektami danego typu. W tym sensie technika ta stanowi
uzupełnienie heightmapy (czyli mapy wysokości) – uzupełnienie o mapę obiektów.
Rysunek 16 przedstawia mapę obiektów użytą w aplikacji:
Rysunek 16. Mapa obiektów terenu.
W tym konkretnym przypadku użyto 3 kolorów do oznaczenia 3 grup obiektów:
zielony oznacza kępki trawy, czerwony oznacza rośliny (palmy i małe roślinki), a
granatowy oznacza kamienie. Pozycje na mapie obiektów odpowiadają pozycjom na
heightmapie – w ten sposób można łatwo zdefiniować, które obszary powinny zostać
pokryte jakimi obiektami. Aplikacja wczytuje taki obraz i na całym obszarze danego
koloru tworzy obiekty danego typu (oczywiście z odpowiednimi odstępami). Obiekty
podzielone są na grupy, co znaczy, że jeden kolor może oznaczać parę różnych obiektów
(jak np. w wypadku roślin). Aplikacja w każdym przypadku losuje konkretny obiekt ze
wszystkich w grupie.
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.4. Mapa obiektów terenu
33
Odstępy między obiektami zostały zdefiniowane w kodzie aplikacji (oczywiście w
razie potrzeby można by je wczytywać z pliku). Odstępy jednocześnie definiują całkowitą
liczbę obiektów – im mniejsze odstępy, tym więcej obiektów się zmieści na tym samym
obszarze.
Ważną cechą tej mapy jest możliwość nachodzenia obszarów na siebie. Kolory
zostały wybrane nieprzypadkowo – czerwony (FF0000), zielony (00FF00) i niebieski
(0000FF) są zapisywane w osobnych kanałach, czyli na osobnych bitach – dzięki temu
nie ma kolizji przy nachodzeniu na siebie obszarów. Nachodzenie wizualnie daje efekt
mieszania kolorów – przykładowo żółty to obszar z obiektami zarówno „czerwonymi”,
jak i „zielonymi”, a biały to obszar z obiektami wszystkich 3 typów.
Wspomniano o możliwości łatwej edycji. W tym celu użyto programu
obsługującego warstwy (np. Photoshop) – każda warstwa zawiera obszar jednego typu
(jednego koloru), a odpowiednie opcje mieszania warstw (ang. blend mode) zapewniają
odpowiednie kolory w miejscach, gdzie obszary na siebie nachodzą. Dzięki takiej
konfiguracji oraz dzięki wykorzystaniu obrazu jako mapy, edycja dowolnego obszaru
obiektów to kwestia jedynie wymazania i/lub pomalowanie odpowiedniego miejsca na
mapie. Warstwy można ukrywać, dzięki czemu można skupić się tylko na wybranym
typie/wybranych typach obiektów, a ponadto możliwe jest jeszcze jedno udogodnienie –
jeśli na samym spodzie doda się dla podglądu używaną heightmapę, to widać od razu, w
których miejscach terenu dodawane są nowe obiekty. Oczywiście sposób tworzenia mapy
(która jest zwykłym obrazem) jest dowolny i leży w gestii grafika/designera,
przedstawiono jedynie propozycję, która sprawdziła się przy tworzeniu tej pracy.
Na poniższych rysunkach pokazano każdą warstwę z osobna, podgląd heightmapy
oraz całościową kompozycję, na której widać dokładnie, które obszary terenu pokryte są
przez jakie obiekty.
Rysunek 17. Osobne warstwy z obiektami: roślin, kępek trawy oraz kamieni.
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
34
4.5. Displacement mapping
Rysunek 18. Heightmapa terenu.
Rysunek 19. Mapa obiektów terenu z podglądem heightmapy.
4.5 DISPLACEMENT MAPPING
Displacement mapping (z ang. displacement – przemieszczenie, przesunięcie) to
metoda dodawania detali do modelu poprzez zmianę pozycji jego wierzchołków na
podstawie specjalnego obrazu (tzw. displacement mapy). Jest ona powiązana z
technikami normal mappingu (który zostanie wyjaśniony w dalszej części pracy), bump
mappingu i parallax mappingu, lecz w odróżnieniu od nich faktycznie modyfikuje siatkę
modelu, a nie jedynie symuluje taki efekt.
Technika ta jest w zasadzie analogiczna do heightmappingu, gdyż działa na tej
samej zasadzie – displacement mapa to też mapa wysokości (tradycyjnie zapisywana w
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.5. Displacement mapping
35
odcieniach szarości), która mówi, o ile przesunąć dany wierzchołek w górę w stosunku do
pozycji wyjściowej. Jest ona jednak stosowana do siatek modeli, które mogą mieć
dowolne kształty i orientację, dlatego zachowuje się troszkę inaczej.
Rysunek 20. Fragment siatki z wektorami normalnymi.
Rysunek 21 przedstawia fragment siatki z zaznaczonymi wektorami normalnymi.
W przypadku displacement mappingu kierunek „góra” jest wyznaczany właśnie przez te
wektory. Jak w przypadku heightmappingu, kolory pikseli mapy przeliczane są na pewną
wartość liczbową, jednak w tym wypadku są one interpretowane jako wartość
przesunięcia wzdłuż wektora normalnego danego wierzchołka. Kolejny rysunek
przedstawia ten sam fragment siatki, jednak tym razem z zaznaczonymi wektorami
przesunięć:
Rysunek 21. Fragment siatki z wektorami przesunięć wierzchołków.
Bladoczerwone punkty to nowe pozycje wierzchołków siatki po zastosowaniu
displacement mappingu. W efekcie otrzymujemy nową siatkę z dodanymi detalami:
Rysunek 22. Nowy kształt fragmentu siatki.
W tym przykładzie może słabo widać korzyść wynikającą z zastosowania tej
techniki, jednak chodziło tutaj raczej o pokazanie zasady działania metody. W praktyce
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
36
4.6. Oświetlenie
displacement mappingu można użyć do dodania kolców na plecach smoka, zrobienia z
płaskiej drogi kamiennej ścieżki z wyraźnie zaznaczonym kształtem każdego kamienia,
dodania wypukłości i kształtu do dachówek dachu itd…
Oczywiście użycie tej techniki ma sens dopiero w połączeniu z teselacją, gdyż jeśli
siatka nie ma dodanych żadnych nowych wierzchołków (a działanie odbywa się jedynie
na wierzchołkach wczytanych z pliku modelu), to równie dobrze grafik 3D mógł
przesunąć te wierzchołki w edytorze i zapisać już gotowy, poprawiony model do pliku,
oszczędzając pracy karcie graficznej podczas renderingu. W przypadku teselacji korzyść
jest oczywista – teselator dynamicznie generuje dodatkowe wierzchołki (dzieląc siatkę na
dokładniejszą), a displacement mapping nadaje pozycje nowym wierzchołkom, dodając
detali do siatki modelu. Ponadto teselacja umożliwia dodawanie szczegółów tylko w tych
miejscach i przypadkach, gdy faktycznie daje to wizualną korzyść na ekranie gracza
(adaptacyjny LOD).
4.6 OŚWIETLENIE
4.6.1
PODSTAWOWE INFORMACJE
Jak już wspomniano wcześniej, programowanie grafiki komputerowej jest sztuką
uproszczeń. Upraszcza się rzeczywiste mechanizmy, prawa, wygląd obiektów i relacje
między nimi do takiego stopnia, w którym dane przybliżenie rzeczywistości jest
zadowalające (tj. wygląda realistycznie, prawdopodobnie lub po prostu ładnie), a
jednocześnie daje się policzyć przy pomocy sprzętu komputerowego w krótkim czasie. W
tym przypadku – w tzw. „czasie rzeczywistym”, co oznacza, że końcowy obraz jest
generowany na bieżąco (nie ma rosnących opóźnień) i wygląda płynnie (najczęściej
przyjmuje się granicę 30 klatek/s).
Nie inaczej jest w przypadku obliczania oświetlenia obiektów. W świecie
rzeczywistym ludzie widzą obiekty dzięki fotonom, które są emitowane lub odbijane
przez te obiekty i docierają do ludzkiego oka. Niestety zasymulowanie tego procesu na
komputerze jest zdecydowanie zbyt złożone obliczeniowo, aby dało się wykonać w czasie
rzeczywistym. Dlatego w aplikacjach 3D często przyjmuje się uproszczony i nie do końca
odpowiadający rzeczywistości model.
Światło jest dzielone na 3 komponenty: ambient, diffuse i specular. Ambient jest
światłem, które zostało tak bardzo rozproszone (poprzez odbijanie się od obiektów), że
zdaje się docierać ze wszystkich kierunków. Oświetla ono wszystkie obiekty w zasięgu
światła, ze wszystkich stron, niezależnie od położenia światła i obiektu i jest zwykle
bardzo słabe (w końcu jest ono wielokrotnie odbite). Jest ono również najmniej złożone
obliczeniowo.
Diffuse jest „głównym” komponentem – to on najbardziej wpływa na oświetlenie
obiektów i nadaje im realistyczny wygląd. Jest to światło, które biegnie z jednego
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.6. Oświetlenie
37
kierunku (źródła światła) i po dotarciu do powierzchni obiektu jest równomiernie
odbijane we wszystkich kierunkach (jest jednakowo jasne niezależnie od pozycji
obserwatora). Naturalnie powierzchnie obiektów odwrócone od źródła światła nie są w
ogóle oświetlone.
Specular odpowiada za (najczęściej białe) odbłyski na powierzchni obiektów (np.
od słońca w tafli wody). Do jego obliczenia jest brana pod uwagę zarówno pozycja źródła
światła, jak i obserwatora (i oczywiście samego obiektu). Ponadto wykorzystywany jest
czynnik zwany shininess, który odpowiada za wielkość tych odbłysków. Generalnie
specular może być utożsamiany z matowością powierzchni – powierzchnia w pełni
matowa nie ma żadnych odbłysków, powierzchnia błyszcząca (np. szkło) je posiada.
Dlatego też w części implementacyjnej pracy dla jednych obiektów użyto komponentu
specu lar (np. dla liści palm), a dla innych nie (np. dla kory palm).
Rysunek 23. Oświetlenie, od lewej: ambient, diffuse, specular i wszystkie razem. [7]
4.6.2
UŻYTE WZORY
Te trzy komponenty razem mają symulować działanie rzeczywistych promieni
świetlnych. Dają one przekonywujący efekt i do zastosowań rozrywkowych (takich, jak
gry komputerowe) są w pełni wystarczające. Jednak na kolor obiektu w danym punkcie
wpływ ma oczywiście także materiał, z którego jest on wykonany. Każdy rzeczywisty
materiał ma wiele cech, które wpływają na jego wygląd. W aplikacjach 3D przyjmuje się
uproszczoną wersję, która posiada kolor (w danym punkcie, cała powierzchnia posiada
zwykle wiele kolorów) oraz matowość (parametr shininess). Kolor jest również (tak jak
w przypadku światła) rozbijany na komponenty: ambient, diffuse i specular. Jedna uwaga
– parametr shininess jest przypisany do materiału, a nie do światła (gdyż odbłyski
światła na danej powierzchni zależą od obiektu, a nie od światła).
W tej pracy ostateczny kolor obiektu w danym punkcie można wyrazić poniższym
wzorem (I – od intensity, czyli natężenie światła):
gdzie:
oraz
oznaczają wpływ każdego z 3 komponentów światła na
obiekt. Jak można zauważyć w kodzie shaderów dołączonym do pracy, wartości
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
38
4.6. Oświetlenie
komponentu ambitne są różne dla różnych obiektów (a np. woda nie ma go wcale).
Wynika to stąd, że celem programisty jest osiągnięcie ładnego wizualnie efektu, a nie
poprawnie fizycznego modelu (który i tak jest niemożliwy do osiągnięcia).
Istnieje jeszcze taki parametr, jak „attenuation” (z ang. osłabienie, tłumienie),
który oznacza siłę oddziaływania światła w funkcji jego odległości od oświetlanego
modelu. Jednak ma on zastosowanie tylko do świateł punktowych i reflektorowych
(wyjaśnienie niżej), a nie do kierunkowych, które są używane w pracy. Dlatego nie
pojawia się on w poniższych wzorach.
gdzie: wartość
jest dobierana empirycznie i służy osłabieniu tego rodzaju
światła (jest to światło wielokrotnie odbite). Niektóre modele oświetlenie zakładają
osobne kolory dla każdego komponentu światła (ambient, diffuse i specular), jednak w tej
pracy wystarczył uproszczony model z jednym, wspólnym kolorem.
gdzie:
oznacz wpływ komponentu specular,
komponentu diffuse światła,
jest wartością
jest wartością, z jaką obiekt odbija komponent
diffuse światła,
jest wektorem normalnym do powierzchni obiektu w danym
punkcie jest wektorem wskazującym kierunek od cieniowanego punktu do źródła
światła, a
jest funkcją zwracającą iloczyn skalarny argumentów.
gdzie:
jest tzw. pół-wektorem (ang. half-vector),
jest funkcją podnoszenia
do potęgi, a wartość
jest dobierana empirycznie i służy poprawieniu efektu. Halfvector zdefiniowany jest następująco:
gdzie:
jest wektorem wskazującym kierunek od cieniowanego punktu do
obserwatora (kamery). Half-vectora używa się w celu optymalizacji obliczeń.
„Oryginalny” wzór na komponent specular jest następujący:
gdzie:
jest wektorem wskazującym kierunek odbicia promienia światła. We
wzorze
idea jest taka, że jeśli wektory odbicia i kierunku obserwatora się pokrywają
(lub prawie), to w tym miejscu mamy odbłysk (specular). Jednak można zauważyć, że
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.6. Oświetlenie
39
jeśli wektory i się pokrywają, to wektor „pomiędzy” i musi się pokrywać z
wektorem normalnym . Na tym się opiera wzór
. Za specyfikacją GLSL wektor
obliczany jest w następujący sposób:
gdzie: wektor jest kierunkiem padania promienia światła, czyli
wg
powyższych oznaczeń. Jak widać, wyznaczenie wektora wymaga wykonania mniejszej
ilości obliczeń niż w przypadku wektora , co daje nam wspomnianą optymalizację.
Jak napisano wyżej – każde światło składa się w tym modelu z 3 komponentów:
ambient, diffuse i specular. Każdy z nich z kolei składa się z 3 składowych: R, G i B.
Podział ten nie jest poprawny fizycznie, ale jest powszechnie stosowany w informatyce i
naturalny dla kart graficznych.
4.6.3
TYPY ŚWIATEŁ
Światła mają również różne typy. Tradycyjnie w programowaniu grafiki wyróżnia
się: kierunkowe (ang. directional lights), punktowe (ang. point lights) i reflektorowe (ang.
spotlights). Pierwszy typ oznacza światła tak odległe (nieskończenie odległe), że
wszystkie ich promienie są równoległe i biegną w jednym kierunku. Klasycznym
przykładem jest światło słoneczne. Drugi typ to światła, które mają swoją pozycję w
przestrzeni i emitują promienie we wszystkich kierunkach. Trzeci typ to w zasadzie
wariant drugiego – światła te również mają swoją pozycję, ale emitują promienie tylko w
określonym zakresie kierunków – w obrębie pewnego stożka, dlatego posiadają również
parametr określający kąt rozwarcia tego stożka.
Światła kierunkowe nie mają promienia – zasięgu ich działania (przyjmuje się
promień nieskończony) ani pozycji – w każdym miejscu na scenie padają pod tym samym
kątem. Światła spotlight wymagałyby uwzględnienia w programie kątów rozwarcia
stożka.
4.6.4
METODY CIENIOWANIA PIKSELI
Na koniec tego rozdziału trzeba jeszcze wybrać metodę cieniowania obiektów.
Istnieją 3 typy: oświetlenie „per wielokąt” (ang. per polygon) – płaskie (ang. flat), „per
wierzchołek” (ang. per vertex) – Gourauda oraz „per piksel” (ang. per pixel) – Phonga.
Cieniowanie płaskie polega na obliczeniu koloru na podstawie wektora normalnego
jednego wierzchołka i zastosowanie tego koloru dla całego wielokąta. Cieniowanie
Gourauda polega na obliczeniu koloru osobno dla każdego wierzchołka i zinterpolowaniu
kolorów pomiędzy wierzchołkami. Cieniowanie Phonga natomiast polega na
zinterpolowaniu wektorów normalnych pomiędzy wierzchołkami i – dopiero wtedy –
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
40
4.7. Normal mapping
obliczeniu koloru dla każdego piksela. Na rysunku 3. dobrze widać różnice między
poszczególnymi typami cieniowania.
Rysunek 24. Oświetlenie, od lewej: flat, Gouraud i Phong. [8]
Najbardziej popularną metodą była metoda Gourauda, zaimplementowana w
większości kart graficznych. Wynikało to z faktu, że do jej zastosowania wystarczała
funkcjonalność fixed graphics pipeline. Jednak dzisiaj wszystkie nowe karty graficzne
umożliwiają pisanie własnych programów dla shaderów, dlatego metoda Phonga staje się
coraz bardziej popularna i prawdopodobnie wyprze zupełnie poprzednią metodę
cieniowania. Naturalnie w tej pracy zastosowano cieniowanie Phonga.
4.7 NORMAL MAPPING
Gdy wyjaśniono już, jak obliczane jest oświetlenie modeli w grafice komputerowej,
można przejść do opisania techniki normal mappingu (mapowania normalnych). Jak
wspomniano wcześniej, jest ona związana z displacement mappingiem. Tak naprawdę
została ona stworzona dokładnie w celu symulowania efektów displacement mappingu
bez konieczności przetwarzania znacznie większej liczby wierzchołków [9]. Jest ona
jedną z podstawowych i najstarszych technik stosowanych w grafice komputerowej i
umożliwiała ona uzyskanie bardzo efektownych wizualnie rezultatów w czasach, gdy
sprzęt komputerowy nie był w stanie przetwarzać takich ilości wierzchołków, jak dzisiaj.
Oświetlenie w grafice komputerowej ma kluczowe znaczenie (w zasadzie w
świecie rzeczywistym również) – zróżnicowane oświetlenie powierzchni obiektu w
zależności od kąta padania światła pozwala ocenić obserwatorowi, jaki kształt ma obiekt
w danym miejscu, gdzie są wgłębienia, gdzie wypukłości itd… Gdyby cały obiekt miał
jednolity kolor (a przynajmniej tę samą jasność na całej powierzchni), to dla obserwatora
wyraźna byłaby wyłącznie sylwetka obiektu i nie byłby on w stanie powiedzieć niczego o
jego kształcie.
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.8. Frustum culling
41
I na tym właśnie opiera się normal mapping. Ponieważ oświetlenie modeli 3D
zależy wyłącznie od kierunku wektora normalnego, technika ta, dla każdego punktu
powierzchni, wczytuje z normal mapy kierunek wektora w danym miejscu i podmienia
oryginalny wektor. „Dla każdego punktu powierzchni” to znaczy dla każdego piksela
zajmowanego na ekranie przez wyrenderowany obiekt – gdyż normal mapping jest
stosowany w Pixel Shaderze. Więc chociaż siatka modelu pozostaje bez zmian, nowe
wektory normalne używane do obliczenia oświetlenia sprawiają, że powierzchnia
wygląda, jakby miała nierówności.
Rysunek 25. Zasada działania normal mappingu.
Powyższy rysunek ilustruje zasadę działania normal mappingu. Podmienione
normalne symulują inna kształt powierzchni (bardziej szczegółowy) niż faktyczny. Szara
linia prezentuje symulowany kształt powierzchni.
4.8 FRUSTUM CULLING
4.8.1
BOUNDING VOLUME
Aby zdefiniować frustum culling najpierw trzeba wyjaśnić dwa pojęcia. Pierwszym
z nich jest bounding volume, czyli bryła brzegowa. Każdy model 3D zajmuje w
przestrzeni jakiś obszar. Jednym ze sposobów określania tego obszaru, jest podanie
najmniejszej bryły, która zawiera w sobie każdy wierzchołek modelu. Brył brzegowych
używa się do przybliżania kształtu modelu w operacjach takich, jak obliczanie kolizji,
fizyce 3D, occlusion czy właśnie frustum cullingu. Cechą tych brył jest łatwość
sprawdzenia, czy nachodzą one na inne bryły i obiekty. Najczęściej stosuje się
prostopadłościany (bounding box) i sfery, rzadziej używa się też walców czy kapsuł
(walec z półkulami na końcach). Wybór konkretnej bryły zależy od potrzeb – najszybciej
przeprowadza się testy na sferach (wystarczy sprawdzić, czy odległość od środka sfery do
testowanego obiektu jest większa lub równa jej promieniowi), jednak inne bryły mogą
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
42
4.8. Frustum culling
być wybrane ze względu na lepsze przybliżenie kształtu modelu. Rysunek 26 przedstawia
przykładowy model ze swoim bounding boxem:
Rysunek 26. Przykład modelu z bounding boxem.
Szczególnym przypadkiem bounding boxu jest tzw. axis aligned bounding box
(AABB). Jest to najmniejszy możliwy prostopadłościan zawierający w sobie wszystkie
wierzchołki modelu, którego boki są równoległe do osi układu współrzędnych. W
praktyce oznacza to, że wszystkie wierzchołki jednej ściany tego prostopadłościanu mają
jedną wspólną współrzędną. Np. jeśli dana ściana jest równoległa do płaszczyzny XZ
układu współrzędnych, to znaczy, że współrzędna X wszystkich wierzchołków tej ściany
ma tę samą wartość. Jest to prawdziwe stwierdzenie dla wszystkich ścian. Ta prosta
zależność pozwala nie tylko zapisać pełną informację o bounding boxie przy pomocy
zaledwie dwóch punktów (o minimalnych i maksymalnych współrzędnych), ale także
upraszcza różne algorytmy związane z zawieraniem bryły brzegowej (np. właśnie frustum
culling).
4.8.2
FRUSTUM
Kamery w grafice komputerowej mają pewien zdefiniowany obszar, który „widzą”
– wszystkie modele, które mieszczą się w tym obszarze, w procesie renderowania zostaną
wyświetlone na ekranie (w całości lub częściowo). Obszar ten nazywa się frustum i ma
kształt piramidy ze ściętym czubkiem. Dokładniej, jest to obszar ograniczony sześcioma
płaszczyznami: lewą, prawą, dolną, górną, bliską (ang. near plane) i daleką (ang. far
plane). Kąt bocznych oraz górnej i dolnej płaszczyzny zależy od pola widzenia kamery
(ang. field of view – FOV) oraz proporcji szerokości do wysokości ekranu, na jaki
kamera renderuje obraz (ang. aspect ratio). Poniższy rysunek ilustruje opisywane
zagadnienie:
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.8. Frustum culling
43
Rysunek 27. Kamera i jej frustum. [10]
4.8.3
ZASADA DZIAŁANIA
Przeciętne aplikacje i gry 3D w każdej klatce renderują dziesiątki, setki albo i
tysiące obiektów. Jednak często obiektów na scenie jest jeszcze więcej. Wyrenderowanie
każdego obiektu to konkretna praca dla karty graficznej. Dlatego nie ma sensu wysyłać
do renderowania obiektów, które w ogóle nie są widoczne w kamerze – ponieważ i tak
nie pojawią się na ekranie. Znajdywaniem obiektów „widzianych” przez kamerę zajmuje
się frustum culling (ang. culling - ubój selektywny, odstrzał).
Metoda ta dla każdego obiektu na scenie testuje, czy jego bryła brzegowa zawiera
się (w całości lub częściowo) we frustum kamery. Jeśli wynik testu jest pozytywny, to
zakłada się, że obiekt jest widoczny w kamerze i musi zostać wyrenderowany. W
przeciwnym wypadku jest pomijany. Ponieważ bryła brzegowa jest jedynie
przybliżeniem kształtu obiektu, możliwy jest wynik fałszywie pozytywny – może się
zdarzyć sytuacja, w której fragment bryły brzegowej jest widoczny w kamerze, ale cały
obiekt przybliżany tą bryłą znajduje się poza frustum. W takim przypadku obiekt jest
oczywiście niepotrzebnie renderowany, jednak jest to rzadki przypadek i nie wpływa
znacznie na skuteczność metody.
W pracy zastosowano axis aligned bounding box z uwagi na łatwość (a zatem i
szybkość) sprawdzania, czy zawiera się we frustum kamery. Wystarczy sprawdzić dla
każdego wierzchołka bounding boxu i dla każdej płaszczyzny frustum, po której stronie
płaszczyzny (zewnętrznej czy wewnętrznej) znajduje się ten wierzchołek. Jeśli choć jeden
wierzchołek znajduje się po wewnętrznej stronie wszystkich 6 płaszczyzn, to wynik testu
jest pozytywny i obiekt powinien zostać wyrenderowany.
8 wierzchołków bounding boxu i 6 płaszczyzn daje 48 testów dla każdego obiektu.
To może się wydawać dużym narzutem obliczeniowym. Jednak sprawdzenie zawierania
we frustum faktycznej siatki nawet bardzo prostych modeli wymagałoby obliczeń
większych o kilka rzędów wielkości, więc stosunkowo jest to bardzo mała liczba. Z
drugiej strony optymalizacja, jaką daje frustum culling, może być ogromna – jeśli spośród
tysięcy modeli na scenie w danej klatce widocznych jest tylko kilkadziesiąt, to metoda ta
pozwala pominąć renderowanie 90-paru % wszystkich obiektów! Oczywiście liczba ta
jest mocno zależna od konkretnej sceny, ale przy dużej liczbie obiektów na scenie zawsze
warto rozważyć zastosowanie frustum cullingu.
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
44
4.9. Instancing
4.9 INSTANCING
Innym przykładem optymalizacji pozwalającej obsłużyć dużą liczbę obiektów w
scenie jest instancing. Jest to technika, wspierana sprzętowo przez karty graficzne,
służąca do wyrenderowania wielu kopii tego samego obiektu przy użyciu jednego draw
callu. [11] Dokładniej – umożliwia ona wyrenderowanie kopii tej samej siatki przy
użyciu tego samego materiału. Poszczególne kopie natomiast mogą się różnić między
sobą różnymi parametrami, takimi jak pozycja w świecie, skala, rotacja, kolor itd…
(każda cecha obiektu, jaką można zróżnicować za pomocą kodu shadera).
Normalny draw call wymaga ustawienia siatki modelu oraz materiału – shadera i
parametrów wysyłanych do niego z kodu (przede wszystkim macierze transformacji, ale
mogą to być dowolne zmienne zdefiniowane przez programistę. Karta graficzna, na
podstawie tych danych, jest w stanie wyrenderować jedną kopię obiektu. Aby
wyrenderować kolejne, trzeba za każdym razem zmienić parametry dla shadera i
wykonać kolejny draw call.
Instancing optymalizuje ten proces poprzez utworzenie bufora, w którym zapisuje
parametry dla shadera od razu dla wszystkich kopii obiektu (np. ich pozycje), i wysłanie
go karcie graficznej. Dzięki temu w pojedynczym draw callu przekazywane są wszystkie
dane potrzebne do wyrenderowania wielu kopii obiektu – dla każdego z nich GPU
odczytuje parametry dla shadera z odpowiedniego miejsca w buforze i dalej proces
renderowania wykonuje się już normalnie.
Różnica polega więc na przesłaniu danych łącznie, zamiast na wiele razy, oraz na
wykonaniu tylko jednego „zlecenia” renderowania. W praktyce jednak oznacza to duży
skok wydajnościowy – draw calle są kosztowne obliczeniowo, a batching operacji (czyli
ich wykonywanie seriami zamiast pojedynczo) zawsze daje duże oszczędności w
programowaniu grafiki. Zysk jest tym większy oczywiście, im więcej kopii obiektów jest
renderowanych.
4.10 SKYBOX
Skybox (z ang. dosł. pudełko z niebem) to technika symulowania nieba w
otwartych terenach bardzo niskim kosztem. Każdy element widoczny na ekranie w
aplikacji 3D to model, który musiał zostać wyrenderowany (ew. jest to kolor, jakim
wypełniony zostaje back buffer przed rozpoczęcie procesu renderowania). Dlatego przy
otwartych terenach trzeba nie tylko stworzyć ląd i obiekty, wśród których porusza się
gracz, ale także zapełnić czymś przestrzeń nad nimi. O ile wypełnienie nieba jednolitym
kolorem (lub gradientem) nie jest trudne, o tyle rendering realistycznie wyglądających
chmur to temat skomplikowany i posiadający pewien narzut obliczeniowy. A ponieważ
niebo i tak stanowi jedynie tło, które jest mało ważne i się nie zmienia, potrzebna jest
jedynie metoda „zapełnienia” go sensowną treścią.
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
4.10. Skybox
45
Rozwiązaniem jest zastosowanie skyboxu. Nazwa nie jest przypadkowa – jest to
sześcian pokryty teksturą nieba, wewnątrz którego znajduje się kamera. Może on być albo
tak dużych rozmiarów, żeby cała scena się w nim zawierała, albo może być renderowany
na samym początku z wyłączonym zapisem do bufora głębokości. Rysunek 28
przedstawia przykładową teksturę skyboxu w postaci jednego pliku – skyboxy bywają też
zapisywane jako 6 osobnych plików.
Rysunek 28. Tekstura skyboxu z podpisanymi stronami.
Jak wspomniano, jest to sześcian – każda jego ściana stanowi tło dla jednego z
sześciu kierunków (podpisane na obrazku). Jeśli kamera znajduje się wewnątrz skyboxu i
patrzy na jego ściany pokryte teksturą nieba, to efekt jest taki, jakby gracz patrzył na
niebo otaczające świat, po którym się porusza. Co ważne, pozycja skyboxu względem
kamery się nigdy nie zmienia. Dzięki temu kamera nigdy nie może się zbliżyć do żadnej z
jego ścian (co zniszczyłoby iluzję nieba) ani wyjść poza jego obszar.
Jest to prosta sztuczka, która jednak świetnie spełnia swoje zadanie – efekt jest
ładny, wiarygodny, teksturami mogą być prawdziwe zdjęcia (i często są), dodając
realizmu grafice, a koszt wyrenderowania takiego nieba jest minimalny (6 quadów z
bardzo prostym shaderem). Oczywiście nie jest to tak dobry efekt, jak rendering chmur (i
słońca), poza tym prawdziwe niebo, w przeciwieństwie do skyboxa, nie jest nieruchome,
jednak prostota i wydajność tego rozwiązania sprawiła, że było ono bardzo popularnym
rozwiązaniem w starszych aplikacjach i nawet dzisiaj wciąż jest używane wszędzie tam,
gdzie potrzebny jest dobry efekt niskim kosztem.
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
46
Renderowanie szczegółowego terenu z użyciem teselacji
4.10. Skybox
Piotr Martynowicz
5.1. Obsługa aplikacji
47
5 IMPLEMENTACJA
5.1 OBSŁUGA APLIKACJI
Jak napisano we wstępie pracy, stworzona aplikacja ma charakter demonstracyjny i
przedstawia duży otwarty teren, po którym można się poruszać. Za przykład wybrano
wyspę tropikalną, pokrytą roślinnością i skałami oraz otoczoną wodą. Cały teren ma
powierzchnię 1 km2, woda sięga nawet dalej.
Po wyspie można poruszać się przy pomocy swobodnej kamery, sterowanej myszką
i klawiaturą. Sposób poruszania się kamery zapożyczono z gier FPS (First Person
Shooter) – ruch myszki obraca kamerą, a odpowiednie przyciski poruszają nią do przodu,
do tyłu i na boki (wg aktualnego wektora kierunku kamery). Dodatkowo umożliwiono
ruch do góry i w dół (znowu – wg wektora kierunku), co nie jest w FPS-ach możliwe,
gdyż tam kamera jest utożsamiona z postacią i porusza się po jakimś terenie. W tym
wypadku kamera „lata”, więc dodano ruch góra/dół, aby ułatwić poruszanie się. Obrót
kamery w poziomie (ang. yaw) jest nieograniczony (360o zakresu), natomiast wychylenie
w pionie (ang. pitch) jest ograniczone do przedziału
– to znaczy, że
kamera może być co najwyżej skierowana pionowo górę lub w dół, ale nie może wyjść
poza ten zakres (bo wtedy obserwowałaby obiekty na scenie do góry nogami). Kamera
nie może być przechylana na boki (ang. roll). Takie ograniczenia obrotu kamery również
są zapożyczone z FPS-ów. Ponadto, ponieważ teren jest naprawdę duży, dodano opcję
przyspieszenia ruchu pod dodatkowym klawiszem.
Aby móc dobrze pokazać działanie teselacji (która się dynamicznie zmienia wraz z
pozycją kamery), zaimplementowano możliwość włączenia widoku siatki terenu i modeli
– na niej doskonale widać, jak tworzone są (i usuwane) wierzchołki siatek obiektów
podczas ruchu kamery po scenie.
W celach porównawczych dodano również możliwość wyłączenia teselacji dla
obiektów (teren jest zawsze teselowany). Szybkie przełączenie modeli teselowanych na
nieteselowane pozwala zobaczyć, co tak naprawdę daje technika teselacji i jak różnią się
siatki takich modeli.
W celach benchmarkowych (pomiaru wydajności aplikacji) umożliwiono
wyłączenie synchronizacji pionowej (ang. v-sync). Dzięki temu karta graficzna renderuje
tyle klatek na sekundę, ile zdoła, co pozwala ocenić wydajność silnika graficznego oraz
porównać wyniki dla różnych widoków kamery (w zależności od liczby widocznych
obiektów, stopnia teselacji itd… karta graficzna w różnych klatkach ma bardzo różną
ilość pracy do wykonania). Aplikacja sama nie oblicza i nie wyświetla liczby
renderowanych klatek, ale istnieją zewnętrzne programy, które to umożliwiają (np.
Fraps).
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
48
5.2. Ładowanie modeli i tworzenie materiałów
Sterowanie:

ruch myszki na boki – obrót kamery w poziomie (yaw)

ruch myszki do przodu/do tyłu – obrót kamery w pionie (pitch)

W – ruch kamery do przodu

S – ruch kamery do tyłu


A – ruch kamery w lewo
D – ruch kamery w prawo

Q – ruch kamery w dół

E – ruch kamery w górę

Spacja + W/S/A/D/Q/E – przyspieszony ruch kamery

F2 – włączenie/wyłączenie synchronizacji pionowej (v-sync)

F3 – włączenie/wyłączenie trybu renderowania siatki

F4 – włączenie/wyłączenie teselacji obiektów
5.2 ŁADOWANIE MODELI I TWORZENIE MATERIAŁÓW
5.2.1
ELASTYCZNY SYSTEM ŁADOWANIA MODELI
Dobry silnik graficzny musi umożliwiać łatwe wczytywanie modeli i materiałów
oraz wygodne korzystanie z nich. Jednak w przypadku assetów 3D nie jest to niestety tak
proste, jak przy innych assetach. Natura sprzętu i pipeline’u graficznego oraz API
zarządzającego nimi, podyktowana wydajnością, tworzy niestety silne powiązania
pomiędzy siatkami, materiałami i stanami maszyny renderującej. Przykładowo, każda
siatka składa się z listy wierzchołków, które tworzą jej kształt. Niestety definicja
konkretnego wierzchołka zależy od użytego shadera. Inny shader może wymagać innej
definicji wierzchołka (chodzi tu o jego atrybuty, takie jak: pozycja, wektor normalny,
współrzędne tekstur itd…). Poza tym w pojedynczym pliku 3D może być zapisanych
wiele różnych obiektów, używających różnych materiałów (pojedyncze obiekty mogą
nawet używać wielu materiałów naraz). Zmiana w pliku 3D może pociągać za sobą
zmianę materiału/ów, a co za tym idzie – potrzebny jest inny kod aplikacji, który wczyta i
przygotuje taki model do renderowania.
Napisanie kodu obsługującego poprawnie pojedynczy przypadek (konkretne
modele, konkretne materiały) jest stosunkowo prosty. Jednak w komercyjnym silniku taki
kod jest kompletnie bezużyteczny – zmiana użytych modeli nie może wymagać zmiany
kodu i rekompilacji silnika. Dlatego potrzebne jest elastyczne rozwiązanie, które poradzi
sobie z wieloma różnymi przypadkami i zasobami, i nie będzie wymagało zmian w
przypadku zmiany assetów. Oczywiście stopień „uniwersalności” rozwiązania bardzo
mocno zależy od konkretnych potrzeb i zastosowań – nie ma sensu tracić czasu na
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
5.2. Ładowanie modeli i tworzenie materiałów
49
projektowanie i implementowanie kodu, który będzie potrafił obsłużyć przypadki, które
nigdy nie wystąpią.
Takie rozwiązanie zostało stworzone w tej pracy. Ponieważ do wyrenderowania
modeli (w uproszczeniu) potrzebne są 2 składniki – siatki oraz materiały, pełny opis
modelu wymaga 2 typów plików – siatki są zapisywane przy użyciu jednego z
popularnych formatów 3D, a dla materiałów stworzono własny format opisujący ich
parametry. Przy wyborze formatu 3D jest pewna swoboda – biblioteka Assimp, używana
w projekcie, obsługuje ich wiele, a jedynym wymaganiem dot. konkretnego formatu jest
obsługa hierarchii obiektów (relacja rodzic – dzieci). Pliki 3D umożliwiają zapis nie tylko
samych siatek, ale też materiałów, animacji, kości, kamer itd… Jednak sposób zapisu
materiałów różni się między formatami, praktycznie żaden nie przechowuje tekstur (są
one zapisywane do osobnych plików) i żaden nie zapisuje wszystkich informacji, jakich
potrzebuje zaimplementowany silnik graficzny (jak choćby przypisanie shaderów czy
sposób próbkowania (ang. sampling) tekstur). Dlatego konieczne było stworzenie
własnego formatu, który zawierałby te wszystkie informacje oraz umożliwiał łatwą
edycję.
5.2.2
PLIKI MATERIAŁÓW
Aby zaprojektować dobry format zapisu informacji, trzeba najpierw ustalić, jakie
dokładnie informacje będą zapisywane, oraz wybrać jakiś sposób zapisu danych – taki,
który jest czytelny, łatwy w ręcznej edycji (gdyż projekt nie zakładał tworzenia
specjalnego edytora do plików materiałów) oraz o dobrej strukturze.
Pliki materiałów muszą umożliwiać zdefiniowanie i przypisanie konkretnym
siatkom materiałów – to jest shaderów i ich parametrów. Dlatego są one podzielone na
sekcje – każda z nich definiuje dane konkretnego typu, z których potem można korzystać
w innych częściach pliku. Sekcja „materials”, jak sama nazwa wskazuje, definiuje
materiały i przypisuje im wszystkie potrzebne parametry oraz nadaje im nazwy. Jest to
bardzo ważne, gdyż siatki, w plikach 3D, mają przypisane materiały właśnie po nazwach
i to dzięki nim silnik graficzny wie, jakiego materiału użyć dla jakiej siatki. Sekcje te
definiują takie rzeczy, jak tekstury, shadery itd…, wszystkie identyfikowane po nazwach,
i po wczytaniu pliku są one dodawane do globalnych list zasobów. Dzięki temu w całym
programie można ich potem używać – np. raz zdefiniowanej tekstury można użyć dla
siatek wczytanych z kilku plików 3D, a zdefiniowane w pliku materiały można przypisać
siatkom wygenerowanym proceduralnie.
Liczba zasobów każdego typu może być różna (i powinno się dać ją łatwo
zmienić), a typy np. parametrów dla shadera mogą być różne. Dlatego potrzebny był
format, który posiada dobrą strukturę i jest elastyczny. Dlatego zamiast wymyślać własną
notację od zera, autor postawił na sprawdzone rozwiązanie. Jest nim JSON (JavaScript
Object Notation). Jest to format tekstowy, który umożliwia zapisywanie danych różnego
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
50
5.2. Ładowanie modeli i tworzenie materiałów
typu, tworzenie tablic o dowolnej liczbie elementów oraz organizowanie danych w
obiekty i hierarchie, a przy tym posiada minimalny narzut (w przeciwieństwie do np.
XML-a).
Poniższy listing przedstawia przykładową zawartość pliku materiałów:
{
"shaders" :
[
{
"name" : "grass",
"filename" : "grass.hlsl",
"stages" :
[
{
"stage" : "vs",
"entryPoint" : "VShader",
"shaderModel" : "vs_5_0"
},
{
"stage" : "ps",
"entryPoint" : "PShader",
"shaderModel" : "ps_5_0"
}
]
}
],
"textures" :
[
{
"name" : "grasses_0_diffuse",
"filename" : "grass_diffuse.png"
}
{
"name" : "grasses_0_displacement",
"filename" : "grass_displacement.png"
}
],
"samplerStates" :
[
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
5.2. Ładowanie modeli i tworzenie materiałów
51
{
"name" : "samplerRepeatLinear",
"wrapMode" : "repeat",
"filtering" : "mipLinear"
}
],
"materials" :
[
{
"name" : "Grass",
"shaderPasses" :
[
{
"shader" : "grass",
"constantBuffers" :
[
{
"name" : "Matrices",
"usage" : "default"
},
{
"name" : "Lights",
"usage" : "default"
}
],
"parameters" :
[
{
"type" : "texture",
"name" : "diffuseTexture",
"value" : "grasses_0_diffuse"
},
{
"type" : "texture",
"name" : "displacementMap",
"value" : "grasses_0_displacement"
},
{
"type" : "samplerState",
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
52
5.2. Ładowanie modeli i tworzenie materiałów
"name" : "samplerState",
"value" : "samplerRepeatLinear"
}
]
}
]
}
],
"meshesBuffers" :
{
"vertexBuffersUsage" : "immutable",
"indexBuffersUsage" : "immutable"
}
}
Listing 1. Przykładowy plik materiałów.
Nazwy sekcji zostały zaznaczone na czerwono. Pierwszą z nich jest „shaders”.
Zawiera ona informację o tym, z jakich plików należy wczytać shadery oraz jakie typy
(etapy) shaderów zostały w nich zdefiniowane (Vertex Shader, Pixel Shader itd…).
Każdy etap ma zdefiniowaną funkcję główną (ang. entry point), oraz Shader Model. [12]
Aktywowanie etapu potoku graficznego jest tak proste, jak dopisanie go do listy.
Przykładowo jeśli programista postanowi zrezygnować z Geometry Shadera i ograniczyć
się do Vertex Shadera i Pixel Shadera dla danego materiału, to wystarczy usunąć jego
definicję z sekcji „shaders” i silnik graficzny sam aktywuje odpowiednie etapy pipeline’u
podczas renderingu – plik z kodem źródłowym dalej może zawierać kod Geometry
Shadera (np. na wypadek, gdyby miał się przydać później). Równie prosto można
włączyć/wyłączyć teselację modelu – wystarczy dodać/usunąć etapy Hull Shader i
Domain Shader – silnik sam dostosuje typ danych wejściowych dla potoku graficznego
(wierzchołki albo punkty kontrolne) i aktywuje/zdezaktywuje etapy teselacji. Oczywiście
to wszystko pod warunkiem, że wybrane etapy i ich kod źródłowy dalej się kompilują i
tworzą poprawną całość. W tej sekcji, tak samo, jak w każdej innej (z wyjątkiem
ostatniej), można zdefiniować dowolną ilość obiektów. Warunkiem jest tylko wybranie
unikalnych nazw.
Drugą sekcją jest „textures”. Definiuje one tekstury i podaje ścieżki do plików, w
których są one zapisane.
Kolejną sekcją jest „samplerStates”. Opisuje ona obiekty, które definiują sposób
próbkowania tekstur. Chodzi o sposób wyboru odpowiedniego punktu na teksturze, wg
zadanych współrzędnych. Rysunek 29 ilustruje, jak zdefiniowane są współrzędne
tekstury:
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
5.2. Ładowanie modeli i tworzenie materiałów
53
Rysunek 29. Współrzędne UV tekstury.
Współrzędne tekstury tradycyjnie oznacza się UV. Punkt
oznacza lewy
dolny róg tekstury, a
prawy górny. To znaczy, że aby pobrać kolor tekstury na
samym środku, należy spróbkować punkt
. Elementy sekcji „samplerStates”
mają dwa parametry. Pierwszy z nich – „wrapMode” – oznacza zachowanie Sammlera
dla współrzędnych wychodzących poza zakres
. Możliwe są dwie opcje: „repeat”
oraz „clamp”. Ta pierwsza oznacza powtarzanie tekstury, czyli np. dla współrzędnych
zostanie spróbkowany ten sam punkt, co dla
- zachowanie to
zaprezentowane jest na rysunku 29. „clamp” natomiast oznacza „przycięcie”
współrzędnych do obsługiwanego zakresu, czyli np. dla współrzędnych
zostanie spróbkowany ten sam punkt, co dla
. Drugi parametr – „filtering” –
oznacza sposób obliczenia koloru, gdy współrzędne wskazują punkt pomiędzy dwoma
tekselami, czyli tzw. filtrowanie. Tutaj opcji jest wiele i zależą one od konkretnej
biblioteki graficznej i jej wersji.
Kolejna sekcja to „materials” i stanowi ona sedno tego pliku. To tutaj definiowane
są konkretne materiały i przypisywane są im parametry. Materiały mają jeden lub więcej
przebiegów (ang. passes), chociaż silnik w tym projekcie obsługuje tylko
jednoprzebiegowe materiały. Każdy przebieg ma zdefiniowany shader, constant buffery
oraz parametry. Constant buffer to sposób zbiorczego przekazywania parametrów do
karty graficznej i dla każdego z nich zdefiniowany jest parametr „usage”, czyli
zamierzony sposób użycia bufora. Chodzi o określenie, jak często dane w buforze będą
zmieniane (dzięki tej informacji DirectX może zoptymalizować ten proces). Możliwe
opcje to „default” – rzadkie zmiany, „dynamic” – częste zmiany oraz „immutable” – dane
po utworzeniu bufora nigdy nie mogą być zmienione. Ostatnia część – „parameters” –
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
54
5.3. Teren
definiuje parametry dla shadera. Każdy parametr ma podany typ, nazwę oraz wartość.
Wszystkie wartości w tej sekcji mogą używać zdefiniowanych wcześniej zasobów –
wystarczy podać ich nazwę.
Ostatnia sekcja – „meshesBuffers” – pełni inną rolę niż pozostałe. Nie definiuje
nowych zasobów, tylko sposób użycia vertex buffera i index buffera (analogicznie jak
wypadku constant bufferów w sekcji „materials”), czyli kolejno bufora wierzchołków i
bufora indeksów siatki.
5.2.3
ŁADOWANIE MODELI
Silnik graficzny ładuje modele, wczytując pliki 3D z siatkami oraz pliki
materiałów jako jeden proces. Najpierw, używając Assimpa, ładuje siatki modeli do
formatu obsługiwanego przez bibliotekę. Następnie ładuje pliki materiałów, tworząc
odpowiednie zasoby (przy okazji wczytując również pliki shaderów i tekstur). Z plików
shaderów, wykorzystując mechanizm refleksji, pobiera informacje o formacie
wierzchołków siatki oraz o constant bufferach i parametrach tychże shaderów. Mając tę
wiedzę, silnik przystępuje do konwertowania danych siatek modeli z formatu Assimpa na
własny format wykorzystywany przez silnik, tworząc siatki z wierzchołkami
posiadającymi atrybuty wymagane przez konkretne shadery oraz tworzy potrzebne
constant buffery z parametrami wymaganymi przez te shadery (konkretne wartości są
ustawiane dopiero podczas renderowania).
Proces jest automatyczny i spełnia warunek elastyczności. Jeśli przykładowo
programista postanowi dodać teksturowanie do materiału swojego modelu, to musi
jedynie zdefiniować nową teksturę (albo użyć istniejącej) w pliku materiałów i ustawić
oraz ją danemu materiałowi oraz oczywiście dopisać odpowiedni kod do shadera. To
wszystko. Silnik, po wczytaniu i utworzeniu tekstury, sam zauważy, że shader wymaga
teraz podpięcia tej tekstury oraz że wierzchołki mają nowy atrybut – współrzędne
tekstury – i uwzględni to, generując inną siatkę niż poprzednio. Takie rozwiązanie jest
bardzo wygodne w użyciu, a zmiany modeli i materiałów wymagają minimum pracy.
5.3 TEREN
5.3.1
WYKORZYSTANIE TESELACJI DO RENDEROWANIA TERENU
Jak napisano wcześniej w pracy, teren jest tworzony na podstawie heightmapy jako
tablica wierzchołków o różnych wysokościach. Jednak tradycyjnie cała siatka jest
generowana przez CPU i przekazywana do karty graficznej, i przy bardzo dużym terenie
musiałaby ona posiadać bardzo dużo wierzchołków. Przykładowo dla kwadratowego
terenu o boku długości 1 km, jeśli programista chciałby uzyskać dokładność siatki do 1 m
[13], musiałaby ona mieć
Renderowanie szczegółowego terenu z użyciem teselacji
wierzchołków, co nawet dla
Piotr Martynowicz
5.3. Teren
55
najmocniejszych kart graficznych w komputerach stacjonarnych jest wartością
niebezpiecznie bliską granicy możliwości renderowania w czasie rzeczywistym.
Tu z pomocą przychodzi teselacja. CPU przygotowuje uproszczoną siatkę o
minimalnej liczbie wierzchołków, a dopiero po przesłaniu jej do karty graficznej GPU
generuje dodatkowe wierzchołki. Oszczędność jest dwojaka – po pierwsze poprzez
przesyłanie uproszczonej siatki omijane jest wąskie gardło pomiędzy CPU a GPU, a po
drugie dzięki adaptacyjnej teselacji duże ilości wierzchołków generowane są tylko blisko
kamery, a w pozostałych obszarach siatki jest ich znacznie mniej. To znaczy, że obszar
siatki blisko kamery jest generowany z dokładnością np. do 1 m, a obszar, który znajduje
się daleko od kamery, może mieć dokładność 10 lub 20 m. W praktyce daje to nie tylko
ogromny skok wydajności – użycie teselacji sprawia, że renderowanie w czasie
rzeczywistym tak dużych terenów przy zachowaniu dokładności siatki w ogóle jest
możliwe.
W przypadku terenu renderowanego w tej pracy uproszczona siatka ma
rozdzielczość 32 x 32 quady, co daje
wierzchołków, co w porównaniu do
ok. miliona wierzchołków przy braku teselacji jest ogromną różnicą. Ponadto, przy
maksymalnym współczynniku teselacji oferowanym przez DirectX – 64 – daje to
maksymalną dokładność siatki wynoszącą
m przy boku długości 1 000 m, co jest
wartością w pełni zadowalającą. Poniższe rysunki ilustrują działanie teselacji terenu w
praktyce:
Rysunek 30. Siatka terenu renderowanego przez aplikację, widok z góry.
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
56
5.3. Teren
Rysunek 31. Efekt działania adaptacyjnej teselacji terenu i wody.
Jak widać na rysunku 31, teren (i woda) znajdujący się bliżej kamery jest mocniej
teselowany, dając większą dokładność, a teren znajdujący się dalej jest mniej teselowany,
gdyż nie potrzebuje on aż takiej dokładności. Czego niestety na statycznych obrazach nie
da się pokazać, to to, jak dynamicznie zmienia się siatka terenu podczas przemieszczania
kamery po scenie. To automatyczne dostosowywanie siatki do pozycji kamery podczas
ruchu stanowi istotę adaptacyjnej teselacji i świetnie pokazuje, co tak naprawdę dzieje się
„za kulisami” i jaką pracę wykonuje GPU.
5.3.2
AUTOMATYCZNE TEKSTUROWANIE TERENU
Inną ciekawą cechą zaimplementowaną w aplikacji jest sposób teksturowania
terenu. Tradycyjnie pokrycie siatki modelu teksturami jest zadaniem wykonywanym
ręcznie (z pomocą odpowiednich narzędzi oczywiście) przez grafika 3D. Daje to pełną
kontrolę nad ułożeniem tekstur na powierzchni modelu, ale jednocześnie zmiana kształtu
siatki może wymagać poprawienia oteksturowania.
Jednak w przypadku terenu generowanego z heightmapy można ten proces
wykonać proceduralnie (podczas generacji siatki). Autor zdecydował się na pokrycie
terenu kilkoma różnymi teksturami symbolizującymi różne elementy terenu: piasek na
plaży, trawę na polanach i łagodnych wzniesieniach oraz skały na stromych zboczach.
Współrzędne tekstur zależą wyłącznie od szerokości i długości terenu (wysokość i inne
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
5.3. Teren
57
parametry nie są brane pod uwagę), natomiast wybór konkretnej tekstury (lub kilku w
przypadku blendingu (mieszania) tekstur) dokonuje się w shaderze terenu na podstawie
cech terenu: stromizny oraz wysokości. Poniższy rysunek ilustruje opisywane
zagadnienie:
Rysunek 32. Teksturowanie terenu na podstawie jego stromizny i wysokości.
Zasady wyboru tekstury są proste. Jeśli dany fragment terenu znajduje się poniżej
pewnego ustalonego progu, to wybierany jest piasek (poniżej i tuż nad powierzchnią
wody), w przeciwnym wypadku – wybierana jest trawa (teren znajdujący się dalej od
wody). Oczywiście zastosowano też pewien obszar pośredni, gdzie obie tekstury są
blendowane wg interpolacji liniowej, aby otrzymać gładkie przejście.
Drugim warunkiem jest stromizna terenu – jeśli stopień pochylenie terenu jest
większy niż pewien ustalony próg, wybierana jest tekstura skał (strome, skaliste zbocza i
urwiska), w przeciwnym wypadku – wybierana jest jedna z poprzednich 2 tekstur –
piasek lub trawa. Stromizna jest oceniana na podstawie znormalizowanego wektora
normalnego danego wierzchołka terenu. Pod uwagę brana jest wartość Y tego wektora,
która wynosi 0 dla poziomego terenu i 1 dla pionowego (teoretycznie, gdyż teren idealnie
pionowy w heightmapie nie występuje). Rysunek 33 ilustruje problem. Oczywiście tutaj
też zastosowano obszar pośredni i blending tekstur.
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
58
5.4. Woda
Rysunek 33. Fragment terenu z zaznaczonymi wektorami normalnymi, widok od boku.
Zaletą użycia shaderów do teksturowania jest fakt, że obliczenia konieczne do
wybrania tekstury (i ew. blendowania kilku) dla każdego wierzchołka siatki są
wykonywane na GPU, które jest przystosowane do przetwarzania ogromnych ilości
danych oraz posiada sprzętowe wsparcie do operacji na teksturach. Wykonywanie ich na
CPU byłoby niepotrzebnym obciążaniem procesora komputera, który ma mnóstwo
innych zadań.
Natomiast plusem samego proceduralnego teksturowania terenu jest brak
konieczności poprawek w przypadku zmiany terenu. Jeśli zajdzie taka sytuacja,
wystarczy zedytować albo podmienić obraz z heightmapą terenu i shader automatycznie
dostosuje oteksturowanie. Zresztą całe zaimplementowane rozwiązanie dla renderowania
terenu jest w ten sposób przygotowane. Jedyne, co programista/użytkownik musi zrobić,
to przygotować obraz z heightmapą terenu oraz mapę obiektów. Aplikacja sama
automatycznie wygeneruje uproszczoną siatkę terenu oraz stworzy instancje i rozmieści
odpowiednie obiekty (roślinność, kamienie) na tym terenie, generując bufory dla
instancingu. Shadery natomiast same oteksturują teren oraz będą dynamicznie teselować
teren i obiekty podczas ruchu kamery. Takie rozwiązanie sprawia, że w przypadku
wprowadzania zmian do terenu konieczny nakład pracy jest minimalny.
5.4 WODA
Wszystkie opisane zalety teselacji użyte do renderowania terenu zostały również
wykorzystane w przypadku wody. Chociaż tutaj zastosowanie teselacji ma trochę
mniejsze znaczenie, gdyż cała siatka wody z daleka wydaje się być płaska i duża gęstość
siatki nie jest niezbędna. Co nie znaczy, że nic nie daje.
Aby woda bardziej przypominała morze otaczające tropikalną wyspę, dodano
prostą animację falowania wody. Siatka, podobnie jak w wypadku terenu, stanowi tablicę
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
5.4. Woda
59
równo rozmieszczonych wierzchołków (szerokość i długość), dla których wartość
wysokości obliczana jest ze wzoru:
position.y += height * (sin(PI * time + position.x) + sin(PI * time + position.z));
Listing 2. Sposób obliczania wysokości wody w danym punkcie.
Jak widać, kod używa funkcji sinus do otrzymania naturalnego kształtu fal. Jej
argumentami, poza oczywiście wartością π, jest czas oraz długość i szerokość samego
wierzchołka – poziom wody ma być wyższy w jednych miejscach i niższy w innych.
Użycie czasu jako argumentu, oczywiście, pozwala wprawić wodę w ruch i uzyskać
animację. „height” to parametr definiujący maksymalne wychylenie wody, czyli
wysokość fal. Ponadto należy wspomnieć, że z powyższego wzoru zostały usunięte
dodatkowe mnożniki (stałe wartości), gdyż ich celem jest jedynie dostosowanie wyglądu
wody do konkretnej sceny (są dobierane empirycznie) i jedynie zaciemniałyby wzór.
Animacja fal szczególnie ładny dobry efekt daje przy piaszczystych brzegach
wyspy, gdzie kolejne fale wpływają kawałek w głąb lądu, po czym się cofają (trochę jak
prawdziwe fale). Efekt ten przedstawia poniższy rysunek, chociaż oczywiście trudno
przedstawić to za pomocą statycznego obrazu:
Rysunek 34. Woda renderowana przez aplikację.
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
60
5.5. Obiekty
Jeśli chodzi o materiał, to woda oczywiście jest półprzeźroczysta (przy jej
renderowaniu włączony jest blending) oraz na jej powierzchni pojawiają się refleksy
świetlne. Te ostatnie są osiągnięte poprzez zastosowanie normal mappingu do
zasymulowania nierówności na powierzchni wody oraz komponent specular modelu
światła. Efekt jest estetyczny i zadowalający na potrzeby tej pracy, choć na pewno daleki
od rozwiązań stosowanych w najnowszych grach komputerowych. Jednak dobrze,
realistycznie wyglądająca woda, to trudny i złożony temat, który sam w sobie mógłby być
tematem pracy dyplomowej. Poza tym naprawdę ładna woda posiada spory narzut
wydajnościowy (zależny od ilości geometrii innych obiektów niż sama woda), który w tej
pracy byłby problemem.
5.5 OBIEKTY
Drugim polem do popisu dla teselacji (po terenie) jest rendering obiektów.
Zastosowanie tej techniki pozwala dodać bardzo drobne detale do ogólnego modelu
obiektu, czyniąc go znacznie ładniejszym i bardziej efektownym, a dzięki uzależnieniu
stopnia teselacji od odległości od kamery wydajność aplikacji pozostaje wysoka (gdyż tak
naprawdę tylko kilka najbliższych obiektów jest teselowanych). Dzięki temu możliwe jest
stworzenie dużego otwartego terenu pokrytego setkami obiektów (i renderowanie w
jednym kadrze wszystkich naraz!), gdzie użytkownik zbliżając się kamerą do każdego
obiektu, widzi mnóstwo drobnych detali i wydaje mu się, że wszystkie obiekty są tak
szczegółowo renderowane, a przy tym FPS całej aplikacji jest wysoki. Takimi właśnie
sztuczkami twórcy gier komputerowych starają się zaimponować graczom i zachęcić ich
do kupna.
Jeśli chodzi o samo „dodawanie” detali do obiektów, to w zasadzie cała teoria
została przedstawiona już w poprzednim rozdziale. Dlatego teraz na podstawie rysunków
z wyrenderowanymi obiektami zostanie omówione, w jaki sposób dany efekt został
osiągnięty.
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
5.5. Obiekty
61
A
B
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
62
5.5. Obiekty
C
D
Rysunek 35. Widok palmy z włączoną i wyłączoną teselacją, widok normalny oraz
siatki.
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
5.5. Obiekty
63
Pierwsza rzecz, jaka się rzuca w oczy, to różnica między siatkami modeli na
rysunkach C i D. Choć tego nie widać, palmom w C jest renderowana sama siatka (sic!),
jednak jest ona tak gęsta, że sprawia wrażenie, jakby model był renderowany normalnie.
Siatka w D posiada ok. pół tysiąca trójkątów, natomiast w C, dzięki teselacji – wiele
tysięcy. Taka gęstość siatki jest więcej niż wystarczająca i pozwala przedstawić nawet
najdrobniejsze detale na powierzchni obiektu.
Dla pni palm zastosowano 2 efekty. Po pierwsze teselację Phonga. Patrząc na sam
dół pnia na rysunku D, widać, że ma on podstawę w kształcie pięciokąta (cały pień ma
taki przekrój poprzeczny), przez co jest on kanciasty i źle wygląda. Ostre załamania
widać również w B wzdłuż pnia. Aby wyeliminować ten problem, zastosowano algorytm
zaokrąglający powierzchnię – właśnie teselację Phonga. Jak widać w A i C, podstawa i
cały pień ma ładne, okrągłe kształty.
Drugim efektem jest displacement mapping. Użyto go do dodania pniowi
wypustek, aby bardziej przypominał palmę. Displacement mapa wygląda tak (białe
elementy to właśnie wypustki na powierzchni pnia):
Rysunek 36. Displacement mapa dla pni palm.
Choć wypustki tutaj są tylko 3, to poprzez odpowiednie oteksturowanie modeli
palm, są one powtarzane przez całą długość pni. Efekt działania displacement mappingu
najlepiej widać na rysunkach A i B na palmie po lewej stronie.
Jeśli chodzi o liście palm, to tutaj zastosowano jedynie teselację Phonga.
Displacement mapping był niepotrzebny, gdyż liście powinny być płaskie. Efekt znowu
widać na rysunkach A i B – na tym drugim widać, że liście, mimo dosyć dokładnej siatki
modelu, mają ostre załamania i są kanciaste. Teselacja Phonga całkowicie eliminuje ten
problem, dając ładne zakrzywione kształty.
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
64
5.5. Obiekty
A
B
C
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
5.6. Niebo
65
D
Rysunek 37. Widok kamienia z włączoną i wyłączoną teselacją, widok normalny oraz
siatki.
W przypadku kamieni również widać ogromną różnicę w siatce z i bez teselacji.
Dzięki tak gęstej siatce (która nie byłaby możliwa do osiągnięcia w tradycyjny sposób
przy zachowaniu wysokiego FPS-u) można przedstawić każdą wypukłość i nierówność
skały.
Oczywiście tutaj również zastosowano teselację Phonga, aby zamienić kanciaste
kształty w ładnie zaokrąglone brzegi bryły. Nie używano jednak displacement mappingu,
gdyż nie był konieczny. Chociaż oczywiście, jakby ktoś chciał, można to zrobić, aby
kamień miał jeszcze więcej wgłębień, wypukłości i załamań. Dodano za to normal
mapping, aby za jego pomocą zasymulować nierówności na powierzchni skały – w
połączeniu z oświetleniem daje to wrażenie, jakby model był bardzo szczegółowy, i
sprawia, że realistycznie wygląda.
5.6 NIEBO
Niebo w tej pracy jest wykonane przy użyciu skyboxa. Tekstura ze zdjęciami nieba
i chmur z różnych stron rozpięta na sześcianie otaczającym całą scenę daje wrażenie,
jakby nad wyspą faktycznie unosiły się chmury, i sprawia, że całość bardziej przypomina
piękny słoneczny dzień.
W zasadzie w samym skyboxie nie ma niczego niezwykłego, natomiast warto
wspomnieć o jednej prostej optymalizacji zastosowanej tutaj. Tradycyjnie skybox posiada
4 ściany po bokach, jedną na górze oraz jedną na dole (czasem skyboxy przedstawiają
również teren znajdujący się bardzo daleko i dolna ściana pokryta jest wtedy teksturą np.
ziemi). Jednak w tym wypadku cały teren jest modelem, dlatego dolna ściana nie jest
potrzebna (więc skybox ma tylko 5 ścian). Jeśli chodzi o liczbę trójkątów (2 mniej) jest to
żadna oszczędność, natomiast różnica pojawia się w wywołaniach Pixel Shadera. Ich
liczba zależy wprost od liczby pikseli zajmowanych na ekranie przez dany trójkąt (co z
kolei zależy od ustawienia kamery). W skrajnym przypadku, gdy kamera patrzy w dół,
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
66
5.7. Użyte modele
dolna ściana skyboxa może rozciągać się nawet na całe okno aplikacji (niezależnie od
tego, czy jest zakrywana przez teren, czy nie), co w przypadku wymiarów 800 x 600
oznacza 480 000 wywołań Pixel Shadera. Usunięcie dolnej ściany daje więc w takim
wypadku dokładnie taką oszczędność.
5.7 UŻYTE MODELE
Aplikacja używa kilku modeli udostępnionych za darmo:

rośliny, autor: killst4r, źródło:
http://www.turbosquid.com/FullPreview/Index.cfm/ID/490485

kamienie, autor: Veleran, źródło:
http://www.turbosquid.com/FullPreview/Index.cfm/ID/561881
trawa – model własny, tekstura: autor: Metadsign, źródło:
http://www.turbosquid.com/FullPreview/Index.cfm/ID/662021

5.8 WYDAJNOŚĆ
Gdy omówiono już, jakie korzyści daje teselacja, można przeanalizować, jak to się
przekłada na poprawę wydajności. W zaimplementowanej aplikacji uproszczona siatka
terenu, jak już wcześniej wspomniano, ma
Maksymalny współczynnik teselacji używany dla terenu wynosi
teren był w takim stopniu teselowany, to finalna siatka miałaby
wierzchołków.
, więc gdyby cały
wierzchołków. Jednak ponieważ zastosowano adaptacyjny Level
of Detail, faktyczna siatka wygenerowana przez GPU będzie miała zmienną liczbę
wierzchołków i będzie gdzieś pomiędzy 1 tysiącem a 4 milionami (prawdopodobnie
najczęściej kilkadziesiąt – kilkaset tysięcy, chociaż ciężko oszacować tę wartość).
Pojedynczy wierzchołek ma 2 atrybuty: pozycję zapisywaną w zmiennej typu float3
oraz współrzędną tekstury zapisywaną w zmiennej typu float2. To daje
bajtów na wierzchołek. Zatem uproszczona siatka terenu zajmuje
bajtów, czyli ok. 21.3 kB. Gdyby nie używać teselacji, to trzeba by wygenerować siatkę o
maksymalnej, wcześniej wspomnianej, liczbie wierzchołków. Jej objętość w pamięci
wynosiłaby więc
bajtów, czyli ok. 80.1 MB. Natomiast,
jak wspomniano, siatka powstała w wyniku teselacji ma zmienne rozmiary, ale dzięki
niskim współczynnikom teselacji dla części siatki oddalonych od kamery, jej rozmiary
nigdy nie zbliżą się do maksymalnych. Na potrzeby obliczeń niech jej średni rozmiar
wynosi
wierzchołków. To daje
bajtów, czyli ok. 1.9
MB.
Różnice są kolosalne. Siatka uproszczona jest ok. 3 800 RAZY mniejsza niż siatka
bez użycia teselacji. Nie tylko jest to bardzo duża oszczędność pamięci (zarówno CPU,
jak i GPU), ale co najważniejsze jest to ogromna optymalizacja, jeśli chodzi o wąskie
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
5.8. Wydajność
67
gardło, jakim jest przesyłanie danych z CPU do GPU. Tak ogromna, że nawet
wygenerowanie nowych wierzchołków i wyliczenie dla nich pozycji (poprzez np.
teselację Phonga albo displacement mapping) wciąż zajmują mniej czasu. Dzięki temu
narzut związany z techniką teselacji jest z nawiązką nadrabiany przez ominięcie wąskiego
gardła i w ostatecznym rozrachunku teselacja wciąż daje duży wzrost wydajności. Tym
bardziej, że dzięki adaptacyjnemu LOD-owi generowana siatka i tak ma znacznie mniej
wierzchołków niż ta maksymalna.
Jak to się przekłada na szybkość działania aplikacji i liczbę klatek na sekundę?
Niestety konkretnych liczb autor nie jest w stanie przytoczyć, gdyż wyrenderowanie tak
dużego terenu bez teselacji, przy dokładności terenu do 0.5 m, jest niemożliwe w czasie
rzeczywistym – przynajmniej, jeśli poza terenem renderowane są również obiekty,
których w całej scenie są tysiące, a ponieważ wszystkie one są teselowane, tutaj również
występuje spora oszczędność. Sam teren przy 4 milionach wierzchołków na sprzęcie
autora (średniej klasy laptop) prawdopodobnie renderowałby się przy bardzo niskim FPSie (15? 5?). Natomiast próba wyrenderowania setek albo tysięcy obiektów przy tak
gęstych siatkach, jakie daje teselacja, skończyłaby się renderowaniem pojedynczej klatki
przez wiele sekund.
Na koniec trzeba wspomnieć o ogólnej wydajności zaimplementowanej aplikacji.
Renderuje ona teren o powierzchni 1 km2 pokrytego tysiącami obiektów. Dzięki frustum
cullingowi oraz instancingowi, bez których to w ogóle nie byłoby możliwe, wydajność
jest dobra i aplikacja chodzi płynnie. W zależności od kadru kamery, FPS wynosi od 20 –
30 do nawet 90 (Intel i5 2.4 GHz, HD Mobility Radeon 5730, 4 GB RAM). Jak na ilości
obiektów i geometrii na scenie to są w pełni zadowalające wyniki.
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
68
Renderowanie szczegółowego terenu z użyciem teselacji
5.8. Wydajność
Piotr Martynowicz
6.1. Realizacja celów
69
6 PODSUMOWANIE
6.1 REALIZACJA CELÓW
Temat pracy został zrealizowany – stworzono aplikację desktopową prezentującą
zalety teselacji poprzez renderowanie przykładowego dużego otwartego terenu pokrytego
tysiącami obiektów. Program pozwala użytkownikowi na poruszanie się kamerą po
scenie i oglądanie efektów działania teselacji. Aplikacja posiada wszystkie zaplanowane
funkcjonalności oraz, co ważne, cechuje się bardzo dobrą wydajnością. Ponadto spełnia
ona swoją funkcję demonstracyjną – pokazuje w praktyce zastosowanie teselacji do
renderowania terenu oraz obiektów i pokazuje korzyści płynące z takiego rozwiązania.
Jednak, jak to zawsze bywa w przypadku silników graficznych, zawsze można wiele
rzeczy ulepszyć i dodać nowe funkcje.
6.2 MOŻLIWE USPRAWNIENIA I PERSPEKTYWY ROZWOJU
Wydajność, jak wspomniano, jest dobra, ale oczywiście zawsze istnieje pole do
ulepszeń. Pierwsze, co przychodzi na myśl, to wykorzystane modele obiektów. Autor nie
jest grafikiem 3D i nie był w stanie sam wymodelować roślinności i skał. Modele
znalezione w internecie są dobrej jakości, ale nie były tworzone z myślą o teselacji i
mogłyby zostać zoptymalizowane w tym kierunku – mogłyby mieć mniej trójkątów
(szczególnie pnie palm), gdyż 64-krotna teselacja i tak dałaby wystarczającą gęstość
siatki. A mniej geometrii do przetworzenia, to oczywiście mniej pracy dla karty
graficznej i wyższy FPS.
Innym pomysłem na optymalizację jest użycie drzewa ósemkowego (ang. octree)
przy frustum cullingu. Pozwala ono na szybkie odrzucenie całych obszarów z obiektami
na scenie, przyspieszając proces szukania widocznych obiektów. Być może również
przejście z bounding boxów na bounding sphere’y dałoby poprawę wydajności (gdyż
testowanie sfery jest znacznie szybsze niż prostopadłościanu), ale jest to coś, co trzeba by
sprawdzić w praktyce (czasem niektóre optymalizacje okazują się wcale nie poprawiać
wydajności). Oczywiście to są tylko przykłady, optymalizacji wydajności można szukać
w bardzo wielu miejscach i możliwości jest sporo.
Poza wydajnością na pewno można by poprawić stronę graficzną renderowanej
sceny. Przede wszystkim przydałaby się porządna woda, implementująca takie efekty jak
odbijanie obiektów na jej powierzchni, refrakcja, kaustyka, może jakaś piana morska przy
brzegach wyspy itd... Jeśli chodzi o teren, to na pewno przydałoby się użycie techniki
zwanej detail mappingiem. Polega ona na użyciu specjalnej, szczegółowej tekstury np.
trawy w miejscach znajdujących się blisko kamery oraz innej, mniej szczegółowej, dla
miejsc bardziej oddalonych (oczywiście przejście między jedną a drugą jest płynne). Daje
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
70
6.2. Możliwe usprawnienia i perspektywy rozwoju
to bardzo ładne efekty i sprawia wrażenie, jakby tekstura całego terenu była taka
szczegółowa. Ponadto całej scenie bardzo by pomógł shadow mapping (technika
renderowania cieni).
Inny element, któremu przydałoby się ulepszeniem, to niebo. Zamiast skyboxa
można by zrobić prawdziwe (w sensie jako osobne obiekty) chmury i słońce. Można
nawet pójść dalej i zrobić tzw. cykl dnia i nocy (zmiana kolorów nieba i terenu/obiektów
na podstawie godziny panującej w świecie gry oraz rotacja słońca i księżyca) czy
zaimplementować różne warunki pogodowe (słońce, deszcz, śnieg, burzę, mgłę).
Możliwości jest bardzo wiele.
Scenie nie zaszkodziłoby też ożywienie jej – animowane, poruszające się po wyspie
zwierzęta, latające nad nią ptaki czy ryby pływające w przybrzeżnych wodach. Do tego
animacje kołysania się palm i trawy na wietrze i/lub dymiący wulkan.
Ponadto w większości współczesnych gier często stosuje się też różne efekty postprocess: bloom (rozmycie w najjaśniejszych miejscach obrazu symulujące podobny efekt
obserwowany w świecie rzeczywistym), blur lub motion blur (rozmycie obiektów w
ruchu), Screen Space Ambient Occlusion (SSAO), Depth of Field (DoF) i wiele innych.
Przy czym to są wszystko raczej smaczki, mające na celu zrobienie wrażenia niż
dodanie wartości merytorycznej. Jeśli chodzi o samo użycie teselacji, to tutaj raczej
niewiele można poprawić – można oczywiście zastosować ją do jeszcze jakichś innych
obiektów (których teraz w scenie nie ma), ale raczej nie wprowadzi to już niczego
naprawdę nowego w temacie. Natomiast rozwiązania przedstawione i zaimplementowane
w tej pracy z pewnością można by zastosować w komercyjnych grach i programach,
poprawiając ich wydajność i jakość grafiki.
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
6.2. Możliwe usprawnienia i perspektywy rozwoju
71
7 PRZYPISY
Zakładając użycie tego samego materiału dla wszystkich trójkątów modelu. Ilość
pracy wykonywanej przez kartę graficzną zależy od wielu czynników, ale w
prostym przypadku dla pojedynczej siatki – 2 razy więcej trójkątów to 2 razy więcej
pracy. ↑
[2] Można oczywiście stworzyć własną bibliotekę przekształcającą dane modeli 3D na
obraz na ekranie, ale jest to zadanie bardzo niskopoziomowe, trudne i wymagające
dużego nakładu pracy (a osiągnięta wydajność prawdopodobnie i tak będzie daleka
od tej oferowanej przez istniejące rozwiązania). ↑
[3] Źródło: MSDN,
http://msdn.microsoft.com/enus/library/windows/desktop/ff476882%28v=vs.85%29.aspx ↑
[4] Oczywiście teselator jest maszyną, która działa wg ściśle ustalonych reguł, jednak
konkretny algorytm podziału prymitywów wg zadanych współczynników teselacji
nie jest podany nigdzie w dokumentacji. Dlatego przedstawiona została ogólna
zasada, którą powinien kierować się programista. ↑
[5] Autor: Bay Raitt, źródło:, Tariq S., NVIDIA, GDC09: D3D11 Tessellation,
http://developer.download.nvidia.com/presentations/2009/GDC/GDC09_D3D11Tes
sellation.pdf ↑
[6] Źródło: Boubekeur T., Alexa M., Phong Tessellation,
http://perso.telecomparistech.fr/~boubek/papers/PhongTessellation/PhongTessellation.pdf ↑ ↑ ↑
[7] Źródło: Wikipedia, http://en.wikipedia.org/wiki/Phong_shading ↑
[8] Źródło: Strona Paula Heckberta, http://www.cs.cmu.edu/~ph/nyit ↑
[9] W zasadzie normal mapping ma ogólnie na celu symulowanie efektów oświetlenia
siatek o znacznie większej liczbie wierzchołków niż faktyczna – to, czy grafik
komputerowy wymodelował szczegółową siatkę, czy szczegóły są dodawane w
locie poprzez displacement mapping, nie ma znaczenia. ↑
[10] Źródło: Lighthouse3d.com,
http://www.lighthouse3d.com/tutorials/view-frustum-culling/ ↑
[11] Draw call to pojedyncze „zlecenie” wyrenderowania obiektu/ów dla karty
graficznej (wszystkie inne wywołania API jedynie przygotowują wszystko do
renderowania). Liczba Draw calli ma duże znaczenie, jeśli chodzi o wydajność – im
większa ich liczba, tym gorsza wydajność. Nawet wyrenderowanie tej samej liczby
obiektów przy pomocy mniejszej liczby draw calli daje poprawę wydajności. ↑
[12] Shader Model to zestaw funkcji i możliwości shaderów obsługiwanych przez
konkretne karty graficzne zdefiniowany przez DirectX. W chwili pisania pracy
najwyższą wersją było 5.0. ↑
[1]
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji
72
6.2. Możliwe usprawnienia i perspektywy rozwoju
[13] Przez dokładność siatki autor ma tu na myśli jej gęstość – przykładowo dokładność
do 1 m oznacza, że kolejne wierzchołki w danym rzędzie znajdują się w odległości
1 m od siebie. ↑
Renderowanie szczegółowego terenu z użyciem teselacji
Piotr Martynowicz
6.2. Możliwe usprawnienia i perspektywy rozwoju
73
8 BIBLIOGRAFIA
1. [Boost] Dokumentacja biblioteki boost, http://www.boost.org/doc
2. [Boubekeur08] Boubekeur T., Alexa M., Phong Tessellation,
http://perso.telecomparistech.fr/~boubek/papers/PhongTessellation/PhongTessellation.pdf
3. [MSDN] Microsoft Developer Network,
http://msdn.microsoft.com/en-us/library/ms123401.aspx
4. [Sawicki07] Sawicki A., Precompiled Headers w Visual C++,
http://www.asawicki.info/productions/artykuly/PrecompiledHeaders.php5
5. [Story10] Story J., Cebenoyan C., Tessellation Performance,
http://developer.download.nvidia.com/presentations/2010/gdc/Tessellation_Performa
nce.pdf
6. [Valdetaro10] Valdetaro A., Nunes G., Raposo A., Feijó B., Understanding Shader
Model 5.0 with DirectX 11,
http://www.sbgames.org/papers/sbgames10/computing/tuto_comp1.pdf
7. [Wikipedia] Wikipedia, http://en.wikipedia.org, http://pl.wikipedia.org
Piotr Martynowicz
Renderowanie szczegółowego terenu z użyciem teselacji

Podobne dokumenty