LABORATORIUM PROGRAMOWANIA KOMPUTERÓW
Transkrypt
LABORATORIUM PROGRAMOWANIA KOMPUTERÓW
Gliwice, 9 III 2008 Semestr: IV Grupa: I Sekcja: I LABORATORIUM PROGRAMOWANIA KOMPUTERÓW Autor: Tomasz bla Fortuna E-mail: [email protected] Prowadzący: Karolina Nurzyńska 1. Temat projektu Stworzenie programu „raytracer” potrafiącego zarządzać elementami umieszczonymi na trójwymiarowej scenie oraz realizującego renderowanie tych scen za pomocą wstecznego śledzenia promieni światła w scenie. Budowa programu musi przewidywać możliwość dodania innych elementów niż początkowo przewidywane oraz możliwość przerobienia go na Photon Mapper. 2. Analiza rozwiązania Definicje i oznaczenia: Promieniem (ang. ray) nazywamy półprostą. Notacja [ A]=[ a , b] oznacza promień [A] o początku w punkcie wyznaczonym przez wektor a, skierowanym w kierunku wyznaczanym przez wektor b. Dla promienia [A] zapis A.S oznacza wektor początku promienia A, natomiast A.D wektor kierunku. , A.D ] A=[ A.S Kolejne etapy programu: 1. Zdefiniowanie sceny (współczynnika załamania powietrza oraz koloru tła) oraz kamery. 2. Zdefiniowanie zbioru materiałów (składających się z tekstur diffuse, specular, reflect, refract, współczynnika połysku (shininess) i współczynnika załamania światła). 3. Zdefiniowanie świateł. 4. Umieszczenie na scenie obiektów, które mają zostać wyrenderowane. 5. Rekurencyjne śledzenie biegu promieni w scenie i określanie wynikowego koloru każdego piksela obrazu. Najistotniejszy w programie jest algorytm raytracingu, który pobieżnie przedstawiam poniżej. , Dir , Top , FOV , gdzie 1. Zdefiniujmy kamerę jako czwórkę Pos ● Pos - wektor pozycji kamery ● Dir - wektor kierunku „patrzenia” kamery ● Top - wektor prostopadły do Dir wskazujący orientację kamery ● FOV - (Field of View) kąt widzenia Renderując obraz o wymiarach (X, Y) w pierwszym etapie tworzone ,D ] , gdzie jest X ∗Y promieni [ Pos Y = XV ∗ x− X YV∗ D y− 2 2 XV =∣Top× Dir∣∗XD =Top∗YD YV FOV FOV tan tan 2 2 Y XD= YD= ∗ X Y X Możemy sobie wyobrazić, że promienie wychodząc z kamery przelatują przez pewien „ekran”. Wtedy XD to pozioma odległość między punktami w tym ekranie, a YD to odległość w pionie. XV to wektor służący do poruszania się w poziomie po ekranie, YV - w pionie. Natomiast D to punkt na ekranie przez który przelatuje nasz promień. 2. Dla każdego z tych promieni: a. Znajdź najbliższą kolizję z obiektem znajdującym się na scenie. Dla tego punktu określamy: - wektor punktu kolizji ● C ● - wektor normalny do powierzchni obiektu w punkcie zderzenia. Normal ● [Reflect] - promień odbity ● [Refract] - promień załamany MRl MRr MS Diffuse, Specular, Reflect, Refract - kolory (wektory R, G, B) ● MD obiektu w punkcie zderzenia odpowiadające światłu rozproszonemu, odbitemu od lampy (połysk), oraz filtrom nakładanym na promienie odbite i załamane do wewnątrz obiektu. , LD ] . b. Z punktu kolizji do wszystkich świateł wypuść promienie (Shadow rays) [C Sprawdzając ponownie kolizje ustalamy czy źródło światła jest zasłonięte. Jeżeli nie jest obliczamy dla niego dwie składowe według prostego modelu Phong lightening: MD , gdzie LC to kolor światła ● Diffuse= Normal⋅ LD∗ LC∗ (reprezentowany przez wektor R, G, B) ● MS Specular= [ Reflect]. D⋅LD∗ LC∗ c. Oblicz sumę kolorów Diffuse oraz Specular dla wszystkich nie zasłoniętych świateł. d. Wykonaj algorytm rekurencyjnie dla [Reflect] i [Refract]. 3. Suma kolorów zwróconych przez wywołania rekurencyjne oraz suma kolorów Diffuse i Specular obliczona dla wszystkich świateł jest kolorem wynikowym danego piksela. 3. Specyfikacja zewnętrzna Interfejs programu Program jest obsługiwany w bardzo prosty sposób z linii komend systemu operacyjnego. Parametry pobiera poprzez przełączniki podane jako argumenty programu. Wszystkie przełączniki mają formę skróconą, poprzedzaną pojedynczym znakiem minus oraz długą poprzedzaną dwoma. Przetłumaczony wyciąg z pomocy programu: Wykorzystanie: ./blaRAY [-x szerokość] [-y wysokość] [-a] --demo 1|2 ./blaRAY [-x szerokosć] [-y wysokość] [-a] --file <plik> --output <plik> Lista opcji: --scene|-s <plik> - Ścieżka do pliku XML z opisem sceny --demo|-d <num> - Uruchamia scenę demo (argument równy 1 lub 2) --output|-o <filename> - Plik do którego zapisać scenę --width|-x <arg> - Szerokość obrazu --height|-y <arg> - Wysokość obrazu --antialiasing|-a - Włącz anty-aliasing. --help|-h - Pomoc Składnia opisu sceny Plik opisujący scenę jest zgodny ze standardem XML. Opis budowany jest od najprostszych elementów do najbardziej złożonych wykorzystujących elementy prostsze. W celu odwołania się do wcześniej zdefiniowanego elementu używamy jego identyfikatora ("id"). Elementy sceny, zaczynając od najprostszych to: ● Kolory Przykład: <Color id="Octarine" r="0.8" g="0.8" b="0.8" /> Atrybuty: ○ id - identyfikator ○ r, g, b - składowa czerwona, zielona i niebieska. Po zdefiniowaniu koloru wykorzystujemy go potem w innych elementach z samym identyfikatoem. ● Tekstury Przykład: <Texture id="Krata" type="Checked" width="0.2" height="0.2"> <Color id="Blue" /> <Color r="1.0" g="0.0" b="0.0" /> </Texture> <Texture id="LightGray" type="Plain"> <Color id="LightGray" /> </Texture> Atrybuty: ○ type - typ tekstury. Aktualnie obsługiwane: Plain, Checked ○ width, height - rozmiary Definicja tekstury typu plain składa się ponadto z jednego koloru, a typu checked z dwóch. Zamiast odwoływać się do zdefiniowanych kolorów możemy również podać od razu ich składowe r,g,b. ● Materiały Przykład: <Material id="Glass" diffuse="Black" specular="LightGray" reflect="DarkGray" refract="LightGray" shininess="15.0" idx="Glass" /> <Material id="Plane" diffuse="Checked" /> Atrybuty: ○ diffuse - kolor światła rozpraszanego ○ specular - kolor „połysku” ○ reflect - filtr światła odbitego ○ refract - filtr światła załamanego ○ shininess - połyskliwość materiału (potęga do której podnoszony jest wynikowy połysk) ○ idx - (refractive index) współczynnik załamania światła w materiale. Liczba zmiennoprzecinkowa lub jedna ze zdefiniowanych nazw: Vacuum, Air, Water, Diamon, Amber, Salt, Ice, Glass. W przypadku nie podania, któregokolwiek z parametrów wykorzystywane są wartości domyślne. ● Obiekty Przykład: <Sphere radius="1.0"> <Position x="-1.5" y="0.0" z="8.0" /> <Material id="Glass" /> </Sphere> <Plane distance="-1.0"> <Normal x="0.0" y="1.0" z="0.0" /> <Material id="Plane" /> </Plane> Aktualnie obsługiwane obiekty to kula i płaszczyzna. Wszystkie powyższe atrybuty są obowiązkowe. Atrybuty: ○ radius - promień kuli ○ Position - środek kuli (wektor) ○ Material - materiał kuli lub płaszczyzny ○ Normal - wektor normalny płaszczyzny ○ distance - odległość płaszczyzny od punktu (0, 0, 0) ● Światła Przykład: <Light type="Point"> <Position x="3.0" y="10.0" z="7.0" /> <Color id="White" /> </Light> <Light type="Ambient"> <Color id="DarkGray" /> </Light> Aktualnie obsługiwane światła są tylko dwa. Światło punktowe type="Point" i światło symulujące światło rozproszone type="Ambient". Światło ambient jest dodawane do obliczeń w każdym punkcie i nigdy nie jest zasłonięte. Atrybuty: ○ Position - wektor położenia światła punktowego ○ Color - kolor świecenia ● Kamera Przykład: <Camera FOV="45"> <Pos x="-2.0" y="3.0" z="-2.0" /> <Dir x="0.2" y="-0.3" z="1.0" /> <!-- <Top x="0.2" y="-0.3" z="1.0" /> --> </Camera> Atrybuty: ○ (Pos, Dir, Top, FOV) - czwórka opisująca kamerę. FOV jest podawany w stopniach. Definicja sceny musi znajdować się pomiędzy tagami <Scene>, </Scene>. Ponadto obsługiwane są następujące tagi: ○ ○ ○ <Dump /> - wyświetlający wszystkie zdefiniowane identyfikatory - również te wbudowane. <Background r="num" g="num" b="num" /> określający kolor tła <Atmosphere idx="współczynnik załamania światła w atmosferze" /> Poniżej znajduje się minimalny plik prezentujący scenę: <Scene> <Atmosphere idx="Air" /> <!-- Texture definitions --> <Texture id="CheckedTex" type="Checked"> <Color id="Black" /> <Color id="White" /> </Texture> <!-- Material definitions --> <Material id="RedMat" diffuse="Red" specular="White" reflect="Gray" refract="Black" shininess="20.0" /> <Material id="GreenMat" diffuse="Green" specular="White" reflect="Gray" shininess="20.0" /> <Material id="BlueMat" diffuse="Blue" specular="White" reflect="Gray" shininess="20.0" /> <Material id="CheckedMat" diffuse="CheckedTex" specular="Black" /> <!-- Scene objects --> <Sphere radius="1.0"> <Position x="-1.0" y="0.0" z="8.0" /> <Material id="RedMat" /> </Sphere> <Sphere radius="1.0"> <Position x="1.0" y="0.0" z="7.0" /> <Material id="GreenMat" /> </Sphere> <Sphere radius="1.0"> <Position x="1.0" y="0.0" z="10.0" /> <Material id="BlueMat" /> </Sphere> <Plane distance="-1.0"> <Material id="CheckedMat" /> <Normal x="0.0" y="1.0" z="0.0" /> </Plane> <!-- Scene lights --> <Light type="Point"> <Position x="-3.0" y="10.0" z="6.0" /> <Color id="White" /> </Light> <Light type="Ambient"> <Color r="0.1" g="0.1" b="0.1" /> </Light> <!-- Scene Camera --> <Camera FOV="45"> <Pos x="-2.0" y="3.0" z="-2.0" /> <Dir x="0.2" y="-0.3" z="1.0" /> </Camera> <Dump /> </Scene> Oraz efekt renderingu: 4. Specyfikacja wewnętrzna Dokumentacja wewnętrzna generowania za pomocą programu Doxygen dołączona jest w formatach html i pdf (ponad 100 stron opisu metod, pól klas oraz grafów dziedziczenia i wywołań funkcji). Z rzeczy, które pomimo, że są tam opisane warto przytoczyć jest wykorzystanie (po przerobieniu) szablonu explicit_t Michała Maleckiego. Dzięki wykorzystaniu w programie tylko typów zmiennych zdefiniowanych analogicznie: typedef explicit_t<double> Double; Udało się zablokować większość implicit conversions przez co znacząco został ustatyczniony system typów C++. Przykłady kodu, które nie powinny się skompilować można znaleźć w Testcases.cc, są to np.: ● Double c = 1; ● Double a = 1.0; Int b = a; 5. Testowanie W trakcie pisania w przestrzeni nazw Testcase tworzyłem funkcje, które miały za zadanie sprawdzać działanie aktualnie pisanych fragmentów programu. Funkcje te podczas programowania były uruchamiane wszystkie przy każdym uruchomieniu programu dzięki czemu ew. powstanie błędu, który wcześniej się nie ujawnił można było dość szybko wyłapać. Nierzadko kod testujący powstawał przed kodem właściwym wymuszając stworzenie właściwego API. Program powstawał więc niejako z wykorzystaniem technik TDD (Test-Driven Development). Po napisaniu działającej podstawy programu stworzyłem parę opisów scen 3D, na renderowaniu których testowałem program i znajdowałem pozostałe powstałe błędy. Sceny te są załączone do programu. Za pomocą programu valgrind przy wyłączonej bibliotece SDL sprawdzałem również czy program nie posiada wycieków pamięci. 6. Wnioski Raytracer to jeden z najprostszych programów służących do generowania grafiki trójwymiarowej. W internecie znajduje się dość sporo opisów w jaki sposób poszczególne fazy renderingu powinny zachodzić, choć było również wiele rzeczy, których nie znalazłem i musiałem sam wyprowadzać. Pomimo, że jest to program dość prosty w trakcie pisania wielokrotnie zastanawiałem się nad strukturą programu - czy na pewno przyjęte przeze mnie podejście jest najlepszym możliwym - i parę razy zmieniałem większe fragmenty kodu lub reorganizowałem jego strukturę. Wszystko po to by rozbudowa programu w przyszłości była prosta. Dla przykładu dodanie nowego obiektu do sceny (np. sześcianu) ograniczyło by się do zdefiniowania mapowania współrzędnych tekstur (UV), wykrywania kolizji z promieniami oraz wczytywania jego danych z pliku XML.