Artykuł o raytracingu

16

Ave!

Ostatnio mnie trochę wzięło na napisanie czegoś ambitnego więc spróbowałem swoich możliwości stworzenia czegoś dłuższego niż kilkanaście linijek w języku nie służącym do programowania.

Raytracing: Spis treści

No ale na lekcjach polskiego się nie uważało i nie jestem specjalnie pewien swoich zdolności literackich - więc byłbym wdzięczny za jakąś krytykę/wskazanie rzeczy do poprawienia...

Z góry dzięki ;)

1

Nigdy nie miałem najmniejszej styczności z programowaniem grafiki.
Bardzo przyjemny artykulik.

1

myślałem, że biblioteke w brainfucku++ napisałeś :D dobra robota!

2

Nawet fajne, ale brakuje jeszcze ogólnego wzoru na kolizję promienia z wielokątem.

1

Jeszcze o GI napisz :D Nie no, żartuje :P
Ja z grafiką 3D miałem już trochę wspólnego, ale nie od strony programistycznej - w wolnej chwili chętnie przeczytam, na razie pobieżnie przejrzałem.

1

Jeśli już musisz robić w raytraycingu grafikę 2D to chociaż zrób jakieś fajne kolory...

Np. przy pomocy "triady" na stronie:
http://colorschemedesigner.com/

1

Fajnie się czyta i wszystko ładnie wyjaśnione. Brakuje mi na końcu jakiś informacji, co dalej. Mogłeś wspomnieć - hasłowo - o jakiś strukturach przyspieszających, algorytmach kolizji z trójkątem, cieniowaniu, teksturowaniu, światłach itd. A także podać może jakieś linki do innych artykułów np. tego http://www.flipcode.com/archives/Raytracing_Topics_Techniques-Part_1_Introduction.shtml. Ale tak ogólnie to dobra robota;).

0
vpiotr napisał(a)

Jeśli już musisz robić w raytraycingu grafikę 2D to chociaż zrób jakieś fajne kolory...

Kto mówi o grafice 2D - obiekty i obliczenia są przeprowadzane w 3D - po prostu 'renderowanie' obecnie jest dość ubogie.
A co do kolorów - na razie zostanę przy R, G i B, później będą minimalnie zmodyfikowane (monochromatyczne kolory (bardzo) kiepsko wyglądają przy próbach obsłużenia odbić i przezroczystości). Uwierz że później to całkiem nieźle wychodzi.

xxx_xx_x napisał(a)

Nawet fajne, ale brakuje jeszcze ogólnego wzoru na kolizję promienia z wielokątem.

Bryłą, z tą grafiką 3D to pisałem poważnie :] Mogę dopisać kolizję i obliczenia związane z trójkątem. Obecnie następną rzeczą którą zamierzam wysłać jest płaszczyzna, może to Ci się spodoba :>.

krwq napisał(a)

myślałem, że biblioteke w brainfucku++ napisałeś :D dobra robota!

Ciągle mam komuś obiecane napisanie kółka i krzyżyk na planszy dowolnej wielkości w Brainfucku - nie myślcie że zapomniałem!

naczelny szaleniec 4p napisał(a)

czekamy na kontynuację :)

pozytywista napisał(a)

Fajnie się czyta i wszystko ładnie wyjaśnione. Brakuje mi na końcu jakiś informacji, co dalej. Mogłeś wspomnieć - hasłowo - o jakiś strukturach przyspieszających, algorytmach kolizji z trójkątem, cieniowaniu, teksturowaniu, światłach itd.

W sumie mam napisane - ale w stanie dość szczątkowym, wymagającym pewnie kilku poprawek - sześć tekstów na ten temat. Tak więc będzie 'dalej', nie tylko hasłowo. Mimo posiadania pewnego zapasu zamierzam wrzucać coś raz na jakieś trzy dni (żeby wyrobić się z pisaniem) - ale życie i obowiązki to mogą zweryfikować :<. Jeśli to sugerowałeś, spróbuję dodawać pod koniec każdego artykułu jakieś co w następnym odcinku.

Jedyne co mnie martwi to fakt że początki przy pisaniu raytracera są dość przydługawe i zanim się dojdzie do jakichś efektów trzeba grzebać w kodzie - wydawałoby się - bez efektów.

Hmm, to dzięki za odezwanie się, czuję się zmotywowany do dalszych prób :>.
Ale mimo wszystko jakaś (konstruktywna) krytyka dot. rzeczy do poprawienia jest mile widziana bo nie łudzę się co do moich możliwości pisarskich...

