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.