Korzystanie z pełnej rozdzielczości bufora głębi do renderowania 2D

9

Pracuję nad rendererem od przodu do tyłu dla silnika 2D z wykorzystaniem projekcji ortograficznej. Chcę użyć bufora głębokości, aby uniknąć overdraw. Mam 16-bitowy bufor głębokości, aparat przy Z = 100 patrząc na Z = 0, zNear wynosi 1, a zFar wynosi 1000. Każdy renderowany duszek ustawia swoje współrzędne Z na coraz bardziej odległe wartości, umożliwiając testowi głębi pominięcie renderowania wszystko, co jest pod spodem.

Wiem jednak, że sposób, w jaki pozycje Z kończą się wartościami bufora Z, jest nieliniowy. Chcę skorzystać z pełnej rozdzielczości 16-bitowego bufora głębi, tzn. Zezwalając na 65536 unikalnych wartości. Tak więc dla każdego renderowanego duszka chcę zwiększyć pozycję Z do następnej pozycji, aby skorelować z następną unikalną wartością bufora głębokości.

Innymi słowy, chcę obrócić indeks rosnący (0, 1, 2, 3 ...) duszka, który jest wciągany do odpowiedniej pozycji Z dla każdego duszka, aby miał unikalną wartość bufora głębokości. Nie jestem pewien matematyki. Jakie są obliczenia, aby to zrobić?

Uwaga: Pracuję w WebGL (w zasadzie OpenGL ES 2) i muszę obsługiwać szeroki zakres sprzętu, więc chociaż rozszerzenia takie jak gl_FragDepth mogą to ułatwić, nie mogę go używać ze względu na kompatybilność.

AshleysBrain
źródło
Nie mogę sobie wyobrazić, że użycie bufora Z zapewni znaczny wzrost wydajności (jeśli w ogóle) po dodaniu wszystkich zapisów, obliczeń i porównań bufora Z w porównaniu z kopiowaniem tekstur z powrotem na przód, nie wspominając o żadnej przezroczystości / mieszaniu alfa nieszczęście
Matt Esch
@MattEsch: Chodzi o to, że wszystkie te obliczenia są wykonywane w GPU z niesamowicie dużymi prędkościami, więc ma to sens.
Panda Pajama
@MattEsch: FWIW jest to skierowane do zintegrowanych procesorów graficznych Intel, które używają pamięci systemowej zamiast dedykowanej pamięci GPU. To sprawia, że ​​są dość powolne i mogą przekroczyć limity wypełnienia, jeśli przekroczą wiele duszków. Intel zalecił mi to podejście jako sposób obejścia tego. Przypuszczalnie ich implementacja testów głębokości jest dobrze zoptymalizowana i może zaoszczędzić wiele wypełnień. Jednak okaże się, że jeszcze go nie profilowałem!
AshleysBrain
@PandaPajama pamięć do kopiowania bloków jest naprawdę bardzo szybka, więc jeśli tylko zlewałeś tekstury na powierzchni, byłoby to naprawdę bardzo szybkie. Pierwszym dużym narzutem jest przede wszystkim przesyłanie danych do GPU, co, jak zauważa Ashley, może być droższe na zintegrowanych GPU. Przekonasz się, że nawet wiele gier 3D wykonuje mało obciążającą pracę CPU (np. Animację kości), ponieważ przesyłanie danych wymaganych do wykonania tych obliczeń macierzowych jest zbyt drogie.
Matt Esch
@MattEsch: Jest tylko tyle, co możesz zrobić z samym blitowaniem. Przychodzą mi na myśl rotacje, skalowanie i deformacje, ale także dlatego, że masz shadery pikseli / wierzchołków, limit tego, co możesz zrobić ze sprzętem, jest znacznie wyższy niż to, co możesz zrobić tylko z blintowaniem.
Panda Pajama

Odpowiedzi:

5

Rzeczywiście, wartości przechowywane w buforze Z nie są liniowe względem rzeczywistych współrzędnych Z twoich obiektów, ale do ich wzajemności, aby dać większą rozdzielczość temu, co jest blisko oka, niż temu, co jest bliżej tylnej płaszczyzny.

To, co robisz, polega na mapowaniu zNeardo 0i zFardo 1. Dla zNear=1i zFar=2powinno to wyglądać tak

Zbuffer

Sposób obliczenia tego jest określony przez:

z_buffer_value = k * (a + (b / z))

Gdzie

 k = (1 << N), maximum value the Z buffer can store
 N = number of bits of Z precision
 a = zFar / ( zFar - zNear )
 b = zFar * zNear / ( zNear - zFar )
 z = distance from the eye to the object

... a wartość z_buffer_value jest liczbą całkowitą.

Powyższe równanie zostało przedstawione dzięki tej niesamowitej stronie , która wyjaśnia bufory Z w naprawdę dobry sposób.

Tak więc, w celu znalezienia niezbędnych zdla danego z_buffer_value, po prostu wyczyścić z:

z = (k * b) / (z_buffer_value - (k * a))
Panda Piżama
źródło
Dziękuję za odpowiedź! Jestem trochę zdezorientowany, jak dostałeś swoją ostateczną formułę. Jeśli wezmę z_buffer_value = k * (a + (b / z))i po prostu przestawię rozwiązanie z, otrzymam: z = b / ((z_buffer_value / k) - a)- jak doszedłeś do innej ostatniej formuły?
AshleysBrain
@AshleysBrain: bierzesz mianownik (v / k) - a => (v - k * a) / ki zwijasz się do (k * b) / (v - (k * a)). To ten sam wynik.
Panda Pajama
O, rozumiem. Dzięki za odpowiedź, działa ładnie!
AshleysBrain
0

Może powinieneś zmienić Twoje podejście do czegoś prostszego. Co bym zrobił; Zachowaj głębię Z, ale zachowaj listę tego, co renderujesz. Uporządkuj tę listę na podstawie wartości z Głębokość i renderuj obiekty w kolejności na liście.

Mam nadzieję, że to może pomóc. Ludzie zawsze mówią mi, żeby wszystko było proste.

HgMerk
źródło
1
Przepraszam, to niewiele pomaga. Już to robię. Pytanie dotyczy tego, jakie pozycje Z wybrać.
AshleysBrain
0

Skoro masz już posortowaną listę rzeczy do renderowania (od przodu do tyłu), czy naprawdę musisz zwiększać indeks Z? Czy nie można użyć „mniejszej lub równej” dla „funkcji sprawdzającej”? W ten sposób sprawdziłby, czy określony piksel został już narysowany, czy nie.

Elfie
źródło
„mniejszy lub równy” nadal spowoduje, że absolutnie wszystko zostanie zastąpione, ponieważ wszystko zawsze będzie miało równy wskaźnik Z, a zatem przejdzie test głębokości.
AshleysBrain