8-BIT SRGB · ~256 krokówDITHERING · szum przed kwantyzacjąKOMPRESJA · zip/piz kontra dwaa
Rozdział 12 / Tekstury

Bit depth, dithering, kompresja

8-bitowa tekstura w przestrzeni liniowej zaczyna się rozpadać na pasy, zanim jeszcze trafi do renderera — dlaczego niektóre kanały muszą być float, a inne nie.

TL;DR
Etap: Mechanizm

Dlaczego akurat cienie

Intuicja „sRGB daje cieniom więcej kroków, więc jest lepiej” jest tylko połową prawdy i nie tłumaczy, dlaczego banding w ogóle się pojawia. Prawdziwy mechanizm jest inny: krzywa dekodowania sRGB→linear (w przybliżeniu potęga ~2,2) jest wypukła — bierze szeroki zakres wejściowych wartości 8-bitowych blisko zera i ściska je w wąski zakres wynikowy, bliski zera. Jeśli wynik tej dekodyzacji zostanie z powrotem zapisany albo przetworzony przy niskim bit depth, wiele różnych wartości wejściowych ląduje w tym samym, jednym „kubełku” wyjściowym — informacja o gradiencie w cieniach znika, zanim jeszcze zacznie się shading. Przy gamma ≈2,2 wartość wejściowa 8-bit musi sięgnąć ~21/255, zanim dekodowany wynik w ogóle przestanie być zerem — wszystko poniżej tego progu koliduje w jeden punkt.

Policzone: ile wartości 8-bit koliduje poniżej progu dekodyzacji
Próg — przy gamma ≈2,2, wejściowe u8 = 21 (0,082) to pierwsza wartość dająca niezerowy wynik po dekodyzacji
Kolizja — wartości wejściowe u8 = 0…20, czyli 21 różnych poziomów, dekodują się i zaokrąglają do tego samego, zerowego wyniku
21 z 256 możliwych poziomów szarości (ok. 8% całego zakresu 8-bit) w cieniach koliduje w jeden punkt, zanim cokolwiek innego zdąży się wydarzyć — i to jeszcze przed jakimkolwiek mnożeniem w shaderze.
Problem narasta w dalszych krokach: mnożenie (światło × albedo, ekspozycja, grading) zachowuje względny błąd kwantyzacji zamiast go uśredniać — każde kolejne mnożenie poszerza widoczną lukę między ocalałymi poziomami, zamiast ją zamykać.

To dlatego problem jest najbardziej widoczny właśnie wtedy, gdy ktoś zapisuje pośredni wynik (np. zdekodowaną teksturę z powrotem jako plik 8-bitowy), a nie wtedy, gdy dekodyzacja dzieje się raz, w locie, przy samplingu tekstury we float. Dopóki cała ścieżka od pliku do shadingu trzyma wysoką precyzję (16 albo 32 bit), banding z tego mechanizmu się nie pojawia — pojawia się dokładnie tam, gdzie ktoś w środku łańcucha zaokrągla z powrotem do 8 bitów.

Etap: Które kanały

Gdzie 8-bit wystarcza, a gdzie nie

Albedo/base color zwykle bezpiecznie zostaje 8-bitowe — to zwykle ostatnie mnożenie w łańcuchu, oglądane po transformie wyświetlania (Rozdział 6), więc drobna utrata precyzji rzadko jest widoczna. Displacement, promień/MFP SSS (Rozdział 16) i roughness sterujący ostrym specularem to co innego — to wartości pośrednie, przez które przechodzi dalsza matematyka (przesunięcie geometrii, mnożenie przez scale, podnoszenie do potęgi w GGX), więc każdy błąd kwantyzacji na wejściu compounduje się w dalszych krokach. Praktyczna zasada: im więcej matematyki dzieje się po odczycie danej mapy, tym wyższy bit depth jest tej mapy wart.

Etap: Dithering

Kontrolowany szum przed kwantyzacją

Dithering to dodanie niewielkiego, losowego szumu do obrazu tuż przed zaokrągleniem do niższego bit depth — szum rozbija ostre granice między pasami na ziarnistą, znacznie mniej widoczną teksturę, kosztem odrobiny szumu zamiast wyraźnych krawędzi. Narzędzie maketx (OpenImageIO, pipeline Arnolda) ma udokumentowaną flagę --dither, włączającą losowy dithering przy każdej konwersji do 8 bitów lub mniej. Odpowiednik po stronie RenderMana, txmake, w dostępnej dokumentacji nie wystawia analogicznej flagi — jeśli dithering jest tam potrzebny, trzeba go zrobić wcześniej, na etapie przygotowania tekstury, a nie liczyć na sam txmake.

Etap: Kompresja

zip/piz kontra dwaa — co tracisz, a co zyskujesz

FormatTypKiedy używać
zip / piz (Arnold, EXR)bezstratnydomyślny wybór maketx; obowiązkowy dla map danych — normal, roughness, displacement
dwaa / dwab (Arnold, EXR)stratny, blokowykolor/albedo w wysokiej rozdzielczości — potrafi przepołowić rozmiar pliku bez widocznej różnicy przy domyślnej jakości
none / lossless / lossy (RenderMan, .tex)trzy tryby wprostformat Pixara domyślnie lossless, EXR domyślnie zip; lossy dla float'owych tekstur, gdzie liczy się rozmiar bardziej niż pełna precyzja

Jeden konkretny, udokumentowany powód, żeby nie sięgać po dwaa dla map jednokanałowych: ma znany błąd na obrazach monochromatycznych, gdy rozmiar kafla jest mniejszy niż 16 albo nie jest potęgą dwójki — Arnold w takim wypadku po cichu spada z powrotem na zip. To dodatkowy, praktyczny argument za regułą, która i tak wynika z poprzedniej sekcji: mapy danych (jednokanałowe, precyzyjne) — bezstratnie; kolor w wysokiej rozdzielczości — stratna kompresja jest akceptowalnym kompromisem.

Słowniczek
Banding (pasy)
Widoczne skoki jasności zamiast gładkiego gradientu, efekt zbyt niskiego bit depth po dekodyzacji/przetworzeniu.
Dithering
Dodanie kontrolowanego szumu przed kwantyzacją, żeby zamienić ostre pasy na mniej widoczne ziarno.
Kompresja bezstratna (zip/piz/lossless)
Zmniejsza rozmiar pliku bez utraty żadnej informacji — konieczna dla map danych.
Kompresja stratna (dwaa/dwab/lossy)
Odrzuca część informacji dla mniejszego rozmiaru pliku — akceptowalna dla koloru, ryzykowna dla danych.