1

Jestem ogromnym zwolennikiem programowania "po angielsku", a raczej "po amerykańsku". Stąd, napisałbym normalized, a nie normalised.

Dodatkowo zastanowiłbym się czy nie zrezygnować z automatycznych właściwości w strukturach. Wymagają one wywołania domyślnego konstruktora, który wyzeruje backing fields. Ty i tak ustawiasz je wszystkie we własnym konstruktorze, więc dochodzi do pewnej redundancji. Kompilator x86 potrafi to zoptymalizować, ale x64 już nie i na moim sprzęcie utworzenie takiej struktury jest o 60% wolniejsze.
Wiadomo od dawna, że kompilator x64 trochę posysa i w zasadzie niewielu z niego korzysta, no, ale warto to wziąć pod uwagę.

0

Jestem ogromnym zwolennikiem programowania "po angielsku", a raczej "po amerykańsku". Stąd, napisałbym normalized, a nie normalised.

Good point, szczerze mówiąc nawet tego nie zauważyłem... Trzeba będzie chyba wszystko poprawić :(.

Dodatkowo zastanowiłbym się czy nie zrezygnować z automatycznych właściwości w strukturach.

Proponujesz publiczne pola? Szczerze mówiąc właściwości w tym miejscu nie mają żadnych specjalnych zalet (X, Y i Z się przecież raczej nie zmieni...) i zastanawiałem się nad tym (tym bardziej że nawet MS swoje standardy w tym względzie czasami ignoruje, np. klasa Vector z XNA) - ostatecznie uznałem że premature optimization itd, ale jeśli różnica to 60% to chyba warto to rozważyć...

1

Nie tyle co publiczne pola, ale właściwości z własnymi backing fields (snippet propfull w VS). Akurat w tym przypadku oba kompilatory radzą sobie dość dobrze i właściwości z prostymi getterami (return x) i setterami (x = value) nie powodują żadnego narzutu (musiałem sprawdzić w słowniku tłumaczenie słowa overhead na polski...).

1
msm napisał(a):
xxx_xx_x napisał(a)

Nawet fajne, ale brakuje jeszcze ogólnego wzoru na kolizję promienia z wielokątem.

Bryłą, z tą grafiką 3D to pisałem poważnie :] Mogę dopisać kolizję i obliczenia związane z trójkątem. Obecnie następną rzeczą którą zamierzam wysłać jest płaszczyzna, może to Ci się spodoba :>.

Nie wiem czy już to wiesz, ale bryły 3d składają się zwykle z wielokątów umieszczonych w przestrzeni 3D. Żeby sprawdzić czy trafiłeś jakąś bryłę (i gdzie) zwykle sprawdza się

  1. Czy promień trafia prymitywną najmniejszą bryłę, w której opisany jest obiekt, np. dla człowieka jest to sfera
  2. Jeżeli tak to wtedy sprawdzasz kolizję z konkretną ścianą w 3d, która jest wielokątem np właśnie trójkątem(najczęściej)

Kolizje z wielokątem sprawdza sie natomiast w taki sposób:(zakładając że punkty leżą na jednej płaszczyźnie tak jak być powinno:p)

  1. Obliczasz równanie płaszczyzny : Ax + By + Cz + D = 0 => Np + D = 0 gdzie N to wektor normalny p to punkt na płaszczyźnie a D to chyba wiadomo.
  2. mając promień l0(x,y,z), l1(x,y,z) obliczasz v0 = N * l0 + D oraz v1 = Nl1 + D i jeżeli v0v1 <= 0 to zaszło przecięcie z płaszczyzną i trzeba policzyć miejsce przecięcia i sprawdzić czy leży w wielokącie.
  3. Obliczenie punktu przecięcia to znowu intersect = l0 + (l1-l0) * ((p - l0) dot N ) / ( (l1-l0) dot N) wszystkie zmienne jak w pkt 2
  4. Zostaje sprawdzić czy intersect leży wewnątrz trójkąta (tutaj jest sporo algorytmów trzeba jakiś wybrać)
2

Mimo starań i, słusznych zresztą, sugestii vpiotra nie udało się wepchnąć materiałów i oświetlenia wcześniej :<. Dalej jest nudno i trzeba wierzyć na słowo że coś widać ale to się za chwilę (w następnej części) zmieni... Dlatego dręczony wyrzutami sumienia dodaję dwie części jednocześnie.
Zresztą zbyt dużo tego nie jest - pierwszy artykuł zajmuje 4 strony a drugi 2...

Część II - realistyczna kamera
Część III - płaszczyzna

PS. Jest sens tworzyć i utrzymywać wersję Coyotową (jak http://4programmers.net/Z_pogranicza/Raytracing_3) każdego artykułu? Zajmuje to (relatywnie) sporo czasu a efekt jest mizerny... Można by było stworzyć jedną stronę ze spisem treści linkującą tylko do plików pdf...

3

Czytam sobie właśnie pierwszą część i miałbym taką uwagę.
Razi trochę, przynajmniej mnie, sformułowanie "nie trzeba rozumieć tego co dzieje się w tej funkcji". Zawsze jak widzę coś takiego w tutorialach/poradnikach/artykułach to mam wrażenie, że autor sam nie wie co się tam dzieje na tyle dobrze, żeby to przystępnie wytłumaczyć. Żadną miarą nie twierdzę, że Ty tego nie wiesz - ale myślę, że lepiej w takich wypadkach jednak spróbować wyjaśnić problem lub jakoś ukierunkować czytelnika, niż rzucać mu, że nie musi wiedzieć co się tam dzieje ;-)
Poza tym dobrze napisane, miło się czyta. ;)

"Pokazuje to ilustracja przygotowana przez profesjonalnego grafika" +1

0

Machnąłem spis treści nareszcie: http://4programmers.net/Z_pogranicza/Raytracing_Spis_Tresci
Graficznie może nie powala, ale swoje zadanie spełnia. Za to raczej zarzuciłem pomysł prób przepisanie pdf na html żeby z nich robić osobne artykuły - ładnie to i tak nie wygląda a zajmuje sporo czasu...

Tak czy inaczej, wysłałem kolejną część (wczoraj zamiast leżeć na plaży rysowałem jakieś ilustracje długopisem na kartce, skanowałem i przeklinałem GIMP-a :( ):

Część IV - światło
Tym razem 12 stron.

Dodatkowo kilka kosmetycznych poprawek w części I.

1

W komentarzu się nie zmieściło ;/

Tak mi się teraz rzuciło jak czytałem poprawioną wersję: "zbiór mandelbrota" - żeby być w zgodzie z językiem, to M powinno być wielki, bo to od nazwiska ;)
Jeszcze dopytać chciałem - rozwiązując równanie kwadratowe w kodzie używasz "t < ray.Epsilon", żeby sprawdzić, czy odległość od punktu przecięcia jest dodatnia - bardziej oczywiste będzie chyba zwykłe "t<0", tak jak wcześniej robisz z wyróżnikiem? Jeśli jestem w błędzie, to będę wdzięczny za poprawienie ;) A, jeszcze jedno - jaką licencją jest objęty ten kod / "algorytm" programu? Znaczy się, czy będę grzeszył przeciw prawu, Tobie, Bogu lub czemukolwiek innemu, jeśli wrzucę kiedyś na swoją stronę moją implementację programu w C++ z użyciem QT - rzecz jasna z podaniem namiarów na tekst źródłowy.

0

Tak mi się teraz rzuciło jak czytałem poprawioną wersję: "zbiór mandelbrota" - żeby być w zgodzie z językiem, to M powinno być wielki, bo to od nazwiska ;)

Ech, ja i znajomość języka ojczystego - nie wiem dlaczego nagle zacząłem pisać nazwiska z małej litery...

Jeszcze dopytać chciałem - rozwiązując równanie kwadratowe w kodzie używasz "t < ray.Epsilon", żeby sprawdzić, czy odległość od punktu przecięcia jest dodatnia - bardziej oczywiste będzie chyba zwykłe "t<0", tak jak wcześniej robisz z wyróżnikiem? Jeśli jestem w błędzie, to będę wdzięczny za poprawienie

Ha, tym razem mnie nie zagiąłeś!
Porównaj sobie obrazek wyrenderowany z t < epsilon :
user image

Z obrazkiem wyrenderowanym z t < 0:
user image
:]

Hmm, ale w sumie faktycznie nie napisałem dlaczego - na usprawiedliwienie powiem że to dlatego że po prostu w Twoim przypadku na razie to faktycznie nie ma znaczenia.
Ale kiedy ma? Otóż prędzej czy później dojdę do czegoś takiego jak odbicia i inne fajne zabawy ze światłem - wtedy okazuje się że promień zaczynamy śledzić ze ściany obiektu - w takim przypadku kiedy t matematycznie równa się dokładnie 0, sprawdzanie sprowadza się do porównanie dwóch wielokrotnie mnożonych i dodawanych liczb zmiennoprzecinkowych - zamiast się bawić można by tam dać rand()%2 i działałoby tak samo ;)

