Tomando o seu exemplo, você tem uma função passo da distância, que produz uma aresta perfeitamente rígida (alias). Uma maneira simples de suavizar o círculo seria transformá-lo em um limiar suave, como:
float distFromEdge = 1.0 - dist; // positive when inside the circle
float thresholdWidth = 0.01; // a constant you'd tune to get the right level of softness
float antialiasedCircle = saturate((distFromEdge / thresholdWidth) + 0.5);
return lerp(outsideColor, insideColor, antialiasedCircle);
Aqui eu usei uma rampa linear fixa para uma função de limite suave, mas você também pode usar smoothstep
ou outra coisa. O + 0.5
é centralizar a rampa na localização matemática da aresta. De qualquer forma, o importante é que essa função mude suavemente de outsideColor
para insideColor
algumas distâncias; portanto, se você escolher thresholdWidth
adequadamente, obterá uma vantagem de aparência antialias.
Mas como você deve escolher thresholdWidth
? Se for muito pequeno, você receberá o alias novamente e, se for muito grande, a borda ficará muito embaçada. Além disso, geralmente dependerá da posição da câmera: se dist
for medido em unidades do espaço mundial ou do espaço da textura, um thresholdWidth
que funcione para uma posição da câmera estará errado para outra.
Aqui é onde os derivados do espaço da tela entram (sim, eles são ddx
e ddy
funcionam como você adivinhou). Ao calcular o comprimento do gradiente, dist
você pode ter uma idéia da rapidez com que está mudando no espaço da tela e usá-lo para estimar thresholdWidth
, como:
float derivX = ddx(distFromEdge);
float derivY = ddy(distFromEdge);
float gradientLength = length(float2(derivX, derivY));
float thresholdWidth = 2.0 * gradientLength; // the 2.0 is a constant you can tune
Você ainda tem um valor que pode ajustar para obter o nível desejado de suavidade, mas agora deve obter resultados consistentes, independentemente da posição da câmera.
derivX
ederivY
realmente representam.