Operações de domínio em SDF
Repita, espelhe, arredonde, esvazie e estique um campo de distância mudando o ponto ou a distância, não a forma.
Como funciona
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 é uma função de um ponto. Daí saem dois tipos de mudança. Mude o ponto antes
que a função o leia e você consegue repetir uma forma ao infinito, espelhá-la ou esticá-la, pelo custo de uma única
avaliação. Mude a distância depois e você consegue arredondar ou esvaziar a forma. Juntas, essas são as operações de
domínio, e é por causa delas que uma cena feita com raymarching consegue mostrar mil pilares avaliando um só.
Repetição infinita
O ladrilhamento com fract, do fundamento de fract e mod, dá a cada célula uma coordenada própria de 0 a 1. Para
campos de distância, a coordenada da célula deve ser centralizada, para que a forma fique no meio de cada célula:
vec2 spacing = vec2(0.5);
vec2 id = round(p / spacing); // qual célula: coordenadas inteiras, (0, 0) na origem
vec2 q = p - spacing * id; // posição dentro da célula, -spacing/2..spacing/2
float d = sdCircle(q, 0.1); // um círculo, repetido em todas as células
round(p / spacing) escolhe o centro de célula mais próximo, e subtraí-lo deixa o deslocamento a partir desse centro.
É o mesmo que mod(p + 0.5 * spacing, spacing) - 0.5 * spacing, escrito de um jeito que também entrega o id. Uma
forma repetida não custa mais que uma só.
O id diferencia as cópias. Passe-o por um hash para ter um tamanho, uma cor ou uma rotação aleatória por célula, ou
use-o direto, como em 0.05 + 0.02 * id.x, para raios que crescem ao longo da fileira.
Quando a repetição quebra
O truque supõe que a forma mais próxima é sempre a da própria célula do pixel. Isso vale quando todas as cópias são iguais e cabem na célula com folga. Falha quando uma forma cruza a borda da célula (ela é cortada na borda) ou quando as cópias diferem, porque aí a forma maior de uma vizinha pode estar mais perto que a forma menor da sua própria célula. A distância fica errada perto das bordas: em 2D as formas aparecem cortadas e, em 3D, o raymarching passa do ponto e as superfícies ganham buracos.
A solução é avaliar as vizinhas também e ficar com o mínimo:
float d = 1e10;
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
vec2 cell = id + vec2(float(x), float(y));
float r = 0.1 + 0.1 * hash21(cell); // um raio diferente por célula
d = min(d, sdCircle(p - spacing * cell, r));
}
}
Nove avaliações em vez de uma. Dependendo de quanto as cópias variam, uma busca menor pode bastar; o artigo sobre repetição de domínio nas fontes explica quando.
Repetição limitada
Para repetir um número fixo de vezes, limite o índice da célula:
vec2 id = clamp(round(p / spacing), vec2(-2.0), vec2(2.0)); // 5 × 5 cópias
vec2 q = p - spacing * id;
Fora do intervalo, o id fica preso na última célula, então a distância continua crescendo corretamente para longe da
grade em vez de se repetir.
Espelhamento
O abs dobra o espaço sobre si mesmo. p.x = abs(p.x) faz tudo à esquerda do eixo y virar um espelho do lado
direito, então uma forma desenhada em x = 0.3 aparece em x = 0.3 e em x = -0.3. O abs nos dois eixos dá
simetria em quatro direções, o truque que a própria função da caixa usa. O espelhamento preserva as distâncias, então o
resultado continua exato, desde que a forma não cruze a linha do espelho.
Arredondamento, casca e alongamento
Estas operações mudam a distância, ou esticam o espaço, em vez de repeti-lo:
// cresce a forma em 0.05 e arredonda os cantos convexos
float rounded = d - 0.05;
// uma casca de 0.04 de espessura ao longo da borda: um anel a partir de um disco
float onion = abs(d) - 0.02;
// o círculo esticado 0.6 ao longo de x
vec2 e = p - clamp(p, -vec2(0.3, 0.0), vec2(0.3, 0.0));
float pill = sdCircle(e, 0.1);
A casca pode ser aplicada de novo sobre o próprio resultado, para cascas concêntricas. O alongamento divide a forma no
centro e afasta as metades: dentro da faixa esticada, e.x vale zero, então todo ponto ali vê a linha central do
círculo e o meio é preenchido com um trecho reto. Ao contrário da escala não uniforme, ele mantém exata a distância do
lado de fora.
As transformações do fundamento de transformações 2D também são operações de domínio: transladar e girar p movem a
forma sem quebrar a distância. A repetição polar, cópias em volta de um círculo, funciona como a repetição em grade,
com ângulo e raio no lugar de x e y.