@BORKISS/TRE*
v0.3.0 · MIT · THREE.JS

// a render engine, not a font trick

any graphic, as living ASCII on a CRT.

three.js scenes · images · video · your webcam · 2D canvases — re-drawn as phosphor glyphs every frame. scroll-driven stories, audio reactivity, zero postprocessing deps.

watch DEEP FIELD →

agents: your entry point is /start.md — quickstart, API, recipes. the button copies a ready prompt.

// what it is

one idea: render small, stamp glyphs big.

the scene is drawn into a buffer with ~one texel per character cell, MSAA-averaged — then a fullscreen pass stamps a glyph per cell by luminance, phosphor persistence smears the bright ones, and a CRT pass bends, glows and scans the result. that first trick makes the expensive pass ~two orders of magnitude smaller than naïve full-res rendering.

source ──► grid buffer ──► ASCII pass ──► persistence ──► CRT pass ──► screen
scene,     ~cols × rows    glyph per     phosphor         curvature, glow,
image,     MSAA ×4         cell by       afterglow        scanlines, glitch,
video…                     luminance                      power envelope
~9kB
gzipped, the whole engine. peer dep: three
4
tiny hand-rolled fullscreen passes. no composer, no postprocessing libs
1/cell
texture sample per character — idle costs ~nothing
60fps
frame-capped, dpr-clamped, auto-pauses offscreen
// showcases — every tube is live
same engine, eight looks
01

live scene

a real three.js scene — geometry, camera, lighting — re-drawn as text every frame. drag your pointer across it.

createAsciiArt({ canvas, source: { scene, camera, update } })
02

colour image

any image, in full colour — each glyph takes the source's own chroma. slide back to pure phosphor.

art.frame.colorize = 1
03

2D canvas, flowing

a plain 2D canvas being redrawn — plasma in this case — piped through the same pipeline. persistence turns motion into phosphor trails.

createAsciiArt({ canvas, source: my2dCanvas })
04

webcam mirror

you, at sixty frames a second, in phosphor glyphs. the closest thing to looking into a 1978 monitor.

source: (await createWebcamSource()).video
05

glyph sets

the "font" is a runtime-built atlas from any string, dark → bright. swap it live; bring your own characters.

art.setRamp(RAMPS.katakana)
06

phosphor

P1 green, P3 amber, ice, hot magenta — the tint is a per-frame uniform, so a palette is one line.

art.frame.tint.set('#ffb454')
07

break it

datamosh, cell scramble, degauss jitter, phosphor smear — and a power envelope that collapses the tube to a bright line, then a dot, then black.

frame.glitch / frame.scramble / frame.power
08

audio signal

a WebAudio analyser feeds bass / mid / treble / beat into the same per-frame contract. running on a synthetic beat — feed it your mic.

await audioReactor.connectMic()
synthetic beat
// how it works
01 · render small

the source lands in a grid-sized buffer — one MSAA-averaged texel per character cell. the ASCII pass only ever reads one sample per cell, so full-res shading would be ~50–200× wasted work.

02 · stamp glyphs

a runtime canvas builds the glyph atlas from any ramp string — no font assets. each cell picks its glyph by luminance; scramble swaps a fraction for garbage characters.

03 · let it linger

persistence is a ping-pong max(current, previous × decay) — bright glyphs leave real phosphor trails, frame-rate corrected, free when off.

04 · bend the glass

one last pass: barrel curvature, chromatic aberration, glow, scanlines, vignette — plus the drivable envelopes: datamosh, degauss jitter, and the power collapse.

standalone — no framework
// one call per canvas. three.js is the only peer dep.
import { createAsciiArt, RAMPS } from '@borkiss/tre/core'

const art = createAsciiArt({
  canvas,
  source: myImageOrVideoOrCanvasOrScene,
  ramp: RAMPS.classic,
  fit: 'cover',
})

// drive the look any time — no re-renders, no allocations
art.frame.colorize    = 1     // glyphs take the source's colour
art.frame.persistence = 0.6   // phosphor trails
art.frame.glitch      = 0.4   // datamosh
art.frame.power       = 0     // CRT off. bright line → dot → black

art.snapshot()                // → PNG data URL

React? <AsciiRenderer /> drops the same pipeline into any R3F canvas. scroll stories? ScrollStage maps Lenis → one GSAP timeline → your params. the README has both.

// the full experience — 物語
// install — 道

ship a tube.

@borkiss/tre/core

the graphics engine alone — pipeline, standalone art, sources, audio, ramps. peer dep: three, nothing else.

@borkiss/tre

everything — plus <AsciiRenderer /> for R3F, ScrollStage, act tables and the one master GSAP timeline.

engine & site built by CLAUDE for borkiss · MIT · this page renders itself with the package it ships