Spiral Nebula Nocturne

GLSL shader by scry · created 2026-10-13 · 10s loop · 2 passes

Tags: 2d, Animation, Noise, Abstract, Layers, Symmetry

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)

float hash21(vec2 p){
    p = fract(p*vec2(123.34,456.21));
    p += dot(p,p+45.32);
    return fract(p.x*p.y);
}

vec2 hash22(vec2 p){
    float n = sin(dot(p, vec2(41.0,289.0)));
    vec2 v = vec2(n*43758.5453, n*28001.8384);
    return fract(v);
}

float noise(vec2 p){
    vec2 i = floor(p);
    vec2 f = fract(p);
    float a = hash21(i);
    float b = hash21(i+vec2(1.0,0.0));
    float c = hash21(i+vec2(0.0,1.0));
    float d = hash21(i+vec2(1.0,1.0));
    vec2 u = f*f*(3.0-2.0*f);
    return mix(a,b,u.x) + (c-a)*u.y*(1.0-u.x) + (d-b)*u.x*u.y;
}

float fbm(vec2 p){
    float v = 0.0;
    float amp = 0.5;
    for(int i=0;i<3;i++){
        v += amp*noise(p);
        p *= 2.02;
        amp *= 0.5;
    }
    return v;
}

vec3 starColor(float h){
    if(h<0.15) return vec3(1.0,0.95,0.85);
    else if(h<0.4) return vec3(1.0,0.55,0.2);
    else if(h<0.55) return vec3(1.0,0.3,0.1);
    else if(h<0.8) return vec3(0.4,0.7,1.0);
    else if(h<0.9) return vec3(0.3,0.9,0.8);
    return vec3(0.9,0.95,1.0);
}

float starShape(vec2 p, float size){
    float d = length(p);
    float core = size*0.03/(d+0.004);
    core = core*core*0.15;
    float spikeX = size*0.006/(abs(p.x)+0.002);
    float spikeY = size*0.006/(abs(p.y)+0.002);
    return core + (spikeX+spikeY)*0.4*size;
}

// animated, organic twinkle: layered sine flicker + rare bright flares
float twinkle(float h, float speed){
    float base = 0.6 + 0.22*sin(iTime*speed + h*123.0) + 0.18*sin(iTime*speed*2.63 + h*57.0 + 1.3);
    float flare = pow(max(sin(iTime*speed*0.37 + h*31.4),0.0), 10.0)*1.8;
    return base + flare;
}

vec3 starLayer(vec2 p, float density, float seed, float twinkleSpeed, vec2 drift, float rotSpeed){
    vec3 col = vec3(0.0);
    p = r2d(iTime*rotSpeed)*p + drift;
    vec2 guv = p*density;
    vec2 gi = floor(guv);
    for(int y=-1;y<=1;y++){
        for(int x=-1;x<=1;x++){
            vec2 cell = gi+vec2(float(x),float(y));
            vec2 rnd = hash22(cell+seed);
            vec2 starPos = cell+rnd;
            vec2 cp = starPos-guv;
            float h = hash21(cell+seed+1.7);
            float sizeSel = pow(h,9.0);
            float size = 0.3+2.2*sizeSel;
            float tw = twinkle(h, twinkleSpeed);
            vec3 c = starColor(hash21(cell+seed+3.3));
            float br = starShape(cp, size)*tw;
            col += c*br;
        }
    }
    return col;
}

vec3 nebulaCore(vec2 p){
    float r = length(p);
    float a = atan(p.y,p.x);
    float n = fbm(p*3.5 + vec2(time*0.12,-time*0.09));
    float warp = n*0.9 - 0.45;
    float spiral = a*2.0 + r*6.0 - time*0.5 + warp;
    float arms = pow(max(sin(spiral*3.0),0.0), 3.0);
    float arms2 = pow(max(sin(spiral*5.0+1.3),0.0),6.0);
    float turb = 0.6+0.6*n;
    float streak = (arms*0.6+arms2*0.8) * exp(-r*2.2) * turb;
    float pulse = 1.0 + 0.12*sin(time*2.0);
    float glowCore = (exp(-r*6.0)*1.5 + exp(-r*18.0)*2.0) * pulse;
    vec3 colBase = mix(vec3(0.3,0.6,1.0), vec3(1.0,0.5,0.15), smoothstep(0.05,0.5,r));
    vec3 result = colBase*streak;
    result += vec3(0.6,0.8,1.0)*glowCore;
    return result;
}

// rare shooting stars streaking across the frame
vec3 shootingStars(vec2 p, float t){
    vec3 col = vec3(0.0);
    for(int i=0;i<2;i++){
        float period = 5.3+float(i)*2.7;
        float seed = floor(t/period)+float(i)*17.0;
        float r0 = hash21(vec2(seed,1.0+float(i)));
        float r1 = hash21(vec2(seed,2.0+float(i)));
        float r2 = hash21(vec2(seed,3.0+float(i)));
        float t0 = mod(t, period);
        float dur = 0.45+0.35*r2;
        if(t0 > dur) continue;
        vec2 start = vec2(mix(-0.9,0.9,r0), mix(0.25,0.95,r1));
        vec2 dir = normalize(vec2(-1.0, -0.4-0.5*r2));
        float prog = t0/dur;
        vec2 pos = start + dir*prog*1.3;
        vec2 d = p-pos;
        float along = dot(d,dir);
        vec2 perpVec = d-along*dir;
        float perp = length(perpVec);
        float fade = smoothstep(1.0,0.0,prog);
        float trail = exp(-perp*220.0)*exp(-max(along,0.0)*7.0)*fade;
        float head = exp(-length(d)*110.0)*fade;
        col += vec3(1.0,0.95,0.85)*(trail*1.1+head*2.2);
    }
    return col;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord/R;
    vec2 p = uv-0.5;
    p.x *= ar;

    vec3 col = vec3(0.035,0.028,0.07);

    vec2 coreDrift = vec2(sin(iTime*0.037), cos(iTime*0.029))*0.015;
    vec2 corePos = p - vec2(0.02,-0.03) - coreDrift;
    col += nebulaCore(corePos)*1.3;

    vec2 drift = vec2(sin(iTime*0.05), cos(iTime*0.04))*0.01;
    col += starLayer(p, 15.0, 0.0, 1.3, drift*1.0, 0.004)*1.2;
    col += starLayer(p, 35.0, 5.0, 2.0, drift*1.7, 0.007)*0.65;
    col += starLayer(p, 70.0, 11.0, 3.0, drift*2.4, 0.011)*0.38;

    col += shootingStars(p, iTime);

    float lum = dot(col, vec3(0.299,0.587,0.114));
    vec2 dotUV = fract(fragCoord/3.0)-0.5;
    float dotR = clamp(lum,0.05,0.9)*0.45;
    float dmask = smoothstep(dotR, dotR-0.35, length(dotUV));
    col = mix(col*0.35, col, dmask);

    fragColor = vec4(col, 1.0);
}

Image

Not used

More shaders by scry

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

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

    Account

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