Interpolacja głębokości dla bufora Z, z linią skanowania

10

Muszę napisać własny programowy rasterizer 3d i do tej pory jestem w stanie rzutować mój model 3d wykonany z trójkątów na przestrzeń 2d:

Obracam, tłumaczę i rzutuję moje punkty, aby uzyskać przestrzenną reprezentację każdego trójkąta. Następnie biorę 3 punkty trójkąta i implementuję algorytm skanowania (używając interpolacji liniowej), aby znaleźć wszystkie punkty [x] [y] wzdłuż krawędzi (lewej i prawej) trójkątów, aby móc skanować trójkąt w poziomie, rząd po rzędzie i wypełnij go pikselami.

To działa. Tyle, że muszę również zaimplementować buforowanie Z. Oznacza to, że znając obrócone i przetłumaczone współrzędne z 3 wierzchołków trójkąta, muszę interpolować współrzędną z dla wszystkich innych punktów, które znajduję za pomocą mojego algorytmu skanowania.

Koncepcja wydaje się dość jasna, po raz pierwszy znajduję Za i Zb z tymi obliczeniami:

var Z_Slope = (bottom_point_z - top_point_z) / (bottom_point_y - top_point_y);
var Za = top_point_z + ((current_point_y - top_point_y) * Z_Slope);

Następnie dla każdej Zp wykonuję tę samą interpolację w poziomie:

var Z_Slope = (right_z - left_z) / (right_x - left_x);
var Zp = left_z + ((current_point_x - left_x) * Z_Slope);

wprowadź opis zdjęcia tutaj

A jeśli bieżący z jest bliżej przeglądarki niż poprzedni z pod tym indeksem, Później zapisz kolor do bufora kolorów ORAZ napisz nowy z do bufora z. (mój układ współrzędnych to x: lewy -> prawy; y: górny -> dolny; z: twoja twarz -> ekran komputera;)

Problem polega na tym, że szaleje. Projekt jest tutaj i jeśli wybrać „Z buforowane” przycisk opcji, zobaczysz wyniki ... ( uwaga, że mogę używać algorytmu malarza (-only- narysować szkielet) w trybie „Z-buforowanej” do celów debugowania )

PS: Przeczytałem tutaj, że z = 1/zprzed interpolacją musisz przekształcić litery Z w ich odwrotność (znaczenie ). Próbowałem tego i wydaje się, że nie ma zmian. czego mi brakuje? (czy ktokolwiek mógłby wyjaśnić, gdzie dokładnie trzeba zmienić z na 1 / z i gdzie (jeśli), aby go odwrócić?)

[EDYCJA] Oto kilka danych o tym, jakie maksymalne i minimalne wartości Z otrzymuję:

    max z: 1;                  min z: -1;                 //<-- obvious, original z of the vertices of the triangles
    max z: 7.197753398761272;  min z: 3.791703256899924;   //<-- z of the points that were drawn to screen (you know, after rotation, translation), by the scanline with zbuffer, gotten with interpolation but not 1/z.
    max z: 0.2649908532179404; min z: 0.13849507306889008;//<-- same as above except I interpolated 1/z instead of z.
//yes, I am aware that changing z to 1/z means flipping the comparison in the zBuffer check. otherwise nothing gets drawn.

Zanim przejdę do starannego debugowania, czy ktoś może potwierdzić, że moja koncepcja do tej pory jest poprawna?

[EDIT2]

Rozwiązałem buforowanie Z. Jak się okazuje, kolejność rysowania wcale nie była pomieszana. Współrzędne Z były obliczane poprawnie.

Problem polegał na tym, że próbując zwiększyć częstotliwość klatek, rysowałem pola 4px / 4px, co 4 piksele, zamiast rzeczywistych pikseli na ekranie. Rysowałem więc 16 pikseli na piksel, ale sprawdzałem bufor Z tylko dla jednego z nich. Jestem takim cyckiem.

TL / DR: Pytanie nadal brzmi: jak / dlaczego / kiedy trzeba używać odwrotności Z (jak w 1 / z) zamiast Z? Ponieważ teraz wszystko działa w obie strony. (nie ma zauważalnej różnicy).

Spectraljump
źródło
Odp: „I oczywiście dodam do zBuffer, jeśli bieżący z jest bliżej przeglądarki niż poprzednia wartość przy tym indeksie.” Nie jest dla mnie jasne, że sformułowałeś to tak, jak chciałeś, ale chciałeś się upewnić, że masz na myśli „jeśli bieżący z jest bliżej przeglądarki niż poprzedni z pod tym indeksem, NASTĘPNIE napisz kolor do bufora kolorów ORAZ napisz nowy z do bufora z "Celem bufora z jest blokowanie zapisu kolorów, jeśli kolor tego piksela został już zapisany bliżej oka kamery.
Alturis,
To jest poprawne. Przepraszam, było późno, kiedy sformułowałem moje pytanie. Poprawię.
Spectraljump

Odpowiedzi:

5

