¿Cuál es una forma adecuada de sujetar el ruido de oscilación?

9

Al reducir la profundidad de color y el tramado con un ruido de 2 bits (con n =] 0.5,1.5 [y salida = piso (entrada * (2 ^ bits-1) + n)), los extremos del rango de valores (entradas 0.0 y 1.0 ) son ruidosos Sería deseable que tuvieran un color sólido.

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

Gradiente de ruido (arriba hay una captura de pantalla del dibujo sombreado, que muestra un gradiente y ambos extremos que deberían ser blancos y negros sólidos, respectivamente, pero que son ruidosos)

Por supuesto, el problema se puede resolver simplemente comprimiendo el rango de valores para que los extremos siempre se redondeen a valores únicos. Sin embargo, esto se siente como un truco, y me pregunto si hay una manera de implementar esto "correctamente".

hotmultimedia
fuente
Por alguna razón, el sombreador no se ejecutó en mi navegador. ¿Podría publicar algunas imágenes simples para demostrar lo que quiere decir?
Simon F
1
¿No debería ser más como n =] - 1, 1 [?
JarkkoL
@JarkkoL Bueno, la fórmula para convertir el punto flotante en entero es output = floor (input * intmax + n), donde n = 0.5 sin ruido, porque desea (por ejemplo)> = 0.5 redondear hacia arriba, pero <0.5 hacia abajo. Es por eso que el ruido está "centrado" en 0.5.
hotmultimedia 01 de
@SimonF agregó imagen de la cortina de sombra
hotmultimedia
1
Parece que estaba truncando la salida en lugar de redondearla (como lo hacen las GPU); en su lugar, al redondear, obtiene los blancos adecuados: shadertoy.com/view/MlsfD7 (imagen: i.stack.imgur.com/kxQWl.png )
Mikkel Gjoel

Respuestas:

8

TL; DR: 2 * 1LSB, el tramado triangular-pdf se rompe en las cajas de borde en 0 y 1 debido a la sujeción. Una solución es saltar a un tramado uniforme de 1 bit en esas cajas de borde.

Estoy agregando una segunda respuesta, ya que esto resultó un poco más complicado de lo que pensé originalmente. Parece que este problema ha sido un "TODO: ¿necesita sujeción?" en mi código desde que cambié de tramado normalizado a triangular ... en 2012. Se siente bien finalmente mirarlo :) Código completo para la solución / imágenes utilizadas en toda la publicación: https://www.shadertoy.com/view/llXfzS

En primer lugar, aquí está el problema que estamos viendo, cuando cuantificamos una señal a 3 bits con tramado triangular-pdf 2 * 1LSB:

salidas - esencialmente lo que mostró hotmultimedia.

Al aumentar el contraste, el efecto descrito en la pregunta se hace evidente: la salida no se promedia en blanco y negro en los bordes (y en realidad se extiende más allá de 0/1 antes de hacerlo).

ingrese la descripción de la imagen aquí

Mirar un gráfico proporciona un poco más de información:

ingrese la descripción de la imagen aquí (las líneas grises marcan 0/1, también en gris es la señal que estamos tratando de emitir, la línea amarilla es el promedio de la salida difuminada / cuantificada, el rojo es el error (promedio de la señal)).

Curiosamente, la salida promedio no solo no es 0/1 en los límites, sino que tampoco es lineal (probablemente debido al pdf triangular del ruido). Mirando el extremo inferior, tiene sentido intuitivo por qué la salida diverge: a medida que la señal difuminada comienza a incluir valores negativos, la sujeción en la salida cambia el valor de las partes difuminadas inferiores de la salida (es decir, los valores negativos), por lo tanto aumentando el valor de la media. Una ilustración parece estar en orden (uniforme, tramado simétrico de 2LSB, promedio todavía en amarillo):

ingrese la descripción de la imagen aquí

Ahora, si solo usamos un tramado normalizado de 1LSB, no hay problemas en los bordes de los casos, pero luego, por supuesto, perdemos las buenas propiedades del tramado triangular (ver, por ejemplo, esta presentación ).

ingrese la descripción de la imagen aquí

Entonces, una solución (pragmática, empírica) (piratear) es revertir a [-0.5; 0.5 [difuminado uniforme para el 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) );

Lo que repara las cajas de borde mientras mantiene intacta la oscilación triangular para el rango restante:

ingrese la descripción de la imagen aquí

Entonces, para no responder a su pregunta: no sé si existe una solución matemáticamente más sólida, y estoy igualmente interesado en saber qué han hecho los Maestros del Pasado :) Hasta entonces, al menos tenemos este truco horrible para mantener nuestro código funcionando.

EDITAR
Probablemente debería cubrir la sugerencia de solución dada en La pregunta, simplemente comprimiendo la señal. Debido a que el promedio no es lineal en las cajas de borde, simplemente comprimir la señal de entrada no produce un resultado perfecto, aunque sí arregla los puntos finales: ingrese la descripción de la imagen aquí

Referencias

