DIFFUSE · irradiance conv.SPECULAR · prefiltered mipsMETODA · split-sum (Karis 2013)EXT OF · Dodatek Y · R.11
Dodatek AF / Globalne oświetlenie

Prefiltered IBL: irradiance i przybliżenie split-sum

Dodatek Y nauczył Cię odpytywać panoramę przy każdym odbiciu ścieżki — poprawnie, ale za wolno na 60 klatek na sekundę. Silniki czasu rzeczywistego liczą tę samą całkę raz, zanim jeszcze zacznie się render, i potem tylko odczytują gotową odpowiedź.

TL;DR
Pass: Problem z czasem rzeczywistym

Path tracer może czekać. Gra — nie

Dodatek Y odpytuje panoramę na nowo przy każdym odbiciu ścieżki, dla każdego piksela, w każdej klatce renderu offline — który i tak liczy się minuty albo godziny, więc kilkaset dodatkowych odczytów tekstury nie robi różnicy. Silnik czasu rzeczywistego ma na całą klatkę, ze wszystkim, budżet rzędu milisekund. Nie może sobie pozwolić na importance sampling po luminancji panoramy dla każdego piksela na ekranie, w każdej klatce.

Rozwiązanie jest tym samym pomysłem, co w Dodatku AE: policz raz, użyj szybko. Zanim gracz w ogóle zobaczy scenę, silnik piecze (bake) odpowiedź na całkę oświetleniową dla danej panoramy — raz, offline albo przy starcie poziomu — i w czasie gry tylko odczytuje gotowy wynik, dwoma-trzema odczytami tekstury na piksel.

Pass: Diffuse

Irradiance convolution: jedna rozmyta wersja panoramy

Dla światła rozproszonego (Lambert) potrzebujesz tylko jednej liczby na kierunek normalnej — napromieniowania E(N), czyli sumy światła dochodzącego z całej hemisfery, ważonej cosinusem, dokładnie jak AO w Dodatku AE, tylko bez binarnej widoczności — tu liczy się prawdziwa wartość radiancji z panoramy:

E(N) = ∫_Ω L_i(ω) · (N·ω) dωL_i(ω) — radiancja odczytana z panoramy w kierunku ω (jak w Dodatku Y), skalar, nie wektor; wynikowy diffuse = (albedo/π)·E(N)

Ponieważ E(N) jest funkcją bardzo gładką (cosinus filtruje wszystkie wysokie częstotliwości panoramy — nawet ostre słońce rozmywa się w łagodny gradient), zwykle nie zapisuje się jej jako obrazu wcale. Zamiast tego kompresuje się ją do garstki liczb — sferycznych harmonik (Ramamoorthi i Hanrahan, 2001): 9 współczynników na kanał koloru wystarcza, żeby odtworzyć E(N) dla dowolnego N z błędem niezauważalnym gołym okiem. To dosłownie tysiące razy mniej danych niż pełna panorama.

Pass: Specular

Łańcuch mipów: osobna rozmyta panorama na każdy roughness

Rozdział 11 pokazał, że rozkład GGX D(H) rozszerza się z roughness — gładki materiał ma wąski lob, chropowaty szeroki. Prefiltrowanie specular IBL robi dokładnie to samo, tylko z panoramą zamiast pojedynczego światła: konwoluuje ją wokół kierunku odbicia R (przy standardowym w real-time uproszczeniu N=V=R) rozkładem GGX o zadanym roughness, i zapisuje wynik jako kolejny poziom mipmapy tej samej tekstury — mip 0 to ostra, oryginalna panorama (roughness≈0), najwyższy mip to najbardziej rozmyta wersja (roughness≈1):

PrefilteredColor(R, roughness) = Σₖ L_i(ωₖ) · (N·ωₖ) / Σₖ (N·ωₖ)ωₖ — próbki wylosowane importance samplingiem GGX wokół R (Dodatek AD, tylko wokół R zamiast wokół H); suma znormalizowana, żeby jasność nie „uciekała” przy dużym roughness
Prefiltered.mip.blurroughness → σ blur
roughness = 0,30 · mip ≈ 1,8 / 6 · σ ≈ 4,2px
EQUIRECTANGULAR PANORAMA — MIP AKTYWNY
Ten sam parametr roughness co w Rozdziale 11 — tylko zamiast szerokości błysku steruje teraz rozmyciem całej zapisanej panoramy.
Pass: Split-sum

Dlaczego jedna tekstura nie wystarczy

Prefiltrowana panorama z poprzedniej sekcji zależy tylko od R i roughness — ale prawdziwa specular IBL to pełna całka ∫f(l,v)·L_i(l)·(N·l)dl, która zależy też od F0 materiału i kąta patrzenia (N·V) osobno. Upieczenie tego wszystkiego w jedną teksturę per environment map byłoby ogromne i musiałoby się powtarzać dla każdej kombinacji materiału. Karis (2013, „Real Shading in Unreal Engine 4”) zauważył, że całkę da się rozbić w przybliżeniu na iloczyn dwóch niezależnych, dużo tańszych całek:

specular ≈ PrefilteredColor(R, roughness) · (F0·A + B)
(A,B) = EnvBRDF-LUT(N·V, roughness)PrefilteredColor — zależy od konkretnej panoramy i roughness (policzone wyżej, raz na environment map); (A,B) — mała, uniwersalna tekstura 2D, niezależna od panoramy i materiału, policzona raz dla całego silnika i używana wszędzie
Split-sum.product2 textures × 1 multiply
PrefilteredColor(R,roughness) per environment map × EnvBRDF-LUT(N·V, roughness) uniwersalna, raz dla silnika = specular IBL 2 odczyty tekstury + 1 mnożenie
Zamiast jednej ogromnej, powtarzanej za każdym razem całki — dwa małe, wielokrotnego użytku odczyty tekstury.
Z praktyki

„Reflection Captures” (Unreal), „Reflection Probes” (Unity), „Irradiance Volumes” i cubemapy odbić w Blender Eevee — wszystkie to dokładnie ten bake: irradiance (sferyczne harmoniki albo mała cubemapa) plus prefiltrowany łańcuch mipów. Dlatego przesunięcie dużego mebla w edytorze wymaga „rebake lighting” — geometria się zmieniła, więc precalkulowana odpowiedź na całkę jest już nieaktualna. To też dlaczego takie probe'y łapią tylko statyczną scenę — ruchome obiekty potrzebują dodatkowo SSR albo ray-traced reflections, żeby w ogóle się odbijały.

Słowniczek
Irradiance convolution
Całka E(N) — światło z całej hemisfery ważone cosinusem, per normalna.
Sferyczne harmoniki (SH)
Skompresowana reprezentacja E(N); zwykle 9 współczynników/kanał.
Prefiltered mip chain
Sekwencja coraz bardziej rozmytych wersji panoramy, indeksowana roughness.
Split-sum approximation
Karis 2013 — rozbicie całki specular IBL na PrefilteredColor × EnvBRDF-LUT.
EnvBRDF-LUT
Mała, uniwersalna tekstura 2D, niezależna od konkretnej panoramy.