// C64 palette and ordered-dither quantization. // Provides Dither() to snap any RGBA color to the nearest C64 color pair // via 8x8 Bayer threshold, suitable for NTSC and retro post-process effects. const NUM_COLORS : u32 = 16; const C64Colors = array( vec3f(13.0/255.0, 13.0/255.0, 13.0/255.0), // 0 black vec3f(242.0/255.0, 242.0/255.0, 242.0/255.0), // 1 white vec3f(89.0/255.0, 39.0/255.0, 33.0/255.0), // 2 red vec3f(170.0/255.0, 220.0/255.0, 240.0/255.0), // 3 cyan vec3f(101.0/255.0, 27.0/255.0, 109.0/255.0), // 4 purple vec3f(120.0/255.0, 198.0/255.0, 112.0/255.0), // 5 green vec3f(54.0/255.0, 53.0/255.0, 152.0/255.0), // 6 blue vec3f(226.0/255.0, 229.0/255.0, 115.0/255.0), // 7 yellow vec3f(141.0/255.0, 85.0/255.0, 50.0/255.0), // 8 orange vec3f(89.0/255.0, 63.0/255.0, 13.0/255.0), // 9 brown vec3f(189.0/255.0, 110.0/255.0, 115.0/255.0), // 10 pink vec3f(80.0/255.0, 80.0/255.0, 80.0/255.0), // 11 dark gray vec3f(124.0/255.0, 124.0/255.0, 124.0/255.0), // 12 gray vec3f(165.0/255.0, 242.0/255.0, 156.0/255.0), // 13 bright green vec3f(114.0/255.0, 111.0/255.0, 224.0/255.0), // 14 light blue vec3f(128.0/255.0, 128.0/255.0, 128.0/255.0) // 15 middle gray ); const BAYER_8X8 = array( 0, 32, 8, 40, 2, 34, 10, 42, 48, 16, 56, 24, 50, 18, 58, 26, 12, 44, 4, 36, 14, 46, 6, 38, 60, 28, 52, 20, 62, 30, 54, 22, 3, 35, 11, 43, 1, 33, 9, 41, 51, 19, 59, 27, 49, 17, 57, 25, 15, 47, 7, 39, 13, 45, 5, 37, 63, 31, 55, 23, 61, 29, 53, 21 ); const DistanceParam = 0.65; fn colorDistance(color: vec4f, c1: vec4f, c2: vec4f, frac: f32) -> f32 { return mix(distance(color, mix(c1, c2, frac)), distance(color, c1) + distance(color, c2), 0.5 * DistanceParam * DistanceParam); } // Quantize col to the nearest C64 color pair using 8x8 Bayer dithering. // xsize/ysize: virtual pixel grid dimensions (e.g. 54.*8., 33.*8.). fn Dither(col: vec4f, uv: vec2f, xsize: f32, ysize: f32) -> vec4f { let ix = u32(floor(uv.x * xsize / 2.)) % 8; let iy = u32(floor(uv.y * ysize / 2.)) % 8; let thresh = f32(BAYER_8X8[ix + 8 * iy]) / 64.; var color = min(col, vec4f(1.)); color *= color; // ~gamma var c1 = vec4f(0.); var c2 = vec4f(0.); var frac = 0.0; var best = 1.e5; for (var i = 0u; i < NUM_COLORS; i += 1u) { var p1 = vec4f(C64Colors[i], 1.); p1 *= p1; for (var j = i + 1u; j < NUM_COLORS; j += 1u) { var p2 = vec4f(C64Colors[j], 1.); p2 *= p2; // min-L2 optim let dc = p1 - p2; var a = dot(dc, p1 - color) / dot(dc, dc); a = clamp(a, 0., 1.); let dist = colorDistance(color, p1, p2, a); if (dist < best) { best = dist; c1 = p1; c2 = p2; frac = a; } } } color = mix(c1, c2, f32(frac > thresh)); return sqrt(color); }