CRT Visual Shader godot 4.0+
CRT Shader for Godot Engine
A custom CRT (Cathode Ray Tube) shader for the Godot Engine, designed to replicate the nostalgic look of old CRT monitors. The shader includes various effects such as scanlines, screen warping, chromatic aberration, noise, and more for retro-style visuals.
Features
- Scanlines: Simulates horizontal lines typical of CRT displays
- Screen Warping: Mimics the curvature of old CRT screens
- Chromatic Aberration: Adds subtle color separation effects
- Noise & Static: Introduces random noise and static for authenticity
- Vignette: Darkens screen edges for a cinematic effect
- Pixelation: Optional pixelation for low-resolution look
- Discoloration: VHS-style color distortion
- Roll Effect: Simulates rolling effects from old CRT TVs
Customizable parameters to tweak the CRT effect:
- Overlay: Toggles shader application as overlay
- Resolution: Sets pixelation resolution
- Brightness: Adjusts overall screen brightness
- Scanlines Opacity: Controls scanline visibility
- Scanlines Width: Adjusts scanline thickness
- Grille Opacity: Controls CRT grille effect opacity
- Roll: Enables/disables rolling effect
- Roll Speed: Adjusts rolling speed
- Roll Size: Controls rolling effect size
- Roll Variation: Adds variation to rolling
- Distort Intensity: Controls screen distortion intensity
- Aberration: Adjusts chromatic aberration amount
- Noise Opacity: Controls noise visibility
- Noise Speed: Adjusts noise effect speed
- Static Noise Intensity: Controls static noise intensity
- Pixelate: Toggles pixelation
- Discolor: Toggles VHS-style discoloration
- Warp Amount: Controls screen warping amount
- Clip Warp: Clips warping to screen borders
- Vignette Intensity: Controls vignette intensity
- Vignette Opacity: Adjusts vignette opacity
Contributing
Suggestions, improvements, and bug fixes are welcome. Please open an issue or submit a pull request.
License
Licensed under the MIT License. See LICENSE for details.
Acknowledgments
Inspired by various CRT shaders and retro-style visual effects
Thanks to the Godot Engine community for support and resources
Repository: https://github.com/Lord0Sanz/CRT-VISUAL-SHADER-GODOT-4.0-/
Shader code
shader_type canvas_item;
// Add required screen texture uniform
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;
// Display settings
uniform bool overlay = false;
uniform vec2 resolution = vec2(640.0, 480.0);
uniform float brightness = 1.4;
// Scanline settings
uniform float scanlines_opacity : hint_range(0.0, 1.0) = 0.4;
uniform float scanlines_width : hint_range(0.0, 0.5) = 0.25;
uniform float grille_opacity : hint_range(0.0, 1.0) = 0.3;
// Distortion settings
uniform bool roll = true;
uniform float roll_speed = 8.0;
uniform float roll_size : hint_range(0.0, 100.0) = 15.0;
uniform float roll_variation : hint_range(0.1, 5.0) = 1.8;
uniform float distort_intensity : hint_range(0.0, 0.2) = 0.05;
uniform float aberration : hint_range(-1.0, 1.0) = 0.03;
// Noise settings
uniform float noise_opacity : hint_range(0.0, 1.0) = 0.4;
uniform float noise_speed = 5.0;
uniform float static_noise_intensity : hint_range(0.0, 1.0) = 0.06;
// Additional effects
uniform bool pixelate = true;
uniform bool discolor = true;
uniform float warp_amount : hint_range(0.0, 5.0) = 1.0;
uniform bool clip_warp = false;
uniform float vignette_intensity = 0.4;
uniform float vignette_opacity : hint_range(0.0, 1.0) = 0.5;
// The rest of the shader code remains exactly the same from here...
// (All the functions and fragment shader code remain unchanged)
// Generate random value
vec2 random(vec2 uv) {
uv = vec2(dot(uv, vec2(127.1, 311.7)), dot(uv, vec2(269.5, 183.3)));
return -1.0 + 2.0 * fract(sin(uv) * 43758.5453123);
}
// Generate noise
float noise(vec2 uv) {
vec2 uv_index = floor(uv);
vec2 uv_fract = fract(uv);
vec2 blur = smoothstep(0.0, 1.0, uv_fract);
return mix(
mix(
dot(random(uv_index + vec2(0.0, 0.0)), uv_fract - vec2(0.0, 0.0)),
dot(random(uv_index + vec2(1.0, 0.0)), uv_fract - vec2(1.0, 0.0)),
blur.x
),
mix(
dot(random(uv_index + vec2(0.0, 1.0)), uv_fract - vec2(0.0, 1.0)),
dot(random(uv_index + vec2(1.0, 1.0)), uv_fract - vec2(1.0, 1.0)),
blur.x
),
blur.y
) * 0.5 + 0.5;
}
// Screen warping
vec2 warp(vec2 uv) {
vec2 delta = uv - 0.5;
float delta2 = dot(delta.xy, delta.xy);
float delta4 = delta2 * delta2;
return uv + delta * (delta4 * warp_amount);
}
// Screen border
float border(vec2 uv) {
float radius = min(warp_amount, 0.08);
radius = max(min(min(abs(radius * 2.0), abs(1.0)), abs(1.0)), 1e-5);
vec2 abs_uv = abs(uv * 2.0 - 1.0) - vec2(1.0, 1.0) + radius;
float dist = length(max(vec2(0.0), abs_uv)) / radius;
return clamp(1.0 - smoothstep(0.96, 1.0, dist), 0.0, 1.0);
}
// Vignette effect
float vignette(vec2 uv) {
uv *= 1.0 - uv.xy;
float vig = uv.x * uv.y * 15.0;
return pow(vig, vignette_intensity * vignette_opacity);
}
void fragment() {
// Get base UV and handle overlay
vec2 uv = overlay ? warp(SCREEN_UV) : warp(UV);
vec2 text_uv = uv;
// Handle pixelation
if (pixelate) {
text_uv = ceil(uv * resolution) / resolution;
}
// Calculate roll effect
float roll_line = 0.0;
vec2 roll_uv = vec2(0.0);
if (roll || noise_opacity > 0.0) {
float time = roll ? TIME : 0.0;
roll_line = smoothstep(0.3, 0.9, sin(uv.y * roll_size - (time * roll_speed)));
roll_line *= roll_line * smoothstep(0.3, 0.9, sin(uv.y * roll_size * roll_variation - (time * roll_speed * roll_variation)));
roll_uv = vec2(roll_line * distort_intensity * (1.0 - UV.x), 0.0);
}
// Sample texture with chromatic aberration
vec4 text;
if (roll) {
text.r = texture(SCREEN_TEXTURE, text_uv + roll_uv * 0.8 + vec2(aberration, 0.0) * 0.1).r;
text.g = texture(SCREEN_TEXTURE, text_uv + roll_uv * 1.2 - vec2(aberration, 0.0) * 0.1).g;
text.b = texture(SCREEN_TEXTURE, text_uv + roll_uv).b;
} else {
text.r = texture(SCREEN_TEXTURE, text_uv + vec2(aberration, 0.0) * 0.1).r;
text.g = texture(SCREEN_TEXTURE, text_uv - vec2(aberration, 0.0) * 0.1).g;
text.b = texture(SCREEN_TEXTURE, text_uv).b;
}
text.a = 1.0;
// Apply CRT grille
if (grille_opacity > 0.0) {
float gr = smoothstep(0.85, 0.95, abs(sin(uv.x * (resolution.x * 3.14159265))));
float gg = smoothstep(0.85, 0.95, abs(sin(1.05 + uv.x * (resolution.x * 3.14159265))));
float gb = smoothstep(0.85, 0.95, abs(sin(2.1 + uv.x * (resolution.x * 3.14159265))));
text.r = mix(text.r, text.r * gr, grille_opacity);
text.g = mix(text.g, text.g * gg, grille_opacity);
text.b = mix(text.b, text.b * gb, grille_opacity);
}
// Apply brightness
text.rgb = clamp(text.rgb * brightness, 0.0, 1.0);
// Apply scanlines
if (scanlines_opacity > 0.0) {
float scan = smoothstep(scanlines_width, scanlines_width + 0.5, abs(sin(uv.y * (resolution.y * 3.14159265))));
text.rgb = mix(text.rgb, text.rgb * vec3(scan), scanlines_opacity);
}
// Apply noise
if (noise_opacity > 0.0) {
float n = smoothstep(0.4, 0.5, noise(uv * vec2(2.0, 200.0) + vec2(10.0, TIME * noise_speed)));
float nl = n * roll_line * clamp(random((ceil(uv * resolution) / resolution) + vec2(TIME * 0.8, 0.0)).x + 0.8, 0.0, 1.0);
text.rgb = clamp(mix(text.rgb, text.rgb + nl, noise_opacity), vec3(0.0), vec3(1.0));
}
// Apply static
if (static_noise_intensity > 0.0) {
text.rgb += clamp(random((ceil(uv * resolution) / resolution) + fract(TIME)).x, 0.0, 1.0) * static_noise_intensity;
}
// Apply border and vignette
text.rgb *= border(uv);
text.rgb *= vignette(uv);
// Handle clip warp
if (clip_warp) {
text.a = border(uv);
}
// Apply VHS discoloration
if (discolor) {
vec3 greyscale = vec3(dot(text.rgb, vec3(0.333)));
text.rgb = mix(text.rgb, greyscale, 0.5);
float midpoint = pow(0.5, 2.2);
text.rgb = (text.rgb - vec3(midpoint)) * 1.2 + midpoint;
}
COLOR = text;
}

I applied in a ColorRect that is in a CanvasLayer in the top of the scene, and everything was perfect
It is like magic!!
Thanks a lot!!
well i wanted to make a easy to use shader without like debugging thanks for using it !!! 🫡
yo, really cool shader! for some reason i cant seem to get the overlay working, it looks fine in the editor and then when i play the scene it reverts to looking like the non-overlay version… i have a control node for scene root, then canvas layer, then color rect with the shader 🙁
well if you have too many layers the GPU may limit the render to a few layers.