Banding e dithering
Por que degradês suaves se partem em faixas com 8 bits, e como um pouco de ruído antes do arredondamento as esconde.
Como funciona
A tela recebe 8 bits por canal: 256 valores possíveis. Na maior parte do tempo, isso sobra. Aí aparece um degradê escuro e lento, um céu noturno ou uma vinheta, e ele se parte em faixas visíveis. Isso é banding.
Os números explicam. Digamos que um céu vá de 0.10 a 0.18 (em sRGB codificadoA curva que telas e arquivos de imagem usam para guardar cor, com mais degraus nos tons escuros. Shaders fazem as contas em luz linear e codificam para sRGB uma vez, no fim. Codificação sRGB A curva que telas e arquivos de imagem usam para guardar cor, com mais degraus nos tons escuros. Shaders fazem as contas em luz linear e codificam para sRGB uma vez, no fim.c ≤ 0.0031308 ? 12.92c : 1.055c^(1/2.4) − 0.055
O banding é pior onde poucos valores cobrem uma área grande: tons escuros, degradês lentos, neblina e céus, e imagens cujas etapas intermediárias foram guardadas com pouca precisão. A compressão de vídeo piora mais ainda.
Duas correções
Mais bits onde der. Render targetsUma textura em que um desenho escreve no lugar da tela, para uma passada seguinte ler o resultado. Blur, bloom e todo efeito com várias passadas usam isso. Render target Uma textura em que um desenho escreve no lugar da tela, para uma passada seguinte ler o resultado. Blur, bloom e todo efeito com várias passadas usam isso.RGBA16F não têm banding;
valores lineares guardados num RGBA8 simples têm muito banding nos escuros. Mas a tela continua recebendo 8 bits,
então a última etapa sempre arredonda.
Dithering na última etapa. Antes de o valor ser arredondado para 8 bits, some um deslocamento minúsculo que muda de pixel para pixel, de até meio degrau para cada lado. Onde o valor real fica a 30% do caminho entre dois níveis, cerca de 30% dos pixels agora arredondam para cima e o resto para baixo. Vistos da distância normal, os vizinhos voltam à média real. As bordas duras das faixas viram uma granulação fina e uniforme, que o olho ignora.
Onde somar
Faça o dithering na precisão que vai cortar o valor, logo antes do corte: depois do tone mapping e depois da
codificação sRGB, como a última coisa da passada final. Um degrau de 8 bits é 1.0 / 255.0:
color = linearToSrgb(color);
color += (noise - 0.5) / 255.0; // noise em 0..1, diferente por pixel
fragColor = vec4(color, 1.0);
Fazer o dithering antes da codificação, em valores lineares, erra o tamanho do degrau. A codificação estica os escuros, então meio degrau de deslocamento em linear vira vários degraus de ruído visível perto do preto, e pouco demais nos claros.
Qual ruído
O deslocamento precisa de um valor por pixel. Três fontes comuns:
- Ruído aleatório, de um hash da posição do pixel. Não precisa de textura. A granulação é irregular: aglomerados e buracos ao acaso.
- Dithering ordenado, de uma pequena matriz que se repete (uma matriz de Bayer). Barato e totalmente previsível. A trama regular aparece se você olhar de perto, e é justamente o visual retrô que os efeitos de dither estilizado buscam.
- Ruído azul (blue noise), de uma textura pré-calculada. Aleatório, mas sem os aglomerados, então dá a granulação mais fina e menos visível. A escolha de costume em produção.
Um dithering ordenado com uma matriz de Bayer 4×4:
const float BAYER4[16] = float[16](
0.0, 8.0, 2.0, 10.0,
12.0, 4.0, 14.0, 6.0,
3.0, 11.0, 1.0, 9.0,
15.0, 7.0, 13.0, 5.0
);
float bayer4(vec2 fragCoord) {
ivec2 p = ivec2(fragCoord) & 3; // posição dentro do bloco 4×4
return (BAYER4[p.y * 4 + p.x] + 0.5) / 16.0; // 16 valores igualmente espaçados em 0..1
}
Passe gl_FragCoord.xy para ela e use o resultado como noise acima. A mesma função com um bit em vez de oito, dois
níveis por canal em vez de 256, é o clássico dither de 1 bit: a técnica Posterize & dither transforma a correção num
estilo.
No Godot
A engine tem uma opção de debanding para 3D (use_debanding no viewport, ou a configuração de projeto Use
Debanding). Ela soma o dithering no fim do quadro por você. Nos seus próprios shaders de pós, some como acima, na
última passada.
Animado ou parado
Um padrão parado pode parecer uma tela suja num movimento lento de câmera. Trocar o ruído a cada quadro o transforma numa cintilação que o olho tira na média, e o anti-aliasing temporal suaviza mais ainda. Para uma imagem parada, um padrão parado serve.
Leitura complementar
- Colour banding (abre em uma nova aba)Referência
- Ordered dithering (abre em uma nova aba)Referência