Raytracing: krok po kroku cz. 7 - doskonałe odbicie lustrzane

Podobne dokumenty
Raytracing: krok po kroku cz soft shading

Grafika Komputerowa Wykład 5. Potok Renderowania Oświetlenie. mgr inż. Michał Chwesiuk 1/38

Oświetlenie. Modelowanie oświetlenia sceny 3D. Algorytmy cieniowania.

GRK 4. dr Wojciech Palubicki

Raytracing: krok po kroku cz. 6 - model Phonga

Oświetlenie obiektów 3D

Oświetlenie w OpenGL. Oprogramowanie i wykorzystanie stacji roboczych. Wykład 8. Światło otaczajace. Światło rozproszone.

Grafika komputerowa. Model oświetlenia. emisja światła przez źródła światła. interakcja światła z powierzchnią. absorbcja światła przez sensor

Programowanie strukturalne i obiektowe. Funkcje

Jak zawsze wyjdziemy od terminologii. While oznacza dopóki, podczas gdy. Pętla while jest

Czym są właściwości. Poprawne projektowanie klas

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

OpenGL oświetlenie. Bogdan Kreczmer. Katedra Cybernetyki i Robotyki Wydziału Elektroniki Politechnika Wrocławska

Pętle. Dodał Administrator niedziela, 14 marzec :27

Model oświetlenia. Radosław Mantiuk. Wydział Informatyki Zachodniopomorski Uniwersytet Technologiczny w Szczecinie

Animowana grafika 3D. Opracowanie: J. Kęsik.

Lekcja : Tablice + pętle

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu.

OpenGL : Oświetlenie. mgr inż. Michał Chwesiuk mgr inż. Tomasz Sergej inż. Patryk Piotrowski. Szczecin, r 1/23

WSTĘP DO GRAFIKI KOMPUTEROWEJ

Informatyka I: Instrukcja 4.2

GRAKO: ŚWIATŁO I CIENIE. Modele barw. Trochę fizyki percepcji światła. OŚWIETLENIE: elementy istotne w projektowaniu

TEMAT : KLASY DZIEDZICZENIE

Celem tego projektu jest stworzenie

Zjawisko widzenia obrazów

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Wstęp do Informatyki zadania ze złożoności obliczeniowej z rozwiązaniami

8. Wektory. Przykłady Napisz program, który pobierze od użytkownika 10 liczb, a następnie wypisze je w kolejności odwrotnej niż podana.

Rekurencja (rekursja)

Grafika 3D program POV-Ray - 1 -

Systemy wirtualnej rzeczywistości. Komponenty i serwisy

Klasy i obiekty cz II

Informatyka II. Laboratorium Aplikacja okienkowa

Julia 4D - raytracing

6. Pętle while. Przykłady

Badanie przy użyciu stolika optycznego lub ławy optycznej praw odbicia i załamania światła. Wyznaczanie ogniskowej soczewki metodą Bessela.

C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów

Programowanie - wykład 4

C++ Przeładowanie operatorów i wzorce w klasach

programowanie w oparciu o platformę netbeans w praktyce

Następnie przypominamy (dla części studentów wprowadzamy) podstawowe pojęcia opisujące funkcje na poziomie rysunków i objaśnień.

Nazwa implementacji: Nauka języka Python wyrażenia warunkowe. Autor: Piotr Fiorek. Opis implementacji: Poznanie wyrażeń warunkowych if elif - else.

Rozdział 4 KLASY, OBIEKTY, METODY

Wskaźnik może wskazywać na jakąś zmienną, strukturę, tablicę a nawet funkcję. Oto podstawowe operatory niezbędne do operowania wskaźnikami:

Testy jednostkowe Wybrane problemy testowania metod rekurencyjnych

Zaawansowana Grafika Komputerowa

Podstawy Programowania Podstawowa składnia języka C++

Grafika 3D program POV-Ray

Światła i rodzaje świateł. Dorota Smorawa

Uniwersytet Zielonogórski Instytut Sterowania i Systemów Informatycznych. Ćwiczenie 3 stos Laboratorium Metod i Języków Programowania

Grafika Komputerowa. Metoda śledzenia promieni

Programowanie obiektowe

Klasa jest nowym typem danych zdefiniowanym przez użytkownika. Najprostsza klasa jest po prostu strukturą, np

6 Przygotował: mgr inż. Maciej Lasota

