@pixerate/flair 0.31.0 → 0.32.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.
@@ -1671,6 +1671,9 @@ var EFFECT_CATEGORIES = [
1671
1671
  "broadcast",
1672
1672
  "annotation",
1673
1673
  "ui",
1674
+ "3d",
1675
+ "weather",
1676
+ "water",
1674
1677
  "transition",
1675
1678
  "loader",
1676
1679
  "data",
@@ -22799,6 +22802,2185 @@ var scrollSteps = {
22799
22802
  };
22800
22803
  var scrollEffects = [scrollDraw, scrollCounter, scrollSteps];
22801
22804
 
22805
+ // src/catalog/shaders/three-d.ts
22806
+ var SPHERE_HIT = `
22807
+ vec2 sphereHit(vec3 ro, vec3 rd, float r) {
22808
+ float b = dot(ro, rd);
22809
+ float c = dot(ro, ro) - r * r;
22810
+ float h = b * b - c;
22811
+ if (h < 0.0) return vec2(1e9, -1e9);
22812
+ h = sqrt(h);
22813
+ return vec2(-b - h, -b + h);
22814
+ }`.trim();
22815
+ var globe = {
22816
+ kind: "shader",
22817
+ id: "globe",
22818
+ name: "Globe",
22819
+ description: "A spinning Earth-like globe, dotted or realistic, with glowing arcs travelling between cities and an atmosphere rim; drag it to turn it.",
22820
+ category: "3d",
22821
+ tags: ["3d", "globe", "earth", "world", "map", "network", "data", "interactive", "press", "background"],
22822
+ source: glsl(
22823
+ `// Globe: an analytic sphere with noise continents (as dots on a lat/long grid, or shaded), great-circle
22824
+ // arcs between cities lifted off the surface, and a rim of atmosphere. Dragging turns it (iPointer, iClicks).
22825
+ uniform int uStyle; // @control min=0 max=1 default=0 label="Style (0 dots, 1 realistic)"
22826
+ uniform vec3 uBackground; // @control color default=#050816 label="Background"
22827
+ uniform vec3 uOcean; // @control color default=#0B1430 label="Ocean"
22828
+ uniform vec3 uLand; // @control color default=#5B8CFF label="Land"
22829
+ uniform vec3 uArcColor; // @control color default=#FFB547 label="Arc colour"
22830
+ uniform vec3 uAtmosphere; // @control color default=#4F8BFF label="Atmosphere"
22831
+ uniform float uSize; // @control min=0.4 max=1.6 default=1 label="Size"
22832
+ uniform float uSpin; // @control min=-1 max=1 default=0.15 label="Spin speed"
22833
+ uniform float uTilt; // @control min=-1 max=1 default=0.35 label="Tilt"
22834
+ uniform float uDots; // @control min=40 max=200 default=110 step=1 label="Dot rows"
22835
+ uniform float uLandAmount; // @control min=0 max=1 default=0.5 label="Land amount"
22836
+ uniform int uArcs; // @control min=0 max=6 default=6 label="Arcs"
22837
+ uniform float uArcSpeed; // @control min=0 max=3 default=1 label="Arc speed"
22838
+ uniform float uGlow; // @control min=0 max=2 default=1 label="Atmosphere glow"
22839
+ uniform bool uDemo; // @control default=true label="Spin when idle"`,
22840
+ HASH,
22841
+ NOISE_3D,
22842
+ SPHERE_HIT,
22843
+ `const float PI = 3.14159265;
22844
+ // Cities as (latitude, longitude) in degrees, and the pairs the arcs join.
22845
+ const vec2 CITY[6] = vec2[6](vec2(40.7, -74.0), vec2(51.5, -0.1), vec2(35.7, 139.7), vec2(-33.9, 151.2), vec2(-23.5, -46.6), vec2(25.2, 55.3));
22846
+ const ivec2 ROUTE[6] = ivec2[6](ivec2(0, 1), ivec2(1, 5), ivec2(5, 2), ivec2(2, 3), ivec2(0, 4), ivec2(4, 1));
22847
+ const float CAM = 3.4;
22848
+
22849
+ // The globe's own frame (where cities and continents live) to the view; its transpose goes back.
22850
+ mat3 view;
22851
+
22852
+ mat3 viewFrom(float yaw, float tilt) {
22853
+ float cy = cos(yaw), sy = sin(yaw), ct = cos(tilt), st = sin(tilt);
22854
+ mat3 y = mat3(cy, 0.0, -sy, 0.0, 1.0, 0.0, sy, 0.0, cy);
22855
+ mat3 x = mat3(1.0, 0.0, 0.0, 0.0, ct, st, 0.0, -st, ct);
22856
+ return x * y;
22857
+ }
22858
+
22859
+ vec3 fromLatLong(vec2 ll) {
22860
+ vec2 r = ll * PI / 180.0;
22861
+ return vec3(cos(r.x) * sin(r.y), sin(r.x), cos(r.x) * cos(r.y));
22862
+ }
22863
+
22864
+ // Above 0 on land: noise continents, ice at the poles.
22865
+ float landAt(vec3 g) {
22866
+ float n = fbm3(g * 1.7 + vec3(3.1, 7.4, 1.3), 5);
22867
+ return n - mix(0.6, 0.38, uLandAmount);
22868
+ }
22869
+
22870
+ // Where the pointer has dragged the globe: the drag so far while pressed, easing back after letting go.
22871
+ vec2 dragOffset() {
22872
+ float w = iPointer.w;
22873
+ if (w == 0.0 || iClicks[0].w < 0.5) return vec2(0.0);
22874
+ vec2 from = iClicks[0].xy;
22875
+ vec2 to = iPointer.xy;
22876
+ float ease = 1.0;
22877
+ if (w < 0.0) {
22878
+ // Let go: where the pointer was then (its trail, newest first), easing back over two seconds.
22879
+ float released = iTime + w;
22880
+ for (int i = 0; i < 32; i++) {
22881
+ if (iTrail[i].w > 0.5 && iTrail[i].z <= released) {
22882
+ to = iTrail[i].xy;
22883
+ break;
22884
+ }
22885
+ }
22886
+ ease = 1.0 - smoothstep(0.0, 2.0, -w);
22887
+ }
22888
+ return (to - from) / iResolution.y * ease;
22889
+ }
22890
+
22891
+ // The screen position (and depth toward the camera) of a point in the view frame.
22892
+ vec3 project(vec3 v, float f) {
22893
+ return vec3(v.xy * f / (CAM - v.z), v.z);
22894
+ }
22895
+
22896
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
22897
+ float px = 1.0 / iResolution.y;
22898
+ vec2 p = (fragCoord - 0.5 * iResolution.xy) * px;
22899
+ float f = 1.15 * uSize;
22900
+ vec2 drag = dragOffset();
22901
+ bool idle = iPointer.w == 0.0 && iPointer.z < 0.01;
22902
+ float yaw = iTime * uSpin * (uDemo || !idle ? 1.0 : 0.0) + drag.x * 3.5 + 0.8;
22903
+ float tilt = clamp(uTilt * 0.6 - drag.y * 2.5, -1.3, 1.3);
22904
+ view = viewFrom(yaw, tilt);
22905
+
22906
+ vec3 ro = vec3(0.0, 0.0, CAM);
22907
+ vec3 rd = normalize(vec3(p, -f));
22908
+ vec2 hit = sphereHit(ro, rd, 1.0);
22909
+ // Closest approach of the ray to the centre, for the atmosphere outside the disc.
22910
+ float closest = length(ro - rd * dot(ro, rd));
22911
+ vec3 col = uBackground + 0.03 * (1.0 - length(p));
22912
+ // Atmosphere halo.
22913
+ float halo = exp(-max(closest - 1.0, 0.0) * 9.0) * uGlow;
22914
+ vec3 sun = normalize(vec3(-0.6, 0.45, 0.75));
22915
+
22916
+ // Size of a pixel on the sphere, in radians at the disc's centre.
22917
+ float pxAngle = px * (CAM - 1.0) / f;
22918
+ if (hit.x < hit.y) {
22919
+ vec3 pos = ro + rd * hit.x;
22920
+ vec3 n = pos;
22921
+ vec3 g = n * view;
22922
+ float facing = max(dot(n, -rd), 0.0);
22923
+ float lat = asin(clamp(g.y, -1.0, 1.0));
22924
+ float lon = atan(g.x, g.z);
22925
+ if (uStyle == 0) {
22926
+ // Dots on land, on a grid of rows whose dots keep their spacing toward the poles.
22927
+ float rowStep = PI / uDots;
22928
+ float row = floor((lat + 0.5 * PI) / rowStep);
22929
+ float rowLat = (row + 0.5) * rowStep - 0.5 * PI;
22930
+ float count = max(floor(2.0 * PI * cos(rowLat) / rowStep), 1.0);
22931
+ float lonStep = 2.0 * PI / count;
22932
+ float cell = floor((lon + PI) / lonStep);
22933
+ float cellLon = (cell + 0.5) * lonStep - PI;
22934
+ vec3 centre = vec3(cos(rowLat) * sin(cellLon), sin(rowLat), cos(rowLat) * cos(cellLon));
22935
+ float onLand = step(0.0, landAt(centre));
22936
+ vec2 d = vec2((lon - cellLon) * cos(lat), lat - rowLat);
22937
+ d.x = mod(d.x + PI, 2.0 * PI) - PI;
22938
+ float aa = pxAngle / max(facing, 0.12);
22939
+ float dotMask = 1.0 - smoothstep(rowStep * 0.3 - aa, rowStep * 0.3 + aa, length(d));
22940
+ // Dots too small to resolve blend into a soft fill.
22941
+ dotMask = mix(dotMask, 0.3, smoothstep(rowStep * 0.25, rowStep * 0.7, aa));
22942
+ vec3 body = uOcean * (0.55 + 0.45 * facing);
22943
+ float light = 0.45 + 0.55 * facing;
22944
+ col = mix(body, uLand * light, dotMask * onLand);
22945
+ } else {
22946
+ // Shaded continents: ocean with a sun glint, land by height, ice at the poles, a lit and a night side.
22947
+ float h = landAt(g);
22948
+ float coast = smoothstep(-0.015, 0.015, h);
22949
+ float relief = fbm3(g * 9.0, 3);
22950
+ vec3 shallow = mix(uOcean * 1.8, uOcean, smoothstep(-0.12, 0.0, -h));
22951
+ vec3 land = mix(uLand * 0.8, mix(uLand, vec3(0.55, 0.45, 0.3), 0.5), smoothstep(0.03, 0.15, h + relief * 0.06));
22952
+ vec3 surface = mix(shallow, land * (0.8 + 0.4 * relief), coast);
22953
+ float ice = smoothstep(0.82, 0.9, abs(g.y) + relief * 0.08);
22954
+ surface = mix(surface, vec3(0.92, 0.95, 1.0), ice);
22955
+ float diffuse = max(dot(n, sun), 0.0);
22956
+ vec3 h2 = normalize(sun - rd);
22957
+ float glint = pow(max(dot(n, h2), 0.0), 140.0) * (1.0 - coast) * (1.0 - ice);
22958
+ col = surface * (0.06 + 0.94 * diffuse) + vec3(1.0, 0.95, 0.85) * glint * 0.6;
22959
+ // City lights on the night side's land.
22960
+ float lights = step(0.86, hash12(floor(vec2(lon, lat) * 90.0))) * coast * (1.0 - ice);
22961
+ col += vec3(1.0, 0.75, 0.4) * lights * smoothstep(0.1, -0.15, dot(n, sun)) * 0.8;
22962
+ halo *= 0.6 + 0.4 * max(dot(normalize(vec3(p, 0.2)), sun), 0.0);
22963
+ }
22964
+ // Atmosphere rim, toward the edge of the disc.
22965
+ float rim = pow(1.0 - facing, 3.0);
22966
+ col += uAtmosphere * rim * 0.9 * uGlow;
22967
+ // Smooth edge.
22968
+ float edge = smoothstep(1.0, 1.0 - 2.5 * px * (CAM - 1.0) / f, closest);
22969
+ col = mix(uBackground + uAtmosphere * halo, col, edge);
22970
+ } else {
22971
+ col += uAtmosphere * halo * 0.8;
22972
+ }
22973
+
22974
+ // Arcs: great circles between cities, lifted off the surface, a glowing head and fading tail travelling along.
22975
+ float arcs = 0.0;
22976
+ float cities = 0.0;
22977
+ for (int i = 0; i < 6; i++) {
22978
+ if (i >= uArcs) break;
22979
+ vec3 a = fromLatLong(CITY[ROUTE[i].x]);
22980
+ vec3 b = fromLatLong(CITY[ROUTE[i].y]);
22981
+ float span = acos(clamp(dot(a, b), -1.0, 1.0));
22982
+ vec3 axis = normalize(b - a * dot(a, b));
22983
+ float period = 3.2;
22984
+ float phase = iTime * uArcSpeed / period + float(i) * 0.37;
22985
+ float head = fract(phase) * 1.6;
22986
+ float lift = 0.12 + 0.18 * span / PI;
22987
+ // Walk the great circle in the view frame, turning by a fixed step (one cos and sin per arc).
22988
+ vec3 va = view * a;
22989
+ vec3 vb = view * axis;
22990
+ // Pixels well away from the arc's bounding sphere skip it.
22991
+ vec3 m = view * (0.5 * (a + b));
22992
+ float radius = max(0.5 * length(a - b), 1.0 - length(m)) + lift + 0.02;
22993
+ float depth = CAM - m.z - radius;
22994
+ if (depth > 0.05 && length(p - project(m, f).xy) > radius * f / depth + 40.0 * px) continue;
22995
+ float cs = cos(span / 16.0), sn = sin(span / 16.0);
22996
+ vec2 cw = vec2(1.0, 0.0);
22997
+ vec3 prevV = va * 1.004;
22998
+ vec3 prev = project(prevV, f);
22999
+ float sPrev = 0.0;
23000
+ for (int k = 1; k <= 16; k++) {
23001
+ float s = float(k) / 16.0;
23002
+ cw = vec2(cw.x * cs - cw.y * sn, cw.y * cs + cw.x * sn);
23003
+ vec3 v = (va * cw.x + vb * cw.y) * (1.004 + lift * sin(PI * s));
23004
+ vec3 cur = project(v, f);
23005
+ vec2 e = cur.xy - prev.xy;
23006
+ float u = clamp(dot(p - prev.xy, e) / max(dot(e, e), 1e-8), 0.0, 1.0);
23007
+ float d = length(p - prev.xy - e * u);
23008
+ // Only near the segment: is that part of it hidden behind the globe?
23009
+ bool hidden = true;
23010
+ if (d < 40.0 * px) {
23011
+ vec3 near = mix(prevV, v, u);
23012
+ vec2 sh = sphereHit(ro, normalize(near - ro), 1.0);
23013
+ hidden = sh.x < sh.y && sh.x < length(near - ro) - 1e-3;
23014
+ }
23015
+ prevV = v;
23016
+ if (!hidden) {
23017
+ float along = mix(sPrev, s, u);
23018
+ float tail = smoothstep(head - 0.6, head, along) * step(along, head);
23019
+ float core = 1.0 - smoothstep(0.6 * px, 1.8 * px, d);
23020
+ float glow = exp(-d / (5.0 * px)) * 0.5;
23021
+ arcs = max(arcs, (core + glow) * tail + core * 0.12);
23022
+ // The head's spark.
23023
+ float spark = exp(-length(p - mix(prev.xy, cur.xy, clamp((head - sPrev) / (s - sPrev), 0.0, 1.0))) / (3.5 * px));
23024
+ arcs = max(arcs, spark * step(sPrev, head) * step(head, s) * 1.4);
23025
+ }
23026
+ prev = cur;
23027
+ sPrev = s;
23028
+ }
23029
+ }
23030
+ for (int i = 0; i < 6; i++) {
23031
+ vec3 c = view * fromLatLong(CITY[i]);
23032
+ if (c.z < 0.05) continue;
23033
+ vec3 q = project(c, f);
23034
+ float d = length(p - q.xy);
23035
+ float pulse = 0.5 + 0.5 * sin(iTime * 3.0 + float(i));
23036
+ cities = max(cities, (1.0 - smoothstep(1.5 * px, 3.0 * px, d)) + exp(-d / (6.0 * px)) * 0.4 * pulse);
23037
+ }
23038
+ col += uArcColor * (arcs + cities);
23039
+ fragColor = vec4(col, 1.0);
23040
+ }`
23041
+ ),
23042
+ presets: [
23043
+ { name: "Dotted dark", values: { uStyle: 0 } },
23044
+ {
23045
+ name: "Realistic",
23046
+ values: {
23047
+ uStyle: 1,
23048
+ uBackground: [0, 0, 0.02],
23049
+ uOcean: [0.02, 0.1, 0.3],
23050
+ uLand: [0.22, 0.42, 0.18],
23051
+ uArcColor: [1, 0.85, 0.45],
23052
+ uAtmosphere: [0.35, 0.6, 1]
23053
+ }
23054
+ },
23055
+ { name: "Light", values: { uStyle: 0, uBackground: [0.96, 0.97, 1], uOcean: [0.86, 0.89, 0.96], uLand: [0.15, 0.25, 0.6], uArcColor: [1, 0.3, 0.45], uAtmosphere: [0.55, 0.65, 1], uGlow: 0.5 } }
23056
+ ]
23057
+ };
23058
+ var rotatingShapes = {
23059
+ kind: "shader",
23060
+ id: "rotating-shapes",
23061
+ name: "Rotating shapes",
23062
+ description: "A torus, a rounded cube and an icosahedron floating and turning in a soft-lit studio, casting soft shadows on the backdrop.",
23063
+ category: "3d",
23064
+ tags: ["3d", "shapes", "studio", "geometry", "product", "background", "minimal"],
23065
+ source: glsl(
23066
+ `// Rotating shapes: three signed-distance primitives ray-marched inside their bounding spheres (64 steps at
23067
+ // most), a studio floor met analytically, soft shadows toward a key light and a little ambient occlusion.
23068
+ uniform vec3 uBackdrop; // @control color default=#ECE7E1 label="Backdrop"
23069
+ uniform vec3 uColorA; // @control color default=#FF6A4D label="Torus"
23070
+ uniform vec3 uColorB; // @control color default=#4C6FFF label="Cube"
23071
+ uniform vec3 uColorC; // @control color default=#FFC23D label="Icosahedron"
23072
+ uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"
23073
+ uniform float uFloat; // @control min=0 max=2 default=1 label="Float"
23074
+ uniform float uGloss; // @control min=0 max=1 default=0.6 label="Gloss"
23075
+ uniform float uSoftness; // @control min=0.1 max=2 default=0.7 label="Shadow softness"`,
23076
+ SPHERE_HIT,
23077
+ `const float PHI = 1.618034;
23078
+ const vec3 LIGHT = vec3(0.55, 0.85, 0.45);
23079
+
23080
+ vec3 centre(int i) {
23081
+ float fi = float(i);
23082
+ return vec3(-1.55 + 1.55 * fi, -0.05 + 0.18 * uFloat * sin(iTime * uSpeed * 0.9 + fi * 2.1), 0.15 * fi - 0.15);
23083
+ }
23084
+
23085
+ mat3 spin(float a, float b) {
23086
+ float ca = cos(a), sa = sin(a), cb = cos(b), sb = sin(b);
23087
+ return mat3(ca, 0.0, -sa, 0.0, 1.0, 0.0, sa, 0.0, ca) * mat3(1.0, 0.0, 0.0, 0.0, cb, sb, 0.0, -sb, cb);
23088
+ }
23089
+
23090
+ float sdTorus(vec3 p) {
23091
+ vec2 q = vec2(length(p.xz) - 0.5, p.y);
23092
+ return length(q) - 0.19;
23093
+ }
23094
+ float sdRoundBox(vec3 p) {
23095
+ vec3 q = abs(p) - vec3(0.36);
23096
+ return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0) - 0.08;
23097
+ }
23098
+ float sdIcosahedron(vec3 p) {
23099
+ p = abs(p);
23100
+ float d = dot(p, normalize(vec3(1.0)));
23101
+ d = max(d, dot(p, normalize(vec3(0.0, 1.0, PHI + 1.0))));
23102
+ d = max(d, dot(p, normalize(vec3(PHI + 1.0, 0.0, 1.0))));
23103
+ d = max(d, dot(p, normalize(vec3(1.0, PHI + 1.0, 0.0))));
23104
+ return d - 0.5;
23105
+ }
23106
+
23107
+ // The distance to the nearest shape, and which (0, 1, 2).
23108
+ vec2 scene(vec3 p) {
23109
+ float t = iTime * uSpeed;
23110
+ vec2 best = vec2(1e9, -1.0);
23111
+ for (int i = 0; i < 3; i++) {
23112
+ vec3 q = (p - centre(i)) * spin(t * (0.5 + 0.15 * float(i)) + float(i), t * 0.7 + float(i) * 1.3);
23113
+ float d = i == 0 ? sdTorus(q) : i == 1 ? sdRoundBox(q) : sdIcosahedron(q);
23114
+ if (d < best.x) best = vec2(d, float(i));
23115
+ }
23116
+ return best;
23117
+ }
23118
+
23119
+ // The stretch of a ray inside any shape's bounding sphere (x > y when it misses them all).
23120
+ vec2 bounds(vec3 ro, vec3 rd) {
23121
+ vec2 span = vec2(1e9, -1e9);
23122
+ for (int i = 0; i < 3; i++) {
23123
+ vec2 h = sphereHit(ro - centre(i), rd, 0.78);
23124
+ if (h.x < h.y) span = vec2(min(span.x, h.x), max(span.y, h.y));
23125
+ }
23126
+ return vec2(max(span.x, 0.0), span.y);
23127
+ }
23128
+
23129
+ vec3 normalAt(vec3 p) {
23130
+ vec2 e = vec2(1.0, -1.0) * 0.0015;
23131
+ return normalize(e.xyy * scene(p + e.xyy).x + e.yyx * scene(p + e.yyx).x + e.yxy * scene(p + e.yxy).x + e.xxx * scene(p + e.xxx).x);
23132
+ }
23133
+
23134
+ // Soft shadow toward the light from p (1 lit, 0 in shadow), marched only through the shapes' bounds.
23135
+ float softShadow(vec3 p, vec3 l) {
23136
+ vec2 b = bounds(p, l);
23137
+ if (b.x > b.y) return 1.0;
23138
+ float k = 10.0 / uSoftness;
23139
+ float res = 1.0;
23140
+ float t = max(b.x, 0.02);
23141
+ for (int i = 0; i < 28; i++) {
23142
+ if (t > b.y) break;
23143
+ float h = scene(p + l * t).x;
23144
+ res = min(res, k * h / t);
23145
+ if (res < 0.002) break;
23146
+ t += clamp(h, 0.03, 0.3);
23147
+ }
23148
+ return smoothstep(0.0, 1.0, res);
23149
+ }
23150
+
23151
+ vec3 studio(vec3 rd) {
23152
+ // The backdrop's sweep, lighter toward the top, with a softbox overhead for reflections.
23153
+ vec3 c = uBackdrop * mix(0.82, 1.05, smoothstep(-0.3, 0.6, rd.y));
23154
+ c += vec3(1.2) * smoothstep(0.75, 0.95, rd.y) * smoothstep(-0.2, 0.3, rd.z + 0.4);
23155
+ return c;
23156
+ }
23157
+
23158
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
23159
+ vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
23160
+ vec3 ro = vec3(0.0, 0.75, 5.6);
23161
+ vec3 target = vec3(0.0, -0.15, 0.0);
23162
+ vec3 fw = normalize(target - ro);
23163
+ vec3 rt = normalize(cross(fw, vec3(0.0, 1.0, 0.0)));
23164
+ vec3 up = cross(rt, fw);
23165
+ vec3 rd = normalize(p.x * rt + p.y * up + 1.9 * fw);
23166
+ vec3 l = normalize(LIGHT);
23167
+
23168
+ // The backdrop: a floor at y = -1 sweeping up into the wall behind.
23169
+ vec3 col = studio(rd) * (0.9 + 0.1 * smoothstep(-0.3, 0.2, rd.y));
23170
+ float tFloor = rd.y < 0.0 ? (-1.0 - ro.y) / rd.y : 1e9;
23171
+ float tHit = 1e9;
23172
+ float id = -1.0;
23173
+ vec2 b = bounds(ro, rd);
23174
+ if (b.x < b.y) {
23175
+ float t = b.x;
23176
+ for (int i = 0; i < 64; i++) {
23177
+ vec2 h = scene(ro + rd * t);
23178
+ if (h.x < 0.001 * t) {
23179
+ tHit = t;
23180
+ id = h.y;
23181
+ break;
23182
+ }
23183
+ t += h.x;
23184
+ if (t > b.y) break;
23185
+ }
23186
+ }
23187
+ if (tHit < tFloor) {
23188
+ vec3 pos = ro + rd * tHit;
23189
+ vec3 n = normalAt(pos);
23190
+ vec3 base = id < 0.5 ? uColorA : id < 1.5 ? uColorB : uColorC;
23191
+ float diff = max(dot(n, l), 0.0) * softShadow(pos + n * 0.01, l);
23192
+ float ao = clamp(0.5 + 0.5 * scene(pos + n * 0.12).x / 0.12, 0.0, 1.0);
23193
+ ao *= clamp(0.6 + 0.4 * (pos.y + 1.0), 0.0, 1.0);
23194
+ float sky = 0.5 + 0.5 * n.y;
23195
+ vec3 lit = base * (0.25 * sky * ao + 0.85 * diff) + base * uBackdrop * 0.12 * (1.0 - sky) * ao;
23196
+ vec3 h = normalize(l - rd);
23197
+ float fres = pow(1.0 - max(dot(n, -rd), 0.0), 5.0);
23198
+ float spec = pow(max(dot(n, h), 0.0), mix(8.0, 90.0, uGloss)) * diff * mix(0.1, 0.9, uGloss);
23199
+ vec3 env = studio(reflect(rd, n));
23200
+ col = lit + spec + env * mix(0.02, 0.25, uGloss) * mix(0.3, 1.0, fres) * ao;
23201
+ } else if (tFloor < 1e8) {
23202
+ vec3 pos = ro + rd * tFloor;
23203
+ float shadow = softShadow(pos, l);
23204
+ // Contact darkening near the shapes.
23205
+ float near = scene(pos).x;
23206
+ float ao = mix(0.55, 1.0, smoothstep(0.0, 0.6, near));
23207
+ vec3 floorCol = uBackdrop * (0.62 + 0.38 * shadow) * ao;
23208
+ // The floor curves up into the wall: blend into the backdrop with distance.
23209
+ col = mix(floorCol, col, smoothstep(9.0, 16.0, tFloor));
23210
+ }
23211
+ // A soft vignette and gamma.
23212
+ col *= 1.0 - 0.25 * dot(p, p);
23213
+ fragColor = vec4(pow(clamp(col, 0.0, 1.0), vec3(0.9)), 1.0);
23214
+ }`
23215
+ ),
23216
+ presets: [
23217
+ { name: "Pastel", values: { uBackdrop: [0.96, 0.92, 0.95], uColorA: [1, 0.62, 0.7], uColorB: [0.55, 0.75, 1], uColorC: [0.62, 0.9, 0.75], uGloss: 0.4 } },
23218
+ { name: "Midnight", values: { uBackdrop: [0.12, 0.13, 0.18], uColorA: [1, 0.35, 0.55], uColorB: [0.3, 0.85, 1], uColorC: [0.9, 0.95, 1], uGloss: 0.9 } }
23219
+ ]
23220
+ };
23221
+ var isometricCity = {
23222
+ kind: "shader",
23223
+ id: "isometric-city",
23224
+ name: "Isometric city",
23225
+ description: "An isometric block city whose buildings rise out of the ground, drifting through day and night as windows, street lights and traffic come on.",
23226
+ category: "3d",
23227
+ tags: ["3d", "city", "isometric", "buildings", "day", "night", "urban", "background"],
23228
+ source: glsl(
23229
+ `// Isometric city: an orthographic camera walks the city's grid cell by cell (48 cells at most), meeting each
23230
+ // cell's building box or the street analytically. A sun circles for the day, windows light up at dusk.
23231
+ uniform vec3 uBuilding; // @control color default=#E8DCCB label="Buildings"
23232
+ uniform vec3 uAccent; // @control color default=#7FA7D9 label="Accent buildings"
23233
+ uniform vec3 uGround; // @control color default=#B9C7A5 label="Ground"
23234
+ uniform vec3 uRoad; // @control color default=#5B6170 label="Roads"
23235
+ uniform vec3 uWindow; // @control color default=#FFD27A label="Window light"
23236
+ uniform float uDayLength; // @control min=4 max=120 default=40 label="Day length (s)"
23237
+ uniform float uTimeOfDay; // @control min=0 max=1 default=0.15 label="Time of day"
23238
+ uniform float uHeight; // @control min=0.3 max=2 default=1 label="Building height"
23239
+ uniform float uRise; // @control min=0 max=10 default=3 label="Rise time (s)"
23240
+ uniform float uZoom; // @control min=0.5 max=2 default=1 label="Zoom"
23241
+ uniform float uPan; // @control min=0 max=2 default=0.4 label="Pan speed"`,
23242
+ HASH,
23243
+ `const float BLOCK = 4.0;
23244
+ const float MAXH = 7.0;
23245
+
23246
+ bool isRoad(vec2 c) {
23247
+ return mod(c.x, BLOCK) < 0.5 || mod(c.y, BLOCK) < 0.5;
23248
+ }
23249
+
23250
+ // The building in cell c: its height (0 for none) and kind (a hash).
23251
+ vec2 building(vec2 c) {
23252
+ if (isRoad(c)) return vec2(0.0);
23253
+ float h = hash12(c);
23254
+ float k = hash12(c + 17.3);
23255
+ // A little park.
23256
+ if (k < 0.08) return vec2(0.0, k);
23257
+ // Downtowns: taller where a slow wave of districts peaks.
23258
+ float district = 0.5 + 0.5 * sin(c.x * 0.13 + 1.3) * sin(c.y * 0.11 + 0.4);
23259
+ float height = (0.6 + 0.6 * h + 4.8 * pow(h, 4.0) * district) * uHeight;
23260
+ float start = hash12(c + 5.1) * uRise * 0.75;
23261
+ float grow = uRise <= 0.0 ? 1.0 : smoothstep(start, start + uRise * 0.25 + 0.01, iTime);
23262
+ return vec2(min(height, MAXH) * grow, k);
23263
+ }
23264
+
23265
+ // The sun's direction, and how much of the day it is (1 by day, 0 at night).
23266
+ vec4 sunAt() {
23267
+ float phase = fract(uTimeOfDay + iTime / uDayLength) * 6.28318;
23268
+ vec3 dir = normalize(vec3(cos(phase) * 0.8, sin(phase), 0.45));
23269
+ return vec4(dir, smoothstep(-0.22, 0.2, dir.y));
23270
+ }
23271
+
23272
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
23273
+ vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
23274
+ vec3 d = normalize(vec3(-1.0, -0.82, -1.0));
23275
+ vec3 rt = normalize(cross(d, vec3(0.0, 1.0, 0.0)));
23276
+ vec3 up = cross(rt, d);
23277
+ vec3 target = vec3(1.0, 0.0, 0.3) * iTime * uPan + vec3(0.5, 0.0, 0.5);
23278
+ vec3 o = target + (p.x * rt + p.y * up) * 10.0 / uZoom - d * 40.0;
23279
+ // Start where the ray comes down to the tallest buildings.
23280
+ float t = (MAXH - o.y) / d.y;
23281
+ vec3 pos = o + d * t;
23282
+ vec2 cell = floor(pos.xz);
23283
+ vec2 stepDir = sign(d.xz);
23284
+ vec2 delta = abs(1.0 / d.xz);
23285
+ vec2 side = (stepDir * (cell - pos.xz) + stepDir * 0.5 + 0.5) * delta + t;
23286
+ vec3 inv = 1.0 / d;
23287
+
23288
+ vec4 sun = sunAt();
23289
+ float day = sun.w;
23290
+ float night = 1.0 - day;
23291
+ vec3 ambient = mix(vec3(0.22, 0.26, 0.45), vec3(1.0), day);
23292
+ vec3 col = uGround * ambient * 0.7;
23293
+ for (int i = 0; i < 48; i++) {
23294
+ float tExit = min(side.x, side.y);
23295
+ vec2 b = building(cell);
23296
+ if (b.x > 0.0) {
23297
+ // The building's box, inset from the cell's edges.
23298
+ vec3 lo = vec3(cell.x + 0.12, 0.0, cell.y + 0.12);
23299
+ vec3 hi = vec3(cell.x + 0.88, b.x, cell.y + 0.88);
23300
+ vec3 t0 = (lo - o) * inv;
23301
+ vec3 t1 = (hi - o) * inv;
23302
+ vec3 tn = min(t0, t1);
23303
+ vec3 tf = max(t0, t1);
23304
+ float tin = max(max(tn.x, tn.y), tn.z);
23305
+ float tout = min(min(tf.x, tf.y), tf.z);
23306
+ if (tin < tout && tout > 0.0) {
23307
+ vec3 hp = o + d * tin;
23308
+ vec3 n = tin == tn.y ? vec3(0.0, 1.0, 0.0) : tin == tn.x ? vec3(-stepDir.x, 0.0, 0.0) : vec3(0.0, 0.0, -stepDir.y);
23309
+ vec3 base = mix(uBuilding, uAccent, step(0.72, b.y)) * (0.85 + 0.3 * hash12(cell + 2.0));
23310
+ float light = max(dot(n, sun.xyz), 0.0) * day;
23311
+ // Faces keep the isometric look: the top brightest, the two sides apart.
23312
+ float face = n.y > 0.5 ? 1.0 : abs(n.x) > 0.5 ? 0.78 : 0.62;
23313
+ vec3 c = base * face * (ambient * 0.55 + vec3(1.0, 0.95, 0.85) * light * 0.6);
23314
+ if (n.y < 0.5) {
23315
+ // Windows on the sides: a grid of panes, glass by day and lit at random by night.
23316
+ float along = abs(n.x) > 0.5 ? hp.z : hp.x;
23317
+ vec2 g = vec2(along * 4.0, hp.y * 3.2);
23318
+ vec2 f = fract(g);
23319
+ float pane = step(0.25, f.x) * step(f.x, 0.75) * step(0.3, f.y) * step(f.y, 0.78) * step(0.25, hp.y) * step(hp.y, b.x - 0.15);
23320
+ float on = step(hash12(floor(g) + cell * 7.0 + n.xz * 3.0), night * 0.75 - 0.05);
23321
+ vec3 glass = mix(vec3(0.35, 0.5, 0.65) * ambient * face, uWindow * 1.6, on);
23322
+ c = mix(c, glass, pane);
23323
+ // Darker toward the ground.
23324
+ c *= 0.7 + 0.3 * smoothstep(0.0, 1.2, hp.y);
23325
+ } else {
23326
+ // A parapet around the roof.
23327
+ vec2 r = abs(fract(hp.xz) - 0.5);
23328
+ c *= 0.9 + 0.1 * step(max(r.x, r.y), 0.3);
23329
+ }
23330
+ col = c;
23331
+ break;
23332
+ }
23333
+ }
23334
+ // Reaching the ground in this cell.
23335
+ float tg = -o.y / d.y;
23336
+ if (tg <= tExit) {
23337
+ vec3 gp = o + d * tg;
23338
+ vec2 f = fract(gp.xz);
23339
+ vec3 g;
23340
+ if (isRoad(cell)) {
23341
+ g = uRoad;
23342
+ // Lane markings, and traffic in both directions.
23343
+ bool alongX = mod(cell.y, BLOCK) < 0.5;
23344
+ bool junction = mod(cell.x, BLOCK) < 0.5 && mod(cell.y, BLOCK) < 0.5;
23345
+ float across = alongX ? f.y : f.x;
23346
+ float run = alongX ? gp.x : gp.z;
23347
+ float lane = across < 0.5 ? 1.0 : -1.0;
23348
+ g = mix(g, vec3(0.92), step(abs(across - 0.5), 0.02) * step(0.5, fract(run * 2.0)) * (junction ? 0.0 : 1.0));
23349
+ float road = alongX ? cell.y : cell.x;
23350
+ float slot = run * 0.25 - iTime * 0.35 * lane + hash12(vec2(road, lane));
23351
+ float car = step(abs(across - 0.5 + lane * 0.22), 0.11) * step(abs(fract(slot) - 0.5), 0.07) * step(0.4, hash12(vec2(floor(slot), road + lane)));
23352
+ vec3 carCol = mix(vec3(0.9, 0.3, 0.3), vec3(0.3, 0.5, 0.9), hash12(vec2(floor(slot), road)));
23353
+ g = mix(g, mix(carCol, vec3(1.0, 0.9, 0.6) * 2.0, night), car);
23354
+ } else {
23355
+ g = b.y < 0.08 ? uGround * vec3(0.8, 1.0, 0.75) : uGround;
23356
+ // Shade around a building's foot.
23357
+ vec2 q = abs(f - 0.5);
23358
+ g *= b.x > 0.0 ? mix(0.6, 1.0, smoothstep(0.38, 0.5, max(q.x, q.y))) : 1.0;
23359
+ // Street lights at the corners of each lot by night.
23360
+ vec2 corner = q - 0.42;
23361
+ float lamp = exp(-length(max(corner, 0.0)) * 18.0) * step(0.0, corner.x) * step(0.0, corner.y);
23362
+ g += uWindow * lamp * night * 1.5;
23363
+ }
23364
+ col = g * mix(ambient * 0.7, ambient, day);
23365
+ break;
23366
+ }
23367
+ if (side.x < side.y) {
23368
+ side.x += delta.x;
23369
+ cell.x += stepDir.x;
23370
+ } else {
23371
+ side.y += delta.y;
23372
+ cell.y += stepDir.y;
23373
+ }
23374
+ }
23375
+ // Warm light at dawn and dusk.
23376
+ col *= mix(vec3(1.0), vec3(1.15, 0.85, 0.7), smoothstep(0.35, 0.0, abs(sun.y)) * 0.6);
23377
+ fragColor = vec4(col, 1.0);
23378
+ }`
23379
+ ),
23380
+ presets: [
23381
+ { name: "Night", values: { uTimeOfDay: 0.72, uDayLength: 120 } },
23382
+ { name: "Toy town", values: { uBuilding: [1, 0.86, 0.75], uAccent: [1, 0.55, 0.5], uGround: [0.66, 0.85, 0.55], uRoad: [0.55, 0.58, 0.66], uHeight: 0.6, uZoom: 1.4 } }
23383
+ ]
23384
+ };
23385
+ var wireframeTerrain = {
23386
+ kind: "shader",
23387
+ id: "wireframe-terrain",
23388
+ name: "Wireframe terrain",
23389
+ description: "A fly-over of a glowing wireframe mesh of hills and valleys, the lines coloured by height and fading into fog on the horizon.",
23390
+ category: "3d",
23391
+ tags: ["3d", "terrain", "wireframe", "mesh", "landscape", "sci-fi", "retro", "background"],
23392
+ source: glsl(
23393
+ `// Wireframe terrain: a noise heightfield ray-marched with steps that grow with distance (80 at most), a
23394
+ // triangle mesh's lines drawn on it from the hit point (anti-aliased with fwidth), fog toward the horizon.
23395
+ uniform vec3 uSky; // @control color default=#03040C label="Sky"
23396
+ uniform vec3 uHorizon; // @control color default=#3A1E5C label="Horizon glow"
23397
+ uniform vec3 uFill; // @control color default=#05060F label="Surface"
23398
+ uniform vec3 uLineLow; // @control color default=#00E5FF label="Lines (low)"
23399
+ uniform vec3 uLineHigh; // @control color default=#FF3BD4 label="Lines (high)"
23400
+ uniform float uHeight; // @control min=0 max=3 default=1 label="Height"
23401
+ uniform float uSpeed; // @control min=0 max=4 default=1 label="Speed"
23402
+ uniform float uDensity; // @control min=0.5 max=4 default=1.5 label="Mesh density"
23403
+ uniform float uLineWidth; // @control min=0.5 max=4 default=1.2 label="Line width (px)"
23404
+ uniform float uGlow; // @control min=0 max=2 default=1 label="Glow"
23405
+ uniform float uFog; // @control min=0 max=2 default=1 label="Fog"`,
23406
+ HASH,
23407
+ `float vnoise(vec2 p) {
23408
+ vec2 i = floor(p);
23409
+ vec2 f = fract(p);
23410
+ vec2 u = f * f * (3.0 - 2.0 * f);
23411
+ return mix(mix(hash12(i), hash12(i + vec2(1.0, 0.0)), u.x), mix(hash12(i + vec2(0.0, 1.0)), hash12(i + vec2(1.0, 1.0)), u.x), u.y);
23412
+ }
23413
+
23414
+ // The ground's height: rolling noise hills, lower along the valley the camera flies down.
23415
+ float terrain(vec2 xz) {
23416
+ vec2 q = xz * 0.12;
23417
+ float h = 0.0;
23418
+ float amp = 1.0;
23419
+ for (int i = 0; i < 3; i++) {
23420
+ h += amp * vnoise(q);
23421
+ q = mat2(0.8, 0.6, -0.6, 0.8) * q * 2.1 + 3.7;
23422
+ amp *= 0.45;
23423
+ }
23424
+ float valley = smoothstep(1.0, 9.0, abs(xz.x));
23425
+ return (h - 0.55) * 4.5 * uHeight * (0.25 + 0.75 * valley);
23426
+ }
23427
+
23428
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
23429
+ vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
23430
+ float z = iTime * uSpeed * 4.0;
23431
+ vec3 ro = vec3(sin(z * 0.03) * 1.5, 0.0, z);
23432
+ ro.y = max(terrain(ro.xz), 0.0) * 0.4 + 2.6 * max(uHeight, 0.4);
23433
+ vec3 fw = normalize(vec3(0.0, -0.22, 1.0));
23434
+ vec3 rt = normalize(cross(vec3(0.0, 1.0, 0.0), fw));
23435
+ vec3 up = cross(fw, rt);
23436
+ vec3 rd = normalize(p.x * rt + p.y * up + 1.3 * fw);
23437
+
23438
+ // The sky: dark above, glowing toward the horizon.
23439
+ vec3 sky = mix(uHorizon, uSky, smoothstep(-0.05, 0.35, rd.y));
23440
+ sky += uHorizon * 0.6 * exp(-abs(rd.y) * 18.0);
23441
+ vec2 sp = floor(fragCoord / 2.0);
23442
+ sky += step(0.997, hash12(sp)) * smoothstep(0.0, 0.3, rd.y) * 0.7;
23443
+ vec3 col = sky;
23444
+
23445
+ float far = 70.0;
23446
+ float t = 0.5;
23447
+ float prevT = t;
23448
+ float prevGap = 1.0;
23449
+ bool hit = false;
23450
+ for (int i = 0; i < 80; i++) {
23451
+ vec3 pos = ro + rd * t;
23452
+ float gap = pos.y - terrain(pos.xz);
23453
+ if (gap < 0.0) {
23454
+ // Between the last step above the ground and this one below it.
23455
+ t = prevT + (t - prevT) * prevGap / (prevGap - gap);
23456
+ hit = true;
23457
+ break;
23458
+ }
23459
+ prevT = t;
23460
+ prevGap = gap;
23461
+ t += max(0.06 * t, gap * 0.5);
23462
+ // Above the highest hill and climbing: nothing left to meet.
23463
+ if (t > far || (rd.y > 0.0 && pos.y > 5.0 * uHeight)) break;
23464
+ }
23465
+ if (hit) {
23466
+ vec3 pos = ro + rd * t;
23467
+ float h = terrain(pos.xz);
23468
+ // A triangle mesh: lines along x, along z and one diagonal.
23469
+ vec2 g = pos.xz * uDensity;
23470
+ vec3 coord = vec3(g, g.x - g.y);
23471
+ vec3 dist = abs(fract(coord + 0.5) - 0.5);
23472
+ vec3 w = fwidth(coord);
23473
+ vec3 lines3 = dist / max(w, 1e-4);
23474
+ float lineD = min(min(lines3.x, lines3.y), lines3.z);
23475
+ float line = 1.0 - smoothstep(uLineWidth * 0.5, uLineWidth * 0.5 + 1.0, lineD);
23476
+ float glow = exp(-lineD / (uLineWidth * 3.0)) * 0.45 * uGlow;
23477
+ // Lines too dense to tell apart blend into a faint wash.
23478
+ float dense = smoothstep(0.25, 0.6, max(max(w.x, w.y), w.z));
23479
+ float amount = mix(line + glow, 0.35, dense);
23480
+ vec3 lineCol = mix(uLineLow, uLineHigh, smoothstep(-1.5, 2.5, h / max(uHeight, 0.1)));
23481
+ vec3 surf = uFill + lineCol * amount;
23482
+ float fog = 1.0 - exp(-t * 0.035 * uFog);
23483
+ col = mix(surf, sky, clamp(fog * fog + smoothstep(far * 0.7, far, t), 0.0, 1.0));
23484
+ }
23485
+ fragColor = vec4(col, 1.0);
23486
+ }`
23487
+ ),
23488
+ presets: [
23489
+ { name: "Matrix", values: { uSky: [0, 0.02, 0], uHorizon: [0, 0.25, 0.08], uFill: [0, 0.02, 0], uLineLow: [0, 0.6, 0.2], uLineHigh: [0.6, 1, 0.6] } },
23490
+ { name: "Blueprint", values: { uSky: [0.05, 0.15, 0.35], uHorizon: [0.2, 0.4, 0.7], uFill: [0.06, 0.18, 0.4], uLineLow: [0.75, 0.88, 1], uLineHigh: [1, 1, 1], uGlow: 0.3 } }
23491
+ ]
23492
+ };
23493
+ var metaballs3d = {
23494
+ kind: "shader",
23495
+ id: "metaballs-3d",
23496
+ name: "3D metaballs",
23497
+ description: "Glossy 3D metaballs drifting, merging and pulling apart like liquid, reflecting a soft studio around them.",
23498
+ category: "3d",
23499
+ tags: ["3d", "metaballs", "liquid", "blob", "glossy", "chrome", "background"],
23500
+ source: glsl(
23501
+ `// 3D metaballs: spheres joined with a smooth minimum, ray-marched inside their bounding sphere (56 steps
23502
+ // at most), shaded with a procedural studio environment for reflections and a fresnel rim.
23503
+ uniform vec3 uBackground; // @control color default=#0E0B1A label="Background"
23504
+ uniform vec3 uColorA; // @control color default=#FF4FA3 label="Colour A"
23505
+ uniform vec3 uColorB; // @control color default=#6B5BFF label="Colour B"
23506
+ uniform int uCount; // @control min=2 max=8 default=6 label="Balls"
23507
+ uniform float uMerge; // @control min=0.05 max=1 default=0.45 label="Merge"
23508
+ uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"
23509
+ uniform float uReflection; // @control min=0 max=1 default=0.7 label="Reflection"
23510
+ uniform float uSize; // @control min=0.5 max=1.5 default=1 label="Size"`,
23511
+ SPHERE_HIT,
23512
+ `vec3 ballAt(int i) {
23513
+ float fi = float(i);
23514
+ float t = iTime * uSpeed * 0.5;
23515
+ return vec3(sin(t * (0.7 + 0.13 * fi) + fi * 1.7) * 1.1, cos(t * (0.9 + 0.11 * fi) + fi * 2.3) * 0.7, sin(t * (0.5 + 0.07 * fi) + fi * 0.9) * 0.6) * uSize;
23516
+ }
23517
+
23518
+ float smin(float a, float b, float k) {
23519
+ float h = max(k - abs(a - b), 0.0) / k;
23520
+ return min(a, b) - h * h * k * 0.25;
23521
+ }
23522
+
23523
+ float scene(vec3 p) {
23524
+ float d = 1e9;
23525
+ for (int i = 0; i < 8; i++) {
23526
+ if (i >= uCount) break;
23527
+ float r = (0.42 + 0.12 * sin(float(i) * 3.1)) * uSize;
23528
+ d = smin(d, length(p - ballAt(i)) - r, uMerge);
23529
+ }
23530
+ return d;
23531
+ }
23532
+
23533
+ vec3 normalAt(vec3 p) {
23534
+ vec2 e = vec2(1.0, -1.0) * 0.002;
23535
+ return normalize(e.xyy * scene(p + e.xyy) + e.yyx * scene(p + e.yyx) + e.yxy * scene(p + e.yxy) + e.xxx * scene(p + e.xxx));
23536
+ }
23537
+
23538
+ // A studio around the balls: a dim room, a bright softbox overhead and two strip lights.
23539
+ vec3 env(vec3 r) {
23540
+ vec3 c = mix(uBackground * 0.6, uBackground * 2.0 + 0.08, smoothstep(-0.4, 0.6, r.y));
23541
+ c += vec3(2.2) * smoothstep(0.82, 0.9, r.y);
23542
+ float a = atan(r.x, r.z);
23543
+ c += vec3(1.4, 1.3, 1.2) * smoothstep(0.06, 0.0, abs(a - 1.9)) * smoothstep(-0.4, 0.0, r.y) * smoothstep(0.7, 0.3, r.y);
23544
+ c += vec3(0.9, 1.1, 1.4) * smoothstep(0.05, 0.0, abs(a + 2.2)) * smoothstep(-0.3, 0.1, r.y) * smoothstep(0.6, 0.2, r.y);
23545
+ return c;
23546
+ }
23547
+
23548
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
23549
+ vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
23550
+ vec3 ro = vec3(0.0, 0.0, 4.2);
23551
+ vec3 rd = normalize(vec3(p, -1.6));
23552
+ vec3 col = uBackground * (1.2 - 0.6 * length(p));
23553
+ vec2 b = sphereHit(ro, rd, 2.3 * uSize);
23554
+ if (b.x < b.y) {
23555
+ float t = max(b.x, 0.0);
23556
+ bool hit = false;
23557
+ for (int i = 0; i < 56; i++) {
23558
+ float d = scene(ro + rd * t);
23559
+ if (d < 0.001) {
23560
+ hit = true;
23561
+ break;
23562
+ }
23563
+ t += d;
23564
+ if (t > b.y) break;
23565
+ }
23566
+ if (hit) {
23567
+ vec3 pos = ro + rd * t;
23568
+ vec3 n = normalAt(pos);
23569
+ float fres = pow(1.0 - max(dot(n, -rd), 0.0), 4.0);
23570
+ vec3 base = mix(uColorA, uColorB, smoothstep(-0.6, 0.6, n.x * 0.7 + n.y * 0.5 + 0.2 * sin(iTime * 0.3)));
23571
+ vec3 l = normalize(vec3(0.4, 0.8, 0.6));
23572
+ float diff = max(dot(n, l), 0.0);
23573
+ vec3 refl = env(reflect(rd, n));
23574
+ col = base * (0.15 + 0.6 * diff) + refl * mix(0.08, 0.9, uReflection) * mix(0.35, 1.0, fres) + base * fres * 0.3;
23575
+ col += pow(max(dot(reflect(rd, n), l), 0.0), 80.0) * 1.2;
23576
+ }
23577
+ }
23578
+ fragColor = vec4(col / (1.0 + col * 0.15), 1.0);
23579
+ }`
23580
+ ),
23581
+ presets: [
23582
+ { name: "Chrome", values: { uBackground: [0.06, 0.06, 0.08], uColorA: [0.35, 0.36, 0.4], uColorB: [0.55, 0.56, 0.6], uReflection: 1 } },
23583
+ { name: "Candy", values: { uBackground: [1, 0.9, 0.93], uColorA: [1, 0.45, 0.6], uColorB: [1, 0.75, 0.3], uReflection: 0.45 } }
23584
+ ]
23585
+ };
23586
+ var torusKnot = {
23587
+ kind: "shader",
23588
+ id: "torus-knot",
23589
+ name: "Torus knot",
23590
+ description: "A glowing torus knot tube turning slowly, its surface shimmering with iridescent thin-film colour.",
23591
+ category: "3d",
23592
+ tags: ["3d", "knot", "torus", "iridescent", "glow", "abstract", "background"],
23593
+ source: glsl(
23594
+ `// Torus knot: a (p, q) knot's tube as a distance from each of its p strands around the torus, ray-marched
23595
+ // inside a bounding sphere (80 steps at most); the march's near misses add up to a glow around it.
23596
+ uniform vec3 uBackground; // @control color default=#05040B label="Background"
23597
+ uniform vec3 uGlowColor; // @control color default=#7A5CFF label="Glow"
23598
+ uniform int uP; // @control min=1 max=7 default=2 label="Turns around (p)"
23599
+ uniform int uQ; // @control min=1 max=9 default=3 label="Turns through (q)"
23600
+ uniform float uThickness; // @control min=0.05 max=0.35 default=0.16 label="Thickness"
23601
+ uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"
23602
+ uniform float uIridescence; // @control min=0 max=1 default=0.8 label="Iridescence"
23603
+ uniform float uGlow; // @control min=0 max=2 default=1 label="Glow amount"`,
23604
+ SPHERE_HIT,
23605
+ `const float R = 1.0;
23606
+ const float STRAND = 0.45;
23607
+
23608
+ mat3 turn(float a, float b) {
23609
+ float ca = cos(a), sa = sin(a), cb = cos(b), sb = sin(b);
23610
+ return mat3(1.0, 0.0, 0.0, 0.0, cb, sb, 0.0, -sb, cb) * mat3(ca, 0.0, -sa, 0.0, 1.0, 0.0, sa, 0.0, ca);
23611
+ }
23612
+
23613
+ // The tube's distance (an underestimate: the march takes shorter steps), and how far along the knot (0-1).
23614
+ vec2 knot(vec3 p) {
23615
+ float r = length(p.xz);
23616
+ float a = atan(p.z, p.x);
23617
+ vec2 q = vec2(r - R, p.y);
23618
+ float best = 1e9;
23619
+ float along = 0.0;
23620
+ float P = float(uP);
23621
+ for (int k = 0; k < 7; k++) {
23622
+ if (k >= uP) break;
23623
+ float s = (a + 6.28318 * float(k)) / P;
23624
+ float ang = s * float(uQ);
23625
+ float d = length(q - STRAND * vec2(cos(ang), sin(ang)));
23626
+ if (d < best) {
23627
+ best = d;
23628
+ along = s / 6.28318;
23629
+ }
23630
+ }
23631
+ return vec2(best - uThickness, along);
23632
+ }
23633
+
23634
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
23635
+ vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
23636
+ vec3 ro = vec3(0.0, 0.0, 5.2);
23637
+ vec3 rd = normalize(vec3(p, -1.3));
23638
+ float t0 = iTime * uSpeed;
23639
+ mat3 m = turn(t0 * 0.3, 1.15 + 0.25 * sin(t0 * 0.2));
23640
+ vec3 lro = m * ro;
23641
+ vec3 lrd = m * rd;
23642
+ vec3 col = uBackground + uGlowColor * 0.05 * (1.0 - length(p));
23643
+ float glow = 0.0;
23644
+ vec2 b = sphereHit(lro, lrd, R + STRAND + uThickness + 0.05);
23645
+ if (b.x < b.y) {
23646
+ float t = max(b.x, 0.0);
23647
+ bool hit = false;
23648
+ vec2 h = vec2(0.0);
23649
+ for (int i = 0; i < 80; i++) {
23650
+ h = knot(lro + lrd * t);
23651
+ if (h.x < 0.001) {
23652
+ hit = true;
23653
+ break;
23654
+ }
23655
+ // The field stretches where strands wind fast around a small radius: step within its slope.
23656
+ vec3 at = lro + lrd * t;
23657
+ float slope = STRAND * float(uQ) / (float(uP) * max(length(at.xz), 0.3));
23658
+ float dt = h.x / sqrt(1.0 + slope * slope) * 0.9;
23659
+ // Glow by the length of ray spent near the tube, so it does not depend on the step count.
23660
+ glow += exp(-h.x * 9.0) * min(dt, b.y - t) * 0.6;
23661
+ t += dt;
23662
+ if (t > b.y) break;
23663
+ }
23664
+ if (hit) {
23665
+ vec3 pos = lro + lrd * t;
23666
+ vec2 e = vec2(0.002, 0.0);
23667
+ vec3 n = normalize(vec3(knot(pos + e.xyy).x - knot(pos - e.xyy).x, knot(pos + e.yxy).x - knot(pos - e.yxy).x, knot(pos + e.yyx).x - knot(pos - e.yyx).x));
23668
+ float facing = max(dot(n, -lrd), 0.0);
23669
+ // Thin film: the colour shifts with the viewing angle and along the knot.
23670
+ vec3 film = 0.5 + 0.5 * cos(6.28318 * (vec3(0.0, 0.33, 0.67) + (1.0 - facing) * 1.4 + h.y * 2.0 + iTime * 0.05));
23671
+ vec3 base = mix(uGlowColor, film, uIridescence);
23672
+ vec3 l = normalize(m * vec3(0.5, 0.7, 0.6));
23673
+ float diff = max(dot(n, l), 0.0);
23674
+ float spec = pow(max(dot(reflect(lrd, n), l), 0.0), 60.0);
23675
+ float rim = pow(1.0 - facing, 3.0);
23676
+ col = base * (0.18 + 0.75 * diff) + spec * 0.9 + mix(uGlowColor, film, 0.5) * rim * 0.9;
23677
+ }
23678
+ }
23679
+ col += uGlowColor * glow * uGlow;
23680
+ fragColor = vec4(col, 1.0);
23681
+ }`
23682
+ ),
23683
+ presets: [
23684
+ { name: "Trefoil", values: { uP: 2, uQ: 3 } },
23685
+ { name: "Cinquefoil", values: { uP: 2, uQ: 5, uThickness: 0.12 } },
23686
+ { name: "Gold", values: { uP: 3, uQ: 4, uThickness: 0.11, uGlowColor: [1, 0.7, 0.25], uIridescence: 0.3 } }
23687
+ ]
23688
+ };
23689
+ var threeDShaders = [globe, rotatingShapes, isometricCity, wireframeTerrain, metaballs3d, torusKnot];
23690
+
23691
+ // src/catalog/shaders/space-more.ts
23692
+ var STARS2 = `
23693
+ float stars(vec2 fragCoord, float density) {
23694
+ vec2 cell = floor(fragCoord / 3.0);
23695
+ float h = hash12(cell);
23696
+ float on = step(1.0 - 0.012 * density, h);
23697
+ vec2 at = hash22(cell) * 3.0;
23698
+ float d = length(fract(fragCoord / 3.0) * 3.0 - at);
23699
+ return on * smoothstep(1.4, 0.0, d) * (0.4 + 0.6 * hash12(cell + 7.0)) * (0.75 + 0.25 * sin(iTime * (1.0 + 3.0 * h) + h * 40.0));
23700
+ }`.trim();
23701
+ var SPHERE_HIT2 = `
23702
+ vec2 sphereHit(vec3 ro, vec3 rd, float r) {
23703
+ float b = dot(ro, rd);
23704
+ float c = dot(ro, ro) - r * r;
23705
+ float h = b * b - c;
23706
+ if (h < 0.0) return vec2(1e9, -1e9);
23707
+ h = sqrt(h);
23708
+ return vec2(-b - h, -b + h);
23709
+ }`.trim();
23710
+ var planet = {
23711
+ kind: "shader",
23712
+ id: "planet",
23713
+ name: "Planet",
23714
+ description: "A turning planet, banded gas giant or rocky world, lit from one side with a soft terminator, a scattering atmosphere and optional rings.",
23715
+ category: "space",
23716
+ tags: ["space", "planet", "saturn", "rings", "astronomy", "sci-fi", "background"],
23717
+ source: glsl(
23718
+ `// Planet: an analytic sphere on a tilted axis, banded (fbm-warped latitude) or rocky (3D noise), with a
23719
+ // rim of scattered light brighter on the day side, and a ring plane that shadows the planet and is shadowed by it.
23720
+ uniform int uStyle; // @control min=0 max=1 default=0 label="Style (0 gas giant, 1 rocky)"
23721
+ uniform vec3 uBackground; // @control color default=#02030A label="Background"
23722
+ uniform vec3 uColorA; // @control color default=#EBD3A8 label="Colour A"
23723
+ uniform vec3 uColorB; // @control color default=#B0683E label="Colour B"
23724
+ uniform vec3 uAtmosphere; // @control color default=#8DB8FF label="Atmosphere"
23725
+ uniform vec3 uRingColor; // @control color default=#D8C6A2 label="Ring colour"
23726
+ uniform bool uRings; // @control default=true label="Rings"
23727
+ uniform float uSize; // @control min=0.4 max=1.6 default=1 label="Size"
23728
+ uniform float uSpin; // @control min=-1 max=1 default=0.12 label="Spin speed"
23729
+ uniform float uTilt; // @control min=-1 max=1 default=0.42 label="Axis tilt"
23730
+ uniform float uSunAngle; // @control min=-3.1416 max=3.1416 default=0.35 label="Sun direction"
23731
+ uniform float uAtmosphereAmount; // @control min=0 max=2 default=1 label="Atmosphere amount"`,
23732
+ HASH,
23733
+ NOISE_3D,
23734
+ STARS2,
23735
+ SPHERE_HIT2,
23736
+ `const float CAM = 4.2;
23737
+
23738
+ // The planet's axis tilted toward the viewer, and its frame (spin about the axis).
23739
+ mat3 frame(float spin) {
23740
+ float ct = cos(uTilt), st = sin(uTilt);
23741
+ float cv = cos(0.35), sv = sin(0.35);
23742
+ float cs = cos(spin), ss = sin(spin);
23743
+ mat3 roll = mat3(ct, st, 0.0, -st, ct, 0.0, 0.0, 0.0, 1.0);
23744
+ mat3 lean = mat3(1.0, 0.0, 0.0, 0.0, cv, sv, 0.0, -sv, cv);
23745
+ mat3 turn = mat3(cs, 0.0, -ss, 0.0, 1.0, 0.0, ss, 0.0, cs);
23746
+ return lean * roll * turn;
23747
+ }
23748
+
23749
+ vec3 surface(vec3 g) {
23750
+ if (uStyle == 0) {
23751
+ // Bands of latitude, warped by noise, and one great storm.
23752
+ float warp = fbm3(g * vec3(2.0, 7.0, 2.0) + vec3(0.0, 0.0, iTime * 0.02), 4);
23753
+ float v = g.y + 0.09 * (warp - 0.5);
23754
+ float bands = 0.5 + 0.35 * sin(v * 17.0) + 0.15 * sin(v * 43.0 + 1.3);
23755
+ vec3 c = mix(uColorB, uColorA, smoothstep(0.2, 0.8, bands));
23756
+ c *= 0.9 + 0.2 * fbm3(g * vec3(4.0, 24.0, 4.0), 3);
23757
+ vec2 storm = vec2(atan(g.x, g.z) - 0.9, (g.y + 0.38) * 3.2);
23758
+ float s = length(storm * vec2(1.0, 1.6));
23759
+ c = mix(c, mix(uColorB * 1.25, uColorA, 0.25), smoothstep(0.32, 0.18, s) * (0.7 + 0.3 * sin(s * 30.0)));
23760
+ return c;
23761
+ }
23762
+ // Rocky: two kinds of ground by noise, darker basins, polar caps.
23763
+ float n = fbm3(g * 2.4, 5);
23764
+ float fine = fbm3(g * 11.0, 3);
23765
+ vec3 c = mix(uColorB, uColorA, smoothstep(0.35, 0.65, n));
23766
+ c *= 0.75 + 0.45 * fine;
23767
+ c = mix(c, uColorB * 0.45, smoothstep(0.58, 0.72, n) * 0.6);
23768
+ c = mix(c, vec3(0.95), smoothstep(0.86, 0.92, abs(g.y) + fine * 0.08));
23769
+ return c;
23770
+ }
23771
+
23772
+ // How much the rings hold at radius r (in planet radii).
23773
+ float ringDensity(float r) {
23774
+ if (r < 1.35 || r > 2.35) return 0.0;
23775
+ float d = 0.5 + 0.2 * sin(r * 61.0) + 0.15 * sin(r * 23.0 + 1.0) + 0.15 * sin(r * 137.0 + 2.0) * sin(r * 9.0);
23776
+ d *= smoothstep(1.35, 1.45, r) * smoothstep(2.35, 2.2, r);
23777
+ // A dark gap partway out.
23778
+ d *= 1.0 - 0.85 * smoothstep(0.04, 0.0, abs(r - 1.95));
23779
+ return clamp(d, 0.0, 1.0);
23780
+ }
23781
+
23782
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
23783
+ float px = 1.0 / iResolution.y;
23784
+ vec2 p = (fragCoord - 0.5 * iResolution.xy) * px;
23785
+ float f = 1.55 * uSize * (uRings ? 0.52 : 1.0);
23786
+ vec3 ro = vec3(0.0, 0.0, CAM);
23787
+ vec3 rd = normalize(vec3(p, -f));
23788
+ vec3 sun = normalize(vec3(cos(uSunAngle), 0.25, sin(uSunAngle)));
23789
+ mat3 m = frame(iTime * uSpin);
23790
+ vec3 axis = m * vec3(0.0, 1.0, 0.0);
23791
+
23792
+ vec3 col = uBackground + stars(fragCoord, 1.0);
23793
+ vec2 hit = sphereHit(ro, rd, 1.0);
23794
+ float closest = length(ro - rd * dot(ro, rd));
23795
+ // Scattered light beyond the limb, brightest toward the sun.
23796
+ vec2 dir = normalize(p + 1e-5);
23797
+ float sunSide = 0.25 + 0.75 * smoothstep(-0.6, 0.6, dot(dir, normalize(sun.xy + vec2(1e-4))) + sun.z * 0.3);
23798
+ float halo = exp(-max(closest - 1.0, 0.0) * 14.0) * sunSide * uAtmosphereAmount;
23799
+ col += uAtmosphere * halo * 0.7;
23800
+
23801
+ float tPlanet = hit.x < hit.y ? hit.x : 1e9;
23802
+ if (tPlanet < 1e8) {
23803
+ vec3 n = ro + rd * tPlanet;
23804
+ vec3 g = n * m;
23805
+ vec3 c = surface(g);
23806
+ float ndl = dot(n, sun);
23807
+ // A soft terminator, slightly warm where day turns to night.
23808
+ float day = smoothstep(-0.12, 0.35, ndl);
23809
+ float light = max(ndl, 0.0) * 0.85 + day * 0.15;
23810
+ vec3 lit = c * light + c * vec3(1.0, 0.55, 0.35) * 0.12 * smoothstep(0.25, 0.0, abs(ndl + 0.02));
23811
+ // The rings' shadow on the planet.
23812
+ if (uRings) {
23813
+ float tr = -dot(n, axis) / dot(sun, axis);
23814
+ if (tr > 0.0) {
23815
+ vec3 q = n + sun * tr;
23816
+ lit *= 1.0 - 0.75 * ringDensity(length(q));
23817
+ }
23818
+ }
23819
+ float facing = max(dot(n, -rd), 0.0);
23820
+ float rim = pow(1.0 - facing, 2.5) * (0.15 + smoothstep(-0.3, 0.6, ndl)) * uAtmosphereAmount;
23821
+ lit += uAtmosphere * rim * 0.9;
23822
+ float edge = smoothstep(1.0, 1.0 - 2.0 * px * (CAM - 1.0) / f, closest);
23823
+ col = mix(col, lit, edge);
23824
+ }
23825
+
23826
+ if (uRings) {
23827
+ float tr = -dot(ro, axis) / dot(rd, axis);
23828
+ if (tr > 0.0 && tr < tPlanet + (tPlanet < 1e8 ? 0.0 : 1e9)) {
23829
+ vec3 q = ro + rd * tr;
23830
+ float r = length(q);
23831
+ float dens = ringDensity(r);
23832
+ if (dens > 0.0) {
23833
+ // Lit unless the planet stands between the ring and the sun.
23834
+ vec2 sh = sphereHit(q, sun, 1.0);
23835
+ float shadow = sh.x < sh.y && sh.y > 0.0 ? 0.12 : 1.0;
23836
+ float light = 0.35 + 0.65 * abs(dot(sun, axis));
23837
+ vec3 rc = uRingColor * (0.85 + 0.15 * sin(r * 97.0)) * light * shadow;
23838
+ col = mix(col, rc, dens * 0.85);
23839
+ }
23840
+ }
23841
+ }
23842
+ fragColor = vec4(col, 1.0);
23843
+ }`
23844
+ ),
23845
+ presets: [
23846
+ { name: "Gas giant", values: { uStyle: 0, uRings: true } },
23847
+ {
23848
+ name: "Ice giant",
23849
+ values: { uStyle: 0, uRings: false, uColorA: [0.72, 0.9, 0.95], uColorB: [0.32, 0.62, 0.82], uAtmosphere: [0.6, 0.85, 1], uTilt: 0.9 }
23850
+ },
23851
+ {
23852
+ name: "Red planet",
23853
+ values: { uStyle: 1, uRings: false, uColorA: [0.86, 0.5, 0.3], uColorB: [0.45, 0.2, 0.12], uAtmosphere: [1, 0.6, 0.45], uAtmosphereAmount: 0.5 }
23854
+ }
23855
+ ]
23856
+ };
23857
+ var spiralGalaxy = {
23858
+ kind: "shader",
23859
+ id: "spiral-galaxy",
23860
+ name: "Spiral galaxy",
23861
+ description: "A spiral galaxy seen at a tilt, its arms of blue stars and pink nebulae wound around a warm core and laced with dark dust lanes, turning slowly.",
23862
+ category: "space",
23863
+ tags: ["space", "galaxy", "stars", "astronomy", "cosmic", "background"],
23864
+ source: glsl(
23865
+ `// Spiral galaxy: log-spiral arms (cos of arms \xD7 (angle \u2212 winding \xD7 ln r)) on a tilted disc, stars
23866
+ // scattered more thickly along the arms, fbm dust lanes on their inner edges and a bulge at the core.
23867
+ uniform vec3 uBackground; // @control color default=#020208 label="Background"
23868
+ uniform vec3 uCore; // @control color default=#FFE0B0 label="Core"
23869
+ uniform vec3 uArmColor; // @control color default=#8AB0FF label="Arms"
23870
+ uniform vec3 uNebula; // @control color default=#FF6FB0 label="Nebulae"
23871
+ uniform int uArms; // @control min=1 max=6 default=2 label="Arms"
23872
+ uniform float uWinding; // @control min=0.5 max=4 default=2.2 label="Winding"
23873
+ uniform float uTilt; // @control min=0 max=1.4 default=0.95 label="Tilt"
23874
+ uniform float uAngle; // @control min=-3.1416 max=3.1416 default=0.5 label="Angle"
23875
+ uniform float uSize; // @control min=0.4 max=2 default=1 label="Size"
23876
+ uniform float uSpeed; // @control min=-2 max=2 default=0.4 label="Rotation speed"
23877
+ uniform float uDust; // @control min=0 max=1 default=0.7 label="Dust"`,
23878
+ HASH,
23879
+ NOISE,
23880
+ FBM,
23881
+ STARS2,
23882
+ `void mainImage(out vec4 fragColor, in vec2 fragCoord) {
23883
+ float px = 1.0 / iResolution.y;
23884
+ vec2 p = (fragCoord - 0.5 * iResolution.xy) * px;
23885
+ float ca = cos(uAngle), sa = sin(uAngle);
23886
+ vec2 q = mat2(ca, sa, -sa, ca) * p / (0.42 * uSize);
23887
+ // Undo the tilt: the disc is foreshortened along y.
23888
+ q.y /= max(cos(uTilt), 0.08);
23889
+ float r = length(q);
23890
+ float spin = iTime * uSpeed * 0.06;
23891
+ // The disc's own coordinates, turned with the galaxy.
23892
+ vec2 d = mat2(cos(spin), sin(spin), -sin(spin), cos(spin)) * q;
23893
+ float theta = atan(d.y, d.x);
23894
+ float arms = float(uArms);
23895
+ float wind = theta - uWinding * log(max(r, 1e-3));
23896
+ float arm = 0.5 + 0.5 * cos(arms * wind);
23897
+ float armSharp = pow(arm, 3.0);
23898
+ float disc = exp(-r * 2.6) * smoothstep(1.4, 0.6, r);
23899
+
23900
+ // Clumpy light along the arms.
23901
+ float clump = fbm(d * 4.0, 5);
23902
+ float light = disc * (0.25 + 1.4 * armSharp * (0.5 + clump));
23903
+ vec3 col = uBackground + stars(fragCoord, 1.0) * (1.0 - smoothstep(0.0, 0.6, disc * 4.0));
23904
+ col += uArmColor * light * 1.2;
23905
+ // Pink star-forming knots on the arms.
23906
+ float knots = smoothstep(0.62, 0.8, fbm(d * 9.0 + 3.0, 4)) * armSharp * disc;
23907
+ col += uNebula * knots * 2.2;
23908
+ // Stars along the arms, more toward the middle.
23909
+ for (int i = 0; i < 2; i++) {
23910
+ float scale = i == 0 ? 90.0 : 180.0;
23911
+ vec2 g = d * scale * uSize;
23912
+ vec2 cell = floor(g);
23913
+ float h = hash12(cell + float(i) * 31.0);
23914
+ float chance = disc * (0.4 + 2.5 * armSharp) * (i == 0 ? 1.6 : 3.0);
23915
+ float s = step(1.0 - min(chance, 0.9), h);
23916
+ vec2 at = hash22(cell) * 0.6 + 0.2;
23917
+ float dist = length(fract(g) - at) / (scale * uSize * px * 0.42 * uSize);
23918
+ col += mix(uArmColor, vec3(1.0), 0.6) * s * smoothstep(1.6, 0.0, dist) * (0.4 + 0.6 * hash12(cell + 9.0));
23919
+ }
23920
+ // Dust lanes on the arms' inner edges.
23921
+ float lane = pow(0.5 + 0.5 * cos(arms * wind - 0.9), 6.0);
23922
+ float dust = lane * smoothstep(0.08, 0.3, r) * smoothstep(1.2, 0.5, r) * (0.35 + 0.65 * fbm(d * 7.0 + 11.0, 4)) * uDust;
23923
+ col *= 1.0 - 0.85 * clamp(dust * 1.6, 0.0, 1.0);
23924
+ // The bulge.
23925
+ float bulge = exp(-r * r * 30.0) * 1.6 + exp(-r * 6.0) * 0.35;
23926
+ col += uCore * bulge;
23927
+ fragColor = vec4(col / (1.0 + 0.25 * col), 1.0);
23928
+ }`
23929
+ ),
23930
+ presets: [
23931
+ { name: "Face on", values: { uTilt: 0.15, uWinding: 2.6 } },
23932
+ { name: "Edge on", values: { uTilt: 1.3, uAngle: 0.15 } },
23933
+ { name: "Four arms", values: { uArms: 4, uWinding: 1.6, uArmColor: [0.75, 0.6, 1], uNebula: [1, 0.45, 0.6] } }
23934
+ ]
23935
+ };
23936
+ var solarSystem = {
23937
+ kind: "shader",
23938
+ id: "solar-system",
23939
+ name: "Solar system",
23940
+ description: "A stylised solar system seen at a tilt: planets circling a glowing sun on their orbit lines, the outer ones slower, one with rings.",
23941
+ category: "space",
23942
+ tags: ["space", "solar system", "planets", "orbit", "astronomy", "sun", "background"],
23943
+ source: glsl(
23944
+ `// Solar system: planets as analytic spheres on circular orbits in a plane seen at a tilt, orbit lines where
23945
+ // each pixel's ray meets that plane (anti-aliased with fwidth), lit from the sun at the centre.
23946
+ uniform vec3 uBackground; // @control color default=#03040B label="Background"
23947
+ uniform vec3 uSun; // @control color default=#FFB443 label="Sun"
23948
+ uniform vec3 uOrbit; // @control color default=#7C8BB8 label="Orbit lines"
23949
+ uniform int uPlanets; // @control min=1 max=8 default=8 label="Planets"
23950
+ uniform float uSpeed; // @control min=0 max=4 default=1 label="Speed"
23951
+ uniform float uTilt; // @control min=0.1 max=1.5 default=0.5 label="Tilt"
23952
+ uniform float uZoom; // @control min=0.5 max=2 default=1 label="Zoom"
23953
+ uniform float uOrbitOpacity; // @control min=0 max=1 default=0.45 label="Orbit opacity"
23954
+ uniform float uPlanetSize; // @control min=0.5 max=2 default=1 label="Planet size"`,
23955
+ HASH,
23956
+ NOISE_3D,
23957
+ STARS2,
23958
+ SPHERE_HIT2,
23959
+ `const vec3 COLOUR[8] = vec3[8](vec3(0.66, 0.62, 0.58), vec3(0.95, 0.78, 0.5), vec3(0.3, 0.55, 0.95), vec3(0.85, 0.42, 0.25),
23960
+ vec3(0.86, 0.72, 0.55), vec3(0.92, 0.82, 0.6), vec3(0.6, 0.88, 0.92), vec3(0.32, 0.45, 0.95));
23961
+ const float SIZE[8] = float[8](0.035, 0.05, 0.055, 0.042, 0.11, 0.095, 0.07, 0.068);
23962
+
23963
+ float orbitRadius(int i) {
23964
+ return 0.42 + float(i) * 0.2 + float(i) * float(i) * 0.008;
23965
+ }
23966
+
23967
+ vec3 planetAt(int i) {
23968
+ float r = orbitRadius(i);
23969
+ float a = iTime * uSpeed * 0.6 / pow(r / 0.42, 1.5) + float(i) * 2.4 + 0.7;
23970
+ return vec3(cos(a), 0.0, sin(a)) * r;
23971
+ }
23972
+
23973
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
23974
+ float px = 1.0 / iResolution.y;
23975
+ vec2 p = (fragCoord - 0.5 * iResolution.xy) * px;
23976
+ float dist = 4.6 / uZoom;
23977
+ vec3 ro = vec3(0.0, sin(uTilt), cos(uTilt)) * dist;
23978
+ vec3 fw = normalize(-ro);
23979
+ vec3 rt = normalize(cross(fw, vec3(0.0, 1.0, 0.0)));
23980
+ vec3 up = cross(rt, fw);
23981
+ vec3 rd = normalize(p.x * rt + p.y * up + 1.8 * fw);
23982
+
23983
+ vec3 col = uBackground + stars(fragCoord, 1.0);
23984
+ float sunR = 0.17;
23985
+
23986
+ // The nearest planet along the ray.
23987
+ float tBest = 1e9;
23988
+ int best = -1;
23989
+ for (int i = 0; i < 8; i++) {
23990
+ if (i >= uPlanets) break;
23991
+ vec2 h = sphereHit(ro - planetAt(i), rd, SIZE[i] * uPlanetSize);
23992
+ if (h.x < h.y && h.x > 0.0 && h.x < tBest) {
23993
+ tBest = h.x;
23994
+ best = i;
23995
+ }
23996
+ }
23997
+ vec2 sunHit = sphereHit(ro, rd, sunR);
23998
+ float tSun = sunHit.x < sunHit.y ? sunHit.x : 1e9;
23999
+
24000
+ // Orbit lines, where the ray meets the plane in front of anything solid.
24001
+ float tPlane = -ro.y / rd.y;
24002
+ if (tPlane > 0.0 && tPlane < min(tBest, tSun)) {
24003
+ vec3 q = ro + rd * tPlane;
24004
+ float r = length(q.xz);
24005
+ float w = fwidth(r);
24006
+ float lines = 0.0;
24007
+ for (int i = 0; i < 8; i++) {
24008
+ if (i >= uPlanets) break;
24009
+ lines = max(lines, 1.0 - smoothstep(0.5, 1.5, abs(r - orbitRadius(i)) / max(w, 1e-5)));
24010
+ }
24011
+ col += uOrbit * lines * uOrbitOpacity;
24012
+ }
24013
+
24014
+ if (best >= 0 && tBest < tSun) {
24015
+ vec3 c = planetAt(best);
24016
+ vec3 pos = ro + rd * tBest;
24017
+ vec3 n = normalize(pos - c);
24018
+ vec3 l = normalize(-c);
24019
+ vec3 base = COLOUR[best];
24020
+ // Gas giants get bands.
24021
+ if (best >= 4) base *= 0.85 + 0.25 * sin(n.y * 26.0 + float(best) + 2.0 * vnoise3(n * 4.0));
24022
+ if (best == 2) base = mix(base, vec3(0.3, 0.6, 0.3), smoothstep(0.5, 0.6, vnoise3(n * 3.0 + 2.0)));
24023
+ float diff = max(dot(n, l), 0.0);
24024
+ col = base * (0.04 + 0.96 * diff) + uSun * pow(1.0 - max(dot(n, -rd), 0.0), 3.0) * diff * 0.4;
24025
+ } else if (tSun < 1e8) {
24026
+ vec3 n = normalize(ro + rd * tSun);
24027
+ float boil = fbm3(n * 6.0 + vec3(0.0, iTime * 0.15, 0.0), 4);
24028
+ float limb = max(dot(n, -rd), 0.0);
24029
+ col = uSun * (0.7 + 0.8 * boil) * (0.55 + 0.45 * limb) + vec3(1.0, 0.95, 0.8) * pow(limb, 3.0) * 0.5;
24030
+ }
24031
+
24032
+ // Saturn's rings (the sixth planet), drawn over whatever lies behind them.
24033
+ if (uPlanets >= 6) {
24034
+ vec3 c = planetAt(5);
24035
+ float t = -ro.y / rd.y;
24036
+ if (t > 0.0 && t < min(tSun, best == 5 ? tBest + 0.0 : 1e9)) {
24037
+ float r = length((ro + rd * t) - c) / (SIZE[5] * uPlanetSize);
24038
+ float ring = smoothstep(1.35, 1.45, r) * smoothstep(2.3, 2.15, r) * (0.6 + 0.4 * sin(r * 40.0));
24039
+ if (best != 5 || t < tBest) col = mix(col, COLOUR[5] * 0.95, ring * 0.75);
24040
+ }
24041
+ }
24042
+
24043
+ // The sun's glow over everything.
24044
+ float closest = length(ro - rd * dot(ro, rd));
24045
+ col += uSun * exp(-max(closest - sunR, 0.0) * 9.0) * 0.6;
24046
+ col += uSun * exp(-max(closest - sunR, 0.0) * 40.0) * 0.6;
24047
+ fragColor = vec4(col, 1.0);
24048
+ }`
24049
+ ),
24050
+ presets: [
24051
+ { name: "Top down", values: { uTilt: 1.45, uZoom: 0.9 } },
24052
+ { name: "Inner planets", values: { uPlanets: 4, uZoom: 1.9, uPlanetSize: 1.6 } }
24053
+ ]
24054
+ };
24055
+ var spaceShaders = [planet, spiralGalaxy, solarSystem];
24056
+
24057
+ // src/catalog/shaders/weather.ts
24058
+ var rainStreaks = {
24059
+ kind: "shader",
24060
+ id: "rain-streaks",
24061
+ name: "Rain streaks",
24062
+ description: "Rain falling in slanted streaks at several depths, splashing where it meets a ground line, to lay over any scene.",
24063
+ category: "weather",
24064
+ tags: ["weather", "rain", "storm", "overlay", "particles", "moody"],
24065
+ transparent: true,
24066
+ source: glsl(
24067
+ `// Rain streaks: layers of streaks in sheared grid cells (far layers finer, slower and fainter), and splashes
24068
+ // (a ring and a few flying droplets) in cells along the ground line.
24069
+ uniform vec3 uColor; // @control color default=#D4E4FF label="Colour"
24070
+ uniform float uDensity; // @control min=0 max=1 default=0.55 label="Density"
24071
+ uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"
24072
+ uniform float uWind; // @control min=-1 max=1 default=0.18 label="Wind (slant)"
24073
+ uniform float uLength; // @control min=0.2 max=3 default=1 label="Streak length"
24074
+ uniform int uLayers; // @control min=1 max=5 default=4 label="Layers"
24075
+ uniform float uGround; // @control min=0 max=0.6 default=0.1 label="Ground line"
24076
+ uniform float uSplashes; // @control min=0 max=1 default=0.8 label="Splashes"
24077
+ uniform float uOpacity; // @control min=0 max=1 default=0.75 label="Opacity"`,
24078
+ HASH,
24079
+ `void mainImage(out vec4 fragColor, in vec2 fragCoord) {
24080
+ float px = 1.0 / iResolution.y;
24081
+ vec2 p = fragCoord * px;
24082
+ float a = 0.0;
24083
+ for (int i = 0; i < 5; i++) {
24084
+ if (i >= uLayers) break;
24085
+ float depth = (float(i) + 1.0) / float(uLayers);
24086
+ // Columns per screen height, and how long a cell is (in columns).
24087
+ float cols = mix(70.0, 18.0, depth);
24088
+ float cellH = 6.0;
24089
+ // Shear so streaks slant with the wind.
24090
+ vec2 q = vec2(p.x - (p.y - uGround) * uWind, p.y);
24091
+ float fall = iTime * uSpeed * mix(1.4, 3.2, depth);
24092
+ float column = floor(q.x * cols + float(i) * 7.3);
24093
+ // Each column's cells start at a height of their own, so streaks never line up in rows.
24094
+ vec2 g = vec2(q.x * cols + float(i) * 7.3, (q.y + fall) * cols / cellH + hash12(vec2(column, float(i))));
24095
+ vec2 cell = floor(g);
24096
+ vec2 f = fract(g);
24097
+ float h = hash12(cell + float(i) * 13.1);
24098
+ if (h > uDensity) continue;
24099
+ float x = 0.2 + 0.6 * hash12(cell + 3.3);
24100
+ float len = clamp(0.35 * uLength * mix(0.6, 1.0, depth) * (0.7 + 0.6 * hash12(cell + 5.7)), 0.05, 0.95);
24101
+ float top = 0.05 + (0.95 - len) * hash12(cell + 9.1);
24102
+ // Distance across the streak in pixels (a column is iResolution.y / cols pixels wide).
24103
+ float across = abs(f.x - x) / (px * cols);
24104
+ float width = mix(0.5, 1.4, depth);
24105
+ float along = (f.y - top) / len;
24106
+ float body = step(0.0, along) * step(along, 1.0) * pow(1.0 - along, 0.6) * smoothstep(0.0, 0.15, along);
24107
+ float streak = (1.0 - smoothstep(width * 0.5, width * 0.5 + 1.0, across)) * body;
24108
+ // Rain stops at the ground.
24109
+ streak *= step(uGround, p.y);
24110
+ a = max(a, streak * mix(0.25, 0.9, depth));
24111
+ }
24112
+ // Splashes along the ground: a flattened ring spreading and droplets thrown up.
24113
+ if (uSplashes > 0.0) {
24114
+ for (int k = 0; k < 2; k++) {
24115
+ float w = 0.045;
24116
+ float sx = p.x / w + float(k) * 0.5;
24117
+ float cell = floor(sx);
24118
+ float period = 0.45 + 0.5 * hash12(vec2(cell, float(k)));
24119
+ float time = iTime * uSpeed + hash12(vec2(cell, 7.0 + float(k))) * period;
24120
+ float cycle = floor(time / period);
24121
+ float age = fract(time / period) * period;
24122
+ if (hash12(vec2(cell, cycle + float(k) * 19.0)) > uSplashes * uDensity * 1.4) continue;
24123
+ vec2 centre = vec2((cell + 0.25 + 0.5 * hash12(vec2(cell, cycle + 2.0)) - float(k) * 0.5) * w, uGround);
24124
+ vec2 d = p - centre;
24125
+ float life = smoothstep(0.25, 0.0, age);
24126
+ // The ring, flattened by perspective.
24127
+ float r = 0.004 + age * 0.08;
24128
+ float ring = abs(length(d * vec2(1.0, 3.5)) - r);
24129
+ a = max(a, (1.0 - smoothstep(0.6 * px, 1.8 * px, ring)) * life * 0.7 * step(-0.003, d.y));
24130
+ // Droplets.
24131
+ for (int j = 0; j < 3; j++) {
24132
+ float fj = float(j);
24133
+ float vx = (fj - 1.0) * 0.12 + (hash12(vec2(cell, fj + cycle)) - 0.5) * 0.06;
24134
+ float vy = 0.22 + 0.12 * hash12(vec2(cell + 4.0, fj + cycle));
24135
+ vec2 at = centre + vec2(vx * age, vy * age - 2.2 * age * age);
24136
+ float drop = 1.0 - smoothstep(0.8 * px, 2.0 * px, length(p - at));
24137
+ a = max(a, drop * life * step(uGround - 0.002, at.y) * 0.9);
24138
+ }
24139
+ }
24140
+ }
24141
+ fragColor = vec4(uColor, a * uOpacity);
24142
+ }`
24143
+ ),
24144
+ presets: [
24145
+ { name: "Drizzle", values: { uDensity: 0.25, uSpeed: 0.7, uLength: 0.6, uWind: 0.05, uOpacity: 0.55 } },
24146
+ { name: "Downpour", values: { uDensity: 0.9, uSpeed: 1.6, uLength: 1.6, uWind: 0.35, uLayers: 5 } },
24147
+ { name: "No splashes", values: { uSplashes: 0, uGround: 0 } }
24148
+ ]
24149
+ };
24150
+ var thunderstorm = {
24151
+ kind: "shader",
24152
+ id: "thunderstorm",
24153
+ name: "Thunderstorm",
24154
+ description: "Dark, roiling storm clouds lit from inside by flickering lightning, with bolts to the ground and driving rain.",
24155
+ category: "weather",
24156
+ tags: ["weather", "storm", "lightning", "clouds", "rain", "dramatic", "background"],
24157
+ source: glsl(
24158
+ `// Thunderstorm: domain-warped fbm clouds, flashes in random slots that light the clouds around a point
24159
+ // (more where they are thin), a jagged bolt to the ground for some, and two layers of rain.
24160
+ uniform vec3 uSky; // @control color default=#10131C label="Sky"
24161
+ uniform vec3 uCloud; // @control color default=#454C5E label="Cloud"
24162
+ uniform vec3 uFlash; // @control color default=#D6DEFF label="Flash"
24163
+ uniform vec3 uRainColor; // @control color default=#9AA8C4 label="Rain"
24164
+ uniform float uFrequency; // @control min=0 max=2 default=0.5 label="Flashes per second"
24165
+ uniform float uRain; // @control min=0 max=1 default=0.6 label="Rain"
24166
+ uniform float uSpeed; // @control min=0 max=3 default=1 label="Cloud speed"
24167
+ uniform bool uBolts; // @control default=true label="Bolts"`,
24168
+ HASH,
24169
+ NOISE,
24170
+ FBM,
24171
+ `// The flash in slot k: its brightness now, where (x), and whether it strikes the ground.
24172
+ vec3 flashAt(float slot, float t) {
24173
+ float h = hash12(vec2(slot, 1.7));
24174
+ if (h > 0.7) return vec3(0.0);
24175
+ float start = hash12(vec2(slot, 4.1)) * 0.5;
24176
+ float tau = t - start;
24177
+ if (tau < 0.0) return vec3(0.0);
24178
+ // A few flickers: a bright strike, a dip, a restrike, then fading.
24179
+ float b = exp(-tau * 9.0) + 0.7 * exp(-max(tau - 0.12, 0.0) * 12.0) * step(0.12, tau) + 0.4 * exp(-max(tau - 0.3, 0.0) * 6.0) * step(0.3, tau);
24180
+ return vec3(b, hash12(vec2(slot, 9.3)), step(0.45, hash12(vec2(slot, 2.2))));
24181
+ }
24182
+
24183
+ float rain(vec2 p, float cols, float speed, float slant, float seed) {
24184
+ vec2 q = vec2(p.x + p.y * slant, p.y + iTime * speed);
24185
+ vec2 g = vec2(q.x * cols + seed, q.y * cols / 8.0);
24186
+ vec2 c = floor(g);
24187
+ vec2 f = fract(g);
24188
+ if (hash12(c + seed) > uRain) return 0.0;
24189
+ float x = 0.2 + 0.6 * hash12(c + 1.3);
24190
+ float across = abs(f.x - x) * iResolution.y / cols;
24191
+ float y0 = hash12(c + 2.9) * 0.5;
24192
+ float body = smoothstep(y0, y0 + 0.1, f.y) * smoothstep(y0 + 0.5, y0 + 0.3, f.y);
24193
+ return (1.0 - smoothstep(0.4, 1.4, across)) * body;
24194
+ }
24195
+
24196
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
24197
+ float px = 1.0 / iResolution.y;
24198
+ vec2 uv = fragCoord / iResolution.xy;
24199
+ vec2 p = fragCoord * px;
24200
+ float aspect = iResolution.x / iResolution.y;
24201
+ float t = iTime * uSpeed * 0.05;
24202
+
24203
+ // Clouds: warped fbm, thickest overhead, with a ragged base.
24204
+ vec2 cp = vec2(p.x, p.y * 1.6) * 1.6;
24205
+ vec2 warp = vec2(fbm(cp + vec2(t, 0.0), 4), fbm(cp + vec2(5.2, 1.3) - vec2(0.0, t * 0.7), 4));
24206
+ float dens = fbm(cp + 1.8 * warp + vec2(t * 2.0, 0.0), 5);
24207
+ float base = smoothstep(0.25, 0.75, uv.y + 0.35 * (dens - 0.5));
24208
+ float cloud = clamp(smoothstep(0.35, 0.68, dens) * 0.75 + base * 0.6, 0.0, 1.0);
24209
+ // Light from above: thick cloud is dark below.
24210
+ float shade = fbm(cp + 1.8 * warp + vec2(t * 2.0, 0.03), 5) - dens;
24211
+ vec3 col = mix(uSky * 0.7, uCloud * clamp(0.55 + 5.0 * shade + 0.45 * uv.y, 0.15, 1.6), cloud);
24212
+
24213
+ // Lightning: one possible flash in each slot.
24214
+ float slotLen = 1.0 / max(uFrequency, 0.01);
24215
+ float slot = floor(iTime / slotLen);
24216
+ vec3 fl = flashAt(slot, iTime - slot * slotLen);
24217
+ if (uFrequency > 0.0 && fl.x > 0.002) {
24218
+ vec2 at = vec2((0.1 + 0.8 * fl.y) * aspect, 0.72 + 0.15 * hash12(vec2(slot, 6.6)));
24219
+ float d = length(p - at);
24220
+ float glow = exp(-d * 3.2);
24221
+ // Light scatters through the cloud: brightest at the source, more where the cloud is thinner.
24222
+ col += uFlash * fl.x * glow * (0.35 + 0.9 * cloud * (1.2 - dens)) * 1.6;
24223
+ col += uFlash * fl.x * 0.12;
24224
+ if (uBolts && fl.z > 0.5) {
24225
+ // A jagged bolt from the cloud base to the ground, with one fork.
24226
+ float y = p.y;
24227
+ float jag = (fbm(vec2(y * 5.0, slot * 3.1), 4) - 0.5) * 0.35 + (vnoise(vec2(y * 40.0, slot)) - 0.5) * 0.03;
24228
+ float x = at.x + jag;
24229
+ float dx = abs(p.x - x);
24230
+ float span = step(y, at.y) * smoothstep(-0.02, 0.05, y);
24231
+ float core = (1.0 - smoothstep(0.6 * px, 2.2 * px, dx)) * span;
24232
+ float halo = exp(-dx * 60.0) * span * 0.5;
24233
+ float forkY = at.y - 0.2 - 0.15 * hash12(vec2(slot, 8.8));
24234
+ float fx = x + (forkY - y) * (0.3 * sign(hash12(vec2(slot, 3.3)) - 0.5)) + (vnoise(vec2(y * 30.0, slot + 9.0)) - 0.5) * 0.04;
24235
+ float fork = (1.0 - smoothstep(0.5 * px, 1.6 * px, abs(p.x - fx))) * step(y, forkY) * step(forkY - 0.18, y) * smoothstep(forkY - 0.18, forkY - 0.1, y);
24236
+ float strike = smoothstep(0.25, 0.6, fl.x);
24237
+ col += (vec3(1.0) * (core + fork * 0.8) + uFlash * halo) * strike * 1.5;
24238
+ }
24239
+ }
24240
+ // The land: a dark ridge lit by the flash.
24241
+ float ridge = 0.08 + 0.04 * fbm(vec2(p.x * 3.0, 1.0), 4);
24242
+ col = mix(col, uSky * 0.35 + uFlash * fl.x * 0.15, 1.0 - smoothstep(ridge - px, ridge + px, p.y));
24243
+ // Rain.
24244
+ float r = max(rain(p, 60.0, 2.6, 0.12, 0.0) * 0.5, rain(p, 24.0, 3.6, 0.15, 17.0));
24245
+ col = mix(col, uRainColor * (1.0 + fl.x), r * 0.3);
24246
+ fragColor = vec4(col, 1.0);
24247
+ }`
24248
+ ),
24249
+ presets: [
24250
+ { name: "Distant storm", values: { uFrequency: 0.25, uRain: 0.2, uBolts: false } },
24251
+ { name: "Supercell", values: { uSky: [0.08, 0.1, 0.1], uCloud: [0.3, 0.38, 0.36], uFlash: [0.85, 0.8, 1], uFrequency: 1.2, uRain: 0.9, uSpeed: 2 } }
24252
+ ]
24253
+ };
24254
+ var rainbow = {
24255
+ kind: "shader",
24256
+ id: "rainbow",
24257
+ name: "Rainbow",
24258
+ description: "A soft rainbow arc fading in over a picture as if after rain, with a faint second bow and optional sun rays.",
24259
+ category: "weather",
24260
+ video: "filter",
24261
+ tags: ["weather", "rainbow", "image", "light", "hopeful", "spring"],
24262
+ source: glsl(
24263
+ `// Rainbow: a primary bow (red outside, violet inside) and a fainter reversed secondary over the picture,
24264
+ // brighter inside the primary and darker between them, fading in over time, with optional sun rays.
24265
+ uniform sampler2D uImage; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.landscape} label="Image"
24266
+ uniform vec2 uCentre; // @control min=-1 max=2 default=0.5,-0.35 label="Bow centre"
24267
+ uniform float uRadius; // @control min=0.2 max=2 default=0.95 label="Radius"
24268
+ uniform float uWidth; // @control min=0.01 max=0.3 default=0.07 label="Width"
24269
+ uniform float uStrength; // @control min=0 max=1.5 default=0.65 label="Strength"
24270
+ uniform float uSecondary; // @control min=0 max=1 default=0.35 label="Second bow"
24271
+ uniform float uDelay; // @control min=0 max=10 default=0.5 label="Fade-in delay (s)"
24272
+ uniform float uFade; // @control min=0 max=10 default=3 label="Fade-in time (s)"
24273
+ uniform float uRays; // @control min=0 max=1 default=0.35 label="Sun rays"
24274
+ uniform vec2 uSun; // @control min=-0.5 max=1.5 default=0.1,1.05 label="Sun position"
24275
+ ${BACKDROP_CONTROLS}`,
24276
+ HASH,
24277
+ NOISE,
24278
+ PICTURE,
24279
+ `// Spectrum across a bow: 0 inside (violet) to 1 outside (red), soft at both edges.
24280
+ vec3 spectrum(float x) {
24281
+ float h = (1.0 - x) * 0.78;
24282
+ vec3 c = clamp(abs(fract(h + vec3(0.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - 3.0) - 1.0, 0.0, 1.0);
24283
+ return c * smoothstep(0.0, 0.18, x) * smoothstep(1.0, 0.82, x);
24284
+ }
24285
+
24286
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
24287
+ vec2 uv = fragCoord / iResolution.xy;
24288
+ float aspect = iResolution.x / iResolution.y;
24289
+ vec4 pic = pictureRGBA(uImage, uv);
24290
+ vec2 p = vec2(uv.x * aspect, uv.y);
24291
+ vec2 c = vec2(uCentre.x * aspect, uCentre.y);
24292
+ float r = length(p - c);
24293
+ float appear = uFade <= 0.0 ? step(uDelay, iTime) : smoothstep(uDelay, uDelay + uFade, iTime);
24294
+ // Bows fade toward the ground and shimmer faintly.
24295
+ float ground = smoothstep(0.0, 0.35, uv.y);
24296
+ float shimmer = 0.85 + 0.15 * vnoise(vec2(atan(p.y - c.y, p.x - c.x) * 6.0, iTime * 0.3));
24297
+ float x1 = (r - (uRadius - uWidth)) / uWidth;
24298
+ vec3 bow = spectrum(x1);
24299
+ float r2 = uRadius * 1.27;
24300
+ float x2 = 1.0 - (r - r2) / (uWidth * 1.6);
24301
+ bow += spectrum(x2) * uSecondary * 0.6;
24302
+ // Brighter sky inside the primary, darker between the bows.
24303
+ float inside = smoothstep(uRadius - uWidth, uRadius - uWidth - 0.25, r) * step(0.0, uRadius - uWidth - r) * 0.12;
24304
+ float between = smoothstep(uRadius, uRadius + 0.02, r) * smoothstep(r2 - uWidth * 1.6, r2 - uWidth * 1.6 - 0.02, r) * 0.08;
24305
+ float amount = appear * ground * shimmer * uStrength;
24306
+ vec3 col = pic.rgb;
24307
+ col = 1.0 - (1.0 - col) * (1.0 - bow * amount);
24308
+ col += inside * amount * 0.8;
24309
+ col *= 1.0 - between * amount;
24310
+ // Sun rays fanning from the sun.
24311
+ if (uRays > 0.0) {
24312
+ vec2 s = vec2(uSun.x * aspect, uSun.y);
24313
+ vec2 d = p - s;
24314
+ float ang = atan(d.y, d.x);
24315
+ float rays = vnoise(vec2(ang * 9.0, iTime * 0.15)) * vnoise(vec2(ang * 23.0 + 4.0, iTime * 0.1));
24316
+ float fall = exp(-length(d) * 1.4);
24317
+ col += vec3(1.0, 0.93, 0.75) * (smoothstep(0.15, 0.6, rays) * 0.7 + 0.3) * fall * uRays * appear * 0.7;
24318
+ }
24319
+ fragColor = vec4(min(col, 1.0), pic.a);
24320
+ }`
24321
+ ),
24322
+ presets: [
24323
+ { name: "Double rainbow", values: { uSecondary: 1, uStrength: 0.9, uWidth: 0.08 } },
24324
+ { name: "Low and wide", values: { uCentre: [0.5, -0.8], uRadius: 1.35, uWidth: 0.09, uRays: 0 } }
24325
+ ]
24326
+ };
24327
+ var windGust = {
24328
+ kind: "shader",
24329
+ id: "wind-gust",
24330
+ name: "Wind gust",
24331
+ description: "Gusts of wind sweeping across: curling wind lines with leaves and dust tumbling along, to lay over any scene.",
24332
+ category: "weather",
24333
+ tags: ["weather", "wind", "leaves", "autumn", "overlay", "particles"],
24334
+ transparent: true,
24335
+ source: glsl(
24336
+ `// Wind gust: wind lines (curving segments sliding along rows), leaves (tumbling ellipses with a vein) and
24337
+ // dust carried in cells by the wind's integrated push, all swelling with each gust.
24338
+ uniform vec3 uLineColor; // @control color default=#FFFFFF label="Wind lines"
24339
+ uniform vec3 uLeafA; // @control color default=#E0892E label="Leaf colour A"
24340
+ uniform vec3 uLeafB; // @control color default=#9BB13E label="Leaf colour B"
24341
+ uniform vec3 uDustColor; // @control color default=#D6C5A4 label="Dust"
24342
+ uniform float uStrength; // @control min=0 max=2 default=1 label="Strength"
24343
+ uniform float uDirection; // @control min=-1 max=1 default=1 step=2 label="Direction"
24344
+ uniform float uGustRate; // @control min=0.05 max=1 default=0.25 label="Gusts per second"
24345
+ uniform float uLines; // @control min=0 max=1 default=0.7 label="Wind lines"
24346
+ uniform float uLeaves; // @control min=0 max=1 default=0.5 label="Leaves"
24347
+ uniform float uDust; // @control min=0 max=1 default=0.5 label="Dust"`,
24348
+ HASH,
24349
+ `const float TAU = 6.28318;
24350
+
24351
+ // The gust at time t: 0.25 calm to 1 at its peak.
24352
+ float gust(float t) {
24353
+ float s = 0.5 + 0.5 * sin(t * uGustRate * TAU);
24354
+ return 0.25 + 0.75 * s * s;
24355
+ }
24356
+ // How far the wind has pushed by time t (the integral of gust: s\xB2 = 3/8 + sin/2 \u2212 cos(2x)/8).
24357
+ float pushed(float t) {
24358
+ float w = uGustRate * TAU;
24359
+ float x = t * w;
24360
+ return 0.25 * t + 0.75 * (0.375 * t - 0.5 * cos(x) / w - 0.125 * sin(2.0 * x) / (2.0 * w));
24361
+ }
24362
+
24363
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
24364
+ float px = 1.0 / iResolution.y;
24365
+ vec2 p = fragCoord * px;
24366
+ float dir = uDirection < 0.0 ? -1.0 : 1.0;
24367
+ p.x *= dir;
24368
+ float aspect = iResolution.x / iResolution.y;
24369
+ vec4 acc = vec4(0.0);
24370
+
24371
+ // Wind lines: each row holds a curved segment sliding fast along it, shown while the gust passes.
24372
+ if (uLines > 0.0) {
24373
+ for (int k = 0; k < 2; k++) {
24374
+ float rows = k == 0 ? 9.0 : 15.0;
24375
+ float y = p.y * rows + float(k) * 0.5;
24376
+ float row = floor(y);
24377
+ float h = hash12(vec2(row, float(k)));
24378
+ float speed = (1.2 + 0.8 * h) * uStrength;
24379
+ float lag = iTime - p.x * 0.35;
24380
+ float g = gust(lag);
24381
+ float u = p.x * 0.7 - iTime * speed * 0.6 - h * 9.0;
24382
+ float seg = floor(u);
24383
+ float fu = fract(u);
24384
+ if (hash12(vec2(seg, row + float(k) * 5.0)) > uLines * 0.7) continue;
24385
+ float len = 0.25 + 0.3 * hash12(vec2(seg, row + 1.0));
24386
+ float along = fu / len;
24387
+ if (along > 1.0) continue;
24388
+ // A gentle wave and, now and then, a curl at the head.
24389
+ float curve = 0.25 * sin(p.x * 5.0 + h * 6.0 + iTime * 0.5) + 0.12 * sin(p.x * 13.0 + h * 3.0);
24390
+ float curl = step(0.75, hash12(vec2(seg, row + 3.0))) * smoothstep(0.65, 1.0, along) * 0.45 * sin((along - 0.65) * 14.0);
24391
+ float centre = row + 0.5 + curve + curl;
24392
+ float d = abs(y - centre) / (rows * px);
24393
+ float taper = smoothstep(0.0, 0.3, along) * smoothstep(1.0, 0.75, along);
24394
+ float line = (1.0 - smoothstep(0.9, 2.2, d)) * taper * mix(0.25, 1.0, smoothstep(0.3, 0.9, g)) * (k == 0 ? 0.85 : 0.5);
24395
+ acc.rgb = mix(acc.rgb, uLineColor, line);
24396
+ acc.a = max(acc.a, line);
24397
+ }
24398
+ }
24399
+
24400
+ // Leaves and dust carried by the wind's push.
24401
+ float push = pushed(iTime) * uStrength;
24402
+ for (int k = 0; k < 3; k++) {
24403
+ bool leaf = k < 2;
24404
+ float amount = leaf ? uLeaves : uDust;
24405
+ if (amount <= 0.0) continue;
24406
+ float scale = k == 0 ? 5.0 : k == 1 ? 8.0 : 22.0;
24407
+ float speed = k == 0 ? 0.9 : k == 1 ? 0.65 : 1.2;
24408
+ vec2 q = p * scale;
24409
+ q.x -= push * speed * scale * 0.55;
24410
+ q.y += sin(iTime * 0.8 + q.x * 0.3) * 0.3 + iTime * 0.15 * scale * 0.1;
24411
+ vec2 cell = floor(q);
24412
+ float h = hash12(cell + float(k) * 11.0);
24413
+ if (h > amount * (leaf ? 0.45 : 0.5)) continue;
24414
+ vec2 at = 0.25 + 0.5 * hash22(cell + float(k));
24415
+ // Tumbling and bobbing within the cell.
24416
+ float spin = iTime * (1.5 + 3.0 * hash12(cell + 2.0)) * (0.5 + gust(iTime)) + h * 20.0;
24417
+ at += 0.15 * vec2(sin(spin * 0.7), cos(spin * 0.9));
24418
+ vec2 d = fract(q) - at;
24419
+ if (leaf) {
24420
+ float c = cos(spin), s = sin(spin);
24421
+ d = mat2(c, s, -s, c) * d;
24422
+ // The tumble foreshortens the leaf.
24423
+ d.y /= max(abs(sin(spin * 0.6)), 0.25);
24424
+ float size = 0.18 + 0.08 * hash12(cell + 4.0);
24425
+ vec2 e = d / vec2(size, size * 0.45);
24426
+ float r = length(e) - (1.0 - 0.35 * abs(e.x));
24427
+ float aa = px * scale / size * 1.5;
24428
+ float shape = 1.0 - smoothstep(-aa, aa, r);
24429
+ vec3 colour = mix(uLeafA, uLeafB, hash12(cell + 6.0)) * (0.8 + 0.3 * sign(sin(spin * 0.6)));
24430
+ float vein = (1.0 - smoothstep(0.0, 0.08, abs(e.y))) * step(abs(e.x), 0.85) * 0.3;
24431
+ colour *= 1.0 - vein;
24432
+ acc.rgb = mix(acc.rgb, colour, shape);
24433
+ acc.a = max(acc.a, shape);
24434
+ } else {
24435
+ // Dust: short streaks along the wind.
24436
+ float len = 0.06 + 0.1 * gust(iTime);
24437
+ float dd = length(vec2(d.x / (1.0 + len * 12.0), d.y));
24438
+ float speck = (1.0 - smoothstep(0.02, 0.05, dd)) * 0.55 * smoothstep(0.3, 0.6, gust(iTime - p.x * 0.35));
24439
+ acc.rgb = mix(acc.rgb, uDustColor, speck * (1.0 - acc.a));
24440
+ acc.a = max(acc.a, speck);
24441
+ }
24442
+ }
24443
+ fragColor = vec4(acc.rgb, acc.a);
24444
+ }`
24445
+ ),
24446
+ presets: [
24447
+ { name: "Autumn", values: { uLeaves: 0.85, uLines: 0.4, uDust: 0.2 } },
24448
+ { name: "Dust storm", values: { uLeaves: 0, uDust: 1, uLines: 0.9, uStrength: 1.8, uLineColor: [0.9, 0.82, 0.68], uDustColor: [0.8, 0.66, 0.45] } },
24449
+ { name: "Breeze", values: { uLeaves: 0.2, uDust: 0, uLines: 0.5, uStrength: 0.6, uGustRate: 0.12 } }
24450
+ ]
24451
+ };
24452
+ var hail = {
24453
+ kind: "shader",
24454
+ id: "hail",
24455
+ name: "Hail",
24456
+ description: "Hailstones pelting down and bouncing on a ground line, each bounce lower than the last, to lay over any scene.",
24457
+ category: "weather",
24458
+ tags: ["weather", "hail", "ice", "storm", "overlay", "particles", "physics"],
24459
+ transparent: true,
24460
+ source: glsl(
24461
+ `// Hail: stones on cycles in columns, each falling under gravity, bouncing three times on the ground line
24462
+ // with a loss of speed each time and scattering sideways, then melting away. A pixel checks its column and
24463
+ // its neighbours (stones scatter into them), two stones each.
24464
+ uniform vec3 uColor; // @control color default=#EEF4FA label="Colour"
24465
+ uniform vec3 uShade; // @control color default=#9FB4CC label="Shade"
24466
+ uniform float uDensity; // @control min=0 max=1 default=0.6 label="Density"
24467
+ uniform float uSize; // @control min=0.3 max=2.5 default=1 label="Size"
24468
+ uniform float uGround; // @control min=0 max=0.6 default=0.12 label="Ground line"
24469
+ uniform float uBounce; // @control min=0 max=0.8 default=0.42 label="Bounciness"
24470
+ uniform float uSpeed; // @control min=0 max=2 default=1 label="Speed"`,
24471
+ HASH,
24472
+ `const float G = 3.2;
24473
+ const float TOP = 1.1;
24474
+
24475
+ // A stone's place at local time tau (x offset from its column, height), its fall speed, and how visible it is.
24476
+ vec4 stoneAt(float tau, float r, float drift) {
24477
+ float h0 = TOP - uGround - r;
24478
+ float tf = sqrt(2.0 * h0 / G);
24479
+ if (tau < tf) return vec4(0.0, TOP - 0.5 * G * tau * tau, G * tau, 1.0);
24480
+ float v = G * tf;
24481
+ float t = tau - tf;
24482
+ float x = 0.0;
24483
+ // Each bounce: up at v\xB7e, back down after 2v\xB7e/G, drifting sideways.
24484
+ for (int b = 0; b < 3; b++) {
24485
+ v *= uBounce;
24486
+ float d = 2.0 * v / G;
24487
+ if (t < d) return vec4(drift * (x + t), uGround + r + v * t - 0.5 * G * t * t, abs(v - G * t), 1.0);
24488
+ t -= d;
24489
+ x += d;
24490
+ }
24491
+ // At rest, melting away.
24492
+ return vec4(drift * x, uGround + r, 0.0, 1.0 - smoothstep(0.2, 0.7, t));
24493
+ }
24494
+
24495
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
24496
+ float px = 1.0 / iResolution.y;
24497
+ vec2 p = fragCoord * px;
24498
+ float w = 0.035;
24499
+ float col0 = floor(p.x / w);
24500
+ vec4 acc = vec4(0.0);
24501
+ for (int c = -1; c <= 1; c++) {
24502
+ float column = col0 + float(c);
24503
+ for (int k = 0; k < 2; k++) {
24504
+ vec2 id = vec2(column, float(k));
24505
+ float period = 1.3 + 1.2 * hash12(id);
24506
+ float time = iTime * uSpeed + hash12(id + 3.1) * period;
24507
+ float cycle = floor(time / period);
24508
+ float tau = time - cycle * period;
24509
+ vec2 seed = id + cycle * 7.13;
24510
+ if (hash12(seed + 1.9) > uDensity) continue;
24511
+ float r = (0.008 + 0.009 * hash12(seed + 5.5)) * uSize;
24512
+ float drift = (hash12(seed + 8.2) - 0.5) * 0.25;
24513
+ vec4 s = stoneAt(tau, r, drift);
24514
+ if (s.w <= 0.0) continue;
24515
+ vec2 centre = vec2((column + 0.2 + 0.6 * hash12(seed + 2.4)) * w + s.x, s.y);
24516
+ vec2 d = p - centre;
24517
+ // Stretched a little along the fall when moving fast.
24518
+ d.y /= 1.0 + min(s.z * 0.12, 0.6);
24519
+ float dist = length(d);
24520
+ float disc = 1.0 - smoothstep(r - px, r + px, dist);
24521
+ if (disc <= 0.0) continue;
24522
+ // Lit from the upper left, with a hard highlight.
24523
+ vec2 n = d / r;
24524
+ float light = clamp(0.6 - 0.5 * dot(n, vec2(0.6, -0.6)), 0.0, 1.0);
24525
+ vec3 colour = mix(uShade, uColor, light);
24526
+ colour += smoothstep(0.35, 0.1, length(n - vec2(-0.35, 0.35))) * 0.5;
24527
+ float a = disc * s.w * 0.95;
24528
+ acc.rgb = mix(acc.rgb, colour, a);
24529
+ acc.a = max(acc.a, a);
24530
+ }
24531
+ }
24532
+ fragColor = vec4(min(acc.rgb, 1.0), acc.a);
24533
+ }`
24534
+ ),
24535
+ presets: [
24536
+ { name: "Light hail", values: { uDensity: 0.3, uSize: 0.7, uBounce: 0.3 } },
24537
+ { name: "Golf balls", values: { uDensity: 0.45, uSize: 2.2, uBounce: 0.5, uSpeed: 0.8 } }
24538
+ ]
24539
+ };
24540
+ var weatherShaders = [rainStreaks, thunderstorm, rainbow, windGust, hail];
24541
+
24542
+ // src/catalog/shaders/water.ts
24543
+ var oceanWaves = {
24544
+ kind: "shader",
24545
+ id: "ocean-waves",
24546
+ name: "Ocean waves",
24547
+ description: "An open ocean at sunset or by day: layered, sharp-crested waves rolling in, foam on the crests, the sky in the water and a glint of sun.",
24548
+ category: "water",
24549
+ tags: ["water", "ocean", "sea", "waves", "sunset", "nature", "3d", "background"],
24550
+ source: glsl(
24551
+ `// Ocean waves: a sum of sharp-crested waves (e^(sin x \u2212 1), each layer's position dragged by the last),
24552
+ // met by marching only between the planes of its highest and lowest points (16 steps and 4 secant steps),
24553
+ // with more layers for the normal. Fresnel mixes the reflected sky with light scattered through the crests.
24554
+ uniform vec3 uSkyTop; // @control color default=#27325E label="Sky (top)"
24555
+ uniform vec3 uSkyHorizon; // @control color default=#FF9B5C label="Sky (horizon)"
24556
+ uniform vec3 uSun; // @control color default=#FFD38A label="Sun"
24557
+ uniform vec3 uWater; // @control color default=#0A2638 label="Water"
24558
+ uniform vec3 uFoam; // @control color default=#F2EEE6 label="Foam"
24559
+ uniform float uSunHeight; // @control min=-0.1 max=1.2 default=0.06 label="Sun height"
24560
+ uniform float uSunAzimuth; // @control min=-1 max=1 default=0 label="Sun direction"
24561
+ uniform float uWaveHeight; // @control min=0 max=2.5 default=1 label="Wave height"
24562
+ uniform float uChoppiness; // @control min=0 max=2 default=1 label="Choppiness"
24563
+ uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"
24564
+ uniform float uFoamAmount; // @control min=0 max=1 default=0.5 label="Foam"`,
24565
+ HASH,
24566
+ NOISE,
24567
+ `const float AMP = 1.1;
24568
+
24569
+ // The sea's height (about 0\u20131) at p from its first n wave layers.
24570
+ float sea(vec2 p, int n) {
24571
+ float t = iTime * uSpeed;
24572
+ float h = 0.0;
24573
+ float total = 0.0;
24574
+ float a = 1.0;
24575
+ float k = 0.26;
24576
+ float ang = 0.3;
24577
+ for (int i = 0; i < 18; i++) {
24578
+ if (i >= n) break;
24579
+ vec2 d = vec2(cos(ang), sin(ang));
24580
+ float x = dot(d, p) * k + t * sqrt(9.8 * k) * 0.55;
24581
+ float e = exp(sin(x) - 1.0);
24582
+ h += a * e;
24583
+ total += a;
24584
+ // Drag the next layers toward this one's crests, so the crests gather.
24585
+ p += d * e * cos(x) * a * 0.35 * uChoppiness;
24586
+ a *= 0.76;
24587
+ k *= 1.24;
24588
+ ang += 2.39996;
24589
+ }
24590
+ return h / total;
24591
+ }
24592
+
24593
+ float heightAt(vec2 p, int n) {
24594
+ return (sea(p, n) - 0.42) * AMP * uWaveHeight;
24595
+ }
24596
+
24597
+ vec3 sky(vec3 rd, vec3 sun) {
24598
+ float y = max(rd.y, 0.0);
24599
+ vec3 c = mix(uSkyHorizon, uSkyTop, pow(y, 0.5));
24600
+ float mu = max(dot(rd, sun), 0.0);
24601
+ c += uSun * (pow(mu, 8.0) * 0.35 + pow(mu, 90.0) * 0.6);
24602
+ c += uSun * smoothstep(0.9993, 0.9997, mu) * 3.0;
24603
+ return c;
24604
+ }
24605
+
24606
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
24607
+ vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
24608
+ vec3 ro = vec3(0.0, 1.6 + 0.4 * uWaveHeight, iTime * uSpeed * 0.6);
24609
+ vec3 rd = normalize(vec3(p.x, p.y - 0.12, 1.2));
24610
+ vec3 sun = normalize(vec3(sin(uSunAzimuth), uSunHeight, cos(uSunAzimuth)));
24611
+ vec3 col = sky(rd, sun);
24612
+
24613
+ float top = AMP * uWaveHeight * 0.6;
24614
+ float bottom = -AMP * uWaveHeight * 0.45;
24615
+ if (rd.y < -0.0005) {
24616
+ // Between the planes of the highest and lowest water.
24617
+ float t0 = (top - ro.y) / rd.y;
24618
+ float t1 = min((bottom - ro.y) / rd.y, 400.0);
24619
+ float tPrev = t0;
24620
+ float gPrev = top - heightAt(ro.xz + rd.xz * t0, 7);
24621
+ float t = t0;
24622
+ float dt = (t1 - t0) / 16.0;
24623
+ float tHit = t1;
24624
+ for (int i = 1; i <= 16; i++) {
24625
+ t = t0 + dt * float(i);
24626
+ vec3 pos = ro + rd * t;
24627
+ float g = pos.y - heightAt(pos.xz, 7);
24628
+ if (g < 0.0) {
24629
+ // Secant steps between the last point above and this one below.
24630
+ float a = tPrev, b = t, ga = gPrev, gb = g;
24631
+ for (int j = 0; j < 4; j++) {
24632
+ float m = a + (b - a) * ga / (ga - gb);
24633
+ vec3 q = ro + rd * m;
24634
+ float gm = q.y - heightAt(q.xz, 7);
24635
+ if (gm > 0.0) {
24636
+ a = m;
24637
+ ga = gm;
24638
+ } else {
24639
+ b = m;
24640
+ gb = gm;
24641
+ }
24642
+ }
24643
+ tHit = a + (b - a) * ga / (ga - gb);
24644
+ break;
24645
+ }
24646
+ tPrev = t;
24647
+ gPrev = g;
24648
+ }
24649
+ vec3 pos = ro + rd * tHit;
24650
+ // The normal from more wave layers, finer up close.
24651
+ int fine = tHit < 30.0 ? 13 : 9;
24652
+ float e = 0.008 * tHit + 0.01;
24653
+ float h = heightAt(pos.xz, fine);
24654
+ vec3 n = normalize(vec3(h - heightAt(pos.xz + vec2(e, 0.0), fine), e, h - heightAt(pos.xz + vec2(0.0, e), fine)));
24655
+ // Far away the waves blur into a flatter surface.
24656
+ n = normalize(mix(n, vec3(0.0, 1.0, 0.0), smoothstep(30.0, 200.0, tHit)));
24657
+ float fres = 0.02 + 0.98 * pow(1.0 - max(dot(n, -rd), 0.0), 5.0);
24658
+ vec3 r = reflect(rd, n);
24659
+ r.y = abs(r.y);
24660
+ vec3 refl = sky(r, sun);
24661
+ float crest = clamp(h / (AMP * max(uWaveHeight, 0.05)) + 0.3, 0.0, 1.0);
24662
+ // Light through the crests, toward the sun.
24663
+ float through = pow(max(dot(rd, sun) * 0.5 + 0.5, 0.0), 3.0) * crest;
24664
+ vec3 water = uWater * (0.35 + 0.65 * max(dot(n, sun), 0.0)) + mix(uWater * 3.0, uSun * 0.6, 0.3) * through * 0.6;
24665
+ col = mix(water, refl, fres);
24666
+ // Glint.
24667
+ col += uSun * pow(max(dot(r, sun), 0.0), 700.0) * 6.0 * smoothstep(-0.1, 0.1, uSunHeight);
24668
+ // Foam on the crests, broken up by noise.
24669
+ float foamNoise = vnoise(pos.xz * 3.0 + iTime * 0.3) * vnoise(pos.xz * 7.0 - iTime * 0.2);
24670
+ float foam = smoothstep(0.55, 0.85, crest + foamNoise * 0.6 - (1.0 - uFoamAmount) * 0.5) * uFoamAmount;
24671
+ col = mix(col, uFoam * (0.5 + 0.5 * max(dot(n, sun), 0.3)) * mix(vec3(1.0), uSkyHorizon, 0.25), foam * smoothstep(80.0, 20.0, tHit));
24672
+ // Haze toward the horizon.
24673
+ col = mix(col, sky(vec3(rd.x, 0.0, rd.z), sun), smoothstep(0.0, 1.0, 1.0 - exp(-tHit * 0.006)));
24674
+ }
24675
+ fragColor = vec4(col, 1.0);
24676
+ }`
24677
+ ),
24678
+ presets: [
24679
+ { name: "Sunset", values: { uSunHeight: 0.06 } },
24680
+ {
24681
+ name: "Day",
24682
+ values: {
24683
+ uSkyTop: [0.22, 0.48, 0.85],
24684
+ uSkyHorizon: [0.8, 0.9, 0.97],
24685
+ uSun: [1, 0.97, 0.9],
24686
+ uWater: [0.03, 0.22, 0.32],
24687
+ uSunHeight: 0.55,
24688
+ uSunAzimuth: 0.4
24689
+ }
24690
+ },
24691
+ { name: "Stormy sea", values: { uSkyTop: [0.2, 0.23, 0.27], uSkyHorizon: [0.5, 0.53, 0.55], uSun: [0.6, 0.62, 0.65], uWater: [0.05, 0.1, 0.12], uSunHeight: 0.3, uWaveHeight: 2.2, uChoppiness: 1.6, uFoamAmount: 0.9, uSpeed: 1.4 } }
24692
+ ]
24693
+ };
24694
+ var underwaterScene = {
24695
+ kind: "shader",
24696
+ id: "underwater-scene",
24697
+ name: "Underwater scene",
24698
+ description: "Looking out under water: shafts of light from the rippling surface, caustics dancing on a sandy floor, drifting particles and deep blue fog.",
24699
+ category: "water",
24700
+ tags: ["water", "underwater", "ocean", "sea", "caustics", "light", "background"],
24701
+ source: glsl(
24702
+ `// Underwater scene: a sandy floor met analytically with caustic lines (ridges of warped noise), the surface
24703
+ // above shimmering inside its bright window, light shafts as streaks of noise fanning down, particles in layers.
24704
+ uniform vec3 uDeep; // @control color default=#021E33 label="Deep water"
24705
+ uniform vec3 uShallow; // @control color default=#13708A label="Shallow water"
24706
+ uniform vec3 uLight; // @control color default=#C8F6FF label="Light"
24707
+ uniform vec3 uSand; // @control color default=#C6B188 label="Sand"
24708
+ uniform float uShafts; // @control min=0 max=1.5 default=0.7 label="Light shafts"
24709
+ uniform float uCaustics; // @control min=0 max=1.5 default=0.8 label="Caustics"
24710
+ uniform float uParticles; // @control min=0 max=1 default=0.6 label="Particles"
24711
+ uniform float uFog; // @control min=0 max=2 default=1 label="Fog"
24712
+ uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"`,
24713
+ HASH,
24714
+ NOISE,
24715
+ `// Bright lines where light gathers: the ridges of two layers of warped noise, multiplied.
24716
+ float caustic(vec2 p, float t) {
24717
+ vec2 w = vec2(vnoise(p * 0.7 + t * 0.3), vnoise(p * 0.7 - t * 0.25 + 5.0));
24718
+ vec2 q = p + w * 1.6;
24719
+ float a = 1.0 - abs(vnoise(q + t * 0.4) * 2.0 - 1.0);
24720
+ float b = 1.0 - abs(vnoise(q * 1.7 - t * 0.35 + 3.0) * 2.0 - 1.0);
24721
+ return pow(a, 6.0) + pow(b, 6.0) * 0.7 + pow(a * b, 4.0) * 1.5;
24722
+ }
24723
+
24724
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
24725
+ float px = 1.0 / iResolution.y;
24726
+ vec2 p = (fragCoord - 0.5 * iResolution.xy) * px;
24727
+ float t = iTime * uSpeed;
24728
+ vec3 ro = vec3(0.0, 0.0, t * 0.15);
24729
+ vec3 rd = normalize(vec3(p.x, p.y + 0.08, 1.0));
24730
+ // The water itself: lighter looking up.
24731
+ vec3 water = mix(uDeep, uShallow, smoothstep(-0.45, 0.7, rd.y));
24732
+ vec3 col = water;
24733
+ float fogK = 0.09 * uFog;
24734
+
24735
+ if (rd.y < 0.0) {
24736
+ // The floor: sand in low dunes, lit by caustics, fading into the blue.
24737
+ float tf = -3.0 / rd.y;
24738
+ vec3 pos = ro + rd * tf;
24739
+ float dunes = vnoise(pos.xz * 0.35) * 0.6 + vnoise(pos.xz * 1.3) * 0.25 + vnoise(pos.xz * 6.0) * 0.08;
24740
+ vec3 sand = uSand * (0.55 + 0.5 * dunes);
24741
+ float c = caustic(pos.xz * 0.9, t * 0.8);
24742
+ // Fine caustics turn to an even glow far away.
24743
+ c = mix(c, 0.5, smoothstep(10.0, 30.0, tf));
24744
+ sand += uLight * c * 0.55 * uCaustics;
24745
+ sand *= mix(uShallow * 1.5, vec3(1.0), 0.4);
24746
+ col = mix(water, sand, exp(-tf * fogK));
24747
+ } else {
24748
+ // The surface: a bright, rippling window overhead.
24749
+ float ts = 4.0 / max(rd.y, 0.05);
24750
+ vec2 sp = (ro + rd * ts).xz;
24751
+ float ripple = caustic(sp * 0.6, t * 1.2);
24752
+ float window = smoothstep(0.35, 0.8, rd.y);
24753
+ col += uLight * (0.25 + 0.6 * ripple) * window * exp(-ts * 0.04);
24754
+ }
24755
+
24756
+ // Light shafts fanning down from the surface, swaying.
24757
+ float ang = (p.x - 0.35 * p.y) / (1.3 - p.y);
24758
+ float shafts = vnoise(vec2(ang * 9.0 + t * 0.15, t * 0.1)) * vnoise(vec2(ang * 23.0 - t * 0.1, 3.0 + t * 0.12));
24759
+ shafts = smoothstep(0.12, 0.6, shafts);
24760
+ col += uLight * shafts * smoothstep(-0.55, 0.45, p.y) * 0.35 * uShafts;
24761
+
24762
+ // Drifting particles: far ones small and dim, near ones larger and soft.
24763
+ for (int i = 0; i < 3; i++) {
24764
+ float fi = float(i);
24765
+ float scale = mix(28.0, 8.0, fi / 2.0);
24766
+ vec2 q = p * scale + vec2(t * 0.05 * (fi + 1.0), t * (0.08 + 0.05 * fi)) + fi * 17.0;
24767
+ vec2 cell = floor(q);
24768
+ if (hash12(cell + fi) > uParticles * 0.5) continue;
24769
+ vec2 at = 0.2 + 0.6 * hash22(cell + 3.0 * fi);
24770
+ at += 0.15 * vec2(sin(t * 0.7 + hash12(cell) * 6.28), cos(t * 0.5 + hash12(cell + 1.0) * 6.28));
24771
+ float d = length(fract(q) - at) / (scale * px);
24772
+ float size = mix(0.8, 3.0, fi / 2.0) * (0.6 + 0.8 * hash12(cell + 5.0));
24773
+ col += uLight * (1.0 - smoothstep(size * 0.4, size + 0.8, d)) * mix(0.25, 0.45, fi / 2.0);
24774
+ }
24775
+ // Darker at the edges and bottom, like a lens in water.
24776
+ col *= 1.0 - 0.35 * dot(p, p);
24777
+ fragColor = vec4(col, 1.0);
24778
+ }`
24779
+ ),
24780
+ presets: [
24781
+ { name: "Tropical", values: { uDeep: [0, 0.25, 0.35], uShallow: [0.1, 0.65, 0.7], uSand: [0.95, 0.88, 0.7], uFog: 0.6, uCaustics: 1.1 } },
24782
+ { name: "Murky", values: { uDeep: [0.02, 0.07, 0.05], uShallow: [0.12, 0.25, 0.18], uLight: [0.75, 0.9, 0.7], uSand: [0.4, 0.38, 0.3], uFog: 1.8, uShafts: 1.1, uParticles: 1 } }
24783
+ ]
24784
+ };
24785
+ var pondRipples = {
24786
+ kind: "shader",
24787
+ id: "pond-ripples",
24788
+ name: "Pond ripples",
24789
+ description: "Raindrops landing on a picture as if it lay under a pond, each spreading rings that bend the picture beneath; clicks drop stones that ripple wider.",
24790
+ category: "water",
24791
+ video: "filter",
24792
+ tags: ["water", "ripples", "rain", "pond", "image", "distortion", "interactive", "click"],
24793
+ source: glsl(
24794
+ `// Pond ripples: drops on cycles in grid cells (a pixel sums its own and the eight around it), clicks as larger
24795
+ // drops (iClicks), each a damped ring whose slope bends the picture and catches a highlight.
24796
+ uniform sampler2D uImage; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.landscape} label="Image"
24797
+ uniform float uRain; // @control min=0 max=1 default=0.55 label="Rain"
24798
+ uniform float uSize; // @control min=0.3 max=2 default=1 label="Ripple size"
24799
+ uniform float uStrength; // @control min=0 max=2 default=1 label="Refraction"
24800
+ uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"
24801
+ uniform vec3 uTint; // @control color default=#BFD8E4 label="Water tint"
24802
+ uniform float uTintAmount; // @control min=0 max=1 default=0.12 label="Tint amount"
24803
+ uniform bool uDemo; // @control default=true label="Demo when idle"
24804
+ ${BACKDROP_CONTROLS}`,
24805
+ HASH,
24806
+ PICTURE,
24807
+ `const float CELL = 0.22;
24808
+
24809
+ // The slope (gradient of the water's height) of one ring: centre c, age in seconds, size and strength.
24810
+ vec2 ring(vec2 p, vec2 c, float age, float speed, float life, float amp) {
24811
+ if (age < 0.0 || age > life) return vec2(0.0);
24812
+ vec2 d = p - c;
24813
+ float r = length(d);
24814
+ float front = age * speed;
24815
+ float x = r - front;
24816
+ float width = 0.025 * uSize + age * 0.012;
24817
+ float k = 6.28318 / (0.035 * uSize + age * 0.01);
24818
+ float env = exp(-x * x / (width * width)) * (1.0 - age / life) * smoothstep(0.0, 0.04, age);
24819
+ // d/dr of sin(k x)\xB7env, about k\xB7cos(k x)\xB7env for narrow rings.
24820
+ float slope = (k * cos(k * x) - 2.0 * x / (width * width) * sin(k * x)) * env * amp;
24821
+ return d / max(r, 1e-4) * slope;
24822
+ }
24823
+
24824
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
24825
+ vec2 uv = fragCoord / iResolution.xy;
24826
+ float px = 1.0 / iResolution.y;
24827
+ vec2 p = fragCoord * px;
24828
+ float t = iTime * uSpeed;
24829
+ vec2 grad = vec2(0.0);
24830
+
24831
+ // Rain: one drop per cell per cycle.
24832
+ if (uRain > 0.0) {
24833
+ vec2 base = floor(p / CELL);
24834
+ for (int j = -1; j <= 1; j++) {
24835
+ for (int i = -1; i <= 1; i++) {
24836
+ vec2 cell = base + vec2(float(i), float(j));
24837
+ float period = mix(3.5, 0.9, uRain) * (0.7 + 0.6 * hash12(cell));
24838
+ float time = t + hash12(cell + 4.2) * period;
24839
+ float cycle = floor(time / period);
24840
+ float age = time - cycle * period;
24841
+ if (hash12(cell + cycle * 3.7) > 0.35 + 0.65 * uRain) continue;
24842
+ vec2 c = (cell + 0.15 + 0.7 * hash22(cell + cycle)) * CELL;
24843
+ grad += ring(p, c, age, 0.12 * uSize, min(period, 1.6), 0.0012 * uSize);
24844
+ }
24845
+ }
24846
+ }
24847
+ // Clicks drop stones; while nobody has clicked, the demo drops one now and then.
24848
+ if (iClicks[0].w > 0.5 || !uDemo) {
24849
+ for (int i = 0; i < 8; i++) {
24850
+ vec4 c = iClicks[i];
24851
+ if (c.w < 0.5) continue;
24852
+ grad += ring(p, c.xy * px, (iTime - c.z) * uSpeed, 0.2 * uSize, 3.0, 0.004 * uSize);
24853
+ }
24854
+ } else {
24855
+ float k = floor(t / 3.0);
24856
+ vec2 at = vec2((0.2 + 0.6 * hash12(vec2(k, 1.0))) * iResolution.x * px, 0.25 + 0.5 * hash12(vec2(k, 2.0)));
24857
+ grad += ring(p, at, t - k * 3.0, 0.2 * uSize, 3.0, 0.004 * uSize);
24858
+ }
24859
+
24860
+ vec2 offset = grad * 0.6 * uStrength;
24861
+ offset.x *= iResolution.y / iResolution.x;
24862
+ vec4 pic = pictureRGBA(uImage, uv - offset);
24863
+ vec3 col = mix(pic.rgb, pic.rgb * uTint * 1.2, uTintAmount);
24864
+ // A highlight on the slopes facing the light, and a little shade on the others.
24865
+ vec3 n = normalize(vec3(-grad * 1.5, 1.0));
24866
+ vec3 l = normalize(vec3(-0.5, 0.6, 0.65));
24867
+ float lit = dot(n, l) - l.z;
24868
+ col += vec3(1.0) * pow(max(lit, 0.0), 1.5) * 0.9;
24869
+ col *= 1.0 + min(lit, 0.0) * 0.6;
24870
+ fragColor = vec4(clamp(col, 0.0, 1.0), pic.a);
24871
+ }`
24872
+ ),
24873
+ presets: [
24874
+ { name: "Drizzle", values: { uRain: 0.25, uSize: 0.7 } },
24875
+ { name: "Downpour", values: { uRain: 1, uSize: 0.8, uStrength: 1.3, uTintAmount: 0.2 } },
24876
+ { name: "Stones only", values: { uRain: 0 } }
24877
+ ]
24878
+ };
24879
+ var waterfall = {
24880
+ kind: "shader",
24881
+ id: "waterfall",
24882
+ name: "Waterfall",
24883
+ description: "A stylised waterfall pouring over a cliff between mossy rocks into a churning plunge pool, wrapped in rising mist.",
24884
+ category: "water",
24885
+ tags: ["water", "waterfall", "nature", "mist", "landscape", "background"],
24886
+ source: glsl(
24887
+ `// Waterfall: a curtain of streaks (noise stretched down, scrolling faster as it falls) between noise-edged
24888
+ // cliffs, foam where it lands in the pool, rings spreading across the pool and fbm mist rising around the foot.
24889
+ uniform vec3 uSky; // @control color default=#A9D3EE label="Sky"
24890
+ uniform vec3 uRock; // @control color default=#4A443E label="Rock"
24891
+ uniform vec3 uFoliage; // @control color default=#3E6B3C label="Foliage"
24892
+ uniform vec3 uWater; // @control color default=#CDEFFA label="Falling water"
24893
+ uniform vec3 uPool; // @control color default=#2D7591 label="Pool"
24894
+ uniform float uWidth; // @control min=0.1 max=0.8 default=0.32 label="Width"
24895
+ uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"
24896
+ uniform float uMist; // @control min=0 max=1.5 default=0.8 label="Mist"`,
24897
+ HASH,
24898
+ NOISE,
24899
+ FBM,
24900
+ `const float LIP = 0.74;
24901
+ const float POOL = 0.24;
24902
+
24903
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
24904
+ float px = 1.0 / iResolution.y;
24905
+ vec2 uv = fragCoord / iResolution.xy;
24906
+ float aspect = iResolution.x / iResolution.y;
24907
+ vec2 p = vec2((uv.x - 0.5) * aspect, uv.y);
24908
+ float t = iTime * uSpeed;
24909
+
24910
+ // Sky and distant hills.
24911
+ vec3 col = mix(uSky * 1.08, uSky * 0.85, uv.y);
24912
+ float hills = 0.83 + 0.05 * fbm(vec2(p.x * 2.0, 3.0), 4);
24913
+ col = mix(col, mix(uFoliage, uSky, 0.55), step(uv.y, hills));
24914
+
24915
+ // The cliff: rock on both sides of the fall, its top edge ragged with foliage.
24916
+ float half_ = uWidth * 0.5 * aspect * 0.5;
24917
+ float edgeNoise = (fbm(vec2(p.y * 8.0, 1.0), 3) - 0.5) * 0.06;
24918
+ float fallEdge = abs(p.x) - half_ - edgeNoise - (LIP - p.y) * 0.03;
24919
+ float cliffTop = LIP + 0.03 + 0.04 * fbm(vec2(p.x * 4.0, 7.0), 3);
24920
+ if (p.y < cliffTop) {
24921
+ float strata = fbm(vec2(p.x * 3.0, p.y * 14.0), 4);
24922
+ vec3 rock = uRock * (0.6 + 0.7 * strata) * (0.75 + 0.35 * smoothstep(0.0, 0.3, fallEdge));
24923
+ // Moss and leaves on ledges and along the top.
24924
+ float moss = smoothstep(0.55, 0.7, fbm(p * 9.0, 4)) + smoothstep(cliffTop - 0.08, cliffTop - 0.01, p.y);
24925
+ rock = mix(rock, uFoliage * (0.7 + 0.5 * fbm(p * 30.0, 2)), clamp(moss, 0.0, 1.0) * 0.85);
24926
+ col = rock;
24927
+ }
24928
+
24929
+ // The falling water, in front of the cliff between its edges.
24930
+ float inFall = smoothstep(px * 2.0, -px * 2.0, fallEdge) * step(POOL - 0.02, p.y) * step(p.y, LIP + 0.012);
24931
+ if (inFall > 0.0) {
24932
+ float drop = max(LIP - p.y, 0.0);
24933
+ // Water speeds up as it falls: the scroll stretches toward the bottom.
24934
+ float s = pow(drop, 0.7) * 6.0 - t * 1.6;
24935
+ float streaks = vnoise(vec2(p.x * 70.0, s * 3.0)) * 0.6 + vnoise(vec2(p.x * 160.0 + 3.0, s * 5.0)) * 0.4;
24936
+ float sheets = vnoise(vec2(p.x * 12.0, s * 1.2));
24937
+ vec3 w = mix(uWater * 0.65, uWater * 1.1, smoothstep(0.3, 0.8, streaks * 0.7 + sheets * 0.5));
24938
+ // The lip: water bulges over the edge, bright.
24939
+ w += vec3(0.25) * smoothstep(LIP - 0.04, LIP, p.y);
24940
+ // Darker gaps near the edges where the curtain thins.
24941
+ float thin = smoothstep(-0.06, 0.0, fallEdge + streaks * 0.03);
24942
+ w = mix(w, col, thin * 0.6);
24943
+ col = mix(col, w, inFall);
24944
+ }
24945
+
24946
+ // The plunge pool.
24947
+ if (p.y < POOL) {
24948
+ float depth = (POOL - p.y) / POOL;
24949
+ float z = 1.0 / (0.05 + depth);
24950
+ vec2 q = vec2(p.x * z * 0.6, z);
24951
+ float waves = vnoise(vec2(q.x * 5.0, q.y * 2.0 - t * 0.6)) * 0.5 + vnoise(vec2(q.x * 13.0, q.y * 5.0 + t * 0.3)) * 0.5;
24952
+ vec3 pool = uPool * (0.7 + 0.5 * waves) + mix(uSky, vec3(1.0), 0.3) * smoothstep(0.6, 0.85, waves) * 0.3;
24953
+ // Rings spreading from where the water lands.
24954
+ float rings = sin(length(vec2(p.x / max(half_ * 1.4, 0.05), (POOL - p.y) * 6.0)) * 9.0 - t * 4.0);
24955
+ pool += uWater * smoothstep(0.7, 1.0, rings) * 0.12 * (1.0 - depth);
24956
+ col = pool;
24957
+ }
24958
+
24959
+ // Foam where the curtain lands, churning.
24960
+ float foamZone = exp(-pow((p.y - POOL) / 0.05, 2.0)) * smoothstep(half_ * 1.6, half_ * 0.6, abs(p.x));
24961
+ float churn = fbm(vec2(p.x * 14.0, p.y * 18.0 - t * 1.5), 4);
24962
+ col = mix(col, uWater * 1.15, clamp(foamZone * (0.6 + churn), 0.0, 1.0));
24963
+
24964
+ // Mist rising from the foot.
24965
+ vec2 mp = vec2(p.x * 2.5, (p.y - POOL) * 3.0 - t * 0.25);
24966
+ float mist = fbm(mp + vec2(fbm(mp + t * 0.05, 3), 0.0), 5);
24967
+ float around = exp(-pow((p.y - POOL - 0.08) / 0.2, 2.0)) * smoothstep(half_ * 3.5, half_ * 0.5, abs(p.x));
24968
+ col = mix(col, vec3(0.95, 0.98, 1.0), clamp(smoothstep(0.35, 0.8, mist) * around * uMist, 0.0, 0.85));
24969
+ // Spray droplets thrown up.
24970
+ vec2 g = vec2(p.x * 90.0, (p.y + t * 0.05) * 90.0);
24971
+ vec2 cell = floor(g);
24972
+ float spray = step(0.93, hash12(cell)) * (1.0 - smoothstep(0.1, 0.35, length(fract(g) - 0.5)));
24973
+ col += spray * around * 0.5 * uMist;
24974
+ fragColor = vec4(col, 1.0);
24975
+ }`
24976
+ ),
24977
+ presets: [
24978
+ { name: "Jungle", values: { uSky: [0.75, 0.9, 0.85], uRock: [0.25, 0.27, 0.22], uFoliage: [0.18, 0.45, 0.2], uPool: [0.12, 0.45, 0.4], uMist: 1.2 } },
24979
+ { name: "Dusk", values: { uSky: [0.95, 0.6, 0.5], uRock: [0.22, 0.18, 0.2], uFoliage: [0.2, 0.22, 0.25], uWater: [0.95, 0.85, 0.85], uPool: [0.3, 0.25, 0.4] } }
24980
+ ]
24981
+ };
24982
+ var waterShaders = [oceanWaves, underwaterScene, pondRipples, waterfall];
24983
+
22802
24984
  // src/catalog/text.ts
22803
24985
  var r112 = (n15) => Math.round(n15 * 10) / 10;
22804
24986
  function fit5(text, width, max, ems = textWidthEm(text)) {
@@ -25197,6 +27379,7 @@ var SHADER_EFFECTS = [
25197
27379
  starfield,
25198
27380
  nebula,
25199
27381
  blackHole,
27382
+ ...spaceShaders,
25200
27383
  synthwaveGrid,
25201
27384
  digitalRain,
25202
27385
  pixelSunset,
@@ -25234,6 +27417,9 @@ var SHADER_EFFECTS = [
25234
27417
  ...audioShaders,
25235
27418
  ...scrollShaders,
25236
27419
  ...uiShaders,
27420
+ ...threeDShaders,
27421
+ ...weatherShaders,
27422
+ ...waterShaders,
25237
27423
  ...transitions
25238
27424
  ];
25239
27425
  var EFFECTS = [...SHADER_EFFECTS, ...vectorEffects, ...textEffects, ...morphEffects, ...interactionEffects, ...pyroEffects, ...timewarpEffects, ...meltEffects, ...electricEffects, ...pixelEffects, ...splatterEffects, ...bubbleEffects, ...stickerEffects, ...trailEffects, ...impactEffects, ...comicEffects, ...glowEffects, ...shadowEffects, ...lightingEffects, ...scrollEffects, ...broadcastEffects, ...annotationEffects, ...uiEffects, ...loaderEffects].sort(
@@ -25312,4 +27498,4 @@ export {
25312
27498
  effectControls,
25313
27499
  effectDefaults
25314
27500
  };
25315
- //# sourceMappingURL=chunk-7F4ITXGK.js.map
27501
+ //# sourceMappingURL=chunk-RS2S2K5C.js.map