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