Pular para o conteúdo
Fundamento · Depuração e desempenhoIniciante

Mostrar valores como cor

Escreva qualquer valor na saída para enxergá-lo e aprenda a ler o que as cores dizem.

Como funciona

Um shader de fragmento não tem print. Ele roda milhões de vezes por quadro, na GPU, e a única coisa que volta é uma cor por pixel. Então a cor é o depurador: quando um valor parece errado, escreva-o na saída e olhe para ele. Toda prévia de passo nos cards deste site é exatamente isso, uma variável mostrada como cor.

Um valor, em cinza

Um float entre 0 e 1 se lê melhor em cinza: preto é 0, branco é 1. Coloque o mesmo valor nos três canais:

float d = length(p);              // o valor que você quer inspecionar
fragColor = vec4(vec3(d), 1.0);   // preto no centro, mais claro para fora

Um vec2 cabe no vermelho e no verde, como acontece com a UV: preto em (0, 0), vermelho onde x vale 1, verde onde y vale 1, amarelo onde os dois valem. Um vec3 preenche os três canais.

Para ler um só componente de um vetor, isole-o: fragColor = vec4(vec3(n.y), 1.0); mostra apenas o y em cinza.

Valores fora de 0 a 1

A tela limita a saída, então tudo abaixo de 0 aparece preto e tudo acima de 1 aparece branco. Uma região inteira de preto chapado pode esconder um valor de -0.01 ou de -500. Remapeie antes:

  • Um intervalo de -1 a 1, como uma direção ou uma normal: v * 0.5 + 0.5 leva o valor para 0..1. O que aponta para +x fica vermelho-rosado, +y verde-claro, +z azul-claro; um vetor nulo vira 0.5 em todos os canais.
  • Um intervalo grande, como uma distância em unidades do mundo ou um tempo em segundos: divida pelo maior valor que você espera, ou mostre fract(v), que se repete a cada 1 unidade e desenha curvas de nível pelo campo.
  • Um sinal que importa, como o de uma distância com sinal: dê uma cor própria aos negativos e outra aos positivos. Associar valores a cores arbitrárias para tornar uma propriedade visível se chama falsa cor.
// vermelho onde v é negativo, verde onde é positivo, preto no zero
vec3 signedView(float v) {
    return vec3(max(-v, 0.0), max(v, 0.0), 0.0);
}

// faixas finas a cada 0.1 unidade: valores iguais caem na mesma faixa
float contours(float v) {
    return step(0.9, fract(v * 10.0));
}

Lendo números exatos

O olho é ruim para julgar brilho, e neste site o código em volta do efeito codifica a saída em sRGB, então um valor de 0.5 aparece bem mais claro que um cinza médio. Para conferir um número, compare em vez de estimar no olho: step(0.5, v) fica branco exatamente onde v chega a 0.5, e uma tela dividida mostra duas alternativas lado a lado.

vec3 a = vec3(smoothstep(0.2, 0.8, x));
vec3 b = vec3(x * x * (3.0 - 2.0 * x));
vec3 color = uv.x < 0.5 ? a : b; // metade esquerda: a, metade direita: b

NaN e infinito

Uma divisão por zero, a raiz quadrada de um número negativo ou o normalize de um vetor nulo podem gerar NaN (not a number, “não é um número”) ou infinito. O NaN se espalha por toda operação que encosta nele, e cada GPU o mostra de um jeito: preto, branco ou aleatório. O GLSL ES 3.00 tem isnan e isinf, então dá para pintar os pixels ruins de uma cor que nada mais usa:

if (isnan(v) || isinf(v)) {
    fragColor = vec4(1.0, 0.0, 1.0, 1.0); // magenta: ache onde isso começa
    return;
}

Alguns compiladores otimizam supondo que NaN nunca acontece, então um resultado limpo não é prova. Depois de marcar um pixel, volte pela cadeia mostrando cada valor anterior até o magenta sumir.

Percorra a cadeia

A maioria dos shaders é uma cadeia: coordenadas, depois uma distância, depois uma máscara, depois uma cor. Quando a imagem final está errada, mostre o primeiro elo, depois o seguinte, e pare no primeiro que parecer errado. Ele, ou a entrada dele, guarda o bug. As prévias de passo nos cards seguem essa mesma ordem pelo mesmo motivo.

Numa engine, garanta que o valor chegue à tela sem alteração. No Godot, escrever em ALBEDO faz o valor ser iluminado e sombreado; enquanto depura, adicione render_mode unshaded;, e aí ALBEDO = valor; mostra o valor cru. Escrever em EMISSION com ALBEDO = vec3(0.0); não basta: o SPECULAR padrão de 0.5 ainda soma luzes e reflexos do céu por cima, então defina também SPECULAR = 0.0; se for por esse caminho.

Leitura complementar