b3d-black-hole

Procedural black hole with accretion disk, gravitational lensing effect, and photon ring. Inspired by the Interstellar visualization — not physically accurate but visually striking.

Demo

import { b3d, b3dLight, b3dBlackHole, b3dSphere, label3d, slider3d, toggle3d } from 'tosijs-3d'
import { tosi, elements } from 'tosijs'
const { div, p } = elements

const { demo } = tosi({
  demo: {
    radius: 10,
    diskInnerRadius: 1.05,
    diskOuterRadius: 3.0,
    diskBrightness: 1.5,
    rotationSpeed: 0.3,
    lensing: true,
    photonRing: true,
    photonRingBrightness: 2.0,
    wireframe: false,
  },
})

const hole = b3dBlackHole({
  radius: demo.radius,
  diskInnerRadius: demo.diskInnerRadius,
  diskOuterRadius: demo.diskOuterRadius,
  diskBrightness: demo.diskBrightness,
  rotationSpeed: demo.rotationSpeed,
  lensing: demo.lensing,
  photonRing: demo.photonRing,
  photonRingBrightness: demo.photonRingBrightness,
  wireframe: demo.wireframe,
})

const scene = b3d(
  {
    frameRate: 60,
    clearColor: '#000000',
    scenePanel: () => [
      label3d({ text: 'Black hole' }),
      slider3d({ label: 'radius', value: demo.radius, min: 3, max: 20, step: 0.5 }),
      slider3d({ label: 'disk inner', value: demo.diskInnerRadius, min: 1.01, max: 2, step: 0.01 }),
      slider3d({ label: 'disk outer', value: demo.diskOuterRadius, min: 2, max: 8, step: 0.1 }),
      slider3d({ label: 'disk brightness', value: demo.diskBrightness, min: 0.5, max: 3, step: 0.1 }),
      slider3d({ label: 'rotation', value: demo.rotationSpeed, min: 0, max: 1, step: 0.01 }),
      slider3d({ label: 'photon ring bright', value: demo.photonRingBrightness, min: 0.5, max: 5, step: 0.1 }),
      toggle3d({ label: 'lensing', value: demo.lensing }),
      toggle3d({ label: 'photon ring', value: demo.photonRing }),
      toggle3d({ label: 'wireframe', value: demo.wireframe }),
    ],
    sceneCreated(el, BABYLON) {
      const camera = new BABYLON.ArcRotateCamera(
        'orbit-cam',
        -Math.PI / 2,
        Math.PI / 3,
        80,
        BABYLON.Vector3.Zero(),
        el.scene
      )
      camera.lowerRadiusLimit = 20
      camera.upperRadiusLimit = 300
      camera.minZ = 0.5
      camera.maxZ = 2000
      camera.attachControl(el.querySelector('canvas'), true)
      el.setActiveCamera(camera)
    },
  },
  b3dLight({ intensity: 0.05 }),
  hole,
  b3dSphere({ radius: 3, x: 50, y: 0, z: 0, color: '#888888' }),
)

preview.append(
  scene,
  div({ class: 'debug-panel' }, p('Scroll to zoom, drag to orbit'))
)

for (const key of ['radius', 'diskInnerRadius', 'diskOuterRadius']) {
  demo[key].observe(() => {
    hole.regenerate()
  })
}
for (const key of ['diskBrightness', 'rotationSpeed', 'lensing', 'photonRing', 'photonRingBrightness', 'wireframe']) {
  demo[key].observe(() => {
    hole.updateOptions()
  })
}
tosi-b3d {
  width: 100%;
  height: 100%;
}
.debug-panel {
  position: absolute;
  top: 8px;
  right: 8px;
  background: rgba(0,0,0,0.6);
  color: white;
  padding: 8px 12px;
  border-radius: 6px;
  font: 12px monospace;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

Attributes

Attribute Default Description
radius 10 Event horizon radius
diskInnerRadius 1.5 Inner edge of accretion disk (multiple of radius)
diskOuterRadius 4.0 Outer edge of accretion disk (multiple of radius)
diskBrightness 1.5 Accretion disk emissive brightness
rotationSpeed 0.3 Disk rotation speed (rad/sec)
lensing true Show gravitational lensing ring
photonRing true Show photon ring at event horizon
photonRingBrightness 2.0 Photon ring glow intensity
wireframe false Debug wireframe
seed 12345 Noise seed for disk turbulence
subdivisions 64 Mesh detail level (lower = faster)