3

Como fiz um buraco negro com lente gravitacional no navegador (Three.js e GLSL)

Reformei meu portfólio e a página inicial virou um buraco negro que dá para girar e aproximar. Aqui eu explico como a parte da física funciona, porque achei mais simples do que parecia.

Cada pixel é um raio de luz

Em vez de desenhar uma imagem de buraco negro, o shader dispara um raio de luz para cada pixel e vai curvando o caminho dele passo a passo. Usei a unidade em que o raio de Schwarzschild vale 1, e a aceleração que curva o raio é -1,5 · h² · r̂ / r⁴, onde h é o momento angular do raio. Isso dá a mesma equação da órbita da luz na métrica de Schwarzschild e coloca a esfera de fótons em 1,5 de raio.

vec3 vel = dir;
float h2 = dot(cross(pos, dir), cross(pos, dir));
for (int i = 0; i < 140; i++) {
  float r2 = dot(pos, pos);
  if (r2 < 1.0) break;                        // caiu no horizonte: pixel preto
  float dt = max(0.05, 0.1 * (sqrt(r2) - 0.9));
  vel += (-1.5 * h2 * pos / (r2 * r2 * sqrt(r2))) * dt;
  vec3 np = pos + vel * dt;
  if (pos.y * np.y < 0.0) {
    // o raio cruzou o plano do disco: pega a cor do disco aqui
  }
  pos = np;
}

O arco de luz que passa por cima do buraco negro não é desenhado. Ele aparece porque os raios que passam atrás do disco são dobrados e cruzam o disco de novo.

O disco

Cada ponto onde o raio cruza o plano do disco recebe uma cor que depende do raio da órbita: branco-dourado por dentro e vermelho por fora. O disco gira com velocidade proporcional a r^(-3/2), então gira mais rápido perto do centro. Para o efeito Doppler usei uma aproximação simples: o lado que se aproxima de você fica mais brilhante. Não é a relatividade completa.

Kepler nos projetos

Os três projetos principais orbitam em elipses com o buraco negro no foco. A posição vem da equação de Kepler, que resolvo com o método de Newton:

function eccentricAnomaly(M: number, e: number) {
  let E = e < 0.8 ? M : Math.PI;
  for (let i = 0; i < 6; i++) E -= (E - e * Math.sin(E) - M) / (1 - e * Math.cos(E));
  return E;
}

O período de cada órbita segue T² ∝ a³. Conferi numericamente que a área varrida em intervalos iguais de tempo é a mesma, que é a segunda lei.

Dilatação do tempo

Para quem fica parado a uma distância r (em raios de Schwarzschild), 1 hora aqui vale 1 / √(1 − 1/r) horas lá longe. Quando você aproxima o zoom, o site mostra esse número. Na animação de queda a câmera para antes do horizonte, então o número que continua caindo na tela escura é uma encenação.

Limitações

O shader é pesado, principalmente no celular, e por isso o site usa menos passos de cálculo em telas pequenas. A luz de fundo e as estrelas não são curvadas pela lente.

O resultado está em https://tobiasteles.dev. Se alguém quiser conversar sobre o shader ou sobre as órbitas, é só comentar.

Carregando publicação patrocinada...
1

Ótima discussão técnica. Para quem trabalha com APIs que recebem chamadas tanto de browser quanto de CLI/curl, suportar text/plain e application/x-www-form-urlencoded além de application/json reduz bastante fricção de integração.