OGRE/Jak pracuje Ogre

Menadżer sceny

edytuj

Wszystko, co jest pokazywane na ekranie jest zarządzane przez menadżera sceny (SceneManager). SceneManager jest klasą, która przechowuje ścieżki obiektów układanych w scenie. Kiedy tworzysz kamerę, z której patrzysz się na scenę, menadżer sceny zachowuje ścieżkę do niej. Menadżer sceny pamięta także ścieżkę do tworzonych kwadratów, billboardów, świateł itp.

Mamy wiele typów menadżerów scen. Wśród nich są między innymi odtwarzające teren, tworzące drzewa BSP i inne.

Jednostka

edytuj

Jednostka (Entity) jest jednym z rodzajów obiektów, które możesz renderować w scenie. Można ją zdefiniować jako coś, co jest reprezentowane przez siatkę 3D (mesh). Może nią być np. robot, ryba, teren. Z kolei światło, billboard, cząsteczki (particles), lub kamera nimi nie są.

Ogre oddziela obiekty, które są renderowane od ich lokacji i orientacji. Tych parametrów nie możesz ustawić bezpośrednio w jednostce, natomiast musisz powiązać jednostkę (Entity) z węzłem sceny (SceneNode). Węzeł sceny zawiera informacje o położeniu i orientacji.

Węzeł sceny

edytuj

Jak już było wspomniane węzeł sceny przechowuje ścieżkę do położenia i orientacji wszystkich obiektów, które są z nim powiązane. Jednostka nie jest renderowana, dopóki nie powiążesz ją do węzła sceny. Podobnie sam węzeł sceny nie jest obiektem wyświetlanym na ekranie. Natomiast zostanie coś pokazane wtedy, gdy utworzysz węzeł sceny i powiążesz do niego jakąś jednostkę (lub inny obiekt).

Do węzła sceny może zostać powiązana dowolna liczba obiektów. Załóżmy, że mamy postać i chcemy wygenerować światło wokół niej. Najpierw powinniśmy utworzyć węzeł sceny, potem jednostkę dla postaci i powiązać ją z węzłem sceny. Następnie tworzymy światło i powiązujemy go z węzłem sceny. Węzeł sceny może także być powiązany z innym węzłem sceny.

Należy dodać, że pozycja węzła sceny jest zawsze relatywna od swojego rodzica. Każdy menadżer sceny zawiera korzeń, do którego są powiązane wszystkie inne węzły sceny.

Pierwsza aplikacja

edytuj

W dalszej części oprzemy się na zalążku programu OGRE, który należy wkleić w pierwszy utworzony plik .cpp. Upewnij się że Twoje środowisko projektowe jest skonfigurowane zgodnie z opisem w rozdziale Środowisko projektowe. Nie zapomnij do konfiguracji kompilatora dodać katalogu $(OGRE_HOME)\samples\include jako jednego z katalogów z plikami źródłowymi. Przekompiluj zalążek programu i uruchom go by sprawdzić czy wszystko jest prawidłowo skonfigurowane.

Podczas pracy już rozbudowanego programu możesz używać przycisków myszy i przesuwać lub ruszać mysz, aby się obrócić. Przycisk Escape kończy pracę programu.

Dodajemy robota

edytuj
Robot w Ogre.

Poszukajmy w naszym kodzie w definicji klasy myApp funkcję createScene. Skorzystamy z tej funkcji, aby osiągnąć nasz cel, czyli wstawić robota. Ustawimy najpierw kolor światła otoczenia za pomocą funkcji setAmbientLight. Należy nadmienić, że argumentami konstruktora ColorValue są trzy wartości dla koloru czerwonego, zielonego, niebieskiego w przedziale [0..1]. Wstawmy więc do niej linię:

       mSceneMgr->setAmbientLight( ColourValue( 1, 1, 1 ) );

Teraz potrzebujemy utworzyć jednostkę. W tym celu wywołujemy metodę createEntity zawartą w SceneManager:

       Entity *ent1 = mSceneMgr->createEntity( "Robot", "robot.mesh" );

