Description
Changelog
Reviews (0)

Overview

  • A two-stage NTSC/CRT television shader for Godot 4, packaged as a drop-in CRTScreen node.
  • Stage 1 (crt_signal.gdshader) builds and degrades the video signal; Stage 2 (crt_display.gdshader) draws it on a simulated tube.
  • Works with any picture: a Texture2D, the ViewportTexture of your game's SubViewport, or an entire scene added to the node.
  • Extracted from TurnTV, a desktop capture tool that shows any part of the Windows desktop as if it were on a CRT television.

Not a look-alike filter

  • Most CRT shaders paint scanlines and a mask over a finished image. This one reproduces the mechanism, and the artifacts fall out of it.
  • The picture is converted to YIQ and modulated onto an NTSC color subcarrier, so an actual composite signal exists inside the shader.
  • That signal is degraded the way a received RF signal is, then demodulated again.
  • The rainbow false color, dot crawl, and color bleeding you see are what the round trip failed to recover, not effects drawn on afterward.
  • The recovered picture is written by a beam onto an RGB phosphor mask, with beam width driven by brightness.
  • Because the artifacts come from the process, they respond correctly to content: fine vertical detail turns into false color, sharp color edges bleed, and a still image still crawls.

Stage 1 — NTSC composite signal

  • Cross-color: luminance detail leaks into the modulated chroma and returns as rainbow false color. Gated to luminance edges, so flat areas stay neutral.
  • Cross-luminance: modulated chroma leaks into luminance, producing the dotted fringe along color boundaries.
  • Imperfect Y/C separation modeled as a notch-filter television, not as a clean split.
  • Separate horizontal bandwidth limits for Y, I, and Q, standing in for roughly 4.2 MHz, 1.3 MHz, and 0.4-0.6 MHz. Q is the blurriest channel, as on a real set.
  • Chroma delay in pixels, pushing color to the side of the edge it belongs to.
  • Subcarrier phase controlled per pixel and per line, with a two-frame cycle that makes dot crawl move on a still picture.
  • Phase jitter per line and per frame for analog instability.
  • Multipath ghosting: a faint second and third image offset horizontally, with brightness renormalized.
  • Independent luminance and chroma noise.
  • A 17-tap FIR filter does modulation, mixing, band limiting, and demodulation in a single pass, with no extra texture fetches for the chroma delay.
  • An RGB bypass ratio for an RGB/SCART connection, which skips the filter entirely.

Stage 1 — RF reception

  • One master amount scales the whole RF group, so a single slider takes you from a clean cable to a noisy antenna.
  • Horizontal sync jitter: per-line horizontal wobble, standing in for a sync PLL that does not track perfectly.
  • Tuning error: a phase gradient across the picture plus a slow drift over time, so hues shift from left to right.
  • Color burst phase noise: residual error in burst phase lock, seen as hue wobble that changes line by line.
  • RF snow: fine reception noise plus occasional white impulses.
  • AGC wobble: slow amplitude drift, as if automatic gain control were lagging behind.
  • Post-demodulation hue and saturation trim, matching the controls on a real receiver.

Stage 2 — CRT display

  • Scanlines with luminance-dependent beam width: dark pixels get a narrow beam and blacker gaps, bright pixels widen and bloom into neighboring lines.
  • Three phosphor mask types, selectable at runtime:
    • Slot mask, typical of consumer televisions, with the sub-pixel rows offset and vertical slot gaps.
    • Aperture grille, the Trinitron-style vertical RGB stripe.
    • Shadow mask, a delta triad with the RGB order shifted every other row.
  • Mask pitch is measured in displayed pixels, so the pattern stays stable at non-integer scales.
  • Mask darkness and edge softness are adjustable; at zero softness the mask uses a hard step and skips fwidth entirely.
  • Brightness compensation in linear space gives back the light that scanlines and the mask took away.
  • Separate input and output gamma, so the whole tube is composited in linear space.
  • Barrel curvature of the tube face, with anything warped off the edge treated as bezel.
  • Rounded screen corners, edge vignette, and bezel darkening.
  • Interlace: even and odd source lines are drawn on alternating fields, with adjustable dimming of the field that is not being drawn and a slight vertical bob between fields.

Stage 2 — CRT optics

  • Horizontal sharpening: a light unsharp mask using the neighboring source texels, the pre-sharpening common to CRT shader chains.
  • RGB convergence error: red and blue shifted in opposite directions, horizontally and vertically, measured in displayed pixels. Small values give the colored edges a real, slightly misaligned tube shows.
  • Halation: light bleeding from bright phosphors into their surroundings, with an adjustable radius and a luminance threshold. Added after the mask, so it crosses the mask's dark gaps the way real glass glow does.
  • Every optical parameter has a zero value that removes its texture samples completely, so quality and cost scale together.

