@pixerate/flair 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/catalog/index.js +1 -1
- package/dist/{chunk-KD7E3RWF.js → chunk-3RKDS64M.js} +874 -229
- package/dist/chunk-3RKDS64M.js.map +1 -0
- package/dist/editor/index.js +1 -1
- package/dist/react/index.js +1 -1
- package/dist/svelte/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-KD7E3RWF.js.map +0 -1
|
@@ -456,17 +456,16 @@ var imageDuotone = {
|
|
|
456
456
|
kind: "shader",
|
|
457
457
|
id: "image-duotone",
|
|
458
458
|
name: "Duotone",
|
|
459
|
-
description: "An image mapped onto two colours,
|
|
459
|
+
description: "An image mapped onto two colours, from its shadows to its highlights. For a passing glare, lay `shine-sweep` over it.",
|
|
460
460
|
category: "image",
|
|
461
461
|
video: "filter",
|
|
462
462
|
tags: ["image", "brand", "editorial", "colour"],
|
|
463
463
|
source: glsl(
|
|
464
|
-
`// Duotone: an image mapped onto two colours
|
|
464
|
+
`// Duotone: an image mapped onto two colours by its brightness.
|
|
465
465
|
uniform sampler2D uImage; // @control filter=linear wrap=clamp default=${SAMPLE_IMAGES.landscape} label="Image"
|
|
466
466
|
uniform vec3 uShadow; // @control color default=#1E1B4B label="Shadows"
|
|
467
467
|
uniform vec3 uHighlight; // @control color default=#FB7185 label="Highlights"
|
|
468
468
|
uniform float uContrast; // @control min=0.5 max=3 default=1.2 label="Contrast"
|
|
469
|
-
uniform float uSweep; // @control min=0 max=1 default=0.3 label="Light sweep"
|
|
470
469
|
${BACKDROP_CONTROLS}`,
|
|
471
470
|
PICTURE,
|
|
472
471
|
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
@@ -474,9 +473,7 @@ ${BACKDROP_CONTROLS}`,
|
|
|
474
473
|
vec4 src = pictureRGBA(uImage, uv);
|
|
475
474
|
float l = dot(src.rgb, vec3(0.2126, 0.7152, 0.0722));
|
|
476
475
|
l = clamp((l - 0.5) * uContrast + 0.5, 0.0, 1.0);
|
|
477
|
-
|
|
478
|
-
vec3 col = mix(uShadow, uHighlight, l) + sweep * uSweep;
|
|
479
|
-
fragColor = vec4(col, src.a);
|
|
476
|
+
fragColor = vec4(mix(uShadow, uHighlight, l), src.a);
|
|
480
477
|
}`
|
|
481
478
|
),
|
|
482
479
|
presets: [
|
|
@@ -6992,16 +6989,25 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
6992
6989
|
float px = 1.0 / iResolution.y;
|
|
6993
6990
|
vec2 p = fragCoord * px;
|
|
6994
6991
|
vec4 acc = vec4(0.0);
|
|
6992
|
+
// The clicks are the same for every bubble, so look them up once.
|
|
6993
|
+
vec4 clicks[8];
|
|
6994
|
+
for (int j = 0; j < 8; j++) clicks[j] = clickAt(j, uDemo);
|
|
6995
|
+
// Nothing of a bubble reaches further than this from where it is now: its pop (up to 0.7 s
|
|
6996
|
+
// ago, so it has drifted at most this far since) throws droplets out to 3.6 radii + 0.1.
|
|
6997
|
+
float drift = 0.7 * (uRise + 0.085 * uWobble + 0.04);
|
|
6998
|
+
float far = max(uSize * 0.12, 0.01) * 3.6 + 0.1 + drift + 2.0 * px;
|
|
6995
6999
|
for (int i = 0; i < 16; i++) {
|
|
6996
7000
|
if (i >= uCount) break;
|
|
6997
7001
|
float age;
|
|
6998
7002
|
float life;
|
|
6999
7003
|
vec4 b = bubbleAt(i, iTime, age, life);
|
|
7004
|
+
vec2 d0 = p - b.xy;
|
|
7005
|
+
if (dot(d0, d0) > far * far) continue;
|
|
7000
7006
|
float birth = iTime - age;
|
|
7001
7007
|
// It pops when its life is up, or at the first click that lands on it.
|
|
7002
7008
|
float popAt = birth + life;
|
|
7003
7009
|
for (int j = 0; j < 8; j++) {
|
|
7004
|
-
vec4 c =
|
|
7010
|
+
vec4 c = clicks[j];
|
|
7005
7011
|
if (c.w < 0.5 || c.z < birth || c.z > iTime || c.z >= popAt) continue;
|
|
7006
7012
|
float a2;
|
|
7007
7013
|
float l2;
|
|
@@ -8240,10 +8246,36 @@ uniform float uClump; // @control min=0 max=1 default=0.5 label="Clumping"
|
|
|
8240
8246
|
uniform float uPart; // @control min=0 max=1 default=0.8 label="Parting"`,
|
|
8241
8247
|
HASH,
|
|
8242
8248
|
NOISE,
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8249
|
+
`// The two slow noise fields that turn the strands, hashed once per pixel on a
|
|
8250
|
+
// 3 x 3 lattice that covers every root the pixel looks at.
|
|
8251
|
+
mat3 clumpGrid;
|
|
8252
|
+
vec2 clumpBase;
|
|
8253
|
+
mat3 swayGrid;
|
|
8254
|
+
vec2 swayBase;
|
|
8255
|
+
|
|
8256
|
+
mat3 noiseGrid(vec2 base) {
|
|
8257
|
+
mat3 g;
|
|
8258
|
+
for (int x = 0; x < 3; x++) {
|
|
8259
|
+
for (int y = 0; y < 3; y++) g[x][y] = hash12(base + vec2(float(x), float(y)));
|
|
8260
|
+
}
|
|
8261
|
+
return g;
|
|
8262
|
+
}
|
|
8263
|
+
|
|
8264
|
+
// vnoise(q), with its corners read from a lattice hashed at base.
|
|
8265
|
+
float gridNoise(vec2 q, vec2 base, mat3 g) {
|
|
8266
|
+
vec2 f = fract(q);
|
|
8267
|
+
vec2 u = f * f * (3.0 - 2.0 * f);
|
|
8268
|
+
ivec2 k = clamp(ivec2(floor(q) - base), 0, 1);
|
|
8269
|
+
vec3 c0 = g[k.x];
|
|
8270
|
+
vec3 c1 = g[k.x + 1];
|
|
8271
|
+
return mix(mix(c0[k.y], c1[k.y], u.x), mix(c0[k.y + 1], c1[k.y + 1], u.x), u.y);
|
|
8272
|
+
}
|
|
8273
|
+
|
|
8274
|
+
vec2 strandDir(vec2 root, vec2 ptr, float hov) {
|
|
8275
|
+
float a = radians(uLie) + (gridNoise(root * 0.07, clumpBase, clumpGrid) - 0.5) * 1.6 * uClump;
|
|
8276
|
+
a += uSway * 0.7 * sin(iTime * 1.3 + root.x * 0.05 + root.y * 0.03 + 2.0 * gridNoise(root * 0.02 + iTime * 0.15, swayBase, swayGrid));
|
|
8246
8277
|
vec2 dir = vec2(cos(a), sin(a));
|
|
8278
|
+
if (uPart * hov <= 0.0) return dir;
|
|
8247
8279
|
vec2 away = root - ptr;
|
|
8248
8280
|
float reach = 0.16 * uDensity;
|
|
8249
8281
|
float f = uPart * hov * exp(-dot(away, away) / (reach * reach));
|
|
@@ -8254,23 +8286,93 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
8254
8286
|
vec2 p = fragCoord / iResolution.y * uDensity;
|
|
8255
8287
|
vec2 ptr = iPointer.xy / iResolution.y * uDensity;
|
|
8256
8288
|
float hov = clamp(iPointer.z, 0.0, 1.0);
|
|
8289
|
+
// Every root looked at below lies within 7 cells of p.
|
|
8290
|
+
clumpBase = floor((p - 7.0) * 0.07);
|
|
8291
|
+
clumpGrid = noiseGrid(clumpBase);
|
|
8292
|
+
swayBase = floor((p - 7.0) * 0.02 + iTime * 0.15);
|
|
8293
|
+
swayGrid = noiseGrid(swayBase);
|
|
8257
8294
|
vec2 here = strandDir(p, ptr, hov);
|
|
8258
8295
|
float top = -1.0;
|
|
8259
8296
|
float across = 0.0;
|
|
8260
8297
|
float tint = 0.0;
|
|
8261
8298
|
vec2 topDir = here;
|
|
8299
|
+
// Clumping and breeze turn a strand by at most 0.8 * uClump and 0.7 * uSway, and
|
|
8300
|
+
// both fields are smooth, so strands rooted within reach of this pixel point
|
|
8301
|
+
// inside a cone around its own direction. Roots whose strands can't reach the
|
|
8302
|
+
// pixel, or can't rise above the strand already found, are skipped before their
|
|
8303
|
+
// directions are worked out. Roots are visited upstream first, so the topmost
|
|
8304
|
+
// strand tends to be found early and the rest skipped. (Hovering parts the fur
|
|
8305
|
+
// near the pointer in any direction, so the cone isn't used there.)
|
|
8306
|
+
float lie = radians(uLie);
|
|
8307
|
+
float R = 1.25 * uLength + uWidth;
|
|
8308
|
+
float cp = (gridNoise(p * 0.07, clumpBase, clumpGrid) - 0.5) * 1.6 * uClump;
|
|
8309
|
+
float sp = uSway * 0.7 * sin(iTime * 1.3 + p.x * 0.05 + p.y * 0.03 + 2.0 * gridNoise(p * 0.02 + iTime * 0.15, swayBase, swayGrid));
|
|
8310
|
+
// Bounds on each field's slope: 0.2376 * uClump and 0.10024 * uSway per cell.
|
|
8311
|
+
float dc = 0.2376 * uClump * R;
|
|
8312
|
+
float ds = 0.10024 * uSway * R;
|
|
8313
|
+
vec2 lo = vec2(max(-0.8 * uClump, cp - dc), max(-0.7 * uSway, sp - ds));
|
|
8314
|
+
vec2 hi = vec2(min(0.8 * uClump, cp + dc), min(0.7 * uSway, sp + ds));
|
|
8315
|
+
float mid = lie + 0.5 * (lo.x + hi.x + lo.y + hi.y);
|
|
8316
|
+
// At most 1.54, under a quarter turn.
|
|
8317
|
+
float cone = 0.5 * (hi.x - lo.x + hi.y - lo.y) + 0.04;
|
|
8318
|
+
vec2 axis = vec2(cos(mid), sin(mid));
|
|
8319
|
+
float cosCone = cos(cone);
|
|
8320
|
+
float sinCone = sin(cone);
|
|
8321
|
+
float reach = 0.16 * uDensity;
|
|
8322
|
+
bool parting = uPart * hov > 0.0;
|
|
8323
|
+
// The box roots that reach this pixel lie in, relative to it: behind it within
|
|
8324
|
+
// the cone (anywhere within reach where the pointer may be parting the fur).
|
|
8325
|
+
float back = 1.25 * uLength;
|
|
8326
|
+
vec2 e1 = -back * vec2(cos(mid + cone), sin(mid + cone));
|
|
8327
|
+
vec2 e2 = -back * vec2(cos(mid - cone), sin(mid - cone));
|
|
8328
|
+
vec2 boxLo = min(vec2(0.0), min(e1, e2));
|
|
8329
|
+
vec2 boxHi = max(vec2(0.0), max(e1, e2));
|
|
8330
|
+
if (-axis.x >= cosCone) boxHi.x = back;
|
|
8331
|
+
if (axis.x >= cosCone) boxLo.x = -back;
|
|
8332
|
+
if (-axis.y >= cosCone) boxHi.y = back;
|
|
8333
|
+
if (axis.y >= cosCone) boxLo.y = -back;
|
|
8334
|
+
float ptrGap = max(length(p - ptr) - R, 0.0);
|
|
8335
|
+
if (uPart * hov * exp(-ptrGap * ptrGap / (reach * reach)) * 1.4 > 0.02) {
|
|
8336
|
+
boxLo = vec2(-back);
|
|
8337
|
+
boxHi = vec2(back);
|
|
8338
|
+
}
|
|
8339
|
+
boxLo += p - uWidth;
|
|
8340
|
+
boxHi += p + uWidth;
|
|
8341
|
+
bvec2 up = lessThan(here, vec2(0.0));
|
|
8262
8342
|
for (int layer = 0; layer < 3; layer++) {
|
|
8263
8343
|
float fl = float(layer);
|
|
8264
8344
|
vec2 off = vec2(0.37, 0.71) * fl;
|
|
8345
|
+
// Each layer's 5 x 5 cells around the middle of the strands through here,
|
|
8346
|
+
// cut to those whose roots (in [id + off, id + off + 1)) can lie in the box.
|
|
8265
8347
|
vec2 base = floor(p - here * uLength * 0.5 - off);
|
|
8266
|
-
|
|
8267
|
-
|
|
8348
|
+
ivec2 first = ivec2(max(ceil(boxLo - off - 1.0), base - 2.0) - base);
|
|
8349
|
+
ivec2 last = ivec2(min(floor(boxHi - off), base + 2.0) - base);
|
|
8350
|
+
for (int row = 0; row < 5; row++) {
|
|
8351
|
+
if (row > last.y - first.y) break;
|
|
8352
|
+
int j = up.y ? last.y - row : first.y + row;
|
|
8353
|
+
for (int column = 0; column < 5; column++) {
|
|
8354
|
+
if (column > last.x - first.x) break;
|
|
8355
|
+
int i = up.x ? last.x - column : first.x + column;
|
|
8268
8356
|
vec2 id = base + vec2(float(i), float(j));
|
|
8269
8357
|
vec2 h = hash22(id + fl * 31.7);
|
|
8270
8358
|
vec2 root = id + off + h;
|
|
8271
|
-
vec2 dir = strandDir(root, ptr, hov);
|
|
8272
8359
|
float len = uLength * (0.75 + 0.5 * h.y);
|
|
8273
8360
|
vec2 rp = p - root;
|
|
8361
|
+
float r2 = dot(rp, rp);
|
|
8362
|
+
// Too far away, or can't rise above the strand already found (along <= r / len).
|
|
8363
|
+
float reachLen = len + uWidth;
|
|
8364
|
+
if (r2 >= reachLen * reachLen || top >= 1.0 || (top >= 0.0 && r2 <= top * top * len * len)) continue;
|
|
8365
|
+
bool parted = false;
|
|
8366
|
+
if (parting) {
|
|
8367
|
+
vec2 away = root - ptr;
|
|
8368
|
+
parted = uPart * hov * exp(-dot(away, away) / (reach * reach)) * 1.4 > 0.02;
|
|
8369
|
+
}
|
|
8370
|
+
if (r2 > uWidth * uWidth && !parted) {
|
|
8371
|
+
// Off the cone widened by the strand's width: it can't cover this pixel.
|
|
8372
|
+
float da = dot(rp, axis);
|
|
8373
|
+
if (da < cosCone * sqrt(r2 - uWidth * uWidth) - sinCone * uWidth) continue;
|
|
8374
|
+
}
|
|
8375
|
+
vec2 dir = strandDir(root, ptr, hov);
|
|
8274
8376
|
float along = clamp(dot(rp, dir) / len, 0.0, 1.0);
|
|
8275
8377
|
float w = uWidth * (1.0 - 0.75 * along);
|
|
8276
8378
|
float dist = length(rp - dir * along * len);
|
|
@@ -9465,29 +9567,61 @@ float poolY(float x, float m) {
|
|
|
9465
9567
|
return m * (0.1 + 0.012 * sin(x * 9.0 + iTime * 0.7854) + 0.025 * vnoise(vec2(x * 6.0, 2.0))) - 0.006;
|
|
9466
9568
|
}
|
|
9467
9569
|
|
|
9468
|
-
|
|
9469
|
-
|
|
9570
|
+
vec4 smin4(vec4 a, vec4 b, float k) {
|
|
9571
|
+
k = max(k, 1e-4);
|
|
9572
|
+
vec4 h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
|
|
9573
|
+
return mix(b, a, h) - k * h * (1.0 - h);
|
|
9574
|
+
}
|
|
9575
|
+
|
|
9576
|
+
// Distances to the liquid (negative inside) at four nearby points (xs, ys) at
|
|
9577
|
+
// once: a lip along the slumped edge, drips hanging from it, the puddle. Each
|
|
9578
|
+
// point sees the drips of the five columns around its own; those columns'
|
|
9579
|
+
// drips are worked out once for all four. edge and pool are edgeY and poolY
|
|
9580
|
+
// at the points; a point's distance only matters below its entry in matter.
|
|
9581
|
+
vec4 liquid(vec4 xs, vec4 ys, vec4 edge, vec4 pool, float m, vec4 matter) {
|
|
9470
9582
|
float n = float(uDrips);
|
|
9471
9583
|
float grow = smoothstep(0.0, 0.2, m);
|
|
9472
|
-
|
|
9584
|
+
vec4 e = edge + 0.01 * (1.0 - grow);
|
|
9473
9585
|
float lip = 0.022 * grow;
|
|
9474
|
-
|
|
9475
|
-
|
|
9476
|
-
|
|
9586
|
+
vec4 d = max(ys - e, e - lip - ys);
|
|
9587
|
+
vec4 cs = floor(xs * n);
|
|
9588
|
+
float c = cs.y;
|
|
9589
|
+
// Where any drip's top and head can be, from the bounds of edgeY and poolY.
|
|
9590
|
+
float topMin = 1.0 - m * 0.34 - lip * 0.5;
|
|
9591
|
+
float topMax = 1.0 - m * 0.04 - lip * 0.5;
|
|
9592
|
+
float poolMin = m * 0.088 - 0.006;
|
|
9593
|
+
// A drip farther than this beyond where a distance matters can't change it
|
|
9594
|
+
// there: the blends that follow can pull a distance down by at most this much.
|
|
9595
|
+
vec4 reach = matter + 0.16;
|
|
9596
|
+
for (int k = -3; k <= 3; k++) {
|
|
9477
9597
|
float ci = c + float(k);
|
|
9598
|
+
bvec4 near = lessThanEqual(abs(cs - ci), vec4(2.0));
|
|
9599
|
+
if (!any(near)) continue;
|
|
9478
9600
|
vec2 h = hash22(vec2(ci, 3.1));
|
|
9479
9601
|
float xi = (ci + 0.2 + 0.6 * h.x) / n;
|
|
9480
9602
|
float w = min(uViscosity * (0.7 + 0.6 * hash12(vec2(ci, 7.7))), 0.6 * aspect / n) * grow;
|
|
9603
|
+
// The nearest this drip can be to each point, before working out its shape:
|
|
9604
|
+
// skip it if it can't change any of them.
|
|
9605
|
+
float f = pow(m, 1.4) * min(0.2 + 0.95 * h.y, 1.0);
|
|
9606
|
+
float headMin = min(topMin, topMin * (1.0 - f) + f * poolMin);
|
|
9607
|
+
vec4 gap = vec4(abs(xs - xi) * aspect);
|
|
9608
|
+
vec4 rise = max(max(headMin - ys, ys - topMax), 0.0);
|
|
9609
|
+
vec4 nearest = sqrt(gap * gap + rise * rise) - 1.3 * w;
|
|
9610
|
+
bvec4 counts = bvec4(vec4(near) * step(nearest, d + 0.012 * grow) * step(nearest, reach));
|
|
9611
|
+
if (!any(counts)) continue;
|
|
9481
9612
|
float top = edgeY(xi, m) - lip * 0.5;
|
|
9482
|
-
float len =
|
|
9613
|
+
float len = f * max(top - poolY(xi, m), 0.0);
|
|
9483
9614
|
float head = top - len;
|
|
9484
|
-
|
|
9615
|
+
vec4 along = clamp(ys, head, top);
|
|
9485
9616
|
// A stem thinning as it runs, then a round, heavier head.
|
|
9486
|
-
|
|
9487
|
-
r = mix(r, w * 1.3, smoothstep(head + 3.0 * w, head,
|
|
9488
|
-
|
|
9617
|
+
vec4 r = w * mix(vec4(0.85), vec4(0.6), smoothstep(top, top - 0.3, ys));
|
|
9618
|
+
r = mix(r, vec4(w * 1.3), smoothstep(head + 3.0 * w, head, ys));
|
|
9619
|
+
vec4 dx = (xs - xi) * aspect;
|
|
9620
|
+
vec4 dy = ys - along;
|
|
9621
|
+
vec4 dd = smin4(d, sqrt(dx * dx + dy * dy) - r, 0.012 * grow);
|
|
9622
|
+
d = mix(d, dd, near);
|
|
9489
9623
|
}
|
|
9490
|
-
return
|
|
9624
|
+
return smin4(d, ys - pool, 0.03 * grow);
|
|
9491
9625
|
}
|
|
9492
9626
|
|
|
9493
9627
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
@@ -9496,23 +9630,32 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
9496
9630
|
float px = 1.0 / iResolution.y;
|
|
9497
9631
|
float m = uAnimate ? meltCycle(uSpeed) : uProgress;
|
|
9498
9632
|
|
|
9633
|
+
// The liquid is sampled at four points: under the shadow, here, and a step
|
|
9634
|
+
// right and up for its normal. They share three columns of x.
|
|
9635
|
+
float eps = 0.0015;
|
|
9636
|
+
vec4 xs = uv.x + vec4(-0.004, 0.0, eps / aspect, 0.0);
|
|
9637
|
+
vec4 ys = uv.y + vec4(0.009, 0.0, 0.0, eps);
|
|
9638
|
+
vec3 edge3 = vec3(edgeY(xs.x, m), edgeY(xs.y, m), edgeY(xs.z, m));
|
|
9639
|
+
vec3 pool3 = vec3(poolY(xs.x, m), poolY(xs.y, m), poolY(xs.z, m));
|
|
9640
|
+
// The shadow shows below 0.012, the liquid below 4 px (its neighbours a little farther).
|
|
9641
|
+
vec4 liq = liquid(xs, ys, edge3.xyzy, pool3.xyzy, m, vec4(0.012, 4.0 * px, vec2(4.0 * px + 0.005)));
|
|
9642
|
+
|
|
9499
9643
|
// The picture slumped under its edge: squashed towards the bottom, stretched thin at the top.
|
|
9500
|
-
float e =
|
|
9644
|
+
float e = edge3.y;
|
|
9501
9645
|
float h = clamp(uv.y / e, 0.0, 1.0);
|
|
9502
|
-
float
|
|
9503
|
-
float
|
|
9504
|
-
vec4 col = pictureRGBA(uImage, vec2(
|
|
9646
|
+
float ys0 = 1.0 - pow(1.0 - h, 1.0 + m * 1.3);
|
|
9647
|
+
float xs0 = uv.x + m * 0.006 * sin(uv.y * 40.0 + uv.x * 13.0);
|
|
9648
|
+
vec4 col = pictureRGBA(uImage, vec2(xs0, ys0));
|
|
9505
9649
|
vec4 behind = vec4(uBackground * (0.75 + 0.5 * uv.y), uBackdropAlpha);
|
|
9506
9650
|
col = mix(col, behind, smoothstep(e - px, e + px, uv.y));
|
|
9507
9651
|
|
|
9508
9652
|
// The liquid's shadow, down and to the right.
|
|
9509
|
-
float shadow =
|
|
9653
|
+
float shadow = liq.x;
|
|
9510
9654
|
col.rgb *= 1.0 - 0.4 * smoothstep(0.012, -0.004, shadow) * smoothstep(0.0, 0.1, m);
|
|
9511
9655
|
|
|
9512
|
-
float d =
|
|
9656
|
+
float d = liq.y;
|
|
9513
9657
|
if (d < 4.0 * px) {
|
|
9514
|
-
|
|
9515
|
-
vec2 g = vec2(liquid(uv + vec2(eps / aspect, 0.0), m) - d, liquid(uv + vec2(0.0, eps), m) - d);
|
|
9658
|
+
vec2 g = vec2(liq.z - d, liq.w - d);
|
|
9516
9659
|
g /= max(length(g), 1e-5);
|
|
9517
9660
|
float thick = sqrt(clamp(-d / 0.018, 0.0, 1.0));
|
|
9518
9661
|
vec3 nrm = normalize(vec3(g * (1.0 - thick) * 1.6, 0.4 + thick));
|
|
@@ -9520,7 +9663,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
9520
9663
|
vec4 top = pictureRGBA(uImage, vec2(uv.x + 0.01 * sin(uv.y * 25.0), 0.965));
|
|
9521
9664
|
float sx = uv.x + 0.04 * sin(uv.x * 11.0 + uv.y * 30.0);
|
|
9522
9665
|
vec4 pool = (pictureRGBA(uImage, vec2(sx - 0.06, 0.25)) + pictureRGBA(uImage, vec2(sx + 0.06, 0.4)) + pictureRGBA(uImage, vec2(sx, 0.9))) / 3.0;
|
|
9523
|
-
float inPool = smoothstep(
|
|
9666
|
+
float inPool = smoothstep(pool3.y + 0.04, pool3.y - 0.01, uv.y);
|
|
9524
9667
|
vec4 base = mix(top, pool, inPool);
|
|
9525
9668
|
base.rgb *= 0.9 + 0.12 * vnoise(vec2(uv.x * 90.0, uv.y * 3.0));
|
|
9526
9669
|
vec3 wet = liquidShade(base.rgb * 1.05, nrm, uGloss);
|
|
@@ -10449,7 +10592,18 @@ uniform float uIntensity; // @control min=0.2 max=3 default=1 label="Intensity"`
|
|
|
10449
10592
|
float w = max(0.0022, px);
|
|
10450
10593
|
float k = 1.4;
|
|
10451
10594
|
float g = 1.5;
|
|
10595
|
+
// Past this distance a streak's glow is far below one level, so sparks farther
|
|
10596
|
+
// away are skipped, and so is the whole loop outside the fountain's reach.
|
|
10597
|
+
float margin = 30.0 * w;
|
|
10598
|
+
float vmax = 1.35 * uHeight * 1.25;
|
|
10599
|
+
float xmax = vmax * sin(min(uSpread * 1.2, 1.5708)) * (1.0 - exp(-k * 1.8)) / k;
|
|
10600
|
+
float apex = log(1.0 + vmax * k / g) / k;
|
|
10601
|
+
float ea = (1.0 - exp(-k * apex)) / k;
|
|
10602
|
+
float ymax = vmax * ea - g / k * (apex - ea);
|
|
10603
|
+
vec2 rel = p - base;
|
|
10604
|
+
bool inReach = abs(rel.x) < xmax + margin && rel.y > -0.01 - margin && rel.y < ymax + margin;
|
|
10452
10605
|
for (int j = 0; j < 112; j++) {
|
|
10606
|
+
if (!inReach) break;
|
|
10453
10607
|
float fj = float(j);
|
|
10454
10608
|
vec2 h = hash22(vec2(fj, 2.31));
|
|
10455
10609
|
float life = 1.0 + 0.8 * h.y;
|
|
@@ -10459,11 +10613,14 @@ uniform float uIntensity; // @control min=0.2 max=3 default=1 label="Intensity"`
|
|
|
10459
10613
|
vec3 s = vec3(hash22(vec2(fj * 1.7, n)), hash12(vec2(n, fj + 0.37)));
|
|
10460
10614
|
if (s.z > uDensity) continue;
|
|
10461
10615
|
float a = (s.x - 0.5) * 2.0 * uSpread * 1.2;
|
|
10616
|
+
// A spark only ever moves to its own side of the base.
|
|
10617
|
+
if (a * rel.x < 0.0 && abs(rel.x) > margin) continue;
|
|
10462
10618
|
vec2 v = vec2(sin(a), cos(a)) * mix(0.9, 1.35, s.y) * uHeight * 1.25;
|
|
10463
10619
|
vec2 head = base + ballistic(v, k, g, age);
|
|
10464
10620
|
vec2 tail = base + ballistic(v, k, g, max(age - 0.07, 0.0));
|
|
10465
10621
|
// Sparks that fall back past the base are gone.
|
|
10466
10622
|
if (head.y < base.y - 0.01) continue;
|
|
10623
|
+
if (any(lessThan(p, min(head, tail) - margin)) || any(greaterThan(p, max(head, tail) + margin))) continue;
|
|
10467
10624
|
float fade = 1.0 - smoothstep(0.5, 1.0, age / life);
|
|
10468
10625
|
float twinkle = 0.6 + 0.4 * hash12(vec2(fj, floor(iTime * 24.0)));
|
|
10469
10626
|
vec3 tint = mix(vec3(1.0, 0.96, 0.85), uColor, smoothstep(0.0, 0.5, age));
|
|
@@ -11708,15 +11865,15 @@ ${BACKDROP_CONTROLS}`,
|
|
|
11708
11865
|
ROTATE,
|
|
11709
11866
|
PICTURE,
|
|
11710
11867
|
COMMON,
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
// Where cell c's particle lands: cells trade places around each 2 \xD7 2 block, turned 1\u20133 steps.
|
|
11868
|
+
`// Where cell c's particle lands: cells trade places around each 2 \xD7 2 block, turned 1\u20133 steps.
|
|
11869
|
+
// Round the block the corners go (0, 0), (1, 0), (1, 1), (0, 1); worked out rather than looked up in an array.
|
|
11714
11870
|
vec2 landing(vec2 c) {
|
|
11715
11871
|
vec2 block = floor(c * 0.5);
|
|
11716
11872
|
vec2 local = c - block * 2.0;
|
|
11717
|
-
|
|
11718
|
-
|
|
11719
|
-
|
|
11873
|
+
float i = local.y < 0.5 ? local.x : 3.0 - local.x;
|
|
11874
|
+
float turn = 1.0 + floor(hash12(block + 7.3) * 2.999);
|
|
11875
|
+
float r = mod(i + turn, 4.0);
|
|
11876
|
+
return block * 2.0 + vec2(r > 0.5 && r < 2.5 ? 1.0 : 0.0, r > 1.5 ? 1.0 : 0.0);
|
|
11720
11877
|
}
|
|
11721
11878
|
|
|
11722
11879
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
@@ -11730,26 +11887,43 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
11730
11887
|
vec2 hitTo = vec2(0.0);
|
|
11731
11888
|
float hitT = 0.0;
|
|
11732
11889
|
float hitCover = 0.0;
|
|
11733
|
-
//
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11890
|
+
// How far a particle's corners stray from the straight line between its two places: the curl, then its turned corners.
|
|
11891
|
+
float reach = max(0.7072, 0.6 * abs(uScatter + uSpin * 0.3) + 0.4951) + 0.01;
|
|
11892
|
+
// Particles travel at most about 1.5 cells, so the 5 \xD7 5 cells around are every candidate;
|
|
11893
|
+
// of those, only the cells within reach (one cell either side of the block) need looking at.
|
|
11894
|
+
vec2 lo = max(base - 2.0, ceil(p - 1.5 - reach));
|
|
11895
|
+
vec2 hi = min(base + 2.0, floor(p + 0.5 + reach));
|
|
11896
|
+
for (int j = 0; j < 5; j++) {
|
|
11897
|
+
if (lo.y + float(j) > hi.y) break;
|
|
11898
|
+
for (int i = 0; i < 5; i++) {
|
|
11899
|
+
if (lo.x + float(i) > hi.x) break;
|
|
11900
|
+
vec2 c = lo + vec2(float(i), float(j));
|
|
11901
|
+
// It stays in its 2 \xD7 2 block, so skip it if this pixel is out of its reach from there.
|
|
11902
|
+
vec2 corner = c - 2.0 * floor(c * 0.5);
|
|
11903
|
+
if (any(lessThan(p, c + 0.5 - corner - reach)) || any(greaterThan(p, c + 1.5 - corner + reach))) continue;
|
|
11737
11904
|
vec2 h = hash22(c);
|
|
11738
11905
|
// Staggered: a sweep from the left plus some randomness.
|
|
11739
11906
|
float delay = 0.5 * (0.55 * clamp(c.x / grid.x, 0.0, 1.0) + 0.45 * h.x);
|
|
11740
11907
|
float k = smoothstep(0.0, 1.0, clamp((t - delay) / 0.5, 0.0, 1.0));
|
|
11741
11908
|
float lift = sin(3.14159 * k);
|
|
11909
|
+
float z = h.y + lift;
|
|
11910
|
+
// Behind what is already here, or (now its lift is known) out of reach: skip it before working out where it is.
|
|
11911
|
+
if (z <= top) continue;
|
|
11912
|
+
float near = 0.6 * abs(uScatter + uSpin * 0.3) * lift + 0.7072 * (1.0 - 0.3 * lift) + 0.01;
|
|
11913
|
+
if (any(lessThan(p, c + 0.5 - corner - near)) || any(greaterThan(p, c + 1.5 - corner + near))) continue;
|
|
11742
11914
|
vec2 from = c + 0.5;
|
|
11743
11915
|
vec2 to = landing(c) + 0.5;
|
|
11744
11916
|
vec2 path = to - from;
|
|
11745
11917
|
vec2 side = normalize(vec2(-path.y, path.x) + (h - 0.5) * 0.8);
|
|
11746
11918
|
vec2 pos = mix(from, to, k) + side * lift * (uScatter + uSpin * 0.3) * (h.y > 0.5 ? 0.6 : -0.6);
|
|
11747
11919
|
float size = 1.0 - 0.3 * lift;
|
|
11920
|
+
// Out of reach of its corners, however it is turned.
|
|
11921
|
+
vec2 rel = p - pos;
|
|
11922
|
+
if (dot(rel, rel) > 0.501 * size * size) continue;
|
|
11748
11923
|
float angle = (h.y - 0.5) * 6.28318 * uSpin * lift;
|
|
11749
|
-
vec2 l = rot(-angle) *
|
|
11924
|
+
vec2 l = rot(-angle) * rel / size;
|
|
11750
11925
|
float m = max(abs(l.x), abs(l.y));
|
|
11751
|
-
|
|
11752
|
-
if (m <= 0.5 && z > top) {
|
|
11926
|
+
if (m <= 0.5) {
|
|
11753
11927
|
top = z;
|
|
11754
11928
|
hitFrom = from + l;
|
|
11755
11929
|
hitTo = to + l;
|
|
@@ -12688,6 +12862,24 @@ ${BACKDROP_CONTROLS}`,
|
|
|
12688
12862
|
return cell + 0.15 + 0.7 * hash22(cell);
|
|
12689
12863
|
}
|
|
12690
12864
|
|
|
12865
|
+
// No point is further than this from its own shard's site (sites sit in the middle 70% of each cell).
|
|
12866
|
+
const float SHARD_REACH = 1.25;
|
|
12867
|
+
|
|
12868
|
+
// Whether grid point q lies on shard c: c's site is the nearest of those in the 3 \xD7 3 cells around q.
|
|
12869
|
+
bool onShard(vec2 q, vec2 c) {
|
|
12870
|
+
vec2 base = floor(q);
|
|
12871
|
+
vec2 off = c - base;
|
|
12872
|
+
if (abs(off.x) > 1.0 || abs(off.y) > 1.0) return false;
|
|
12873
|
+
float own = distance(q, shardSite(c));
|
|
12874
|
+
for (int j = -1; j <= 1; j++) {
|
|
12875
|
+
for (int i = -1; i <= 1; i++) {
|
|
12876
|
+
vec2 n = base + vec2(float(i), float(j));
|
|
12877
|
+
if (n != c && distance(q, shardSite(n)) < own) return false;
|
|
12878
|
+
}
|
|
12879
|
+
}
|
|
12880
|
+
return true;
|
|
12881
|
+
}
|
|
12882
|
+
|
|
12691
12883
|
// The shard (cell) under grid point q, and how far q is from its edge (xy: cell, z: edge gap).
|
|
12692
12884
|
vec3 shardAt(vec2 q) {
|
|
12693
12885
|
vec2 base = floor(q);
|
|
@@ -12716,28 +12908,42 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
12716
12908
|
// Shard centres move out from the middle; shards keep their size, so gaps open between them.
|
|
12717
12909
|
float grow = 1.0 + uSpread * k;
|
|
12718
12910
|
vec2 base = floor(p / grow);
|
|
12719
|
-
vec4 col = vec4(uBackground, uBackdropAlpha);
|
|
12720
12911
|
float top = -1.0;
|
|
12721
|
-
|
|
12722
|
-
|
|
12723
|
-
|
|
12724
|
-
|
|
12912
|
+
vec2 hitQ = vec2(0.0);
|
|
12913
|
+
float hitA = 0.0;
|
|
12914
|
+
// Every shard that could reach this pixel (of the 5 \xD7 5 cells around, those whose site, wherever it sits
|
|
12915
|
+
// in its cell and however far it is thrown, lands close enough); the one on top (highest h.y) wins.
|
|
12916
|
+
float slack = 0.6 * k + SHARD_REACH;
|
|
12917
|
+
vec2 lo = max(base - 2.0, ceil((p - slack) / grow - 0.85));
|
|
12918
|
+
vec2 hi = min(base + 2.0, floor((p + slack) / grow - 0.15));
|
|
12919
|
+
for (int j = 0; j < 5; j++) {
|
|
12920
|
+
if (lo.y + float(j) > hi.y) break;
|
|
12921
|
+
for (int i = 0; i < 5; i++) {
|
|
12922
|
+
if (lo.x + float(i) > hi.x) break;
|
|
12923
|
+
vec2 c = lo + vec2(float(i), float(j));
|
|
12725
12924
|
vec2 h = hash22(c + 41.7);
|
|
12925
|
+
if (h.y <= top) continue;
|
|
12926
|
+
vec2 site = shardSite(c);
|
|
12726
12927
|
vec2 centre = site * grow + (h - 0.5) * 1.2 * k;
|
|
12928
|
+
vec2 rel = p - centre;
|
|
12929
|
+
if (dot(rel, rel) > SHARD_REACH * SHARD_REACH) continue;
|
|
12727
12930
|
float a = (h.x - 0.5) * 2.0 * uSpin * k;
|
|
12728
12931
|
// Back to where this pixel would sit in the unbroken image, if it is on this shard.
|
|
12729
|
-
vec2 q = rot(-a) *
|
|
12730
|
-
|
|
12731
|
-
if (s.x == c.x && s.y == c.y && h.y > top) {
|
|
12932
|
+
vec2 q = rot(-a) * rel + site;
|
|
12933
|
+
if (onShard(q, c)) {
|
|
12732
12934
|
top = h.y;
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
shard.rgb *= 1.0 - 0.35 * k * abs(sin(a));
|
|
12736
|
-
shard.rgb += k * 0.35 * smoothstep(0.06, 0.0, s.z);
|
|
12737
|
-
col = shard;
|
|
12935
|
+
hitQ = q;
|
|
12936
|
+
hitA = a;
|
|
12738
12937
|
}
|
|
12739
12938
|
}
|
|
12740
12939
|
}
|
|
12940
|
+
vec4 col = vec4(uBackground, uBackdropAlpha);
|
|
12941
|
+
if (top > -1.0) {
|
|
12942
|
+
vec2 uv = hitQ / uCount * iResolution.y / iResolution.xy + 0.5;
|
|
12943
|
+
col = pictureRGBA(uImage, uv);
|
|
12944
|
+
col.rgb *= 1.0 - 0.35 * k * abs(sin(hitA));
|
|
12945
|
+
col.rgb += k * 0.35 * smoothstep(0.06, 0.0, shardAt(hitQ).z);
|
|
12946
|
+
}
|
|
12741
12947
|
fragColor = col;
|
|
12742
12948
|
}`
|
|
12743
12949
|
),
|
|
@@ -13182,31 +13388,48 @@ uniform float uSharpness; // @control min=0.5 max=8 default=4 label="Sharpness"
|
|
|
13182
13388
|
uniform float uSaturation; // @control min=0 max=2 default=1.15 label="Saturation"
|
|
13183
13389
|
${BACKDROP_CONTROLS}`,
|
|
13184
13390
|
PICTURE,
|
|
13185
|
-
|
|
13391
|
+
`// Sums of colour and colour\xB2 over four overlapping squares (down-left, down-right, up-left, up-right),
|
|
13392
|
+
// each pixel read once: the shared middle row and column count towards both squares they border.
|
|
13393
|
+
// A macro so the image and the test card each get their own loop, rather than every read paying for both.
|
|
13394
|
+
#define GATHER(TAP) { \\
|
|
13395
|
+
vec3 c = TAP(uv); \\
|
|
13396
|
+
vec3 cc = c * c; \\
|
|
13397
|
+
m0 = c; m1 = c; m2 = c; m3 = c; \\
|
|
13398
|
+
s0 = cc; s1 = cc; s2 = cc; s3 = cc; \\
|
|
13399
|
+
for (int i = 1; i <= 8; i++) { \\
|
|
13400
|
+
if (i > uRadius) break; \\
|
|
13401
|
+
float o = float(i); \\
|
|
13402
|
+
c = TAP(uv - vec2(o * px.x, 0.0)); cc = c * c; m0 += c; s0 += cc; m2 += c; s2 += cc; \\
|
|
13403
|
+
c = TAP(uv + vec2(o * px.x, 0.0)); cc = c * c; m1 += c; s1 += cc; m3 += c; s3 += cc; \\
|
|
13404
|
+
c = TAP(uv - vec2(0.0, o * px.y)); cc = c * c; m0 += c; s0 += cc; m1 += c; s1 += cc; \\
|
|
13405
|
+
c = TAP(uv + vec2(0.0, o * px.y)); cc = c * c; m2 += c; s2 += cc; m3 += c; s3 += cc; \\
|
|
13406
|
+
} \\
|
|
13407
|
+
for (int j = 1; j <= 8; j++) { \\
|
|
13408
|
+
if (j > uRadius) break; \\
|
|
13409
|
+
for (int i = 1; i <= 8; i++) { \\
|
|
13410
|
+
if (i > uRadius) break; \\
|
|
13411
|
+
vec2 o = vec2(float(i), float(j)) * px; \\
|
|
13412
|
+
c = TAP(uv - o); m0 += c; s0 += c * c; \\
|
|
13413
|
+
c = TAP(uv + vec2(o.x, -o.y)); m1 += c; s1 += c * c; \\
|
|
13414
|
+
c = TAP(uv + vec2(-o.x, o.y)); m2 += c; s2 += c * c; \\
|
|
13415
|
+
c = TAP(uv + o); m3 += c; s3 += c * c; \\
|
|
13416
|
+
} \\
|
|
13417
|
+
} \\
|
|
13418
|
+
}
|
|
13419
|
+
|
|
13420
|
+
vec3 imageTap(vec2 uv) {
|
|
13421
|
+
return overBackdrop(texture(uImage, uv)).rgb;
|
|
13422
|
+
}
|
|
13423
|
+
|
|
13424
|
+
vec3 cardTap(vec2 uv) {
|
|
13425
|
+
return testCard(fract(uv));
|
|
13426
|
+
}
|
|
13427
|
+
|
|
13428
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
13186
13429
|
vec2 uv = fragCoord / iResolution.xy;
|
|
13187
13430
|
vec2 px = 1.0 / iResolution.xy;
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
vec3 s0 = vec3(0.0), s1 = vec3(0.0), s2 = vec3(0.0), s3 = vec3(0.0);
|
|
13191
|
-
for (int j = 0; j <= 8; j++) {
|
|
13192
|
-
if (j > uRadius) break;
|
|
13193
|
-
for (int i = 0; i <= 8; i++) {
|
|
13194
|
-
if (i > uRadius) break;
|
|
13195
|
-
vec2 o = vec2(float(i), float(j)) * px;
|
|
13196
|
-
vec3 c = picture(uImage, uv + vec2(-o.x, -o.y));
|
|
13197
|
-
m0 += c;
|
|
13198
|
-
s0 += c * c;
|
|
13199
|
-
c = picture(uImage, uv + vec2(o.x, -o.y));
|
|
13200
|
-
m1 += c;
|
|
13201
|
-
s1 += c * c;
|
|
13202
|
-
c = picture(uImage, uv + vec2(-o.x, o.y));
|
|
13203
|
-
m2 += c;
|
|
13204
|
-
s2 += c * c;
|
|
13205
|
-
c = picture(uImage, uv + o);
|
|
13206
|
-
m3 += c;
|
|
13207
|
-
s3 += c * c;
|
|
13208
|
-
}
|
|
13209
|
-
}
|
|
13431
|
+
vec3 m0, m1, m2, m3, s0, s1, s2, s3;
|
|
13432
|
+
if (textureSize(uImage, 0).x > 1) GATHER(imageTap) else GATHER(cardTap)
|
|
13210
13433
|
float n = float((uRadius + 1) * (uRadius + 1));
|
|
13211
13434
|
m0 /= n; m1 /= n; m2 /= n; m3 /= n;
|
|
13212
13435
|
// Weight each square by how calm (low-variance) it is; sharpness sets how strongly the calmest wins.
|
|
@@ -13244,7 +13467,7 @@ var imageTreatments = [
|
|
|
13244
13467
|
];
|
|
13245
13468
|
|
|
13246
13469
|
// src/catalog/shaders/trail.ts
|
|
13247
|
-
function trailHelper(dt, speed) {
|
|
13470
|
+
function trailHelper(dt, speed, walk = false) {
|
|
13248
13471
|
return `
|
|
13249
13472
|
const int TRAIL_POINTS = 32;
|
|
13250
13473
|
vec2 demoPath(float t) {
|
|
@@ -13261,6 +13484,52 @@ vec4 trailPoint(int i, bool demo) {
|
|
|
13261
13484
|
const float dt = ${dt.toFixed(5)};
|
|
13262
13485
|
float t = i == 0 ? iTime : (floor(iTime / dt) + 1.0 - float(i)) * dt;
|
|
13263
13486
|
return vec4(demoPath(t), t, 1.0);
|
|
13487
|
+
}${walk ? `
|
|
13488
|
+
${trailWalk(dt, speed)}` : ""}`.trim();
|
|
13489
|
+
}
|
|
13490
|
+
function trailWalk(dt, speed) {
|
|
13491
|
+
const rates = [1, 2.3, 2, 3.1];
|
|
13492
|
+
const turn = rates.map((r) => r * dt * speed);
|
|
13493
|
+
const vec = (f) => `vec4(${turn.map((x) => f(x).toFixed(8)).join(", ")})`;
|
|
13494
|
+
return `
|
|
13495
|
+
bool trailLive(bool demo) {
|
|
13496
|
+
return !demo || (iTrail[0].w > 0.5 && iPointer.z > 0.0);
|
|
13497
|
+
}
|
|
13498
|
+
vec4 trailAt(int i, bool live) {
|
|
13499
|
+
if (live) return iTrail[i];
|
|
13500
|
+
const float dt = ${dt.toFixed(5)};
|
|
13501
|
+
float t = i == 0 ? iTime : (floor(iTime / dt) + 1.0 - float(i)) * dt;
|
|
13502
|
+
return vec4(demoPath(t), t, 1.0);
|
|
13503
|
+
}
|
|
13504
|
+
int gWalkI;
|
|
13505
|
+
float gWalkBase;
|
|
13506
|
+
vec4 gWalkCos;
|
|
13507
|
+
vec4 gWalkSin;
|
|
13508
|
+
// Starts a walk at point i (1 or more).
|
|
13509
|
+
void trailFrom(int i, bool live) {
|
|
13510
|
+
gWalkI = i;
|
|
13511
|
+
if (live) return;
|
|
13512
|
+
const float dt = ${dt.toFixed(5)};
|
|
13513
|
+
gWalkBase = floor(iTime / dt) + 1.0;
|
|
13514
|
+
float s = (gWalkBase - float(i)) * dt * ${speed.toFixed(3)};
|
|
13515
|
+
vec4 phase = vec4(s, s * 2.3 + 1.0, s * 2.0 + 0.4, s * 3.1);
|
|
13516
|
+
gWalkCos = cos(phase);
|
|
13517
|
+
gWalkSin = sin(phase);
|
|
13518
|
+
}
|
|
13519
|
+
// The walk's next point: as trailAt(i, live) for i = the start, then one more each call.
|
|
13520
|
+
vec4 trailNext(bool live) {
|
|
13521
|
+
int i = gWalkI;
|
|
13522
|
+
gWalkI++;
|
|
13523
|
+
if (live) return iTrail[min(i, TRAIL_POINTS - 1)];
|
|
13524
|
+
const float dt = ${dt.toFixed(5)};
|
|
13525
|
+
vec2 q = vec2(0.34 * gWalkSin.x + 0.05 * gWalkSin.y, 0.27 * gWalkSin.z + 0.04 * gWalkCos.w);
|
|
13526
|
+
vec4 point = vec4(iResolution.xy * (0.5 + q), (gWalkBase - float(i)) * dt, 1.0);
|
|
13527
|
+
const vec4 turnCos = ${vec(Math.cos)};
|
|
13528
|
+
const vec4 turnSin = ${vec(Math.sin)};
|
|
13529
|
+
vec4 c = gWalkCos * turnCos + gWalkSin * turnSin;
|
|
13530
|
+
gWalkSin = gWalkSin * turnCos - gWalkCos * turnSin;
|
|
13531
|
+
gWalkCos = c;
|
|
13532
|
+
return point;
|
|
13264
13533
|
}`.trim();
|
|
13265
13534
|
}
|
|
13266
13535
|
var SEGMENT3 = `
|
|
@@ -13275,6 +13544,14 @@ vec2 catmull(vec2 p0, vec2 p1, vec2 p2, vec2 p3, float t) {
|
|
|
13275
13544
|
float t3 = t2 * t;
|
|
13276
13545
|
return 0.5 * (2.0 * p1 + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + (3.0 * p1 - p0 - 3.0 * p2 + p3) * t3);
|
|
13277
13546
|
}`.trim();
|
|
13547
|
+
var CATMULL_BOX = `
|
|
13548
|
+
float trailBoxDist(vec2 p, vec2 p0, vec2 p1, vec2 p2, vec2 p3, float scale) {
|
|
13549
|
+
vec2 c1 = p1 + (p2 - p0) / 6.0;
|
|
13550
|
+
vec2 c2 = p2 - (p3 - p1) / 6.0;
|
|
13551
|
+
vec2 lo = min(min(p1, p2), min(c1, c2)) * scale;
|
|
13552
|
+
vec2 hi = max(max(p1, p2), max(c1, c2)) * scale;
|
|
13553
|
+
return length(max(max(lo - p, p - hi), 0.0));
|
|
13554
|
+
}`.trim();
|
|
13278
13555
|
var GLOW5 = `
|
|
13279
13556
|
vec4 glow(vec3 c) {
|
|
13280
13557
|
c = 1.0 - exp(-max(c, 0.0));
|
|
@@ -13371,14 +13648,16 @@ uniform float uSpin; // @control min=0 max=10 default=2 label="Spin"
|
|
|
13371
13648
|
uniform float uShine; // @control min=0 max=1 default=0.7 label="Shine"
|
|
13372
13649
|
uniform float uLife; // @control min=0.1 max=3 default=1.1 label="Fade time"
|
|
13373
13650
|
uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
13374
|
-
trailHelper(1 / 30, 1.5),
|
|
13651
|
+
trailHelper(1 / 30, 1.5, true),
|
|
13375
13652
|
SEGMENT3,
|
|
13376
13653
|
CATMULL,
|
|
13654
|
+
CATMULL_BOX,
|
|
13377
13655
|
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
13378
13656
|
float px = 1.0 / iResolution.y;
|
|
13379
13657
|
vec2 p = fragCoord * px;
|
|
13380
|
-
|
|
13381
|
-
vec4
|
|
13658
|
+
bool live = trailLive(uDemo);
|
|
13659
|
+
vec4 a = trailAt(0, live);
|
|
13660
|
+
vec4 b = trailAt(1, live);
|
|
13382
13661
|
if (a.w < 0.5 || b.w < 0.5) {
|
|
13383
13662
|
fragColor = vec4(0.0);
|
|
13384
13663
|
return;
|
|
@@ -13389,14 +13668,16 @@ uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
|
13389
13668
|
float bestFace = 1.0;
|
|
13390
13669
|
float bestV = 0.0;
|
|
13391
13670
|
float bestFade = 0.0;
|
|
13671
|
+
trailFrom(2, live);
|
|
13392
13672
|
for (int k = 0; k < TRAIL_POINTS - 1; k++) {
|
|
13393
|
-
b = trailPoint(k + 1, uDemo);
|
|
13394
13673
|
if (b.w < 0.5 || trailJump(a, b)) break;
|
|
13395
|
-
|
|
13674
|
+
// The point after b: read once, it is the next segment's b.
|
|
13675
|
+
vec4 c = k + 2 < TRAIL_POINTS ? trailNext(live) : vec4(0.0);
|
|
13396
13676
|
vec2 next = c.w > 0.5 && !trailJump(b, c) ? c.xy : 2.0 * b.xy - a.xy;
|
|
13397
13677
|
vec2 lo = min(min(prev, a.xy), min(b.xy, next)) * px - uWidth - 2.0 * px;
|
|
13398
13678
|
vec2 hi = max(max(prev, a.xy), max(b.xy, next)) * px + uWidth + 2.0 * px;
|
|
13399
|
-
|
|
13679
|
+
// The ribbon is at most half its width either side of the spline: further off than that, a segment cannot be nearer.
|
|
13680
|
+
if (all(greaterThan(p, lo)) && all(lessThan(p, hi)) && trailBoxDist(p, prev, a.xy, b.xy, next, px) - uWidth * 0.5 < best) {
|
|
13400
13681
|
vec2 q0 = a.xy * px;
|
|
13401
13682
|
for (int j = 1; j <= 4; j++) {
|
|
13402
13683
|
float t1 = float(j) / 4.0;
|
|
@@ -13422,6 +13703,7 @@ uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
|
13422
13703
|
}
|
|
13423
13704
|
prev = a.xy;
|
|
13424
13705
|
a = b;
|
|
13706
|
+
b = c;
|
|
13425
13707
|
}
|
|
13426
13708
|
float cover = (1.0 - smoothstep(-px, px, best)) * smoothstep(0.0, 0.15, bestFade);
|
|
13427
13709
|
if (cover <= 0.0) {
|
|
@@ -13466,21 +13748,31 @@ uniform float uTwinkle; // @control min=0 max=1 default=0.7 label="Twinkle"
|
|
|
13466
13748
|
uniform float uIntensity; // @control min=0.2 max=3 default=1.2 label="Intensity"
|
|
13467
13749
|
uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
13468
13750
|
HASH,
|
|
13469
|
-
trailHelper(1 / 40, 1.4),
|
|
13751
|
+
trailHelper(1 / 40, 1.4, true),
|
|
13470
13752
|
GLOW5,
|
|
13471
13753
|
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
13472
13754
|
float px = 1.0 / iResolution.y;
|
|
13473
13755
|
vec2 p = fragCoord * px;
|
|
13474
13756
|
vec3 col = vec3(0.0);
|
|
13757
|
+
bool live = trailLive(uDemo);
|
|
13758
|
+
// How far a sparkle's light reaches from where it is (twice its largest size), and its sideways sway.
|
|
13759
|
+
float reach = 2.5 * uSize + px;
|
|
13760
|
+
float side = uSpread + 0.012 + reach;
|
|
13475
13761
|
// Point 0 is still moving with the pointer, so sparkles come from the settled points after it.
|
|
13762
|
+
trailFrom(1, live);
|
|
13476
13763
|
for (int k = 1; k < TRAIL_POINTS; k++) {
|
|
13477
|
-
vec4 q =
|
|
13764
|
+
vec4 q = trailNext(live);
|
|
13478
13765
|
if (q.w < 0.5) break;
|
|
13479
13766
|
float age = iTime - q.z;
|
|
13480
13767
|
if (age < 0.0 || age > uLife) continue;
|
|
13768
|
+
// Skip points none of whose sparkles (spread, swaying and drifting down) can reach this pixel.
|
|
13769
|
+
vec2 rel = p - q.xy * px;
|
|
13770
|
+
float fall = uFall * age * (0.4 + 0.6 * age);
|
|
13771
|
+
if (abs(rel.x) > side || rel.y > uSpread + reach || rel.y < -uSpread - fall - reach) continue;
|
|
13481
13772
|
float life = age / uLife;
|
|
13482
13773
|
// Points drop off the end of the trail: fade those about to go.
|
|
13483
13774
|
float keep = 1.0 - smoothstep(float(TRAIL_POINTS) - 6.0, float(TRAIL_POINTS) - 1.0, float(k));
|
|
13775
|
+
float env = smoothstep(0.0, 0.08, life) * (1.0 - life) * (1.0 - life) * keep;
|
|
13484
13776
|
for (int j = 0; j < 4; j++) {
|
|
13485
13777
|
if (j >= uCount) break;
|
|
13486
13778
|
vec2 h = hash22(vec2(q.z * 61.37 + float(j) * 7.13, float(j) + 0.5));
|
|
@@ -13493,13 +13785,12 @@ uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
|
13493
13785
|
// Turn some of them a little.
|
|
13494
13786
|
float an = (h.y - 0.5) * 0.9;
|
|
13495
13787
|
d = mat2(cos(an), sin(an), -sin(an), cos(an)) * d;
|
|
13496
|
-
float env = smoothstep(0.0, 0.08, life) * (1.0 - life) * (1.0 - life);
|
|
13497
13788
|
float tw = mix(1.0, 0.35 + 0.65 * (0.5 + 0.5 * sin(age * (12.0 + 12.0 * h.y) + h.x * 6.28318)), uTwinkle);
|
|
13498
13789
|
float rays = exp(-abs(d.x) / (s * 0.05)) * exp(-abs(d.y) / (s * 0.45));
|
|
13499
13790
|
rays += exp(-abs(d.y) / (s * 0.05)) * exp(-abs(d.x) / (s * 0.45));
|
|
13500
13791
|
float core = exp(-dot(d, d) / (s * s * 0.02));
|
|
13501
13792
|
vec3 tint = h3 < 0.5 ? uColorA : uColorB;
|
|
13502
|
-
col += (tint * (rays * 0.9 + core * 0.6) + vec3(1.0) * core) * env * tw
|
|
13793
|
+
col += (tint * (rays * 0.9 + core * 0.6) + vec3(1.0) * core) * env * tw;
|
|
13503
13794
|
}
|
|
13504
13795
|
}
|
|
13505
13796
|
fragColor = glow(col * uIntensity);
|
|
@@ -13532,7 +13823,7 @@ uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
|
13532
13823
|
HASH,
|
|
13533
13824
|
NOISE,
|
|
13534
13825
|
FBM,
|
|
13535
|
-
trailHelper(1 / 30, 1.3),
|
|
13826
|
+
trailHelper(1 / 30, 1.3, true),
|
|
13536
13827
|
SEGMENT3,
|
|
13537
13828
|
`// The stroke's width at a point from how fast the segment from a to b was drawn: faster, thinner.
|
|
13538
13829
|
float pressWidth(vec4 a, vec4 b, float px) {
|
|
@@ -13543,47 +13834,61 @@ float pressWidth(vec4 a, vec4 b, float px) {
|
|
|
13543
13834
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
13544
13835
|
float px = 1.0 / iResolution.y;
|
|
13545
13836
|
vec2 p = fragCoord * px;
|
|
13546
|
-
|
|
13547
|
-
vec4
|
|
13837
|
+
bool live = trailLive(uDemo);
|
|
13838
|
+
vec4 a = trailAt(0, live);
|
|
13839
|
+
vec4 b = trailAt(1, live);
|
|
13548
13840
|
float best = 1e9;
|
|
13549
13841
|
float bestU = 1.0;
|
|
13550
13842
|
float bestV = 0.0;
|
|
13551
13843
|
float bestFade = 0.0;
|
|
13844
|
+
// Ragged edges: the same noise for every segment, so read it once.
|
|
13845
|
+
float rag = 0.9 + 0.2 * vnoise(p * 180.0);
|
|
13552
13846
|
if (a.w > 0.5 && b.w > 0.5) {
|
|
13553
|
-
|
|
13847
|
+
// Faster strokes are thinner, never wider than uWidth: ragged, at most 1.1 \xD7 that from the line.
|
|
13848
|
+
float reach = 1.1 * uWidth;
|
|
13849
|
+
trailFrom(2, live);
|
|
13554
13850
|
for (int k = 0; k < TRAIL_POINTS - 1; k++) {
|
|
13555
|
-
b = trailPoint(k + 1, uDemo);
|
|
13556
13851
|
if (b.w < 0.5 || trailJump(a, b)) break;
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
vec2
|
|
13561
|
-
float
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
float
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
13852
|
+
// The point after b: read once, it is the next segment's b.
|
|
13853
|
+
vec4 next = k + 2 < TRAIL_POINTS ? trailNext(live) : vec4(0.0);
|
|
13854
|
+
// A segment whose box is further off than the stroke reaches cannot be nearer than the best so far.
|
|
13855
|
+
vec2 off = max(max(min(a.xy, b.xy) * px - p, p - max(a.xy, b.xy) * px), 0.0);
|
|
13856
|
+
float r = best + reach;
|
|
13857
|
+
if (r > 0.0 && dot(off, off) < r * r) {
|
|
13858
|
+
// The width where the segment starts is from it, where it ends from the next (or the same at the end).
|
|
13859
|
+
float wa = pressWidth(a, b, px);
|
|
13860
|
+
float wb = next.w > 0.5 && !trailJump(b, next) ? pressWidth(b, next, px) : wa;
|
|
13861
|
+
vec2 s = segment(p, a.xy * px, b.xy * px);
|
|
13862
|
+
float u = (float(k) + s.y) / float(TRAIL_POINTS - 1);
|
|
13863
|
+
float fade = clamp(1.0 - (iTime - mix(a.z, b.z, s.y)) / uLife, 0.0, 1.0);
|
|
13864
|
+
// A rounded start and a tail that thins out.
|
|
13865
|
+
float w = mix(wa, wb, s.y) * (1.0 - 0.6 * smoothstep(0.55, 1.0, u)) * smoothstep(0.0, 0.04, u + 0.01);
|
|
13866
|
+
w *= 0.4 + 0.6 * fade;
|
|
13867
|
+
float sd = s.x - w * rag;
|
|
13868
|
+
if (sd < best) {
|
|
13869
|
+
vec2 dir = b.xy - a.xy;
|
|
13870
|
+
float side = dir.x * (p.y - a.y * px) - dir.y * (p.x - a.x * px);
|
|
13871
|
+
best = sd;
|
|
13872
|
+
bestU = u;
|
|
13873
|
+
bestV = clamp(s.x / max(w, 1e-5), 0.0, 1.0) * (side < 0.0 ? -1.0 : 1.0);
|
|
13874
|
+
bestFade = fade;
|
|
13875
|
+
}
|
|
13575
13876
|
}
|
|
13576
13877
|
a = b;
|
|
13577
|
-
|
|
13878
|
+
b = next;
|
|
13578
13879
|
}
|
|
13579
13880
|
}
|
|
13580
13881
|
// Bristles streak along the stroke (they vary across it); the paper's tooth breaks them up.
|
|
13581
|
-
|
|
13582
|
-
float
|
|
13583
|
-
|
|
13584
|
-
|
|
13585
|
-
|
|
13586
|
-
|
|
13882
|
+
// Away from the stroke nothing shows, so the bristles are not worked out there.
|
|
13883
|
+
float cover = (1.0 - smoothstep(-px, px, best)) * smoothstep(0.0, 0.1, bestFade);
|
|
13884
|
+
vec3 col = uInk;
|
|
13885
|
+
if (cover > 0.0) {
|
|
13886
|
+
float bristle = 0.6 * vnoise(vec2(bestV * uBristles, 3.7)) + 0.4 * fbm(p * vec2(55.0, 55.0), 3);
|
|
13887
|
+
float dry = uDry * smoothstep(0.15, 1.0, bestU) + 0.35 * uDry * smoothstep(0.6, 1.0, abs(bestV)) + 0.7 * (1.0 - bestFade);
|
|
13888
|
+
cover *= smoothstep(dry - 0.06, dry + 0.06, bristle * 1.1);
|
|
13889
|
+
// Ink pools darker at the edges and thins where a bristle runs dry.
|
|
13890
|
+
col = uInk * (1.0 + 0.6 * (1.0 - bristle) * (1.0 - abs(bestV))) + 0.04 * (1.0 - bristle);
|
|
13891
|
+
}
|
|
13587
13892
|
if (uPaper) {
|
|
13588
13893
|
vec3 paper = uPaperColor * (0.95 + 0.07 * fbm(p * 9.0, 4)) - 0.03 * smoothstep(0.55, 0.9, vnoise(p * vec2(400.0, 30.0)));
|
|
13589
13894
|
fragColor = vec4(mix(paper, col, cover * 0.95), 1.0);
|
|
@@ -13701,9 +14006,10 @@ uniform float uLife; // @control min=0.5 max=10 default=4 label="Fade time"
|
|
|
13701
14006
|
uniform float uFlicker; // @control min=0 max=1 default=0.25 label="Flicker"
|
|
13702
14007
|
uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
13703
14008
|
HASH,
|
|
13704
|
-
trailHelper(0.05, 1.1),
|
|
14009
|
+
trailHelper(0.05, 1.1, true),
|
|
13705
14010
|
SEGMENT3,
|
|
13706
14011
|
CATMULL,
|
|
14012
|
+
CATMULL_BOX,
|
|
13707
14013
|
`vec3 spectrum(float h) {
|
|
13708
14014
|
return 0.5 + 0.5 * cos(6.28318 * (h + vec3(0.0, 0.33, 0.67)));
|
|
13709
14015
|
}
|
|
@@ -13716,8 +14022,9 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
13716
14022
|
vec3 col = uBackground * (1.0 - 0.45 * dot(uv - 0.5, uv - 0.5) * 2.0);
|
|
13717
14023
|
col += (hash12(fragCoord + fract(iTime) * 113.0) - 0.5) * 0.012;
|
|
13718
14024
|
|
|
13719
|
-
|
|
13720
|
-
vec4
|
|
14025
|
+
bool live = trailLive(uDemo);
|
|
14026
|
+
vec4 a = trailAt(0, live);
|
|
14027
|
+
vec4 b = trailAt(1, live);
|
|
13721
14028
|
if (a.w > 0.5 && b.w > 0.5) {
|
|
13722
14029
|
float glowR = uWidth * 4.0 * max(uGlow, 0.05);
|
|
13723
14030
|
float margin = uWidth * 3.0 + glowR * 6.0 + 2.0 * px;
|
|
@@ -13726,35 +14033,44 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
13726
14033
|
float halo = 0.0;
|
|
13727
14034
|
float best = 1e9;
|
|
13728
14035
|
float bestWhen = a.z;
|
|
14036
|
+
trailFrom(2, live);
|
|
13729
14037
|
for (int k = 0; k < TRAIL_POINTS - 1; k++) {
|
|
13730
|
-
b = trailPoint(k + 1, uDemo);
|
|
13731
14038
|
if (b.w < 0.5) break;
|
|
13732
14039
|
// Earlier strokes stay lit, but the jump between strokes is not drawn.
|
|
13733
14040
|
bool jump = trailJump(a, b);
|
|
13734
|
-
|
|
14041
|
+
// The point after b: read once, it is the next segment's b.
|
|
14042
|
+
vec4 c = k + 2 < TRAIL_POINTS ? trailNext(live) : vec4(0.0);
|
|
13735
14043
|
vec2 next = c.w > 0.5 && !trailJump(b, c) ? c.xy : 2.0 * b.xy - a.xy;
|
|
13736
14044
|
vec2 lo = min(min(prev, a.xy), min(b.xy, next)) * px - margin;
|
|
13737
14045
|
vec2 hi = max(max(prev, a.xy), max(b.xy, next)) * px + margin;
|
|
13738
14046
|
if (!jump && all(greaterThan(p, lo)) && all(lessThan(p, hi))) {
|
|
13739
|
-
|
|
13740
|
-
|
|
13741
|
-
|
|
13742
|
-
|
|
13743
|
-
|
|
13744
|
-
|
|
13745
|
-
|
|
13746
|
-
|
|
13747
|
-
|
|
13748
|
-
|
|
13749
|
-
|
|
13750
|
-
|
|
13751
|
-
|
|
14047
|
+
// Nothing on the segment is brighter than its newer end: skip it when it can be neither nearer
|
|
14048
|
+
// nor brighter than what is found.
|
|
14049
|
+
float dmin = trailBoxDist(p, prev, a.xy, b.xy, next, px);
|
|
14050
|
+
float top = clamp(1.0 - (iTime - max(a.z, b.z)) / uLife, 0.0, 1.0);
|
|
14051
|
+
top *= top;
|
|
14052
|
+
if (dmin < best || exp(-dmin / glowR) * top > halo || exp(-dmin * dmin / (uWidth * uWidth)) * top > core) {
|
|
14053
|
+
vec2 q0 = a.xy * px;
|
|
14054
|
+
for (int j = 1; j <= 4; j++) {
|
|
14055
|
+
vec2 q1 = catmull(prev, a.xy, b.xy, next, float(j) / 4.0) * px;
|
|
14056
|
+
vec2 s = segment(p, q0, q1);
|
|
14057
|
+
float when = mix(a.z, b.z, (float(j - 1) + s.y) / 4.0);
|
|
14058
|
+
float fade = clamp(1.0 - (iTime - when) / uLife, 0.0, 1.0);
|
|
14059
|
+
fade *= fade;
|
|
14060
|
+
float d = s.x;
|
|
14061
|
+
core = max(core, exp(-d * d / (uWidth * uWidth)) * fade);
|
|
14062
|
+
halo = max(halo, exp(-d / glowR) * fade);
|
|
14063
|
+
if (d < best) {
|
|
14064
|
+
best = d;
|
|
14065
|
+
bestWhen = when;
|
|
14066
|
+
}
|
|
14067
|
+
q0 = q1;
|
|
13752
14068
|
}
|
|
13753
|
-
q0 = q1;
|
|
13754
14069
|
}
|
|
13755
14070
|
}
|
|
13756
14071
|
prev = jump ? 2.0 * b.xy - next : a.xy;
|
|
13757
14072
|
a = b;
|
|
14073
|
+
b = c;
|
|
13758
14074
|
}
|
|
13759
14075
|
// Colour from when the light passed, so each part keeps its colour as it fades.
|
|
13760
14076
|
vec3 tint = spectrum(uHue + bestWhen * uCycle);
|
|
@@ -13803,6 +14119,16 @@ vec4 clickAt(int i, bool demo, float period) {
|
|
|
13803
14119
|
vec2 h = hash22(vec2(k * 1.37 + 0.5, 3.7));
|
|
13804
14120
|
return vec4((0.2 + 0.6 * h) * iResolution.xy, k * period, 1.0);
|
|
13805
14121
|
}`.trim();
|
|
14122
|
+
var CORNER_HASHES = `
|
|
14123
|
+
vec4 cornerHashes(vec2 i) {
|
|
14124
|
+
vec2 x = fract(vec2(i.x, i.x + 1.0) * 0.1031);
|
|
14125
|
+
vec2 y = fract(vec2(i.y, i.y + 1.0) * 0.1031);
|
|
14126
|
+
vec4 X = x.xyxy;
|
|
14127
|
+
vec4 Y = y.xxyy;
|
|
14128
|
+
vec4 d = X * (Y + 33.33) + Y * (X + 33.33) + X * (X + 33.33);
|
|
14129
|
+
vec4 px = X + d;
|
|
14130
|
+
return fract((px + (Y + d)) * px);
|
|
14131
|
+
}`.trim();
|
|
13806
14132
|
var flowParticles = {
|
|
13807
14133
|
kind: "shader",
|
|
13808
14134
|
id: "flow-particles",
|
|
@@ -13825,19 +14151,25 @@ uniform vec3 uFast; // @control color default=#7CFFE0 label="Fast colour
|
|
|
13825
14151
|
uniform vec3 uBackground; // @control color default=#04060E label="Background"
|
|
13826
14152
|
uniform bool uTransparent; // @control default=false label="Transparent"`,
|
|
13827
14153
|
HASH,
|
|
13828
|
-
|
|
14154
|
+
CORNER_HASHES,
|
|
13829
14155
|
GLOW6,
|
|
13830
|
-
|
|
13831
|
-
|
|
14156
|
+
`// The gradient of vnoise, worked out exactly (its value is not needed here).
|
|
14157
|
+
vec2 vnoiseGrad(vec2 p) {
|
|
14158
|
+
vec2 i = floor(p);
|
|
14159
|
+
vec2 f = fract(p);
|
|
14160
|
+
vec2 u = f * f * (3.0 - 2.0 * f);
|
|
14161
|
+
vec2 du = 6.0 * f * (1.0 - f);
|
|
14162
|
+
vec4 h = cornerHashes(i);
|
|
14163
|
+
float k = h.x - h.y - h.z + h.w;
|
|
14164
|
+
return du * vec2(h.y - h.x + k * u.y, h.z - h.x + k * u.x);
|
|
13832
14165
|
}
|
|
13833
14166
|
|
|
13834
|
-
// The curl of the potential (
|
|
14167
|
+
// The curl of the potential vnoise(p + vec2(t, -0.6 t)) + turbulence * 0.5 * vnoise(p * 2.3 - vec2(0.7 t, t) + 9.1)
|
|
14168
|
+
// (divergence free, so streams swirl rather than pool), plus a gentle drift.
|
|
13835
14169
|
vec2 flow(vec2 p, float t) {
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
13839
|
-
float dy = potential(p + vec2(0.0, e), t) - c;
|
|
13840
|
-
return vec2(dy, -dx) / e + vec2(0.35, 0.08);
|
|
14170
|
+
vec2 g = vnoiseGrad(p + vec2(t, -0.6 * t));
|
|
14171
|
+
if (uTurbulence > 0.0) g += uTurbulence * 1.15 * vnoiseGrad(p * 2.3 - vec2(0.7 * t, t) + 9.1);
|
|
14172
|
+
return vec2(g.y, -g.x) + vec2(0.35, 0.08);
|
|
13841
14173
|
}
|
|
13842
14174
|
|
|
13843
14175
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
@@ -13847,22 +14179,38 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
13847
14179
|
float ft = iTime * uSpeed * 0.04;
|
|
13848
14180
|
float tail = max(uTail * float(STEPS), 1.0);
|
|
13849
14181
|
vec3 col = vec3(0.0);
|
|
14182
|
+
// The walk stays a few steps in each cell, so its particle is worked out once per cell.
|
|
14183
|
+
vec2 cellAt = vec2(-1e9);
|
|
14184
|
+
bool alive = false;
|
|
14185
|
+
float life = 0.0;
|
|
14186
|
+
vec2 at = vec2(0.0);
|
|
14187
|
+
vec2 v = vec2(0.0);
|
|
14188
|
+
float sp = 0.0;
|
|
13850
14189
|
for (int k = 0; k < STEPS; k++) {
|
|
13851
|
-
|
|
13852
|
-
|
|
14190
|
+
// The field barely turns over a step, so it is looked up every other step.
|
|
14191
|
+
if ((k & 1) == 0) {
|
|
14192
|
+
v = flow(q * uScale, ft);
|
|
14193
|
+
sp = length(v);
|
|
14194
|
+
}
|
|
13853
14195
|
// One particle per cell at most; each life starts somewhere new in the cell.
|
|
13854
14196
|
vec2 c = floor(q * uDensity);
|
|
13855
|
-
|
|
13856
|
-
|
|
13857
|
-
|
|
13858
|
-
|
|
13859
|
-
|
|
13860
|
-
|
|
13861
|
-
|
|
13862
|
-
|
|
13863
|
-
|
|
13864
|
-
|
|
13865
|
-
|
|
14197
|
+
if (c != cellAt) {
|
|
14198
|
+
cellAt = c;
|
|
14199
|
+
float h0 = hash12(c + 0.31);
|
|
14200
|
+
float phase = iTime * uSpeed * (0.3 + 0.3 * h0) + hash12(c + 7.7);
|
|
14201
|
+
life = fract(phase);
|
|
14202
|
+
float cyc = floor(phase);
|
|
14203
|
+
alive = hash12(c * 1.7 + cyc * 3.1) < uAmount;
|
|
14204
|
+
if (alive) at = (c + 0.25 + 0.5 * hash22(c + cyc * 17.13)) * cell;
|
|
14205
|
+
}
|
|
14206
|
+
// This pixel lies k steps downstream of the particle's start; its head is "head" steps along.
|
|
14207
|
+
float behind = life * float(STEPS) - float(k);
|
|
14208
|
+
if (alive && behind >= 0.0 && behind < tail) {
|
|
14209
|
+
vec2 dq = (q - at) / cell;
|
|
14210
|
+
float d2 = dot(dq, dq);
|
|
14211
|
+
// exp(-14 d^2) > 0.01
|
|
14212
|
+
if (d2 < 0.329) {
|
|
14213
|
+
float spot = exp(-d2 * 14.0);
|
|
13866
14214
|
float w = 1.0 - behind / tail;
|
|
13867
14215
|
float fade = smoothstep(0.0, 0.08, life) * (1.0 - smoothstep(0.75, 1.0, life));
|
|
13868
14216
|
vec3 hue = mix(uSlow, uFast, smoothstep(0.3, 2.2, sp));
|
|
@@ -14130,12 +14478,40 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
14130
14478
|
float t = iTime * uSpeed * 0.5;
|
|
14131
14479
|
float len = 0.018 * uSize;
|
|
14132
14480
|
float cover = 0.0;
|
|
14481
|
+
// A bird is within its formation offset, jitter and size of the line between the two flocks
|
|
14482
|
+
// it is moving between, so first find which pairs of flocks pass near this pixel (bit a * 4 + b).
|
|
14483
|
+
float groups = float(uGroups);
|
|
14484
|
+
vec2 flocks[4];
|
|
14485
|
+
for (int g = 0; g < 4; g++) flocks[g] = flockAt(float(g), t, aspect);
|
|
14486
|
+
float margin = 1.89 * uSpread + 0.036 * uJitter + 2.04 * len + 2.0 * px;
|
|
14487
|
+
int near = 0;
|
|
14488
|
+
for (int a = 0; a < 4; a++) {
|
|
14489
|
+
if (a >= uGroups) break;
|
|
14490
|
+
for (int b = 0; b < 4; b++) {
|
|
14491
|
+
if (b >= uGroups) break;
|
|
14492
|
+
vec2 pa = p - flocks[a];
|
|
14493
|
+
vec2 ba = flocks[b] - flocks[a];
|
|
14494
|
+
vec2 off = pa - ba * clamp(dot(pa, ba) / max(dot(ba, ba), 1e-8), 0.0, 1.0);
|
|
14495
|
+
if (dot(off, off) < margin * margin) near |= 1 << (a * 4 + b);
|
|
14496
|
+
}
|
|
14497
|
+
}
|
|
14133
14498
|
for (int i = 0; i < 120; i++) {
|
|
14134
|
-
if (i >= uCount) break;
|
|
14499
|
+
if (near == 0 || i >= uCount) break;
|
|
14135
14500
|
float fi = float(i);
|
|
14501
|
+
// birdAt, with the flock centres looked up and far birds dropped before the rest of the work.
|
|
14502
|
+
vec2 h = hash22(vec2(fi, 3.1));
|
|
14503
|
+
float sw = t * 0.06 * (0.6 + 0.8 * h.y) + h.x * 10.0;
|
|
14504
|
+
float k = floor(sw);
|
|
14505
|
+
int ga = int(floor(hash12(vec2(fi, k)) * groups));
|
|
14506
|
+
float glide = smoothstep(0.6, 1.0, fract(sw));
|
|
14507
|
+
int gb = glide > 0.0 ? int(floor(hash12(vec2(fi, k + 1.0)) * groups)) : ga;
|
|
14508
|
+
if ((near & (1 << (ga * 4 + gb))) == 0) continue;
|
|
14509
|
+
vec2 o = (hash22(vec2(fi, 8.3)) - 0.5) * 2.0 * uSpread;
|
|
14510
|
+
o.x *= 1.6;
|
|
14511
|
+
vec2 jitter = uJitter * 0.025 * vec2(sin(t * (1.1 + h.x) + fi), cos(t * (1.3 + h.y) + fi * 1.7));
|
|
14512
|
+
vec2 pos = mix(flocks[ga], flocks[gb], glide) + o + jitter;
|
|
14136
14513
|
float scale = 0.7 + 0.5 * hash12(vec2(fi, 6.6));
|
|
14137
14514
|
float l = len * scale;
|
|
14138
|
-
vec2 pos = birdAt(fi, t, aspect);
|
|
14139
14515
|
vec2 dp = p - pos;
|
|
14140
14516
|
if (abs(dp.x) > l * 1.2 || abs(dp.y) > l * 1.2) continue;
|
|
14141
14517
|
vec2 v = pos - birdAt(fi, t - 0.03, aspect);
|
|
@@ -14194,7 +14570,7 @@ uniform float uDensity; // @control min=40 max=240 default=120 step=1 label
|
|
|
14194
14570
|
uniform float uScatter; // @control min=0.1 max=1.5 default=0.4 label="Scatter distance"
|
|
14195
14571
|
uniform float uDirection; // @control min=-180 max=180 default=20 label="Drift direction"`,
|
|
14196
14572
|
HASH,
|
|
14197
|
-
|
|
14573
|
+
CORNER_HASHES,
|
|
14198
14574
|
`// The text image's alpha at frame point p (height 1, centred), the image contained in uSize of the frame.
|
|
14199
14575
|
float letters(vec2 p) {
|
|
14200
14576
|
vec2 size = vec2(textureSize(uText, 0));
|
|
@@ -14206,6 +14582,15 @@ float letters(vec2 p) {
|
|
|
14206
14582
|
return texture(uText, uv).a;
|
|
14207
14583
|
}
|
|
14208
14584
|
|
|
14585
|
+
// Half the size of the text's box in the frame (letters is 0 outside it).
|
|
14586
|
+
vec2 lettersHalf() {
|
|
14587
|
+
vec2 size = vec2(textureSize(uText, 0));
|
|
14588
|
+
float aspect = size.x / max(size.y, 1.0);
|
|
14589
|
+
float frame = iResolution.x / iResolution.y;
|
|
14590
|
+
float h = min(uSize, frame * uSize / aspect);
|
|
14591
|
+
return 0.5 * vec2(h * aspect, h);
|
|
14592
|
+
}
|
|
14593
|
+
|
|
14209
14594
|
// How far through dissolving the particle at home is (0 in place, 1 gone): left to right, then back together.
|
|
14210
14595
|
float scattered(vec2 home, float phase) {
|
|
14211
14596
|
float frame = iResolution.x / iResolution.y;
|
|
@@ -14216,16 +14601,40 @@ float scattered(vec2 home, float phase) {
|
|
|
14216
14601
|
return a * a * (3.0 - 2.0 * a);
|
|
14217
14602
|
}
|
|
14218
14603
|
|
|
14604
|
+
// vnoise, keeping the corner hashes of the lattice cell it last looked in: the wind is smooth and
|
|
14605
|
+
// its lattice coarse, so most lookups land in the same cell as the one before.
|
|
14606
|
+
vec2 eddyCellA = vec2(-1e9);
|
|
14607
|
+
vec2 eddyCellB = vec2(-1e9);
|
|
14608
|
+
vec4 eddyHashA = vec4(0.0);
|
|
14609
|
+
vec4 eddyHashB = vec4(0.0);
|
|
14610
|
+
|
|
14611
|
+
float eddy(vec2 p, inout vec2 cellAt, inout vec4 h) {
|
|
14612
|
+
vec2 i = floor(p);
|
|
14613
|
+
if (i != cellAt) {
|
|
14614
|
+
cellAt = i;
|
|
14615
|
+
h = cornerHashes(i);
|
|
14616
|
+
}
|
|
14617
|
+
vec2 f = fract(p);
|
|
14618
|
+
vec2 u = f * f * (3.0 - 2.0 * f);
|
|
14619
|
+
return mix(mix(h.x, h.y, u.x), mix(h.z, h.w, u.x), u.y);
|
|
14620
|
+
}
|
|
14621
|
+
|
|
14622
|
+
// Group g's gust: its wind (xy) and how much it lifts what it carries (z).
|
|
14623
|
+
vec3 gustOf(float g) {
|
|
14624
|
+
float ang = radians(uDirection) + (fract(g * 0.618) - 0.5) * 1.2;
|
|
14625
|
+
return vec3(vec2(cos(ang), sin(ang)) * (0.3 + 0.18 * g), 0.3 * (0.5 + 0.2 * g));
|
|
14626
|
+
}
|
|
14627
|
+
|
|
14219
14628
|
// Where the wind has carried a particle of group g from home: smooth in home, so it can be inverted.
|
|
14220
14629
|
// Each of the six groups rides its own gust (speed, heading and eddies), so they spread apart.
|
|
14221
|
-
vec2 carried(vec2 home, float phase, float g) {
|
|
14630
|
+
vec2 carried(vec2 home, float phase, float g, vec3 gust) {
|
|
14222
14631
|
float a = scattered(home, phase);
|
|
14223
|
-
|
|
14224
|
-
|
|
14632
|
+
// Still at home (most of the frame, most of the time): no need for the eddies.
|
|
14633
|
+
if (a <= 0.0) return vec2(0.0);
|
|
14225
14634
|
float t = phase * 3.0;
|
|
14226
14635
|
vec2 q = home * 3.0 + g * 5.3;
|
|
14227
|
-
vec2 swirl = vec2(
|
|
14228
|
-
return a * uScatter * (
|
|
14636
|
+
vec2 swirl = vec2(eddy(q + vec2(t, 0.0), eddyCellA, eddyHashA), eddy(q + vec2(4.7, t), eddyCellB, eddyHashB)) - 0.5;
|
|
14637
|
+
return a * uScatter * (gust.xy + swirl * 0.8 + vec2(0.0, gust.z * a));
|
|
14229
14638
|
}
|
|
14230
14639
|
|
|
14231
14640
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
@@ -14239,22 +14648,28 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
14239
14648
|
vec3 col = uColor;
|
|
14240
14649
|
float cover = solid;
|
|
14241
14650
|
|
|
14651
|
+
// Every particle is home outside this part of the loop.
|
|
14652
|
+
bool moving = phase > 0.12 && phase < 0.94;
|
|
14653
|
+
vec2 box = lettersHalf() + 2.0 * cell;
|
|
14242
14654
|
for (int gi = 0; gi < 6; gi++) {
|
|
14655
|
+
if (!moving) break;
|
|
14243
14656
|
float g = float(gi);
|
|
14657
|
+
vec3 gust = gustOf(g);
|
|
14244
14658
|
// Trace back to the home a particle of this group here would have come from, then search the cells round it.
|
|
14245
14659
|
vec2 home = p;
|
|
14246
|
-
for (int i = 0; i < 4; i++) home = p - carried(home, phase, g);
|
|
14660
|
+
for (int i = 0; i < 4; i++) home = p - carried(home, phase, g, gust);
|
|
14661
|
+
// The cells searched are all off the text.
|
|
14662
|
+
if (any(greaterThan(abs(home), box))) continue;
|
|
14247
14663
|
vec2 base = floor(home / cell);
|
|
14248
14664
|
for (int y = -1; y <= 1; y++) {
|
|
14249
14665
|
for (int x = -1; x <= 1; x++) {
|
|
14250
14666
|
vec2 c = base + vec2(float(x), float(y));
|
|
14251
14667
|
if (floor(hash12(c + 0.73) * 6.0) != g) continue;
|
|
14252
14668
|
vec2 hc = (c + 0.5) * cell;
|
|
14253
|
-
if (letters(hc) < 0.5) continue;
|
|
14254
14669
|
float a = scattered(hc, phase);
|
|
14255
|
-
if (a < 0.002) continue;
|
|
14670
|
+
if (a < 0.002 || letters(hc) < 0.5) continue;
|
|
14256
14671
|
vec2 j = hash22(c);
|
|
14257
|
-
vec2 pos = hc + carried(hc, phase, g) + (j - 0.5) * cell * 1.1 * a;
|
|
14672
|
+
vec2 pos = hc + carried(hc, phase, g, gust) + (j - 0.5) * cell * 1.1 * a;
|
|
14258
14673
|
float r = cell * (0.5 - 0.1 * a) * (0.75 + 0.5 * j.x);
|
|
14259
14674
|
float d = length(p - pos);
|
|
14260
14675
|
float disc = (1.0 - smoothstep(r - px, r + px * 0.5, d)) * (1.0 - smoothstep(0.6, 1.0, a) * 0.6 * j.y);
|
|
@@ -14312,14 +14727,32 @@ vec4 blast(vec4 acc, vec2 p, vec2 origin, float angle, float spread, float speed
|
|
|
14312
14727
|
float e = (1.0 - exp(-k * age)) / k;
|
|
14313
14728
|
float fade = 1.0 - smoothstep(LIFE - 1.0, LIFE, age);
|
|
14314
14729
|
float reach = 0.03 * uSize;
|
|
14730
|
+
// Every piece is within its sway and the spread of fall speeds of the fan its launch sweeps out
|
|
14731
|
+
// (speeds 0.5 to 1 times speed, within spread of angle, all dropped by the middle fall speed),
|
|
14732
|
+
// so skip the blast where this pixel is clear of that.
|
|
14733
|
+
float drop = 0.16 * uFall * (age - e);
|
|
14734
|
+
vec2 rel = p - origin + vec2(0.0, drop);
|
|
14735
|
+
float slack = reach * 1.42 + 0.035 + 0.3 * drop + px;
|
|
14736
|
+
float r = length(rel);
|
|
14737
|
+
float far = speed * e;
|
|
14738
|
+
if (r > far + slack || r < 0.5 * far - slack) return acc;
|
|
14739
|
+
if (spread < 6.2 && r > slack) {
|
|
14740
|
+
float off = abs(mod(atan(rel.y, rel.x) - angle + 3.14159265, 6.28318531) - 3.14159265);
|
|
14741
|
+
if (off > spread * 0.5 + asin(slack / r) + 0.01) return acc;
|
|
14742
|
+
}
|
|
14315
14743
|
for (int j = 0; j < 80; j++) {
|
|
14316
14744
|
if (j >= count) break;
|
|
14317
14745
|
float fj = float(j);
|
|
14318
14746
|
vec2 h = hash22(vec2(fj * 7.13 + 1.0, seed));
|
|
14747
|
+
float launch = speed * (0.5 + 0.5 * h.y);
|
|
14748
|
+
// The same tests for this piece before working out the rest of it: how far out, then where.
|
|
14749
|
+
if (abs(r - launch * e) > slack) continue;
|
|
14750
|
+
float a = angle + (h.x - 0.5) * spread;
|
|
14751
|
+
vec2 v0 = vec2(cos(a), sin(a)) * launch;
|
|
14752
|
+
vec2 dv = rel - v0 * e;
|
|
14753
|
+
if (abs(dv.x) > reach + 0.035 || abs(dv.y) > reach + 0.3 * drop) continue;
|
|
14319
14754
|
vec2 h2 = hash22(h * 53.1 + seed);
|
|
14320
14755
|
float h3 = hash12(h2 * 17.7 + fj);
|
|
14321
|
-
float a = angle + (h.x - 0.5) * spread;
|
|
14322
|
-
vec2 v0 = vec2(cos(a), sin(a)) * speed * (0.5 + 0.5 * h.y);
|
|
14323
14756
|
float fall = 0.16 * uFall * (0.7 + 0.6 * h2.x);
|
|
14324
14757
|
vec2 pos = origin + v0 * e - vec2(0.0, fall * (age - e));
|
|
14325
14758
|
pos.x += 0.035 * sin(age * (2.0 + 2.0 * h2.y) + h.x * 6.28) * smoothstep(0.3, 1.2, age);
|
|
@@ -14732,18 +15165,6 @@ vec3 shardOf(vec2 q) {
|
|
|
14732
15165
|
return vec3(sid, ring, edge);
|
|
14733
15166
|
}
|
|
14734
15167
|
|
|
14735
|
-
// A shard's middle, which it turns about, and how far it reaches from there.
|
|
14736
|
-
vec3 shardCentre(float sid, float ring) {
|
|
14737
|
-
float a0 = spoke(sid);
|
|
14738
|
-
float a1 = spoke(sid + 1.0);
|
|
14739
|
-
float mid = 0.5 * (a0 + a1);
|
|
14740
|
-
float inner = ring > 0.0 ? ringAt(sid, ring) : 0.0;
|
|
14741
|
-
float outer = ring + 1.0 < float(RINGS) ? ringAt(sid, ring + 1.0) : inner * uGrowth;
|
|
14742
|
-
float rc = ring > 0.0 ? 0.5 * (inner + outer) : 0.66 * outer;
|
|
14743
|
-
float reach = length(vec2(max(outer - rc, rc - inner), outer * tan(0.5 * (a1 - a0))));
|
|
14744
|
-
return vec3(vec2(cos(mid), sin(mid)) * rc, reach + 0.01);
|
|
14745
|
-
}
|
|
14746
|
-
|
|
14747
15168
|
// When a shard lets go: the middle first, the outer pane last.
|
|
14748
15169
|
float release(float sid, float ring) {
|
|
14749
15170
|
return 0.3 + 0.16 * ring + 0.25 * hash12(vec2(sid, ring + 9.0));
|
|
@@ -14765,33 +15186,109 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
14765
15186
|
vec4 col = vec4(uBackground, 1.0);
|
|
14766
15187
|
|
|
14767
15188
|
// Shards that have let go, falling and spinning: the first to fall is in front.
|
|
15189
|
+
// A ring at a time: the rings all of whose shards are too far from this pixel, wherever they are
|
|
15190
|
+
// in their fall, are passed over whole; later rings let go later, so once one is not yet falling
|
|
15191
|
+
// (or would be behind what is already here) neither are the rest.
|
|
14768
15192
|
float best = 1e3;
|
|
14769
15193
|
vec4 hit = vec4(0.0);
|
|
14770
15194
|
vec2 hitCentre = vec2(0.0);
|
|
14771
|
-
|
|
14772
|
-
|
|
14773
|
-
|
|
14774
|
-
|
|
14775
|
-
|
|
15195
|
+
float n = float(uSpokes);
|
|
15196
|
+
float spreadMax = tan(min(0.8 * TAU / n, 1.5));
|
|
15197
|
+
for (int j = 0; j < RINGS; j++) {
|
|
15198
|
+
float ring = float(j);
|
|
15199
|
+
float firstGo = 0.3 + 0.16 * ring;
|
|
15200
|
+
float dtHi = age - firstGo;
|
|
15201
|
+
if (dtHi <= 0.0 || firstGo >= best) break;
|
|
15202
|
+
float dtLo = max(dtHi - 0.25, 0.0);
|
|
15203
|
+
// Bounds on this ring's shards: how far out their middles are, how far they reach, how far they have drifted and dropped.
|
|
15204
|
+
float span = uScale * pow(uGrowth, ring);
|
|
15205
|
+
float lo = j > 0 ? span / uGrowth : 0.0;
|
|
15206
|
+
float rcLo = j > 0 ? 0.425 * (lo + span) : 0.561 * span;
|
|
15207
|
+
float rcHi = j > 0 ? 0.575 * (lo + span) : 0.759 * span;
|
|
15208
|
+
float radial = j > 0 ? 0.5 * max(1.15 * span - 0.85 * lo, 1.15 * lo - 0.85 * span) : rcHi;
|
|
15209
|
+
float reachMax = length(vec2(radial, 1.15 * span * spreadMax)) + 0.02;
|
|
15210
|
+
float rMin = rcLo + 0.05 * (1.0 - exp(-0.6 * dtLo)) / 0.6 / (1.0 + ring) - reachMax - 0.01;
|
|
15211
|
+
float rMax = rcHi + 0.2 * (1.0 - exp(-0.6 * dtHi)) / 0.6 / (1.0 + ring) + reachMax + 0.01;
|
|
15212
|
+
float yA = ballistic(vec2(0.0, 0.08), 0.6, uGravity, dtLo).y;
|
|
15213
|
+
float yB = ballistic(vec2(0.0, 0.08), 0.6, uGravity, dtHi).y;
|
|
15214
|
+
float yLo = min(yA, yB) - 0.02;
|
|
15215
|
+
float yHi = max(yA, yB) + 0.02;
|
|
15216
|
+
float dNear = length(vec2(q.x, q.y - clamp(q.y, yLo, yHi)));
|
|
15217
|
+
float dFar = max(length(q - vec2(0.0, yLo)), length(q - vec2(0.0, yHi)));
|
|
15218
|
+
if (dFar < rMin || dNear > rMax) continue;
|
|
15219
|
+
// And of its shards, only those whose sector points (near enough) at this pixel from where the impact point has dropped to:
|
|
15220
|
+
// each sector's middle lies 0.2\u20130.8 of the way round its share of the circle.
|
|
15221
|
+
float first = 0.0;
|
|
15222
|
+
float count = n;
|
|
15223
|
+
float slack = reachMax + 0.01;
|
|
15224
|
+
if (dNear > slack) {
|
|
15225
|
+
vec2 na = normalize(q - vec2(0.0, yLo));
|
|
15226
|
+
vec2 nb = normalize(q - vec2(0.0, yHi));
|
|
15227
|
+
float w = 0.5 * acos(clamp(dot(na, nb), -1.0, 1.0)) + asin(slack / dNear);
|
|
15228
|
+
vec2 bisect = na + nb;
|
|
15229
|
+
float toward = atan(bisect.y, bisect.x) / (TAU / n);
|
|
15230
|
+
w /= TAU / n;
|
|
15231
|
+
first = ceil(toward - w - 0.8);
|
|
15232
|
+
count = min(floor(toward + w - 0.2) - first + 1.0, n);
|
|
15233
|
+
}
|
|
15234
|
+
for (int k = 0; k < 16; k++) {
|
|
15235
|
+
if (float(k) >= count) break;
|
|
15236
|
+
float sid = mod(first + float(k), n);
|
|
14776
15237
|
float tr = release(sid, ring);
|
|
14777
15238
|
float dt = age - tr;
|
|
14778
15239
|
if (dt <= 0.0 || tr >= best) continue;
|
|
15240
|
+
// How far it has drifted (along e) and dropped since it let go: ballistic(), split so the drop can be used first.
|
|
15241
|
+
float e = (1.0 - exp(-0.6 * dt)) / 0.6;
|
|
15242
|
+
// Seen from where the impact point has dropped to by now, too near or far, or not in this sector's direction.
|
|
15243
|
+
vec2 dropped = q - vec2(0.0, 0.08 * e - uGravity / 0.6 * (dt - e));
|
|
15244
|
+
float far = length(dropped);
|
|
15245
|
+
if (far < rMin || far > rMax) continue;
|
|
15246
|
+
// Its two cracks, the direction halfway between them and tan of half the angle they make.
|
|
15247
|
+
float a0 = spoke(sid);
|
|
15248
|
+
float a1 = spoke(sid + 1.0);
|
|
15249
|
+
vec2 d0 = vec2(cos(a0), sin(a0));
|
|
15250
|
+
vec2 d1 = vec2(cos(a1), sin(a1));
|
|
15251
|
+
vec2 dir = normalize(d0 + d1);
|
|
15252
|
+
float spread = (d0.x * d1.y - d0.y * d1.x) / (1.0 + dot(d0, d1));
|
|
15253
|
+
// Not pointing (near enough) this way: further off its middle than any of its corners could be.
|
|
15254
|
+
float near = length(vec2(radial, 1.15 * span * spread)) + 0.03;
|
|
15255
|
+
if (far > near && dot(dropped, dir) < sqrt(far * far - near * near)) continue;
|
|
15256
|
+
// Its middle, which it turns about, and how far it reaches from there (ringAt, with the ring's scale worked out above).
|
|
15257
|
+
float inner = j > 0 ? lo * (0.85 + 0.3 * hash12(vec2(sid, ring + 0.5))) : 0.0;
|
|
15258
|
+
float outer = j + 1 < RINGS ? span * (0.85 + 0.3 * hash12(vec2(sid, ring + 1.5))) : inner * uGrowth;
|
|
15259
|
+
float rc = j > 0 ? 0.5 * (inner + outer) : 0.66 * outer;
|
|
15260
|
+
float reach = length(vec2(max(outer - rc, rc - inner), outer * spread)) + 0.01;
|
|
15261
|
+
vec2 cen = dir * rc;
|
|
14779
15262
|
vec2 h = hash22(vec2(sid, ring) + 17.3);
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
vec2 rel = q - cen.xy - ballistic(v0, 0.6, uGravity, dt);
|
|
14783
|
-
if (length(rel) > cen.z) continue;
|
|
15263
|
+
vec2 rel = dropped - cen - normalize(cen + 1e-4) * (0.05 + 0.15 * h.x) / (1.0 + ring) * e;
|
|
15264
|
+
if (dot(rel, rel) > reach * reach) continue;
|
|
14784
15265
|
float ang = (h.y - 0.5) * 2.0 * uSpin * dt;
|
|
14785
15266
|
// It also turns about its own axis, narrowing and catching the light.
|
|
14786
15267
|
float tilt = cos(dt * (0.6 + 1.2 * h.x) * uSpin);
|
|
14787
15268
|
vec2 local = rot(-ang) * rel;
|
|
14788
15269
|
local.x /= sign(tilt) * max(abs(tilt), 0.05);
|
|
14789
|
-
vec2 orig = cen
|
|
14790
|
-
|
|
14791
|
-
|
|
15270
|
+
vec2 orig = cen + local;
|
|
15271
|
+
// Back on the pane, is that point inside this shard: between its two cracks and its two rings?
|
|
15272
|
+
float side0 = d0.x * orig.y - d0.y * orig.x;
|
|
15273
|
+
float side1 = d1.x * orig.y - d1.y * orig.x;
|
|
15274
|
+
float d = dot(orig, dir);
|
|
15275
|
+
float rim = j + 1 < RINGS ? outer : 1e3;
|
|
15276
|
+
if (side0 < 0.0 || side1 >= 0.0 || d >= rim) continue;
|
|
15277
|
+
// Past every ring inside it (they are a little ragged, so not only the nearest).
|
|
15278
|
+
bool past = true;
|
|
15279
|
+
for (int i = 1; i < RINGS; i++) {
|
|
15280
|
+
if (i > j) break;
|
|
15281
|
+
if (d < ringAt(sid, float(i))) {
|
|
15282
|
+
past = false;
|
|
15283
|
+
break;
|
|
15284
|
+
}
|
|
15285
|
+
}
|
|
15286
|
+
if (!past) continue;
|
|
15287
|
+
float edge = min(rim - d, min(abs(side0), abs(side1)));
|
|
15288
|
+
if (j > 0) edge = min(edge, d - inner);
|
|
14792
15289
|
best = tr;
|
|
14793
|
-
hit = vec4(orig, tilt,
|
|
14794
|
-
hitCentre = cen
|
|
15290
|
+
hit = vec4(orig, tilt, edge);
|
|
15291
|
+
hitCentre = cen;
|
|
14795
15292
|
}
|
|
14796
15293
|
}
|
|
14797
15294
|
|
|
@@ -15273,6 +15770,14 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
15273
15770
|
float b = now - float(k);
|
|
15274
15771
|
float a = e - b * BATCH;
|
|
15275
15772
|
if (a < 0.0 || a > LIFE) continue;
|
|
15773
|
+
// Where its grains came from, give or take the turbulence: skip the batch unless that is in
|
|
15774
|
+
// the shape and in the slab of it this batch was taken from (see batchOf).
|
|
15775
|
+
vec2 from = (p - vec2(dirX * uWind * (0.15 * a + 0.55 * a * a), 0.05 * a + 0.03 * a * a) - origin) / vec2(shapeW, shapeH);
|
|
15776
|
+
vec2 sway = uTurbulence * a * vec2(0.04, 0.11) / vec2(shapeW, shapeH) + 1e-3;
|
|
15777
|
+
if (uFromRight) from.x = 1.0 - from.x;
|
|
15778
|
+
float slabLo = max((b * BATCH - 0.775) / 1.5, 0.0);
|
|
15779
|
+
float slabHi = min(((b + 1.0) * BATCH - 0.125) / 1.5, 1.0);
|
|
15780
|
+
if (from.x + sway.x < slabLo || from.x - sway.x > slabHi || from.y + sway.y < 0.0 || from.y - sway.y > 1.0) continue;
|
|
15276
15781
|
// Back from where a grain is to where it came from.
|
|
15277
15782
|
vec2 s = p - blown(a, p);
|
|
15278
15783
|
s = p - blown(a, s);
|
|
@@ -16256,12 +16761,64 @@ uniform float uSpeed; // @control min=0 max=3 default=0.6 label="Drift spe
|
|
|
16256
16761
|
`const float BOTTOM = 1.5;
|
|
16257
16762
|
const float TOP = 3.2;
|
|
16258
16763
|
|
|
16259
|
-
|
|
16260
|
-
|
|
16261
|
-
|
|
16764
|
+
// The noise's lattice cell last used, and its eight corner hashes. Successive
|
|
16765
|
+
// samples along a ray (and toward the sun) mostly fall in the same cell of the
|
|
16766
|
+
// coarse octaves, so they reuse its hashes instead of hashing it again.
|
|
16767
|
+
struct Cell {
|
|
16768
|
+
vec3 id;
|
|
16769
|
+
vec4 lo;
|
|
16770
|
+
vec4 hi;
|
|
16771
|
+
};
|
|
16772
|
+
|
|
16773
|
+
Cell noCell() {
|
|
16774
|
+
return Cell(vec3(1e9), vec4(0.0), vec4(0.0));
|
|
16775
|
+
}
|
|
16776
|
+
|
|
16777
|
+
// vnoise3(p), with its corners taken from the cell when p is still in it.
|
|
16778
|
+
float cellNoise(vec3 p, inout Cell c) {
|
|
16779
|
+
vec3 i = floor(p);
|
|
16780
|
+
if (i != c.id) {
|
|
16781
|
+
c.id = i;
|
|
16782
|
+
c.lo = vec4(hash13(i), hash13(i + vec3(1.0, 0.0, 0.0)), hash13(i + vec3(0.0, 1.0, 0.0)), hash13(i + vec3(1.0, 1.0, 0.0)));
|
|
16783
|
+
c.hi = vec4(hash13(i + vec3(0.0, 0.0, 1.0)), hash13(i + vec3(1.0, 0.0, 1.0)), hash13(i + vec3(0.0, 1.0, 1.0)), hash13(i + vec3(1.0, 1.0, 1.0)));
|
|
16784
|
+
}
|
|
16785
|
+
vec3 f = fract(p);
|
|
16786
|
+
vec3 u = f * f * (3.0 - 2.0 * f);
|
|
16787
|
+
float a = mix(c.lo.x, c.lo.y, u.x);
|
|
16788
|
+
float b = mix(c.lo.z, c.lo.w, u.x);
|
|
16789
|
+
float cc = mix(c.hi.x, c.hi.y, u.x);
|
|
16790
|
+
float d = mix(c.hi.z, c.hi.w, u.x);
|
|
16791
|
+
return mix(mix(a, b, u.y), mix(cc, d, u.y), u.z);
|
|
16792
|
+
}
|
|
16793
|
+
|
|
16794
|
+
// fbm3(p, octaves) for three octaves or more, the first three through cells c0, c1 and c2.
|
|
16795
|
+
float cloudNoise(vec3 p, int octaves, inout Cell c0, inout Cell c1, inout Cell c2) {
|
|
16796
|
+
float sum = 0.5 * cellNoise(p, c0);
|
|
16797
|
+
p = p * 2.03 + vec3(11.7, 3.1, 7.9);
|
|
16798
|
+
sum += 0.25 * cellNoise(p, c1);
|
|
16799
|
+
p = p * 2.03 + vec3(11.7, 3.1, 7.9);
|
|
16800
|
+
sum += 0.125 * cellNoise(p, c2);
|
|
16801
|
+
p = p * 2.03 + vec3(11.7, 3.1, 7.9);
|
|
16802
|
+
float amp = 0.0625;
|
|
16803
|
+
for (int i = 3; i < 6; i++) {
|
|
16804
|
+
if (i >= octaves) break;
|
|
16805
|
+
sum += amp * vnoise3(p);
|
|
16806
|
+
p = p * 2.03 + vec3(11.7, 3.1, 7.9);
|
|
16807
|
+
amp *= 0.5;
|
|
16808
|
+
}
|
|
16809
|
+
return sum;
|
|
16810
|
+
}
|
|
16811
|
+
|
|
16812
|
+
// The cloud's density at p from its noise to the given octaves; rest stands in
|
|
16813
|
+
// for the octaves left out (their average), so the cloud keeps its mass.
|
|
16814
|
+
float cloudDensity(vec3 p, int octaves, float rest, inout Cell c0, inout Cell c1, inout Cell c2) {
|
|
16262
16815
|
float h = clamp((p.y - BOTTOM) / (TOP - BOTTOM), 0.0, 1.0);
|
|
16263
16816
|
// Rounded tops and flat-ish bases.
|
|
16264
16817
|
float shape = smoothstep(0.0, 0.15, h) * (1.0 - smoothstep(0.45, 1.0, h));
|
|
16818
|
+
// Outside the slab there's no cloud, whatever the noise.
|
|
16819
|
+
if (shape <= 0.0) return 0.0;
|
|
16820
|
+
vec3 q = p * 0.42 + vec3(iTime * uSpeed * 0.12, 0.0, iTime * uSpeed * 0.05);
|
|
16821
|
+
float n = cloudNoise(q, octaves, c0, c1, c2) + rest;
|
|
16265
16822
|
return clamp((n - (1.0 - uCoverage) * 0.75) * 3.0 * shape, 0.0, 1.0) * uDensity;
|
|
16266
16823
|
}
|
|
16267
16824
|
|
|
@@ -16280,21 +16837,35 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
16280
16837
|
if (rd.y > 0.02) {
|
|
16281
16838
|
float t0 = BOTTOM / rd.y;
|
|
16282
16839
|
float t1 = min(TOP / rd.y, t0 + 14.0);
|
|
16283
|
-
|
|
16840
|
+
// Steps of about 0.35 (as long as a ray toward the horizon takes in 40), so
|
|
16841
|
+
// short, steep rays take fewer: between 28 and 40 (fewer shows the jitter as grain).
|
|
16842
|
+
float steps = clamp(ceil((t1 - t0) / 0.35), 28.0, 40.0);
|
|
16843
|
+
float dt = (t1 - t0) / steps;
|
|
16284
16844
|
float t = t0 + dt * fract(sin(dot(fragCoord, vec2(12.9898, 78.233))) * 43758.5453);
|
|
16285
16845
|
// Henyey-Greenstein: a forward lobe for the silver lining plus a soft back lobe.
|
|
16286
16846
|
float g = 0.65;
|
|
16287
16847
|
float hg = (1.0 - g * g) / pow(1.0 + g * g - 2.0 * g * mu, 1.5) / 12.566;
|
|
16288
16848
|
float phase = mix(0.08, hg * 4.0, 0.7);
|
|
16849
|
+
// Noise cells along the ray (m) and toward the sun (l).
|
|
16850
|
+
Cell m0 = noCell();
|
|
16851
|
+
Cell m1 = noCell();
|
|
16852
|
+
Cell m2 = noCell();
|
|
16853
|
+
Cell l0 = noCell();
|
|
16854
|
+
Cell l1 = noCell();
|
|
16855
|
+
Cell l2 = noCell();
|
|
16289
16856
|
for (int i = 0; i < 40; i++) {
|
|
16290
|
-
if (trans < 0.02 || t > 60.0) break;
|
|
16857
|
+
if (trans < 0.02 || t > 60.0 || float(i) >= steps) break;
|
|
16291
16858
|
vec3 pos = rd * t;
|
|
16292
|
-
|
|
16293
|
-
|
|
16859
|
+
// Fine detail where it shows: five octaves near and in front, fewer once
|
|
16860
|
+
// the cloud in front hides most of this sample, three far away.
|
|
16861
|
+
int oct = t < 18.0 ? (trans > 0.5 ? 5 : trans > 0.15 ? 4 : 3) : 3;
|
|
16862
|
+
float rest = t < 18.0 ? 0.5 * (exp2(-float(oct)) - exp2(-5.0)) : 0.0;
|
|
16863
|
+
float d = cloudDensity(pos, oct, rest, m0, m1, m2);
|
|
16294
16864
|
if (d > 0.01) {
|
|
16295
|
-
// Light march toward the sun for self-shadowing.
|
|
16865
|
+
// Light march toward the sun for self-shadowing: the cloud between 0.11 and
|
|
16866
|
+
// 0.99 toward it, sampled at the middles of its two halves.
|
|
16296
16867
|
float shade = 0.0;
|
|
16297
|
-
for (int j =
|
|
16868
|
+
for (int j = 0; j < 2; j++) shade += 2.0 * cloudDensity(pos + sun * (0.33 + 0.44 * float(j)), 3, 0.0, l0, l1, l2);
|
|
16298
16869
|
float light = exp(-shade * 1.4);
|
|
16299
16870
|
float powder = 1.0 - exp(-d * 4.0);
|
|
16300
16871
|
vec3 lit = mix(uShadowColor, uCloudColor, light) + uSunColor * light * powder * phase * 2.5;
|
|
@@ -16769,6 +17340,75 @@ float coverage(vec2 p, float radius) {
|
|
|
16769
17340
|
}
|
|
16770
17341
|
return sum / 16.0;
|
|
16771
17342
|
}`.trim();
|
|
17343
|
+
function goldenTaps(n11) {
|
|
17344
|
+
const taps = Array.from({ length: n11 }, (_, i) => {
|
|
17345
|
+
const r = Math.sqrt((i + 0.5) / n11);
|
|
17346
|
+
const a = i * 2.39996;
|
|
17347
|
+
return `vec2(${(r * Math.cos(a)).toFixed(5)}, ${(r * Math.sin(a)).toFixed(5)})`;
|
|
17348
|
+
});
|
|
17349
|
+
return `const vec2 GOLDEN${n11}[${n11}] = vec2[${n11}](${taps.join(", ")});`;
|
|
17350
|
+
}
|
|
17351
|
+
function coverageTaps(name, n11, blur) {
|
|
17352
|
+
return `
|
|
17353
|
+
${goldenTaps(n11)}
|
|
17354
|
+
float ${name}(vec2 p, float radius) {
|
|
17355
|
+
float sum = 0.0;
|
|
17356
|
+
if (gMaskPlace.w < 0.5) {
|
|
17357
|
+
for (int i = 0; i < ${n11}; i++) sum += shapeAt(p + radius * GOLDEN${n11}[i], radius * ${blur.toFixed(2)});
|
|
17358
|
+
} else {
|
|
17359
|
+
float lod = log2(max(radius * ${blur.toFixed(2)} * gMaskPlace.z, 1.0));
|
|
17360
|
+
for (int i = 0; i < ${n11}; i++) sum += shapeLod(p + radius * GOLDEN${n11}[i], lod);
|
|
17361
|
+
}
|
|
17362
|
+
return sum / ${n11}.0;
|
|
17363
|
+
}`.trim();
|
|
17364
|
+
}
|
|
17365
|
+
var LITE_TAPS = 10;
|
|
17366
|
+
var MASK_FAST = `
|
|
17367
|
+
vec3 gKeyColour;
|
|
17368
|
+
float gKeyTolerance;
|
|
17369
|
+
// Picture units per frame unit (xy), texels per frame unit (z), 1 with a picture (w).
|
|
17370
|
+
vec4 gMaskPlace;
|
|
17371
|
+
|
|
17372
|
+
// The colour in the picture's top-left corner: the ground to key out; and where the picture sits.
|
|
17373
|
+
void setKey(float tolerance) {
|
|
17374
|
+
gKeyTolerance = tolerance;
|
|
17375
|
+
vec4 c = textureLod(uMask, vec2(0.01, 0.99), 0.0);
|
|
17376
|
+
gKeyColour = c.rgb / max(c.a, 1e-3);
|
|
17377
|
+
vec2 size = vec2(textureSize(uMask, 0));
|
|
17378
|
+
float aspect = size.x / size.y;
|
|
17379
|
+
float h = min(uSize, iResolution.x / iResolution.y * uSize / aspect);
|
|
17380
|
+
gMaskPlace = vec4(1.0 / (h * aspect), 1.0 / h, size.y / h, size.x > 1.0 ? 1.0 : 0.0);
|
|
17381
|
+
}
|
|
17382
|
+
|
|
17383
|
+
// Where frame point p falls in the picture (xy) and texels per frame unit (z).
|
|
17384
|
+
vec3 maskPlace(vec2 p) {
|
|
17385
|
+
return vec3(p * gMaskPlace.xy + 0.5, gMaskPlace.z);
|
|
17386
|
+
}
|
|
17387
|
+
|
|
17388
|
+
// The picture's coverage at p read at mip level lod (needs a picture).
|
|
17389
|
+
float shapeLod(vec2 p, float lod) {
|
|
17390
|
+
vec2 uv = p * gMaskPlace.xy + 0.5;
|
|
17391
|
+
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) return 0.0;
|
|
17392
|
+
vec4 c = textureLod(uMask, uv, lod);
|
|
17393
|
+
float a = c.a;
|
|
17394
|
+
if (gKeyTolerance > 0.0) {
|
|
17395
|
+
float away = distance(c.rgb / max(c.a, 1e-3), gKeyColour);
|
|
17396
|
+
a *= smoothstep(gKeyTolerance, gKeyTolerance + 0.12, away);
|
|
17397
|
+
}
|
|
17398
|
+
return a;
|
|
17399
|
+
}
|
|
17400
|
+
|
|
17401
|
+
float shapeAt(vec2 p, float blur) {
|
|
17402
|
+
if (gMaskPlace.w < 0.5) {
|
|
17403
|
+
float d = length(p) - 0.3 * uSize;
|
|
17404
|
+
return clamp(0.5 - d / max(blur, 0.004), 0.0, 1.0);
|
|
17405
|
+
}
|
|
17406
|
+
return shapeLod(p, log2(max(blur * gMaskPlace.z, 1.0)));
|
|
17407
|
+
}
|
|
17408
|
+
|
|
17409
|
+
${coverageTaps("coverage", 16, 0.4)}
|
|
17410
|
+
|
|
17411
|
+
${coverageTaps("coverageLite", LITE_TAPS, 0.5)}`.trim();
|
|
16772
17412
|
var aura = {
|
|
16773
17413
|
kind: "shader",
|
|
16774
17414
|
id: "aura",
|
|
@@ -16793,7 +17433,7 @@ uniform float uIntensity; // @control min=0 max=3 default=1.1 label="Intensit
|
|
|
16793
17433
|
HASH,
|
|
16794
17434
|
NOISE,
|
|
16795
17435
|
FBM,
|
|
16796
|
-
|
|
17436
|
+
MASK_FAST,
|
|
16797
17437
|
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
16798
17438
|
setKey(uKey);
|
|
16799
17439
|
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
|
|
@@ -16805,10 +17445,11 @@ uniform float uIntensity; // @control min=0 max=3 default=1.1 label="Intensit
|
|
|
16805
17445
|
float n2 = fbm(vec2(q.x * 1.7 + 4.1, q.y * 0.9 - t * 2.9), 4);
|
|
16806
17446
|
float pulse = 1.0 + uPulse * (0.3 * sin(t * 3.0) + 0.12 * sin(t * 7.3 + 1.0));
|
|
16807
17447
|
// The silhouette blurred at three widths, swayed and stretched upward by the noise, so light gathers above it.
|
|
17448
|
+
// The two wide blurs, torn by the noise anyway, take fewer taps.
|
|
16808
17449
|
vec2 warp = vec2((n2 - 0.5) * uSpread * 1.5, -(n - 0.3) * uHeight);
|
|
16809
17450
|
float inner = coverage(p + warp * 0.3, uSpread * 0.5);
|
|
16810
|
-
float outer =
|
|
16811
|
-
float tall =
|
|
17451
|
+
float outer = coverageLite(p + warp, uSpread * 1.4);
|
|
17452
|
+
float tall = coverageLite(p + warp * 1.5 - vec2(0.0, uHeight), uSpread * 2.2);
|
|
16812
17453
|
float field = (inner * 0.9 + outer * 0.8 + tall * 0.6) * pulse;
|
|
16813
17454
|
// Torn into tongues: the noise breaks the thin outer field into licks, the thick inner field holds.
|
|
16814
17455
|
float tongues = smoothstep(0.12, 0.95, field * (0.45 + 1.1 * n));
|
|
@@ -16818,7 +17459,7 @@ uniform float uIntensity; // @control min=0 max=3 default=1.1 label="Intensit
|
|
|
16818
17459
|
light *= uIntensity;
|
|
16819
17460
|
vec3 col = uBackground + (1.0 - exp(-light));
|
|
16820
17461
|
// The figure in front, its own colours, rim-lit by the aura.
|
|
16821
|
-
if (
|
|
17462
|
+
if (gMaskPlace.w > 0.5 && body > 0.0) {
|
|
16822
17463
|
vec3 m = maskPlace(p);
|
|
16823
17464
|
vec4 c = texture(uMask, m.xy);
|
|
16824
17465
|
vec3 own = c.rgb / max(c.a, 1e-3);
|
|
@@ -17059,7 +17700,7 @@ uniform float uSpeed; // @control min=0 max=2 default=0.4 label="Ray drif
|
|
|
17059
17700
|
uniform float uIntensity; // @control min=0 max=3 default=1.3 label="Intensity"`,
|
|
17060
17701
|
HASH,
|
|
17061
17702
|
NOISE,
|
|
17062
|
-
|
|
17703
|
+
MASK_FAST,
|
|
17063
17704
|
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
17064
17705
|
setKey(uKey);
|
|
17065
17706
|
float px = 1.0 / iResolution.y;
|
|
@@ -17068,17 +17709,21 @@ uniform float uIntensity; // @control min=0 max=3 default=1.3 label="Intensit
|
|
|
17068
17709
|
vec2 toL = L - p;
|
|
17069
17710
|
float dl = length(toL);
|
|
17070
17711
|
// Light reaching here: march toward the light, the shape shadowing what is behind it.
|
|
17712
|
+
// 24 jittered steps (decay and sum scaled to match 0.965 per 1/40 of the way); steps three light
|
|
17713
|
+
// sizes or more from it add almost nothing, so they skip reading the shape.
|
|
17071
17714
|
float shafts = 0.0;
|
|
17072
17715
|
float decay = 1.0;
|
|
17073
17716
|
float jitter = hash12(fragCoord);
|
|
17074
|
-
|
|
17075
|
-
|
|
17076
|
-
|
|
17077
|
-
float
|
|
17078
|
-
|
|
17079
|
-
|
|
17080
|
-
|
|
17081
|
-
|
|
17717
|
+
float far = 3.0 * uGlowSize;
|
|
17718
|
+
for (int i = 0; i < 24; i++) {
|
|
17719
|
+
float s = (float(i) + jitter) / 24.0;
|
|
17720
|
+
float r = dl * (1.0 - s);
|
|
17721
|
+
float open = r < far ? 1.0 - shapeAt(p + toL * s, 2.0 * px + 0.01 * s) : 1.0;
|
|
17722
|
+
float g = r / uGlowSize;
|
|
17723
|
+
shafts += open * exp(-g * g) * decay;
|
|
17724
|
+
decay *= 0.94236;
|
|
17725
|
+
}
|
|
17726
|
+
shafts /= 12.0;
|
|
17082
17727
|
// Streaks round the light, drifting slowly.
|
|
17083
17728
|
vec2 dir = -toL / max(dl, 1e-4);
|
|
17084
17729
|
float a = iTime * uSpeed * 0.2;
|
|
@@ -17086,7 +17731,7 @@ uniform float uIntensity; // @control min=0 max=3 default=1.3 label="Intensit
|
|
|
17086
17731
|
float rays = shafts * mix(1.0, 0.25 + 1.5 * streak * streak, uRays);
|
|
17087
17732
|
// Spill: light wrapping just round the edges.
|
|
17088
17733
|
float body = shapeAt(p, 1.2 * px);
|
|
17089
|
-
float spill = coverage(p, 0.03) * 0.8 +
|
|
17734
|
+
float spill = coverage(p, 0.03) * 0.8 + coverageLite(p, 0.08) * 0.5;
|
|
17090
17735
|
float glowAt = exp(-pow(dl / (uGlowSize * 1.6), 2.0));
|
|
17091
17736
|
vec3 light = uLight * (rays * 1.4 + spill * (1.0 - body) * glowAt * 1.2) * uIntensity;
|
|
17092
17737
|
vec3 col = uBackground + (1.0 - exp(-light));
|
|
@@ -19534,4 +20179,4 @@ export {
|
|
|
19534
20179
|
effectControls,
|
|
19535
20180
|
effectDefaults
|
|
19536
20181
|
};
|
|
19537
|
-
//# sourceMappingURL=chunk-
|
|
20182
|
+
//# sourceMappingURL=chunk-3RKDS64M.js.map
|