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.