Liquid Fill

by MusicBar, progress bar

A glass tube fills with glossy liquid up to the playhead, its surface rippling with the music, bubbles rising and a splash running back on every beat.

60 downloads Download file, 7.0 KB
How it installs, its SHA-256 and its code

Click Apply: MusicBar downloads it, checks its SHA-256 and puts it on the taskbar, or just puts it on if it already has it. Needs MusicBar 1.8 or newer.

SHA-256 13d1738e1dcda750601a8ee797b842cdca5c8c9933be92256d73e5bf23d69256

//! name: Liquid Fill
//! author: MusicBar
//! kind: progress
//! description: A glass tube fills with glossy liquid up to the playhead, its surface rippling with the music, bubbles rising and a splash running back on every beat.

float hash1(float n)
{
    n = frac(n * 0.1031);
    n *= n + 33.33;
    n *= n + n;
    return frac(n);
}

void layer(inout float3 acc, inout float cover, float3 rgb, float a)
{
    a = saturate(a);
    acc = rgb * a + acc * (1.0 - a);
    cover = a + cover * (1.0 - a);
}

// Smooth maximum: rounds the corner where the surface meets the front of the liquid.
float smax(float a, float b, float k)
{
    float h = saturate(0.5 + 0.5 * (a - b) / k);
    return lerp(b, a, h) + k * h * (1.0 - h);
}

