Qual é a maneira correta de conter o ruído de pontilhamento?

9

Ao reduzir a profundidade de cor e o pontilhamento com um ruído de 2 bits (com n =] 0,5,1,5 [e saída = piso (entrada * (2 ^ bits-1) + n)), as extremidades do intervalo de valores (entradas 0.0 e 1.0 ) são barulhentos. Seria desejável que eles fossem de cor sólida.

Exemplo: https://www.shadertoy.com/view/llsfz4

Gradiente de ruído (acima, é uma captura de tela do shadertoy, representando um gradiente e as duas extremidades, que devem ser brancas e pretas sólidas, respectivamente, mas barulhentas)

Obviamente, o problema pode ser resolvido apenas compactando a faixa de valores para que as extremidades sejam sempre arredondadas para valores únicos. Isso parece um pouco complicado, e eu estou querendo saber se há uma maneira de implementar isso "corretamente"?

hotmultimedia
fonte
Por alguma razão, o shadertoy não foi executado no meu navegador. Você poderia postar uma / algumas imagens simples para demonstrar o que você quer dizer?
Simon F
11
Não deveria ser mais como n =] - 1, 1 [?
JarkkoL
@JarkkoL Bem, a fórmula para converter ponto flutuante em número inteiro é output = floor (entrada * intmax + n), onde n = 0.5 sem ruído, porque você deseja (por exemplo)> = 0.5 arredondar para cima, mas <0.5 para baixo. É por isso que o ruído é "centrado" em 0,5.
Hotmultimedia
@SimonF adicionou imagem de shadertoy
hotmultimedia
11
Parece que você estava truncando a saída em vez de arredondá-la (como as GPUs fazem) - arredondando, pelo menos, você obtém os espaços adequados: shadertoy.com/view/MlsfD7 (imagem: i.stack.imgur.com/kxQWl.png )
Mikkel Gjoel

Respostas:

8

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:

saídas - 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).

insira a descrição da imagem aqui

Olhar para um gráfico fornece um pouco mais de insight:

insira a descrição da imagem aqui (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):

insira a descrição da imagem aqui

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 ).

insira a descrição da imagem aqui

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:

insira a descrição da imagem aqui

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: insira a descrição da imagem aqui

Referências

Mikkel Gjoel
fonte
É incrível que o lerp nas bordas produza um resultado perfeito. Eu esperaria pelo menos um pequeno desvio: P
Alan Wolfe
Sim, também fiquei positivamente surpreso :) Acredito que funcione porque estamos diminuindo linearmente a magnitude do pontilhamento, na mesma velocidade em que o sinal está diminuindo. Então, pelo menos, a escala corresponde ... mas eu concordo que é interessante que a mistura direta das distribuições pareça não ter efeitos colaterais negativos.
Mikkel Gjoel
@MikkelGjoel Infelizmente, sua crença está incorreta devido a um erro no seu código. Você reutilizou o mesmo RNG para ambos dithertrie em dithernormvez de um independente. Depois de trabalhar com toda a matemática e cancelar todos os termos, você descobrirá que não está lendo nada! Em vez disso, o código age como um ponto de corte rígido v < 0.5 / depth || v > 1 - 0.5/depth, alternando instantaneamente para a distribuição uniforme lá. Não que isso afaste o belo pontilhamento que você tem, é apenas desnecessariamente complicado. Corrigir o bug é realmente ruim, você vai acabar com uma pontada pior. Basta usar um ponto de corte rígido.
orlp
Depois de cavar ainda mais fundo, encontrei outro problema no seu shadertoy, no qual você não corrige gama enquanto calcula a média das amostras (você calcula a média no espaço sRGB que não é linear). Se você lida com a gama adequadamente, descobrimos que, infelizmente, ainda não terminamos. Devemos moldar nosso ruído para lidar com a correção gama. Aqui está um shadertoy exibindo o problema: shadertoy.com/view/3tf3Dn . Eu tentei várias coisas e não consegui fazê-lo funcionar, então postei uma pergunta aqui: computergraphics.stackexchange.com/questions/8793/… .
orlp
3

Não sei se posso responder totalmente à sua pergunta, mas acrescentarei algumas idéias e talvez possamos chegar a uma resposta juntos :)

