Hopf Vibratiok in pink

GLSL shader by merrypranxter · created 2026-02-15 · 10s loop · 2 passes

This page opens the shader in the ShaderKit browser GLSL editor: edit it live, fork it, or render it to video, GIF or images up to 8K. Also available as a full-screen view and an embeddable player.

Shader source (GLSL)

Common

#define pi acos(-1.)
#define deg pi/180.  //1 degree
#define time iTime*2.*pi/10. //sin(time) loops 10 seconds
#define R iResolution.xy //shorthand
#define ar R.x/R.y //aspect ratio
#define M iMouse //shorthand
#define xm (M.xy/R) //normalized mouse
#define nm ((xm.xy-0.5)*vec2(ar,1.)+0.5) //aspect ratio correction
vec3 cs = vec3(1.,2.,3.);
mat2 r2d(float a) {
    return mat2(cos(a),sin(a),-sin(a),cos(a));
}

Buffer A (iChannel0)

// ============================================================
//  HOPF JEWEL v1.0
//  Hopf fibration — 4D topology projected to 3D
//  Interlocking circles, tube SDF, thin-film, GGX gloss
// ============================================================

const float PI  = 3.14159265359;
const float TAU = 6.28318530718;
const float speed      = 0.18;
const float tubeR      = 0.045;
const float brightness = 2.0;
const float fiberN     = 28.0;

// ---- ACES tonemap ----
vec3 ACESFilm(vec3 x) {
    return clamp((x*(2.51*x+0.03))/(x*(2.43*x+0.59)+0.14), 0.0, 1.0);
}

// ---- Palettes ----
vec3 pal1(float t) {
    vec3 a = vec3(0.5, 0.4, 0.6);
    vec3 b = vec3(0.5, 0.4, 0.5);
    vec3 c = vec3(1.0, 1.4, 0.9);
    vec3 d = vec3(0.0, 0.2, 0.6);
    return a + b * cos(TAU * (c * t + d));
}
vec3 pal2(float t) {
    vec3 a = vec3(0.4, 0.2, 0.5);
    vec3 b = vec3(0.5, 0.5, 0.4);
    vec3 c = vec3(2.0, 1.5, 0.8);
    vec3 d = vec3(0.1, 0.4, 0.9);
    return a + b * cos(TAU * (c * t + d));
}
vec3 pal3(float t) {
    vec3 a = vec3(0.6, 0.3, 0.4);
    vec3 b = vec3(0.4, 0.5, 0.4);
    vec3 c = vec3(0.5, 1.8, 2.0);
    vec3 d = vec3(0.8, 0.1, 0.4);
    return a + b * cos(TAU * (c * t + d));
}

// ---- Hopf fiber ----
// Given a point on S2 (unit sphere), returns the
// corresponding circle in 3D via Hopf projection
// p2: point on S2 parameterized by (theta, phi)
vec3 hopfFiber(float theta, float phi, float s) {
    // Point on S2
    float ct = cos(theta * 0.5);
    float st = sin(theta * 0.5);
    float cp = cos(phi);
    float sp = sin(phi);

    // Quaternion on S3
    float q0 = ct;
    float q1 = st * cp;
    float q2 = st * sp;
    float q3 = 0.0;

    // Rotate by fiber parameter s
    float cs = cos(s);
    float ss = sin(s);

    // Hopf map: S3 -> S2 composed with S3 rotation
    float r0 = cs * q0 - ss * q3;
    float r1 = cs * q1 - ss * q2;
    float r2 = cs * q2 + ss * q1;
    float r3 = cs * q3 + ss * q0;

    // Stereographic projection from S3 to R3
    float denom = max(1.0 - r0, 0.0001);
    return vec3(r1, r2, r3) / denom;
}