Szybka odpowiedź: Z nie jest funkcją liniową (X ', Y'), ale 1 / Z jest. Ponieważ interpolujesz liniowo, otrzymujesz poprawne wyniki dla 1 / Z, ale nie dla Z.

Nie zauważysz, ponieważ dopóki porównanie między Z1 i Z2 jest prawidłowe, zbuffer zrobi właściwą rzecz, nawet jeśli obie wartości są błędne. Na pewno zauważysz, gdy dodasz odwzorowanie tekstur (i aby odpowiedzieć na pytanie, które będziesz mieć: interpoluj 1 / Z, U / Z i V / Z, i zrekonstruuj U i V z tych wartości: U = (U / Z) / (1 / Z), V = (V / Z) / (1 / Z). Podziękujesz mi później)

Przykład. Weź kawałek papieru. Widok z góry, więc zapomnij o współrzędnej Y. X jest osią poziomą, Z jest osią pionową, kamera znajduje się w (0, 0), płaszczyzna projekcji wynosi z = 1.

Rozważ punkty A (-2, 2) i B (2, 4). Punkt środkowy M odcinka AB to (0, 3). Na razie w porządku.

Projektujesz A na A ': X' = X / Z = -1, więc A 'wynosi (-1, 1). Podobnie B 'wynosi (0,5, 1). Zauważ jednak, że rzut M jest (0, 1), który NIE jest punktem środkowym A'B '. Dlaczego? Ponieważ prawa połowa segmentu znajduje się dalej od aparatu niż lewa połowa, więc wygląda na mniejszą.

Co się stanie, jeśli spróbujesz obliczyć Z M 'przy użyciu interpolacji liniowej? dx = (0,5 - -1) = 1,5, dz = (4 - 2) = 2, więc dla M 'gdzie X' = 0 interpolacja liniowa Z to zA + (dz / dx) (x - xA) = 2 + (2 / 1,5) (0 - -1) = 2 + 1,333 = 3,3333 - NIE 3!

Dlaczego? Ponieważ dla każdego kroku w kierunku X 'nie przesuwasz tej samej wartości w kierunku Z (lub innymi słowy, Z nie jest funkcją liniową X'). Dlaczego? Ponieważ im bardziej idziesz w prawo, tym bardziej segment jest oddalony od kamery, więc jeden piksel reprezentuje większą odległość w przestrzeni.

Co się stanie, jeśli interpolujesz 1 / Z zamiast tego? Najpierw obliczasz 1 / Z dla A i B: odpowiednio 0,5 i 0,25. Następnie interpolujesz: dx = (0,5 - -1) = 1,5, dz = (0,25 - 0,5) = -0,25, więc przy X '= 0 obliczasz 1 / Z = 0,5 + (-0,25 / 1,5) * (0 - -1) = 0,3333. Ale to 1 / Z, więc wartość Z wynosi ... dokładnie, 3. Jak powinna być.

Tak, matematyka jest niesamowita.

ggambett
źródło
1
Aha, i odnośnie „kiedy”: oblicz wartości 1 / Z przed rozpoczęciem rasteryzacji trójkąta (np. Tuż przed pionową pętlą), aby uzyskać interpolację 1 / Z po lewej i prawej stronie linii skanowania. Interpoluj je liniowo (NIE rób ponownie 1 / Z - interpolowane wartości wynoszą już 1 / Z!) I cofnij transformację tuż przed sprawdzeniem zbuffera.
ggambett
1
I w końcu dlaczego. Płaszczyzna (w której osadzony jest trójkąt) to Ax + By + Cz + D = 0. z jest wyraźnie liniową funkcją (x, y). Projektujesz więc x '= x / z oraz y' = y / z. Stamtąd x = x'z i y = y'z. Jeśli zastąpisz je w pierwotnym równaniu, otrzymasz Ax'z + By'x + Cz + D = 0. Teraz z = -D / (Ax '+ By' + C), gdzie jest jasne, że z nie jest funkcją liniową z (x ', y'). Ale 1 / z jest zatem (Ax '+ By' + C) / -D, która jest funkcją liniową (x ', y').
ggambett
1
Wiesz, przeczytałem sporo artykułów i kursów, a żaden z nich nie był tak jasny, jak twoja odpowiedź. Dla potomnych zauważę również, że „litery” U ”i„ V ”oznaczają osie tekstury 2D, ponieważ„ X ”,„ Y ”i„ Z ”są już używane do oznaczenia osi obiektu 3D w modelu przestrzeń. Teksturowanie UV pozwala na pomalowanie wielokątów tworzących obiekt 3D kolorem z obrazu ”. - Wikipedia - Mapowanie UV
Spectraljump
Cieszę się, że to słyszę. W rzeczywistości uczyłem grafiki komputerowej w poprzednim życiu :)
ggambett
Dziękuję bardzo - zawsze byłem tego ciekawy - i nie wiem, czy kiedykolwiek znalazłbym lepszą odpowiedź! +1
Codesmith