Saída em sRGB
Faça as contas em luz linear e codifique para sRGB uma única vez, no caminho para a tela.
Como funciona
O número que um shader de fragmentoO programa da GPU que roda uma vez para cada pixel coberto por um triângulo e decide a cor dele. Shader de fragmento O programa da GPU que roda uma vez para cada pixel coberto por um triângulo e decide a cor dele.
Isso é ótimo para armazenar e péssimo para fazer contas. Luz se soma: duas lâmpadas dão o dobro da luz de uma.
Iluminação, blending, blur e todo mixMistura de a até b por um fator t, em linha reta. O GLSL chama de mix; o HLSL e a maioria dos editores de nós chamam de lerp. Interpolação linear (mix) Mistura de a até b por um fator t, em linha reta. O GLSL chama de mix; o HLSL e a maioria dos editores de nós chamam de lerp.mix(a, b, t) = a (1 − t) + b t
Então um renderizador correto mantém dois tipos de número separados:
- Valores lineares medem luz. Todo o sombreamento acontece neles.
- Valores codificados em sRGB servem para armazenar e exibir: arquivos de imagem, seletores de cor e a tela final.
A regra é decodificar na entrada e codificar uma vez na saída.
A curva
O sRGB fica perto de uma potência de 2.2, com um trechinho reto perto do preto para a inclinação nunca ficar infinita. As duas direções, por canal (o alfa nunca é codificado):
vec3 srgbToLinear(vec3 c) {
vec3 low = c / 12.92;
vec3 high = pow((c + 0.055) / 1.055, vec3(2.4));
return mix(low, high, step(vec3(0.04045), c));
}
vec3 linearToSrgb(vec3 c) {
vec3 low = c * 12.92;
vec3 high = 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055;
return mix(low, high, step(vec3(0.0031308), c));
}
pow(c, vec3(1.0 / 2.2)) é um atalho comum. Fica perto, mas não é igual; ele clareia um pouco os tons mais escuros.
Decodificando as entradas
Cores que uma pessoa escolheu ou pintou estão em sRGB. O #808080 de um seletor de cor está codificado, e também
quase toda foto e textura pintada à mão. Decodifique antes de usá-las nas contas.
Com texturas, a GPU faz isso por você. Uma textura criada como SRGB8_ALPHA8 é decodificada para linear a cada
amostragem, antes da filtragem, então uma textura sRGB borrada ou com mipmaps continua tirando a média certa. Texturas
de dados (normal maps, ruído, máscaras) não são cores e precisam ficar em RGBA8 simples: decodificá-las entorta os
dados.
Codificando a saída
O OpenGL também sabe codificar na escrita. Quando o anexo de cor de um framebuffer é uma textura SRGB8_ALPHA8, o
WebGL2 e o OpenGL ES 3.0 codificam cada valor escrito nela, e o blending acontece em linear antes da codificação. (O
OpenGL de desktop pede também glEnable(GL_FRAMEBUFFER_SRGB).)
O canvas do WebGL2 é a exceção. O framebuffer padrão dele não tem modo sRGB: o que o último shader escreve aparece
do jeito que está, e o navegador trata como já codificado. Então a última passada no WebGL2 precisa chamar
linearToSrgb ela mesma, depois de qualquer tone mapping:
fragColor = vec4(linearToSrgb(clamp(color, 0.0, 1.0)), alpha);
Exatamente uma vez
Dois erros são comuns, e cada um tem uma cara:
- Nunca codificado. Os valores lineares vão crus para a tela. A imagem fica escura e contrastada demais, e esferas iluminadas mostram uma borda dura onde a luz cai, em vez de uma transição suave.
- Codificado duas vezes. A engine já codifica e o shader codifica de novo. A imagem fica lavada e leitosa, sem pretos de verdade.
Render targets intermediários devem guardar valores lineares em Banding e dithering Banding são as faixas em que um degradê suave se parte quando guardado com poucos níveis. O dithering soma um ruído minúsculo por pixel antes do arredondamento, e as faixas viram uma granulação fina.RGBA16F, ou em SRGB8_ALPHA8 se precisarem ser
pequenos. Valores lineares num RGBA8 simples perdem os escuros: ganham 3 dos 256 degraus abaixo de 1% do brilho, onde
a curva sRGB gasta uns 25, e as sombras ficam com bandingBanding são as faixas em que um degradê suave se parte quando guardado com poucos níveis. O dithering soma um ruído minúsculo por pixel antes do arredondamento, e as faixas viram uma granulação fina.
No Godot
O renderizador 3D trabalha em linear e codifica no fim, então um shader spatial escreve ALBEDO linear. Uniforms de
cor e texturas de cor marcados com source_color chegam decodificados. O renderizador 2D é diferente: a menos que o
viewport ative o HDR 2D, ele trabalha direto em sRGB, e source_color não faz nada num shader canvas_item. É por isso
que o template Godot deste site para cards 2D decodifica as cores e codifica o resultado à mão.