The Code Therapy

Me on my Commodore 64

Commodore 64 color palette with dithering over my webcam

Created by marcogomez on Sat, 25 Sep 2021 14:40:39 GMT.


// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;

uniform sampler2D prgm2Texture;
uniform vec2 resolution;
uniform float time;

void main(void) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  vec4 prgm2 = texture2D(prgm2Texture, uv);
  gl_FragColor = prgm2;
}

// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;

uniform sampler2D camTexture;
uniform vec2 resolution;

void main(void) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  float ar = resolution.x / resolution.y;
  uv = uv * 2.0 - 1.0; uv *= 0.9; uv = uv * 0.5 + 0.5;
  vec2 uvar = uv * vec2(ar, 16.0 / 9.0) * 0.5;
  vec4 cam = texture2D(camTexture, uvar);
  gl_FragColor = cam;
}

// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;

uniform sampler2D prgm1Texture;
uniform vec2 resolution;

const float reinhardAmount = 0.6;
const float contrast = 1.3;
const float brightness = 1.3;
const float saturation = 0.8;
const vec2 vignetteSize = vec2(0.25, 0.25);
const float vignetteRoundness = 0.3;
const float vignetteMix = 0.5;
const float vignetteSmoothness = 0.5;
const float W = 1.2;
const float T = 7.5;

vec3 findClosest(in vec3 ref) {
  vec3 old = vec3 (100.0 * 255.0);
  #define try(new) old = mix (new, old, step (length (old-ref), length (new-ref)));
  try(vec3(000.0, 000.0, 000.0)); //  0 - black       (YPbPr = 0.0   ,  0.0   ,  0.0  )
  try(vec3(255.0, 255.0, 255.0)); //  1 - white       (YPbPr = 1.0   ,  0.0   ,  0.0  )
  try(vec3(161.0, 077.0, 067.0)); //  2 - red         (YPbPr = 0.313 , -0.383 ,  0.924)
  try(vec3(106.0, 193.0, 200.0)); //  3 - cyan        (YPbPr = 0.625 ,  0.383 , -0.924)
  try(vec3(162.0, 086.0, 165.0)); //  4 - purple      (YPbPr = 0.375 ,  0.707 ,  0.707)
  try(vec3(092.0, 173.0, 095.0)); //  5 - green       (YPbPr = 0.5   , -0.707 , -0.707)
  try(vec3(079.0, 068.0, 156.0)); //  6 - blue        (YPbPr = 0.25  ,  1.0   ,  0.0  )
  try(vec3(203.0, 214.0, 137.0)); //  7 - yellow      (YPbPr = 0.75  , -1.0   ,  0.0  )
  try(vec3(163.0, 104.0, 058.0)); //  8 - orange      (YPbPr = 0.375 , -0.707 ,  0.707)
  try(vec3(110.0, 083.0, 011.0)); //  9 - brown       (YPbPr = 0.25  , -0.924 ,  0.383)
  try(vec3(204.0, 127.0, 118.0)); // 10 - light red   (YPbPr = 0.5   , -0.383 ,  0.924)
  try(vec3(099.0, 099.0, 099.0)); // 11 - dark grey   (YPbPr = 0.313 ,  0.0   ,  0.0  )
  try(vec3(139.0, 139.0, 139.0)); // 12 - grey        (YPbPr = 0.469 ,  0.0   ,  0.0  )
  try(vec3(155.0, 227.0, 157.0)); // 13 - light green (YPbPr = 0.75  , -0.707 , -0.707)
  try(vec3(138.0, 127.0, 205.0)); // 14 - light blue  (YPbPr = 0.469 ,  1.0   ,  0.0  )
  try(vec3(175.0, 175.0, 175.0)); // 15 - light grey  (YPbPr = 0.625 , 0.0    ,  0.0  )
  return old;
}

const mat4 bayertl = mat4(
  00.0 / 64.0, 32.0 / 64.0, 08.0 / 64.0, 40.0 / 64.0,
  48.0 / 64.0, 16.0 / 64.0, 56.0 / 64.0, 24.0 / 64.0,
  12.0 / 64.0, 44.0 / 64.0, 04.0 / 64.0, 36.0 / 64.0,
  60.0 / 64.0, 28.0 / 64.0, 52.0 / 64.0, 20.0 / 64.0
);

const mat4 bayertr = mat4(
  02.0 / 64.0, 34.0 / 64.0, 10.0 / 64.0, 42.0 / 64.0,
  50.0 / 64.0, 18.0 / 64.0, 58.0 / 64.0, 26.0 / 64.0,
  14.0 / 64.0, 46.0 / 64.0, 06.0 / 64.0, 38.0 / 64.0,
  62.0 / 64.0, 30.0 / 64.0, 54.0 / 64.0, 22.0 / 64.0
);

const mat4 bayerbl = mat4(
  03.0 / 64.0, 35.0 / 64.0, 11.0 / 64.0, 43.0 / 64.0,
  51.0 / 64.0, 19.0 / 64.0, 59.0 / 64.0, 27.0 / 64.0,
  15.0 / 64.0, 47.0 / 64.0, 07.0 / 64.0, 39.0 / 64.0,
  63.0 / 64.0, 31.0 / 64.0, 55.0 / 64.0, 23.0 / 64.0
);