Vertical (tate) games

  • Two independent quarter-turn rotations cover every vertical arrangement.
  • screen_rotation turns the whole CRT plane. Scanlines and the phosphor mask rotate with the tube, exactly like physically turning an arcade monitor on its side. This is not a portrait crop of a horizontal CRT.
  • content_rotation turns the picture inside the tube instead, leaving the tube upright and the scanlines horizontal.
  • The two combine: screen_rotation = DEG_90 with content_rotation = DEG_270 gives the classic cabinet, a landscape tube on its side with the game drawn upright.
  • A quarter turn transposes the drawing area, so get_content_size() returns 240x320 for a 320x240 signal, and the content_size_changed signal tells your game to lay itself out for the new shape.
  • Games already authored portrait need only signal_resolution set to the portrait size and screen_rotation alone.
  • display_fit decides what happens when the Control is not the same shape as the tube: keep the tube's aspect ratio and letterbox the rest, or stretch to cover the Control.
  • screen_aspect sets the tube's shape explicitly for non-square pixels, for example 4:3 for a 256x224 picture on a television.
  • The letterbox color is configurable, including fully transparent so the scene behind shows through.

Presets

  • Composite TV: the defaults, a composite-video television.
  • CRT Studio: a clean sharp monitor. No RF instability, more sharpening, halation, and a visible convergence error.
  • Famicom RF: an 8-bit console in the antenna socket. Heavy false color, bleeding, sync instability, tuning error, snow, and AGC wobble.
  • RGB Direct: an RGB/SCART connection. No composite artifacts at all, but still a CRT, and Stage 1's filter never runs.
  • Lightweight: scanlines and phosphor mask only, for weak GPUs.
  • Presets only set video parameters, and a preset that omits a parameter leaves it at its default, so they never surprise you with a hidden change.

Node and API

  • CRTScreen extends Control, so it drops into any UI or scene and follows anchors and containers.
  • The whole pipeline is internal: the low-resolution canvas, the Stage 1 SubViewport, and the Stage 2 display pass are created and resized by the node, and are not saved into your scene file.
  • source_texture displays any Texture2D, with four layout modes: 1:1 centered, fit, fill, and whole-number integer scaling with nearest filtering.
  • get_content_root() returns the node your own scene belongs under, to run a whole game through the CRT.
  • get_signal_texture() exposes the Stage 1 output if you need it for a secondary effect.
  • signal_resolution sets the simulated signal size, which is what Stage 1 costs are tied to.
  • animate and frames_per_second control the frame counter that drives dot crawl, noise, sync jitter, and the interlace field flip. Turn it off to freeze the picture for screenshots.
  • background_color fills the canvas area not covered by the picture, reading as the no-signal border of a real television.
  • Runs in the editor: assign a CRTSettings resource and the viewport updates live while you drag sliders.

Settings resource

  • All 48 shader parameters live in one CRTSettings resource, grouped in the inspector as Signal, RF, Display, and Optics.
  • Every property is generated from a single parameter table, so the inspector rows, the demo sliders, the JSON format, and the presets can never drift apart.
  • Each parameter carries its own range and step, so the inspector shows a correctly bounded slider.
  • Right-click revert works: every parameter knows its own default.
  • to_dict() / from_dict() and to_json() / from_json() round-trip the whole set, ready for your own options screen or save file.
  • Loading ignores missing keys, so a settings file written by an older version still loads and new parameters keep their defaults.
  • Saving a CRTSettings as a .tres gives you a reusable look you can share between scenes or ship as a graphics option.
  • set_param() and get_param() change one parameter at a time without touching the rest.

Performance

  • Stage 1 runs its 17-tap filter at the signal resolution, not at display resolution, so its cost does not grow when the window does.
  • Stage 2 runs per displayed pixel and adds at most eight extra texture samples: four for halation, two for convergence, two for sharpening.
  • Every expensive feature has a zero value that branches its samples away, so the shader can be scaled down without editing it.
  • Setting signal_amount to 0, or bypassing Stage 1 for an RGB connection, skips the 17-tap loop entirely.
  • No frame drops in normal use. Measured at a steady 60 FPS, vsync-limited, at 1280x768 with the default composite preset.
  • The Lightweight preset is the recommended starting point for integrated graphics.

Demo project

  • An animated NTSC test pattern: 75% color bars, castellations, fine 1-pixel line and checkerboard blocks that trigger cross-color, a luminance ramp, and moving objects that reveal dot crawl and sync jitter.
  • Every one of the 48 parameters exposed as a live slider, checkbox, or dropdown, grouped by stage.
  • Preset switching, screen and content rotation, signal resolution presets, and tube fit, all from the panel or the keyboard.
  • Drop an image file on the window, or open one from a dialog, to view your own artwork through the CRT.
  • Copy the current settings to the clipboard as JSON, ready to paste into your project.
  • The interface is available in English and Japanese.
  • Environment variables drive scripted screenshots, including a clean capture mode with the interface hidden.

Compatibility

  • Godot 4.6 or later.
  • Verified on both the Forward+ (Vulkan) and Compatibility (OpenGL) renderers, with identical output.
  • Both shaders are canvas_item shaders with no external dependencies and no compute or GDExtension requirement.
  • No plugin activation needed: CRTScreen, CRTSettings, and CRTPresets register through class_name and appear in the Create New Node dialog immediately.
  • The addon folder is self-contained and refers to its own files relatively, so it can be moved or renamed and does not have to live under addons/.
  • The download contains only the addon; the demo and documentation live in the repository.
  • MIT licensed, so it can be used in commercial games.
  • Full parameter reference included in English and Japanese, and the shader source is commented line by line.

Changelog for version v1.0.0

No changelog provided for this version.

Reviews

tkCRTshader(emulator) has no reviews yet.

Login to write a review.

Consider supporting the creators!

If you enjoyed this asset consider supporting its creator. Follow the link below.