Mandelbrot infinito

carregando…

Arraste para navegar · role para dar zoom · duplo clique aproxima (com shift, afasta) · pinça no celular · teclado: setas, +, e 0 para reiniciar

O que é isso?

O conjunto de Mandelbrot é o exemplo mais famoso de fractal: um objeto com detalhe infinito. Cada ponto c do plano complexo entra ou não no conjunto conforme a iteração z → z² + c (começando com z = 0) fica limitada ou escapa para o infinito. As cores mostram quantas iterações cada pixel levou para escapar.

A graça é dar zoom: não importa o quanto você aproxime, sempre há estrutura nova. Este app foi feito para aguentar esse “sempre” — o zoom não para em 100.000×, vai até onde sua paciência aguentar (o limite configurado é 10^1200).

Como funciona

A GPU faz o trabalho pesado (WebGL)

Cada pixel é independente dos demais: é o cenário perfeito para a GPU. A conta z → z² + c roda num fragment shader WebGL2, milhões de pixels em paralelo. Para não travar o navegador em cenas pesadas, as iterações rodam em blocos: o estado de cada pixel (z, contagem, status) fica guardado em texturas float que fazem ping-pong entre passadas, e a imagem vai se completando progressivamente na tela.

O problema: float32 acaba rápido

A GPU só calcula nativamente com float32, que tem ~7 dígitos de precisão. Por volta de zoom 10⁵, pixels vizinhos passam a ter o mesmo valor de c e a imagem vira blocos. Precisão dupla no shader não existe em WebGL, e emular precisão arbitrária por pixel na GPU seria lento demais.

A saída: teoria de perturbação

O truque (K. I. Martin, 2013) é separar o problema em duas partes:

  1. Uma órbita de referência Z₀, Z₁, Z₂, … é calculada com precisão arbitrária na CPU — aqui, aritmética de ponto fixo com BigInt, num web worker para não travar a página. São os únicos números “caros” de todo o processo.
  2. Cada pixel só acompanha a diferença δ = z − Z para a referência. A álgebra fecha bonito: δ′ = 2·Z·δ + δ² + δc. Essa recorrência só usa números pequenos e roda inteira em float na GPU.

Ou seja: a precisão arbitrária entra uma vez por imagem (na referência), e a GPU segue trabalhando com números baratos. É o mesmo esquema dos renderizadores sérios de deep zoom, como Kalles Fraktaler e fraktaler-3.

Quando a órbita do pixel se aproxima demais de zero (perto de um minibrot) ou sobrevive além do fim da referência, o pixel sofre rebase (técnica do usuário Zhuoran, 2021): o δ vira o valor completo e volta a acompanhar a referência do início. Isso elimina os “glitches” clássicos da perturbação sem precisar de múltiplas referências.

E quando nem o expoente do float32 aguenta?

Em zoom além de ~10²⁷, o δ de um pixel é menor que o menor float32 positivo (~10⁻³⁸) — o número vira zero antes de perder precisão. A solução é o formato floatexp: mantissa float32 + expoente inteiro separado, com soma e multiplicação implementadas à mão no shader. É mais lento, então o app escolhe o modo conforme a profundidade:

zoom modo
até ~10³ float32 direto
até ~10²⁶ perturbação com δ em float32
além perturbação com δ em floatexp

Detalhes de engenharia

  • O centro da imagem vive em BigInt de ponto fixo (a precisão cresce com o zoom), então arrastar a 10^500 continua exato.
  • Enquanto você arrasta ou dá zoom, a última imagem pronta é reprojetada (transformada afim) como preview; o render de verdade recomeça quando o gesto assenta.
  • A referência é reaproveitada entre movimentos próximos: dar zoom perto do mesmo ponto não recomputa a órbita.
  • A órbita de referência também é guardada em floatexp na textura — perto de minibrots ela passa tão perto de zero que float32 puro descartaria exatamente a informação de que o rebase precisa.
  • O link do botão “compartilhar” carrega as coordenadas completas em hexadecimal, com quantos dígitos forem necessários.

Limites honestos

Perto da fronteira do conjunto a dinâmica é caótica: com milhares de iterações, a contagem exata de escape de um pixel depende da precisão usada — em qualquer renderizador em ponto flutuante. A estrutura da imagem é a mesma; faixas de cor individuais podem diferir de renders feitos com outra aritmética. Em profundidade extrema com muitas iterações, o render leva alguns segundos (a imagem vai aparecendo aos poucos). Aproximação por séries (BLA) e antialiasing ficam para uma próxima versão.

Referências

  • K. I. Martin, SuperFractalThing (2013) — a formulação original da perturbação.
  • Zhuoran, Another solution to perturbation glitches (fractalforums, 2021) — o rebasing.
  • Claude Heiland-Allen, fraktaler-3 e o blog mathr.co.uk — floatexp e muito do estado da arte.
  • Inigo Quilez — as paletas por cosseno.