const mat4 bayerbr = mat4(
  01.0 / 64.0, 33.0 / 64.0, 09.0 / 64.0, 41.0 / 64.0,
  49.0 / 64.0, 17.0 / 64.0, 57.0 / 64.0, 25.0 / 64.0,
  13.0 / 64.0, 45.0 / 64.0, 05.0 / 64.0, 37.0 / 64.0,
  61.0 / 64.0, 29.0 / 64.0, 53.0 / 64.0, 21.0 / 64.0
);

float dither(mat4 m, ivec2 p) {
  if (p.y == 0) {
    if (p.x == 0) { return m[0][0]; }
    else if (p.x == 1) { return m[1][0]; }
    else if (p.x == 2) { return m[2][0]; }
    else { return m[3][0]; }
  } else if (p.y == 1) {
    if (p.x == 0) { return m[0][1]; }
    else if (p.x == 1) { return m[1][1]; }
    else if (p.x == 2) { return m[2][1]; }
    else { return m[3][1]; }
  } else if (p.y == 2) {
    if (p.x == 0) { return m[0][1]; }
    else if (p.x == 1) { return m[1][2]; }
    else if (p.x == 2) { return m[2][2]; }
    else { return m[3][2]; }
  } else {
    if (p.x == 0) { return m[0][3]; }
    else if (p.x == 1) { return m[1][3]; }
    else if (p.x == 2) { return m[2][3]; }
    else { return m[3][3]; }
  }
}

float filmicReinhardCurve(float x) {
  float q = (T * T + 1.0) * x * x;
  return q / (q + x + T * T);
}

vec3 filmicReinhard(vec3 c) {
  float w = filmicReinhardCurve(W);
  return vec3(
    filmicReinhardCurve(c.r),
    filmicReinhardCurve(c.g),
    filmicReinhardCurve(c.b)
  ) / w;
}

vec3 ContrastSaturationBrightness(vec3 color, float brt, float sat, float con) {
  const float AvgLumR = 0.5;
  const float AvgLumG = 0.5;
  const float AvgLumB = 0.5;
  const vec3 LumCoeff = vec3(0.2125, 0.7154, 0.0721);
  vec3 AvgLumin = vec3(AvgLumR, AvgLumG, AvgLumB);
  vec3 brtColor = color * brt;
  vec3 intensity = vec3(dot(brtColor, LumCoeff));
  vec3 satColor = mix(intensity, brtColor, sat);
  vec3 conColor = mix(AvgLumin, satColor, con);
  return conColor;
}

float sdSquare(vec2 point, float width) {
  vec2 d = abs(point) - width;
  return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}

float vignette(vec2 uv, vec2 size, float roundness, float smoothness) {
  uv -= 0.5;
  float minWidth = min(size.x, size.y);
  uv.x = sign(uv.x) * clamp(abs(uv.x) - abs(minWidth - size.x), 0.0, 1.0);
  uv.y = sign(uv.y) * clamp(abs(uv.y) - abs(minWidth - size.y), 0.0, 1.0);
  float boxSize = minWidth * (1.0 - roundness);
  float dist = sdSquare(uv, boxSize) - (minWidth * roundness);
  return 1.0 - smoothstep(0.0, smoothness, dist);
}

void main(void) {
  ivec2 p = ivec2(mod(gl_FragCoord.xy, 8.0));
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  vec2 res = vec2(320.0, 200.0);
  uv = vec2(floor(uv.x * res.x + 0.5) / res.x, floor(uv.y * res.y + 0.5) / res.y);
  vec4 prgm1 = texture2D(prgm1Texture, uv);
  vec3 c = prgm1.xyz;
  vec3 reinhard = filmicReinhard(c);
  c = mix(c + vec3(0.1), reinhard, reinhardAmount);
  c = ContrastSaturationBrightness(c, brightness, saturation, contrast);
  float v = vignette(uv, vignetteSize, vignetteRoundness, vignetteSmoothness);
  vec3 vig = c * v;
  c = mix(c, vig, vignetteMix);
  c = pow(abs(c), vec3(2.2));
  c -= 1.0 / 255.0;
  vec3 d = vec3(0.0);
  if (p.x <= 3 && p.y <= 3) {
    d.r = float(c.r > dither(bayertl, p));
    d.g = float(c.g > dither(bayertl, p));
    d.b = float(c.b > dither(bayertl, p));
  } else if (p.x > 3 && p.y <= 3) {
    d.r = float(c.r > dither(bayertr, p - ivec2(4, 0)));
    d.g = float(c.g > dither(bayertr, p - ivec2(4, 0)));
    d.b = float(c.b > dither(bayertr, p - ivec2(4, 0)));
  } else if (p.x <= 3 && p.y > 3) {
    d.r = float(c.r > dither(bayerbl, p - ivec2(0, 4)));
    d.g = float(c.g > dither(bayerbl, p - ivec2(0, 4)));
    d.b = float(c.b > dither(bayerbl, p - ivec2(0, 4)));
  } else if (p.x > 3 && p.y > 3) {
    d.r = float(c.r > dither(bayerbr, p - ivec2(4, 4)));
    d.g = float(c.g > dither(bayerbr, p - ivec2(4, 4)));
    d.b = float(c.b > dither(bayerbr, p - ivec2(4, 4)));
  }
  d = findClosest(clamp(d * 255.0, 0.0, 255.0)) / 255.0;
  vec4 color = vec4(d, 1.0);
  gl_FragColor = color;
}