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:
- 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).
Mirar un gráfico proporciona un poco más de información:
(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):
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 ).
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:
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:
Referencias