Pular para o conteúdo
Fundamento · Campos de distânciaIntermediário

Booleanas de SDF e mínimo suave

Combine campos de distância com min e max para união, interseção e subtração, e funda as formas com um mínimo suave.

Como funciona

Formas isoladas são só o começo. Como um campo de distância é apenas um número por pixel, as formas se combinam com matemática comum: min, max e um sinal de menos dão as três operações booleanas da geometria sólida construtiva, e uma versão suave do min mistura as formas como se fossem de massinha. Tudo aqui funciona do mesmo jeito em 2D e em 3D.

União, interseção, subtração

Dadas duas distâncias a e b:

float unite     = min(a, b);   // dentro de qualquer uma das formas
float intersect = max(a, b);   // dentro das duas formas
float subtract  = max(a, -b);  // dentro de a, mas fora de b

A união é min porque um ponto está dentro da união quando está dentro de qualquer uma das formas, ou seja, quando qualquer uma das distâncias é negativa, e a borda mais próxima é a mais próxima das duas. A interseção é max: estar dentro das duas significa que as duas são negativas. Negar uma distância vira a forma do avesso (dentro vira fora), então subtrair b é fazer a interseção com “não b”. A subtração é a única das três em que a ordem importa.

float d = min(sdCircle(p - vec2(-0.25, 0.0), 0.3),
              sdBox(p - vec2(0.25, 0.0), vec2(0.2)));
// um círculo e um quadrado como uma forma só
float shape = 1.0 - smoothstep(-px, px, d);

Onde a distância continua exata

Essas operações colocam a borda exatamente onde ela deve ficar, mas longe da borda o valor nem sempre é a distância real. A união é exata fora das duas formas; dentro, onde elas se sobrepõem, ela subestima a profundidade de um ponto. A interseção e a subtração são exatas dentro, mas só um limitante fora: perto dos cantos que elas criam, a distância real é maior que o valor devolvido. Um preenchimento ou um contorno fino não se importa. Qualquer coisa que leia o valor longe da borda se importa. Fazer a forma crescer com d - r arredonda direito os cantos de uma união, mas deixa afiados os cantos criados por uma interseção ou subtração, porque a distância de fora está errada ali; os brilhos também ficam irregulares em volta desses cantos e, no raymarching 3D, um limitante custa passos extras.

Mínimo suave

Um min comum se encontra num vinco afiado onde as duas distâncias são iguais. Um mínimo suave arredonda esse vinco, então formas próximas se esticam uma em direção à outra e se fundem como gotas de líquido:

float smin(float a, float b, float k) {
    // 1 onde a vence, 0 onde b vence
    float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
    return mix(b, a, h) - k * h * (1.0 - h);
}

h é um peso de mistura: 1 quando a é claramente menor, 0 quando b é, e intermediário só onde as duas distâncias estão a menos de k uma da outra. O mix mistura as duas distâncias por ele, e o último termo puxa o resultado para baixo na zona de mistura, para que a junção se estufe num filete. Esse afundamento é máximo onde as distâncias são iguais, k / 4, e some nas pontas da zona, então a função é igual ao min onde quer que as duas distâncias difiram em mais de k.

k fica em unidades de distância: é o tamanho do filete. Perto de zero ele se comporta como o min; valores maiores dão uma junção mais grossa e mais pegajosa. Animar as posições das formas com um k fixo dá o visual clássico de metaballs.

O mesmo h mistura qualquer outra coisa entre as duas formas. Misturar as cores delas por ele dá uma transição suave de cor ao longo da junção, o que um min duro não consegue:

// calcule h como no smin, a partir dos mesmos a, b e k
vec3 color = mix(colorB, colorA, h);

Interseção e subtração suaves

O máximo suave sai do mínimo suave negando, do mesmo jeito que max(a, b) é igual a -min(-a, -b):

float smax(float a, float b, float k) { return -smin(-a, -b, k); }

float carved = smax(a, -b, k); // a, com b escavado e a borda arredondada

Uma subtração suave arredonda a beirada do buraco em vez de deixar uma quina viva, o formato de um chanfro ou de uma mordida.

Outras misturas

Existem muitos mínimos suaves: exponenciais, baseados em potência, cúbicos e circulares, que diferem no perfil da junção e no alcance da influência. O quadrático acima é o mais barato e o mais comum; o único defeito é que a curvatura do filete dá um salto nas pontas da zona de mistura, o que é invisível no sombreamento na maioria dos tamanhos. Nenhum deles devolve uma distância exata dentro da zona de mistura.

Transformar uma forma em outra é uma operação diferente: mix(a, b, t) interpola os próprios campos de distância, e a borda zero se move suavemente de um contorno para o outro conforme t vai de 0 a 1. Funciona melhor entre formas convexas de tamanho parecido; entre formas muito diferentes, a forma do meio do caminho pode se partir.

Leitura complementar