Czy istnieje sposób na użycie dowolnej liczby świateł w shaderze fragmentów?

19

Czy istnieje sposób na przekazanie dowolnej liczby jasnych lokalizacji (i kolorów) dla modułu cieniującego fragmenty i nałożenie na nie pętli w module cieniującym?

Jeśli nie, to jak symulować wiele świateł? Na przykład w odniesieniu do rozproszonego oświetlenia kierunkowego nie można po prostu przekazać sumy wag światła dla modułu cieniującego.

NotRoyal
źródło
Nie współpracowałem z WebGL, ale w OpenGL masz maksymalnie 8 źródeł światła. Moim zdaniem, jeśli chcesz przekazać więcej, musisz użyć na przykład zmiennych jednolitych.
zacharmarz
Starą metodą było zawsze przejście do wszystkich świateł, nieużywane światła były ustawione na 0 luminancji i dlatego nie wpływałyby na scenę. Prawdopodobnie już nie używany ;-)
Patrick Hughes,
7
Kiedy robisz takie rzeczy w Google, nie używaj terminu „WebGL” - technologia jest zbyt młoda, aby ludzie mogli się nad nią zastanawiać. Weźmy na przykład to wyszukiwanie: „Czuję się szczęśliwy” zadziałałoby. Pamiętaj, że problem z WebGL powinien dobrze przełożyć się na dokładnie ten sam problem z OpenGL.
Jonathan Dickinson,
W przypadku więcej niż 8 świateł w renderowaniu do przodu zazwyczaj używam shadera wieloprzebiegowego i każdemu przejściu daje inną grupę 8 świateł do przetworzenia, stosując mieszanie addytywne.
ChrisC,

Odpowiedzi:

29

Istnieją dwie metody radzenia sobie z tym. Obecnie nazywane są renderowaniem do przodu i renderowaniem odroczonym. Istnieje jedna odmiana tych dwóch, które omówię poniżej.

Renderowanie do przodu

Renderuj każdy obiekt raz dla każdego światła, które na niego wpływa. Obejmuje to światło otoczenia. Korzystasz z addytywnego trybu mieszania ( glBlendFunc(GL_ONE, GL_ONE)), więc wkład każdego światła jest dodawany do siebie. Ponieważ udział różnych świateł jest addytywny, bufor ramki ostatecznie otrzymuje wartość

Możesz uzyskać HDR, renderując do bufora zmiennoprzecinkowego. Następnie wykonujesz ostatni ruch nad sceną, aby zmniejszyć wartości oświetlenia HDR do widocznego zakresu; będzie to również miejsce, w którym wdrażasz rozkwit i inne efekty końcowe.

Powszechnym ulepszeniem wydajności dla tej techniki (jeśli scena zawiera wiele obiektów) jest użycie „wstępnego przejścia”, w którym renderowane są wszystkie obiekty bez rysowania czegokolwiek w buforze ramki kolorów (użyj, glColorMaskaby wyłączyć zapisywanie kolorów). To po prostu wypełnia bufor głębokości. W ten sposób, jeśli renderujesz obiekt znajdujący się za innym, GPU może szybko pominąć te fragmenty. Nadal musi uruchamiać moduł cieniujący wierzchołki, ale może pomijać zwykle droższe obliczenia modułu cieniującego fragmenty.

Jest to prostsze do kodowania i łatwiejsze do wizualizacji. A w przypadku niektórych urządzeń (głównie mobilnych i wbudowanych układów GPU) może być bardziej wydajny niż alternatywa. Ale w sprzęcie wyższej klasy alternatywa zazwyczaj wygrywa w scenach z dużą ilością świateł.

Odroczone renderowanie

Odroczone renderowanie jest nieco bardziej skomplikowane.

Równanie oświetlenia używane do obliczania światła dla punktu na powierzchni wykorzystuje następujące parametry powierzchni:

  • Pozycja na powierzchni
  • Normalne powierzchni
  • Rozproszony kolor powierzchni
  • Kolor lustrzany powierzchni
  • Błyszczący połysk powierzchni
  • Możliwe inne parametry powierzchni (w zależności od złożoności równania oświetlenia)

