]

Thorne Brandt

Mobile Menu

Book of Shaders : Pollock

GLSL Study 4

May 22nd, 2022

In the Noise chapter of the The Book of Shaders, there is an interesting challenge to recreate a Pollock, using shader code. The following image is presented as an example.

Jackson Pollock - Number 14 gray

The simplest part of this algorithm would be the drips. We know how to make a cicle, and we know how to get a random number based on an input, so all we need to do is create circles in a loop and use the loop index as the random input.


float circle(vec2 _st, float radius, vec2 offset){
    vec2 circle_st = fract(_st) - 0.5;
    circle_st -= offset;
    float circle = step(length(circle_st), 0.5);
    circle = 1.0 - smoothstep(radius-0.005, radius+0.0005, length(circle_st));
    return circle;
}
float splats;
for(int i = 0; i < 200; i++){
    float random_x = (fract(random(float(i) + 0.1)) - 0.5) * 0.99;
    float random_y = (fract(random(float(i)) - 1.0) - 0.5) * 1.02;
    float random_radius = fract(max(0.001, random(float(i) + 0.2) * 0.006));
    splats += circle(st, random_radius, vec2(random_x, random_y)); 
}

But how do we get thin organic lines of the paint drips of the pollock? I noticed in one of the noise examples for *flow*, the st itself could be modulated by some noise to create some nice splotches.


float splatters(vec2 _st){
    vec2 splatter_st = _st * 6.0;
    color = smoothstep(.16, .2, noise(splatter_st);
}

I realized that if I offset this shape and then subtract it from itself, it would produce some nice painterly lines.


    float splatters(vec2 _st){
        vec2 splatter_st = _st * 6.0;
        color = smoothstep(.16, .2, noise(splatter_st);
        color -= smoothstep(.17, .2, noise(splatter_st+0.0001);
    }

If I add this to the loop like the circle drips, it starts looking like some grassy wallpaper, which is interesting in itself, but not the painterly style that is our goal.

The pattern that is emerging here tells me that I need to add some variation to the rotation. We can accomplish this using the for loop as well.


mat2 rotate2d(float angle){
    return mat2(cos(angle),-sin(angle),
                sin(angle),cos(angle));
}

for(int i = 0; i < 200; i++){
    float random_x = (fract(random(float(i) + 0.1)) - 0.5) * 0.99;
    float random_y = (fract(random(float(i)) - 1.0) - 0.5) * 1.02;
    float random_radius = fract(max(0.001, random(float(i) + 0.2) * 0.006));
    splats += circle(st, random_radius, vec2(random_x, random_y));
    rotated_st = rotate2D(rotated_st, 3.14*94); //added arbitrary angles here until it looked good.
    splats += splatters(rotated_st);
}

Now we're getting close! We just need some some more variation. We can plug the index into the splatters function and add a few different subtractions. Let's also start adding some time. Here's the final result.


vec2 random2(vec2 st){
    st = vec2( dot(st,vec2(127.1,311.7)),
            dot(st,vec2(269.5,183.3)) );
    return -1.0 + 2.0*fract(sin(st)*43758.5453123);
}

float noise(vec2 st) {
    vec2 i = floor(st);
    vec2 f = fract(st);

    vec2 u = f*f*(3.0-2.0*f);

    return mix( mix( dot( random2(i + vec2(0.0,0.0) ), f - vec2(0.0,0.0) ),
                    dot( random2(i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ), u.x),
                mix( dot( random2(i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ),
                    dot( random2(i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ), u.x), u.y);
}

vec2 rotate2D (vec2 _st, float _angle) {
    _st -= 0.5;
    _st =  mat2(cos(_angle),-sin(_angle),
                sin(_angle),cos(_angle)) * _st;
    _st += 0.5;
    return _st;
}


float circle(vec2 _st, float radius, vec2 offset){
    vec2 circle_st = fract(_st) - 0.5;
    circle_st -= offset;
    float circle = step(length(circle_st), 0.5);
    circle = 1.0 - smoothstep(radius-0.005, radius+0.0005, length(circle_st));
    return circle;
}

float splatters(vec2 _st, int index){
    vec3 color;
    float _index = float(index);
    if(_index == 0.){
        vec2 splatter_st = _st * 10.5;
        color = vec3(1.) * smoothstep(.1, .2, noise(splatter_st + float(index)));
        color -= smoothstep(.1, .2, noise(splatter_st+0.25+float(index)));
    } else {
        vec2 splatter_st = _st * 6. + mod(_index, 12.);
        if(mod(_index, 10.0) < 5.0){
            splatter_st = min(_st * 2.0, _st * mod(_index, 12.0));
            color = vec3(1.) * smoothstep(.19, .2, noise(splatter_st+ float(index)));
            color -= smoothstep(.18, .2, noise(splatter_st+0.025+float(index)));
        } else {
            splatter_st = max(_st * 12., splatter_st);
            color = vec3(1.) * smoothstep(.16, .2, noise(splatter_st+ float(index)));
            color -= smoothstep(.17, .2, noise(splatter_st+0.0001*_index+float(index)));
        }
    }
    color = smoothstep(0.4, 1., color);
    return max(0.0, color.r);
}

mat2 rotate2d(float angle){
    return mat2(cos(angle),-sin(angle),
                sin(angle),cos(angle));
}

void mainImage(out vec4 fragColor, in vec2 fragCoord){
    vec2 st = fragCoord.xy/iResolution.xy;
    st += vec2(iTime*0.03, 0.0);
    float splats;
    vec2 rotated_st = st;

    for(int i = 0; i < 200; i++){
        float slow_time = iTime * 1. * random((float(i)+1.)*(float(i)+1.0));
        float sin_time = sin(slow_time);
        float random_x = (fract(random(float(i) + 0.1)) - 0.5) * 0.99;
        float random_y = (fract(random(float(i)) - 1.0) - 0.5) * 1.02;
        float random_radius = fract(max(0.001, (random(float(i) + 0.2) + sin_time) * 0.006));
        splats += circle(st, random_radius, vec2(random_x, random_y)); 
            splats += splatters(rotated_st, i);
            rotated_st = rotate2D(rotated_st, 3.14*95.);
    }
    vec3 color = (1.0 - splats) * vec3(1.0, 0.97, 0.9);
    fragColor = vec4(vec3(color),1.0);
}