shader_type canvas_item; uniform bool shader_enabled = true; uniform sampler2D palette; uniform sampler2D SCREEN_TEXTURE: hint_screen_texture, filter_linear_mipmap; uniform bool dithering = false; uniform int dithering_size : hint_range(1, 16) = 2; uniform int resolution_scale : hint_range(1, 64) = 2; uniform int quantization_level : hint_range(2, 64) = 64; int dithering_pattern(ivec2 fragcoord) { const int pattern[] = { -4, +0, -3, +1, +2, -2, +3, -1, -3, +1, -4, +0, +3, -1, +2, -2 }; int x = fragcoord.x % 4; int y = fragcoord.y % 4; return pattern[y * 4 + x] * dithering_size; } float color_distance(vec3 a, vec3 b) { vec3 diff = a - b; return dot(diff, diff); } vec3 quantize_color(vec3 color, int levels) { if (levels <= 1) return vec3(0.0); float step = 1.0 / float(levels - 1); return round(color / step) * step; } void fragment() { vec4 final_color; if (!shader_enabled) { final_color = texture(SCREEN_TEXTURE, UV); } else { vec2 tex_size = vec2(textureSize(SCREEN_TEXTURE, 0)); if (tex_size.x <= 0.0 || tex_size.y <= 0.0) { final_color = vec4(0.0); } else { ivec2 texel_coord = ivec2(floor(UV * tex_size / float(resolution_scale))); vec3 color = texelFetch(SCREEN_TEXTURE, texel_coord * resolution_scale, 0).rgb; if (dithering) { int dither = dithering_pattern(texel_coord); color += vec3(float(dither) / 255.0); } color = clamp(color, 0.0, 1.0); color = quantize_color(color, quantization_level); int palette_size = 64; float closest_distance = 9999.0; vec4 closest_color = vec4(0.0); for (int i = 0; i < palette_size; i++) { float u = float(i) / float(palette_size - 1); vec3 palette_color = texture(palette, vec2(u, 0.0)).rgb; float dist = color_distance(color, palette_color); if (dist < closest_distance) { closest_distance = dist; closest_color = vec4(palette_color, 1.0); } } final_color = closest_color; } } COLOR = final_color; }