Anthony Del Ciotto's Website

Digital Art: Plasma Beat

2019-06-11

Plasma Beat is a retro digital art piece implemented entirely in a GLSL fragment shader. You can run the shader live on this page by hovering over the preview below and clicking the play button.

The shader was written in Visual Studio Code using the glsl-canvas extension. Original source code is shown below:

precision mediump float;

uniform vec2 u_resolution;
uniform float u_time;

#define RETRO_MODE 1

const float retroPixelSize = 10.0;
const float pi = 3.1415926535897932384626433832795;
const float twoPi = pi * 2.0;
const float pulsateDuration = 1.5;

//  function from IƱigo Quiles (no cubic smoothing)
//  https://www.shadertoy.com/view/MsS3Wc
vec3 hsv2rgb(in vec3 c) {
    vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
    return c.z * mix(vec3(1.0), rgb, c.y);
}

vec2 rotate(vec2 p, float angle) {
    return vec2(
        cos(angle) * p.x + sin(angle) * p.y,
        - sin(angle) * p.x + cos(angle) * p.y
    );
}

float heart(vec2 p, vec2 center, float size, float angle) {
    vec2 o = (p - center) / (1.6 * size);
    vec2 ro = rotate(o, angle);
    float a = ro.x * ro.x + ro.y * ro.y - 0.3;
    return step(a * a*a * 2.0, ro.x * ro.x * ro.y * ro.y * ro.y);
}

// modified plasma effect from https://www.bidouille.org/prog/plasma
vec3 plasma(vec2 p, float scale) {
    float angle = u_time * 0.3;
    vec2 rp = rotate(p, angle);
    rp *= scale;

    float v1 = sin(rp.x + u_time);
    float v2 = sin(rp.y + u_time);
    float v3 = sin(rp.x + rp.y + u_time);
    float v4 = sin(length(rp) + 1.7 * u_time);
    float v = v1 + v2 + v3 + v4;

    v *= 2.0;
    vec3 col = vec3(1.0, 0.3 - sin(v + pi * 0.5) * 0.2, 0.8 - sin(v + pi * 0.5) * 0.2);
    return col * 0.5 + 0.5;
}

void main() {
    vec2 fragCoord = gl_FragCoord.xy;
    #if RETRO_MODE
    fragCoord = ceil(fragCoord / retroPixelSize) * retroPixelSize;
    #endif
    vec2 currentCoord = 2.0 * vec2(fragCoord - 0.5 * u_resolution.xy) / u_resolution.y;

    // pulsate animation
    float pulsateTime = mod(u_time, pulsateDuration) / pulsateDuration;
    float pulsateScalar = pow(pulsateTime, 0.2) * 0.5 + 0.5;
    pulsateScalar = 1.0 + pulsateScalar * 0.5 * sin(pulsateTime * twoPi * 3.0 + currentCoord.y * 0.5) * exp(-pulsateTime * 4.0);

    // plasma background
    vec3 finalColor = plasma(currentCoord, pulsateScalar * 8.0);

    // center heart
    float radius = pulsateScalar * 0.25;
    float d = heart(currentCoord, vec2(0, - 0.03), radius, 0.0);
    vec3 col = mix(vec3(1.0), vec3(0.95, 0.37, 0.47), pulsateScalar);
    finalColor = mix(finalColor, col, d);

    // rotating heart ring
    const float piOver6 = pi / 6.0;
    pulsateScalar = 0.4 + pulsateScalar * 0.3 * sin(pulsateTime * twoPi * 3.0 + currentCoord.x * 0.6) * exp(-pulsateTime * 3.33);
    float ringRadius = 0.1 + pulsateScalar;
    for(float angle = piOver6; angle < twoPi; angle += piOver6) {
        float currentAngle = u_time * 0.8 + angle;
        vec2 center = vec2(ringRadius * cos(currentAngle), ringRadius * sin(currentAngle));
        float d = heart(currentCoord, center, 0.08, currentAngle);
        vec3 color = hsv2rgb(vec3((currentAngle / twoPi) + 0.5, ringRadius, 1.0));
        finalColor = mix(finalColor, color, d);
    }

    gl_FragColor = vec4(finalColor, 1.0);
}

The pixelated effect can be disabled by setting RETRO_MODE = 0.

Plasma Beat with RETRO_MODE off

Plasma Beat with RETRO_MODE off

I may update this article soon with a more in-depth explanation of the shader source-code.