Pętle i tablice. Spotkanie 3. Pętle: for, while, do while. Tablice. Przykłady

Przykładowe zadanie z unikania blokad.

Synteza i obróbka obrazu. Algorytmy oświetlenia globalnego

1 Podstawy c++ w pigułce.

Weźmy wyrażenie. Pochodna tej funkcji wyniesie:. Teraz spróbujmy wrócić.

Lab 9 Podstawy Programowania

Luty 2001 Algorytmy (7) 2000/2001

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

PDF stworzony przez wersję demonstracyjną pdffactory

Wyszukiwanie binarne

Leszek Stasiak Zastosowanie technologii LINQ w

Wyznaczanie stałej słonecznej i mocy promieniowania Słońca

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki

Raytracing: krok po kroku

Podstawy programowania obiektowego

Aplikacje w środowisku Java

Programowanie w Baltie klasa VII

7. Pętle for. Przykłady

Gry komputerowe, Informatyka N1, III Rok

Bartosz Bazyluk POTOK RENDEROWANIA Etapy renderowania w grafice czasu rzeczywistego. Grafika Komputerowa, Informatyka, I Rok

WSNHiD, Programowanie 2 Lab. 2 Język Java struktura programu, dziedziczenie, abstrakcja, polimorfizm, interfejsy

2. Zmienne i stałe. Przykłady Napisz program, który wypisze na ekran wynik dzielenia 281 i 117 w postaci liczby mieszanej (tj. 2 47/117).

MODELE OŚWIETLENIA. Mateusz Moczadło

Materiały. Dorota Smorawa

Wykład 4: Klasy i Metody

Wykład 5: Klasy cz. 3

Raytracer. Seminaria. Hotline. początkujący zaawansowani na miejscu

Podstawą w systemie dwójkowym jest liczba 2 a w systemie dziesiętnym liczba 10.

Programowanie obiektowe

Warto też w tym miejscu powiedzieć, że w C zero jest rozpoznawane jako fałsz, a wszystkie pozostałe wartości jako prawda.

Podłączanie bibliotek Zapis danych do pliku graficznego Generowanie promienia pierwotnego Import sceny z pliku Algorytm ray tracingu

Tablice cz. I Tablice jednowymiarowe, proste operacje na tablicach

PROE wykład 2 operacje na wskaźnikach. dr inż. Jacek Naruniec

Programowanie obiektowe i zdarzeniowe

Aleksandra Zając. Raport. Blender. Pokemon: Eevee

REKURENCJA W JĘZYKU HASKELL. Autor: Walczak Michał

Wykład 17: Optyka falowa cz.2.

Dlaczego make-up jest niezbędny do zdjęć? Wiemy, jak tego uniknąć. Nasz specjalista od makijażu przygotuje Cię do zdjęć.

Grafika realistyczna. Oświetlenie globalne ang. global illumination. Radosław Mantiuk

Część XV C++ Ćwiczenie 1

Algorytmy oświetlenia globalnego

Programowanie Obiektowe Ćwiczenie 4

Klimat na planetach. Szkoła Podstawowa Klasy VII-VIII Gimnazjum Klasa III Doświadczenie konkursowe 2

Dziedziczenie. Streszczenie Celem wykładu jest omówienie tematyki dziedziczenia klas. Czas wykładu 45 minut.

Zasada Fermata mówi o tym, że promień światła porusza się po drodze najmniejszego czasu.

Przekształcanie wykresów.

Transkrypt:

Raytracing: krok po kroku cz. 7 - doskonałe odbicie lustrzane I. Co/Dlaczego? Jako że dodawanie kolejnych materiałów jest jednocześnie dość proste i powoduje szybkie efekty, spróbujemy w tej części pójść dalej w tym kierunku - ale prędzej czy później będzie trzeba wrócić do trudniejszych i bardziej nużących tematów... Więc co tym razem? Stworzymy materiał potrafiący odbijać światło. Koniec z czasami kiedy wygląd obiektu był niezależny od reszty świata - pojawią się teraz odbicia lustrzane pozwalające na nowe efekty. Materiał którym się zajmiemy będzie doskonałym materiałem lustrzanym odbijającym promienie bez najmniejszego rozproszenia. Nie jest to oczywiście rzecz możliwa w prawdziwym świecie, ale ostatecznie to samo dotyczy materiałów perfekcyjnie rozpraszających i modelu Phonga. II. Teoria Do tego momentu kolor cieniowanego punktu był zależny tylko od światła padającego na niego w prostej linii ze źródła - jest to tzw. direct illumination - bezpośrednie oświetlenie, jako że światło dociera do punktu bezpośrednio. W tym momencie spotykamy się z indirect illumination - światło odbite od innych obiektów. Ostateczny kolor jest sumą direct i indirect illumination. L( p, ω) = L ( p, ω) L ( p, ω) direct + Żeby wyliczyć kolor pokrytego lustrzanym materiałem punktu, musimy zsumować te dwa czynniki. Wyliczenie oświetlenia bezpośredniego przyjdzie nam łatwo - skorzystamy z kodu napisanego w poprzedniej części. Pytanie na dzisiaj brzmi - jak wyliczyć dla naszego materiału oświetlenie pośrednie? indirect 1

