Skip to content

Instantly share code, notes, and snippets.

@eira-fransham
Last active September 19, 2024 12:12
Show Gist options
  • Save eira-fransham/19ad4194ae8be303182e7fccbe7350fe to your computer and use it in GitHub Desktop.
Save eira-fransham/19ad4194ae8be303182e7fccbe7350fe to your computer and use it in GitHub Desktop.
Best shader for generating CMYK halftone dots for full colour images on the internet, to my knowledge (ShaderToy- and TouchDesigner-compatible)
#define SHADERTOY
#ifdef SHADERTOY
const float Frequency = 60.0;
const float Size = 0.7;
const float Saturation = 5.;
const float Exponent = 0.3;
const float ReduceColor = 0.3;
const float Lightness = 0.306;
const float LumMul = 2.;
const float KDesat = 1.;
const float Noise = 0.3;
#else // TouchDesigner
#define iChannel0 sTD2DInputs[0]
uniform vec3 iResolution;
uniform vec2 iChannelResolution[1];
uniform vec4 iAmts;
out vec4 fragColor;
layout(constant_id = 0) const float Frequency = 300.0;
layout(constant_id = 1) const float Size = 0.7;
layout(constant_id = 2) const float Saturation = 5.0;
layout(constant_id = 3) const float Exponent = 0.3;
layout(constant_id = 4) const float ReduceColor = 0.3;
layout(constant_id = 5) const float Lightness = 0.3;
layout(constant_id = 6) const float LumMul = 2.0;
layout(constant_id = 7) const float KDesat = 1.0;
layout(constant_id = 8) const float Noise = 0.3;
layout(constant_id = 9) const float Offset = 0.3;
#endif // SHADERTOY
float aastep(float threshold, float value) {
float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;
return smoothstep(threshold-afwidth, threshold+afwidth, value);
}
vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }
float snoise(vec2 v){
const vec4 C = vec4(0.211324865405187, 0.366025403784439,
-0.577350269189626, 0.024390243902439);
vec2 i = floor(v + dot(v, C.yy) );
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1;
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod(i, 289.0);
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
+ i.x + vec3(0.0, i1.x, 1.0 ));
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy),
dot(x12.zw,x12.zw)), 0.0);
m = m*m ;
m = m*m ;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
const mat2 CROT = mat2(0.966, -0.259, 0.259, 0.966);
const mat2 MROT = mat2(0.966, 0.259, -0.259, 0.966);
const mat2 YROT = mat2(1, 0, 0, 1);
const mat2 KROT = mat2(0.707, -0.707, 0.707, 0.707);
const mat2 CINVROT = mat2(0.966, 0.259, -0.259, 0.966);
const mat2 MINVROT = mat2(0.966, -0.259, 0.259, 0.966);
const mat2 YINVROT = mat2(1, 0, 0, 1);
const mat2 KINVROT = mat2(0.707, 0.707, -0.707, 0.707);
float getComponent(float aaThresh, float n, float val, float frequency, mat2 rot, vec2 st, vec2 center) {
return aastep(aaThresh, Size*pow(val * 2.0, Exponent)-(length(st - center) * frequency)+n);
}
vec4 halftone(vec4 cmyk, vec2 st, vec2 center[4], float frequency) {
float n = 0.1*snoise(st*200.0); // Fractal noise
n += 0.05*snoise(st*400.0);
n += 0.025*snoise(st*800.0);
float c = getComponent(ReduceColor, n * Noise, cmyk.r, frequency, CROT, st, center[0]);
float m = getComponent(ReduceColor, n * Noise, cmyk.g, frequency, MROT, st, center[1]);
float y = getComponent(ReduceColor, n * Noise, cmyk.b, frequency, YROT, st, center[2]);
float k = getComponent(Lightness, n * Noise, cmyk.a, frequency, KROT, st, center[3]);
return vec4(c, m, y, k);
}
vec2 textureCoords(float freq, mat2 invrot, mat2 rot, vec2 uv) {
return (invrot*floor(rot*(freq*uv)))/freq;
}
void textureCoords(float freq, mat2 invrot, mat2 rot, vec2 uv, vec2 aspect, out vec2 coords[9]) {
vec2 base = floor(rot*(freq*uv*aspect));
vec2 offsets[9];
offsets[0] = vec2(-1, -1);
offsets[1] = vec2(-1, 0);
offsets[2] = vec2(-1, 1);
offsets[3] = vec2(0, -1);
offsets[4] = vec2(0, 0);
offsets[5] = vec2(0, 1);
offsets[6] = vec2(1, -1);
offsets[7] = vec2(1, 0);
offsets[8] = vec2(1, 1);
for (int i = 0; i < 9; i++) {
coords[i] = invrot * (base + offsets[i]) / freq / aspect;
}
}
const mat3 ToXYZMatrix = mat3(
0.4124564, 0.3575761, 0.1804375,
0.2126729, 0.7151522, 0.0721750,
0.0193339, 0.1191920, 0.9503041
);
const float WHITE_START = 0.9;
float luminance(vec3 color) {
return dot(color, ToXYZMatrix[1]);
}
vec3 crosstalk(vec3 tonemapped) {
float tonemappedMax = max(tonemapped.r, max(tonemapped.g, tonemapped.b));
vec3 ratio = tonemapped / tonemappedMax;
tonemappedMax = min(tonemappedMax, 1.0);
ratio = pow(ratio, vec3(1.1 / 2.0));
ratio = mix(ratio, vec3(1.0), pow(tonemappedMax, 2.0));
ratio = pow(ratio, vec3(2.0));
return ratio * tonemappedMax;
}
vec4 rgbToCmyk(vec3 rgb) {
float lum = luminance(rgb);
float maxrgb = max(rgb.r, max(rgb.g, rgb.b));
float minrgb = min(rgb.r, max(rgb.g, rgb.b));
vec3 cmy = (maxrgb - rgb) / maxrgb;
float sat = (maxrgb - minrgb) / (1. - abs(2. * lum - 1.));
sat = clamp(sat * Saturation, 0., 1.);
sat = lum >= 1.0 ? 0. : sat;
float k = 1.0 - clamp(lum * LumMul, 0., 1.);
if (KDesat < 1.0) {
cmy = cmy * (1. - clamp(k - KDesat, 0., 1. - KDesat) / (1. - KDesat));
}
return vec4(cmy * sat, k);
}
float blendScreen(float base, float blend) {
return 1.0-((1.0-base)*(1.0-blend));
}
vec3 blendScreen(vec3 base, vec3 blend) {
return vec3(blendScreen(base.r,blend.r),blendScreen(base.g,blend.g),blendScreen(base.b,blend.b));
}
#ifdef SHADERTOY
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 vUV = fragCoord / iResolution.xy;
#else // TouchDesigner
void main() {
#endif // SHADERTOY
vec2 aspect = vec2(iChannelResolution[0].x / iChannelResolution[0].y, 1.0);
vec3 rgb = vec3(0.);
float a = 0.;
vec2 rcoords[9];
vec2 gcoords[9];
vec2 bcoords[9];
vec2 kcoords[9];
textureCoords(Frequency, CINVROT, CROT, vUV.st, aspect, rcoords);
textureCoords(Frequency, MINVROT, MROT, vUV.st, aspect, gcoords);
textureCoords(Frequency, YINVROT, YROT, vUV.st, aspect, bcoords);
textureCoords(Frequency, KINVROT, KROT, vUV.st, aspect, kcoords);
for (int i = 0; i < 9; i++) {
vec2 centers[4];
centers[0] = rcoords[i] * aspect;
centers[1] = gcoords[i] * aspect;
centers[2] = bcoords[i] * aspect;
centers[3] = kcoords[i] * aspect;
vec4 rcolor = clamp(texture(iChannel0, rcoords[i]), 0., 1.);
vec4 gcolor = clamp(texture(iChannel0, gcoords[i]), 0., 1.);
vec4 bcolor = clamp(texture(iChannel0, bcoords[i]), 0., 1.);
vec4 kcolor = clamp(texture(iChannel0, kcoords[i]), 0., 1.);
float ci = clamp(rgbToCmyk(rcolor.rgb).r, 0., 1.);
float mi = clamp(rgbToCmyk(gcolor.rgb).g, 0., 1.);
float yi = clamp(rgbToCmyk(bcolor.rgb).b, 0., 1.);
float ki = clamp(rgbToCmyk(kcolor.rgb).a, 0., 1.);
vec4 halftoned = halftone(vec4(ci, mi, yi, ki), vUV.st * aspect, centers, Frequency);
rgb = rgb + halftoned.rgb;
a = a + halftoned.a;
}
rgb = clamp(rgb, 0., 1.);
a = clamp(a, 0., 1.);
fragColor = vec4((1.0 - rgb) * (1.0 - a), 1.0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment