Last active
September 19, 2024 12:12
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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