HDR e tone mapping
Mantenha a luz acima de 1 durante todo o quadro e depois comprima-a no que uma tela consegue mostrar.
Como funciona
A luz real cobre uma faixa enorme. Neve sob sol forte é dezenas de milhares de vezes mais clara que o interior de um quarto à noite, e o filamento de uma lâmpada é mais claro ainda. Uma tela mostra uma faixa de talvez mil para um, e uma imagem de 8 bits guarda de 0 a 1. As contas de sombreamento não ligam para esse limite: uma luz forte numa superfície branca dá 4.0 com facilidade, e um letreiro emissivo pode dar 20.0.
Se esses valores vão direto para um destino RGBA8, tudo acima de 1 é cortado em 1. Os brilhos viram manchas brancas
chapadas, sem forma. Cores fortes também mudam de matiz ao serem cortadas: um laranja intenso (3.0, 1.2, 0.2) vira
(1.0, 1.0, 0.2), um amarelo, porque o vermelho e o verde batem no teto.
A renderização em high dynamic range (HDR) resolve isso em duas partes: guardar os valores reais pelo maior tempo possível e depois mapeá-los para a tela de propósito, uma vez, no fim.
Guardar os valores: destinos float
A cena é renderizada num render targetUma 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, que guarda valores até uns 65 000.
Assim o bloom encontra o que é claro de verdade (qualquer coisa acima de 1), efeitos de lente trabalham com
intensidades reais e nada foi jogado fora ainda. No WebGL2, renderizar num destino float exige a extensão
EXT_color_buffer_float.
“Renderização HDR” aqui quer dizer a faixa interna. É uma coisa diferente de um monitor HDR; numa tela comum o resultado ainda precisa caber em 0–1.
Exposição
Uma câmera escolhe quanta luz chega ao sensor; um renderizador faz o mesmo com uma multiplicação. A exposição costuma ser dada em stops, e cada stop dobra a luz:
color *= exp2(exposureStops); // +1 é o dobro do brilho, -1 a metade
Tone mapping
Um tone mapper é uma curva que recebe de 0 a infinito e devolve de 0 a 1. Ela quase não mexe nos valores escuros e médios e dobra os claros para que se aproximem de 1 sem nunca chegar lá. O mais simples é o de Reinhard:
vec3 reinhard(vec3 c) {
return c / (1.0 + c);
}
0.1 fica em cerca de 0.09, 1.0 vira 0.5 e 10.0 vira 0.91: os brilhos mantêm um degradê em vez de estourar. Os defeitos aparecem rápido, porém. Os tons médios escurecem (1.0 cai pela metade), então as cenas ficam chapadas e acinzentadas, e, aplicado a cada canal separadamente, ele puxa cores saturadas para o branco numa velocidade diferente por canal.
Curvas de produção se esforçam mais:
- Curvas fílmicas acrescentam um “pé” que aprofunda os pretos e um “ombro” que arredonda os brilhos com suavidade, como um filme fotográfico.
- ACES vem da indústria do cinema: contraste forte, e valores muito claros perdem saturação.
- AgX mantém o matiz das cores mais estável conforme elas clareiam, dessaturando-as rumo ao branco de forma controlada.
- Khronos PBR Neutral é feito para renders de produto: cores-base entre cerca de 0.08 e 0.8 passam quase sem mudança, e só os brilhos são comprimidos, sem desvio de matiz.
Não existe uma curva certa. É uma escolha artística, feita uma vez para o quadro inteiro.
Onde ele fica
O tone mapping fica no fim da cadeia de pós-processamentoPassadas de tela cheia rodadas em ordem sobre o quadro pronto, cada uma lendo a imagem que a anterior escreveu. Bloom, tone mapping e correção de cor ficam aqui. Cadeia de pós-processamento Passadas de tela cheia rodadas em ordem sobre o quadro pronto, cada uma lendo a imagem que a anterior escreveu. Bloom, tone mapping e correção de cor ficam aqui.
- Sombreie a cena em HDR linear.
- Rode os efeitos que precisam do brilho real: bloom, lens flares, motion blur.
- Aplique a exposição e depois o tone mapper. O resultado volta a ser linear, mas agora em 0–1.
- Faça a correção de cor sobre a imagem mapeada.
- Codifique para sRGBA 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.Renderização
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.055Fontese escreva na tela.
Juntando tudo na última passada:
vec3 hdr = texture(uScene, vUv).rgb;
vec3 mapped = reinhard(hdr * exp2(uExposure));
fragColor = vec4(linearToSrgb(mapped), 1.0);
Fazer o tone mapping antes do bloom deixa o bloom sem nada acima de 1 para trabalhar. Codificar antes do tone mapping aplica a curva aos números errados.
No Godot
O recurso Environment define o tone mapper: Linear (sem curva: valores acima de 1 estouram), Reinhard, Filmic, ACES e
AgX, com ajustes de exposição e de ponto branco. O glow, o bloom do Godot, roda sobre a imagem HDR antes dele. Um shader
spatial pode escrever ALBEDO ou EMISSION acima de 1 e a engine cuida do resto.
Neste site
O quadro de preview não faz tone mapping: ele limita a cor final a 0–1 e a codifica. Um card que gera valores acima de 1, como o Neon bloom, faz o tone mapping na sua própria última passada, então a curva faz parte do código que você copia.