Witamy w kolejnym ekscytującym tutorialu! Twórcą kodu źródłowego do tego tutorialu jest Ben Humphrey i bazuje na GL Framework z lekcji 1. Lecz teraz powinieneś już być ekspertem OpenGL i przenoszenie kodu do twojego własnego powinno być błyskawiczne.
Ten tutorial nauczy Cię jak zrobić dobrze wyglądający teren z height mapy. Pewnie nie masz pojęcia czym jest height mapa. Spróbuję Ci to wytłumaczyć w prosty sposób. Height mapa jest po prostu... wyparciem, wzniesieniem z powierzchni. Niektórzy z Was pewnie teraz pytają sami siebie "o czym ten gość gada?!"... po polsku height mapa prezentuje wysokie i niskie punkty naszego widoku. Do Ciebie należy decyzja, który odcień prezentuje wysokie punkty, a który niskie. Warto także zapamiętać, że height mapy wcale nie muszą powstawać z obrazków, mogą być utworzone z różnych typów danych. Na przykład możesz użyć strumienia audio i za pomocą height mapy stworzyć jego wizualną prezentację. Wciąż jesteś skołowany? Czytaj dalej, wszystko nabierze sensu w trakcie czytania.
Zaczniemy od zdefiniowania kilku ważnych zmiennych. MAP_SIZE jest rozmiarem naszej mapy. W tym tutorialu mapa ma wymiary 1024x1024. STEP_SIZE jest rozmiarem każdego kwadratu jakiego użyjemy do rysowania widoku. Zmniejszając tą wartość sprawiamy, że widok staje się bardziej wygładzony. Trzeba pamiętać, że im mniejszy rozmiar tym więcej operacji program będzie musiał wykonać (należy wybrać między jakością, a szybkością) szczególnie, gdy używamy dużych height map. HEIGHT_RATIO złuży do skalowania widoku przez oś Y, niskie wartości powodują, że wzniesienia są spłaszczone. Wyższe wartości robią wzniesienia bardziej wyraziste (wyostrzone).
Dalej w kodzie zobaczysz bRender. Jeżeli bRender jest ustawione na TRUE (standardowo), to będziemy rysować pełne wielokąty. Jeżeli będzie FALSE to będą rysowane tylko linie łączące.
Tutaj zrobimy tablicę bajtów (g_HeightMap[]) do przetrzymywania danych naszej height mapy. Plik .RAW przechowuje wartości od 0 do 255, możemy użyć tych wartości jako wysokości, gdzie 255 będzie najwyższym punktem, a 0 najniższym. Tworzymy także zmienną scaleValue do skalowania całej sceny. Daje to możliwości przybliżania i oddalania widoku.
Funkcja ReSizeGLScene() jest taka sama jak w lekcji pierwszej, tylko dalszy dystans został zmieniony z 100.0f do 500.0f.
Następna część kodu wczytuje plik .RAW. Niezbyt skomplikowane ! Otwieramy plik w trybie read binary. Następnie upewniamy się, że plik istnieje i został otwarty. Jeżeli wystąpił jakikolwiek błąd to wyświetlamy stosowny komunikat.
Jeżeli nie było problemów z otwarciem pliku to idziemy dalej. Teraz wczytamy dane. Wykorzystamy fukcję fread(). pHeightMap jest przechowalnią dla naszych danych (jest wskaźnikiem na tablicę g_Heightmap). wczytywanie zaczynamy od pierwszego bitu, kończąc na maksymalnym - nSize (rozmiar to wysokość * szerokość). Na końcu pFile jest wskaźnikiem do struktury naszego pliku.
Po wczytywaniu sprawdzamy czy wystąpiły błędy. Wynik przechowujemy w zmiennej result i ją sprawdzamy, jeżeli błąd miał miejsce to informujemy o tym użytkownika. Ostatnią rzeczą jaką robimy to zamknięcie pliku za pomocą fclose(pFile).
Kod inicjalizujący jest całkiem prosty. Ustawiamy kolor tła na czarny, włączamy depth testing, wygładzanie wielokątów (ang. polygon smoothing) itd. Po wykonaniu tego wszystkiego ładujemy nasz plik .RAW, aby to zrobić podajemy ścieżkę do naszego pliku ("Data/Terrain.raw"), jego rozmiary (MAP_SIZE * MAP_SIZE), i tablice danych (g_HeightMap) dp funkcji LoadRawFile(). Plik .RAW zostanie wczytany i jego dane będą zapisane w tablicy (g_HeightMap).
Zawsze, gdy mamy do czynienia z tablicy chcemy mieć pewność, że nie wykroczymy poza nie. Aby mieć pewność, że to się nie zdaży używamy % (tłum. reszta z dzielenia). % powstrzyma wartości x, y przez przekraczaniem MAX_SIZE - 1.
Upewaniamy się, że nasze dane są w porządku, jeśli nie, zwracamy 0.
Inaczej zwracamy wartości x, y w naszej height mapie. Teraz powinieneś wiedzieć, że musimy pomnożyć y przez MAP_SIZE aby poruszać się wśród danych. Więcej o tym poniżej!
Musimy traktować pojedynczą tablicę jak tablicę 2D. Możemy użyć równania indeks = (x + (y * arrayWidth) ). To zakłada, że reprezentujemy to jako: pHeightMap[x][y], inaczej to jest przeciwieńtwo, czyli: (y + (x * arrayWidth) ).
Teraz mamy poprawny indeks i zwrócimy wysokość (dane pod x, y w naszej tablicy).
Ustalamy tutaj kolor punktu bazując na jego wysokości. Żeby zrobić to ciemniejsze zacząłem od -0.15f. Wartość otzrymujemy dzieląc wysokość przez 256.0f. Jeżeli nie ma danych to funckja się kończy bez ustalenia koloru. Jeśli wszystko jest ok to ustawiamy odcień niebieskiego używając glColor3f(0.0f, fColor, 0.0f), jeżeli interesują Cię inne kolory niż niebieski to pozmieniaj miejsce zmiennej fColor.
To jest kod, który aktualnie rysuje nasz teren. Zmienne x, y będą użyte w pętli do przejścia przez dane mapy i będą użyte do rysowania kwadratów.
Jak zwykle sprawdzamy poprawność danych, jeżeli jest błąd do kończymy nie robiąc nic.
Sprawdzamy zmienną bRender, jeżeli ma wartość TRUE to rysujemy całe wielokąty, jeżeli ma wartość FALSE to rysujemy linie.
Następnie będziemy rysować teren z height mapy. Aby to zrobić po prostu przejdziemy przez naszą tablicę danych, będziemy wyciągać wysokości aby nanieść nasze punkty. Gdybyśmy mogli zobaczyć to zdarzenie to najpierw zostałyby narysowane kolumny (Y), a następnie wiersze. Zauważ, że mamy STEP_SIZE. To określa jak jest sprecyzowana nasza height mapa. Im większa wartość tym teren jest bardziej "blokowy". Gdy jest mniejsza wartość to teren jest bardziej wygładzony. Jeśli ustawimy STEP_SIZE = 1 to będzie tworzył wierzchołek dla każdego piksela height mapy. Wybrałem 16 jako przyzwoity rozmiar. Każda za niska wartość będzie przykładem szaleństwa i wszystko będzie powolne. Oczywiśćie możesz zwiększyć wartość, gdy dodasz oświetlenie. Wtedy oświetlenie przysłoni klocowate kształty. Zamiast oświetlenia daliśmy kolor skojarzony z każdym wielokątem, aby uprościć lekcję. Im wyżej jest wielokąt tym jaśniejszy jest kolor.
Po skończonym rysowaniu ustawiamy kolor tła na jasny biały z ustawionym kanałem alpha na 1.0f. Jeżeli były jakiekolwiek inne obiekty na ekranie to nie chcemy ich mieć na niebiesko :D
Dla tych z Was, którzy nie używali gluLookAt(), co to robi ? pozycja kamery, widoku, i wektor widza. Tutaj ustawiamy kamerę w zaciemnionym obszarze, aby widok na nasz teren był wyraźny. W celu unikania używania tak wysokich numerów podzielimy wierzchołki terenu przez stałą scale jak poniżej za pomocą glScalef().
Wartościami gluLookAt() są w kolejności: pierwsze numery prezentują, gdzie kamera się znajduje. Więc pierwsze trzy wartości przenoszą naszą kamerę 212 jednostek na osi X, 60 jednostek na osi Y oraz 194 jednostki na osi Z ze środka. Następne trzy wartości prezentują, gdzie kamera ma się patrzeć. W tej lekcji, zauważysz podczas oglądania dema, że patrzymy lekko na lewo. Patrzymy także na dół, na nasz teren. 186 z 212 dają nam widok w lewo natomiast 55 jest mniejsze od 60 stąd mamy wrażenie patrzenia w dół pod lekkim kątem. Wartość 171 wyznacza jak daleko znajdujemy się od obiektu. Ostatnie trzy wartości mówią jakie kierunki nas interesują. W naszych górach interesuje nas oś Y i ustawiamy ją na 1, reszte ustawiamy na 0.
gluLookAt może wydawać się bardzo straszne, gdy używasz tego pierwszy raz. Pomimo przeczytania różnych wyjaśnień wciąż
możesz być zdezorientowany. Najelpszą moją radą jest to abyś po porstu eksperymentował z różnymi wartościami. Na
przykład możesz zmienić wysokość kamery na 120, wtedy zobaczysz teren bardziej od góry, ponieważ punkt na, który
patrzysz wciąż ma wartość 55.
Nie wiem czy to pomoże, ale podam jeden z moich życiowych przykładów :). Powiedzmy, że masz wysokość 6 stóp i trochę (tłum. 1 stopa angielska ~ 30,479 cm, czyli, że masz około 182,874 cm wzrostu + trochę) i załóżmy, że twoje oczy są na wysokości 6 stóp (twoje oczy reprezentują kamerę - 6 stóp reprezentuje 6 jednostek na osi Y). Teraz stoisz naprzeciwko muru, który ma 2 stopy wysokości (2 jednostki na osi Y), więc jak się patrzysz na mur to patrzysz się w DÓŁ i widzisz górę muru. Jeśli mur miałby 8 stóp wysokości to patrzyłbyś w górę i nie widziałbyś góry muru. Widok się zmienia w zależności czy patrzysz w górę czy w dół(czy jesteś wyższy czy niższy od obiektu, na który patrzysz). Mam nadzieję, że to zaczyna nabierać sensu dla Ciebie.
Po skończonym rysowaniu ustawiamy kolor tła na jasny biały z ustawionym kanałem alpha na 1.0f. Jeżeli były jakiekolwiek inne obiekty na ekranie to nie chcemy ich mieć na niebiesko :D
Teraz skalujemy nasz teren, łatwiej się będzie oglądać i nie będzie za duży. Możemy zmienić wartość scaleValue za pomocą strzałek GóRA/Dół na klawiaturze. Zauważysz, że mnożymy Y scaleValue przez HEIGHT_RATIO. Dzięki temu teren wydaje się wyższy i jest bardziej wyrazisty.
Jeśli przekazaliśmy dane g_HeightMap funkcji RenderHeightMap() to narysuje teren w kwadratach. Jeżeli zmiaerzasz wykorzystać tą funkcję to dobrym pomysłem będzie dodanie wartośći X, Y do rysowania albo po prostu użyć operacji na macierzach OpenGL (glTranslatef(), glRotate() itd.) aby umieścić teren dokładnie tam gdzie chcesz.
Funkcja KillGLWindow() jest taka sama jak w lekcji 1.
Funkcja CreateGLWindow() jest taka sama jak w lekcji 1.
Jedyną zmianą w WndProc() jest dodanie WM_BUTTONDOWN. To sprawdza czy lewy przycisk myszy został naciśnięty. Jeśli był, to sposób rysowania został zmieniony z polygon mode na line mode.
Nie ma większych zmian w tej części kodu. Jedyną zmianą jest zmiana nazwy okna. Wszystko jest tak samo aż do momentu sprawdzania klawiszy.
Kod poniżej pozwala kierować wartością scaleValue za pomocą strzałek. Naciskając strzałkę w górę - wartość rośnie, a strzałkę w dół - wartość maleje i widok się oddala.
WSzystko na temat tworzenia pięknego widoku z height mapy. Mam nadzieję, że doceniacie pracę Ben-a. Jak zawsze, jeśli znaleźliście błędy w tekście lub kodzie to skontaktujcie się ze mną a ja postaram się to naprawić.
Więc rozumiesz jak kod działa, pobaw się nim trochę. Jedną z rzeczy, które możesz zrobić jest dodanie malutkiej piłki toczącej się wszerz powierzchni. Znasz wysokość każdej części terenu więc dodanie piłki nie powinno być problemem. Inną rzeczą jaką możesz zrobić jest zrobienie ręcznie height mapy. Zrób przewijany widok, dodaj kolory do reprezentowania ośnieżonych szczytów, wody itd. dodaj tekstury, użyj efektu plazmy do stworzenia stale zmiennego widoku. Możliwości są nieskończone :)
Mam nadzieję, że lekcja Ci się podobała, możesz odwiedzić stronę Ben'a http://www.GameTutorials.com