Hi! I built a web-based oscilloscope simulator using Three.js and custom GLSL shaders.
The rendering uses a 4-pass pipeline:
- Beam — point sprites with additive blending into HalfFloat textures
- Phosphor — exponential decay persistence in linear HDR (ping-pong buffers)
- Bloom — separable 13-tap Gaussian at half resolution
- Composite — Reinhard tone mapping, barrel distortion, scanlines, vignette
The key was keeping the phosphor buffer in linear HDR space and only tone mapping once in the composite pass. Per-frame tone mapping causes values to converge instead of accumulating naturally.
Features: Lissajous figures, waveform synthesis, AM/FM modulation, real-time audio visualization (Web Audio API), and freehand vector drawing with keyframe animation.
Built with Three.js, TypeScript, and Vite. Runs in Docker for local dev.
Would love feedback — especially on the phosphor decay approach. I went with exponential decay in linear HDR space, clamped at 2.5 to give Reinhard headroom.