PRIMITIVE · TRIANGLENORMAL · NWEIGHTS · u, vCULL · BACK-FACE
Rozdział 4 / Podstawy

Przecięcia z płaszczyznami i trójkątami

Sfera była wygodnym punktem startu, ale prawie nic w produkcyjnej scenie nie jest sferą. Wracamy do obietnicy z Rozdziału 2: płaszczyzny i trójkąty — czyli dosłownie każda siatka, którą wyeksportujesz z Blendera, Mayi albo Houdiniego.

TL;DR
Pass: Płaszczyzna

Najprostszy przypadek: płaszczyzna

Płaszczyznę opisuje normalna N i dowolny punkt Q, który na niej leży. Punkt P należy do płaszczyzny, jeśli wektor od Q do P jest prostopadły do N — czyli ich iloczyn skalarny wynosi zero:

N · (P − Q) = 0

Podstawiamy nasz wzór promienia P(t) = O + t·D i rozwiązujemy — bez kwadratów, wprost:

t = N · (Q − O) / (N · D)gdy N·D ≈ 0, promień jest równoległy do płaszczyzny (albo leży w niej) — brak jednoznacznego t
Plane.hitlinear · 1 root
N·D = 0,267 · t ≈ 6,2
płaszczyzna Q N P(t) RÓWNOLEGŁY · N·D ≈ 0 · brak jednoznacznego t
Przesuń β — im bliżej równoległości D do płaszczyzny, tym mniejsze N·D i tym dalej (większe t) leży P(t).
Pass: Trójkąt i wagi

Trójkąt to płaszczyzna z granicami

Normalną trójkąta o wierzchołkach A, B, C liczymy tym samym iloczynem wektorowym, który poznałeś w Rozdziale 3:

N = normalize((B − A) × (C − A))

Możemy teraz policzyć t tak jak dla płaszczyzny — ale to tylko połowa roboty. Trójkąt zajmuje wyłącznie fragment tej płaszczyzny, więc trzeba sprawdzić, czy trafiony punkt leży w jego granicach. Do tego służą współrzędne barycentryczne: każdy punkt trójkąta zapisujemy jako wagowaną kombinację jego trzech wierzchołków.

P = (1 − u − v)·A + u·B + v·Cpunkt leży w trójkącie ⇔ u ≥ 0, v ≥ 0, u + v ≤ 1
Barycentric.weightsu + v + w = 1
A B C P 1−u−v u v
Te same wagi, które testują „czy P jest w trójkącie”, interpolują UV, normalne i vertex color w każdym shaderze.
Pass: Algorytm produkcyjny

Algorytm Möllera–Trumbore

W praktyce nikt nie liczy najpierw normalnej, potem t, potem osobno u i v — to trzy niezależne kroki, które można połączyć w jeden szybki test. Algorytm Möllera–Trumbore (1997) robi to wszystko naraz, korzystając wyłącznie z iloczynów wektorowych i skalarnych, które już znasz:

↓ Dodatek K — skąd się bierze ten algorytm
Moller-Trumbore.stepsStages · 6
Krawędzie trójkąta Wektor pomocniczy h Wyznacznik Waga u Waga v Parametr t → trafienie edge1 = B − A edge2 = C − A h = D × edge2 det = edge1 · h |det| ≈ 0 → promień równoległy s = O − A u = (s · h) / det u<0 lub u>1 → odrzuć q = s × edge1 v = (D · q) / det v<0 lub u+v>1 → odrzuć t = (edge2 · q) / det P(t), wagi = (1−u−v, u, v)
Ta sama „skrzynka” z Rozdziału 1 — „test przecięcia promienia ze sceną” — rozłożona na sześć konkretnych operacji.

To właśnie ta funkcja jest wołana najczęściej w całym programie: dla siatki z milionem trójkątów, dla każdego promienia z każdego piksela, z każdym odbiciem. Struktury akceleracyjne (BVH, o których będzie osobny rozdział) nie zmieniają tego testu — tylko drastycznie ograniczają liczbę trójkątów, które trzeba nim sprawdzić.

Z praktyki

Wagi barycentryczne, które właśnie policzyłeś do testu przecięcia, to dokładnie ten sam mechanizm, który interpoluje UV, normalne i vertex color w każdym shaderze rasteryzującym trójkąt — GPU robi to sprzętowo przy każdym trójkącie. Ray tracer musi policzyć to samo ręcznie, dla każdego punktu trafienia.

Znak iloczynu skalarnego N·D mówi, z której strony trójkąta nadchodzi promień — ujemny oznacza trafienie w przednią (front-facing) stronę. Odrzucanie trafień w tylną stronę (backface culling) przyspiesza render i jest źródłem znanego artefaktu: „dziury” w jednostronnych powierzchniach, jak liście czy plane’y UI, widziane z drugiej strony.

Dlaczego w ogóle trójkąt, a nie na przykład quad, na którym zwykle modelujesz w Blenderze czy Mayi? Trzy punkty zawsze leżą na jednej płaszczyźnie — trójkąt z definicji jest płaski i wypukły. Cztery punkty takiej gwarancji nie mają: poruszony niezależnie jeden wierzchołek quada robi z niego powierzchnię niepłaską (non-planar quad), a wtedy pytanie „którą przekątną go przeciąć” ma dwie różne odpowiedzi, dające dwa różne kształty. Dlatego każdy silnik renderujący — Arnold, Cycles, V-Ray, sprzętowy ray tracing w Unrealu — triangularyzuje geometrię, zanim promień w ogóle ją zobaczy; nawet powierzchnie podziałowe kończą jako mikropoligony albo trójkąty w momencie testu przecięcia. Modelujesz w quadach dla wygody edycji — renderer i tak zawsze widzi wyłącznie trójkąty.

Non-planar.quadprzekątna A-C vs B-D
PRZEKĄTNA A–C PRZEKĄTNA B–D A B C D A B C D
Te same cztery wierzchołki (D podniesiony ponad płaszczyznę A-B-C), dwa różne cięcia po przekątnej — dwie różne, niezgodne ze sobą powierzchnie. Trójkąt nigdy nie ma tego problemu, bo nie ma drugiej przekątnej do wyboru.
Słowniczek
Płaszczyzna
Zbiór punktów P spełniających N · (P − Q) = 0.
Normalna trójkąta
Wektor prostopadły do powierzchni, liczony z iloczynu wektorowego dwóch krawędzi.
Współrzędne barycentryczne
Wagi (1−u−v, u, v) opisujące punkt jako kombinację trzech wierzchołków.
Algorytm Möllera–Trumbore
Standardowy, jednoetapowy test przecięcia promień–trójkąt.
Backface culling
Odrzucanie trafień w tylną stronę trójkąta na podstawie znaku N·D.
Interpolacja atrybutów
Liczenie UV/normalnej/koloru w punkcie trafienia z wag barycentrycznych wierzchołków.
Triangularyzacja
Zamiana quadów/n-gonów na trójkąty — wykonywana zawsze przed testem przecięcia z promieniem, niezależnie od tego, w czym modelowałeś.
Idź głębiej
Dodatek K — Wyprowodzenie Möller–Trumbore