W tym tutorialu nauczę Cię jak używać list wyświetlania (ang. display lists). One nie tylko przyspieszą Twój kod, ale także go skrócą.
Na przykład. Powiedzmy, że tworzysz grę z asteroidami. Każdy poziom zaczyna się z przynajmniej dwoma asteroidami. Więc siadasz z kartką papieru i próbujesz wymyślić jak zrobić asteroid w 3D. Kiedy już wszystko wymyśliłeś, budujesz go w OpenGL używająć wielokątów albo czworokątów. Powiedzmy, że asteroid jest oktogonalny (8 boków). Jeżeli jesteś bystry, to stworzysz pętlę i narysujesz asteroid w pętli. W ten sposób będziesz miał 18 a może nawet więcej linii kodu tworzących asteroid. Tworzenie asteroidu za każdym razem kiedy ma być wyświetlony jest uciąliwe dla Twojego systemu. Jak zaczniesz tworzyć bardziej skomplikowane obiekty, to dowiesz się co mam na myśli.
Rozwiązanie? Listy wyświetlania!!! Używając ich, tworzysz obiekt tylko raz. Możesz na niego nałożyć teksturę, pokolorować go, cokolwiek chcesz. Listę wyświetlania trzeba nazwać. Jako, że jest to asteroid, listę wyświetlania nazwiemy 'asteroid'. Teraz kiedykolwiek będę chciał narysować oteksteruwany/pokolorowany asteroid na ekranie, będę musiał tylko wywyłać glCallList(asteroid). Wcześniej utworzony asteroid pojawi się na ekranie. Ponieważ asteroid został zbudowany jako lista wyświetlania, to OpenGL nie musi się ponownie zastanawiać jak go zbudować. To bardzo odciąża procesor i przyspiesza Twój program.
Jesteś gotowy na naukę? Program nazwiemy Q-Bert Display List demo. Efektem końcowym będzie 15 sześcianów. Każdy z nich będzie zrobiony z wieczka/klapki i pudełka. Wieczko będzie oddzielną listą wyświetlania, więc możemy nadać mu ciemniejszy kolor. Pudełko jest sześcianem bez górnej ściany :)
Ten kod bazuje na lekcji 6.. Przepiszę większość kodu programu, żeby zmiany były lepiej widoczne.
Ustawiamy zmienne. Najpierw ustawiamy "magazyn" dla tekstur. Następnie tworzymy dwie nowe zmienne na dwie listy wyświetlania. Te zmienne będą występowały jako wskaźniki na miejsce przechowywania list wyświetlania w pamięci ram.
Następnie mamy 2 zmienne, xloop i yloop, które są używane do określania pozycji sześcianów na ekranie oraz 2 zmienne, xrot i yroot, które są używane do obracania sześcianu w układzie współrzędnych.
Teraz tworzymy dwie tablice z kolorami. Pierwsza, boxcol, przechowuje wartości składowych dla kolorów: czerwonego, pomarańczowego, żółtego, zielonego i niebieskiego. Każda z trzech wartości w nawiasach klamrowych określa składowe koloru - odpowiednio czerwoną, zieloną i niebieską.
Druga tablica z kolorami przechowuje ciemny czerwony, ciemny pomarańczowy, ciemny żółty, ciemny zielony i ciemny niebieski. Ciemne kolory użyjemy do narysowania wieczek naszych pudełek.
Teraz budujemy listę wyświetlania. Jeśli się przyjrzysz, to zauważysz, że kod budujący pudełko jest w pierwszej liście wyświetlania, a kod budujący wieczko w drugiej. Postaram się wyjaśnić tę część kodu nieco bardziej szczegółowo.
Zaszynamy od powiedzenie OpenGL, że chcemy dwie listy. glGenLists(2) tworzy miejsce na dwie listy i zwraca wskaźnik na pierwszą. 'box' będzie przechowywało położenie pierwszej listy. Jeżeli odpalimy listę 'box', to zostanie narysowana pierwsza lista.
Teraz zbudujemy naszą pierwszą listę. Wszystko co musimy zrobić, to powiedzieć OpenGL gdzie będzie nasza lista przechowywana i jaki rodzaj listy sobie życzymy.
glNewList() załatwi sprawę. Na pewno zauważyłeś 'box' jako pierwszy argument. Mówi on, żeby OpenGL przechowywał listę w miejscu, na które wskazuje 'box'. Drugi parametr, GL_COMPILE mówi, że chcemy zbudować listę i przechowywać ją w pamięci, co pozwoli na nietworzenie obiekty za każdym razem, kiedy chcemy go narysować.
Użycie GL_COMPILE jest podobne do programowania. Kiedy piszesz program, to musisz skompilować go za każdym razem kiedy chcesz go uruchomić. Jeżeli jest już skompilowany to exeka, wystarczy go odpalić i działa. Bez potrzeby ponownej kompilacji. Kiedy OpenGL skompiluje listę wyświetlania, ta jest gotowa do użycia i nie ma potrzeby znów jej kompilować. I tu właśnie jest profit z korzystania z list wyświetlania.
Następna partia kodu rysuje pudełko bez wieczka. Nie pojawi się ono na ekranie. Będzie jedynie przechowywane w na liście wyświetlania.
Między glNewList() i glEndList() możesz używać jakich komend chcesz. Możesz zmieniać klory, tekstury, itp. Jedyne czego nie możesz umieścić to kod, który zmieniałby listę w locie. Kiedy lista zostaje skompilowana, to NIE można jej zmienić.
Dodając linię glColor3ub(rand()%255,rand()%255,rand()%255) do tego blok, mógłbyś sądzić, że przy każdym rysowaniu kolor obiektu się zmieni. Niestety, lista została raz utworzona i kolor ustawiony poraz pierwszy nie ulegnie zmianie.
Jeżeli chcesz zmienić kolor, musisz to zrobić zanim lista zostanie wyświetlona na ekran. Więcej powiem później.
Przez glEndList() mówisz OpenGL, że skończyliśmy rysowanie. Wszystko co jest między glNewList() i glEndList jest częścią listy wyświetlania, wszystko przed i po nich - nie.
Czas na drugą listę. Żeby znaleźć miejsce przechowywania listy w pamięci, bierzemy poprzednią listę i dodajemy 1.
Kiedy już wiemy gdzie będzie nasza lista, możemy ją zbudować. Robimy to analogicznie do przypadku pierwszej listy.
Poniższy kod rysuje wieczko. To zwykły kwadrat narysowany na planie Z.
Ponownie mówimy OpenGL, że skończyliśmy budowanie używając glEndList(). To jest to!. Zakończyliśmy tworzenie dwóch list wyśweitlania.
Kod do tworzenie tekstur jest taki sam jak w poprzednich lekcjach. Teraz chcemy mieć teksturę do nałożenia na wszystkie ściany sześcianu. Zdecydowałem się użyć mipmappingu, żeby tekstura wyglądała gładko. Nie nawidzę pikselozy! Tekstura do załadowania jest w pliku 'cube.bmp'. Znajdź LoadBMP() i zmień odpowiednią linię tak, aby wyglądała jak ta poniżej.
Kod do zmiany rozmiaru nie uległ zmianie. Patrz lekcja 6..
Kod do inicjalizacji ma tylko drobne zmiany. Dodałem linijkę BuildList(). Wykona ona skok to sekcji kodu budujądej listy wyświetlania. Zauważ, że BuildList() jest po LoadGLTextures(). Zapamiętaj tę kolejność! Najpierw budujemy teksturę, a potem listy wyświetlania, które będą z niej korzystały.
Następne linie włączają szybkie i jednocześnie brzydkie oświetlenia. Light0 jest predefiniowane na większości kart, więc nie musimy się martwić o jego ustawianie. Jeżeli oświetlenie nie działa na Twojej karcie (widzisz ciemność) to je wyłącz.
Ostatnia linia GL_COLOR_MATERIAL, pozwala nam dodać kolor do tekstury. Jeżeli nie włączymy mapowania kolorów, to zmiana koloru (glColor3f(r,g,b)) nie da żadnego efektu. Pamiętaj, aby to włączyć.
Poniższa linia ustawia korektę perspektywy tak, aby ta ładnie wyglądała. Potem zwracamy TRUE, ponieważ inicjalizacja zakończyła się sukcesem.
A teraz rysowanie. Jak zwykle mam bzika na punkcie matematyki. Tym razem nie ma trygonometrii, ale wciąż wygląda nieco dziwnie :) Jak zwykle zaczynamy od wyczyszczenia ekranu i bufora głębi.
Podpinamy teksturę do sześcianu. Mogłem dodać tę linię do listy wyświetlania, ale nie zrobiłem tego. Dzięki temu mogę zmieniać teksturę gdzie tylko chcę.
A teraz coś zabawnego - pętelki :D Pierwsza pętla ustala pozycję na osi Y (góra/dół). Chcemy mieć 5 rzędów, więc robimy pętlę od 1 do mniej niż 6 (czyli 5).
Kolejna pętla. Ustawia sześciany na osi x (lewo/prawo). Liczba sześcianów zależy od tego, w którym jesteśmy rzędzie. Jeżeli jesteśmy na górze, to xloop będzie między 0 i 1 (tylko jeden sześcian). W kolejnych rzędach będzie o jeden sześcian więcej.
Resetujemy macierz widoku.
Następna linie przesuwa nas w odpowiednie miejsce na ekranie. Wygląda na skomplikowane, ale takie wcale nie jest.
Oto co się dzieje na osi X: przesuwamy się w prawo 1.4 jednostki, więc piramida jest wyśrodkowana. Mnożymy xloop przez 2.8 i dodajemy 1.4. (mnożymy przez 2.8, dzięki czemu sześciany nie nachodzą na siebie. 2.8 jest szerokością sześcianu, kiedy obrócimy go o 45 stopni). Ok. Odejmujemy yloop*1.4. To przesuwa sześciany w lewo w zależności od rzędu, w którym jesteśmy. Gdybyśmy tego nie zrobili to piramida była by wyrównana do lewej krawędzi (i nie wyglądałaby jak piramida :/ ).
A na osi Y? Odejmujemy yloop od 6 - w przeciwnym razie piramida powstałaby do góry nogami. Mnożymy wynik przez 2.4. W przeciwnym razie sześciany nachodziłyby na siebie. Odejmujemy 7, więc piramida zaczyna się na dole i jest budowana do góry.
W końcu, na osi Z przesuwamy się "w ekran" 20 jednostek. W ten sposób piramida ładnie pasuje do ekranu.
Teraz obracamy wokół osi x. Przechylamy sześcian w przód o 45 stopni i odejmujemy 2*yloop. Perspektywa pochyla sześciany automatycznie, więc odejmuję, aby zrekompensować przechył. Może nie najlepsze rozwiązanie, ale działa :)
Na koniec dodajemy xrot. Daje nam to kontrolę nad kątem za pomocą klawiatury. (życzę dobrej zabawy).
Jak już skończyliśmy z obrotami na osi x, robimy obród o 45 stopni na wokół osi y i dodajemy yrot, żeby mieć kontrolę nad obrotem za pomocą klawiatury.
Teraz wybieramy kolor pudełka. Zauważ, że używamy glColor3fv(). Ta funkcja ładuje trzy składowe koloru za jednym razem. 3fv oznacza 3 wartości zmiennoprzecinkowe (ang. floating point), v oznacza wskaźnik na tablice. Kolor, który wybieramy to yloop-1, więc każdy rząd będzie miał inny kolor. Spróbuj użyć xloop-1.
Kiedy ustawiliśmy kolor, czas na rysowanie. Zamiast pisać cały kod rusyjący, wystarczy, że wywołamy listę wyświetlania. Robimy to przez glCallList(box). box mówi OpenGL, że ma narysować pudełko (bez wieczka).
Wybieramy kolor wieczka zanim przystąpimy do rysowania. Kolor uzależniamy od numeru rzędu - (yloop-1).
I rysujemy. Banalne!
Pozostałe zmiany zaszły w WinMain(). Kod został dodany zaraz po linijce z SwapBuffers(hDC). Kod sprawdza czy zostały naciśnięte strzałki i odpowiednio porusza sześcianami.
Jak w poprzednich lekcjach, upewnijmy się, że napis na belce tytułowej programu jest poprawny.
Teraz powinieneś już dobrze rozumieć jak działają listy wyświetlania, jak je stworzyć i jak je wyświetlić na ekran. Listy wyświetlania są świetne. Nie tylko ze względu na proste tworzenie złożonych projektów, ale także ze względu na przyspieszenie, króre dają, a które jest wymagane do osiągnięcia wysokich FPSów.
Mam nadzieję, że tutorial Ci się podobał. Jeżeli masz jakieś pytania, albo coś nie jest jasne, napisz do mnie maila.