| 1 | #version 300 es |
| 2 | // Posterization Shader - OPTIMIZED |
| 3 | // Fixes: Proper rounding, optional dithering to reduce banding |
| 4 | |
| 5 | precision highp float; |
| 6 | |
| 7 | in vec2 v_texcoord; |
| 8 | uniform sampler2D tex; |
| 9 | out vec4 fragColor; |
| 10 | |
| 11 | // --- CONFIGURATION --- |
| 12 | const float COLOR_LEVELS = 4.0; |
| 13 | // Enable dithering to reduce visible banding |
| 14 | const bool DITHER = true; |
| 15 | |
| 16 | // Simple dither pattern |
| 17 | float dither(vec2 pos) { |
| 18 | // 4x4 Bayer matrix approximation |
| 19 | vec2 p = fract(pos * 0.5); |
| 20 | float d = fract(dot(p, vec2(0.75, 0.5))); |
| 21 | return (d - 0.5) / COLOR_LEVELS; |
| 22 | } |
| 23 | |
| 24 | void main() { |
| 25 | vec4 color = texture(tex, v_texcoord); |
| 26 | |
| 27 | vec3 posterized; |
| 28 | if (DITHER) { |
| 29 | // Add dither noise before quantization |
| 30 | float ditherValue = dither(gl_FragCoord.xy); |
| 31 | posterized = floor((color.rgb + ditherValue) * COLOR_LEVELS + 0.5) / COLOR_LEVELS; |
| 32 | } else { |
| 33 | // FIXED: Use round() behavior instead of floor() for better color accuracy |
| 34 | posterized = floor(color.rgb * COLOR_LEVELS + 0.5) / COLOR_LEVELS; |
| 35 | } |
| 36 | |
| 37 | posterized = clamp(posterized, 0.0, 1.0); |
| 38 | fragColor = vec4(posterized, color.a); |
| 39 | } |