Show a TextMode view through a simulated cathode ray tube, with scanlines, phosphor masks, afterglow, glow and a curved screen.
Summary
TextModeCRTDriver shows a TextMode view the way a cathode ray tube would have shown it. Add it next to a UITextModeView, pick a preset, and the grid appears through scanlines, a phosphor mask and a soft glow. Green and amber terminals keep a fading afterglow behind scrolled text and the moving cursor.
The simulation works from the grid itself rather than from a screenshot of it. Every frame the driver renders the grid at its size on screen, so the text stays sharp, and then averages it down to one row per scanline. The scanline count follows a pitch in screen pixels (3 by default) and is rounded per character row, so every row of text gets the same pattern and the scanlines stay visible at any window size. The beam gets wider as the picture gets brighter, the mask repeats at a whole number of screen pixels, and the glow is computed in linear light, which keeps highlights clean instead of gray.
The picture is flat by default. Screen curvature and rounded corners are optional settings, off in every preset except Poor Reception.
The view keeps working as before. Text selection, mouse reporting and hyperlink clicks follow the curved picture, because the driver maps the pointer through the same curvature the shader uses. Turning the driver off shows the plain view again.
The driver needs no render pipeline code, so it works the same in URP, HDRP and the Built-in pipeline.
In URP and HDRP the same simulation can also cover a whole camera: 3D scenes, sprites and anything else the camera draws. That part comes as two separate packages, which ship inside the TextMode package in its Extras folder for you to import by hand. Scylla TextMode for URP adds a renderer feature and a Volume effect, and Scylla TextMode for HDRP adds a custom post process controlled by a Volume. Both use the same presets and settings. Import only the one for your project’s render pipeline; Getting Started shows how.
Use the CRT for:
- A retro terminal or a DOS-style interface on a green, amber or white monochrome monitor.
- A text-mode game that should look like it runs on a 1980s or 1990s color monitor.
- A light CRT look on screens that still have to be easy to read.
Features
- Presets. Seventeen monitors are included, listed in this order:
- Standard Terminal, the default. Keeps the colors of the active theme; not modelled on one screen.
- Television, in the style of a 1980s or 1990s consumer color TV fed S-Video.
- Composite TV, the same television fed the composite output of a game console.
- Antenna TV, the same television with a console on its antenna input: snow, a slight wobble and a slow hum bar.
- Poor Reception, an old, worn-out set on a weak antenna signal, with colors far out of convergence, a burned-in picture and curved glass in a bezel; not modelled on one screen.
- Arcade Monitor, in the style of a late 1980s or early 1990s arcade cabinet monitor such as the Nanao MS-9 or the Wells-Gardner 25K7.
- Arcade Monitor 2, the same arcade monitor with a finer, more colorful shadow mask that shows between the scanlines.
- Home Computer Monitor, in the style of the Commodore 1084S.
- CGA Monitor, in the style of the IBM 5153 Color Display.
- Artifact Color Monitor, the composite signal of a CGA card, where fine detail in the text turns into artifact colors.
- VGA Monitor, in the style of a 31 kHz VGA monitor such as the IBM 8513.
- Aperture Grille Monitor, in the style of a Sony Trinitron PVM.
- Clean Readable, a light CRT look for readability; not modelled on one screen.
- Amber Terminal, in the style of amber P3 terminals such as the amber DEC VT220.
- Green Terminal, in the style of the IBM 5151 Monochrome Display.
- Grayscale Terminal, in the style of white P4 terminals such as the DEC VT100.
- CCTV Monitor, a black-and-white security monitor showing a camera feed over coax; not modelled on one screen.
- Beam. Gaussian scanlines whose width follows the brightness of each line, and a horizontal Gaussian or Lanczos filter for the softness along the line.
- Phosphor mask. Aperture grille, slot mask or shadow mask, sized from a TV line count and drawn at whole screen pixels.
- Afterglow. Two decay times per color channel, driven by real elapsed time, so trails look the same at any frame rate. The game’s pause does not freeze it.
- Glow and halation. A short glow around bright text and a wide, faint glow from the front glass, both in linear light.
- Scanlines. A pitch in screen pixels, or a fixed count per character row, with at least nine scanlines per row so letters stay readable. Beam Blend chooses between crisp lines that keep dark text sharp and overlapping beams that bleed into each other like a real tube. Stroke Snap decides how firmly each scanline snaps to the strokes it covers, so the tops and bottoms of letters stay crisp instead of leaving faint smudged lines.
- Tube shape. A vignette, and optionally spherical or cylindrical curvature, rounded corners and overscan. Curvature is off by default.
- Bezel. An optional frame around the picture in any color, whose inner edge reflects a soft copy of the picture. The picture shrinks to make room for it, in the view and in the camera effect alike. Off in every preset.
- Signal. Tube gamma, white point in kelvin, brightness, contrast and saturation, and a monochrome mode with any phosphor color.
- Video signal. The tube can be fed through S-Video, composite or RF instead of a clean RGB cable, in NTSC or PAL. Each scanline is encoded as a real signal and decoded again, so color bleed, dot crawl along colored edges, rainbow fringes on fine detail and artifact colors come out of the signal itself. Bandwidth, sharpness and the subcarrier’s timing are adjustable, down to a CGA card’s artifact colors. NTSC composite uses the comb filter of a good television by default, which keeps text readable; turning it off gives the heavier look of a cheap set. A weak signal can add snow, lines that wobble sideways, a hum bar rolling through the picture and a picture that rolls vertically.
- Convergence. The red and blue beams can miss the green one toward the edges, which gives bright edges colored fringes near the corners. It works with every signal type.
- Tube events. The tube can be switched off, which squeezes the picture into a bright line, shrinks the line into a dot and lets the dot fade, and switched on again, which fades the picture in slightly out of focus while the tube warms up. A degauss shakes the picture for about two seconds while colored patches swirl across it. Burn-in, off by default, lets text that stays in one place leave a faint ghost and dim the phosphor there for the rest of the session. Events for each change let the game play its own sounds.
- Quality tiers. Low is a single pass for mobile and handheld devices. Medium adds afterglow and glow. High adds halation and a separate beam width per color channel.
- Readability clamp. Limits softness, curvature, glow, afterglow, snow, wobble, the hum bar and convergence, and stops a rolling picture, on screens the player has to read quickly.
- Reduced motion. With Reduce Motion on in the TextMode configuration, snow, wobble, the hum bar, the rolling picture and dot crawl stand still, switching the tube on and off becomes a plain fade, and a degauss shows nothing.
- Beam simulator. On a display at 120 Hz or more, each refresh lights only the part of the picture the beam is drawing at that moment, the way a real CRT does. Moving images get the sharp motion of a CRT back: up to half the motion blur disappears at 120 Hz, three quarters at 240 Hz. It works on views and camera effects, and flickers at the CRT rate like the real thing.
- Calibration patterns. A grille, a gray ramp and a convergence grid can replace the picture and go through the tube with every setting applied, for adjusting the CRT to a display: mask alignment and moire, gamma, black level and brightness, convergence and curvature.
- Camera effect (URP and HDRP). A Volume component shows any camera through the tube, with a fixed line count such as 240 for a low-resolution look. Each camera keeps its own afterglow. The effect also works under HDR output.
Components
| Type | What it is |
|---|---|
TextModeCRTDriver | The component you add. Owns the simulation for one view and holds the settings in use. |
TextModeCRTPreset | An asset holding a named set of settings. Presets in a Resources/CRT/Presets folder are found automatically. |
TextModeCRTSettings | Every parameter of the simulation. The driver holds its own copy. |
TextModeCRTPresetRegistry | Lists presets in their set order and finds them by name. |
TextModeCRTMonitor | Shows a world-space TextModeView on a monitor screen in a 3D scene, with the mask on the glass. Its picture is also available as a texture for your own materials. |
TextModeCRTTube | The tube itself: switches it on and off, degausses it and clears its burn-in, and raises an event for each. Every driver has one, and a camera effect can be given one. |
TextModeCRTScreen | The graphic the driver creates to show the picture. You do not add it yourself. |
TextModeGridCapture | Renders any TextMode view into a render texture at exact cell pixels. The CRT is built on it, and you can use it for your own effects. |
TextModeCRTImageRenderer | Runs the CRT over any image, such as a camera’s picture, from a command buffer. The URP camera effect uses it, and so can your own render code in any pipeline. |
TextModeCRTCalibration | Shows a calibration pattern in place of the picture on every CRT at once. |
TextModeCRTRendererFeature | URP package. The renderer feature that draws the CRT for cameras. |
TextModeCRTVolume | URP package. The Volume effect that switches the CRT on and picks the preset, the quality and the line count. |
TextModeCRTHDRPVolume | HDRP package. The custom post process that draws the CRT for cameras, with the same Volume settings as the URP effect plus a Scene view switch. |
Recipes
Put a terminal on a green monochrome monitor
Problem: A game’s terminal screen should look like an old green phosphor terminal, with text that leaves a trail when it scrolls.
Solution: Add the driver to the view’s GameObject and give it the Green Terminal preset.
using Scylla.TextMode;
using Scylla.TextMode.CRT;
using UnityEngine;
public class GreenTerminalScreen : MonoBehaviour
{
[SerializeField] private UITextModeView _view;
private void Start()
{
var crt = _view.gameObject.AddComponent<TextModeCRTDriver>();
crt.ApplyPreset(TextModeCRTPresetRegistry.Find("Green Terminal"));
}
}
In the editor the same thing takes two steps: add TextMode CRT from the component menu under Scylla > TextMode, then drag the preset asset onto its Preset field.
Adjust a preset for one screen
Problem: The VGA Monitor preset is close, but this screen needs coarser scanlines, a curved screen and a bluer white.
Solution: Change the driver’s settings after the preset is applied. The preset asset stays as it is.
var settings = crt.Settings;
settings.ScanlinePitch = 4f;
settings.CurvatureEnabled = true;
settings.Curvature = new Vector2(0.03f, 0.04f);
settings.ColorTemperature = 9300f;
crt.ResetToPreset() goes back to the preset’s values.
Switch the monitor off and on
Problem: A computer in the game should shut down with the collapsing picture of an old monitor, and come back with a warm-up, each with a sound.
Solution: Use the driver’s tube. The events fire the moment the switch is pressed, which is when the sound should start.
var tube = crt.Tube;
tube.PoweredOff += () => _audio.PlayOneShot(_switchOffClip);
tube.PoweredOn += () => _audio.PlayOneShot(_switchOnClip);
tube.PowerOff();
// later
tube.PowerOn();
Settings.WarmUpTime sets how long the warm-up takes, 1.5 seconds by default. tube.Degauss() plays a degauss, and tube.IsDark tells when the last of the picture has faded. The view keeps its input while the tube is off.
Feed the tube a composite signal
Problem: The game should look like a home console plugged into a television, with the color bleed and fringes of a composite cable.
Solution: Set the signal type. The rest of the preset stays as it is.
var settings = crt.Settings;
settings.SignalType = CRTSignalType.Composite;
settings.SignalStandard = CRTSignalStandard.NTSC;
/* Consoles did not move the subcarrier from field to field, so the dots along colored edges
stand still. Leave it at 0.5 for broadcast television, where they crawl. */
settings.SubcarrierFieldShift = 0f;
CRTSignalType.SVideo keeps text sharp and only smears color, which suits a text-heavy screen. For CGA-style artifact colors, use the Artifact Color Monitor preset, or set SubcarrierPeriod to 4 and SubcarrierLineShift to 0. Leave SourceCellSize at 0 x 0: at small fixed cell sizes such as 8 x 8, the font breaks into thin fragments that the signal turns into color, and text becomes unreadable.
Keep a menu readable
Problem: The CRT look is right for the game, but the settings menu must stay easy to read.
Solution: Turn on the readability clamp for the driver on that screen, or for the whole project under the CRT section of the TextMode configuration.
crt.ReadabilityClamp = true;
Run on low-end hardware
Problem: A handheld device cannot afford the afterglow and glow passes.
Solution: Set the quality tier to Low. The picture keeps its scanlines, mask and curvature and is drawn in a single pass.
crt.QualityTier = CRTQualityTier.Low;
Put a terminal on a monitor in a 3D scene
Problem: A computer in the level should show a working terminal on its screen, and the screen should hold up when the player walks right up to it.
Solution: Add a TextModeView where the screen is, add a TextModeCRTMonitor next to it, and point it at the screen’s renderer.
var view = screenAnchor.AddComponent<TextModeView>();
view.ViewWidth = 0.48f;
view.ViewHeight = 0.36f;
var monitor = screenAnchor.AddComponent<TextModeCRTMonitor>();
monitor.Screen = screenRenderer;
monitor.ApplyPreset(TextModeCRTPresetRegistry.Find("Home Computer Monitor"));
A screen without a material gets the CRT Monitor material, which works in URP and the Built-in pipeline. It lays the phosphor mask out on the glass, so the triads show up close and fade smoothly with distance instead of shimmering. To use your own lit material instead, for example in HDRP, set monitor.TextureProperty to its emission map. monitor.OutputTexture holds the picture as well. Without a Screen, the monitor creates a quad where the view’s grid is. Pointer input on the 3D screen is not mapped to cells.
Show a whole URP camera through a CRT
Problem: The CRT should cover a 3D scene, not only a TextMode view.
Solution: Import the Scylla TextMode for URP package from the TextMode package’s Extras folder, then choose Tools > Scylla > TextMode > Add CRT to URP Renderers, which adds the renderer feature to every Universal renderer in the project. Add a Volume to the scene, add Scylla > TextMode CRT to its profile, switch Enable on and pick a preset. The same works from code:
using Scylla.TextMode.CRT;
using Scylla.TextMode.URP;
using UnityEngine;
using UnityEngine.Rendering;
public class ArcadeLook : MonoBehaviour
{
private void Start()
{
var profile = ScriptableObject.CreateInstance<VolumeProfile>();
var crt = profile.Add<TextModeCRTVolume>(true);
crt.Enable.value = true;
crt.Preset.value = TextModeCRTPresetRegistry.Find("Arcade Monitor");
crt.SourceLines.value = 240;
var volume = gameObject.AddComponent<Volume>();
volume.isGlobal = true;
volume.sharedProfile = profile;
}
}
Code that should not depend on a render pipeline can use TextModeCRTCameraEffects.Provider from Scylla.TextMode.CRT instead. The package for the active pipeline registers itself there, and Show puts a camera on the tube with a given settings object, while Hide takes it off again. CanShow reports whether the effect is set up, and why not when it is not. When no pipeline package is installed, Provider is null. The terminal demo’s crt mode camera works this way, and the same code runs unchanged in URP and HDRP.
The effect runs after post-processing by default, so bloom and color grading happen first, the way a game’s signal reached the monitor. It also follows the URP Render Scale setting, so scanlines keep their height in screen pixels. Screen Space Overlay canvases are drawn after the camera and stay outside the tube; use a Screen Space Camera canvas for UI that belongs on the screen.
Show a whole HDRP camera through a CRT
Problem: The CRT should cover an HDRP camera.
Solution: Import the Scylla TextMode for HDRP package from the TextMode package’s Extras folder, then choose Tools > Scylla > TextMode > Add CRT to HDRP Global Settings once. HDRP only runs custom post processes that are listed in its Global Settings, and the menu item adds the CRT to the After Post Process list. If it reports that it cannot, add TextModeCRTHDRPVolume to that list by hand under Project Settings > Graphics > HDRP > Custom Post Process Orders. Then add Scylla > TextMode CRT to a Volume profile, switch Enable on and pick a preset. From code it works as in URP:
using Scylla.TextMode.CRT;
using Scylla.TextMode.HDRP;
using UnityEngine;
using UnityEngine.Rendering;
public class ArcadeLook : MonoBehaviour
{
private void Start()
{
var profile = ScriptableObject.CreateInstance<VolumeProfile>();
var crt = profile.Add<TextModeCRTHDRPVolume>(true);
crt.Enable.value = true;
crt.Preset.value = TextModeCRTPresetRegistry.Find("Arcade Monitor");
var volume = gameObject.AddComponent<Volume>();
volume.isGlobal = true;
volume.sharedProfile = profile;
}
}
The HDRP effect always runs after post-processing, so exposure, bloom, tonemapping and color grading shape the picture first. Tick Show In Scene View on the Volume effect to see the CRT in the Scene view as well. The package ships a demo script, TextModeCRTHDRPDemo, which builds a small scene with its own camera and Volume when added to an empty GameObject in any HDRP scene.
Sharpen motion on a fast display
Problem: On a 120 Hz or faster display, scrolling and moving sprites should look as sharp as on a real CRT, not smeared like on a flat panel.
Solution: Switch on Beam Simulation in the CRT section of the TextMode configuration, or from code:
using Scylla.TextMode.CRT;
using UnityEngine;
public class SharpMotion : MonoBehaviour
{
private void Start()
{
/* The beam simulator needs one frame per display refresh. */
QualitySettings.vSyncCount = 1;
TextModeCRTBeam.Enabled = true;
}
}
Every TextMode view with a CRT and every CRT camera effect follows the switch; the in-world monitor does not. The simulator lights each part of the tube only for the refreshes after the beam passes it, as bright as its content allows, and keeps it dark for the rest of the CRT frame. That strobing is what keeps motion sharp. It also makes the picture darker, by the Beam Gain (0.5 at 120 Hz and 0.7 at 240 Hz by default), and makes it flicker at the CRT rate.
TextModeCRTBeam.GetStatus() reports whether the simulator runs, and why not when it does not: the display rate it counts with, the refreshes per CRT frame, the frame rate the game reaches and the refreshes it missed. The terminal demo shows the same with crt beam, and the Shmup demo switches it with F4.
Warning: flickering images can affect people with photosensitive epilepsy. The simulator is off by default, Reduce Motion keeps it off, and a game that offers it should say so next to the option.
Calibrate the tube for a display
Problem: A settings screen should let the player check the mask, the brightness and the black level on their own display.
Solution: Show a calibration pattern through the tube:
using Scylla.TextMode.CRT;
/* Gray steps, a gradient, and patches just above black and just below white. */
TextModeCRTCalibration.Pattern = CRTCalibrationPattern.GrayRamp;
/* Back to the picture. */
TextModeCRTCalibration.Pattern = CRTCalibrationPattern.None;
The pattern replaces the picture on every TextMode view with a CRT, every CRT camera effect and every in-world monitor, and all settings of the preset apply to it. It is drawn at the resolution the picture is captured at, which is one pattern pixel per display pixel unless the preset fixes the cell size or the number of lines.
Grillealternates white and black vertical lines,TextModeCRTCalibration.GrilleWidthpixels wide (1 to 4). It shows how the mask lines up with the picture, moire, and the artifact colors of a composite signal.GrayRampshows 16 gray steps, a smooth gradient, and three patches each on black (2, 4 and 6 percent) and on white (94, 96 and 98 percent). With HDR mask mode on, the white of the tube should look as bright as white outside it;HDRMaskBrightnessin the configuration adjusts it.ConvergenceGriddraws thin lines through the center and along the edges, in square cells with a dot in each. Convergence errors show as colored fringes, curvature as bent lines.
A camera effect replaces the camera’s whole picture, including a menu that camera draws, so draw the settings controls on another camera or on a view without a CRT. The pattern switches off at the start of every play session. The terminal demo shows the patterns with crt calibrate.
Configuration
The CRT section of ScyllaTextModeConfiguration holds the project defaults:
| Setting | Default | What it does |
|---|---|---|
| Default Preset | Standard Terminal | The preset a driver starts from when it has none of its own. |
| Quality Tier | 1 (Medium) | Quality for drivers that follow the project defaults. |
| Readability Clamp | Off | Readability clamp for drivers that follow the project defaults. |
| Max Delta Time | 0.1 | Longest step the afterglow takes in one frame, in seconds, so a loading hitch does not wipe it. |
| HDR Mask | On | Lets presets that ask for it show the phosphor mask at full strength on an HDR display. |
| HDR Mask Brightness | 1 | Brightness calibration for HDR mask mode, from 0.25 to 2. |
| HDR Headroom | 0 | The display’s headroom, peak over paper white, for displays that report it wrongly. 0 reads it from the display. |
| Beam Simulation | Off | Switches the beam simulator on. |
| Beam CRT Rate | 60 | Refresh rate of the simulated CRT in hertz. The display needs at least twice this rate. |
| Beam Refresh Rate | 0 | The display’s refresh rate, for displays that report it wrongly. 0 reads it from the display. |
| Beam Gain | 0 | How bright the picture stays, from 0 to 1. Higher is brighter with more motion blur. 0 picks 0.5 below 200 Hz and 0.7 above. |
| Beam Scan Direction | 1 | 1 sweeps from top to bottom like a CRT, 2 from bottom to top, 0 lights the whole tube at once. |
| Beam Anti-Retention | On | Lets the sweep drift slowly against the display, which keeps an LCD from retaining a pattern. Can be off on OLED displays. |
Each value also has a key in the CRT group of the JSON config file.
Good to know
- Beam simulator requirements. It needs vSync with one refresh per frame (
QualitySettings.vSyncCount = 1), a display at twice the CRT rate or more, and variable refresh rate (G-Sync, FreeSync, ProMotion) switched off. A frame the game misses shows as a flicker, so the game has to keep up with the display. When the game runs much faster than the display, vSync isn’t in effect, and the simulator stays off until the frame rate matches. In the Editor, the Game view only follows vSync when VSync is ticked in its menu. Displays with local dimming (FALD, Mini-LED) or their own backlight strobing are poor candidates. It only runs in Play mode. - Beam simulator and edges. The simulator reads only the current picture. While the content changes, the top and bottom rows can show the new picture a moment early or the old one a moment late.
- HDR output. HDR mask mode only takes effect while the display runs in HDR mode, which needs Allow HDR Display Output in the Player settings. Keep Use HDR Display Output off unless the whole game is meant to start in HDR. With it on, Unity switches the display to HDR mode shortly after launch, and URP then draws standard content such as TextMode canvases at the display’s paper white. On many displays, Mac displays in particular, that is well below the normal desktop white, so the whole game looks darker and flatter whether a CRT is on or not. To use HDR mask mode without that, leave startup HDR off and request HDR mode at runtime with
HDROutputSettings.main.RequestHDRModeChange(true)when the player turns it on. - HDRP injection point. The HDRP effect only runs after post-processing. Before it, HDRP’s picture has not been exposed yet, so the tube’s brightness and afterglow would depend on the scene’s lighting.
- HDRP updates. The menu item that registers the HDRP effect relies on HDRP 17.5’s settings layout. If a later HDRP version changes it, the menu item says so, and the effect can be added by hand as described above.
- XR. The camera effects skip stereo cameras. Use a TextMode view or an in-world monitor in XR.
- Other effects. Turn off the camera’s own bloom, film grain, vignette and chromatic aberration when a CRT is on screen. The CRT draws its own, and two glows stacked together look like fog.
- Render scale. The CRT lays out its scanlines and mask in screen pixels, so a URP render scale of 1 or above is fine. A render scale below 1 upscales the picture afterwards and blurs the mask; use a Screen Space Overlay canvas in that case.
- Line count. A fixed line count for the camera effect scales the picture down before the tube draws it. Detail thinner than one line, such as a one-pixel grid, can drop out rather than blend in.
- View type. The driver works with
UITextModeView. World-spaceTextModeViewgrids can already be captured withTextModeGridCapture, and a CRT for them is planned. - Small text. Scanlines need about 3 screen pixels each to show a dark gap. With small text and at least nine scanlines per row, they get finer and fade into the picture. Larger text gives more visible scanlines.
- Afterglow values. The afterglow times are estimates. Real phosphors varied from tube to tube.
- Video signal and quality. The signal is simulated in the Medium and High tiers. Low shows every preset as if it were fed RGB.
- Composite and text. A composite signal makes small colored text hard to read, just as it did on a real television. Use S-Video or RGB for screens full of text.
Related
- 01 Scylla TextMode
- 03 TextMode – Views and Grid
- 06 TextMode – Terminal Emulation
- 09 TextMode – Color Themes
- 12 TextMode – Configuration
- API reference:
Scylla.TextMode.CRT.TextModeCRTDriver - API reference:
Scylla.TextMode.CRT.TextModeCRTPreset - API reference:
Scylla.TextMode.CRT.TextModeCRTSettings - API reference:
Scylla.TextMode.TextModeGridCapture - API reference:
Scylla.TextMode.CRT.TextModeCRTImageRenderer