That’s an interesting idea. I found a good tutorial for a WebGL CMYK halftone shader but didn’t have much luck converting the entire thing. I’ll try going through step-by-step and see if I have any better luck.
Also, I lol’ed at the TMNT costumes in that last image.
EDIT: Ok, going through step-by-step, I was able to get something pretty good (looks best up close / in-motion)
Here’s the code (XML/GLSL at the moment; I’ll try to get a Cg version going soon):
<?xml version="1.0" encoding="UTF-8"?>
<shader language="GLSL" style="GLES2">
<vertex><![CDATA[
#version 120
uniform mat4 MVPMatrix;
attribute vec2 VertexCoord;
attribute vec2 TexCoord;
varying vec2 tex_coord;
void main()
{
gl_Position = MVPMatrix * vec4(VertexCoord, 0.0, 1.0);
tex_coord = TexCoord;
}
]]></vertex>
<fragment><![CDATA[
// GLSL halftone shader demo for WebGL
// Stefan Gustavson 2012-02-16 ([email protected])
//
// 2D simplex noise by Ian McEwan, distributed under
// the MIT license. All other code in this shader is
// my original work, and is in the public domain.
// Credit is appreciated where appropriate, though.
// Ported to RetroArch by hunterk
uniform sampler2D Texture;
varying vec2 tex_coord; // Texcoords
#define frequency 600.0
float aastep(float threshold, float value) {
float afwidth = 0.7 * length(vec2(dFdx(value), dFdy(value)));
return smoothstep(threshold-afwidth, threshold+afwidth, value);
}
// Description : Array- and textureless GLSL 2D simplex noise.
// Author : Ian McEwan, Ashima Arts. Version: 20110822
// Copyright (C) 2011 Ashima Arts. All rights reserved.
// Distributed under the MIT License. See LICENSE file.
// https://github.com/ashima/webgl-noise
vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec3 permute(vec3 x) { return mod289((( x * 34.0) + 1.0) * x); }
float snoise(vec2 v) {
const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0
0.366025403784439, // 0.5*(sqrt(3.0)-1.0)
-0.577350269189626, // -1.0 + 2.0 * C.x
0.024390243902439); // 1.0 / 41.0
// First corner
vec2 i = floor(v + dot(v, C.yy) );
vec2 x0 = v - i + dot(i, C.xx);
// Other corners
vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
// Permutations
i = mod289(i); // Avoid truncation effects in permutation
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;
// Gradients
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 a0 = x - floor(x + 0.5);
// Normalise gradients implicitly by scaling m
m *= 1.792843 - 0.853735 * ( a0*a0 + h*h );
// Compute final noise value at P
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);
}
void main() {
// Distance to nearest point in a grid of
// (frequency x frequency) points over the unit square
vec2 st2 = mat2(0.707, -0.707, 0.707, 0.707) * tex_coord;
vec2 nearest = 2.0*fract(frequency * st2) - 1.0;
float dist = length(nearest);
vec3 texcolor = texture2D(Texture, tex_coord).rgb;
float radius = sqrt(1.0-texcolor.g);//0.5;
float n = 0.1*snoise(tex_coord * 200.0); // Fractal noise
n += 0.05*snoise(tex_coord * 400.0);
n += 0.025*snoise(tex_coord * 800.0);
// start fast version
vec3 white = vec3(1.0, 1.0, 1.0);
vec3 black = vec3(0.0, 0.0, 0.0);
// end fast version
// start slow version
// vec3 white = vec3(n * 0.5 + 0.98);
// vec3 black = vec3(n + 0.1);
// end slow version
// Perform a rough RGB-to-CMYK conversion
vec4 cmyk;
cmyk.xyz = 1.0 - texcolor;
cmyk.w = min(cmyk.x, min(cmyk.y, cmyk.z)); // Create K
cmyk.xyz -= cmyk.w; // Subtract K equivalent from CMY
// Distance to nearest point in a grid of
// (frequency x frequency) points over the unit square
vec2 Kst = frequency * mat2(0.707, -0.707, 0.707, 0.707) * tex_coord;
vec2 Kuv = 2.0 * fract(Kst) - 1.0;
float k = aastep(0.0, sqrt(cmyk.w) - length(Kuv) + n);
vec2 Cst = frequency * mat2(0.966, -0.259, 0.259, 0.966) * tex_coord;
vec2 Cuv = 2.0 * fract(Cst) - 1.0;
float c = aastep(0.0, sqrt(cmyk.x) - length(Cuv) + n);
vec2 Mst = frequency * mat2(0.966, 0.259, -0.259, 0.966) * tex_coord;
vec2 Muv = 2.0 * fract(Mst) - 1.0;
float m = aastep(0.0, sqrt(cmyk.y) - length(Muv) + n);
vec2 Yst = frequency * tex_coord; // 0 deg
vec2 Yuv = 2.0 * fract(Yst) - 1.0;
float y = aastep(0.0, sqrt(cmyk.z) - length(Yuv) + n);
vec3 rgbscreen = 1.0 - 0.9*vec3(c,m,y) + n;
rgbscreen = mix(rgbscreen, black, 0.85*k + 0.3*n);
vec3 fragcolor = mix(black, white, aastep(radius, dist));
gl_FragColor = vec4(rgbscreen * rgbscreen, 1.0);
}
]]></fragment>
</shader>
To get the effect you’re describing, it might be good to add some blur in there or something, though, and maybe increase the color saturation.
Note: You can change the ‘frequency’ variable’s value to increase/decrease the dot density. 400 makes everything all Lichtenstein-y