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
Coordenadas UV Uma posição 2D numa superfície ou na tela, em geral de 0 a 1 nos dois eixos. Shaders desenham formas e padrões como funções dela.step(edge, x) devolve 0.0 quando x está abaixo de edge e 1.0 nos outros casos. Aplicado a uma coordenada
UVUma posição 2D numa superfície ou na tela, em geral de 0 a 1 nos dois eixos. Shaders desenham formas e padrões como funções dela.uv = gl_FragCoord.xy / resolution
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
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.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 lerpMistura 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
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
- step (abre em uma nova aba)Referência
- smoothstep (abre em uma nova aba)Referência
- Shaping functions (abre em uma nova aba)Livro