TL; DR: dithering trójkątny-pdf 2 * 1LSB przerywa edgecases w 0 i 1 z powodu zaciśnięcia. Rozwiązaniem jest lerp do jednobitowego ditheringu w tych edgecases.
Dodaję drugą odpowiedź, ponieważ okazało się to nieco bardziej skomplikowane niż początkowo myślałem. Wygląda na to, że ten problem to „DO ZROBIENIA: wymaga mocowania?” w moim kodzie, odkąd przeszedłem ze znormalizowanego na trójkątny dithering ... w 2012 roku. Dobrze jest w końcu na to spojrzeć :) Pełny kod dla rozwiązania / obrazów używanych w całym poście: https://www.shadertoy.com/view/llXfzS
Po pierwsze, oto problem, na który patrzymy, przy kwantyzacji sygnału do 3 bitów przy ditheringu trójkątnym-pdf 2 * 1LSB:
- zasadniczo to, co pokazała hotmultimedia.
Zwiększając kontrast, efekt opisany w pytaniu staje się widoczny: Moc wyjściowa nie jest średnia do czerni / bieli w krawędziach (i faktycznie wykracza znacznie poza 0/1 przed zrobieniem tego).
Spojrzenie na wykres zapewnia nieco więcej wglądu:
(szare linie oznaczają 0/1, również szary jest sygnałem, który próbujemy wyprowadzić, żółta linia jest średnią z rozłożonego / skwantowanego wyjścia, czerwona to błąd (średni sygnał)).
Co ciekawe, nie tylko średnia moc wyjściowa nie wynosi 0/1 na granicach, ale także nie jest liniowa (prawdopodobnie z powodu trójkątnego pdf hałasu). Patrząc na dolny koniec, intuicyjne jest zrozumienie, dlaczego sygnał wyjściowy jest rozbieżny: w miarę jak sygnał przerywany zaczyna zawierać wartości ujemne, zaciskanie na wyjściu zmienia wartość dolnych odcinków wyjściowych (tj. Wartości ujemne), tym samym zwiększenie wartości średniej. Ilustracja wydaje się być w porządku (jednolity, symetryczny dither 2LSB, średnia wciąż na żółto):
Teraz, jeśli użyjemy znormalizowanego ditheringu 1LSB, nie będzie żadnych problemów na skrzynkach brzegowych, ale wtedy oczywiście tracimy ładne właściwości trójkątnego ditheringu (patrz np. Ta prezentacja ).
(Pragmatycznym, empirycznym) rozwiązaniem (hack) jest więc powrót do [-0,5; 0,5 [dithering jednolity dla edgecase:
float dithertri = (rnd.x + rnd.y - 1.0); //note: symmetric, triangular dither, [-1;1[
float dithernorm = rnd.x - 0.5; //note: symmetric, uniform dither [-0.5;0.5[
float sizt_lo = clamp( v/(0.5/7.0), 0.0, 1.0 );
float sizt_hi = 1.0 - clamp( (v-6.5/7.0)/(1.0-6.5/7.0), 0.0, 1.0 );
dither = lerp( dithernorm, dithertri, min(sizt_lo, sizt_hi) );
Które naprawia krawędzie przy jednoczesnym zachowaniu nienaruszonego trójkątnego ditheringu dla pozostałego zakresu:
Więc nie odpowiadaj na twoje pytanie: nie wiem, czy istnieje bardziej solidne matematycznie rozwiązanie, i równie chętnie wiem, co zrobili Mistrzowie Przeszłości :) Do tego czasu przynajmniej mamy straszny hack, aby utrzymać funkcjonowanie naszego kodu.
EDYCJA
Prawdopodobnie powinienem uwzględnić sugestię obejścia podaną w pytaniu, dotyczącą po prostu kompresji sygnału. Ponieważ średnia nie jest liniowa w końcowych przypadkach, zwykła kompresja sygnału wejściowego nie daje idealnego wyniku - chociaż naprawia punkty końcowe:
Bibliografia