Ładowanie obiektów za pomocą Assimp

W projekcie zaimplementowano ładowanie modeli przy użyciu biblioteki Assimp. Obiekty są ładowane za pomocą funkcji loadModelToContext(std::string path, Core::RenderContext& context). Pierwszym argumentem tej funkcji jest ścieżka, pod którą znajduje się model, a drugim jest referencja do RenderContext. Ta struktura przechowuje informacje o modelu, m.in. o jego VAO czy liczbie wierzchołków. Rysowanie obiektu odbywa się za pomocą funkcji Core::DrawContext(Core::RenderContext& context). Obecnie na przykładzie rysowana jest sfera ładowana z pliku.

Podczas poprzednich zadań zdefiniowaliśmy funkcje tworzące macierze widoku i projekcji. Aby narysować model, należy najpierw zdefiniować macierz modelu, przemnożyć ją przez macierz kamery i macierz widoku, a następnie wysłać ją do GPU i dopiero potem narysować model. Te operacje są bardzo powtarzalne, dlatego można je przenieść do osobnej funkcji. W pliku ex_4_1.hpp znajduje się funkcja drawObjectColor, która przyjmuje rysowany obiekt jako Core::RenderContext&, macierz modelu jako glm::mat4 oraz kolor jako glm::vec3.

Zadanie

Wszystkie obiekty rysowane przez drawObjectColor są w jednym kolorze – napraw to. Wewnątrz funkcji przekaż kolor jako uniform do GPU (za pomocą funkcji glUniform3f) i odpowiednio zmodyfikuj shader fragmentów, aby wyznaczyć go jako kolor wyjściowy.

Korzystając z tej funkcji, stwórz układ słoneczny z przynajmniej jedną planetą, która posiada księżyc. Planeta powinna poruszać się wokół słońca, a księżyc wokół planety.

Zadanie*

Rozbuduj układ planetarny do przynajmniej 5 planet i pasa asteroid. Ściągnij z internetu/stwórz kilka prostych modeli asteroid, z których zbudujesz pas asteroid.

Zadanie

Celem tego zadania jest dodanie statku, który będzie latać po układzie planetarnym.

Załaduj model statku, który jest w pliku spaceship.obj. Stwórz zmienne globalne spaceshipPos oraz spaceshipDir, które będą określać pozycję i kierunek, w którym statek się porusza. Później będziemy je zmieniać za pomocą przycisków, na razie wewnątrz funkcji processInput przypisz do nich odpowiednio cameraPos+1.5*cameraDir+glm::vec3(0,-0.5f,0) oraz cameraDir. W ten sposób po prawidłowym ustawieniu macierzy statek będzie znajdował się zawsze przed kamerą.

Przesuń i obróć statek w odpowiedni sposób. Przesunięcie zrealizujemy przez translację do spaceshipPos natomiast macierz statku liczy się tak samo, jak macierz kamery, tylko zamiast -cameraDir bierzemy spaceshipDir i na końcu trzeba tę macierz odwrócić (lub transponować, co jest tym samym, ponieważ mówimy o macierzy ortonormalnej).

Zadanie

Zadanie*

Obecnie klawisze regulują ustawienia kamery, do której jest podpięty statek. Zmodyfikuj aplikację, aby klawisze umożliwiały poruszanie się statkiem, a kamera podążała za nim. W tym celu w obsłudze klawiatury modyfikuj wektory spaceshipPos i spaceshipDir. Następnie dostosuj cameraPos i cameraDir w zależności od wektorów spaceshipPos i spaceshipDir.

Zadanie*

Obecnie szybkość ruchu statku lub kamery zależy od liczby klatek, co może powodować różne efekty na różnych komputerach, co nie jest pożądane. Aby to skorygować, musimy obliczyć czas, jaki upłynął między poszczególnymi klatkami i dostosować do niego przesunięcia oraz obroty. Utwórz zmienne globalne float lastFrameTime oraz float deltaTime, następnie w funkcji renderScene dodaj oblicz deltaTime = time-lastFrameTime i przypisz do zmiennej lastFrameTime wartość time. Nie chcemy, żeby wartość deltaTime była zbyt duża, gdy nagle spadnie liczba klatek, dlatego ucinamy ją od góry przez 0.1.

Wykorzystaj deltaTime w funkcji processInput aby uniezależnić prędkość poruszania się od liczby klatek na sekundę.

Zadanie*

Zastąp model statku innym modelem.

Bufor głębokości

Bufor głębokości rejestruje odległość danego piksela od kamery. Pozwala to podczas rysowania kolejnych obiektów odrzucić piksele, które znajdowałyby się za już narysowanymi. Ten mechanizm jest automatycznie, żeby go uruchomić, wystarczy dodać instrukcję glEnable(GL_DEPTH_TEST), poza tym przed rysowaniem klatki należy wyczyścić bufor głębokości, co robimy w instrukcji glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT).

Zadanie

Sprawdź, co się stanie, gdy nie włączymy glEnable(GL_DEPTH_TEST) oraz sprawdź co, się dzieje, gdy nie czyścimy żadnego bufora lub gdy czyścimy tylko bufor koloru czy tylko bufor głębokości. Dlaczego dzieje się to, co widzisz?

Wizualizacja bufora głębokości

W tej części zwizualizujemy jak wygląda bufor głębokości przy pomocy skali szarości. Zrealizujemy poprzez napisanie odpowiedniego shadera.

Uwaga nie jest to faktycznie rysowanie bufora głębokości, to wymagałoby stworzenie FrameBufferObject renderowanie głębokości do niego i narysowanie wyniku na ekranie. Zrobimy to na późniejszych zajęciach przy okazji rysowania cieni.

