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ânciaUma função que devolve a distância de um ponto até a borda de uma forma, negativa dentro e positiva fora. O zero é o próprio contorno. Campo de distância com sinal (SDF) Uma função que devolve a distância de um ponto até a borda de uma forma, negativa dentro e positiva fora. O zero é o próprio contorno.circle(p) = |p| − r é 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 Mínimo suave Uma versão do min que arredonda o canto onde dois valores se cruzam. Aplicado a dois campos de distância, funde as formas numa só, com a largura da fusão definida por k.min comum se encontra num vinco afiado onde as duas distâncias são iguais. Um
mínimo suaveUma versão do min que arredonda o canto onde dois valores se cruzam. Aplicado a dois campos de distância, funde as formas numa só, com a largura da fusão definida por k.h = clamp(0.5 + 0.5 (b − a) / k, 0, 1); smin = mix(b, a, h) − k h (1 − h)
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);
}
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.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 mixMistura 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 tk / 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.