W renderowaniu do przodu parametry te trafiają do funkcji oświetlenia modułu cieniującego fragmenty albo poprzez przekazanie bezpośrednio z modułu cieniującego wierzchołki, albo wyciągnięcie z tekstur (zwykle przez współrzędne tekstury przekazane z modułu cieniującego wierzchołek), lub wygenerowanie z całej tkaniny w module cieniującym fragmenty na podstawie inne parametry. Kolor rozproszenia można obliczyć, łącząc kolor na wierzchołek z teksturą, łącząc wiele tekstur, cokolwiek.

W renderowaniu odroczonym robimy to wszystko jawnie. W pierwszym przejściu renderujemy wszystkie obiekty. Ale nie renderujemy kolorów . Zamiast tego renderujemy parametry powierzchni . Dlatego każdy piksel na ekranie ma zestaw parametrów powierzchni. Odbywa się to poprzez renderowanie tekstur poza ekranem. Jedna tekstura przechowałaby rozproszony kolor jako RGB, a być może lśniący połysk jako alfa. Inna tekstura przechowałaby lustrzany kolor. Trzecia przechowa normalność. I tak dalej.

Pozycja zwykle nie jest przechowywana. Zamiast tego jest odtwarzany w drugim przejściu przez matematykę, która jest zbyt skomplikowana, aby się tu dostać. Wystarczy powiedzieć, że używamy bufora głębokości i pozycji fragmentu przestrzeni ekranu jako danych wejściowych, aby obliczyć pozycję punktu na powierzchni kamery w przestrzeni.

Teraz, gdy te tekstury przechowują zasadniczo wszystkie informacje o powierzchni dla każdego widocznego piksela w scenie, zaczynamy renderować quady pełnoekranowe. Każde światło otrzymuje pełnoekranowy render quad. Próbkujemy z tekstur parametrów powierzchni (i odtwarzamy pozycję), a następnie wykorzystujemy je do obliczenia udziału tego światła. To jest dodawane (ponownie glBlendFunc(GL_ONE, GL_ONE)) do obrazu. Robimy to, dopóki nie zabraknie świateł.

HDR ponownie jest etapem postprocesowym.

Największym minusem odroczonego renderowania jest antyaliasing. Prawidłowe wykonanie antyialii wymaga nieco więcej pracy.

Największą zaletą, jeśli twój GPU ma dużą przepustowość pamięci, jest wydajność. Rzeczywistą geometrię renderujemy tylko raz (lub 1 + 1 na światło, które ma cienie, jeśli wykonujemy mapowanie cienia). Nigdy nie spędzamy czasu na ukrytych pikselach lub geometrii, które nie są widoczne po tym. Cały czas spędzany na oświetleniu jest spędzany na rzeczach, które są rzeczywiście widoczne.

Jeśli twój GPU nie ma dużej przepustowości pamięci, wtedy przejście światła naprawdę może zacząć boleć. Ciągnięcie od 3-5 tekstur na piksel ekranu nie jest zabawne.

Light Pre-Pass

Jest to swego rodzaju odmiana renderowania odroczonego, która ma interesujące kompromisy.

Podobnie jak w przypadku renderowania odroczonego, renderujesz parametry powierzchni do zestawu buforów. Masz jednak skrócone dane powierzchni; jedynymi danymi powierzchniowymi, na których Ci zależy, są wartości bufora głębokości (do rekonstrukcji pozycji), normalne i połysk.

Następnie dla każdego światła obliczasz tylko wyniki oświetlenia. Bez mnożenia z kolorami powierzchni, nic. Tylko kropka (N, L) i określenie lustrzane, całkowicie bez kolorów powierzchni. Określenia zwierciadlane i rozproszone powinny być przechowywane w osobnych buforach. Terminy zwierciadlane i rozproszone dla każdego światła są sumowane w dwóch buforach.

Następnie ponownie renderujesz geometrię, korzystając z obliczeń całkowitego światła rozproszonego i rozproszonego, aby uzyskać ostateczną kombinację z kolorem powierzchni, a tym samym generować całkowity współczynnik odbicia.