// ---- SDF: distance to a Hopf fiber circle ----
// We find closest point on the circle by sampling
float hopfFiberDist(float theta, float phi, vec3 p, out float bestS) {
    float minD = 1e10;
    bestS = 0.0;
    int steps = 32;
    for(int i = 0; i < 32; i++) {
        float s  = float(i) / float(steps) * TAU;
        vec3  fp = hopfFiber(theta, phi, s);
        float d  = length(p - fp);
        if(d < minD) {
            minD  = d;
            bestS = s;
        }
    }
    return minD - tubeR;
}

// ---- Scene: collection of Hopf fibers ----
vec3 mapScene(vec3 p, float t, out float matID, out float fiber_s, out float fiber_phi) {
    float minD  = 1e10;
    matID    = 0.0;
    fiber_s  = 0.0;
    fiber_phi = 0.0;

    float N = fiberN;

    for(int i = 0; i < 28; i++) {
        float fi = float(i);

        // Distribute fibers across S2
        float phi   = fi / N * TAU;
        float theta = PI * (0.25 + 0.5 * (fi / N));

        float bs  = 0.0;
        float d   = hopfFiberDist(theta + t * 0.07, phi + t * 0.04, p, bs);

        if(d < minD) {
            minD      = d;
            matID     = fi / N;
            fiber_s   = bs;
            fiber_phi = phi;
        }
    }

    return vec3(minD, 0.0, 0.0);
}

float mapDist(vec3 p, float t) {
    float m  = 0.0;
    float fs = 0.0;
    float fp = 0.0;
    return mapScene(p, t, m, fs, fp).x;
}

vec3 calcNormal(vec3 p, float t) {
    float e  = 0.0008;
    float d0 = mapDist(p, t);
    float nx = mapDist(p + vec3(e,0,0), t) - d0;
    float ny = mapDist(p + vec3(0,e,0), t) - d0;
    float nz = mapDist(p + vec3(0,0,e), t) - d0;
    return normalize(vec3(nx, ny, nz));
}

float calcAO(vec3 p, vec3 n, float t) {
    float ao = 0.0;
    float sc = 0.06;
    for(int i = 1; i <= 4; i++) {
        float fi = float(i);
        float dv = mapDist(p + n * sc * fi, t);
        ao += max(0.0, sc * fi - dv) / (sc * fi);
    }
    return clamp(1.0 - ao * 0.7, 0.0, 1.0);
}

// ---- GGX ----
float ggxD(float ndoth, float rough) {
    float a2    = rough * rough * rough * rough;
    float denom = ndoth * ndoth * (a2 - 1.0) + 1.0;
    return a2 / max(PI * denom * denom, 0.0001);
}

float schlick(float cosT, float f0) {
    float t1 = 1.0 - cosT;
    float t2 = t1 * t1;
    return f0 + (1.0 - f0) * t2 * t2 * t1;
}

