RGB(A), luminância e modos de mesclagem
Cor como um vec3 ou vec4, o que o alfa significa, o quanto uma cor parece clara e a matemática por trás de multiplicar, screen, overlay e somar.
Como funciona
Para um shader, uma cor é um vetor: vermelho, verde e azul num Vetor Um grupo de dois a quatro números tratado como um só valor (vec2, vec3, vec4). O mesmo tipo guarda uma posição, uma direção, uma cor ou uma UV. 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.vec3, de 0 (nada) a 1 (cheio), mais um quarto valor
opcional, o alfa, num vec4. Tudo de vetoresUm grupo de dois a quatro números tratado como um só valor (vec2, vec3, vec4). O mesmo tipo guarda uma posição, uma direção, uma cor ou uma UV..rgb e .a são swizzles, e o 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.mix(a, b, t) = a (1 − t) + b t
Aritmética como luz
Cada operação básica tem uma leitura física, o que facilita escolher:
vec3 base = vec3(0.8, 0.6, 0.4);
vec3 tint = base * vec3(1.0, 0.5, 0.5); // multiplicar: um filtro colorido; só escurece
vec3 lit = base + vec3(0.2); // somar: mais luz por cima; só clareia
vec3 dim = base * 0.5; // escalar: a mesma cor, metade da luz
vec3 blend = mix(base, vec3(0.1, 0.2, 0.9), 0.3); // 30% do caminho até o azul
Multiplicar por branco (vec3(1.0)) não muda nada e por preto dá preto, e é por isso que texturas e iluminação se
combinam multiplicando. Valores acima de 1 são normais dentro de um shader, como luz forte que ainda não foi ajustada
para a tela; a saída corta em 1, a não ser que esteja renderizando num alvo de ponto flutuante. Valores negativos não
têm sentido como cor e costumam vir de uma subtração ou de um filtro de nitidez que precisa de um max(c, 0.0).
Alfa
O alfa é o quarto canal. Por convenção ele significa cobertura ou opacidade: 1 é sólido, 0 é invisível. O shader só o
escreve na saída; se ele faz alguma coisa depende da mesclagem (blending), configurada fora do shader. Com a mesclagem
desligada, o alfa não mistura este desenho com o que já está lá; ele só é guardado no destino. A maioria dos destinos o
ignora, mas um canvas WebGL com o padrão alpha: true o usa para se compor sobre a página, então escreva 1.0 nele a
menos que queira que a página apareça por trás.
Há dois jeitos de guardar uma cor com alfa. O alfa direto (straight) guarda a cor como ela é, e a opacidade à parte. O
alfa pré-multiplicadoUma cor guardada já multiplicada pela opacidade. Ela filtra e mescla sem franjas escuras e consegue expressar luz aditiva. Alfa pré-multiplicado Uma cor guardada já multiplicada pela opacidade. Ela filtra e mescla sem franjas escuras e consegue expressar luz aditiva.over: out = src + dst · (1 − src.a)vec4(0.5, 0.0, 0.0, 0.5). Pôr uma cor sobre um fundo (“over”) fica assim:
vec3 overStraight = src.rgb * src.a + dst.rgb * (1.0 - src.a); // alfa direto
vec3 overPremul = src.rgb + dst.rgb * (1.0 - src.a); // pré-multiplicado
Cores pré-multiplicadas filtram e desfocam certo (um texel totalmente transparente não contribui com nada, seja qual for a cor guardada nele) e conseguem expressar brilho aditivo (alfa 0 com cor acima de 0). Misturar as duas convenções sem querer dá franjas escuras ou claras em volta das bordas suaves.
Luminância
Os três canais não parecem igualmente claros. O olho é mais sensível ao verde e menos ao azul, então o verde cheio parece muito mais claro que o azul cheio. A luminância pesa os canais por essa sensibilidade; para as primárias do sRGB e do Rec. 709, usadas pelas telas, os pesos são:
float luminance(vec3 c) { return dot(c, vec3(0.2126, 0.7152, 0.0722)); }
Os pesos somam 1, então o branco continua 1. A luminânciaO quanto uma cor parece clara, a partir do vermelho, verde e azul lineares, pesados pela sensibilidade do olho a cada um. Luminância O quanto uma cor parece clara, a partir do vermelho, verde e azul lineares, pesados pela sensibilidade do olho a cada um.Y = 0.2126 R + 0.7152 G + 0.0722 Bvec3(luminance(c))), de ajustar a saturação (mix(vec3(luminance(c)), c, amount): 0 é cinza, 1 é o
original, acima de 1 fica mais viva), de escolher as áreas claras para um brilho ou de rodar um detector de bordas num
canal só em vez de três. Esses pesos valem para valores lineares; a próxima página, sobre gama, explica a diferença.
Modos de mesclagem
Os editores de imagem dão nome a um punhado de jeitos de combinar uma cor de base a com uma camada b. Cada um é uma
linha de matemática canal a canal, com valores de 0 a 1:
- Multiplicar
a * b: escurece, como sobrepor dois slides. O branco é neutro. - Screen
1.0 - (1.0 - a) * (1.0 - b): clareia, o inverso de multiplicar. O preto é neutro. - Somar
a + b: clareia e pode passar de 1; brilhos, fogo e luz são somados. - Overlay: multiplica onde a base é escura e faz screen onde ela é clara, o que aumenta o contraste. O cinza 50% é neutro.
O overlay tem um desvio por canal, que step e mix escrevem sem if:
vec3 overlay(vec3 a, vec3 b) {
vec3 dark = 2.0 * a * b; // multiplicar, dobrado
vec3 light = 1.0 - 2.0 * (1.0 - a) * (1.0 - b); // screen, dobrado
return mix(dark, light, step(0.5, a)); // escolhe por canal pela base
}
Para aplicar qualquer modo com força parcial, misture da base até o resultado: mix(a, overlay(a, b), opacity).
As cores nos cards deste site seguem a mesma matemática. Uma cor que você escolhe nos controles de um card é escrita como você a vê e chega ao shader já convertida para valores lineares, o que faz valerem os pesos de luminância e a aritmética de luz acima.
Leitura complementar
- Relative luminance (abre em uma nova aba)Referência
- Blend modes (abre em uma nova aba)Referência
- Alpha compositing (abre em uma nova aba)Referência