Back to shaders
Shader test bench
CMYK Halftone.fs
runnable fragment
Complete GLSL fragment shader. Stronghold runs it directly when the browser can compile it.
Code
uniform sampler2D inputImage;
uniform float gridSize;
uniform float smoothing;
precision mediump float;
/*{
"CATEGORIES": [
"Halftone Effect",
"Retro"
],
"CREDIT": "by zoidberg",
"INPUTS": [
{
"NAME": "inputImage",
"TYPE": "image"
},
{
"DEFAULT": 45,
"MAX": 256,
"MIN": 1,
"NAME": "gridSize",
"TYPE": "float"
},
{
"DEFAULT": 0.15,
"MAX": 1,
"MIN": 0,
"NAME": "smoothing",
"TYPE": "float"
}
],
"ISFVSN": "2"
}
*/
const vec4 gridRot = vec4(15.0, 45.0, 0.0, 75.0);
vec4 RGBAtoCMYK(vec4 inColor)
{
vec4 ret;
ret.w = 1.0 - max(max(inColor.x, inColor.y), inColor.z);
ret.x = (1.0-inColor.x-ret.w)/(1.0-ret.w);
ret.y = (1.0-inColor.y-ret.w)/(1.0-ret.w);
ret.z = (1.0-inColor.z-ret.w)/(1.0-ret.w);
return ret;
}
vec4 CMYKtoRGBA(vec4 inColor)
{
vec4 ret;
ret.xyz = (1.0-inColor.xyz)*(1.0-inColor.w);
ret.w = 1.0;
return ret;
}
void main() {
// a halftone is an overlapping series of grids of dots
// each grid of dots is rotated by a different amount
// the size of the dots determines the colors. the shape of the dot should never change (always be a dot with regular edges)
vec4 cmykAmounts = vec4(0.0);
// for each of the channels (i) of CMYK...
for (int i=0; i<4; ++i) {
// figure out the rotation of the grid in radians
float rotRad = radians(gridRot[i]);
// the grids are rotated counter-clockwise- to find the nearest dot, take the fragment pixel loc,
// rotate it clockwise, and split by the grid to find the center of the dot. then rotate this
// coord counter-clockwise to yield the location of the center of the dot in pixel coords local to the render space
mat2 ccTrans = mat2(vec2(cos(rotRad), sin(rotRad)), vec2(-1.0*sin(rotRad), cos(rotRad)));
mat2 cTrans = mat2(vec2(cos(rotRad), -1.0*sin(rotRad)), vec2(sin(rotRad), cos(rotRad)));
// render loc -> grid loc -> grid dot loc -> grid dot loc in render coords -> pixel color under grid dot loc
vec2 gridFragLoc = cTrans * gl_FragCoord.xy;
vec2 gridDotLoc = vec2(floor(gridFragLoc.x/gridSize)*gridSize, floor(gridFragLoc.y/gridSize)*gridSize);
gridDotLoc = gridDotLoc + vec2(gridSize/2.0);
vec2 renderDotLoc = ccTrans * gridDotLoc;
vec4 renderDotImageColorRGB = texture2D(inputImage, (renderDotLoc) / u_resolution.xy);
vec4 renderDotImageColorCMYK = RGBAtoCMYK(renderDotImageColorRGB);
float channelAmount = renderDotImageColorCMYK[i];
float dotRadius = channelAmount * (gridSize/2.0);
float fragDistanceToGridCenter = distance(gl_FragCoord.xy, renderDotLoc);
// the amount of the channel depends on the distance to the center of the grid, the size of the dot, and smoothing
float smoothDist = smoothing * (gridSize/6.0);
cmykAmounts[i] += smoothstep(dotRadius, dotRadius-(dotRadius*smoothing), fragDistanceToGridCenter);
}
gl_FragColor = CMYKtoRGBA(cmykAmounts);
}