I co, znowu trzeba poprawiać artykuł :>

;) A, jeszcze jedno - jaką licencją jest objęty ten kod / "algorytm" programu? Znaczy się, czy będę grzeszył przeciw prawu, Tobie, Bogu lub czemukolwiek innemu, jeśli wrzucę kiedyś na swoją stronę moją implementację programu w C++ z użyciem QT - rzecz jasna z podaniem namiarów na tekst źródłowy

Tekst + kod na http://creativecommons.org/licenses/by-nc/3.0/pl/, ale jeśli Ci z jakiegoś specjalnego powodu zależy na mówieniu że kod to twoje dzieło (nie wiem, pochwalić się przed dziewczyną czy coś) to możemy nawet uzgodnić przeniesienie kodu na http://pl.wikipedia.org/wiki/WTFPL ;)

1

2 część.
1.

Dowolna orientacje względem kierunku patrzenia (reprezentowany za pomocą wektora up wzkazującego w górę).

  • orientacja, reprezentowanego (albo ", który jest reprezentowany...")
    #Skoro klasa kamery z drugiej części nazywa się PinholeCamera, to może niech ta z pierwszej zwie się OrthogonalCamera - albo niech mianem tej pierwszej będzie samo Pinhole, żeby trzymać się jednej ścieżki nazewnictwa?

PS Jak ktoś ma pomysł (tak bez kodu, bo dużo tego do wrzucania, może sam znajdę jakiś błąd), dlaczego moje kulki z końca drugiego rozdziału mają postać taką http://dl.dropbox.com/u/40441161/pierwszytrack.bmp postać, to się nie obrażę za info :D Zwłaszcza, że w kodzie nigdzie nie definiowałem czarnego koloru, który pojawia się na skrajnych lewo-górnych pikselach.

0

Sorry za duże opóźnienie...

#Dowolna orientacje względem kierunku patrzenia (reprezentowany za pomocą wektora up wzkazującego w górę).- orientacja, reprezentowanego (albo ", który jest reprezentowany...")
#Skoro klasa kamery z drugiej części nazywa się PinholeCamera, to może niech ta z pierwszej zwie się OrthogonalCamera - albo niech mianem tej pierwszej będzie samo Pinhole, żeby trzymać się jednej ścieżki nazewnictwa?

Jesteś świetny z tym szukaniem błędów (btw. wzkazującego?!?). ;) Widzę że za dużo ich przepuszczam, trzeba będzie dokładniej czytać przed wypuszczeniem :<.

Z pierwszym jest ciekawie, bo to wskazywany odnosi się do słowa 'parametr' kilka linijek wyżej :> Wywalę tą 'reprezentację' po prostu, i tak nic nie wnosi...

Drugie - na początku miałem PinholeCamera, OrthogonalCamera, PerfectDiffuseMaterial itd, itd - ale szybko zdecydowałem że to bezsensowne pisanie i wywaliłem sufiksy z kodu i tekstu. Teraz pytanie - dlaczego to przeszło...?
EDIT - już wiem dlaczego to przeszło, niepotrzebnie duplikowałem pdf-y na dysku. Hmm...

PS Jak ktoś ma pomysł (tak bez kodu, bo dużo tego do wrzucania, może sam znajdę jakiś błąd), dlaczego moje kulki z końca drugiego rozdziału mają postać taką http://dl.dropbox.com/u/40441161/pierwszytrack.bmp postać, to się nie obrażę za info :D Zwłaszcza, że w kodzie nigdzie nie definiowałem czarnego koloru, który pojawia się na skrajnych lewo-górnych pikselach.

IMO to wygląda jakbyś miał za duże kule ustawione (albo za blisko np.) - w moim przypadku (kontrola wersji FTW) kod z wersji 2 z kulami o wielkości 40 wygląda tak:
user image

Btw - jeśli jesteś załamany ilością poprawek w tekście inspirowanych przez Ciebie i boisz się że kod też jest dziurawy - o ile tekst to inna sprawa, nie po to zostałem programistą żeby pisać po polsku, z kodem jest inaczej. Zanim wyślę artykuł, cały kod z artykułu implementuje w osobnym, dedykowanym do tego projekcie (to niezbyt dużo roboty, kilka copy&paste) i sprawdzam czy wszystko działa. No i mogę w każdej chwili sprawdzić jak wyglądał kod w momencie wysyłania dowolnego artykułu.
Tak tylko piszę po części żeby się pochwalić a po części żebyś, jeśli coś nie działa, szukał błędu w artykule na ostatnim miejscu :]