// ---- Thin-film iridescence ----
vec3 iridFilm(float ndotv, float thick) {
    float ph = thick * ndotv;
    float r  = cos(ph * 0.85) * 0.5 + 0.5;
    float g  = cos(ph * 1.00 + 2.09) * 0.5 + 0.5;
    float b  = cos(ph * 1.20 + 4.19) * 0.5 + 0.5;
    return vec3(r, g, b);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2  uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
    float t  = iTime * speed;

    // Cinematic camera orbiting the structure
    float camA = t * 0.22;
    float camB = sin(t * 0.15) * 0.35;
    float camR = 5.5 + sin(t * 0.1) * 0.8;
    vec3  ro   = vec3(sin(camA) * camR, camB * 2.0, cos(camA) * camR);
    vec3  tgt  = vec3(0.0, 0.0, 0.0);
    vec3  fw   = normalize(tgt - ro);
    vec3  rt   = normalize(cross(fw, vec3(0,1,0)));
    vec3  up   = cross(rt, fw);
    vec3  rd   = normalize(fw + uv.x * rt * 1.1 + uv.y * up);

    // Raymarch
    float d    = 0.1;
    float glow = 0.0;
    bool  hit  = false;

    for(int i = 0; i < 120; i++) {
        vec3  p  = ro + rd * d;
        float ds = mapDist(p, t);

        float gw = 0.006 / (0.006 + abs(ds) * abs(ds) * 40.0);
        glow += gw;

        d  += ds * 0.55;
        if(ds < 0.0005) { hit = true; break; }
        if(d  > 20.0)   { break; }
    }

    // Black void
    vec3 col = vec3(0.005, 0.005, 0.012);

    if(hit) {
        vec3  p    = ro + rd * d;
        vec3  n    = calcNormal(p, t);
        float ao   = calcAO(p, n, t);

        float matID   = 0.0;
        float fiber_s = 0.0;
        float fiber_phi = 0.0;
        mapScene(p, t, matID, fiber_s, fiber_phi);

        // Lights
        vec3  ldir1  = normalize(vec3(3.0, 5.0, 2.0));
        vec3  ldir2  = normalize(vec3(-2.0, -1.0, -3.0));
        vec3  vdir   = -rd;

        float diff1  = max(dot(n, ldir1), 0.0);
        float diff2  = max(dot(n, ldir2), 0.0) * 0.25;
        float ndotv  = max(dot(n, vdir), 0.0);

        // GGX clear coat — polished metal/glass tube
        vec3  h1     = normalize(ldir1 + vdir);
        float ndoth1 = max(dot(n, h1), 0.0);
        float spec1  = ggxD(ndoth1, 0.08) * schlick(ndotv, 0.06);

        // GGX second lobe
        vec3  h2     = normalize(ldir2 + vdir);
        float ndoth2 = max(dot(n, h2), 0.0);
        float spec2  = ggxD(ndoth2, 0.3) * schlick(ndotv, 0.04) * 0.3;

        // Fresnel rim
        float rimW   = pow(1.0 - ndotv, 4.0);

        // Thin-film — varies along fiber
        float thick  = 8.0 + sin(fiber_s * 3.0 + fiber_phi * 2.0) * 5.0;
        vec3  filmC  = iridFilm(ndotv, thick);

        // Per-fiber color — each ring has its own palette position
        float phase1 = matID + t * 0.06;
        float phase2 = matID * 1.3 + fiber_s / TAU + t * 0.04;
        float phase3 = matID * 0.7 + t * 0.08;

        vec3 baseCol = pal1(phase1);
        vec3 rimCol  = pal2(phase3);
        vec3 specCol = pal3(phase2);

        // Compose
        col  = baseCol * (diff1 * 0.6 + diff2 + 0.08) * ao;
        col  = mix(col, col * filmC * 2.2, 0.45 * schlick(ndotv, 0.03));
        col += vec3(0.95, 0.98, 1.00) * spec1 * 6.0;
        col += specCol * spec2 * 3.0;
        col += rimCol  * rimW * 1.8 * ao;

        // Tube interior glow — each ring glows its own color
        float interior = exp(-d * 0.08) * (1.0 - ndotv);
        col += pal2(phase1 + 0.3) * interior * 0.4;
    }

    // Volumetric inter-ring glow
    float glowC = clamp(glow, 0.0, 1.0);
    col += pal1(t * 0.07)             * glowC * 1.5;
    col += pal2(glowC + t * 0.05)     * glowC * glowC * 3.0;
    col += pal3(t * 0.04 + 0.5)       * glowC * 0.8;

    // Depth fog
    float fogA = clamp(d / 20.0, 0.0, 1.0);
    col = mix(col, vec3(0.005, 0.005, 0.012), fogA * 0.5);

    // ACES + gamma
    col = ACESFilm(col * 1.4);
    col = pow(max(col, 0.0), vec3(0.4545));

    // Vignette
    col *= 1.0 - dot(uv * 0.9, uv * 0.9) * 0.5;

    fragColor = vec4(col, 1.0);
}

Image

Not used

More shaders by merrypranxter

Browse all public shaders · All shaders by merrypranxter · ShaderKit home

Vibe Mode BETA
💰 ~0 credits
Uniforms
FPS: 0
Time: 0
Resolution: 0 x 0

Account

FPS: -- Time: -- --×-- 1x