Mikkel Gjoel
fuente
Es sorprendente que el lerp en los bordes dé un resultado perfecto. Esperaría al menos una pequeña desviación: P
Alan Wolfe
Sí, también me sorprendió positivamente :) Creo que funciona porque estamos reduciendo la magnitud del dither linealmente, al mismo ritmo que la señal está disminuyendo. Entonces, al menos, la escala coincide ... pero estoy de acuerdo en que es interesante que la combinación directa de las distribuciones parece no tener efectos secundarios negativos.
Mikkel Gjoel
@MikkelGjoel Desafortunadamente, su creencia es incorrecta debido a un error en su código. Reutilizaste el mismo RNG para ambos dithertriy en dithernormlugar de uno independiente. Una vez que trabaje con todas las matemáticas y cancele todos los términos, ¡encontrará que no le está molestando en absoluto! En cambio, el código actúa como un punto de corte duro v < 0.5 / depth || v > 1 - 0.5/depth, cambiando instantáneamente a la distribución uniforme allí. No es que le quite el buen tramado que tiene, simplemente es innecesariamente complicado. Corregir el error es realmente malo, terminarás con un tramado peor. Solo usa un corte duro.
orlp
Después de profundizar aún más, he encontrado otro problema en su sombreador donde no se corrige gamma mientras se promedian las muestras (promedia en el espacio sRGB que no es lineal). Si maneja gamma de manera apropiada, encontramos que desafortunadamente aún no hemos terminado. Debemos dar forma a nuestro ruido para lidiar con la corrección gamma. Aquí hay un sombreador que muestra el problema: shadertoy.com/view/3tf3Dn . He intentado un montón de cosas y no pude hacerlo funcionar, así que publiqué una pregunta aquí: computergraphics.stackexchange.com/questions/8793/… .
orlp
3

No estoy seguro de poder responder completamente a su pregunta, pero agregaré algunas ideas y tal vez podamos llegar a una respuesta juntos :)

Primero, la base de la pregunta es un poco confusa para mí: ¿por qué considera deseable tener un blanco / negro limpio cuando cualquier otro color tiene ruido? El resultado ideal después del tramado es su señal original con un ruido completamente uniforme. Si el blanco y el negro son diferentes, su ruido se vuelve dependiente de la señal (lo que podría estar bien, ya que de todos modos sucede donde los colores están sujetos).

Dicho esto, hay situaciones en las que tener ruido en blancos o negros plantea un problema (no estoy al tanto de los casos de uso que requieren que tanto el blanco como el negro estén "limpios" simultáneamente): cuando se procesa una partícula mezclada aditivamente como un quad con una textura, no desea que se agregue ruido en todo el quad, ya que eso también se vería fuera de la textura. Una solución es compensar el ruido, por lo que en lugar de sumar [-0.5; 1.5 [agrega [-2.0; 0.0 [(es decir, resta 2 bits de ruido). Esta es una solución bastante empírica, pero no estoy al tanto de un enfoque más correcto. Pensando en ello, es probable que también desee aumentar su señal para compensar la intensidad perdida ...

Algo relacionado, Timothy Lottes hizo una charla de GDC sobre la configuración del ruido en las partes del espectro donde más se necesita, reduciendo el ruido en el extremo brillante del espectro: http://32ipi028l5q82yhj72224m8j-wpengine.netdna-ssl.com/wp- content / uploads / 2016/03 / GdcVdrLottes.pdf

Mikkel Gjoel
fuente
(lo siento, presioné enter accidentalmente y el límite de tiempo de edición expiró) El caso de uso en el ejemplo es una de esas situaciones en las que sería importante: renderizar una imagen en escala de grises de punto flotante en un dispositivo de visualización de 3 bits. Aquí la intensidad cambia mucho simplemente cambiando el LSB. Estoy tratando de entender si existe la "forma correcta" de asignar valores finales a colores sólidos, como comprimir el rango de valores y saturar los valores finales. ¿Y cuál es la explicación matemática de eso? En la fórmula de ejemplo, el valor de entrada de 1.0 no produce una salida que promedia a 7, y eso es lo que me molesta.
hotmultimedia 01 de
1

He simplificado la idea de Mikkel Gjoel de difuminar con ruido triangular a una función simple que solo necesita una sola llamada RNG. He eliminado todos los bits innecesarios, por lo que debería ser bastante legible y comprensible lo que está sucediendo:

// 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 la idea y el contexto, lo remitiré a la respuesta de Mikkel Gjoel.

orlp
fuente
0

Seguí los enlaces a esta excelente pregunta junto con el ejemplo de sombreado.

Tengo algunas preguntas sobre la solución sugerida:

  1. ¿Cuál es el valor v? ¿Es la señal RGB en el rango de 0.0 a 1.0 (negro a blanco) que queremos cuantificar? ¿Es un flotador único? (y si es así, ¿cómo lo calculó a partir de la señal RGB original?)
  2. ¿Cuál es la fuente del "número mágico" 0.5 / 7.0? Supongo que es el medio bin, pero esperaría que el tamaño del bin representado por 8 bits sea 1.0 / 255.0, por lo que me sorprendió ver 0.5 / 0.7. ¿Te importaría explicar cómo obtuviste estos números? ¿Qué me estoy perdiendo?
  3. Supongo que la distribución del triángulo está en el rango [-1,1] y el uniforme está en [-0.5,0.5] ("medio bit" porque nos acercamos al borde y no queremos sobrepasarnos), es que la lógica que aplicaste?
  4. Las variables aleatorias uniformes y triangulares deben ser independientes. ¿Estoy en lo correcto?

¡Buen trabajo! Quiero ver que entendí tu línea de pensamiento correctamente. ¡Gracias!

zrizi
fuente