EDIT 2 - poprawiłem pierwszą i drugą część.

0

W związku z wysłaniem kolejnej części mam pretekst do kolejnego podbicia tego wątku:
Część IV - światło

W sumie 4 strony niezbyt gęstego tekstu...

1

user image
Zaczyna ten raytracer już jakoś sensownie wyglądać ;)

0

Jeśli to nie dział Grafika (bo go nie ma) to może chociaż C#... ale na pewno nie off-topic...

Fajnie tak obserwować jak obrazek jest udoskonalany krok po kroku.

0

Straciłem już wszelką nadzieję, że uda mi się znaleźć błąd w swoim kodzie implementującym tego raytracera w C++. Stąd moja prośba, do kogokolwiek, kto zechciałby poświęcić klika minut i spróbować dostrzec jakiś błąd, który mi umyka.
Dla kamery ortogonalnej wszystko jest tak jak być powinno, otrzymuję ładne trzy kulki. Niestety, dla kamery perspektywicznej trzy kulki zamieniają się w user image
Przepisałem dziś na nowo te dwie klasy z 2 części - bazę ortogonalną i kamerę perspektywiczną, bo myślałem, że może gdzieś popełniłem jakąś kretyńską literówkę, której nie mogę zauważyć - ale nie, nadal wynik jest ten sam. Tak więc jestem w kropce - kod dotyczący pierwszej części wydaje się działać, kod z drugiej wydaje się być napisany zgodnie z wytycznymi artykułu, ale nie działa.

Link do projektu (C++ i QT)
http://dl.dropbox.com/u/40441161/MyRayTracer.zip

Proszę o pomoc, bo nie mam już pomysłu, co może być nie tak, a chciałbym się zabrać sobie za dalszą część ;-/

2
    origin = origin;
    distance = distance;

Tutaj nie trzeba aby zrobić czegoś a'la:

this->origin = origin;
0

Punkt dla Ciebie - pochrzaniłem sobie konwencję nazewnictwa i nie dałem pól klasy dużymi literami, jak we wcześniejszych. Kretyński błąd. Jeślibyś kiedyś wylądował na północno-wschodnich krańcach Polski, zgłoś się po piwo ;-)
Po poprawieniu mam http://scr.hu/1j3/jfaxe ,więc progres jest. Szukam dalej błędu.
Pola klasy Pinhole dałem wielkimi literami, podobnie dla utrzymania porządku pola U,V,W w klasie OrthonolmalBasis, więc teraz ten konstruktor ma postać

Pinhole::Pinhole(Vector3 origin, Vector3 lookAt, Vector3 up, double distance)
{
    Onb = OrthonormalBasis(origin, lookAt, up);
    Origin = origin;
    Distance = distance;
}
1

Bez sensu taka konwencja. Zapoznaj się z const-correctness - przydaje się do wyłapywania błędów. W twoim przypadku użyłbym takich samych nazw pól i parametrów, notacji this->coś = coś, dodał modyfikator const dla parametrów funkcji/ konstruktorów - żeby uniknąć przypadkowych przypisań do parametrów.

2

@Jadeszek:

Jak napisane wcześniej - stosuj const, do tego referencje, czyli

W sphere.h / .cpp

zamiast

bool HitTest(Ray ray, double& minDistance);

powinno być:

bool HitTest(const Ray &ray, double& minDistance) const;

const nr 1 - po to żeby zaznaczyć że parametr "ray" jest tylko do odczytu
const nr 2 - żeby zaznaczyć że funkcja Sphere::HitTest() nic nie modyfikuje w Sphere
referencja przy "ray" - po to żeby nie kopiować na okrągło obiektów (do zastosowania praktycznie wszędzie gdzie używasz obiektów przez wartość)

0

No i kolejna część, 7 stron, w tym sporo obrazków.

Część VI - Model Phonga

Wygląda to już ładniej, jeśli ktoś ma skrzywione poczucie estetyki to może nawet mu się spodoba ;).

2

Bardzo fajne, ale poczekam na wersję filmową.

1

Te ilustracje, kod, opis to materiał na książkę a nie tutorial.
Marnujesz się :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1