Under the glass

How Phosphors works

Phosphors doesn't add a "CRT filter" to your video. It rebuilds the picture from scratch by simulating the physics of a real cathode-ray tube: an electron beam, a phosphor-coated screen, and the glass in front. Everything you see emerges from that simulation.

  1. Simulate the hardware, don't fake the look
  2. The signal path
  3. The electron beam
  4. Phosphor excitation and decay
  5. Fields, interlace and the retro console
  6. The shadow mask
  7. The display pass and HDR
  8. The receiver's audio
  9. Why this approach

1. Simulate the hardware, don't fake the look

Most "retro CRT" tools draw scanlines and a glow on top of your image. Phosphors works the way the tube did: it fires a simulated electron beam at a simulated phosphor screen and shows you what lights up. Scanlines, bloom, smeared motion trails, the rolling hum bar, the fine mask texture: none of them are painted on. They happen, the same way they happened in 1985.

The consequence that drives the whole architecture: the phosphor buffer is the screen. It is a GPU-resident image of the current brightness of every phosphor on the tube. Your Mac's display is just a window onto that buffer. The host monitor's refresh rate is irrelevant to the physics. The beam scans at the CRT's rate and phosphors decay at their real-world rates, both driven by an independent clock.

That is also why the look stays physically correct at any resolution, from a 13-inch portable to a 4K master, instead of breaking down like a fixed overlay would. More resolution means more phosphors and a finer beam, so the picture gets sharper, not blockier.

  1. Your video Any clip macOS can decode. Conformed to a small SDR frame on the media engine, because the tube is standard definition and HDR is an output property.
  2. Broadcast encode and decode The frame is encoded into a baseband composite signal (or routed through the VGA path), pushed through a tuner with realistic noise behaviour, and demodulated back to RGB scanlines by a receiver.
  3. Phosphor simulation kernel Reads the decoded signal, advances the beam, excites and decays every phosphor. Output: a state buffer with one brightness per phosphor.
  4. Display pass Applies mask geometry, afterglow colour, vignette, hum bar and HDR headroom at the output resolution. This is the picture, on screen and in the recording.

2. The signal path

Before anything reaches the tube it travels the way a broadcast did. Each input on a television preset is a different chain with its own real artifacts, dirtiest to cleanest:

The NTSC Tint knob exists for the same reason it did on a real set: hue was a real artifact of how colour was encoded. Monitors take a clean digital or RGB feed and hide the whole section. The Bandwidth dial models the analog video chain before the tube: lower is crisper horizontal detail with authentic edge ringing, like a wide-band amplifier; higher rolls off softer, like a long cheap cable.

3. The electron beam

A clock, not a frame counter

The beam is driven by a monotonic clock independent of the host display. Fields are dispatched at the preset's rate: 59.94 Hz for NTSC, 50 to 85 Hz for monitors. The simulation is correct whether your Mac runs at 60, 120 or 144 Hz.

Arrival time is per scanline

The beam paints top to bottom over one field period, so each scanline has its own arrival time within the field. Top lines are fresher than bottom lines. That is the physical basis for the rolling brightness gradient, for correct interaction with persistence, and for the Shutter control: at 1.00 you are the rested eye and everything fuses; lower it and you watch through an ever-faster camera shutter, and the rolling band of filmed CRT footage appears.

The beam's horizontal landing point per line is offset by the sync-PLL result plus jitter, so losing horizontal lock bends individual lines exactly as a real tube does.

Shape: a Gaussian with a Nyquist floor

Two Gaussian beam profiles across scanlines: a tight beam leaves dark gaps, a wide beam fills them in. tight beam → visible scanlines wide beam → filled-in field scanline centres every 100 px; brightness at a pixel = exp(−d² / 2σ²)
Across a scanline the beam has a Gaussian cross-section. Make it tight and the lines stand apart with dark gaps; widen it and they fill in. The dashed line is what the eye sums.