Wykorzystamy wbudowaną zmienną gl_FragCoord we fragment shaderze. Zawiera ona informacje o pozycji fragmentu.

Zadanie

We fragment shaderze podmień wartości R G B w shaderze fragmentów na gl_FragCoord.z.

Zauważ, że obiekty są bardzo jasne i stają się ciemniejsze, dopiero gdy kamera podjedzie bardzo blisko. Wynika to z tego, że wartości z w gl_FragCoord nie są liniowe ze względu na rzutowanie perspektywiczne omówione na poprzednich zajęciach. Poniższy wykres prezentuje przykładową różnicę między faktyczną wartością a wartością w gl_FragCoord.

My chcielibyśmy wyświetlać je liniowo. W tym celu będziemy musieli wrócić do współrzędnych w przestrzeni świata. Zauważ, że wartości gl_FragCoord.z są z zakresu od \([0,1]\) a nie \([-1,1]\) jak są zapisane współrzędne w przestrzeni ekranu. Dlatego pierwszym krokiem będzie przekonwertowanie ich (poprzez pomnożenie przez 2 i odjęcie 1). Współrzędne w przestrzeni ekranu obliczamy wzorem \[z'=-\frac{(n + f)}{(n - f)}- \frac{(2 n f)}{z(n - f)}.\]My chcemy obliczyć \(z\) po przekształceniu wzoru otrzymujemy: \[z=\frac{-2nf}{z'(n-f)+n+f}.\]

Zadanie

We fragment shaderze uwórz funkcję, która oblicza \(z\) i wyświetl zlinearyzowaną odległość. Pamiętaj, że wartość \(z\) jest z zakresu od \(n\) do \(f\), dlatego zmień podziel ją przez \(-f\) przed rysowaniem.

Zadanie

Wykorzystaj informację o odległości, żeby dodać do sceny efekt mgły. Zmieszaj kolor (funkcja "mix" w glsl) obiektu z kolorem tła, jako współczynnik weź wartość z poprzedniego zadania.

Kreatywne wykorzystanie bufora głębokości

Czasem chcielibyśmy, żeby niektóre wyświetlane elementy były inaczej traktowane przez bufor głębokości. Przykładowo chcielibyśmy stworzyć bardziej złożone tło dla naszej sceny. Chcemy wtedy, żeby to tło było ,,za’’ każdym innym obiektem w scenie. Możemy to osiągnąć poprzez namalowanie tła na początku, a następnie usunięcie zawartości bufora głębokości.

Zadanie*

Dodaj jakiś rodzaj tła w sposób opisany powyżej. Mogą być to na przykład małe sfery udające gwiazdy. Prostokąt w przestrzeni ekranu, który będzie zmieniał kolory czy kręcący się prostopadłościan.

Implementacja ruchu kamery w symulatorze kosmicznym bez synchronizacji modelu statku

Zadanie*

W tym ćwiczeniu zajmiemy się implementacją systemu kontroli kamery, który pozwoli na interaktywne sterowanie widokiem w symulatorze lotu. Będzie to wymagało obsługi wejścia myszy do kontroli ruchów pitch i yaw. Dodaj nastepujace zmienne globalne do kodu.


float pitch = 0.0f;
float yaw = -90.0f;
float lastX = 800.0f / 2.0;
float lastY = 600.0 / 2.0;
bool firstMouse = true;

Zaimplementuj w funkcji processinput logikę odpowiedzialną za przetwarzanie wejścia myszy. Użyj funkcji glfwGetCursorPos do uzyskania bieżącej pozycji kursora. Dodaj zmienną sensitivity do regulacji czułości myszy. Na początku sprawdzamy, czy jest to pierwsze uchwycenie pozycji myszy po uruchomieniu aplikacji. Jeśli tak, to zapisujemy bieżącą pozycję myszy jako punkt odniesienia (lastX i lastY) i ustawiamy zmienną firstMouse na false. To zapobiega gwałtownemu skoku kamery, gdybyśmy po raz pierwszy poruszyli myszą. Następnie obliczamy różnicę (xoffset i yoffset) między aktualną pozycją kursora a ostatnią zapisaną pozycją. Zauważ, że wartość yoffset jest odwrócona, ponieważ w systemach okienkowych współrzędna y zwykle rośnie w dół ekranu, a chcemy, aby ruch w górę odpowiadał wzrostowi kąta widzenia kamery. Po obliczeniu przesunięć skalujemy je wartością sensitivity, która pozwala na dostosowanie reakcji kamery na ruch myszy. Mała wartość czułości sprawi, że kamera będzie reagować łagodniej, podczas gdy większa wartość uczyni ruchy bardziej gwałtownymi. Na koniec, dodajemy przeskalowane przesunięcia do naszych kątów orientacji kamery: yaw (odpowiedzialny za obrót wokół osi pionowej) i pitch (odpowiedzialny za obrót wokół osi poziomej). Trzeba tu uważać, aby nie przekroczyć pewnych granic. Na przykład, nie chcemy, aby kamera "przewracała się" do góry nogami, dlatego ograniczamy kąt pitch do wartości pomiędzy -89 a 89 stopni. Dodaj logikę do ograniczenia kąta pitch, aby uniknąć "przewrócenia" kamery.
 if (pitch > 89.0f)
	pitch = 89.0f;
if (pitch < -89.0f)
	pitch = -89.0f;

Zastosuj funkcje trygonometryczne do przeliczenia kątów na wektor kierunku kamery:

glm::vec3 front;
front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
front.y = sin(glm::radians(pitch));
front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
cameraDir = glm::normalize(front);

Na zakończenie ćwiczenia oczekujemy, że symulator będzie poprawnie implementował ruch kamery, lecz dołączony model statku kosmicznego nie będzie jeszcze obracał się razem z kamerą.