shader_type canvas_item; uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_nearest; uniform float pixel_scale : hint_range(1.0, 8.0, 1.0) = 4.0; uniform float color_depth : hint_range(2.0, 8.0, 1.0) = 5.0; uniform float dither_strength : hint_range(0.0, 2.0, 0.05) = 0.8; uniform float scanline_strength : hint_range(0.0, 1.0, 0.05) = 0.2; uniform float roll_speed : hint_range(0.0, 10.0, 0.1) = 3.0; uniform float roll_strength : hint_range(0.0, 1.0, 0.05) = 1.0; float bayer4(ivec2 p) { int x = p.x & 3; int y = p.y & 3; float m[16] = float[16]( 0.0, 8.0, 2.0, 10.0, 12.0, 4.0, 14.0, 6.0, 3.0, 11.0, 1.0, 9.0, 15.0, 7.0, 13.0, 5.0 ); return m[y * 4 + x] / 16.0 - 0.5; } void fragment() { vec2 pixel_size = SCREEN_PIXEL_SIZE * pixel_scale; vec2 snapped = (floor(SCREEN_UV / pixel_size) + 0.5) * pixel_size; vec3 col = texture(SCREEN_TEXTURE, snapped).rgb; ivec2 dpos = ivec2(FRAGCOORD.xy / pixel_scale); float t = bayer4(dpos) * dither_strength; float levels = exp2(color_depth) - 1.0; col = clamp(floor(col * levels + t + 0.5) / levels, 0.0, 1.0); if (scanline_strength > 0.0) { // Rolling gradient mask scrolling top-to-bottom float screen_y = FRAGCOORD.y * SCREEN_PIXEL_SIZE.y; float roll_mask = sin(screen_y * TAU - TIME * roll_speed) * 0.5 + 0.5; // Scanlines modulated by the roll: strong at peak, faint at trough float effective_strength = scanline_strength * mix(1.0 - roll_strength, 1.0, roll_mask); float row_frac = fract(FRAGCOORD.y / pixel_scale); float scan = smoothstep(0.0, 0.3, row_frac) * (1.0 - smoothstep(0.7, 1.0, row_frac)); col *= 1.0 - effective_strength * (1.0 - scan); } COLOR = vec4(col, 1.0); }