Dithered Observations

This is a simple plasma retro piece created to study and observe color quantization, resolution constraining, and different types of dithering.

Created by marcogomez on Wed, 27 Oct 2021 10:23:54 GMT.


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

uniform sampler2D prgm2Texture;
uniform vec2 resolution;

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

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

uniform vec2 resolution;
uniform vec2 mouselerp;
uniform float time;
uniform float fftNormalized;

const float PI = acos(-1.0);
const float TAU = PI * 2.0;

void main(void) {
  float t = time * 0.2;
  vec2 res = resolution.xy;
  vec2 coord = gl_FragCoord.xy;
  float ar = resolution.x / resolution.y;
  vec2 mCoord = vec2(mouselerp.x * res.x / ar, mouselerp.y * res.y / ar);
  coord -= mCoord;
  float scale = resolution.x / 1.25;
  vec2 center = vec2(
    (res.x / 2.0) + sin(t) * (res.x / 1.5),
    (res.y / 2.0) + cos(t) * (res.y / 1.5)
  );
  float dist = length(coord.xy - center);
  float c = (
    (sin(dist / (scale /  7.6) + sin(t * 1.1) * 5.0) + 1.25) +
    (sin(dist / (scale / 11.5) - sin(t * 1.1) * 6.0) + 1.25)
  );
  float x = (
    (sin(coord.x / (scale / 6.5) + sin(t * 1.1) * 4.5) + 1.25) +
    (sin(coord.x / (scale / 9.2) - sin(t * 1.1) * 5.5) + 1.25) +
    fftNormalized + (mouselerp.x * TAU)
  );
  float y = (
    (sin(coord.y / (scale /  6.8) + sin(t * 1.1) * 4.75) + 1.25) +
    (sin(coord.y / (scale / 12.5) - sin(t * 1.1) * 5.75) + 1.25) +
    fftNormalized + (mouselerp.y * TAU)
  );
  float acc = c + x + y / 3.0;
  vec3 color = vec3(
    (cos(PI * acc / 4.0 + t * 3.0) + 1.0) / 2.0,
    (sin(PI * acc / 3.5 + t * 3.0) + 1.0) / 2.5,
    (sin(PI * acc / 2.0 + t * 3.0) + 2.0) / 8.0
  );
  gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);
}

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

uniform sampler2D blueNoiseTexture;
uniform sampler2D ditherTexture;
uniform sampler2D prgm1Texture;
uniform vec2 resolution;
uniform float time;

#define BITS           5
#define PIXEL_SIZE     8
#define ENCODE_IN_SRGB 1
#define DITHER         1
#define DITHER_TYPE    1

const float PI = acos(-1.0);
const float TAU = PI * 2.0;
const float bgWaveSpeed = 1.0;
const float xDistMag = 0.025;
const float yDistMag = 0.025;
const float xSineCycles = TAU;
const float ySineCycles = TAU;

float uniformNoise(vec2 n) {
  return fract(sin(dot(n, vec2(12.9898, 78.233))) * 43758.5453);
}

float triangleNoise(vec2 n) {
  vec2 p = fract(n * vec2(5.3987, 5.4421));
  p += dot(p.yx, p.xy + vec2(21.5351, 14.3137));
  float xy = p.x * p.y;
  float noise = (fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0);
  return noise;
}

float interleavedGradientNoise(vec2 n) {
  float f = 0.06711056 * n.x + 0.00583715 * n.y;
  return fract(52.9829189 * fract(f));
}

float triangleRemap(float n) {
  float origin = n * 2.0 - 1.0;
  float v = origin / sqrt(abs(origin));
  v = max(-1.0, v);
  v -= sign(origin);
  return v;
}

vec3 triangleRemap(const vec3 n) {
  return vec3(
    triangleRemap(n.x),
    triangleRemap(n.y),
    triangleRemap(n.z)
  );
}

vec3 HSVToSRGB(float h, float s, float v) {
  return vec3(
    v * (1.0 - s + s * clamp(abs(fract(h + 1.0)       * 6.0 - 3.0) - 1.0, 0.0, 1.0)),
    v * (1.0 - s + s * clamp(abs(fract(h + 2.0 / 3.0) * 6.0 - 3.0) - 1.0, 0.0, 1.0)),
    v * (1.0 - s + s * clamp(abs(fract(h + 1.0 / 3.0) * 6.0 - 3.0) - 1.0, 0.0, 1.0))
  );
}