Primeiro, a base da pergunta não é clara para mim: por que você considera desejável ter um preto / branco limpo quando todas as outras cores apresentam ruído? O resultado ideal após o pontilhamento é o sinal original com ruído totalmente uniforme. Se preto e branco forem diferentes, seu ruído ficará dependente do sinal (o que pode ser bom, pois acontece onde as cores são fixadas de qualquer maneira).

Dito isto, há situações em que o ruído nos brancos ou nos negros representa um problema (não conheço casos de uso que exijam que o preto e o branco sejam simultaneamente "limpos"): Ao renderizar uma partícula combinada aditivamente como um quad com como uma textura, você não deseja adicionar ruído em todo o quad, pois isso também seria mostrado fora da textura. Uma solução é compensar o ruído, em vez de adicionar [-0,5; 1,5 [você adiciona [-2,0; 0,0 [(isto é, subtrai 2 bits de ruído). Essa é uma solução bastante empírica, mas não estou ciente de uma abordagem mais correta. Pensando nisso, você provavelmente também quer aumentar seu sinal para compensar a intensidade perdida ...

De alguma forma relacionado, Timothy Lottes fez uma palestra do GDC sobre moldar o ruído nas partes do espectro onde é mais necessário, reduzindo o ruído na extremidade brilhante do espectro: http://32ipi028l5q82yhj72224m8j-wpengine.netdna-ssl.com/wp- content / uploads / 2016/03 / GdcVdrLottes.pdf

Mikkel Gjoel
fonte
(desculpe, pressione enter acidentalmente e o prazo de edição expirou) O exemplo de usuário no exemplo é uma daquelas situações em que seria importante: renderizar uma imagem em escala de cinza de ponto flutuante em um dispositivo de exibição de 3 bits. Aqui a intensidade muda bastante mudando apenas o LSB. Estou tentando entender se existe alguma "maneira correta" de fazer com que os valores finais sejam mapeados para cores sólidas, como comprimir o intervalo de valores e saturar os valores finais. E qual é a explicação matemática disso? Na fórmula de exemplo, o valor de entrada 1.0 não produz uma saída em média 7, e é isso que me incomoda.
Hotmultimedia
1

Simplifiquei a ideia de Mikkel Gjoel de pontilhar com ruído triangular para uma função simples que precisa apenas de uma única chamada RNG. Eu retirei todos os bits desnecessários para que fique bem legível e compreensível o que está acontecendo:

// Dithers and quantizes color value c in [0, 1] to the given color depth.
// It's expected that rng contains a uniform random variable on [0, 1].
uint dither_quantize(float c, uint depth, float rng) {
    float cmax = float(depth) - 1.0;
    float ci = c * cmax;

    float d;
    if (ci < 0.5 || ci >= cmax - 0.5) {
        // Uniform distribution on [-0.5, 0.5] for edges.
        d = rng - 0.5;
    } else {
        // Symmetric triangular distribution on [-1, 1].
        d = (rng < 0.5) ? sqrt(2.0 * rng) - 1.0 : 1.0 - sqrt(2.0 - 2.0*rng);
    }

    return uint(clamp(ci + d + 0.5, 0.0, cmax));
}

Para a idéia e o contexto, encaminhá-lo-ei à resposta de Mikkel Gjoel.

orlp
fonte
0

Eu segui os links para esta excelente pergunta, juntamente com o exemplo shadertoy.

Tenho algumas perguntas sobre a solução sugerida:

  1. Qual é o valor v? É o sinal RGB na faixa de 0,0 a 1,0 (preto para branco) que queremos quantizar? É um único flutuador? (e se sim, como você o calculou a partir do sinal RGB original?)
  2. Qual é a fonte do "número mágico" 0,5 / 7,0? Suponho que seja a meia caixa, mas esperaria que o tamanho da caixa representado por 8 bits fosse 1.0 / 255.0, então fiquei surpreso ao ver 0,5 / 0,7. Importa-se de explicar como você derivou esses números. o que estou perdendo?
  3. Estou assumindo que a distribuição do triângulo esteja no intervalo [-1,1] e o uniforme esteja em [-0,5,0,5] ("meio bit" porque estamos perto da borda e não queremos ultrapassar - é que a lógica que você aplicou?)
  4. As variáveis ​​aleatórias uniforme e triângulo devem ser independentes. Estou correcto?

Ótimo trabalho! Quero ver que entendi sua linha de pensamento corretamente. Obrigado!

zrizi
fonte