Dwie sprawy wymagają wyjaśnienia -- skąd się wziął mSceneMgr i czym jest, a także jakie argumenty przyjmuje createEntity. mSceneMgr to wskaźnik wskazujący na bieżący menadżer sceny, utworzony przez klasę ExampleApplication. Pierwszym argumentem metody createEntity jest nazwa obiektu. Każda jednostka musi mieć unikalną nazwę. Jeśli spróbujesz utworzyć drugą jednostkę o tej samej nazwie zostanie zwrócony błąd. Drugi argument (w tym przypadku "robot.mesh") określa siatkę z jakiej ma korzystać jednostka. Plik opisujący siatkę 'robot.mesh' dostajemy wraz z Ogre. Znajduje się on w katalogu 'media/models'.

Następnym krokiem jest utworzenie węzła sceny. Ponieważ menadżer sceny przechowuje korzeń sceny, dlatego musimy też utworzyć węzeł sceny, który będzie jego synem. Zrobimy to w ten sposób:

       SceneNode *node1 = mSceneMgr->getRootSceneNode()->createChildSceneNode( "RobotNode" );

Wywołujemy getRootSceneNode otrzymujemy korzeń sceny, następnie z korzenia wywołujemy createChildSceneNode. W ten sposób utworzyliśmy węzeł sceny, który jest synem korzenia. Na ten węzeł wskazuje teraz node1. Argumentem createChildSceneNode jest nazwa tworzonego węzła. Podobnie, jak podczas tworzenia jednostek, nazwa nie może się powtarzać.

Na koniec powiązujemy jednostkę z węzłem. I w ten sposób będziemy mogli zobaczyć naszego robota.

       node1->attachObject( ent1 );

Przekompiluj teraz program i uruchom go. Powinieneś zobaczyć na ekranie robota.

Uwaga! Jeśli resources.cfg nie jest odpowiednio skonfigurowany, możesz nic nie zobaczyć. Aby nie było problemów powinieneś ustawić odpowiednio ścieżki resources.cfg tak by wskazywały istniejące i właściwe katalogi podkatalogu media. W poniższym przykładzie katalog media znajdziemy wychodząc dwukrotnie w górę z katalogu z binariami. Układ katalogów jest zgodny z podanym w rozdziale Środowisko projektowe. Można także podawać ścieżki w sposób bezwzględny:

FileSystem=../../media/materials/programs
FileSystem=../../media/materials/scripts
FileSystem=../../media/materials/textures
FileSystem=../../media/materials/models
FileSystem=../../media/models

Współrzędne a wektory

edytuj

Ogre, a także wiele innych silników graficznych używa osi x i z jako poziomą płaszczyznę, a oś y jako pionową. Względem monitora osie idą tak (kierunek od ujemnych do dodatnich współrzędnych): współrzędne osi x idą od strony lewej do prawej, osi y od dołu do góry, a współrzędne z osi z idą od głębi, wychodząc z monitora.

Jak nasz robot jest zwrócony wzdłuż dodatniego kierunku x? To zależy od samej siatki, a także od tego, jak ją zaprojektujesz. Ogre nie zakłada, jak ma mieć orientację model. Każda wczytywana siatka może mieć różny "kierunek początkowy", który określa, jak jest zwrócona siatka.

Ogre używa klasy Vector zarówno do reprezentowania pozycji, jak i kierunku, nie posiada natomiast klasy Point. Istnieją wektory określone dla 2, 3 i 4 wymiarów, nazywają się odpowiednio Vector2, Vector3, Vector4. Najczęściej używany jest Vector3. Jeśli nie jesteś zaznajomiony z wektorami, zobacz Wektory, zanim zaczniesz robić coś poważnego w Ogre. Matematyka oparta na wektorach może się okazać bardzo przydatna, jeśli rozpoczniesz pracę nad skomplikowanymi programami.

Dodawanie innych obiektów

edytuj

Zrozumiawszy, jak pracuje system współrzędnych, możemy powrócić do naszego kodu. W liniach, które wcześniej zapisaliśmy nie określiliśmy położenia, w którym ma się znajdować robot. Większość funkcji w Ogre mają domyślne argumenty np. SceneNode::createChildSceneNode ma trzy argumenty: nazwa węzła sceny, initializowana pozycja (lokacja) i obrót (orientacja). Współrzędna którą widzieliśmy została ustawiona na (0,0,0). Utwórzmy teraz inny węzeł sceny, lecz tym razem określimy początkową pozycję, inną niż domyślna:

        Entity *ent2 = mSceneMgr->createEntity( "Robot2", "robot.mesh" );
        SceneNode *node2 = mSceneMgr->getRootSceneNode()->createChildSceneNode( "RobotNode2", Vector3( 50, 0, 0 ) );
        node2->attachObject( ent2 );

