WEJŚCIE · tekstura sRGBOBLICZENIA · linioweWYJŚCIE · display transformBŁĄD · pominięty dekoding
Rozdział 3 / Kolor i linearność

Linear light workflow

Wzory z poprzedniej książki (Lambert, Fresnel, GGX) zakładają, że podwojenie liczby oznacza podwojenie jasności. Żaden plik obrazu i żaden ekran tego założenia nie spełnia.

TL;DR
Etap: Definicja

Co dokładnie znaczy „liniowe”

Przypomnij sobie najprostszy wzór na oświetlenie rozproszone z raytracing-book (Rozdział 5):

L_o = k_d · (N·L) · L_iL_o — jasność odbita w stronę obserwatora; k_d — kolor/albedo powierzchni; N·L — cosinus kąta między normalną a kierunkiem do światła; L_i — jasność światła padającego

Ten wzór jest wprost proporcjonalny względem L_i: podwój jasność źródła światła, a L_o też się podwoi. To jest dokładnie to, co znaczy liniowość radiometryczna — nie „prosta linia na wykresie” w sensie ogólnym, tylko konkretna własność fizyczna: ilość energii świetlnej i liczba, którą tę energię reprezentujesz, rosną w tym samym tempie. Dwa razy więcej fotonów = dwa razy większa liczba. Bez tego założenia każdy wzór z poprzedniej książki — Lambert, Fresnel, rozkład GGX — przestaje dawać sensowne wyniki, bo cała ich algebra (dodawanie wkładów wielu świateł, mnożenie przez cosinus, całkowanie po hemisferze) zakłada dokładnie tę proporcjonalność.

Etap: Skąd bierze się problem

Piksel w pliku to nie to samo co jasność fizyczna

Rozdział 2 pokazał, że oko reaguje na jasność w przybliżeniu logarytmicznie, nie liniowo — i że z tego powodu formaty obrazu (a za nimi każdy monitor) kodują jasność percepcyjnie, gamma lub logiem, żeby nie marnować poziomów tam, gdzie oko i tak ich nie rozróżnia. To świetne rozwiązanie dla plików, które ktoś ogląda — i fatalne, jeśli wrzucić taką liczbę wprost do wzoru na L_o powyżej, bo ten wzór nie wie nic o percepcji. On zakłada, że liczba, którą dostaje, jest proporcjonalna do fotonów, a wartość 0,5 zapisana w pliku sRGB reprezentuje nie połowę fizycznej jasności bieli, tylko dużo mniej — mniej więcej jedną piątą (dokładny wywód krzywej sRGB w Rozdziale 5 i Dodatku C).

Etap: Policzone

Co się dzieje, kiedy zabraknie dekodowania

Najprostszy sposób, żeby zobaczyć problem na liczbach: uśrednij dwa piksele tekstury — jeden ciemny (0,2), jeden jasny (0,8) — tak, jak robi to każde rozmycie, mipmapping czy crossfade. Porównaj wynik, gdy uśredniasz wartości wprost z pliku (błędnie), z wynikiem, gdy najpierw je dekodujesz do linear, uśredniasz fizyczną jasność, i dopiero potem kodujesz z powrotem do wyświetlenia (poprawnie).

Policzone: uśrednianie dwóch pikseli (uproszczona gamma 2,2)
Piksel A, plik (sRGB) = 0,2
Piksel B, plik (sRGB) = 0,8
Błędnie — uśrednienie wprost z pliku: (0,2 + 0,8) / 2 = 0,5
Dekodowanie A: 0,2^2,2 ≈ 0,029 (linear)
Dekodowanie B: 0,8^2,2 ≈ 0,612 (linear)
Poprawnie — uśrednienie w linear: (0,029 + 0,612) / 2 ≈ 0,321 (linear)
Kodowanie z powrotem: 0,321^(1/2,2) ≈ 0,596
Wynik błędny: 0,5  •  wynik poprawny: 0,596 — różnica ok. 16%, widoczna jako przyciemnienie
Sprawdzenie kierunku błędu: gamma „ściska” jasne wartości bliżej 1 i „rozciąga” ciemne bliżej 0, więc uśrednianie wprost w tej przestrzeni zawsze zaniża wynik względem uśredniania fizycznej jasności — stąd błąd jest systematycznym przyciemnieniem, nie losowym szumem.

Ten sam mechanizm — nie losowy, tylko systematyczny błąd w jedną stronę — stoi za klasycznym objawem „render wygląda spłowiały/zmatowiały mimo poprawnych wzorów”: ktoś gdzieś w łańcuchu uśrednił, przefiltrował albo przemnożył wartości zakodowane percepcyjnie tak, jakby były liniowe.

Etap: Diagnoza

Trzy miejsca, w których liniowość najczęściej się łamie

Etap: Dyscyplina

Linear light workflow jako reguła, nie wyjątek

Linear light workflow to nazwa dla prostej zasady porządkującej wszystko powyżej: dekoduj na wejściu, licz w scene-linear, koduj dopiero na wyjściu — i nigdzie pomiędzy. „Scene-linear” oznacza tu przestrzeń, w której liczby są wprost proporcjonalne do fizycznej ilości światła (tak jak w formule z pierwszej sekcji) — cały shading, cały light transport, całe blending i filtering z poprzedniej sekcji powinny się odbywać właśnie w niej. Kodowanie percepcyjne (gamma, log) pojawia się tylko w dwóch miejscach: na wejściu, przy odczycie tekstur kolorowych zapisanych percepcyjnie (dekodowanie w drugą stronę), i na wyjściu, przy wysłaniu finalnego obrazu na ekran (display transform).

Linear.light.pipelinedecode → compute → encode
Tekstura plik, sRGB Decode ^2,2 lub pełne EOTF Shading Lambert, GGX, blend, filtering Encode display transform cała strefa środkowa: wyłącznie scene-linear
Jedyne miejsca z kodowaniem percepcyjnym to skrajne kolumny — wejście i wyjście. Cała środkowa strefa, gdzie dzieje się shading, liczy wyłącznie na wartościach liniowych.

Zwróć uwagę, że sam mechanizm dekodowania i kodowania to na razie czarna skrzynka — „jakaś krzywa gamma”. Rozdział 5 pokazuje, czym dokładnie ta krzywa jest (i że prawdziwe sRGB to coś więcej niż czysta potęga), a Rozdział 6 pokazuje, jak w praktyce ten cały pipeline konfiguruje się jednym configiem OCIO zamiast ręcznie w każdym node'zie. Ta sama zasada „dekoduj na wejściu, koduj na wyjściu” jest już wspomniana pobieżnie w raytracing-book (Dodatek E) — tam jako ciekawostka zamykająca temat renderowania; tutaj jest to fundament, na którym stoi reszta książki.

Słowniczek
Liniowość radiometryczna
Własność, w której podwojenie fizycznej ilości światła odpowiada dokładnie podwojeniu zapisanej liczby.
Scene-linear
Przestrzeń wartości proporcjonalnych do fizycznego światła w scenie — tu odbywa się cały shading i light transport.
Display-referred
Wartości zakodowane percepcyjnie pod konkretny ekran/wyjście — nie nadają się do dalszych obliczeń światła.
Decode / Encode
Przejście z wartości zakodowanej percepcyjnie do scene-linear (decode) i z powrotem (encode).