vec3 SRGBToRGB(vec3 v) {
  for (int i = 0; i < 3; ++i) {
    if (v[i] <= 0.04045) {
      v[i] /= 12.92;
    } else {
      v[i] = pow((v[i] + 0.055) / (1.055), 2.4);
    }
  }
  return v;
}

vec3 RGBToSRGB(vec3 v) {
  for (int i = 0; i < 3; ++i) {
    if (v[i] <= 0.0031308) {
      v[i] *= 12.92;
    } else {
      v[i] = (1.055) * pow(v[i], 1.0/2.4) - 0.055;
    }
  }
  return v;
}

float precisionFromBits(float b) {
  return 1.0 / (pow(abs(2.0), b) - 1.0);
}

vec3 roundToPrecision(vec3 v, float p) {
  return clamp(p * floor((v / p) + 0.5), vec3(0.0), vec3(1.0));
}

vec3 dither(vec3 v, vec2 fragCoord, float p, int type) {
  vec2 coord = fragCoord.xy / 16.0;
  if (type == 1) {
    return v + (texture2D(blueNoiseTexture, mod(fragCoord, 1024.0) / 1024.0).rgb - 0.5) * (p * 1.5);
  } else if (type == 2) {
    float n = texture2D(ditherTexture, fract(coord)).r;
    n = n - 0.5;
    return v + vec3(n / 4.0);
  } else if (type == 3) {
    float noise = uniformNoise((coord));
    noise = noise - 0.5;
    return v + vec3(noise / 8.0);
  } else if (type == 4) {
    vec3 noise = vec3(triangleNoise(coord) / 8.0);
    return v + noise;
  } else if (type == 5) {
    float noise = interleavedGradientNoise(coord * 4.0);
    noise = noise - 0.5;
    return v + vec3(noise / 4.0);
  } else if (type == 6) {
    vec3 noise = vec3(dot(vec2(171.0, 231.0), coord / 1.3));
    noise = fract(noise / vec3(103.0, 71.0, 97.0));
    noise = triangleRemap(noise);
    return v + vec3(noise / 8.0);
  } else if (type == 7) {
    float gridPosition = fract(dot(fragCoord.xy - vec2(0.5), vec2(1.0 / 16.0, 10.0 / 36.0) + 0.25));
    float ditherShift = 0.25 * p;
    vec3 ditherShiftRGB = vec3(ditherShift, -ditherShift, ditherShift);
    ditherShiftRGB = mix(2.0 * ditherShiftRGB, -2.0 * ditherShiftRGB, gridPosition);
    return v + ditherShiftRGB + p * 0.5;
  }
}

void main(void) {
  vec2 fragCoord = (floor((gl_FragCoord.xy - 0.5) / float(PIXEL_SIZE)) + 0.5) * float(PIXEL_SIZE);
  vec2 uv = fragCoord.xy / resolution.xy;
  float minRes = min(resolution.x, resolution.y);
  vec2 fc = gl_FragCoord.xy / minRes;
  float wt = time * bgWaveSpeed;
  float xAngle = wt + fc.y * ySineCycles;
  float yAngle = wt + fc.x * xSineCycles;
  bool bxHalf, byHalf;
  vec2 distortOffset = vec2(sin(xAngle), sin(yAngle)) * vec2(xDistMag, yDistMag);
  vec3 tex = texture2D(prgm1Texture, uv + distortOffset).rgb;
  float p = precisionFromBits(float(BITS));
  #if ENCODE_IN_SRGB == 0
    tex = SRGBToRGB(tex);
  #endif
  #if DITHER
    tex = dither(tex, fragCoord, p, DITHER_TYPE);
  #endif
  tex = roundToPrecision(tex, p);
  #if ENCODE_IN_SRGB == 0
    tex = RGBToSRGB(tex);
  #endif
  gl_FragColor = vec4(tex, 1.0);
}