logo
GravityLens

rendering · physics-puzzle

GravityLens

Listens toAxisViewCamera
2
Unlock

Install with the CLI:

bjs download GravityLens

GravityLens

A starfield that warps around black holes, as real gravity bends light.

What it does

GravityLens: {} on the world entity draws a nebula-and-stars sky and, each frame, bends it around each GravityMass marked lenses. Drag a black hole and the stars behind it smear into arcs that follow it. Raise its mass and the arcs close into a ring, with a thin bright ring hugging the hole's black shadow.

The bend happens in the sky's own shader, not in a post-process: it turns each pixel back into a direction, bends that around each hole, then looks up the sky there. A post-process would warp the rocks too; this bends only what lies behind them. The same shader draws the stars from the bent direction, so they stay sharp points where stars baked into a texture would turn into squares.

A hole's bend is its Einstein angle, θE, which grows as √(mass ÷ distance from the camera):

TypeScript
einsteinAngle(600, 40, 0.022);  // mass, distance, strength: 0.085 rad, about 5°
einsteinAngle(2400, 40, 0.022); // 0.170: four times the mass, twice the bend
einsteinAngle(-600, 40, 0.022); // -0.085: a white hole bends the other way

A pixel at angle θ from a hole shows the sky from angle β = θ − θE²/θ. Inside θE, β goes below zero: that light came from the far side of the hole, so a second, flipped image hugs the shadow. A white hole's mass is below zero too, which flips the bend: it pushes stars out of a disc, with no shadow and no ring.

Setup

The engine work ships with the component, in adapter/register.ts. A project made with bjs create installs it for you: src/main.ts calls every adapter/register.ts that bjs download puts in the project, before the scene loads. If you boot the game from your own code, call it once, right after building the adapter:

TypeScript
import { registerGravityLensExtension } from './Components/GravityLens/adapter/register';

const adapter = new BabylonAdapter();
registerGravityLensExtension(adapter);
const game = new ArcadeGame(adapter, { … });

Without it there is no sky and the System warns once.

Use it in a scene

A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.

Build it from scratch with the bjs CLI:

  1. 1Scaffold a project
    npm create @babylonjsmarket/arcade@latest my-game

    World, renderer, and dev server — ready to run.

  2. 2Install dependencies
    cd my-game && npm install
  3. 3Add GravityLens
    bjs download GravityLens

    Copies its source into src/ so the scene resolves.

    First time? Run bjs login once.

  4. 4Paste the scene into src/scenes/arcade-room.ts and run
    npm run dev

    SceneLoader builds the world from the JSON; reload to rebuild.

JSON
{
  "World": {
    "tags": ["world"],
    "components": {
      "GravityLens": { "seed": 21, "densityThreshold": 0.38, "size": 256 }
    }
  },
  "Camera": {
    "tags": ["camera"],
    "components": {
      "AxisViewCamera": { "view": "top", "size": [72, 32, 32], "fov": 0.6 }
    }
  },
  "Hole": {
    "components": {
      "MeshPrimitive": {
        "primitive": "sphere",
        "diameter": 2,
        "position": [-14, 0, -12],
        "material": { "diffuseColor": [0, 0, 0], "specularColor": [0, 0, 0] }
      },
      "GravityMass": { "mass": 450, "radius": 1, "absorbs": true }
    }
  },
  "Fountain": {
    "components": {
      "MeshPrimitive": {
        "primitive": "sphere",
        "diameter": 1.8,
        "position": [0, 0, 6],
        "material": { "emissiveColor": [1, 1, 1] }
      },
      "GravityMass": { "mass": -400, "radius": 0.9, "lenses": true }
    }
  }
}

This bare white hole needs lenses: true. The flag defaults to absorbs, which is true for black holes and false for rocks and white holes. The WhiteHole and BlackHole components set it for you.

Props

  • size (number, default 512) — face size of the sky cube, in texels.
  • seed (number, default 0x5ca1e) — seeds the nebula noise.
  • starCount (number, default 0) — stars baked into the sky texture. They turn square when magnified, so they suit only a wide camera.
  • starDensity (number, default 1) — how many shader stars. More stars show the bend better.
  • starBrightness (number, default 1) — how bright the shader stars are.
  • starSeed (number, default 0xbadc0de) — seeds the baked stars.
  • galaxyCount (number, default 26) / galaxySeed (default 0xfeed5).
  • nebulaScale (number, default 1.4) — nebula noise frequency. Higher means smaller clouds.
  • densityThreshold (number, default 0.42) — nebula thinner than this reads as empty space.
  • intensity (number, default 0.7) — peak nebula brightness, 0 to 1.
  • skyboxSize (number, default 2000) — world size of the sky cube.
  • strength (number, default 0.022) — scales every Einstein angle. Raise it to make the bend read from further away.
  • ringIntensity (number, default 0.9) — brightness of the ring at the shadow's edge.
  • ringColor (RGB, default [1, 0.78, 0.52]) — colour of that ring.

Events

  • camera.active.changed (listens) — the camera to measure from. Until one arrives, the sky is unbent.

Emits nothing.

Dependencies

  • GravityMass — each hole's mass, radius and lenses flag. The radius sizes the shadow.
  • MeshPrimitive — where each hole is. A mass with no mesh is skipped.
  • NebulaSky — its generator paints the sky. No NebulaSky entity is needed.
  • A camera that sends camera.active.changed, such as AxisViewCamera.

Notes

  • At most 8 holes reach the shader, ranked by how far they bend, white holes included. A heavy far hole can outrank a light near one.
  • Only strength and ringIntensity are read each frame. The rest are read once, when the sky is built.
  • skyboxSize must fit inside the camera's far plane. The default 2000 fits AxisViewCamera's 4000.
  • The plugin is Babylon only. Under Three.js or Babylon Lite there is no sky.

More like this

AxisViewCamera
GravityMass
MeshPrimitive
NebulaSky
BlackHole
BoatMode
BoatWater
Bumper
CaptureZone
ContactDamage
ContactImpulse
DirectionalLight
EnvironmentTexture
Flash
GravityBody
Hand
HemisphericLight
LensFlare
MassDriver
Mesh
MotionTrail
MouseHole
Obstacle
Physics
PointerDrag
Renderable
RoadTreadmill
Shadow
ShipDamageFX
Skybox
SmokeScreen
SpaceDust
SpaceGolfDirector
SpaceGolfHUD
SpaceRock
SpaceShooterFX
Spin
Spinner
SurfacePatch
ToneMapping
TrajectoryPreview
Velocity6DOF
WhiteHole
Wormhole

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search