float4 main(float2 uv : TEXCOORD) : COLOR
{
    float2 px = float2(uv.x * size.x, (1.0 - uv.y) * size.y);  // pixels: along the bar, and up the screen so gravity holds on a top taskbar too
    float u = size.y;
    float head = progress * size.x;
    float mid = u * 0.5;
    float r = u * 0.3;                                        // inner radius of the tube
    float x0 = u * 0.4;
    float x1 = size.x - u * 0.4;

    // The tube: a long capsule, negative inside.
    float2 tq = float2(px.x - clamp(px.x, x0, x1), px.y - mid);
    float tube = length(tq) - r;
    float n = clamp((px.y - mid) / r, -1.0, 1.0);             // -1 at the bottom of the glass, 1 at the top

    // The music: bass near the front, treble further back.
    float behind = max(head - px.x, 0.0);
    float spec = spectrum(saturate(behind / (u * 16.0)));
    float bass = spectrum(0.03);

    // The surface: rolling waves that grow with the spectrum, a splash ripple running back from the
    // front on the beat, and a little bulge where the liquid piles up at the front.
    float amp = u * (0.02 + 0.08 * spec) * (0.6 + 0.7 * level);
    float surf = mid + r * 0.42 + amp * sin(px.x / u * 3.6 - time * 4.5) + u * 0.015 * sin(px.x / u * 9.0 + time * 3.1);
    surf += beat * u * 0.09 * sin(behind / u * 6.5 + time * 16.0) * exp(-behind / (u * 2.2));
    surf += u * (0.04 + 0.05 * bass) * exp(-behind / (u * 0.7));

    // The front leans back at the top, wobbles, and surges forward on the beat.
    float front = head + u * (0.1 - 0.2 * (n + 1.0) * 0.5 + 0.05 * sin(time * 5.0 + n * 2.5) * (0.5 + level) + 0.08 * beat);
    float liquid = max(smax(px.y - surf, px.x - front, u * 0.12), tube + 1.2);
    float inLiquid = smoothstep(0.6, -0.6, liquid);

    float3 acc = float3(0, 0, 0);
    float cover = 0;

    // The glass itself: clear in the middle, a little denser toward the walls, so the empty tube reads.
    float glass = smoothstep(0.6, -0.6, tube);
    layer(acc, cover, lerp(colorC.rgb, float3(1, 1, 1), 0.5), glass * (0.03 + 0.1 * pow(abs(n), 6.0)));

    // Light from the liquid spilling into the empty glass ahead of it.
    float spill = exp(-max(liquid, 0.0) / (u * 0.8)) * smoothstep(head - u * 0.4, head + u * 0.1, px.x) * glass;
    layer(acc, cover, colorA.rgb, spill * (0.12 + 0.18 * level + 0.15 * beat));

    // The liquid: colorA at the surface to colorB at the bottom, rounded like a cylinder, with a slow shimmer.
    float depth = saturate((surf - px.y) / (r * 1.4));
    float3 liq = lerp(colorA.rgb, colorB.rgb, depth);
    liq *= 0.6 + 0.4 * sqrt(saturate(1.0 - n * n * 0.9));
    liq += colorA.rgb * 0.12 * (0.5 + 0.5 * sin(px.x / u * 2.3 - time * 1.7 + sin(px.y / u * 7.0 + time)));
    layer(acc, cover, liq, inLiquid * 0.92);

    // Gloss: a soft band of light riding just under the surface, and light gathered along the bottom.
    float gloss = smoothstep(u * 0.06, 0.0, abs(px.y - (surf - u * 0.11))) * smoothstep(0.2, 0.6, sin(px.x / u * 1.25 + 1.0) * 0.5 + 0.5);
    layer(acc, cover, lerp(colorA.rgb, float3(1, 1, 1), 0.75), gloss * inLiquid * 0.35);
    float caustic = smoothstep(u * 0.05, 0.0, abs(px.y - (mid - r * 0.8))) * (0.6 + 0.4 * sin(px.x / u * 5.0 - time * 3.0));
    layer(acc, cover, lerp(colorB.rgb, colorA.rgb, 0.6), caustic * inLiquid * 0.45);

    // Bubbles rising through the liquid: one per narrow column, more when the song is loud.
    float cw = u * 0.24;
    float col = floor(px.x / cw);
    float hb = hash1(col * 3.17 + 1.3);
    float rb = u * (0.025 + 0.03 * hash1(col * 7.1 + 2.0));
    float by = mid - r + rb + frac(time * (0.25 + 0.35 * hb) + hb * 9.0) * (2.0 * r);
    float bx = (col + 0.5) * cw + sin(time * 3.0 + hb * 30.0) * cw * 0.18;
    float2 bd = px - float2(bx, by);
    float bl = length(bd);
    float on = step(hb, 0.3 + 0.45 * level + 0.3 * beat) * inLiquid * smoothstep(0.0, u * 0.05, surf - by);
    float ring = smoothstep(1.1, 0.2, abs(bl - rb)) * 0.7 + smoothstep(rb, rb * 0.3, bl) * 0.12;
    float shineB = smoothstep(rb * 0.45, rb * 0.1, length(bd - float2(-0.35, 0.35) * rb));
    layer(acc, cover, lerp(colorA.rgb, float3(1, 1, 1), 0.7), on * ring);
    layer(acc, cover, float3(1, 1, 1), on * shineB);

    // On the beat a few drops spray forward off the front and fall into the empty glass.
    float t = 1.0 - beat;
    float drops = 0;
    [unroll] for (int i = 0; i < 3; i++)
    {
        float2 dp = float2(head + u * (0.05 + (0.22 + 0.26 * i) * t), mid + r * 0.4 + u * (0.5 - 0.18 * i) * t - u * 0.9 * t * t);
        float dr = u * (0.045 - 0.01 * i);
        drops += smoothstep(dr + 0.5, dr - 0.5, length(px - dp));
    }
    layer(acc, cover, lerp(colorA.rgb, float3(1, 1, 1), 0.3), drops * step(0.04, beat) * smoothstep(0.5, -0.5, tube + 1.0));

    // The meniscus: a bright line just under the surface and along the front.
    float edge = exp(-abs(liquid + 1.5) / 1.1) * inLiquid;
    layer(acc, cover, lerp(colorA.rgb, float3(1, 1, 1), 0.65), edge * (0.65 + 0.35 * beat));

    // Glass graduations printed along the top, every bar height and a longer one every five.
    float tick = px.x / u;
    float tickLen = lerp(0.14, 0.3, step(frac(floor(tick + 0.5) / 5.0 + 0.01), 0.02)) * r;
    float tickOn = smoothstep(1.0, 0.3, abs(frac(tick + 0.5) - 0.5) * u) * step(mid + r - tickLen, px.y) * glass;
    layer(acc, cover, float3(1, 1, 1), tickOn * 0.28);

    // The glass highlight: a long streak along the top, broken into slices, and a faint one below.
    float slice = frac(px.x / (u * 7.0));
    float streak = smoothstep(u * 0.05, 0.0, abs(px.y - (mid + r * 0.62))) * smoothstep(0.84, 0.8, slice) * smoothstep(0.0, 0.03, slice);
    streak *= smoothstep(0.0, u * 0.3, px.x - x0 + r) * smoothstep(0.0, u * 0.3, x1 + r - px.x);
    layer(acc, cover, float3(1, 1, 1), streak * (0.3 + 0.25 * inLiquid));
    float lowStreak = smoothstep(u * 0.035, 0.0, abs(px.y - (mid - r * 0.7))) * glass;
    layer(acc, cover, lerp(colorC.rgb, float3(1, 1, 1), 0.6), lowStreak * 0.12);

    // The glass wall: a crisp outline, lighter on top where it catches the light, with a dark
    // hairline outside it so the tube still shows on a light taskbar.
    layer(acc, cover, float3(0.08, 0.1, 0.14), smoothstep(1.2, 0.2, abs(tube - 1.8)) * 0.35);
    float wall = smoothstep(1.3, 0.3, abs(tube - 0.6));
    layer(acc, cover, lerp(colorC.rgb, float3(1, 1, 1), 0.55), wall * (0.38 + 0.3 * saturate(n)));

    return paint(acc / max(cover, 0.001), cover);
}