Realistic CRT shader
This shader features more controls that can simulate an affected antenna.
Features:
- Scan lines
- Warping
- Noise
- Interference (adds horizontal offset to pixel lines)
- RGB grille
- Vignette
- Chromatic aberation
- Rolling line interference
By turning up the noise you can also have a really nice TV static shader.
If you want to support me, I have a twitter @c64cosmin
https://twitter.com/c64cosmin
Also you can check out my game 😀
https://store.steampowered.com/app/2954860/Senseless?utm_source=gds
Shader code
/*
Shader from Godot Shaders - the free shader library.
This shader is under CC0 license. Feel free to use, improve and
change this shader according to your needs and consider sharing
the modified result to godotshaders.com.
Optimised and packed by @c64cosmin
If you do use this please share it with me
Would love to see what you're making with it <3
It's a combination of these two shaders
~godotshaders.com/shader/VHS-and-CRT-monitor-effect
godotshaders.com/shader/crt-shader-with-realistic-blurring/
CRT grille and rolling lines made by @c64cosmin
Vignette and warping effect was made by pend00
Scanlines are from "TimothyLottes" FROM SHADERTOY
Then ported by AHOPNESS (@ahopness)
https://www.shadertoy.com/view/MsjXzh
*/
shader_type canvas_item;
uniform sampler2D SCREEN_TEXTURE: hint_screen_texture;
uniform vec2 resolution = vec2(320.0, 180.0);
uniform float scan_line_amount :hint_range(0.0, 1.0) = 1.0;
uniform float warp_amount :hint_range(0.0, 5.0) = 0.1;
uniform float noise_amount :hint_range(0.0, 0.3) = 0.03;
uniform float interference_amount :hint_range(0.0, 1.0) = 0.2;
uniform float grille_amount :hint_range(0.0, 1.0) = 0.1;
uniform float grille_size :hint_range(1.0, 5.0) = 1.0;
uniform float vignette_amount :hint_range(0.0, 2.0) = 0.6;
uniform float vignette_intensity : hint_range(0.0, 1.0) = 0.4;
uniform float aberation_amount :hint_range(0.0, 1.0) = 0.5;
uniform float roll_line_amount :hint_range(0.0, 1.0) = 0.3;
uniform float roll_speed :hint_range(-8.0, 8.0) = 1.0;
uniform float scan_line_strength :hint_range(-12.0, -1.0) = -8.0;
uniform float pixel_strength :hint_range(-4.0, 0.0) = -2.0;
float random(vec2 uv){
return fract(cos(uv.x * 83.4827 + uv.y * 92.2842) * 43758.5453123);
}
vec3 fetch_pixel(vec2 uv, vec2 off){
vec2 pos = floor(uv * resolution + off) / resolution + vec2(0.5) / resolution;
float noise = 0.0;
if(noise_amount > 0.0){
noise = random(pos + fract(TIME)) * noise_amount;
}
if(max(abs(pos.x - 0.5), abs(pos.y - 0.5)) > 0.5){
return vec3(0.0, 0.0, 0.0);
}
vec3 clr = texture(SCREEN_TEXTURE , pos, -16.0).rgb + noise;
return clr;
}
// Distance in emulated pixels to nearest texel.
vec2 Dist(vec2 pos){
pos = pos * resolution;
return - ((pos - floor(pos)) - vec2(0.5));
}
// 1D Gaussian.
float Gaus(float pos, float scale){ return exp2(scale * pos * pos); }
// 3-tap Gaussian filter along horz line.
vec3 Horz3(vec2 pos, float off){
vec3 b = fetch_pixel(pos, vec2(-1.0, off));
vec3 c = fetch_pixel(pos, vec2( 0.0, off));
vec3 d = fetch_pixel(pos, vec2( 1.0, off));
float dst = Dist(pos).x;
// Convert distance to weight.
float scale = pixel_strength;
float wb = Gaus(dst - 1.0, scale);
float wc = Gaus(dst + 0.0, scale);
float wd = Gaus(dst + 1.0, scale);
// Return filtered sample.
return (b * wb + c * wc + d * wd) / (wb + wc + wd);
}
// Return scanline weight.
float Scan(vec2 pos, float off){
float dst = Dist(pos).y;
return Gaus(dst + off, scan_line_strength);
}
// Allow nearest three lines to effect pixel.
vec3 Tri(vec2 pos){
vec3 clr = fetch_pixel(pos, vec2(0.0));
if(scan_line_amount > 0.0){
vec3 a = Horz3(pos,-1.0);
vec3 b = Horz3(pos, 0.0);
vec3 c = Horz3(pos, 1.0);
float wa = Scan(pos,-1.0);
float wb = Scan(pos, 0.0);
float wc = Scan(pos, 1.0);
vec3 scanlines = a * wa + b * wb + c * wc;
clr = mix(clr, scanlines, scan_line_amount);
}
return clr;
}
// Takes in the UV and warps the edges, creating the spherized effect
vec2 warp(vec2 uv){
vec2 delta = uv - 0.5;
float delta2 = dot(delta.xy, delta.xy);
float delta4 = delta2 * delta2;
float delta_offset = delta4 * warp_amount;
vec2 warped = uv + delta * delta_offset;
return (warped - 0.5) / mix(1.0,1.2,warp_amount/5.0) + 0.5;
}
float vignette(vec2 uv){
uv *= 1.0 - uv.xy;
float vignette = uv.x * uv.y * 15.0;
return pow(vignette, vignette_intensity * vignette_amount);
}
float floating_mod(float a, float b){
return a - b * floor(a/b);
}
vec3 grille(vec2 uv){
float unit = PI / 3.0;
float scale = 2.0*unit/grille_size;
float r = smoothstep(0.5, 0.8, cos(uv.x*scale - unit));
float g = smoothstep(0.5, 0.8, cos(uv.x*scale + unit));
float b = smoothstep(0.5, 0.8, cos(uv.x*scale + 3.0*unit));
return mix(vec3(1.0), vec3(r,g,b), grille_amount);
}
float roll_line(vec2 uv){
float x = uv.y * 3.0 - TIME * roll_speed;
float f = cos(x) * cos(x * 2.35 + 1.1) * cos(x * 4.45 + 2.3);
float roll_line = smoothstep(0.5, 0.9, f);
return roll_line * roll_line_amount;
}
void fragment(){
vec2 pix = FRAGCOORD.xy;
vec2 pos = warp(SCREEN_UV);
float line = 0.0;
if(roll_line_amount > 0.0){
line = roll_line(pos);
}
vec2 sq_pix = floor(pos * resolution) / resolution + vec2(0.5) / resolution;
if(interference_amount + roll_line_amount > 0.0){
float interference = random(sq_pix.yy + fract(TIME));
pos.x += (interference * (interference_amount + line * 6.0)) / resolution.x;
}
vec3 clr = Tri(pos);
if(aberation_amount > 0.0){
float chromatic = aberation_amount + line * 2.0;
vec2 chromatic_x = vec2(chromatic,0.0) / resolution.x;
vec2 chromatic_y = vec2(0.0, chromatic/2.0) / resolution.y;
float r = Tri(pos - chromatic_x).r;
float g = Tri(pos + chromatic_y).g;
float b = Tri(pos + chromatic_x).b;
clr = vec3(r,g,b);
}
if(grille_amount > 0.0)clr *= grille(pix);
clr *= 1.0 + scan_line_amount * 0.6 + line * 3.0 + grille_amount * 2.0;
if(vignette_amount > 0.0)clr *= vignette(pos);
COLOR.rgb = clr;
COLOR.a = 1.0;
}
This looks great, thanks for sharing!
I was wondering if you could help me get it working in my game, which is made in Godot 3.5.3 using GLES2. I get this error when I try to copy in your shader:
“error(25): Redefinition of ‘SCREEN_TEXTURE'”
After looking into it a bit, it seems like SCREEN_TEXTURE is predefined in Godot 3, so the error seems to come from trying to define it again. However, when I remove the line that defines SCREEN_TEXTURE, I get a different error:
“error(59): Unknown identifier in expression: SCREEN_TEXTURE”
So it seems to be saying that I do need to define it, but when it do it also errors out. Any ideas what I could do to get it working? Thanks in advance!
This seems to produce a white rectangle that covers my game, instead of overlaying it. Is there something I need to do to get it to overlay the game? I see there’s an empty hint_screen_texture parameter, but I’m not sure what I should put there.
For anyone else who might be looking in the future, I managed to update this shader to work in Godot 3:
Hi, is there a way to change shader resolution in game?
this is the code for godot 3.6,3.5 or godot 3 hehe you need go to visibility and put down the alpha use a colorrect ,
This seems very versatile, thanks for sharing!
PS- Senseless looks neat, good luck 🙂
eu amei esse shader ele tem muitos tipos de configuração é bem trabalhado e ainda é oferecido de graça porem ainda é muito pesado mas até que faz sentido pela quantidade de configurações que ele tem