@dopaminefx/effect-aurora 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aurora-shader.d.ts +45 -0
- package/dist/aurora-shader.d.ts.map +1 -0
- package/dist/aurora-shader.js +277 -0
- package/dist/aurora-shader.js.map +1 -0
- package/dist/aurora.dope.json +417 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
- package/src/aurora-shader.ts +287 -0
- package/src/aurora.dope.json +417 -0
- package/src/index.ts +55 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GLSL ES 3.00 source for **Aurora** — a calm success / ambient effect, and a
|
|
3
|
+
* deliberate DIVERGENCE from Solarbloom's centered radial bloom and Verdict's
|
|
4
|
+
* single directional stroke.
|
|
5
|
+
*
|
|
6
|
+
* Governing metaphor: HANGING CURTAINS OF POLAR LIGHT (aurora borealis). The
|
|
7
|
+
* composition is a horizontal BAND of vertical light RIBBONS that drape across
|
|
8
|
+
* the upper field, sway, and sweep sideways, then gently brighten and fade. It
|
|
9
|
+
* is DIRECTIONAL/curtain — emphatically NOT a radial bloom: there is no bright
|
|
10
|
+
* core, no concentric falloff. The light reads as sheets hanging from an unseen
|
|
11
|
+
* line near the top of the frame, feathering downward into nothing.
|
|
12
|
+
*
|
|
13
|
+
* Layers, summed as light (canvas is black, composited `mix-blend-mode: screen`,
|
|
14
|
+
* so black == no change, bright == cast light onto the page beneath):
|
|
15
|
+
* 1. SKY WASH — a very faint cool gradient hugging the top of the band, so the
|
|
16
|
+
* curtains hang from a soft glow rather than empty black.
|
|
17
|
+
* 2. THE CURTAINS — several vertical ribbons. Each ribbon's horizontal centre
|
|
18
|
+
* is displaced by layered fbm (slow, nature-informed drift) plus a global
|
|
19
|
+
* sideways SWEEP; coverage is a soft horizontal lobe. Vertical STRIATIONS
|
|
20
|
+
* (fine fluting) ride each ribbon. Brightness peaks high in the band and
|
|
21
|
+
* feathers toward the bottom edge (the draped hem).
|
|
22
|
+
* 3. RAYS — faint brighter vertical streaks inside the curtains (the classic
|
|
23
|
+
* "searchlight" pillars), modulated by a second noise so they twinkle.
|
|
24
|
+
* 4. CROWN SHIMMER — a slow hue/intensity breathing across the whole band as
|
|
25
|
+
* it settles, so the colour wanders the way a real aurora pulses.
|
|
26
|
+
*
|
|
27
|
+
* Reward timing lives in uniforms (uAmp = envelope amplitude, brighten→fade;
|
|
28
|
+
* uLife = whole-effect progress; uSweep = accumulated sideways drift). Pure
|
|
29
|
+
* function of uTimeS — frame-perfect & cheap under SwiftShader (analytic noise,
|
|
30
|
+
* single pass, no loops over large counts).
|
|
31
|
+
*
|
|
32
|
+
* whimsy == uStyle: 0 = photoreal soft VOLUMETRIC curtains (smooth gradients,
|
|
33
|
+
* feathered hems, dithered); 1 = stylized HARD CEL bands — posterized ribbons
|
|
34
|
+
* with crisp edges + a bright rim, the drift snapped (the pass runner already
|
|
35
|
+
* animates the clock "on twos" as style rises).
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Number of curtain ribbons. Single source of truth: BOTH the GLSL
|
|
39
|
+
* `#define CURTAINS` (interpolated below) and the integer-clamp const the
|
|
40
|
+
* `.dope` mapping references (passed to the loader as `MAX_CURTAINS`).
|
|
41
|
+
*/
|
|
42
|
+
export declare const MAX_CURTAINS = 7;
|
|
43
|
+
export declare const AURORA_VERTEX_SRC = "#version 300 es\nvoid main() {\n vec2 pos = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));\n gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);\n}";
|
|
44
|
+
export declare const AURORA_FRAGMENT_SRC = "#version 300 es\nprecision highp float;\nout vec4 fragColor;\n\nuniform vec2 uResolution; // device pixels\nuniform float uLife; // whole-effect progress 0..1\nuniform float uTimeS; // elapsed seconds\nuniform float uAmp; // envelope amplitude (brighten -> fade; peaks > 1)\nuniform float uExposure; // overall light gain\nuniform float uCoverage; // 0..1 band height + ribbon count fraction (intensity)\nuniform float uBandY; // band vertical centre as fraction of height (0=bottom,1=top)\nuniform float uBandHeight; // band half-height as fraction of height\nuniform float uSway; // horizontal drift amplitude (fraction of width)\nuniform float uSweep; // global sideways sweep offset (fraction of width)\nuniform float uStriation; // 0..1 vertical fluting strength\nuniform float uRays; // 0..1 searchlight-pillar strength\nuniform float uSeed; // per-fire hash offset\nuniform float uStyle; // 0..1 photoreal volumetric -> cel posterized (whimsy)\nuniform float uShadow; // 0 = light pass (screen), 1 = shadow pass (multiply)\nuniform vec2 uShadowOffset; // device-px offset of the cast silhouette (away from light)\nuniform float uShadowSoft; // penumbra softness in device px (blur tap radius)\nuniform float uShadowStrength;// 0..1 max darkening of the multiply layer\nuniform vec3 uC0; // curtain core color\nuniform vec3 uC1; // mid\nuniform vec3 uC2; // crown / accent\n\n#define CURTAINS 7\n\n#define TAU 6.28318530718\n\n\nfloat hash11(float p){ p = fract(p * 0.1031); p *= p + 33.33; p *= p + p; return fract(p); }\nvec2 hash21(float p){\n vec3 p3 = fract(vec3(p) * vec3(0.1031, 0.1030, 0.0973));\n p3 += dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\n\nfloat vnoise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n float a = hash11(dot(i, vec2(1.0, 57.0)));\n float b = hash11(dot(i + vec2(1.0, 0.0), vec2(1.0, 57.0)));\n float c = hash11(dot(i + vec2(0.0, 1.0), vec2(1.0, 57.0)));\n float d = hash11(dot(i + vec2(1.0, 1.0), vec2(1.0, 57.0)));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\nfloat fbm(vec2 p){\n float s = 0.0, a = 0.5;\n mat2 rot = mat2(0.80, -0.60, 0.60, 0.80);\n for (int i = 0; i < 4; i++) { s += a * vnoise(p); p = rot * p * 2.03; a *= 0.5; }\n return s;\n}\n\n\nvec3 paletteMix(float t){\n t = clamp(t, 0.0, 1.0);\n return t < 0.5 ? mix(uC0, uC1, t * 2.0) : mix(uC1, uC2, (t - 0.5) * 2.0);\n}\n\n\nvec3 tonemapACES(vec3 x){\n const float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14;\n return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);\n}\n\n\nvec3 ditherAdd(vec3 col, vec2 frag, float t, float fade){\n float dz = hash11(dot(frag, vec2(12.989, 78.233)) + t) - 0.5;\n return col + (dz / 255.0) * fade;\n}\n\n\n// Vertical envelope of the curtain band: bright high in the band, feathering to\n// nothing at the draped hem below and to a soft top above. ny is the normalized\n// vertical position WITHIN the band (0 = hem/bottom, 1 = top), so the curtains\n// hang from the top and fade downward like real sheets of light.\nfloat bandProfile(float ny){\n // Feather the bottom hem (long, soft) and the top (soft, generous) so the\n // curtain reads as a hanging SHEET \u2014 no hard edges top or bottom.\n float hem = smoothstep(0.0, 0.45, ny); // long fade up from the hem\n float top = 1.0 - smoothstep(0.7, 1.0, ny); // soft, early top falloff\n // Bias brightness upward (the top of a curtain glows hardest).\n float bias = mix(0.6, 1.0, smoothstep(0.1, 0.85, ny));\n return clamp(hem * top * bias, 0.0, 1.0);\n}\n\n// One curtain ribbon's coverage at horizontal position x (fraction 0..1) for a\n// given band-vertical ny. Each ribbon has its OWN base x, sway phase and width;\n// its centre is displaced by slow layered fbm (the living drift) + the global\n// sweep, and is bowed slightly with height so the sheet drapes rather than\n// standing dead-vertical. Returns 0..1 soft horizontal coverage.\nfloat curtain(int i, float x, float ny, out float along){\n float fi = float(i);\n vec2 h = hash21(fi * 3.17 + uSeed);\n // Base horizontal slot, spread across the frame with a little jitter.\n float base = (fi + 0.5) / float(CURTAINS) + (h.x - 0.5) * 0.10;\n // Slow nature-informed drift: two fbm samples at different rates, scrolled by\n // time, so the ribbon wanders organically rather than oscillating mechanically.\n float n1 = fbm(vec2(fi * 1.7 + uSeed, ny * 1.3 + uTimeS * 0.13)) - 0.5;\n float n2 = fbm(vec2(fi * 0.9 + uSeed + 7.0, ny * 2.6 - uTimeS * 0.07)) - 0.5;\n float drift = (n1 * 0.7 + n2 * 0.3) * uSway;\n // Drape bow: the hem swings further than the top (parallax of a hanging sheet).\n float bow = (h.y - 0.5) * uSway * 0.6 * (1.0 - ny);\n float cx = base + drift + bow + uSweep;\n along = x - cx;\n // Ribbon width breathes a touch per-ribbon; soft horizontal lobe.\n float w = mix(0.045, 0.085, h.y) * (0.85 + 0.3 * uCoverage);\n float cov = exp(-pow(along / w, 2.0));\n return cov;\n}\n\n// The full curtain field at fragment uv (0..1, y up): sum the ribbons (capped by\n// coverage so low intensity shows fewer sheets), shaped vertically by the band\n// profile, with vertical striations + searchlight rays riding the light. Outputs\n// total coverage 'cov' and a 0..1 hue coordinate 'hue' (left->right across the\n// band, wandering with the crown shimmer) for the palette.\nfloat auroraField(vec2 uv, out float cov, out float hue){\n // Vertical position within the band.\n float top = uBandY + uBandHeight;\n float bot = uBandY - uBandHeight;\n float ny = (uv.y - bot) / max(top - bot, 1e-3); // 0 at hem, 1 at top\n float vprof = bandProfile(ny);\n cov = 0.0;\n hue = 0.0;\n if (vprof <= 0.0) return 0.0;\n\n // How many ribbons are \"lit\" scales with coverage (intensity): low intensity\n // shows a few calm sheets, high shows the full curtain.\n float lit = mix(2.5, float(CURTAINS), clamp(uCoverage, 0.0, 1.0));\n\n float total = 0.0;\n float hueAccum = 0.0;\n for (int i = 0; i < CURTAINS; i++) {\n float gate = clamp(lit - float(i), 0.0, 1.0); // soft last-ribbon fade-in\n if (gate <= 0.0) break;\n float along;\n float c = curtain(i, uv.x, ny, along) * gate;\n if (c <= 0.001) continue;\n total += c;\n // Hue coordinate: ribbon's place across the band, nudged by its own offset.\n float hi = (float(i) + 0.5) / float(CURTAINS);\n hueAccum += c * hi;\n }\n cov = total * vprof;\n hue = total > 1e-3 ? hueAccum / total : 0.5;\n\n // Vertical STRIATIONS: fine fluting along the curtains (the characteristic\n // ribbon texture). A medium-frequency noise in x that gently darkens/brightens\n // narrow vertical lanes, only inside the lit region so the background stays\n // clean. Kept bounded so it textures the sheet without shredding its edge.\n float flute = fbm(vec2(uv.x * 55.0 + uSeed, uv.y * 4.0 - uTimeS * 0.2));\n float striate = 1.0 + uStriation * (flute - 0.5) * 0.7;\n cov *= striate;\n\n // SEARCHLIGHT RAYS: a few brighter vertical pillars that twinkle \u2014 soft, fairly\n // wide bands in x gated by a slow noise so they come and go. Scaled by the\n // existing coverage so rays live INSIDE the curtains, never as bare spikes.\n float rayBand = pow(max(0.0, sin(uv.x * 60.0 + fbm(vec2(uv.x * 5.0, uTimeS * 0.3)) * 5.0)), 3.0);\n float rayGate = smoothstep(0.5, 0.95, fbm(vec2(uv.x * 9.0 + uSeed, uTimeS * 0.25)));\n cov += rayBand * rayGate * uRays * smoothstep(0.05, 0.5, cov) * 0.5;\n\n return cov;\n}\n\n// SHADOW silhouette \u2014 a cheap occlusion field for the curtain mass (no striation\n// detail / rays), so the faint cast shadow tracks the hanging sheets without an\n// extra heavy pass under software WebGL.\nfloat auroraOcclusion(vec2 frag){\n vec2 uv = frag / uResolution;\n float top = uBandY + uBandHeight;\n float bot = uBandY - uBandHeight;\n float ny = (uv.y - bot) / max(top - bot, 1e-3);\n float vprof = bandProfile(ny);\n if (vprof <= 0.0) return 0.0;\n float lit = mix(2.5, float(CURTAINS), clamp(uCoverage, 0.0, 1.0));\n float total = 0.0;\n for (int i = 0; i < CURTAINS; i++) {\n float gate = clamp(lit - float(i), 0.0, 1.0);\n if (gate <= 0.0) break;\n float along;\n total += curtain(i, uv.x, ny, along) * gate;\n }\n return clamp(total * vprof * uAmp, 0.0, 1.0);\n}\n\nvec4 auroraShadowColor(vec2 frag){\n vec2 sp = frag - uShadowOffset;\n float occ = auroraOcclusion(sp);\n float soft = uShadowSoft;\n occ += auroraOcclusion(sp + vec2( soft, 0.0));\n occ += auroraOcclusion(sp + vec2(-soft, 0.0));\n occ += auroraOcclusion(sp + vec2(0.0, soft));\n occ += auroraOcclusion(sp + vec2(0.0, -soft));\n occ /= 5.0;\n // A real aurora casts almost no shadow; keep it very faint.\n float dark = clamp(occ, 0.0, 1.0) * uShadowStrength * 0.35;\n vec3 tint = mix(vec3(1.0), 0.6 + 0.4 * normalize(uC0 + 1e-3), 0.2);\n vec3 mul = mix(vec3(1.0), tint, dark);\n return vec4(mul, 1.0);\n}\n\nvoid main(){\n vec2 frag = gl_FragCoord.xy;\n vec2 res = uResolution;\n\n if (uShadow > 0.5) {\n fragColor = auroraShadowColor(frag);\n return;\n }\n\n vec2 uv = frag / res;\n vec3 col = vec3(0.0);\n float gain = uAmp * uExposure;\n\n // ---- SKY WASH: a faint cool glow the curtains hang from. ----\n // A soft horizontal band centred near the top of the curtain band, so there is\n // a gentle ground for the light without a radial core.\n float washY = exp(-pow((uv.y - (uBandY + uBandHeight * 0.45)) / max(uBandHeight, 1e-3), 2.0));\n col += mix(uC0, uC2, 0.5) * washY * 0.06 * gain;\n\n // ---- THE CURTAINS ----\n float cov, hue;\n auroraField(uv, cov, hue);\n\n // CROWN SHIMMER: the aurora pulses \u2014 a slow drift of the hue coordinate and a\n // gentle global breathing of intensity, so the colour wanders as it settles.\n float pulse = 0.85 + 0.15 * sin(uTimeS * 0.9 + hue * 4.0 + uSeed);\n float hueShift = hue + 0.15 * sin(uTimeS * 0.4 + uSeed * 6.28) + 0.1 * (fbm(vec2(uv.x * 3.0, uTimeS * 0.2)) - 0.5);\n\n vec3 curtainCol = paletteMix(clamp(hueShift, 0.0, 1.0));\n col += curtainCol * clamp(cov, 0.0, 4.0) * pulse * gain;\n\n // A subtle brighter crown along the very top edge of each lit column (where a\n // real curtain glows hottest), tinted toward the accent.\n float crown = smoothstep(0.0, 0.5, cov) * smoothstep(uBandY + uBandHeight * 0.2, uBandY + uBandHeight, uv.y);\n col += uC2 * crown * 0.4 * gain;\n\n // ---- Tone + finishing (ACES filmic, shared look/glsl) ----\n col = tonemapACES(col * 0.9);\n\n // ---- Non-photoreal pass: hard CEL posterized ribbons (whimsy) ----\n // Toward the cel end the soft volumetric curtains snap into flat posterized\n // bands with crisp edges + a bright rim \u2014 a stylized stained-glass aurora.\n if (uStyle > 0.001) {\n // Posterize the curtain luminance into a few hard tones (don't quantize the\n // dark background \u2014 that shatters the wash into camouflage blocks).\n float lum = clamp(cov * pulse * uExposure * uAmp, 0.0, 1.5);\n float steps = mix(6.0, 3.0, uStyle); // fewer bands at full cel\n float q = floor(lum * steps) / steps;\n vec3 celCol = paletteMix(clamp(hueShift, 0.0, 1.0)) * (q * 1.15 + 0.05);\n // Bright crisp rim at each posterized step edge.\n float band = lum * steps;\n float edge = abs(fract(band) - 0.5);\n float rim = (1.0 - smoothstep(0.0, 0.12, edge)) * smoothstep(0.06, 0.2, lum);\n celCol += clamp(uC2 * 1.5 + 0.1, 0.0, 1.4) * rim * 0.6;\n float mask = smoothstep(0.04, 0.14, lum); // only inside the curtains\n vec3 styled = mix(col, celCol, mask);\n col = mix(col, styled, uStyle);\n }\n\n // Ordered dither (~1/255, shared look/glsl) to kill banding the screen blend\n // would reveal on the page beneath; faded out toward the cel end.\n col = ditherAdd(col, frag, uTimeS, 1.0 - uStyle);\n\n fragColor = vec4(max(col, 0.0), 1.0);\n}";
|
|
45
|
+
//# sourceMappingURL=aurora-shader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aurora-shader.d.ts","sourceRoot":"","sources":["../src/aurora-shader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAWH;;;;GAIG;AACH,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B,eAAO,MAAM,iBAAiB,mKAI5B,CAAC;AAEH,eAAO,MAAM,mBAAmB,4vXAmO9B,CAAC"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GLSL ES 3.00 source for **Aurora** — a calm success / ambient effect, and a
|
|
3
|
+
* deliberate DIVERGENCE from Solarbloom's centered radial bloom and Verdict's
|
|
4
|
+
* single directional stroke.
|
|
5
|
+
*
|
|
6
|
+
* Governing metaphor: HANGING CURTAINS OF POLAR LIGHT (aurora borealis). The
|
|
7
|
+
* composition is a horizontal BAND of vertical light RIBBONS that drape across
|
|
8
|
+
* the upper field, sway, and sweep sideways, then gently brighten and fade. It
|
|
9
|
+
* is DIRECTIONAL/curtain — emphatically NOT a radial bloom: there is no bright
|
|
10
|
+
* core, no concentric falloff. The light reads as sheets hanging from an unseen
|
|
11
|
+
* line near the top of the frame, feathering downward into nothing.
|
|
12
|
+
*
|
|
13
|
+
* Layers, summed as light (canvas is black, composited `mix-blend-mode: screen`,
|
|
14
|
+
* so black == no change, bright == cast light onto the page beneath):
|
|
15
|
+
* 1. SKY WASH — a very faint cool gradient hugging the top of the band, so the
|
|
16
|
+
* curtains hang from a soft glow rather than empty black.
|
|
17
|
+
* 2. THE CURTAINS — several vertical ribbons. Each ribbon's horizontal centre
|
|
18
|
+
* is displaced by layered fbm (slow, nature-informed drift) plus a global
|
|
19
|
+
* sideways SWEEP; coverage is a soft horizontal lobe. Vertical STRIATIONS
|
|
20
|
+
* (fine fluting) ride each ribbon. Brightness peaks high in the band and
|
|
21
|
+
* feathers toward the bottom edge (the draped hem).
|
|
22
|
+
* 3. RAYS — faint brighter vertical streaks inside the curtains (the classic
|
|
23
|
+
* "searchlight" pillars), modulated by a second noise so they twinkle.
|
|
24
|
+
* 4. CROWN SHIMMER — a slow hue/intensity breathing across the whole band as
|
|
25
|
+
* it settles, so the colour wanders the way a real aurora pulses.
|
|
26
|
+
*
|
|
27
|
+
* Reward timing lives in uniforms (uAmp = envelope amplitude, brighten→fade;
|
|
28
|
+
* uLife = whole-effect progress; uSweep = accumulated sideways drift). Pure
|
|
29
|
+
* function of uTimeS — frame-perfect & cheap under SwiftShader (analytic noise,
|
|
30
|
+
* single pass, no loops over large counts).
|
|
31
|
+
*
|
|
32
|
+
* whimsy == uStyle: 0 = photoreal soft VOLUMETRIC curtains (smooth gradients,
|
|
33
|
+
* feathered hems, dithered); 1 = stylized HARD CEL bands — posterized ribbons
|
|
34
|
+
* with crisp edges + a bright rim, the drift snapped (the pass runner already
|
|
35
|
+
* animates the clock "on twos" as style rises).
|
|
36
|
+
*/
|
|
37
|
+
import { GLSL_CONSTANTS, GLSL_DITHER, GLSL_FBM, GLSL_HASH, GLSL_PALETTE_MIX, GLSL_TONEMAP_ACES, } from "@dopaminefx/core";
|
|
38
|
+
/**
|
|
39
|
+
* Number of curtain ribbons. Single source of truth: BOTH the GLSL
|
|
40
|
+
* `#define CURTAINS` (interpolated below) and the integer-clamp const the
|
|
41
|
+
* `.dope` mapping references (passed to the loader as `MAX_CURTAINS`).
|
|
42
|
+
*/
|
|
43
|
+
export const MAX_CURTAINS = 7;
|
|
44
|
+
export const AURORA_VERTEX_SRC = /* glsl */ `#version 300 es
|
|
45
|
+
void main() {
|
|
46
|
+
vec2 pos = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
|
|
47
|
+
gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);
|
|
48
|
+
}`;
|
|
49
|
+
export const AURORA_FRAGMENT_SRC = /* glsl */ `#version 300 es
|
|
50
|
+
precision highp float;
|
|
51
|
+
out vec4 fragColor;
|
|
52
|
+
|
|
53
|
+
uniform vec2 uResolution; // device pixels
|
|
54
|
+
uniform float uLife; // whole-effect progress 0..1
|
|
55
|
+
uniform float uTimeS; // elapsed seconds
|
|
56
|
+
uniform float uAmp; // envelope amplitude (brighten -> fade; peaks > 1)
|
|
57
|
+
uniform float uExposure; // overall light gain
|
|
58
|
+
uniform float uCoverage; // 0..1 band height + ribbon count fraction (intensity)
|
|
59
|
+
uniform float uBandY; // band vertical centre as fraction of height (0=bottom,1=top)
|
|
60
|
+
uniform float uBandHeight; // band half-height as fraction of height
|
|
61
|
+
uniform float uSway; // horizontal drift amplitude (fraction of width)
|
|
62
|
+
uniform float uSweep; // global sideways sweep offset (fraction of width)
|
|
63
|
+
uniform float uStriation; // 0..1 vertical fluting strength
|
|
64
|
+
uniform float uRays; // 0..1 searchlight-pillar strength
|
|
65
|
+
uniform float uSeed; // per-fire hash offset
|
|
66
|
+
uniform float uStyle; // 0..1 photoreal volumetric -> cel posterized (whimsy)
|
|
67
|
+
uniform float uShadow; // 0 = light pass (screen), 1 = shadow pass (multiply)
|
|
68
|
+
uniform vec2 uShadowOffset; // device-px offset of the cast silhouette (away from light)
|
|
69
|
+
uniform float uShadowSoft; // penumbra softness in device px (blur tap radius)
|
|
70
|
+
uniform float uShadowStrength;// 0..1 max darkening of the multiply layer
|
|
71
|
+
uniform vec3 uC0; // curtain core color
|
|
72
|
+
uniform vec3 uC1; // mid
|
|
73
|
+
uniform vec3 uC2; // crown / accent
|
|
74
|
+
|
|
75
|
+
#define CURTAINS ${MAX_CURTAINS}
|
|
76
|
+
${GLSL_CONSTANTS}
|
|
77
|
+
${GLSL_HASH}
|
|
78
|
+
${GLSL_FBM}
|
|
79
|
+
${GLSL_PALETTE_MIX}
|
|
80
|
+
${GLSL_TONEMAP_ACES}
|
|
81
|
+
${GLSL_DITHER}
|
|
82
|
+
|
|
83
|
+
// Vertical envelope of the curtain band: bright high in the band, feathering to
|
|
84
|
+
// nothing at the draped hem below and to a soft top above. ny is the normalized
|
|
85
|
+
// vertical position WITHIN the band (0 = hem/bottom, 1 = top), so the curtains
|
|
86
|
+
// hang from the top and fade downward like real sheets of light.
|
|
87
|
+
float bandProfile(float ny){
|
|
88
|
+
// Feather the bottom hem (long, soft) and the top (soft, generous) so the
|
|
89
|
+
// curtain reads as a hanging SHEET — no hard edges top or bottom.
|
|
90
|
+
float hem = smoothstep(0.0, 0.45, ny); // long fade up from the hem
|
|
91
|
+
float top = 1.0 - smoothstep(0.7, 1.0, ny); // soft, early top falloff
|
|
92
|
+
// Bias brightness upward (the top of a curtain glows hardest).
|
|
93
|
+
float bias = mix(0.6, 1.0, smoothstep(0.1, 0.85, ny));
|
|
94
|
+
return clamp(hem * top * bias, 0.0, 1.0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// One curtain ribbon's coverage at horizontal position x (fraction 0..1) for a
|
|
98
|
+
// given band-vertical ny. Each ribbon has its OWN base x, sway phase and width;
|
|
99
|
+
// its centre is displaced by slow layered fbm (the living drift) + the global
|
|
100
|
+
// sweep, and is bowed slightly with height so the sheet drapes rather than
|
|
101
|
+
// standing dead-vertical. Returns 0..1 soft horizontal coverage.
|
|
102
|
+
float curtain(int i, float x, float ny, out float along){
|
|
103
|
+
float fi = float(i);
|
|
104
|
+
vec2 h = hash21(fi * 3.17 + uSeed);
|
|
105
|
+
// Base horizontal slot, spread across the frame with a little jitter.
|
|
106
|
+
float base = (fi + 0.5) / float(CURTAINS) + (h.x - 0.5) * 0.10;
|
|
107
|
+
// Slow nature-informed drift: two fbm samples at different rates, scrolled by
|
|
108
|
+
// time, so the ribbon wanders organically rather than oscillating mechanically.
|
|
109
|
+
float n1 = fbm(vec2(fi * 1.7 + uSeed, ny * 1.3 + uTimeS * 0.13)) - 0.5;
|
|
110
|
+
float n2 = fbm(vec2(fi * 0.9 + uSeed + 7.0, ny * 2.6 - uTimeS * 0.07)) - 0.5;
|
|
111
|
+
float drift = (n1 * 0.7 + n2 * 0.3) * uSway;
|
|
112
|
+
// Drape bow: the hem swings further than the top (parallax of a hanging sheet).
|
|
113
|
+
float bow = (h.y - 0.5) * uSway * 0.6 * (1.0 - ny);
|
|
114
|
+
float cx = base + drift + bow + uSweep;
|
|
115
|
+
along = x - cx;
|
|
116
|
+
// Ribbon width breathes a touch per-ribbon; soft horizontal lobe.
|
|
117
|
+
float w = mix(0.045, 0.085, h.y) * (0.85 + 0.3 * uCoverage);
|
|
118
|
+
float cov = exp(-pow(along / w, 2.0));
|
|
119
|
+
return cov;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// The full curtain field at fragment uv (0..1, y up): sum the ribbons (capped by
|
|
123
|
+
// coverage so low intensity shows fewer sheets), shaped vertically by the band
|
|
124
|
+
// profile, with vertical striations + searchlight rays riding the light. Outputs
|
|
125
|
+
// total coverage 'cov' and a 0..1 hue coordinate 'hue' (left->right across the
|
|
126
|
+
// band, wandering with the crown shimmer) for the palette.
|
|
127
|
+
float auroraField(vec2 uv, out float cov, out float hue){
|
|
128
|
+
// Vertical position within the band.
|
|
129
|
+
float top = uBandY + uBandHeight;
|
|
130
|
+
float bot = uBandY - uBandHeight;
|
|
131
|
+
float ny = (uv.y - bot) / max(top - bot, 1e-3); // 0 at hem, 1 at top
|
|
132
|
+
float vprof = bandProfile(ny);
|
|
133
|
+
cov = 0.0;
|
|
134
|
+
hue = 0.0;
|
|
135
|
+
if (vprof <= 0.0) return 0.0;
|
|
136
|
+
|
|
137
|
+
// How many ribbons are "lit" scales with coverage (intensity): low intensity
|
|
138
|
+
// shows a few calm sheets, high shows the full curtain.
|
|
139
|
+
float lit = mix(2.5, float(CURTAINS), clamp(uCoverage, 0.0, 1.0));
|
|
140
|
+
|
|
141
|
+
float total = 0.0;
|
|
142
|
+
float hueAccum = 0.0;
|
|
143
|
+
for (int i = 0; i < CURTAINS; i++) {
|
|
144
|
+
float gate = clamp(lit - float(i), 0.0, 1.0); // soft last-ribbon fade-in
|
|
145
|
+
if (gate <= 0.0) break;
|
|
146
|
+
float along;
|
|
147
|
+
float c = curtain(i, uv.x, ny, along) * gate;
|
|
148
|
+
if (c <= 0.001) continue;
|
|
149
|
+
total += c;
|
|
150
|
+
// Hue coordinate: ribbon's place across the band, nudged by its own offset.
|
|
151
|
+
float hi = (float(i) + 0.5) / float(CURTAINS);
|
|
152
|
+
hueAccum += c * hi;
|
|
153
|
+
}
|
|
154
|
+
cov = total * vprof;
|
|
155
|
+
hue = total > 1e-3 ? hueAccum / total : 0.5;
|
|
156
|
+
|
|
157
|
+
// Vertical STRIATIONS: fine fluting along the curtains (the characteristic
|
|
158
|
+
// ribbon texture). A medium-frequency noise in x that gently darkens/brightens
|
|
159
|
+
// narrow vertical lanes, only inside the lit region so the background stays
|
|
160
|
+
// clean. Kept bounded so it textures the sheet without shredding its edge.
|
|
161
|
+
float flute = fbm(vec2(uv.x * 55.0 + uSeed, uv.y * 4.0 - uTimeS * 0.2));
|
|
162
|
+
float striate = 1.0 + uStriation * (flute - 0.5) * 0.7;
|
|
163
|
+
cov *= striate;
|
|
164
|
+
|
|
165
|
+
// SEARCHLIGHT RAYS: a few brighter vertical pillars that twinkle — soft, fairly
|
|
166
|
+
// wide bands in x gated by a slow noise so they come and go. Scaled by the
|
|
167
|
+
// existing coverage so rays live INSIDE the curtains, never as bare spikes.
|
|
168
|
+
float rayBand = pow(max(0.0, sin(uv.x * 60.0 + fbm(vec2(uv.x * 5.0, uTimeS * 0.3)) * 5.0)), 3.0);
|
|
169
|
+
float rayGate = smoothstep(0.5, 0.95, fbm(vec2(uv.x * 9.0 + uSeed, uTimeS * 0.25)));
|
|
170
|
+
cov += rayBand * rayGate * uRays * smoothstep(0.05, 0.5, cov) * 0.5;
|
|
171
|
+
|
|
172
|
+
return cov;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// SHADOW silhouette — a cheap occlusion field for the curtain mass (no striation
|
|
176
|
+
// detail / rays), so the faint cast shadow tracks the hanging sheets without an
|
|
177
|
+
// extra heavy pass under software WebGL.
|
|
178
|
+
float auroraOcclusion(vec2 frag){
|
|
179
|
+
vec2 uv = frag / uResolution;
|
|
180
|
+
float top = uBandY + uBandHeight;
|
|
181
|
+
float bot = uBandY - uBandHeight;
|
|
182
|
+
float ny = (uv.y - bot) / max(top - bot, 1e-3);
|
|
183
|
+
float vprof = bandProfile(ny);
|
|
184
|
+
if (vprof <= 0.0) return 0.0;
|
|
185
|
+
float lit = mix(2.5, float(CURTAINS), clamp(uCoverage, 0.0, 1.0));
|
|
186
|
+
float total = 0.0;
|
|
187
|
+
for (int i = 0; i < CURTAINS; i++) {
|
|
188
|
+
float gate = clamp(lit - float(i), 0.0, 1.0);
|
|
189
|
+
if (gate <= 0.0) break;
|
|
190
|
+
float along;
|
|
191
|
+
total += curtain(i, uv.x, ny, along) * gate;
|
|
192
|
+
}
|
|
193
|
+
return clamp(total * vprof * uAmp, 0.0, 1.0);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
vec4 auroraShadowColor(vec2 frag){
|
|
197
|
+
vec2 sp = frag - uShadowOffset;
|
|
198
|
+
float occ = auroraOcclusion(sp);
|
|
199
|
+
float soft = uShadowSoft;
|
|
200
|
+
occ += auroraOcclusion(sp + vec2( soft, 0.0));
|
|
201
|
+
occ += auroraOcclusion(sp + vec2(-soft, 0.0));
|
|
202
|
+
occ += auroraOcclusion(sp + vec2(0.0, soft));
|
|
203
|
+
occ += auroraOcclusion(sp + vec2(0.0, -soft));
|
|
204
|
+
occ /= 5.0;
|
|
205
|
+
// A real aurora casts almost no shadow; keep it very faint.
|
|
206
|
+
float dark = clamp(occ, 0.0, 1.0) * uShadowStrength * 0.35;
|
|
207
|
+
vec3 tint = mix(vec3(1.0), 0.6 + 0.4 * normalize(uC0 + 1e-3), 0.2);
|
|
208
|
+
vec3 mul = mix(vec3(1.0), tint, dark);
|
|
209
|
+
return vec4(mul, 1.0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
void main(){
|
|
213
|
+
vec2 frag = gl_FragCoord.xy;
|
|
214
|
+
vec2 res = uResolution;
|
|
215
|
+
|
|
216
|
+
if (uShadow > 0.5) {
|
|
217
|
+
fragColor = auroraShadowColor(frag);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
vec2 uv = frag / res;
|
|
222
|
+
vec3 col = vec3(0.0);
|
|
223
|
+
float gain = uAmp * uExposure;
|
|
224
|
+
|
|
225
|
+
// ---- SKY WASH: a faint cool glow the curtains hang from. ----
|
|
226
|
+
// A soft horizontal band centred near the top of the curtain band, so there is
|
|
227
|
+
// a gentle ground for the light without a radial core.
|
|
228
|
+
float washY = exp(-pow((uv.y - (uBandY + uBandHeight * 0.45)) / max(uBandHeight, 1e-3), 2.0));
|
|
229
|
+
col += mix(uC0, uC2, 0.5) * washY * 0.06 * gain;
|
|
230
|
+
|
|
231
|
+
// ---- THE CURTAINS ----
|
|
232
|
+
float cov, hue;
|
|
233
|
+
auroraField(uv, cov, hue);
|
|
234
|
+
|
|
235
|
+
// CROWN SHIMMER: the aurora pulses — a slow drift of the hue coordinate and a
|
|
236
|
+
// gentle global breathing of intensity, so the colour wanders as it settles.
|
|
237
|
+
float pulse = 0.85 + 0.15 * sin(uTimeS * 0.9 + hue * 4.0 + uSeed);
|
|
238
|
+
float hueShift = hue + 0.15 * sin(uTimeS * 0.4 + uSeed * 6.28) + 0.1 * (fbm(vec2(uv.x * 3.0, uTimeS * 0.2)) - 0.5);
|
|
239
|
+
|
|
240
|
+
vec3 curtainCol = paletteMix(clamp(hueShift, 0.0, 1.0));
|
|
241
|
+
col += curtainCol * clamp(cov, 0.0, 4.0) * pulse * gain;
|
|
242
|
+
|
|
243
|
+
// A subtle brighter crown along the very top edge of each lit column (where a
|
|
244
|
+
// real curtain glows hottest), tinted toward the accent.
|
|
245
|
+
float crown = smoothstep(0.0, 0.5, cov) * smoothstep(uBandY + uBandHeight * 0.2, uBandY + uBandHeight, uv.y);
|
|
246
|
+
col += uC2 * crown * 0.4 * gain;
|
|
247
|
+
|
|
248
|
+
// ---- Tone + finishing (ACES filmic, shared look/glsl) ----
|
|
249
|
+
col = tonemapACES(col * 0.9);
|
|
250
|
+
|
|
251
|
+
// ---- Non-photoreal pass: hard CEL posterized ribbons (whimsy) ----
|
|
252
|
+
// Toward the cel end the soft volumetric curtains snap into flat posterized
|
|
253
|
+
// bands with crisp edges + a bright rim — a stylized stained-glass aurora.
|
|
254
|
+
if (uStyle > 0.001) {
|
|
255
|
+
// Posterize the curtain luminance into a few hard tones (don't quantize the
|
|
256
|
+
// dark background — that shatters the wash into camouflage blocks).
|
|
257
|
+
float lum = clamp(cov * pulse * uExposure * uAmp, 0.0, 1.5);
|
|
258
|
+
float steps = mix(6.0, 3.0, uStyle); // fewer bands at full cel
|
|
259
|
+
float q = floor(lum * steps) / steps;
|
|
260
|
+
vec3 celCol = paletteMix(clamp(hueShift, 0.0, 1.0)) * (q * 1.15 + 0.05);
|
|
261
|
+
// Bright crisp rim at each posterized step edge.
|
|
262
|
+
float band = lum * steps;
|
|
263
|
+
float edge = abs(fract(band) - 0.5);
|
|
264
|
+
float rim = (1.0 - smoothstep(0.0, 0.12, edge)) * smoothstep(0.06, 0.2, lum);
|
|
265
|
+
celCol += clamp(uC2 * 1.5 + 0.1, 0.0, 1.4) * rim * 0.6;
|
|
266
|
+
float mask = smoothstep(0.04, 0.14, lum); // only inside the curtains
|
|
267
|
+
vec3 styled = mix(col, celCol, mask);
|
|
268
|
+
col = mix(col, styled, uStyle);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Ordered dither (~1/255, shared look/glsl) to kill banding the screen blend
|
|
272
|
+
// would reveal on the page beneath; faded out toward the cel end.
|
|
273
|
+
col = ditherAdd(col, frag, uTimeS, 1.0 - uStyle);
|
|
274
|
+
|
|
275
|
+
fragColor = vec4(max(col, 0.0), 1.0);
|
|
276
|
+
}`;
|
|
277
|
+
//# sourceMappingURL=aurora-shader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aurora-shader.js","sourceRoot":"","sources":["../src/aurora-shader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EACL,cAAc,EACd,WAAW,EACX,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAE9B,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAC;;;;EAI1C,CAAC;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;mBA0B3B,YAAY;EAC7B,cAAc;EACd,SAAS;EACT,QAAQ;EACR,gBAAgB;EAChB,iBAAiB;EACjB,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmMX,CAAC"}
|