The crucial parameter is σ, the beam width. It is derived from the Sharpness dial's falloff ratio and clamped to a floor of half a pixel: below that, a point-sampled Gaussian aliases against the sample grid and the scanline pattern beats. So Phosphors keeps the beam exactly as sharp as the render resolution allows without aliasing. Scanlines read as crisp glowing lines at 480p and dissolve into a smooth field at 4K, which is also how a real set looks lined up close and seamless from across the room.

σ = (scanlineSpacing / 2) / √(−2 · ln(falloff))
σ ≥ 0.5 px

Imperfections, all physically motivated

4. Phosphor excitation and decay

Where the beam lands, it lifts the phosphor's brightness. The phosphor state is updated with a maximum, not an accumulation: a phosphor decays autonomously every frame, and a fresh beam pass only lifts it if the new excitation is brighter. That makes the steady-state image independent of how many times or in what order the kernel runs, which is what keeps the simulation correct across wildly different host frame rates.

A two-component exponential

Phosphor brightness over time: a fast collapse in under a millisecond, then a long shallow afterglow tail. fast component: gone in under a millisecond (stops motion from smearing) slow component: still ~50–60% a frame later (stops flicker, leaves the motion trail) time → bright dark dashed: a single exponential, for comparison
Real phosphors don't decay as a single exponential. A fast component and a slow tail are modelled directly, and brighter excitation biases toward the slow component, matching measured behaviour.
decay = A_fast · e^(−k_fast · dt) + A_slow · e^(−k_slow · dt)
brightness = max(brightness · decay, excitation)

The presets are real phosphor data

Decay rates and amplitudes are not tuning knobs invented for looks. They come from documented phosphor types, and each colour channel can decay at its own rate.

PresetCompoundUsed inCharacter
P22Y₂O₂S:Eu (R) · ZnS:Cu,Al (G) · ZnS:Ag (B)Colour TV and monitorsPer-channel decay. Red slow, blue fast, which is why moving edges on a colour tube tint the way they do.
P4ZnS:Ag + (Zn,Cd)S:AgBlack-and-white TV, white terminalsFast white with a short power-law tail.
P39Zn₂SiO₄:Mn,AsIBM MDA, long-persistence greenArsenic trap-state tail lasting two to three seconds. 34.5% remaining at 30 ms.
P1Zn₂SiO₄:MnClassic green terminals and scopesMedium green, T10 ≈ 24 ms.
P31ZnS:CuModern oscilloscopesThree times brighter than P1, very short persistence.
P3Zn₈BeSi₅O₁₉:MnAmber terminalsWarm 602 nm amber, medium persistence.
P4GZnS:Ag, green-tintedDEC VT220Short-persistence green TV phosphor.

The display stage also applies a small decay-dependent afterglow colour shift, so as each channel fades its emission drifts the way the real compound does. The red Y₂O₂S:Eu tail warms toward orange.

5. Fields, interlace and the retro console

Monitors are progressive: every scanline is painted every field. Televisions are interlaced, and the visible region is mapped through the vertical blanking interval so framing matches the standard, roughly 480 visible lines of 525 for NTSC. Because each scanline carries its own arrival time and each phosphor its own decay since excitation, brightness naturally varies down the frame. The scan gradient is a consequence of per-line timing, not an overlay.

The Retro Console input models what a 240p signal does on a 480-line tube. The electron spot is a property of the tube, not the signal: a 240p feed keeps the native thin beam while the line spacing doubles, so the positions a 480i raster would visit in between stay dark. Those gaps are the retro scanline look. One thin beam per content line, never a fattened beam, never two beams, every line refreshed at 60 Hz with no interlace flicker.

6. The shadow mask

Up close, a colour CRT isn't made of pixels. It is a fine lattice of red, green and blue phosphors behind a perforated metal mask. Phosphors models the real geometries rather than a generic grid:

Why the pattern is coarser than a real tube's. A 21-inch Trinitron has about 1,700 RGB triads across its width; a colour TV around 750. Drawing one triad takes at least three pixels, one per stripe, so reproducing a real monitor's density would need over 5,000 pixels of width for the pattern alone. Phosphors draws the pattern at the finest size a pixel grid can hold, and it is deliberately locked to whole pixels so it stays crisp instead of shimmering. What you get is a faithful impression of phosphor structure rather than a one-to-one copy. It also means, exactly as on a real tube, that at 1× you may not see it from a normal viewing distance. Lean in with the viewing zoom, or raise Pattern Scale.

Pattern Scale and Pattern Focus

Pattern Scale is a multiple of the finest lattice that still reads, from 1× to 3×, and it is a multiple rather than a pixel size so the same value looks the same on screen and in a recording even though the two are different resolutions. Pattern Focus states how tightly the beam is focused on a single cell, interpreted per pattern: on stripes it sets how far the spot spills into neighbours, on the shadow mask how wide each dot is. Right is always tighter, purer and more separated; left is softer and fainter.

Focus also protects a recording. A hard-edged stripe is a square wave and carries harmonics, and those are what fold into rainbow moiré when a player scales your video to a window that isn't its native size. A fully blended stripe has a single frequency and nothing left to fold. Softening the pattern at the source beats any filter applied afterwards, because the harmonics are never created in the first place.

The stripe lattice also changes shape with how many pixels a stripe covers. Merged, all three channels light one bar per triad so it reads as white with a thin black line beside it; separated, when zoomed in, it splits into three coloured bars with black between. It is a continuous morph driven by pixels per stripe, and it is why the mask resolves cleanly instead of aliasing as it shrinks.

7. The display pass and HDR

Two passes run per frame. The simulation pass is the tube's state. The display pass is the picture: it samples the phosphor buffer through a mipmapped texture so minification integrates the scanline and mask comb cleanly, applies the mask tile, phosphor colour and afterglow, brightness, vignette and the sinusoidal mains hum bar. The live view and the recording are each their own display pass at their own resolution, fed by the same canonical phosphor image. Recording Preview shows the recording's pass on screen at 1:1, so you judge the file itself.

Output is a 16-bit float image with no clamp at white. With HDR on, the tube's own bright peaks, such as pattern sparkle and small bright areas above full-field white, are allowed to rise into the display's headroom through a per-pass highlight ceiling that is capped by what the panel can deliver right now. The picture itself never gets brighter; only the peaks do, and it cannot blow out. Recordings write HLG (Rec. 2020), chosen because it degrades gracefully: HDR displays show the full range and SDR players get a usable picture straight from the curve, with no tone-mapping metadata.

8. The receiver's audio

On broadcast presets the sound gets the same treatment as the picture. Raw audio goes through a full FM receiver chain implemented as pure DSP: pre-emphasis, phase-accumulator modulation, signal-to-noise phase noise, FM discrimination, de-emphasis, a capture-effect sigmoid, MTS stereo with dbx companding and a signal-tracking stereo blend, a TV speaker biquad, and intercarrier buzz. The receiver's hiss, the stereo image narrowing to mono as a weak signal fades, and the buzzy small-speaker character all emerge from the chain rather than from layered effect plug-ins. The Stereo, Speaker and Shielding dials sit on that chain.

9. Why this approach

Modelling the hardware instead of the output means the artifacts are correct by construction and interact the way the real ones do. Bloom widens the beam, which fattens scanlines, which changes how the mask reads, which the persistence then trails, all from one coherent simulation. Accuracy becomes a function of render resolution rather than of hand-tuned effect stacks, and the same canonical phosphor image feeds both the live preview and the recorder.

Phosphors' display presets are modelled from publicly documented CRT physics, not from any manufacturer's firmware. The result is a look that's correct by construction, and that gets sharper, not blockier, the more resolution you give it.

Download on the Mac App Store Back to overview