Można zauważyć że wektor odbitego światła L jest wektorem wchodzącego światła pośredniego I odbitym od normalnej powierzchni w tym punkcie - jako że znamy wektor odbitego światła (jest to wektor przeciwny do kierunku naszego promienia) i znamy normalną powierzchni, daje nam to prosty sposób na znalezienie wektora wchodzącego światła - postępujemy w odwrotny sposób i odbijamy wektor odbitego światła od normalnej. Do czego nam wektor wchodzącego światła? Możemy kontynuować robienie wszystkiego na opak, i śledzić wchodzący promień żeby zobaczyć z jakiego kierunku promień do nas przyszedł. A kiedy już wiemy skąd promień nadszedł... Możemy obliczyć jego jasność względem cieniowanego (lustrzanego) punktu w taki sam sposób jak obliczamy jego jasność względem kamery (obliczyć ile światła odbija w kierunku cieniowanego, pierwotnie punktu). Dzięki temu otrzymamy współczynnik pośredniego oświetlenia i skończymy z materiałem odbijającym światło. III. Zmiany Zanim zaczniemy kombinować z odbiciem, musimy zrobić coś jeszcze. Pamiętacie część mówiącą o cieniowaniu? Dodaliśmy tam między innymi taką pętlę (w klasie Raytracer): foreach (var light in world.lights) if (world.anyobstaclebetween(info.hitpoint, light.position)) continue; finalcolor += material.radiance(light, info); Jest to ładny, czytelny kod i dobrze oddaje nasze intencje. Niestety - czas się za wstydem przyznać - to było podwójne oszustwo: Założyliśmy że jeśli do jakiegoś punktu nie dochodzi światło bezpośrednio, ten punkt z pewnością jest zupełnie nieoświetlony (czarny) - w przypadku materiału lustrzanego to już nieprawda, ponieważ materiał może być oświetlony wyłącznie za pomocą światła odbitego. Założyliśmy że możemy obliczyć jasność punktu sumując wkład poszczególnych świateł - co jest nieprawdą, ponieważ czynniki takie jak odbicie liczymy tylko raz. Co więc z tym zrobimy? Niestety, wymienimy siekierkę na kijek, piękny kod na copy&paste. Każdy materiał będzie musiał osobiście zajmować się sprawdzaniem wszystkich świateł po kolei i sumowaniem ich wkładu. 2

Interfejs IMaterial będzie się musiał mocno zmienić: interface IMaterial ColorRgb Shade(Raytracer tracer, HitInfo hit); Jak widać, nie ma już parametru `light` (światło którego wkład liczymy), za to pojawił się `tracer` (o nim napiszemy później - na razie niech tam siedzi). Zmienią się w związku z tym klasy PerfectDiffuse i Phong - w obydwóch trzeba dodać pętlę foreach iterującą po światłach w świecie (przekazywanym wśród parametrów). Czyli poprzedni kod który można streścić mniej-więcej tak: ColorRgb result; /* obliczenia */ return result; Zmieni się na taki: ColorRgb totalcolor = ColorRgb.Black; foreach (var light in hit.world.lights) ColorRgb result; /* obliczenia */ totalcolor += result; return totalcolor; Dla kompletności, poniżej podany jest kompletny, zmieniony kod: klasa PerfectDiffuse: public ColorRgb Shade(Raytracer tracer, HitInfo hit) ColorRgb totalcolor = ColorRgb.Black; foreach (var light in hit.world.lights) Vector3 indirection = (light.position - hit.hitpoint).normalized; double diffusefactor = indirection.dot(hit.normal); if (diffusefactor < 0) continue; if (hit.world.anyobstaclebetween(hit.hitpoint, light.position)) continue; totalcolor += light.color * materialcolor * diffusefactor; return totalcolor; klasa Phong: public ColorRgb Shade(Raytracer tracer, HitInfo hit) ColorRgb totalcolor = ColorRgb.Black; foreach (var light in hit.world.lights) Vector3 indirection = (light.position - hit.hitpoint).normalized; double diffusefactor = indirection.dot(hit.normal); 3

