OGRE/Teren, niebo i mgła

Główny obiekt i tworzenie menadżera sceny

edytuj

Podobnie jak w poprzednim rozdziale oprzemy się na zalążku programu OGRE, który należy wkleić w pierwszy utworzony plik .cpp.

Korzeń

edytuj

W tym przykładzie zajmiemy się renderowaniem terenu w Ogre. Aby to zrobić, potrzebujemy ustawić menadżera sceny na TerrainSceneManager. Odszukaj linie:

     /** createScene jest funkcją czysto wirtualną w ExampleApplication,

i wstaw przed nią następujący kod:

     // Tworzenie Menadżera Sceny
     void chooseSceneManager(void)
     {
 #if OGRE_VERSION < OGRE_CHANGE1
        mSceneMgr = mRoot->getSceneManager(ST_EXTERIOR_CLOSE);
 #else
        mSceneMgr = mRoot->createSceneManager(ST_EXTERIOR_CLOSE, "TerrainSceneManager");
 #endif
     }

Uwaga! Tworzenie Menadżera Sceny zostało zmienione w wersji 1.2 (Dagon) w stosunku do poprzednich. Stąd wykorzystujemy dyrektywy prekompilacji umożliwiające wybór odpowiedniej wersji kodu do wersji systemu.(z forum)

Obiekt Root (mRoot jest instancją klasy Root) jest rdzeniem ("core") Ogrowych obiektów. Tutaj możesz zobaczyć diagram UML zależności miedzy obiektami. W tym kawałku kodu mówimy korzeniowi, że potrzebujemy menadżera sceny typu ST_EXTERIOR_CLOSE. Korzeń wykorzystuje SceneManagerEnumerator, aby odnaleźć odpowiedni menadżer sceny i potem go zwraca.

Po tym, jak aplikcja zostanie skonfigurowana nie będziesz już musiał używać obiektu Root.

Tworzenie menadżera sceny

edytuj

Można utworzyć menadżera sceny bezpośrednio wywołując new SceneManager, bynajmniej nie trzeba używać metody getSceneManager ani createSceneManager z obiektu Root. Jednoczesnie można mieć wiele menadżerów scen o różnych kształtach i innych jednostkach. Można też zmieniać menadżera podczas odnawiania viewportu lub wyświetlać wiele menadżerów używając wielu vieportów.

Dlaczego zatem używamy getSceneManager/createSceneManager zamiast tworzyć go normalnie? System wtyczek w Ogre daje nam duży poziom elastyczności podczas pracy z menadżerami scen. Zaś w "normalnym" przypadku mamy dostępnych tylko kilka rodzajów menadżerów scen. ExampleApplication ma domyślnego menadżera sceny, który jest ST_GENERIC. Może się wydawać, że jest to klasa bazowa, dopóki nie zacznie się głębiej sięgać w konfigurację pluginów. Gdy używamy pluginu OctreeSceneManager, ten rejestruje siebie, i nadpisuje klasę bazową SceneManager. OctreeSceneManager używa systemu zrywania elementów, które nie są aktualnie na scenie. W rezultacie działa szybciej niż wbudowany, regularny menadżer sceny. Jeśli zaś usunąłeś OctreeSceneManager z pliku plugins.cfg i żądasz ST_GENERIC w getSceneManager, prawdopodobnie używasz wtedy prostego menadżera sceny.

Żądanie ST_GENERIC to prosty i łatwy sposób tworzenia menadżera, ale może powodować parę problemów. Bez względu na okoliczności nie gwarantuje, że dostaniesz takiego menadżera sceny, którego chciałeś, ponieważ niektóre pluginy próbują nadpisać ten sam typ ST_*. Raczej jednak nie występują tego typu problemy. Niemniej, jeśli potrzebujesz zaawansowanego, konkretnego menadżera sceny, powinieneś utworzyć go samemu. A jeśli potrzebujesz standardowych rzeczy możesz po prostu używać dostępnych menadżerów. Przy okazji pamiętaj że SceneManagerEnumerator nie jest czymś w rodzaju fabryki, dlatego nigdy nie powinieneś bezpośrednio usuwać zwróconego menadżera sceny.

Dodawanie terenu do sceny

edytuj
 
Teren jaki uda nam się stworzyć przy pomocy Ogre

Zajmiemy się teraz tworzeniem terenu. Bazowa klasa SceneMenager określa metodę setWorld, której używają klasy pochodne w celu utworzenie sceny. W klasie TerrainSceneManager funkcja ta wymaga nazwy pliku, z którego ma wczytać konfigurację terenu. Znajdź funkcję createScene i dodaj poniższy kod:

         mSceneMgr->setWorldGeometry( "terrain.cfg" );

Przekompiluj teraz i uruchom program. W niektórych przypadkach zauważysz, że teren znajduje się nie tak jak na załączonym rysunku lecz u góry po prawej strony. Wówczas dodaj dodatkowy poniższy kod poprawiający ustawienie kamery:

         // Poprawienie ustawienia kamery
         mCamera->setPosition( 500.0f, 90.0f, 500.0f );

Można również osiągnąć to przesuwając kamerę za pomocą myszki.

Plik terrain.cfg

edytuj

Mamy wiele opcji w tym pliku. Zajmiemy się tylko niektórymi. TerrainSceneManager został zaprojektowany w celu takim, aby miał możliwość stronicowania, niestety jednak wciąż jest jeszcze to implementowane. System stronnicowania terenu polega na tym, że teren jest dzielony na kawałki i kawałek jest wyświetlany wtedy, kiedy użytkownik go widzi. Umożliwia to stworzenie dużego świata, bez obniżenia wydajności w szybkości wyświetlania klatek. Na szczęście istnieje wtyczka, która daje taką możliwość: Paging Scene Manager.

TerrainSceneManager używa mapy wysokości, aby utworzyć teren. Możesz określić ją, jeśli zmienisz właściwość Heightmap.image. Możesz wstawić teksturę terenu za pomocą proporcji WorldTexture. Właściwość DetailTexture umożliwia określić poziom interpolacji tekstury, tworząc w ten sposób teren bardziej realistyczny.

Ogre dostarcza trzy różne rodzaje nieba: SkyBox, SkyDome i SkyPlane. Omówimy każdy po kolei.

SkyBox

edytuj
 
Teren wraz z niebem

SkyBox jest prostym, wielkim sześcianem, który otacza wszystkie obiekty w scenie. Najszybszym sposobem, jak to działa jest zobaczenie przykładu. Tak więc wstawmy poniższy kod do funkcji createScene:

         mSceneMgr->setSkyBox( true, "Examples/SpaceSkyBox" );

Przekompiluj i uruchom program. (SkyBox jest pikselowate, ponieważ użyta tekstura jest niskiej jakości. Wstawiając teksturę o większych wymiarach, otrzymamy lepszy wygląd.) Mamy kilka przydatnych argumentów dla SkyBoxów, które możemy ustawić za pomocą funkcji setSkyBox. Pierwszy mówi, czy włączyć lub wyłączyć SkyBox. Jeśli potrzebujesz wyłączyć SkyBox po prostu użyj mSceneMgr->setSkyBox( false, "" ). Drugim parametrem jest skrypt materiału użytego dla nieba. Trzeci argument określa dystans, o jaki SkyBox ma być oddalony od kamery. Czwarty argument określa, czy SkyBox ma być renderowany przed, czy po narysowaniu sceny. Zobaczmy, co się stanie, kiedy zmienimy domyślny dystans SkyBox (5000j) na bardzo mały:

         mSceneMgr->setSkyBox( true, "Examples/SpaceSkyBox", 10 );

Nic się nie zmieniło. Jest tak, ponieważ czwarty argument określa, czy niebo ma być najpierw rysowane. Domyślnie jest włączony (true). Jeśli SkyBox jest najpierw rysowany, wtedy reszta jest renderowana po nim, nakładając się w ten sposób na niego. Powoduje to, że SkyBox jest zawsze w tle. Należy zaznaczyć, że odległość nieba od kamery powinna być tak ustawiona, aby mieściło się ono między dystansami obcinania kamery. W przeciwnym wypadku nie zobaczymy nieba. Nie jest zalecane rysowanie całego nieba na początku, ponieważ obniża to szybkość działania klatek. Kiedy niebo jest rysowane pod koniec, wtedy tylko widoczne części są renderowane. Umożliwia to przyszybszenie czasu działania. Ma to także swoje ciemne strony. Uczyńmy, aby nasz SkyBox był rysowany na końcu:

         mSceneMgr->setSkyBox( true, "Examples/SpaceSkyBox", 5000, false );

Wygląda to identycznie, jak poprzednio, jednak nie widoczne części nie były renderowane. Należy dodać, że jeśli utworzony SkyBox jest za ciasny, możesz zobaczyć wycięte przez niebo fragmenty świata. Nie wygląda to za ciekawie. Zobaczmy:

         mSceneMgr->setSkyBox( true, "Examples/SpaceSkyBox", 100, false );

Możesz teraz zobaczyć teren, który jest ograniczony i zapakowany przez niebo. Szybka metoda renderowania nieba jest bardzo ograniczona i nie obchodzi się zbyt ostrożnie z geometrią sceny. Innymi słowy pominięcie dwóch ostatnich argumentów jest bardziej bezpieczne, nie musisz wtedy obawiać się o nieporządane efekty.

SkyDome

edytuj
 
Niebo SkyDome z poziomem krzywizny ustawionym na 2

SkyDome bardzo przypomina SkyBox. Możesz go użyć wywołując funkcję setSkyDome. Duży sześcian jest tworzony naokoło kamery a następnie renderowany (sześcian jest mniejszy od terenu i przesuwa się wraz z kamerą). Jednak istnieją duże różnice między nimi, otóż tekstura w SkyDome jest pokazywana w sferyczny sposób. Spoglądając w sześcian masz wrażenie, że wygląda on tak, jakby tekstura była nałożona na sferę. Inną widoczną rzeczą jest to, że sześcian ten nie ma podstawy.

Zobaczmy teraz przykład. Usuńmy wstawioną wcześniej funkcję setSkyBox i wstawmy zamiast tego poniższy kod:

         mSceneMgr->setSkyDome( true, "Examples/CloudySky", 5, 8 );
 
Niebo SkyDome z poziomem krzywizny ustawionym na 64

Przekompiluj to teraz i uruchom. Przesuń kamerę w centrum terenu, tak aby z każdej strony było widać nasze góry. Następnie spójrz na niebo, jak pięknie wygląda.Patrząc na to przyciśnij przycisk R, aby przełączyć widok na widok siatki. Zapewne zauważysz, że patrzysz na sześcian, ale tekstura chmur jest tak nałożona, że wygląda jak sfera.

Pierwsze dwa argumenty oznaczają to samo, co w funkcji setSkyBox. Możesz wyłączyć SkyDome poprzez wywołanie mSceneMgr->setSkyDome( false, "" ). Trzeci argument ustawia poziom krzywizny. Najlepiej, aby argument ten był między 2 a 65. Mniejsze wartości sprawiają wrażenie, że niebo jest dalej, natomiast większe, że jest bliżej i jest bardziej wygładzone. Czwarty argument jest liczbą rzeczywistą, która określa na ile kawałków ma zostać podzielona tekstura. Należy dodać, że argument ten może być ułamkiem np. 1.234. Piąty i szósty argument opisuje odległość od kamery, a także czy niebo ma być rysowane na samym początku. Te dwa argumenty zostały one opisane szerzej w sekcji SkyBox.

SkyPlane

edytuj
 
Nasz SkyPlane

SkyPlane wielce się różni od SkyBoxów i SkyDome. Zamiast renderować niebo na sześcianie, wykorzystuje on pojedyńczą płaszczyznę. Wyczyśćmy nasz kod SkyDome z funkcji createScene. Pierwszą rzeczą jaką zrobimy to utworzymy płaszczyznę i skierujemy ją ku dołowi. Wywołując metodę setSkyPlane nie podajemy argumentu określającego odległość jak było w SkyBox i SkyPlane. Zamiast tego ustawiamy parametr d w naszej płaszczyźnie:

         Plane plane;
         plane.d = 1000;
         plane.normal = Vector3::NEGATIVE_UNIT_Y;

Kiedy już mamy naszą płaszczyznę, możemy utworzyć SkyPlane. Dodajmy, że czwarty parametr jest wielkością SkyPlane (w tym przypadku 1500x1500), a piąty określa jak wiele razy ma podzielić teksturę:

         mSceneMgr->setSkyPlane( true, plane, "Examples/SpaceSkyPlane", 1500, 75 );

Przekompiluj i uruchom program. Mamy dwa problemy związane z utworzonym tutaj SkyPlanem. Pierwszy polega na tym, że użyta tekstura jest za mała, dlatego też nie pokrywa ona nieba za dobrze. Możemy tego problemu się pozbyć tworząc dobrą i większą teksturę. Jakkolwiek głównym problemem z tą techniką jest to, że gdy spoglądniesz na horyzont, możesz zobaczyć koniec SkyPlane. Nawet jeśli masz dobrą teksturę, może nie wyglądać najlepiej, ponieważ zobaczysz koniec horyzontu. Te podstawowe użycie SkyPlanu jest przydatne tylko wtedy, kiedy masz wysokie ściany naokoło viewportu. W takiej sytuacji używanie SkyPlanu może być korzystniejsze niż tworzenie pełnych SkyBoxów/SkyDome.

Na szczęście, nie jest to wszystko, co możemy zrobić ze SkyPlane. Szósty argument jest znanym już "renderFirst", opisanym w sekcji SkyBox i SkyDome. Siódmy argument umożliwia określanie poziomu krzywizny SkyPlanu, tak więc nie musimy dłużej używać płaszczyny, zamiast tego może to być krzywa powierzchnia. Możemy także ustawić liczbę segmentów x i y, które mają zostać użyte do tworzenia SkyPlane (domyślnie SkyPlane jest wielkim kwadratem, ale jeśli chcemy go skrzywić potrzebujemy naszą płaszczyznę podzielić na mniejsze kwadraty). Ósmy i dziewiąty argument są liczbą segmentów na x i y:

         mSceneMgr->setSkyPlane( true, plane, "Examples/SpaceSkyPlane", 1500, 50, true, 1.5f, 150, 150 );

Przekompiluj i ruchom program. Teraz nasz SkyPlane wygląda o wiele lepiej, chociaż używa trochę pracy. Możesz także użyć inny materiał chmur:

         mSceneMgr->setSkyPlane( true, plane, "Examples/CloudySky", 1500, 40, true, 1.5f, 150, 150  );

Przekompiluj i uruchom program.

Możesz wyczyścić SkyPlane wywołując 'mSceneMgr->setSkyPlane( false, Plane(), "" );'

Którego nieba użyć

edytuj

Które niebo zastosować, zależy wyłącznie od twojej aplikacji. Jeśli musisz widzieć wszystko dookoła, nawet ujemny kierunek osi y, wtedy jedyną możliwością którą musisz zastosować jest SkyBox. Jeśli masz teren lub jakieś podłogi z blokami, które zakrywają ujemny kierunek osi y, wtedy używając SkyDome uzyskamy bardziej realistyczny efekt. W obszarach, gdzie nie możesz zobaczyć horyzontu (np. dolina ze wszystkich stron otoczona górami), SkyPlane będzie trafnym wyborem, który najmniej obciąża GPU. Główną przyczyną, aby używać SkyPlane, co zobaczymy w następnej sekcji, jest to, że dobrze współgra z mgłą.

Wprowadzenie

edytuj
 
Mgła

Używanie mgły jest bardzo proste. Jest jedno ostrzeżenie, zanim zrozumiesz jak wykorzystać mgłę w swoim programie. Kiedy używasz TerrainSceneManager, musisz wywołać setFog przed funkcją setWorldGeometry. (W innych menadżerach nie ma większej różnicy). W zależności, co wywołamy najpierw, różna vertex programy będą wybrane, aby tworzyć mgłę na teranie.

Zanim rozpoczniesz, wyczyść całą zawartość funkcji createScene, z wyjątkiem setWorldGeometry.

Najbardziej ważną rzeczą, jaką trzeba zrozumieć o mgle, jest to, że nie jest ona w rzeczywistości jednostką tworzoną w pustej przestrzeni, jak mogłeś sobie wyobrażać. Zamiast tego jest po prostu filtrem używanym dla obiektów, na które aktualnie spoglądasz. Jeśli nie patrzysz na żaden obiekt, nie zobaczysz mgły - zobaczysz tylko kolor tła. Tak więc kolor tła powinien być taki sam, jak kolor mgły.

Mamy dwa typy mgieł: liniową i wykładnicza. Najłatwiej zobaczyć różnicę między nimi spoglądając na przykłady.

Rodzaje mgieł

edytuj

Pierwszą mgłą, najłatwiejszą do zrozumienia, jest mgła liniowa. Najpierw co musimy zrobić przed wywołaniem funkcji setWorldGeometry jest ustawienie koloru tła viewportu. Moglibyśmy tak jak w poprzednim rozdziale użyć funkcję createViewport, ale spróbujemy to zrobić bez ponownego tworzenia viewportu. W tym celu na początku metody createScene dodajemy:

         ColourValue fadeColour( 0.9, 0.9, 0.9 );
         mWindow->getViewport(0)->setBackgroundColour( fadeColour );

Jeżeli posiadamy więcej niż jeden viewPort należałoby użyć getNumViewports, aby pobrać ich liczbę, a następnie iterować każdy po kolei. Jednak w naszym przypadku mamy tylko jeden viewport, więc używamy go bezpośrednio. Kiedy już ustawiliśmy kolor tła, możemy utworzyć mgłę. Wstawiamy kolejną linię po wyżej podanych:

         mSceneMgr->setFog( FOG_LINEAR, fadeColour, 0.0, 50, 500 );

Pierwszym argumentem jest metody setFog jest typ mgły (w tym przypadku liniowy). Drugi argument określa kolor mgły (w tym przypadku prawie biały). Trzeci argument jest używany do nie liniowej mgły. Czwarty i piąty określa od kąd się zaczyna mgła, a gdzie kończy. W tym zaczyna się w odległości 50 od kamery, a kończy się na 500. Zależność między gęstością mgły jest liniowa. Wszystko, co jest za 500 od kamery nie jest widoczne. Przekompiluj i uruchom program.

Innym typem mgły jest mgła wykładnicza. Zamiast ustawiania początku i końca mgły, okreśłamy gęstość mgły (czwarty i piąty argument jest nieokreślony). Zamieńmy poprzedni kod na ten:

         mSceneMgr->setFog( FOG_EXP, fadeColour, 0.005 );

Przekompiluj i uruchom program. Mgła ta wygląda inaczej, niż poprzednia. Mamy do dyspozycji także inny typ tej mgły, która jest jeszcze bardziej sroga od pierwszej. Zamień poprzednie wywołanie setFog na takie:

         mSceneMgr->setFog( FOG_EXP2, fadeColour, 0.003 );

Przekompiluj i uruchom ten program. Mgła ta jest najbardziej zmienna, pomiędzy wszystkimi innymi. Spróbuj poeksperymentować z różnymi funkcjami mgły.

Mgła a niebo

edytuj

Może powstać kilka interesujących problemów, kiedy próbujesz używać mgły razem z SkyBox i SkyDome. Ponieważ SkyDome i SkyBox są sześcianami, kiedy używamy mgły powstają problemy, ze względu na to, że mgła działa w sposób sferyczny. Wyczyśćmy zawartość metody createScene. Jeśli sprytnie dobierzemy argumenty dla SkyDome i mgły, możemy zobaczyć bezpośrednio problem:

         ColourValue fadeColour( 0.9, 0.9, 0.9 );
         mSceneMgr->setFog( FOG_LINEAR, fadeColour, 0.0, 50, 515 );
         mWindow->getViewport(0)->setBackgroundColour( fadeColour );
         
         mSceneMgr->setSkyDome( true, "Examples/CloudySky", 5, 8, 500 );

Przkompiluj i uruchom program. Jeśli będziesz ruszał kamerę naokoło, zobaczysz różną gęstość mgły na SkyDome, która zależy od tego, na którą część patrzymy.

Na pewno takiego czegoś nie chcemy. Inną możłiwością jest użycie SkyPlanu. Zamieńmy kod w createScene na ten:

         ColourValue fadeColour( 0.9, 0.9, 0.9 );
         mSceneMgr->setFog( FOG_LINEAR, fadeColour, 0.0, 0, 130 );
         mWindow->getViewport(0)->setBackgroundColour( fadeColour );
 
         Plane plane;
         plane.d = 100;
         plane.normal = Vector3::NEGATIVE_UNIT_Y;
 
         mSceneMgr->setWorldGeometry( "terrain.cfg" );
 
         mSceneMgr->setSkyPlane( true, plane, "Examples/CloudySky", 500, 20, true, 0.5, 150, 150 );

To wygląda poprawnie. Jeśli patrzymy do góry możemy zobaczyć niebo. Jednak jeśli używa się krzywizny lub nie, może powstać problem związany z tym, że zobaczymy horyzont, który nie wygląda poprawnie.

Mgła jako ciemność

edytuj

Nieraz jest tak, że nie chcemy, aby mgła była widoczna na niebie np. chcemy stworzyć ciemność. W tym celu możemy wykorzystać pewien użyteczny trik. Ustawmy kolor mgły zamiast na jasny, na ciemny kolor i zobaczmy co będzie (zauważmy, że SkyPlane jest oddalone tylko o 10 jednostek miary od kamery):

        ColourValue fadeColour( 0.1, 0.1, 0.1 );
        mWindow->getViewport(0)->setBackgroundColour( fadeColour );
        mSceneMgr->setFog( FOG_LINEAR, fadeColour, 0.0, 10, 150 );

        mSceneMgr->setWorldGeometry( "terrain.cfg" );

        Plane plane;
        plane.d = 10;
        plane.normal = Vector3::NEGATIVE_UNIT_Y;

        mSceneMgr->setSkyPlane( true, plane, "Examples/SpaceSkyPlane", 100, 45, true, 0.5, 150, 150 );

Przekompiluj i uruchom program.

Oczywiście, zamiast tego chwytu możemy wykorzystać odpowiednio dobraną lampę, ale ten przykład pokazuje, w czym jeszcze może zostać wykorzystana mgła. Używając ciemnej mgły można uzyskać interesujący efekt mroku lub zaślepienia np. w grach, w których poruszamy się postacią pierwszoplanową.

Kod źródłowy

edytuj

Dostępny tu jest kompletny kod źródłowy wykorzystany w powyższym artykule.