WebGL Background: Flickering Blocks

This is the final installment of my WebGL series, revisiting the topic with a focus on creating animated techno-style flickering blocks. In this article, we continue to build upon the groundwork established in a previous article, making use of its well-established infrastructure. Our main objective in this piece will be to explore the use of shaders to bring this animated backdrop to life.

For this background, I've employed a blend of simple noise and a hexagonal pattern sourced from here. Below, you'll find my noise code, which incorporates a random positional function with an added scaling factor:

float rand(vec2 pos) {
    return fract(sin(dot(pos,
                         vec2(12.9898, 78.233))) *
                 43758.5453123);
}

float white_noise(vec2 pos, vec2 steps) {
    vec2 scaled_pos = floor(pos * steps);
    return rand(scaled_pos);
}

Next, we require a hexagonal pattern generator:

float hex(vec2 pos, vec2 steps) {
    vec2 p = (pos * steps);
    p.x *= 0.57735 * 2.0;
    p.y += mod(floor(p.x), 2.0) * 0.5;
    p = abs((mod(p, 1.0) - 0.5));
    return pow(abs(max(p.x * 1.5 + p.y, p.y * 2.0) - 1.0), 0.25);
}

I utilize the steps parameter to determine the number of bricks in both the horizontal and vertical dimensions. You can achieve variability in the number of bricks by using either one or both of these values.

Now that we've prepared all the necessary components, let's combine them in the main function. To begin, we'll generate random bricks and set them in motion with animation:

float lum = white_noise(uv, uResolution * 0.02 * vec2(5.0, 3.0));
lum = sin(lum * uTime * 3.0) * 0.5 + 0.5;
lum = 0.25 + 0.75 * lum;
  • uv represents the current position in the viewport, ranging from 0 to 1.
  • uResolution indicates the resolution of the viewport in pixels.
  • uResolution * 0.02 ensures a constant brick size by using a ratio, resulting in side_length / 50 bricks along each side, making the pattern size independent of the window size.
  • vec2(5.0, 3.0) defines the ratio of the bricks' sides.
  • lum = sin(lum * uTime * 3.0) * 0.5 + 0.5; is responsible for changing the value over time within the range of 0 to 1. You can increase the animation speed by changing the 3.0 to a higher value.
  • lum = 0.25 + 0.75 * lum; adjusts the range of final values to fall between 0.25 and 1.0.

The second step involves combining the generated random bricks with the hexagonal pattern and applying colorization:

lum *= hex(uv, uResolution * 0.15);
vec3 clr = vec3(0.2, 0.381, 0.541); 
gl_FragColor = vec4(lum * clr, 1.0);

Certainly, you can examine the entire implementation of the effect by inspecting the page code. Here's the complete fragment shader code for your reference:

#ifdef GL_ES
    precision highp float;
#endif

uniform vec2 uResolution;
uniform float uTime;

varying vec2 tPos;

float rand(vec2 pos) {
    return fract(sin(dot(pos,
                         vec2(12.9898, 78.233))) *
                 43758.5453123);
}

float white_noise(vec2 pos, vec2 steps) {
    vec2 scaled_pos = floor(pos * steps);
    return rand(scaled_pos);
}

float hex(vec2 pos, vec2 steps) {
    vec2 p = (pos * steps);
    p.x *= 0.57735 * 2.0;
    p.y += mod(floor(p.x), 2.0) * 0.5;
    p = abs((mod(p, 1.0) - 0.5));
    return pow(abs(max(p.x * 1.5 + p.y, p.y * 2.0) - 1.0), 0.25);
}

void main(void)
{
    vec2 uv = tPos;

    float lum = white_noise(uv, uResolution * 0.02 * vec2(5.0, 3.0));
    lum = sin(lum * uTime * 3.0) * 0.5 + 0.5;
    lum = 0.25 + 0.75 * lum;

    lum *= hex(uv, uResolution * 0.15);

    vec3 clr = vec3(0.2, 0.381, 0.541); 
    gl_FragColor = vec4(lum * clr, 1.0);
}

George Ostrobrod, 2019 (edited 2024)