if (diffusefactor < 0) continue; if (hit.world.anyobstaclebetween(hit.hitpoint, light.position)) continue; ColorRgb result = light.color * materialcolor * diffusefactor * diffusecoeff; double phongfactor = PhongFactor(inDirection, hit.normal, -hit.ray.direction); if (phongfactor!= 0) result += materialcolor * specular * phongfactor; totalcolor += result; return totalcolor; Skromnym zdaniem autora, nowa wersja jest dużo brzydsza od poprzedniej, ale będziemy się z tym musieli nauczyć żyć (reszta pisanego dzisiaj kodu będzie już schludniejsza). Jedyną zaletą dokonanych zmian jest to, że wspomniana pętla: foreach (var light in world.lights) if (world.anyobstaclebetween(info.hitpoint, light.position)) continue; finalcolor += material.radiance(light, info); return finalcolor; Zamienia się w proste return material.shade(this, info); III. Implementacja Pisanie materiału modelującego odbicie (nazwiemy go Reflective) zaczniemy jak zwykle od napisania szkieletu klasy: class Reflective : IMaterial Phong direct; // do bezpośredniego oświetlenia double reflectivity; ColorRgb reflectioncolor; public Reflective(ColorRgb materialcolor, double diffuse, double specular, double exponent, double reflectivity) this.direct = new Phong(materialColor, diffuse, specular, exponent); this.reflectivity = reflectivity; this.reflectioncolor = materialcolor; public ColorRgb Shade(Raytracer tracer, HitInfo hit) throw new System.NotImplementedException(); Jak zwykle - kolejny materiał, więcej parametrów w konstruktorze. MaterialColor to podstawowy kolor materiału i nie wymaga raczej żadnych wyjaśnień. Diffuse, specular i exponent są już znane - oznaczają odpowiednio jaka część światła jest przez obiekt rozpraszana, jak silne są specular highlights, oraz jaki wykładnik jest użyty do ich modelowania. Reflectivity oznacza jak mocno materiał odbija światło - przy wartości 0 nie ma żadnych odbić i materiał Reflective 4

jest identyczny do Phonga. Przy wartości 1 (i diffuse równym 0) materiał jest idealnym lustrem i odbija całe padające na niego światło. Współczynniki diffuse i reflectivity powinny zawsze się sumować maksymalnie do 1 - ponieważ część światła jest przez obiekt rozpraszana, część doskonale odbijana, ale nigdy więcej światła nie opuści obiekt niż na niego pada. Nie jest to wymuszone w kodzie, ale lepiej o tym pamiętać - w przeciwnym wypadku wyrenderowane sceny mogą wyjść nienaturalnie jasne. Dzięki polu `direct` całe obliczenia dla światła bezpośredniego wykonamy jedną linijką: ColorRgb radiance = direct.radiance(light, hit); Oświetlenie bezpośrednie załatwione. I co dalej? Do wyliczenia odbicia, potrzebujemy informacji skąd wpada odbijane światło. Możemy się tego dowiedzieć, jak było wspominane, odbijając wektor światła odbitego (liczymy światło odbijające się w kierunku kamery, więc bierzemy odwrotność kierunku promienia który idzie od kamery) od normalnej: Vector3 tocameradirection = -hit.ray.direction; Vector3 reflectiondirection = Vector3.Reflect(toCameraDirection, hit.normal); Ray reflectedray = new Ray(hit.HitPoint, reflectiondirection); Znając kierunek (reflectiondirection) z którego przychodzi odbijane światło, możemy stworzyć promień (reflectedray) w tamtym kierunku i `śledzić` go. tracer.shaderay(hit.world, reflectedray); Ale musimy wziąć pod uwagę to, że promień może teoretycznie odbijać się w nieskończoność od powierzchni (na przykład wewnątrz lustrzanej sfery) - dlatego musimy ograniczyć ilość odbić do jakiejś, ustalonej z góry wartości. Trzeba w jakiś sposób śledzić informację o poziomie rekurencji w jakim się znajdujemy podczas cieniowania punktu. Do przechowywania takich informacji przystosowana jest klasa HitInfo, dlatego dorzucimy jej jeszcze jedno pole: /// <summary>zwiększana przy śledzeniu odbitego lub załamanego promienia</summary> public int Depth get; set; Zmodyfikujemy lekko funkcję Raytracer.ShadeRay żeby brała pod uwagę możliwość zapędzenia się w rekurencji: public ColorRgb ShadeRay(World world, Ray ray, int currentdepth) if (currentdepth > maxdepth) return ColorRgb.Black; 5

