Comic shader

Inspired by the default video filters which come with the Photo Booth app on OSX, I decided to try my hand at writing a GLSL comic print shader.

Written in RenderMonkey, this shader has three levels of detail:

  1. Overlapping CYM circles, for the close up detail.
  2. These are faded into square “bricks”, to reduce the need for circle checks.
  3. Which are faded into standard pixels

It isn’t very fast due to the excessive amount of loops and conditionals, which AFAIK can’t be bypassed without integer arithmetic, but there are lots of early outs. I avoided the need for reference textures and other external dependencies, modern cards shouldn’t have a problem with running it.

Here’s a video of it in action:

You can download the RFX file here, but I’ll also provide the fragment shader code below:

// comic shader attempt :D
 
uniform sampler2D image;
uniform float gridsize;
uniform float blackness;
varying vec2  TexCoord0;
 
#define isEven(x) (floor(x / 2.0) == float(x) / 2.0)
 
void main (void)
{
    const vec2 cyan    = vec2(0.0, 0.3 / gridsize),
               yellow  = vec2(-0.3 / gridsize, -0.3 / gridsize),
               magenta = vec2(0.3 / gridsize, -0.3 / gridsize);
 
    float gridlen = 1.0 / (length(vec2(gridsize)));
 
    vec2 gridpos;
    gridpos.y = floor(TexCoord0.y * gridsize);
    gridpos.x = floor(TexCoord0.x * gridsize);
    vec2 bgPos = gridpos / vec2(gridsize);
 
    bgPos.y += (0.5 / gridsize);
 
    bool evenRow = isEven(gridpos.y);
 
    float halfWidth = (0.5 / gridsize);
 
    if (evenRow)
    {
       gridpos.x = floor((TexCoord0.x + halfWidth ) * gridsize);
       bgPos.x = gridpos.x / gridsize;
    }
    else
    {
       bgPos.x = gridpos.x / gridsize + halfWidth;
    }
 
    vec4 backCol = texture2D(image, bgPos, -100.0).rgba;
    vec4 realbackCol = texture2D(image, bgPos).rgba;
    vec4 posCol  = texture2D(image, TexCoord0).rgba;
    float lum = (posCol.r + posCol.g + posCol.b) / 3.0;
    float variance = (abs(posCol.r -lum) + abs(posCol.g -lum) + abs(posCol.b - lum)) / 3.0;
 
    if ( lum < blackness && variance < blackness)
    {
       gl_FragColor = vec4(0,0,0,1);
    }
    else
    {
       vec2 fw = fwidth(TexCoord0);
 
       float f =  (1.0 / gridsize) / fw.x;
 
       if (f < 2.0)        {           gl_FragColor = mix(realbackCol, backCol, f / 2.0);           // before:  gl_FragColor = backCol; //         }        else        {           vec4 newCol = vec4(1.0, 1.0, 1.0, backCol.a);                      // first do the centre piece                      // get cyan (inverse red)           newCol.r = (distance(TexCoord0, bgPos + cyan) > gridlen * (1.0-backCol.r) ) ? 1.0 : 0.0;
          // magenta (inverse green)
          newCol.g = (distance(TexCoord0, bgPos + magenta) > gridlen * (1.0-backCol.g) ) ? 1.0 : 0.0;
          // yellow (inverse blue)
          newCol.b = (distance(TexCoord0, bgPos + yellow) > gridlen * (1.0-backCol.b) ) ? 1.0 : 0.0;
 
          vec4 sampleCol;
          vec2 newPos;
          bool cont = (newCol.r + newCol.g + newCol.b != 0);
 
          for (int y = -2; y < 4 && cont; y += 2)
          {
             for (int x = -2; x < 2 && cont; x += 2)              {                 if (x == 0 && y == 0)                    x = 2;                 else if (y != 0 && x == -2)                 {                    x -= y / 2;                 }                                  newPos = bgPos + vec2(halfWidth*float(x), float(y) * halfWidth );                 sampleCol = texture2D(image, newPos, -100.0).rgba;                               newCol.r = (distance(TexCoord0, newPos + cyan) > gridlen * (1.0-sampleCol.r) ) ? newCol.r : 0.0;
                newCol.g = (distance(TexCoord0, newPos + magenta) > gridlen * (1.0-sampleCol.g) ) ? newCol.g : 0.0;
                newCol.b = (distance(TexCoord0, newPos + yellow) > gridlen * (1.0-sampleCol.b) ) ? newCol.b : 0.0;
 
                // take no more samples if already black
                cont = (newCol.r + newCol.g + newCol.b > 0);
 
             }
 
          }
 
          if ( f > 15.0)
          {
             gl_FragColor = newCol;
          }
          else
          {
             // blend
             gl_FragColor = mix(backCol, newCol, (f-2.0)/13.0);
          }
       }
 
    }
 
    return;
}