Turn historical stories into little hologram scenes.
Try it at https://hologram.aditbajaj.com/. It works normally in a browser, but the four-way view needs a clear pyramid reflector sitting on the screen.
|
|
|
| Anzac Cove soldiers | Four cars being mirrored for the reflector |
It is written in TypeScript, React and Three.js.
I wanted something that feels a little more alive: models appearing in the dark, a camera moving through the scene, narration and sound carrying the story, and the whole thing reflecting inside a physical display. Although what was made here is... not that good tbh..
It is not really a hologram floating in the air. It uses the old Pepper's ghost trick: bright objects on a black screen reflect off clear plastic while the background mostly disappears. Put four views around a clear pyramid and you get a tiny fake museum display on a desk :)
The scenes are plain YAML. (annoying cause it took up most of the damn code).
- Normal 3D view and four-way reflector output
- YAML scenes with timed actors, cameras and chapters
- A mastered soundtrack with SRT subtitles
- Play, pause, scrubbing and chapter jumping
- GLB/GLTF models with optional colour and glow changes
- Configurable lights, bloom and scene colours
- Runtime scene validation instead of quietly accepting broken YAML
- An ANZAC Cove story and a smaller car scene
The app loads a scene from public/scenes, validates it, then runs the whole thing from one clock measured in seconds. The models, camera, chapter label, soundtrack and subtitles all follow that clock, so pausing and scrubbing move everything together.
Normal mode is a regular Three.js view. While paused you can move the camera around. Reflector mode renders one canvas and mirrors its stream into the other three sides, which is much lighter than rendering the same models four times.
Scenes can be linked directly with ?scene=cars. Without that, the first scene in public/scenes/index.yaml opens.
The models and MP3 files use Git LFS, so make sure it is installed first.
git clone https://github.com/intercepted16/history-hologram
cd history-hologram
git lfs pull
bun install
bun run devdevBun works too:
bun install
bun run devvOther useful commands:
bun run build
bun run lint
bun run preview
bun run model:removeoveThere is no server or database. Vite builds the whole thing as a static site.
- Open a scene and select Reflector ×4.
- Place a clear four-sided pyramid in the centre of the screen.
- Select Clean output to hide the controls.
- Turn the brightness up and dim the room.
- Line the pyramid up with the black square in the middle.
Black backgrounds, bright edges and a clean bit of plastic help a lot. Normal mode is much easier while working on a scene.
Physical setup with an iPad; use anything though
Add the scene to the catalog:
# public/scenes/index.yaml
scenes:
- id: my-scene
title: My Scene
path: /scenes/my-scene.yamlThen make the YAML file. This is the general shape, shortened a bit so this README does not become the documentation for YAML itself:
id: my-scene
title: My Scene
date: SOME TIME
place: SOMEWHERE
duration: 20
soundtrack: /audio/story-mix.mp3
palette:
glow: "#66e7ff"
ember: "#ff6b4a"
background: "#02070c"
camera:
position: [0, 1.2, 5]
target: [0, 0.4, 0]
fov: 35
cues: []
lighting:
ambient: { color: "#ffffff", intensity: 1.2 }
hologram:
opacity: 1
bloom: 1.2
moments:
- at: 0
label: START
narration:
- at: 0
duration: 10
text: Something happened here.
subtitles: /subtitles/start.srt
actors:
- id: hero
src: /models/hero.glb
position: [0, 0, 0]
opacity: 0
cues:
- at: 1
duration: 2
opacity: 1
positionBy: [0, 0, -0.5]All timing is in seconds. Actor cues can set an exact transform or add to the previous one with positionBy, rotationBy, scaleBy and opacityBy. Moments become the chapter menu. Optional shots decide which actors appear in each chapter.
Asset URLs start at public, so /models/hero.glb means public/models/hero.glb. Subtitle timestamps are relative to their narration block rather than the whole scene.
The small example is public/scenes/cars.yaml. The everything-at-once example is public/scenes/anzac-dawn.yaml. The exact accepted fields are in src/lib/scene/schema.ts.
GLB is easiest because it normally keeps the model and textures in one file. Models are automatically brought to roughly the same size before the YAML scale is applied, because every downloaded model apparently uses a different idea of a metre.
If a GLB has an unwanted node, mesh or material, this little tool removes it after asking for confirmation:
bun run model:removeThe ANZAC narration, music and effects are mixed into one MP3. The original pieces are in sources/audio; the browser only loads the finished mix from public/audio.
Reflector mode needs browser support for WebGL and canvas.captureStream(). If normal mode works but the other three faces are black, that is probably where to look.
The models, audio, etc have no licenses here.
ChatGPT was used to help debug errors, write some implementation snippets and assemble much of the ANZAC scene YAML.
- React for the interface
- Three.js and React Three Fiber for the 3D renderer
- Drei for useful Three.js helpers
- React Postprocessing for bloom and visual effects
- YAML and ArkType for readable, validated scene files
- Howler for soundtrack playback