Kod ten jest podobny do poprzedniego z dwoma wyjątkami. Pierwszy -- zmieniliśmy trochę nazwę jednostki i węzła sceny. Drugi -- zmieniliśmy pozycję początkową na odległą od korzenia sceny o 50 jednostek miary (nie mylić z jednostkami - Entity) współrzędnej x (pamiętamy, że pozycja węzła sceny jest względne do swojego rodzica). Przekompiluj i uruchom demo. Będziemy mieli dwa roboty koło siebie.

Więcej o jednostkach

edytuj

Klasa Entity jest bardzo rozległa i nie zostaną tutaj opisane wszystkie jej możliwości, tylko tyle, ile potrzebuje początkujący. Opiszemy natomiast tutaj kilka przydatnych metod.

Za pomocą metod Entity::setVisible i Entity::isVisible możemy ustawić, żeby jednostka była widoczna lub nie, albo sprawdzić czy jest widoczna. Jeśli potrzebujesz ukryć jednostkę, lecz później ją chcesz znowu pokazać nie musisz usuwać jej, a potem tworzyć jej od nowa. Wystarczy, jeśli użyjesz tej funkcji.

Funkcja getName zwraca nazwę jednostki, a getParentSceneNode zwraca węzeł sceny, do którego dana jednostka jest powiązana.

Więcej o węzłach sceny

edytuj

Klasa SceneNode jest bardzo złożona. Za pomocą SceneNode można zrobić wiele rzeczy, zostaną tutaj jednak przedstawione te najbardziej użyteczne.

Możesz pobrać i ustawić pozycję węzła sceny za pomocą metody getPosition i setPosition (zawsze względnie do swojego rodzica). Możesz także przenieść obiekt względnie do bieżącej pozycji używając metody translate.

SceneNode możemy także przeskalować i obrócić. Skalę możemy zmienić za pomocą funkcji scale. Możesz użyć funkcji yaw, roll i pitch, aby obrócić obiekty. Metoda resetOrientation resetuje wszystkie rotacje zrobione na obiekcie. Można także użyć setOrientation, getOrientation i rotate do bardziej zaawansowanych obrotów.

Poprzednio użyliśmy funkcji attachObject. Funkcje podobne do tej są przydatne, jeśli potrzebujesz manipulować obiektami powiązanymi z węzłem sceny: numAttachedObjects, getAttachedObject (jest wiele wersji tej funkcji), detachObject (także wiele wersji), detachAllObjects. Posiada także wiele funkcji wykonująmi pewne zadania na rodzicu i dzieciach węzła.

Ponieważ wszystkie pozycje/translacje robione są względnie do rodzica węzła sceny, możemy przenieść wspólnie dwa węzły sceny w sposób bardzo prosty. Jak na razie mamy taki kod w naszej aplikacji:

        Entity *ent1 = mSceneMgr->createEntity( "Robot", "robot.mesh" );
        SceneNode *node1 = mSceneMgr->getRootSceneNode()->createChildSceneNode( "RobotNode" );
        node1->attachObject( ent1 );
  
        Entity *ent2 = mSceneMgr->createEntity( "Robot2", "robot.mesh" );
        SceneNode *node2 = mSceneMgr->getRootSceneNode()->createChildSceneNode( "RobotNode2", Vector3( 50, 0, 0 ) );
        node2->attachObject( ent2 );

Zamieńmy 6 linię:

        SceneNode *node2 = mSceneMgr->getRootSceneNode()->createChildSceneNode( "RobotNode2", Vector3( 50, 0, 0 ) );

na

        SceneNode *node2 = node1->createChildSceneNode( "RobotNode2", Vector3( 50, 0, 0 ) );

W ten sposób czynimy RobotNode2 dzieckiem RobotNode. Przenosząc node1 będziemy przenosić node2 względem jego, ale przenosząc node2 nie będziemy zmieniać node1. Ten przykład przeniesie tylko RobotNode2:

        node2->translate( Vector3( 10, 0, 10 ) );

Natomiast ten poniższy kod przenosi RobotNode, a ponieważ RobotNode2 jest jego dzieckiem, RobotNode2 zostanie także przeniesiony:

        node1->translate( Vector3( 25, 0, 0 ) );

Kod źródłowy

edytuj

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