HitInfo info = world.traceray(ray); info.depth = currentdepth + 1; if (info.hitobject == null) return world.backgroundcolor; IMaterial material = info.hitobject.material; return material.shade(this, info); Zmienna `maxdepth` nie weźmie się znikąd niestety, więc musimy ją zadeklarować i przypisać wartość w konstruktorze: class Raytracer int maxdepth; public Raytracer(int maxdepth) this.maxdepth = maxdepth;... W metodzie Main, zmieniamy tworzenie tracera na: Raytracer tracer = new Raytracer(5); W samym tracerze, usunęliśmy wersję metody ShadeRay nie pobierającą parametru currentdepth (zapomnienie o dodaniu tego parametru mogłoby mieć fatalne skutki), więc musimy zmienić linijkę kodu w funkcji Raytrace: bmp.setpixel(x, y, StripColor(ShadeRay(world, ray, 0))); Teraz możemy w końcu prześledzić odbity promień pisząc: tracer.shaderay(hit.world, reflectedray, hit.depth); Co jeszcze może wpływać na jasność odbitego światła? Może czynnik lamberta? Otóż okazuje się że nie, nie w tym przypadku: 6

Przekrój przez strumień promieni wchodzących jest równy przekrojowi promieni odbitych - oznacza to że gęstość strumienia promieni odbitych nie zależy od kąta padania. Teraz możemy w końcu wyliczyć odbite światło: ColorRgb reflected = tracer.shaderay(hit.world, reflectedray, hit.depth) * reflectioncolor * reflectivity; I połączyć wszystko w całość: public ColorRgb Shade(Raytracer tracer, HitInfo hit) Vector3 tocameradirection = -hit.ray.direction; ColorRgb radiance = direct.shade(tracer, hit); Vector3 reflectiondirection = Vector3.Reflect(toCameraDirection, hit.normal); Ray reflectedray = new Ray(hit.HitPoint, reflectiondirection); radiance += tracer.shaderay(hit.world, reflectedray, hit.depth) * reflectioncolor * reflectivity; return radiance; Pomijając zmiany wprowadzone w innych klasach, kodu wykonującego odbicie jak widać nie jest dużo. IV. Wyniki Wszystkie przykładowe sceny wyrenderowane oczywiście za pomocą tylko i wyłącznie dotychczas napisanego kodu. Nareszcie mamy coś czym można się ewentualnie komuś pochwalić: Przy odrobinie wyobraźni można bez problemu stworzyć robiące wrażenie, niemożliwe w prawdziwym świecie sceny (czy kiedykolwiek zastanawiałeś się co się stanie jeśli ustawisz dwa lustra naprzeciwko siebie?). A jeśli ustawimy kilka kul, oświetlimy je i przykryjemy odbijającą światło sferą? 7

Ciekawostka - jeśli połączymy cztery kule krawędziami i dodamy odpowiednie świato, otrzymamy pewien znany fraktal: I na sam koniec - jak prezentuje się nasza ulubiona scena? 8

Zmodyfikujmy materiały obiektów na scenie: IMaterial redmat = new Reflective(Color.LightCoral, 0.4, 1, 300, 0.6); IMaterial greenmat = new Reflective(Color.LightGreen, 0.4, 1, 300, 0.6); IMaterial bluemat = new Reflective(Color.LightBlue, 0.4, 1, 300, 0.6); IMaterial graymat = new Reflective(Color.Gray, 0.4, 1, 300, 0.6); A oto efekt: Całkiem nieźle w porównaniu z poprzednią wersją... Niestety - tak jak groziłem - następna część będzie nudniejsza i nie przyniesie tak rzucających się w oczy rezultatów - ale bez niej wiele byśmy stracili... 9