TL; DR: 2 * 1LSB pontilhado triangular-pdf quebra nas bordas em 0 e 1 devido ao aperto. Uma solução é ler um pontilhado uniforme de 1bit nessas bordas.
Estou adicionando uma segunda resposta, visto que isso ficou um pouco mais complicado do que eu pensava inicialmente. Parece que esse problema foi "TODO: precisa de aperto?" no meu código desde que mudei do pontilhamento normalizado para o triangular ... em 2012. É bom finalmente vê-lo :) Código completo para soluções / imagens usadas em todo o post: https://www.shadertoy.com/view/llXfzS
Primeiro de tudo, aqui está o problema que estamos vendo, ao quantificar um sinal para 3 bits com o pontilhamento triangular-pdf de 2 * 1LSB:
- essencialmente o que a hotmultimedia mostrou.
Aumentando o contraste, o efeito descrito na pergunta se torna aparente: o resultado não é em média preto / branco nas bordas (e na verdade se estende muito além de 0/1 antes de fazê-lo).
Olhar para um gráfico fornece um pouco mais de insight:
(as linhas cinza marcam 0/1, também em cinza é o sinal que estamos tentando emitir, a linha amarela é a média da saída pontilhada / quantizada, o vermelho é o erro (média do sinal)).
Curiosamente, a saída média não é apenas 0/1 nos limites, mas também não é linear (provavelmente devido ao pdf triangular do ruído). Observando a extremidade inferior, faz sentido intuitivo o motivo pelo qual a saída diverge: Como o sinal pontilhado começa a incluir valores negativos, a fixação na saída altera o valor das partes pontilhadas inferiores da saída (ou seja, os valores negativos). aumentando o valor da média. Uma ilustração parece estar em ordem (pontilhado 2LSB uniforme e simétrico, média ainda em amarelo):
Agora, se apenas usarmos um pontilhamento 1LSB normalizado, não haverá problemas nos casos de bordas, mas é claro que perderemos as boas propriedades do pontilhamento triangular (veja, por exemplo, esta apresentação ).
Uma solução (pragmática, empírica) (hack), então, é reverter para [-0,5; 0,5 [pontilhamento uniforme para o 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) );
Que corrige as bordas, mantendo intacto o pontilhamento triangular para o intervalo restante:
Portanto, para não responder à sua pergunta: não sei se existe uma solução matematicamente mais sólida e estou igualmente interessado em saber o que os Masters of Past fizeram :) Até então, pelo menos, temos esse truque horrível para manter nosso código funcionando.
EDIT
Provavelmente, eu deveria abordar a sugestão de solução alternativa apresentada em The Question, simplesmente comprimindo o sinal. Como a média não é linear nas edgecases, a simples compactação do sinal de entrada não produz um resultado perfeito - embora conserte os pontos finais:
Referências