Zaletą jest to, że otrzymujesz z powrotem multisampling (przynajmniej łatwiej niż przy odroczeniu). Renderujesz mniej na obiekt niż renderowanie do przodu. Ale najważniejsze, że odroczenie tego zapewnia, to łatwiejszy czas na uzyskanie różnych równań oświetlenia dla różnych powierzchni.

Dzięki opóźnionemu renderowaniu generujesz całą scenę przy użyciu tego samego modułu cieniującego na światło. Dlatego każdy obiekt musi używać tych samych parametrów materiałowych. Dzięki wstępnemu przejściu światła możesz nadać każdemu obiektowi inny moduł cieniujący, aby sam mógł wykonać ostatni krok oświetlenia.

Nie zapewnia to tyle swobody, co przypadek renderowania do przodu. Ale jest jeszcze szybszy, jeśli masz wolne pasmo tekstur.

Nicol Bolas
źródło
-1: brak wzmianki o LPP / PPL. -1 odroczone: renderowanie jest natychmiastową wygraną na dowolnym sprzęcie DX9.0 (tak, nawet na moim laptopie „biznesowym”) - co stanowi podstawowe wymagania ok. 2009 r., Chyba że celujesz w DX8.0 (który nie może wykonać Odroczonego / LPP) Odroczony / LPP jest domyślny . Wreszcie „duża przepustowość pamięci” jest szalona - generalnie nawet nie nasycamy PCI-X x4, a ponadto LPP znacznie zmniejsza przepustowość pamięci. Wreszcie -1 za komentarz; Pętle takie jak OK? Wiesz, że te pętle zdarzają się 2073600 razy na klatkę, prawda? Nawet przy parrelizmie karty graficznej jest źle.
Jonathan Dickinson
1
@JonathanDickinson Myślę, że jego celem było to, że przepustowość pamięci dla odroczonego / lekkiego wstępnego przejścia jest zazwyczaj kilka razy większa niż w przypadku renderowania do przodu. Nie unieważnia to odroczonego podejścia; to tylko coś do rozważenia przy wyborze. BTW: twoje odroczone bufory powinny znajdować się w pamięci wideo, więc przepustowość PCI-X nie ma znaczenia; liczy się wewnętrzna przepustowość GPU. Długie moduły cieniujące piksele, np. Z rozwiniętą pętlą, nie mają powodów do przerażenia, jeśli wykonują pożyteczną pracę. I nie ma nic złego w sztuczce wstępnej bufora Z; to działa dobrze.
Nathan Reed,
3
@JathanathanDickinson: Mówi się o WebGL, więc wszelka dyskusja na temat „modeli shaderów” jest nieistotna. A jakiego rodzaju renderowania użyć nie jest „temat religijny”: to po prostu kwestia sprzętu, na którym pracujesz. Wbudowany procesor graficzny, w którym „pamięć wideo” to zwykła pamięć RAM procesora, bardzo źle sprawdzi się w przypadku odroczonego renderowania. W mobilnym rendererze opartym na kafelkach jest jeszcze gorzej . Odroczone renderowanie nie jest „natychmiastową wygraną” niezależnie od sprzętu; ma swoje wady, podobnie jak każdy sprzęt.
Nicol Bolas,
2
@JathanathanDickinson: „Poza tym dzięki sztuczce przed przejściem do bufora Z będziesz walczył o wyeliminowanie walki z obiektami, które powinny zostać narysowane”. To totalny nonsens. Renderujesz te same obiekty za pomocą tych samych macierzy transformacji i tego samego modułu cieniującego wierzchołki. Renderowanie wielopasmowe zostało wykonane w Voodoo 1 dni; to jest rozwiązany problem. Kumulacja oświetlenia nic nie zmienia.
Nicol Bolas,
8
@JathanathanDickinson: Ale nie mówimy o renderowaniu szkieletu, prawda? Mówimy o renderowaniu tych samych trójkątów, co poprzednio. OpenGL gwarantuje niezmienność tego samego renderowanego obiektu (o ile oczywiście używasz tego samego modułu cieniującego wierzchołki, a nawet wtedy istnieje invariantsłowo kluczowe, które gwarantuje to w innych przypadkach).
Nicol Bolas,
4

