Pular para o conteúdo
Fundamento · Funções de formaIniciante

Step e smoothstep

Limiares duros e suaves, onde começa toda borda e toda máscara.

Como funciona

Uma forma num fragment shader é uma decisão que cada pixel toma sozinho: estou dentro ou fora? step e smoothstep transformam um valor contínuo, como uma coordenada ou uma distância, nessa decisão.

step: uma borda dura

step(edge, x) devolve 0.0 quando x está abaixo de edge e 1.0 nos outros casos. Aplicado a uma coordenada UV, ele divide a tela em duas:

float right = step(0.5, uv.x); // 0 na metade esquerda, 1 na direita

Atenção à ordem dos argumentos: o limiar vem primeiro. Trocar os argumentos inverte o resultado, e 1.0 - step(...) também. Multiplicar dois steps mantém só os pixels em que os dois valem 1, e é assim que começa uma faixa ou uma caixa:

float band = step(0.4, uv.y) * (1.0 - step(0.6, uv.y)); // 1 entre 0.4 e 0.6

Um step muda de uma vez, dentro de um único pixel. Numa borda que não seja perfeitamente horizontal ou vertical, isso aparece como uma escadinha serrilhada (aliasing), e uma borda em movimento cintila.

smoothstep: uma borda suave

smoothstep(e0, e1, x) vale 0 abaixo de e0, 1 acima de e1 e sobe suavemente entre os dois. Por dentro, ele primeiro remapeia x para 0 a 1 com uma lerp inversa limitada e depois curva essa reta com a cúbica 3t² − 2t³:

float t = clamp((x - e0) / (e1 - e0), 0.0, 1.0);
float s = t * t * (3.0 - 2.0 * t); // o que smoothstep(e0, e1, x) devolve

A curva é plana nas duas pontas, então a transição se funde com as regiões em volta sem nenhuma quina visível. Uma transição estreita dá uma borda sem serrilhado; uma larga dá um brilho ou um esmaecimento. Com p sendo a coordenada centralizada e com a proporção corrigida de UV e proporção de tela:

float d = length(p);
float disc = 1.0 - smoothstep(0.49, 0.51, d); // um disco nítido de raio 0.5
float glow = 1.0 - smoothstep(0.0, 1.0, d);   // um esmaecimento suave a partir do centro

A especificação do GLSL deixa o resultado indefinido quando e0 não é menor que e1, então uma borda invertida que parece certa numa GPU pode quebrar em outra. Para uma borda que desce, escreva 1.0 - smoothstep(e0, e1, x) em vez de trocar as bordas.

Quão estreito é estreito? Uma largura fixa como 0.02 dá mais ou menos um pixel num tamanho de tela e fica borrada ou serrilhada em outro. A resposta robusta mede um pixel nas unidades de d com derivadas de espaço de tela (fwidth), que ganham um fundamento próprio mais adiante.

Leitura complementar