Musisz użyć odroczonego renderowania lub oświetlenia wstępnego . Niektóre starsze potoki ze stałymi funkcjami (czytaj: brak shaderów) obsługiwały do ​​16 lub 24 lampek - ale to wszystko . Odroczone renderowanie eliminuje limit światła; ale kosztem znacznie bardziej skomplikowanego systemu renderowania.

Najwyraźniej WebGL obsługuje MRT, który jest absolutnie wymagany dla każdej formy odroczonego renderowania - więc może to być wykonalne; Po prostu nie jestem pewien, czy to prawdopodobne.

Ewentualnie możesz zbadać Unity 5 - która odroczyła renderowanie od razu po wyjęciu z pudełka.

Innym prostym sposobem na poradzenie sobie z tym jest po prostu ustalenie priorytetów świateł (być może, w zależności od odległości od odtwarzacza i tego, czy znajdują się one w frustum aparatu) i włączenie tylko pierwszej 8. Wiele tytułów AAA udało się to zrobić bez większego wpływu na temat jakości wydruku (na przykład Far Cry 1).

Możesz także zajrzeć do wstępnie obliczonych map świetlnych . Gry takie jak Quake 1 mają z tego duży przebieg - i mogą być dość małe (dwuliniowe filtrowanie całkiem ładnie zmiękcza rozciągnięte mapy świetlne). Niestety wstępnie obliczone wyklucza pojęcie 100% dynamicznych świateł, ale naprawdę wygląda świetnie . Możesz to połączyć z limitem 8 świateł, więc na przykład tylko rakiety lub podobne miałyby prawdziwe światło - ale światła na ścianie lub podobne byłyby mapami świetlnymi.

Uwaga dodatkowa: Nie chcesz zapętlać ich w shaderze? Pożegnaj się z występem. Procesor graficzny nie jest procesorem i nie został zaprojektowany do działania w taki sam sposób, jak na przykład JavaScript. Pamiętaj, że każdy renderowany piksel (nawet jeśli zostanie nadpisany) musi wykonać pętlę - więc jeśli uruchomisz w rozdzielczości 1920 x 1080 i prostej pętli, która działa 16 razy, skutecznie uruchomisz wszystko w tej pętli 33177600 razy. Twoja karta graficzna będzie działała równolegle z wieloma fragmentami, ale te pętle nadal będą zjadać starszy sprzęt.

Jonathan Dickinson
źródło
-1: „Musisz użyć odroczonego renderowania” To wcale nie jest prawda. Odroczone renderowanie to z pewnością sposób, aby to zrobić, ale nie jest to jedyny sposób. Również pętle nie są tak złe pod względem wydajności, szczególnie jeśli są oparte na jednolitych wartościach (tj .: każdy fragment nie ma innej długości pętli).
Nicol Bolas,
1
Przeczytaj czwarty akapit.
Jonathan Dickinson
2

Możesz użyć modułu cieniującego piksele, który obsługuje n świateł (gdzie n jest małą liczbą, np. 4 lub 8), i przerysować scenę wiele razy, za każdym razem przepuszczając nową partię świateł i stosując mieszanie addytywne, aby połączyć je wszystkie razem.

To podstawowy pomysł. Oczywiście potrzeba wielu optymalizacji, aby uczynić to wystarczająco szybkim dla sceny o rozsądnej wielkości. Nie rysuj wszystkich świateł, tylko te widzialne (ścięcie i wybijanie okluzji); nie rysuj ponownie całej sceny za każdym przejściem, tylko obiekty znajdujące się w zasięgu światła w tym przejściu; masz wiele wersji modułu cieniującego, które obsługują różne liczby świateł (1, 2, 3, ...), więc nie marnuj czasu na ocenę większej ilości światła niż potrzebujesz.

Odroczone renderowanie, jak wspomniano w drugiej odpowiedzi, jest dobrym wyborem, gdy masz wiele małych świateł, ale nie jest to jedyny sposób.

Nathan Reed
źródło