@pixerate/flair 0.20.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 +18 -0
- package/README.md +7 -3
- package/dist/catalog/index.d.ts +2 -2
- package/dist/catalog/index.js +1 -1
- package/dist/{chunk-TCNONB22.js → chunk-ECBG5K6E.js} +3151 -376
- package/dist/chunk-ECBG5K6E.js.map +1 -0
- package/dist/{chunk-MZ37DE7K.js → chunk-LVPIIWPP.js} +2 -2
- package/dist/{chunk-YCQ5RU74.js → chunk-PFGGPIMT.js} +5 -1
- package/dist/chunk-PFGGPIMT.js.map +1 -0
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.js +2 -2
- package/dist/index.js +2 -2
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +3 -3
- package/dist/svelte/index.d.ts +1 -1
- package/dist/svelte/index.js +3 -3
- package/dist/{types-CawMRRXq.d.ts → types-Cy5EihZ8.d.ts} +1 -1
- package/dist/vector/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-TCNONB22.js.map +0 -1
- package/dist/chunk-YCQ5RU74.js.map +0 -1
- /package/dist/{chunk-MZ37DE7K.js.map → chunk-LVPIIWPP.js.map} +0 -0
|
@@ -3510,8 +3510,154 @@ var lineBoil = {
|
|
|
3510
3510
|
};
|
|
3511
3511
|
var comicEffects = [soundEffect, speechBubble, fightCloud, dizzyStars, animeEmotes, cartoonPoof, lineBoil];
|
|
3512
3512
|
|
|
3513
|
-
// src/catalog/
|
|
3513
|
+
// src/catalog/glow.ts
|
|
3514
3514
|
var r16 = (v) => Math.round(v * 10) / 10;
|
|
3515
|
+
var n7 = (name) => `{{${name}}}`;
|
|
3516
|
+
var SANS4 = `system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif`;
|
|
3517
|
+
var mixHex2 = (a, b, t) => {
|
|
3518
|
+
const x = Array.isArray(a) ? a : [1, 1, 1];
|
|
3519
|
+
const y = Array.isArray(b) ? b : [1, 1, 1];
|
|
3520
|
+
return rgbToHex([0, 1, 2].map((i) => x[i] + (y[i] - x[i]) * t));
|
|
3521
|
+
};
|
|
3522
|
+
function fit3(text, width, max) {
|
|
3523
|
+
const e = Math.max(0.6, textWidthEm(String(text)));
|
|
3524
|
+
const size = Math.min(max, width / e);
|
|
3525
|
+
return { size: r16(size), length: r16(e * size) };
|
|
3526
|
+
}
|
|
3527
|
+
var RINGS = 3;
|
|
3528
|
+
var RING_FROM = 18;
|
|
3529
|
+
var RING_TO = 112;
|
|
3530
|
+
function ringAnimations(k) {
|
|
3531
|
+
const at3 = (phase2) => ({ r: r16(RING_FROM + (RING_TO - RING_FROM) * phase2), o: Math.round((1 - phase2) * 100) / 100 });
|
|
3532
|
+
const id = `glowRingPulseRing${k}`;
|
|
3533
|
+
const phase = k / RINGS;
|
|
3534
|
+
const from = at3(phase);
|
|
3535
|
+
const end = at3(1);
|
|
3536
|
+
const start = at3(0);
|
|
3537
|
+
const out = [
|
|
3538
|
+
{
|
|
3539
|
+
id,
|
|
3540
|
+
attrs: { r: [from.r, end.r], opacity: [from.o, end.o], "stroke-width": [n7(`ws${k}`), n7("wEnd")] },
|
|
3541
|
+
ms: n7(`rest${k}`)
|
|
3542
|
+
}
|
|
3543
|
+
];
|
|
3544
|
+
if (phase > 0)
|
|
3545
|
+
out.push({
|
|
3546
|
+
id,
|
|
3547
|
+
attrs: { r: [start.r, from.r], opacity: [start.o, from.o], "stroke-width": [n7("wStart"), n7(`ws${k}`)] },
|
|
3548
|
+
delay: n7(`rest${k}`),
|
|
3549
|
+
ms: n7(`lead${k}`)
|
|
3550
|
+
});
|
|
3551
|
+
return out;
|
|
3552
|
+
}
|
|
3553
|
+
var glowRingPulse = {
|
|
3554
|
+
kind: "vector",
|
|
3555
|
+
id: "glow-ring-pulse",
|
|
3556
|
+
name: "Glow ring pulse",
|
|
3557
|
+
description: "Glowing rings pulsing out from a softly breathing core in endless waves, like a beacon or a live indicator.",
|
|
3558
|
+
category: "glow",
|
|
3559
|
+
tags: ["glow", "ring", "pulse", "beacon", "live", "loop", "radar"],
|
|
3560
|
+
hold: 0,
|
|
3561
|
+
presets: [
|
|
3562
|
+
{ name: "Live", values: { ring: [1, 0.25, 0.35], core: [1, 0.72, 0.75], period: 1.6 } },
|
|
3563
|
+
{ name: "Gold", values: { ring: [1, 0.75, 0.25], core: [1, 0.95, 0.75], background: [0.08, 0.06, 0.03], glow: 8 } },
|
|
3564
|
+
{ name: "Light", values: { background: [0.96, 0.97, 1], ring: [0.31, 0.27, 0.9], core: [0.55, 0.5, 1], glow: 4 } }
|
|
3565
|
+
],
|
|
3566
|
+
derived: (v) => {
|
|
3567
|
+
const period = Math.round(Number(v.period) * 1e3);
|
|
3568
|
+
const width = Number(v.width);
|
|
3569
|
+
const out = { half: Math.round(period / 2), wStart: r16(width * 1.6), wEnd: r16(width * 0.4) };
|
|
3570
|
+
for (let k = 0; k < RINGS; k++) {
|
|
3571
|
+
const phase = k / RINGS;
|
|
3572
|
+
out[`rest${k}`] = Math.round(period * (1 - phase));
|
|
3573
|
+
out[`lead${k}`] = period - out[`rest${k}`];
|
|
3574
|
+
out[`ws${k}`] = r16(width * (1.6 - 1.2 * phase));
|
|
3575
|
+
}
|
|
3576
|
+
return out;
|
|
3577
|
+
},
|
|
3578
|
+
motion: {
|
|
3579
|
+
svg: `<svg viewBox="0 0 240 240"><!-- {{period: float min=0.8 max=6 default=2.4 step=0.1 label='Seconds per wave'}} {{width: float min=1 max=10 default=4 step=0.5 label='Ring width'}} --><defs><filter id="glowRingPulseGlow" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur in="SourceGraphic" stdDeviation="{{glow: float min=0 max=16 default=6 step=0.5 label='Glow'}}" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter><radialGradient id="glowRingPulseCoreFill"><stop offset="0" stop-color="#FFFFFF"/><stop offset="0.45" stop-color="{{core: color default=#A5F3FC label='Core'}}"/><stop offset="1" stop-color="{{ring: color default=#22D3EE label='Ring'}}"/></radialGradient></defs><rect width="240" height="240" fill="{{background: color default=#060A14 label='Background'}}"/><g filter="url(#glowRingPulseGlow)">${Array.from(
|
|
3580
|
+
{ length: RINGS },
|
|
3581
|
+
(_, k) => `<circle id="glowRingPulseRing${k}" cx="120" cy="120" r="${r16(RING_FROM + (RING_TO - RING_FROM) * k / RINGS)}" fill="none" stroke="{{ring}}" stroke-width="{{ws${k}}}" opacity="${Math.round((1 - k / RINGS) * 100) / 100}"/>`
|
|
3582
|
+
).join("")}<circle id="glowRingPulseCore" cx="120" cy="120" r="22" fill="url(#glowRingPulseCoreFill)"/></g></svg>`,
|
|
3583
|
+
animate: [
|
|
3584
|
+
...Array.from({ length: RINGS }, (_, k) => ringAnimations(k)).flat(),
|
|
3585
|
+
// The core breathes once a period, out and back, so it too loops seamlessly.
|
|
3586
|
+
{ id: "glowRingPulseCore", attrs: { r: [22, 26] }, ms: n7("half"), ease: "in-out" },
|
|
3587
|
+
{ id: "glowRingPulseCore", attrs: { r: [26, 22] }, delay: n7("half"), ms: n7("half"), ease: "in-out" }
|
|
3588
|
+
]
|
|
3589
|
+
}
|
|
3590
|
+
};
|
|
3591
|
+
var haloPose = (s) => `translate(160 70) scale(${s}) translate(-160 -70)`;
|
|
3592
|
+
var glowButton = {
|
|
3593
|
+
kind: "vector",
|
|
3594
|
+
id: "glow-button",
|
|
3595
|
+
name: "Glow button",
|
|
3596
|
+
description: "A pill button with a soft glow that, on hover, brightens, spreads its glow wider and lifts a little.",
|
|
3597
|
+
category: "glow",
|
|
3598
|
+
tags: ["glow", "button", "hover", "interactive", "ui", "cta", "neon"],
|
|
3599
|
+
trigger: "hover",
|
|
3600
|
+
hold: 800,
|
|
3601
|
+
presets: [
|
|
3602
|
+
{ name: "Violet", values: { accent: [0.55, 0.36, 0.96], background: [0.05, 0.04, 0.09] } },
|
|
3603
|
+
{ name: "Mint", values: { accent: [0.2, 0.9, 0.6], ink: [0.02, 0.15, 0.1], label: "Start free trial" } },
|
|
3604
|
+
{ name: "Sunset", values: { accent: [1, 0.45, 0.3], label: "Join now" } }
|
|
3605
|
+
],
|
|
3606
|
+
derived: (v) => {
|
|
3607
|
+
const { size, length } = fit3(v.label, 180, 22);
|
|
3608
|
+
return { size, length, base: r16(70 + size * 0.35), bright: mixHex2(v.accent, [1, 1, 1], 0.3) };
|
|
3609
|
+
},
|
|
3610
|
+
motion: {
|
|
3611
|
+
svg: `<svg viewBox="0 0 320 140"><!-- {{label: text default='Get started' label='Label'}} {{background: color default=#0A0D18 label='Background'}} {{accent: color default=#38BDF8 label='Accent'}} {{ink: color default=#04121C label='Label colour'}} --><defs><filter id="glowButtonBlur" x="-60%" y="-120%" width="220%" height="340%"><feGaussianBlur id="glowButtonSpread" stdDeviation="10"/></filter><linearGradient id="glowButtonSheen" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#FFFFFF" stop-opacity="0.35"/><stop offset="0.5" stop-color="#FFFFFF" stop-opacity="0.05"/><stop offset="1" stop-color="#FFFFFF" stop-opacity="0"/></linearGradient></defs><rect width="320" height="140" fill="{{background}}"/><g opacity="0.9"><g id="glowButtonHalo" transform="${haloPose(1)}" opacity="0.55"><rect x="50" y="42" width="220" height="56" rx="28" fill="{{accent}}" filter="url(#glowButtonBlur)"/></g></g><g id="glowButtonLift" transform="translate(0 0)"><rect id="glowButtonFace" x="50" y="42" width="220" height="56" rx="28" fill="{{accent}}"/><rect x="50" y="42" width="220" height="56" rx="28" fill="url(#glowButtonSheen)"/><text x="160" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${SANS4}" font-size="{{size}}" font-weight="700" letter-spacing="0.3" fill="{{ink}}">{{label}}</text></g></svg>`,
|
|
3612
|
+
animate: [
|
|
3613
|
+
{ id: "glowButtonHalo", attrs: { opacity: [0.55, 0.95], transform: [haloPose(1), haloPose(1.08)] }, ms: 320, ease: "out" },
|
|
3614
|
+
{ id: "glowButtonSpread", attrs: { stdDeviation: [10, 14] }, ms: 320, ease: "out" },
|
|
3615
|
+
{ id: "glowButtonLift", attrs: { transform: ["translate(0 0)", "translate(0 -3)"] }, ms: 260, ease: "out" },
|
|
3616
|
+
{ id: "glowButtonFace", attrs: { fill: ["{{accent}}", "{{bright}}"] }, ms: 260 }
|
|
3617
|
+
]
|
|
3618
|
+
}
|
|
3619
|
+
};
|
|
3620
|
+
var glowEffects = [glowRingPulse, glowButton];
|
|
3621
|
+
|
|
3622
|
+
// src/catalog/shadow.ts
|
|
3623
|
+
var r17 = (v) => Math.round(v * 10) / 10;
|
|
3624
|
+
var SANS5 = `system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif`;
|
|
3625
|
+
function fit4(text, width, max) {
|
|
3626
|
+
const e = Math.max(0.6, textWidthEm(String(text)));
|
|
3627
|
+
const size = Math.min(max, width / e);
|
|
3628
|
+
return { size: r17(size), length: r17(e * size) };
|
|
3629
|
+
}
|
|
3630
|
+
var raised = (up, scale) => `translate(0 ${-up}) translate(160 120) scale(${scale}) translate(-160 -120)`;
|
|
3631
|
+
var liftCard = {
|
|
3632
|
+
kind: "vector",
|
|
3633
|
+
id: "lift-card",
|
|
3634
|
+
name: "Lift card",
|
|
3635
|
+
description: "A content card that lifts toward you on hover like a material elevation change, rising and growing slightly as its shadow spreads and softens beneath it.",
|
|
3636
|
+
category: "shadow",
|
|
3637
|
+
tags: ["shadow", "card", "hover", "interactive", "elevation", "material", "ui", "micro-interaction"],
|
|
3638
|
+
trigger: "hover",
|
|
3639
|
+
hold: 800,
|
|
3640
|
+
presets: [
|
|
3641
|
+
{ name: "Dark", values: { background: [0.07, 0.08, 0.11], card: [0.13, 0.15, 0.2], ink: [0.9, 0.92, 0.96], shadow: [0, 0, 0], accent: [0.4, 0.85, 1] } },
|
|
3642
|
+
{ name: "Warm", values: { title: "Weekend recipes", background: [0.98, 0.94, 0.88], accent: [0.93, 0.42, 0.25], shadow: [0.35, 0.18, 0.08] } }
|
|
3643
|
+
],
|
|
3644
|
+
derived: (v) => {
|
|
3645
|
+
const { size, length } = fit4(v.title, 116, 17);
|
|
3646
|
+
return { size, length, base: r17(82 + size * 0.35) };
|
|
3647
|
+
},
|
|
3648
|
+
motion: {
|
|
3649
|
+
svg: `<svg viewBox="0 0 320 240"><!-- {{title: text default='Getting started' label='Title'}} {{background: color default=#EEF1F6 label='Background'}} {{card: color default=#FFFFFF label='Card'}} {{ink: color default=#1F2937 label='Text colour'}} {{accent: color default=#6366F1 label='Accent'}} {{shadow: color default=#0F172A label='Shadow colour'}} --><defs><filter id="liftCardTight" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="2.5"/></filter><filter id="liftCardSoft" x="-40%" y="-40%" width="180%" height="180%"><feGaussianBlur stdDeviation="14"/></filter></defs><rect width="320" height="240" fill="{{background}}"/><rect id="liftCardAmbient" x="70" y="50" width="180" height="140" rx="14" fill="{{shadow}}" opacity="0" filter="url(#liftCardSoft)" transform="${raised(-4, 0.94)}"/><rect id="liftCardKey" x="70" y="50" width="180" height="140" rx="14" fill="{{shadow}}" opacity="0.22" filter="url(#liftCardTight)" transform="translate(0 2)"/><g id="liftCardFace" transform="${raised(0, 1)}"><rect x="70" y="50" width="180" height="140" rx="14" fill="{{card}}"/><circle cx="98" cy="80" r="12" fill="{{accent}}"/><text x="118" y="{{base}}" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${SANS5}" font-size="{{size}}" font-weight="700" fill="{{ink}}">{{title}}</text><g fill="{{ink}}" opacity="0.16"><rect x="86" y="108" width="148" height="8" rx="4"/><rect x="86" y="124" width="148" height="8" rx="4"/><rect x="86" y="140" width="96" height="8" rx="4"/></g><rect x="86" y="160" width="54" height="16" rx="8" fill="{{accent}}"/></g></svg>`,
|
|
3650
|
+
animate: [
|
|
3651
|
+
{ id: "liftCardFace", attrs: { transform: [raised(0, 1), raised(6, 1.02)] }, ms: 260, ease: "out" },
|
|
3652
|
+
{ id: "liftCardAmbient", attrs: { opacity: [0, 0.3], transform: [raised(-4, 0.94), raised(-16, 1)] }, ms: 300, ease: "out" },
|
|
3653
|
+
{ id: "liftCardKey", attrs: { opacity: [0.22, 0.12], transform: ["translate(0 2)", "translate(0 5)"] }, ms: 260, ease: "out" }
|
|
3654
|
+
]
|
|
3655
|
+
}
|
|
3656
|
+
};
|
|
3657
|
+
var shadowEffects = [liftCard];
|
|
3658
|
+
|
|
3659
|
+
// src/catalog/electric.ts
|
|
3660
|
+
var r18 = (v) => Math.round(v * 10) / 10;
|
|
3515
3661
|
var about5 = (cx, cy, s) => `translate(${cx} ${cy}) scale(${Math.round(s * 1e3) / 1e3}) translate(${-cx} ${-cy})`;
|
|
3516
3662
|
var snap6 = (id, attr, value, delay, step) => ({
|
|
3517
3663
|
id,
|
|
@@ -3520,7 +3666,7 @@ var snap6 = (id, attr, value, delay, step) => ({
|
|
|
3520
3666
|
delay,
|
|
3521
3667
|
ms: 0
|
|
3522
3668
|
});
|
|
3523
|
-
var
|
|
3669
|
+
var SANS6 = `system-ui, -apple-system, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif`;
|
|
3524
3670
|
var BOLT = "M68 8 L28 66 L56 66 L46 112 L92 48 L64 48 L76 8 Z";
|
|
3525
3671
|
var zapIcon = {
|
|
3526
3672
|
kind: "vector",
|
|
@@ -3537,7 +3683,7 @@ var zapIcon = {
|
|
|
3537
3683
|
motion: (() => {
|
|
3538
3684
|
const sparks = [-55, 25, 160, 215].map((deg, i) => {
|
|
3539
3685
|
const a = deg * Math.PI / 180;
|
|
3540
|
-
const at3 = (r) => [
|
|
3686
|
+
const at3 = (r) => [r18(60 + r * Math.cos(a)), r18(60 + r * Math.sin(a))];
|
|
3541
3687
|
return { id: `s${i}`, from: at3(30), mid: at3(46), to: at3(56) };
|
|
3542
3688
|
});
|
|
3543
3689
|
return {
|
|
@@ -3605,7 +3751,7 @@ var powerButton = {
|
|
|
3605
3751
|
};
|
|
3606
3752
|
})()
|
|
3607
3753
|
};
|
|
3608
|
-
var CELLS = Array.from({ length: 5 }, (_, i) => ({ id: `cell${i}`, x:
|
|
3754
|
+
var CELLS = Array.from({ length: 5 }, (_, i) => ({ id: `cell${i}`, x: r18(36 + i * 24.8) }));
|
|
3609
3755
|
var boltPose = (k) => `translate(96 58) scale(${Math.round(0.36 * k * 1e3) / 1e3}) translate(-60 -60)`;
|
|
3610
3756
|
var batteryCharge = {
|
|
3611
3757
|
kind: "vector",
|
|
@@ -3622,7 +3768,7 @@ var batteryCharge = {
|
|
|
3622
3768
|
motion: {
|
|
3623
3769
|
svg: `<svg viewBox="0 0 200 140"><!-- {{background: color default=#0F172A label='Background'}} {{shell: color default=#CBD5E1 label='Case'}} {{charge: color default=#22C55E label='Charge'}} {{bolt: color default=#FDE047 label='Bolt'}} {{ink: color default=#F8FAFC label='Percent'}} --><rect width="200" height="140" fill="{{background}}"/><rect id="case" x="28" y="26" width="136" height="64" rx="12" fill="none" stroke="{{shell}}" stroke-width="5"/><rect id="nub" x="167" y="46" width="9" height="24" rx="3" fill="{{shell}}"/>${CELLS.map(
|
|
3624
3770
|
({ id, x }) => `<rect id="${id}" x="${x}" y="34" width="20.8" height="48" rx="4" fill="{{charge}}" opacity="0"/>`
|
|
3625
|
-
).join("")}<path id="bolt" d="${BOLT}" transform="${boltPose(1)}" fill="{{bolt}}" stroke="{{background}}" stroke-width="10" stroke-linejoin="round" paint-order="stroke"/><rect id="flash" x="28" y="26" width="136" height="64" rx="12" fill="#FFFFFF" opacity="0"/><text id="pct" x="96" y="124" text-anchor="middle" font-family="${
|
|
3771
|
+
).join("")}<path id="bolt" d="${BOLT}" transform="${boltPose(1)}" fill="{{bolt}}" stroke="{{background}}" stroke-width="10" stroke-linejoin="round" paint-order="stroke"/><rect id="flash" x="28" y="26" width="136" height="64" rx="12" fill="#FFFFFF" opacity="0"/><text id="pct" x="96" y="124" text-anchor="middle" font-family="${SANS6}" font-size="22" font-weight="700" fill="{{ink}}">0%</text></svg>`,
|
|
3626
3772
|
animate: [
|
|
3627
3773
|
{ id: "pct", count: [0, 100], format: "{n}%", ms: 2e3, ease: "linear" },
|
|
3628
3774
|
// Each cell lights as the count passes its share.
|
|
@@ -3913,8 +4059,8 @@ function packGlyph(rows) {
|
|
|
3913
4059
|
return `ivec2(${a}, ${b})`;
|
|
3914
4060
|
}
|
|
3915
4061
|
var vec3Hex = (hex) => {
|
|
3916
|
-
const
|
|
3917
|
-
const c = [
|
|
4062
|
+
const n11 = parseInt(hex.slice(1), 16);
|
|
4063
|
+
const c = [n11 >> 16, n11 >> 8 & 255, n11 & 255].map((v) => +(v / 255).toFixed(3));
|
|
3918
4064
|
return `vec3(${c.map((v) => Number.isInteger(v) ? `${v}.0` : String(v)).join(", ")})`;
|
|
3919
4065
|
};
|
|
3920
4066
|
var paletteArray = (name, hexes) => `const vec3 ${name}[${hexes.length}] = vec3[${hexes.length}](
|
|
@@ -4699,7 +4845,7 @@ var pixelShaders = [
|
|
|
4699
4845
|
|
|
4700
4846
|
// src/catalog/pixel.ts
|
|
4701
4847
|
var r29 = (v) => Math.round(v * 100) / 100;
|
|
4702
|
-
var
|
|
4848
|
+
var n8 = (name) => `{{${name}}}`;
|
|
4703
4849
|
function bitmapPath(bitmap, char, unit = 1, x = 0, y = 0) {
|
|
4704
4850
|
let d = "";
|
|
4705
4851
|
bitmap.forEach((row, j) => {
|
|
@@ -4764,10 +4910,10 @@ var pixelText = {
|
|
|
4764
4910
|
(_, i) => `<g id="g${i}" transform="${letterAt(i, -14)}" opacity="0"><path transform="translate(0.6 0.6)" fill="{{shadow}}" d="{{d${i}}}"/><path fill="{{ink}}" d="{{d${i}}}"/></g>`
|
|
4765
4911
|
).join("")}</g></svg>`,
|
|
4766
4912
|
animate: Array.from({ length: TEXT_SLOTS }, (_, i) => [
|
|
4767
|
-
{ id: `g${i}`, attrs: { opacity: [0, 1] }, delay:
|
|
4768
|
-
{ id: `g${i}`, attrs: { transform: [letterAt(i, -14), letterAt(i, 0)] }, delay:
|
|
4769
|
-
{ id: `g${i}`, attrs: { transform: [letterAt(i, 0), letterAt(i, -1.6)] }, delay:
|
|
4770
|
-
{ id: `g${i}`, attrs: { transform: [letterAt(i, -1.6), letterAt(i, 0)] }, delay:
|
|
4913
|
+
{ id: `g${i}`, attrs: { opacity: [0, 1] }, delay: n8(`t${i}`), ms: 60, ease: "linear" },
|
|
4914
|
+
{ id: `g${i}`, attrs: { transform: [letterAt(i, -14), letterAt(i, 0)] }, delay: n8(`t${i}`), ms: 260, ease: "in-out" },
|
|
4915
|
+
{ id: `g${i}`, attrs: { transform: [letterAt(i, 0), letterAt(i, -1.6)] }, delay: n8(`u${i}`), ms: 110, ease: "out" },
|
|
4916
|
+
{ id: `g${i}`, attrs: { transform: [letterAt(i, -1.6), letterAt(i, 0)] }, delay: n8(`w${i}`), ms: 120, ease: "in-out" }
|
|
4771
4917
|
]).flat()
|
|
4772
4918
|
}
|
|
4773
4919
|
};
|
|
@@ -4972,7 +5118,7 @@ var chompLoader = {
|
|
|
4972
5118
|
var pixelEffects = [pixelText, coinSpin, spaceInvader, pixelHeartHealth, chompLoader];
|
|
4973
5119
|
|
|
4974
5120
|
// src/catalog/pyro.ts
|
|
4975
|
-
var round = (
|
|
5121
|
+
var round = (n11) => Math.round(n11 * 10) / 10;
|
|
4976
5122
|
var fireworkBurst = {
|
|
4977
5123
|
kind: "vector",
|
|
4978
5124
|
id: "firework-burst",
|
|
@@ -5027,10 +5173,10 @@ var fireworkBurst = {
|
|
|
5027
5173
|
var pyroEffects = [fireworkBurst];
|
|
5028
5174
|
|
|
5029
5175
|
// src/catalog/timewarp.ts
|
|
5030
|
-
var
|
|
5176
|
+
var r19 = (n11) => Math.round(n11 * 10) / 10;
|
|
5031
5177
|
var at2 = (degrees, radius) => {
|
|
5032
5178
|
const a = degrees * Math.PI / 180;
|
|
5033
|
-
return [
|
|
5179
|
+
return [r19(100 + radius * Math.sin(a)), r19(100 - radius * Math.cos(a))];
|
|
5034
5180
|
};
|
|
5035
5181
|
var slice = (from, to, radius) => {
|
|
5036
5182
|
const [x0, y0] = at2(from, radius);
|
|
@@ -5101,7 +5247,7 @@ var r210 = (v) => {
|
|
|
5101
5247
|
const r = Math.round(v * 100) / 100;
|
|
5102
5248
|
return Object.is(r, -0) ? 0 : r;
|
|
5103
5249
|
};
|
|
5104
|
-
var
|
|
5250
|
+
var n9 = (name) => `{{${name}}}`;
|
|
5105
5251
|
var pointsOf = (d) => {
|
|
5106
5252
|
const nums = (d.match(NUMBER) ?? []).map(Number);
|
|
5107
5253
|
return Array.from({ length: nums.length >> 1 }, (_, i) => [nums[2 * i], nums[2 * i + 1]]);
|
|
@@ -5161,16 +5307,16 @@ function around(c, r, from, to, avoid) {
|
|
|
5161
5307
|
if (((avoid - from) % 360 + 360) % 360 < delta) delta -= 360;
|
|
5162
5308
|
return arc2(c[0], c[1], r, r, from, from + delta);
|
|
5163
5309
|
}
|
|
5164
|
-
function crescent(c1,
|
|
5310
|
+
function crescent(c1, r111, c2, r2_) {
|
|
5165
5311
|
const [dx, dy] = [c2[0] - c1[0], c2[1] - c1[1]];
|
|
5166
5312
|
const d = Math.hypot(dx, dy);
|
|
5167
|
-
const a = (
|
|
5168
|
-
const h = Math.sqrt(Math.max(0,
|
|
5313
|
+
const a = (r111 * r111 - r2_ * r2_ + d * d) / (2 * d);
|
|
5314
|
+
const h = Math.sqrt(Math.max(0, r111 * r111 - a * a));
|
|
5169
5315
|
const [px, py] = [c1[0] + a * dx / d, c1[1] + a * dy / d];
|
|
5170
5316
|
const i1 = [px - h * dy / d, py + h * dx / d];
|
|
5171
5317
|
const i2 = [px + h * dy / d, py - h * dx / d];
|
|
5172
5318
|
const angle = (c, p) => Math.atan2(p[1] - c[1], p[0] - c[0]) / DEG2;
|
|
5173
|
-
const outer = around(c1,
|
|
5319
|
+
const outer = around(c1, r111, angle(c1, i1), angle(c1, i2), angle(c1, c2));
|
|
5174
5320
|
const inner = around(c2, r2_, angle(c2, i2), angle(c2, i1), angle(c2, c1) + 180);
|
|
5175
5321
|
return polyline3([...outer, ...inner.slice(1, -1)], true);
|
|
5176
5322
|
}
|
|
@@ -5225,8 +5371,8 @@ var shapeMorph = {
|
|
|
5225
5371
|
motion: {
|
|
5226
5372
|
svg: `<svg viewBox="0 0 100 100"><!-- {{from: text default='${SHAPE_FROM}' label='From path'}} {{to: text default='${SHAPE_TO}' label='To path'}} {{fillFrom: color default=#6366F1 label='Fill (from)'}} {{fillTo: color default=#F59E0B label='Fill (to)'}} {{strokeFrom: color default=#C7D2FE label='Outline (from)'}} {{strokeTo: color default=#FEF3C7 label='Outline (to)'}} {{background: color default=#0F172A label='Background'}} {{width: float min=0 max=6 default=1.5 step=0.25 label='Outline width'}} {{morph: float min=200 max=3000 default=1000 step=50 label='Morph ms'}} {{pause: float min=0 max=3000 default=700 step=50 label='Pause ms'}} --><rect width="100" height="100" fill="{{background}}"/><path id="shape" d="{{a}}" fill="{{fillFrom}}" fill-rule="evenodd" stroke="{{strokeFrom}}" stroke-width="{{width}}" stroke-linejoin="round"/></svg>`,
|
|
5227
5373
|
animate: [
|
|
5228
|
-
{ id: "shape", attrs: { d: ["{{a}}", "{{b}}"], fill: ["{{fillFrom}}", "{{fillTo}}"], stroke: ["{{strokeFrom}}", "{{strokeTo}}"] }, delay:
|
|
5229
|
-
{ id: "shape", attrs: { d: ["{{b}}", "{{a}}"], fill: ["{{fillTo}}", "{{fillFrom}}"], stroke: ["{{strokeTo}}", "{{strokeFrom}}"] }, delay:
|
|
5374
|
+
{ id: "shape", attrs: { d: ["{{a}}", "{{b}}"], fill: ["{{fillFrom}}", "{{fillTo}}"], stroke: ["{{strokeFrom}}", "{{strokeTo}}"] }, delay: n9("pause"), ms: n9("morph"), ease: "in-out" },
|
|
5375
|
+
{ id: "shape", attrs: { d: ["{{b}}", "{{a}}"], fill: ["{{fillTo}}", "{{fillFrom}}"], stroke: ["{{strokeTo}}", "{{strokeFrom}}"] }, delay: n9("back"), ms: n9("morph"), ease: "in-out" }
|
|
5230
5376
|
]
|
|
5231
5377
|
}
|
|
5232
5378
|
};
|
|
@@ -5317,7 +5463,7 @@ var morphSequenceEffect = {
|
|
|
5317
5463
|
svg: `<svg viewBox="0 0 200 200"><!-- {{c0: color default=#38BDF8 label='Circle'}} {{c1: color default=#FACC15 label='Star'}} {{c2: color default=#F43F5E label='Heart'}} {{c3: color default=#A78BFA label='Hexagon'}} {{background: color default=#0F172A label='Background'}} {{morph: float min=200 max=3000 default=900 step=50 label='Morph ms'}} {{pause: float min=0 max=3000 default=500 step=50 label='Pause ms'}} --><rect width="200" height="200" fill="{{background}}"/><path id="shape" d="${SEQUENCE[0]}" fill="{{c0}}" fill-rule="evenodd"/></svg>`,
|
|
5318
5464
|
animate: SEQUENCE.map((d, i) => {
|
|
5319
5465
|
const next = (i + 1) % SEQUENCE.length;
|
|
5320
|
-
return { id: "shape", attrs: { d: [d, SEQUENCE[next]], fill: [`{{c${i}}}`, `{{c${next}}}`] }, delay:
|
|
5466
|
+
return { id: "shape", attrs: { d: [d, SEQUENCE[next]], fill: [`{{c${i}}}`, `{{c${next}}}`] }, delay: n9(`at${i}`), ms: n9("morph"), ease: "in-out" };
|
|
5321
5467
|
})
|
|
5322
5468
|
}
|
|
5323
5469
|
};
|
|
@@ -5358,7 +5504,7 @@ var gooeyMorph = {
|
|
|
5358
5504
|
motion: {
|
|
5359
5505
|
svg: `<svg viewBox="0 0 200 200"><!-- {{colorA: color default=#22D3EE label='Colour A'}} {{colorB: color default=#A855F7 label='Colour B'}} {{background: color default=#0B0B1A label='Background'}} {{goo: float min=1 max=14 default=7 step=0.5 label='Gooeyness'}} {{sharp: float min=8 max=40 default=20 step=1 label='Edge sharpness'}} {{morph: float min=300 max=3000 default=1200 step=50 label='Morph ms'}} {{pause: float min=0 max=3000 default=400 step=50 label='Pause ms'}} --><defs><linearGradient id="gooFill" gradientUnits="userSpaceOnUse" x1="30" y1="30" x2="170" y2="170"><stop offset="0" stop-color="{{colorA}}"/><stop offset="1" stop-color="{{colorB}}"/></linearGradient><filter id="goo" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur in="SourceGraphic" stdDeviation="{{goo}}" result="soft"/><feColorMatrix in="soft" type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 {{sharp}} {{threshold}}"/></filter></defs><rect width="200" height="200" fill="{{background}}"/><g filter="url(#goo)" fill="url(#gooFill)">${GOO.map((stages2, i) => `<path id="goo${i}" d="${stages2[0]}" fill-rule="evenodd"/>`).join("")}</g></svg>`,
|
|
5360
5506
|
animate: GOO.flatMap(
|
|
5361
|
-
(stages2, i) => stages2.map((d, s) => ({ id: `goo${i}`, attrs: { d: [d, stages2[(s + 1) % stages2.length]] }, delay:
|
|
5507
|
+
(stages2, i) => stages2.map((d, s) => ({ id: `goo${i}`, attrs: { d: [d, stages2[(s + 1) % stages2.length]] }, delay: n9(`at${s}`), ms: n9("morph"), ease: "in-out" }))
|
|
5362
5508
|
)
|
|
5363
5509
|
}
|
|
5364
5510
|
};
|
|
@@ -5410,10 +5556,10 @@ var particleMorph = {
|
|
|
5410
5556
|
const back = 3e3;
|
|
5411
5557
|
const spread = DOTS4 * DOT_STAGGER;
|
|
5412
5558
|
const leg = (i, at3, from, mid, to, colours) => [
|
|
5413
|
-
{ id: `p${i}`, attrs: { cx: [
|
|
5414
|
-
{ id: `p${i}`, attrs: { cx: [
|
|
5559
|
+
{ id: `p${i}`, attrs: { cx: [n9(`${from}x${i}`), n9(`${mid}x${i}`)], cy: [n9(`${from}y${i}`), n9(`${mid}y${i}`)] }, delay: at3 + i * DOT_STAGGER, ms: 450, ease: "out" },
|
|
5560
|
+
{ id: `p${i}`, attrs: { cx: [n9(`${mid}x${i}`), n9(`${to}x${i}`)], cy: [n9(`${mid}y${i}`), n9(`${to}y${i}`)], fill: colours }, delay: at3 + 450 + i * DOT_STAGGER, ms: 650, ease: "in-out" }
|
|
5415
5561
|
];
|
|
5416
|
-
const fade = (id, at3, show) => ({ id, attrs: { opacity: show ? [0,
|
|
5562
|
+
const fade = (id, at3, show) => ({ id, attrs: { opacity: show ? [0, n9("ghost")] : [n9("ghost"), 0] }, delay: at3, ms: 450, ease: "in-out" });
|
|
5417
5563
|
return {
|
|
5418
5564
|
svg: `<svg viewBox="0 0 200 200"><!-- {{from: text default='${PARTICLE_FROM}' label='From path'}} {{to: text default='${PARTICLE_TO}' label='To path'}} {{fromColor: color default=#F472B6 label='From colour'}} {{toColor: color default=#FBBF24 label='To colour'}} {{background: color default=#0C0A1D label='Background'}} {{size: float min=0.8 max=6 default=2.6 step=0.1 label='Dot size'}} {{scatter: float min=0 max=80 default=34 step=1 label='Scatter'}} {{ghost: float min=0 max=1 default=0.16 label='Shape fill'}} --><rect width="200" height="200" fill="{{background}}"/><path id="ghostA" d="{{a}}" fill="{{fromColor}}" fill-rule="evenodd" opacity="{{ghost}}"/><path id="ghostB" d="{{b}}" fill="{{toColor}}" fill-rule="evenodd" opacity="0"/>${Array.from(
|
|
5419
5565
|
{ length: DOTS4 },
|
|
@@ -5501,10 +5647,10 @@ var digitMorph = {
|
|
|
5501
5647
|
animate: Array.from({ length: SLOTS }, (_, j) => [
|
|
5502
5648
|
...Array.from({ length: MAX_STEPS }, (_2, i) => {
|
|
5503
5649
|
const k = i + 1;
|
|
5504
|
-
return { id: `s${j}`, attrs: { d: [`{{d${j}_${k - 1}}}`, `{{d${j}_${k}}}`], opacity: [
|
|
5650
|
+
return { id: `s${j}`, attrs: { d: [`{{d${j}_${k - 1}}}`, `{{d${j}_${k}}}`], opacity: [n9(`o${j}_${k - 1}`), n9(`o${j}_${k}`)] }, delay: n9(`t${k}`), ms: n9("morph"), ease: "in-out" };
|
|
5505
5651
|
}),
|
|
5506
5652
|
// Back to the first number in one morph, so it loops.
|
|
5507
|
-
{ id: `s${j}`, attrs: { d: [`{{d${j}_${MAX_STEPS}}}`, `{{d${j}_0}}`], opacity: [
|
|
5653
|
+
{ id: `s${j}`, attrs: { d: [`{{d${j}_${MAX_STEPS}}}`, `{{d${j}_0}}`], opacity: [n9(`o${j}_${MAX_STEPS}`), n9(`o${j}_0`)] }, delay: n9("back"), ms: n9("morph"), ease: "in-out" }
|
|
5508
5654
|
]).flat()
|
|
5509
5655
|
}
|
|
5510
5656
|
};
|
|
@@ -6846,16 +6992,25 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
6846
6992
|
float px = 1.0 / iResolution.y;
|
|
6847
6993
|
vec2 p = fragCoord * px;
|
|
6848
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;
|
|
6849
7002
|
for (int i = 0; i < 16; i++) {
|
|
6850
7003
|
if (i >= uCount) break;
|
|
6851
7004
|
float age;
|
|
6852
7005
|
float life;
|
|
6853
7006
|
vec4 b = bubbleAt(i, iTime, age, life);
|
|
7007
|
+
vec2 d0 = p - b.xy;
|
|
7008
|
+
if (dot(d0, d0) > far * far) continue;
|
|
6854
7009
|
float birth = iTime - age;
|
|
6855
7010
|
// It pops when its life is up, or at the first click that lands on it.
|
|
6856
7011
|
float popAt = birth + life;
|
|
6857
7012
|
for (int j = 0; j < 8; j++) {
|
|
6858
|
-
vec4 c =
|
|
7013
|
+
vec4 c = clicks[j];
|
|
6859
7014
|
if (c.w < 0.5 || c.z < birth || c.z > iTime || c.z >= popAt) continue;
|
|
6860
7015
|
float a2;
|
|
6861
7016
|
float l2;
|
|
@@ -8094,10 +8249,36 @@ uniform float uClump; // @control min=0 max=1 default=0.5 label="Clumping"
|
|
|
8094
8249
|
uniform float uPart; // @control min=0 max=1 default=0.8 label="Parting"`,
|
|
8095
8250
|
HASH,
|
|
8096
8251
|
NOISE,
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
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));
|
|
8100
8280
|
vec2 dir = vec2(cos(a), sin(a));
|
|
8281
|
+
if (uPart * hov <= 0.0) return dir;
|
|
8101
8282
|
vec2 away = root - ptr;
|
|
8102
8283
|
float reach = 0.16 * uDensity;
|
|
8103
8284
|
float f = uPart * hov * exp(-dot(away, away) / (reach * reach));
|
|
@@ -8108,23 +8289,93 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
8108
8289
|
vec2 p = fragCoord / iResolution.y * uDensity;
|
|
8109
8290
|
vec2 ptr = iPointer.xy / iResolution.y * uDensity;
|
|
8110
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);
|
|
8111
8297
|
vec2 here = strandDir(p, ptr, hov);
|
|
8112
8298
|
float top = -1.0;
|
|
8113
8299
|
float across = 0.0;
|
|
8114
8300
|
float tint = 0.0;
|
|
8115
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));
|
|
8116
8345
|
for (int layer = 0; layer < 3; layer++) {
|
|
8117
8346
|
float fl = float(layer);
|
|
8118
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.
|
|
8119
8350
|
vec2 base = floor(p - here * uLength * 0.5 - off);
|
|
8120
|
-
|
|
8121
|
-
|
|
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;
|
|
8122
8359
|
vec2 id = base + vec2(float(i), float(j));
|
|
8123
8360
|
vec2 h = hash22(id + fl * 31.7);
|
|
8124
8361
|
vec2 root = id + off + h;
|
|
8125
|
-
vec2 dir = strandDir(root, ptr, hov);
|
|
8126
8362
|
float len = uLength * (0.75 + 0.5 * h.y);
|
|
8127
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);
|
|
8128
8379
|
float along = clamp(dot(rp, dir) / len, 0.0, 1.0);
|
|
8129
8380
|
float w = uWidth * (1.0 - 0.75 * along);
|
|
8130
8381
|
float dist = length(rp - dir * along * len);
|
|
@@ -9319,29 +9570,61 @@ float poolY(float x, float m) {
|
|
|
9319
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;
|
|
9320
9571
|
}
|
|
9321
9572
|
|
|
9322
|
-
|
|
9323
|
-
|
|
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) {
|
|
9324
9585
|
float n = float(uDrips);
|
|
9325
9586
|
float grow = smoothstep(0.0, 0.2, m);
|
|
9326
|
-
|
|
9587
|
+
vec4 e = edge + 0.01 * (1.0 - grow);
|
|
9327
9588
|
float lip = 0.022 * grow;
|
|
9328
|
-
|
|
9329
|
-
|
|
9330
|
-
|
|
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++) {
|
|
9331
9600
|
float ci = c + float(k);
|
|
9601
|
+
bvec4 near = lessThanEqual(abs(cs - ci), vec4(2.0));
|
|
9602
|
+
if (!any(near)) continue;
|
|
9332
9603
|
vec2 h = hash22(vec2(ci, 3.1));
|
|
9333
9604
|
float xi = (ci + 0.2 + 0.6 * h.x) / n;
|
|
9334
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;
|
|
9335
9615
|
float top = edgeY(xi, m) - lip * 0.5;
|
|
9336
|
-
float len =
|
|
9616
|
+
float len = f * max(top - poolY(xi, m), 0.0);
|
|
9337
9617
|
float head = top - len;
|
|
9338
|
-
|
|
9618
|
+
vec4 along = clamp(ys, head, top);
|
|
9339
9619
|
// A stem thinning as it runs, then a round, heavier head.
|
|
9340
|
-
|
|
9341
|
-
r = mix(r, w * 1.3, smoothstep(head + 3.0 * w, head,
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
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);
|
|
9626
|
+
}
|
|
9627
|
+
return smin4(d, ys - pool, 0.03 * grow);
|
|
9345
9628
|
}
|
|
9346
9629
|
|
|
9347
9630
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
@@ -9350,23 +9633,32 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
9350
9633
|
float px = 1.0 / iResolution.y;
|
|
9351
9634
|
float m = uAnimate ? meltCycle(uSpeed) : uProgress;
|
|
9352
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
|
+
|
|
9353
9646
|
// The picture slumped under its edge: squashed towards the bottom, stretched thin at the top.
|
|
9354
|
-
float e =
|
|
9647
|
+
float e = edge3.y;
|
|
9355
9648
|
float h = clamp(uv.y / e, 0.0, 1.0);
|
|
9356
|
-
float
|
|
9357
|
-
float
|
|
9358
|
-
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));
|
|
9359
9652
|
vec4 behind = vec4(uBackground * (0.75 + 0.5 * uv.y), uBackdropAlpha);
|
|
9360
9653
|
col = mix(col, behind, smoothstep(e - px, e + px, uv.y));
|
|
9361
9654
|
|
|
9362
9655
|
// The liquid's shadow, down and to the right.
|
|
9363
|
-
float shadow =
|
|
9656
|
+
float shadow = liq.x;
|
|
9364
9657
|
col.rgb *= 1.0 - 0.4 * smoothstep(0.012, -0.004, shadow) * smoothstep(0.0, 0.1, m);
|
|
9365
9658
|
|
|
9366
|
-
float d =
|
|
9659
|
+
float d = liq.y;
|
|
9367
9660
|
if (d < 4.0 * px) {
|
|
9368
|
-
|
|
9369
|
-
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);
|
|
9370
9662
|
g /= max(length(g), 1e-5);
|
|
9371
9663
|
float thick = sqrt(clamp(-d / 0.018, 0.0, 1.0));
|
|
9372
9664
|
vec3 nrm = normalize(vec3(g * (1.0 - thick) * 1.6, 0.4 + thick));
|
|
@@ -9374,7 +9666,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
9374
9666
|
vec4 top = pictureRGBA(uImage, vec2(uv.x + 0.01 * sin(uv.y * 25.0), 0.965));
|
|
9375
9667
|
float sx = uv.x + 0.04 * sin(uv.x * 11.0 + uv.y * 30.0);
|
|
9376
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;
|
|
9377
|
-
float inPool = smoothstep(
|
|
9669
|
+
float inPool = smoothstep(pool3.y + 0.04, pool3.y - 0.01, uv.y);
|
|
9378
9670
|
vec4 base = mix(top, pool, inPool);
|
|
9379
9671
|
base.rgb *= 0.9 + 0.12 * vnoise(vec2(uv.x * 90.0, uv.y * 3.0));
|
|
9380
9672
|
vec3 wet = liquidShade(base.rgb * 1.05, nrm, uGloss);
|
|
@@ -10303,7 +10595,18 @@ uniform float uIntensity; // @control min=0.2 max=3 default=1 label="Intensity"`
|
|
|
10303
10595
|
float w = max(0.0022, px);
|
|
10304
10596
|
float k = 1.4;
|
|
10305
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;
|
|
10306
10608
|
for (int j = 0; j < 112; j++) {
|
|
10609
|
+
if (!inReach) break;
|
|
10307
10610
|
float fj = float(j);
|
|
10308
10611
|
vec2 h = hash22(vec2(fj, 2.31));
|
|
10309
10612
|
float life = 1.0 + 0.8 * h.y;
|
|
@@ -10313,11 +10616,14 @@ uniform float uIntensity; // @control min=0.2 max=3 default=1 label="Intensity"`
|
|
|
10313
10616
|
vec3 s = vec3(hash22(vec2(fj * 1.7, n)), hash12(vec2(n, fj + 0.37)));
|
|
10314
10617
|
if (s.z > uDensity) continue;
|
|
10315
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;
|
|
10316
10621
|
vec2 v = vec2(sin(a), cos(a)) * mix(0.9, 1.35, s.y) * uHeight * 1.25;
|
|
10317
10622
|
vec2 head = base + ballistic(v, k, g, age);
|
|
10318
10623
|
vec2 tail = base + ballistic(v, k, g, max(age - 0.07, 0.0));
|
|
10319
10624
|
// Sparks that fall back past the base are gone.
|
|
10320
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;
|
|
10321
10627
|
float fade = 1.0 - smoothstep(0.5, 1.0, age / life);
|
|
10322
10628
|
float twinkle = 0.6 + 0.4 * hash12(vec2(fj, floor(iTime * 24.0)));
|
|
10323
10629
|
vec3 tint = mix(vec3(1.0, 0.96, 0.85), uColor, smoothstep(0.0, 0.5, age));
|
|
@@ -11562,15 +11868,15 @@ ${BACKDROP_CONTROLS}`,
|
|
|
11562
11868
|
ROTATE,
|
|
11563
11869
|
PICTURE,
|
|
11564
11870
|
COMMON,
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
// 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.
|
|
11568
11873
|
vec2 landing(vec2 c) {
|
|
11569
11874
|
vec2 block = floor(c * 0.5);
|
|
11570
11875
|
vec2 local = c - block * 2.0;
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
|
|
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);
|
|
11574
11880
|
}
|
|
11575
11881
|
|
|
11576
11882
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
@@ -11584,26 +11890,43 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
11584
11890
|
vec2 hitTo = vec2(0.0);
|
|
11585
11891
|
float hitT = 0.0;
|
|
11586
11892
|
float hitCover = 0.0;
|
|
11587
|
-
//
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
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;
|
|
11591
11907
|
vec2 h = hash22(c);
|
|
11592
11908
|
// Staggered: a sweep from the left plus some randomness.
|
|
11593
11909
|
float delay = 0.5 * (0.55 * clamp(c.x / grid.x, 0.0, 1.0) + 0.45 * h.x);
|
|
11594
11910
|
float k = smoothstep(0.0, 1.0, clamp((t - delay) / 0.5, 0.0, 1.0));
|
|
11595
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;
|
|
11596
11917
|
vec2 from = c + 0.5;
|
|
11597
11918
|
vec2 to = landing(c) + 0.5;
|
|
11598
11919
|
vec2 path = to - from;
|
|
11599
11920
|
vec2 side = normalize(vec2(-path.y, path.x) + (h - 0.5) * 0.8);
|
|
11600
11921
|
vec2 pos = mix(from, to, k) + side * lift * (uScatter + uSpin * 0.3) * (h.y > 0.5 ? 0.6 : -0.6);
|
|
11601
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;
|
|
11602
11926
|
float angle = (h.y - 0.5) * 6.28318 * uSpin * lift;
|
|
11603
|
-
vec2 l = rot(-angle) *
|
|
11927
|
+
vec2 l = rot(-angle) * rel / size;
|
|
11604
11928
|
float m = max(abs(l.x), abs(l.y));
|
|
11605
|
-
|
|
11606
|
-
if (m <= 0.5 && z > top) {
|
|
11929
|
+
if (m <= 0.5) {
|
|
11607
11930
|
top = z;
|
|
11608
11931
|
hitFrom = from + l;
|
|
11609
11932
|
hitTo = to + l;
|
|
@@ -12542,6 +12865,24 @@ ${BACKDROP_CONTROLS}`,
|
|
|
12542
12865
|
return cell + 0.15 + 0.7 * hash22(cell);
|
|
12543
12866
|
}
|
|
12544
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
|
+
|
|
12545
12886
|
// The shard (cell) under grid point q, and how far q is from its edge (xy: cell, z: edge gap).
|
|
12546
12887
|
vec3 shardAt(vec2 q) {
|
|
12547
12888
|
vec2 base = floor(q);
|
|
@@ -12570,28 +12911,42 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
12570
12911
|
// Shard centres move out from the middle; shards keep their size, so gaps open between them.
|
|
12571
12912
|
float grow = 1.0 + uSpread * k;
|
|
12572
12913
|
vec2 base = floor(p / grow);
|
|
12573
|
-
vec4 col = vec4(uBackground, uBackdropAlpha);
|
|
12574
12914
|
float top = -1.0;
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
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));
|
|
12579
12927
|
vec2 h = hash22(c + 41.7);
|
|
12928
|
+
if (h.y <= top) continue;
|
|
12929
|
+
vec2 site = shardSite(c);
|
|
12580
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;
|
|
12581
12933
|
float a = (h.x - 0.5) * 2.0 * uSpin * k;
|
|
12582
12934
|
// Back to where this pixel would sit in the unbroken image, if it is on this shard.
|
|
12583
|
-
vec2 q = rot(-a) *
|
|
12584
|
-
|
|
12585
|
-
if (s.x == c.x && s.y == c.y && h.y > top) {
|
|
12935
|
+
vec2 q = rot(-a) * rel + site;
|
|
12936
|
+
if (onShard(q, c)) {
|
|
12586
12937
|
top = h.y;
|
|
12587
|
-
|
|
12588
|
-
|
|
12589
|
-
shard.rgb *= 1.0 - 0.35 * k * abs(sin(a));
|
|
12590
|
-
shard.rgb += k * 0.35 * smoothstep(0.06, 0.0, s.z);
|
|
12591
|
-
col = shard;
|
|
12938
|
+
hitQ = q;
|
|
12939
|
+
hitA = a;
|
|
12592
12940
|
}
|
|
12593
12941
|
}
|
|
12594
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
|
+
}
|
|
12595
12950
|
fragColor = col;
|
|
12596
12951
|
}`
|
|
12597
12952
|
),
|
|
@@ -13036,31 +13391,48 @@ uniform float uSharpness; // @control min=0.5 max=8 default=4 label="Sharpness"
|
|
|
13036
13391
|
uniform float uSaturation; // @control min=0 max=2 default=1.15 label="Saturation"
|
|
13037
13392
|
${BACKDROP_CONTROLS}`,
|
|
13038
13393
|
PICTURE,
|
|
13039
|
-
|
|
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) {
|
|
13040
13432
|
vec2 uv = fragCoord / iResolution.xy;
|
|
13041
13433
|
vec2 px = 1.0 / iResolution.xy;
|
|
13042
|
-
|
|
13043
|
-
|
|
13044
|
-
vec3 s0 = vec3(0.0), s1 = vec3(0.0), s2 = vec3(0.0), s3 = vec3(0.0);
|
|
13045
|
-
for (int j = 0; j <= 8; j++) {
|
|
13046
|
-
if (j > uRadius) break;
|
|
13047
|
-
for (int i = 0; i <= 8; i++) {
|
|
13048
|
-
if (i > uRadius) break;
|
|
13049
|
-
vec2 o = vec2(float(i), float(j)) * px;
|
|
13050
|
-
vec3 c = picture(uImage, uv + vec2(-o.x, -o.y));
|
|
13051
|
-
m0 += c;
|
|
13052
|
-
s0 += c * c;
|
|
13053
|
-
c = picture(uImage, uv + vec2(o.x, -o.y));
|
|
13054
|
-
m1 += c;
|
|
13055
|
-
s1 += c * c;
|
|
13056
|
-
c = picture(uImage, uv + vec2(-o.x, o.y));
|
|
13057
|
-
m2 += c;
|
|
13058
|
-
s2 += c * c;
|
|
13059
|
-
c = picture(uImage, uv + o);
|
|
13060
|
-
m3 += c;
|
|
13061
|
-
s3 += c * c;
|
|
13062
|
-
}
|
|
13063
|
-
}
|
|
13434
|
+
vec3 m0, m1, m2, m3, s0, s1, s2, s3;
|
|
13435
|
+
if (textureSize(uImage, 0).x > 1) GATHER(imageTap) else GATHER(cardTap)
|
|
13064
13436
|
float n = float((uRadius + 1) * (uRadius + 1));
|
|
13065
13437
|
m0 /= n; m1 /= n; m2 /= n; m3 /= n;
|
|
13066
13438
|
// Weight each square by how calm (low-variance) it is; sharpness sets how strongly the calmest wins.
|
|
@@ -13098,7 +13470,7 @@ var imageTreatments = [
|
|
|
13098
13470
|
];
|
|
13099
13471
|
|
|
13100
13472
|
// src/catalog/shaders/trail.ts
|
|
13101
|
-
function trailHelper(dt, speed) {
|
|
13473
|
+
function trailHelper(dt, speed, walk = false) {
|
|
13102
13474
|
return `
|
|
13103
13475
|
const int TRAIL_POINTS = 32;
|
|
13104
13476
|
vec2 demoPath(float t) {
|
|
@@ -13115,6 +13487,52 @@ vec4 trailPoint(int i, bool demo) {
|
|
|
13115
13487
|
const float dt = ${dt.toFixed(5)};
|
|
13116
13488
|
float t = i == 0 ? iTime : (floor(iTime / dt) + 1.0 - float(i)) * dt;
|
|
13117
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;
|
|
13118
13536
|
}`.trim();
|
|
13119
13537
|
}
|
|
13120
13538
|
var SEGMENT3 = `
|
|
@@ -13129,6 +13547,14 @@ vec2 catmull(vec2 p0, vec2 p1, vec2 p2, vec2 p3, float t) {
|
|
|
13129
13547
|
float t3 = t2 * t;
|
|
13130
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);
|
|
13131
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();
|
|
13132
13558
|
var GLOW5 = `
|
|
13133
13559
|
vec4 glow(vec3 c) {
|
|
13134
13560
|
c = 1.0 - exp(-max(c, 0.0));
|
|
@@ -13225,14 +13651,16 @@ uniform float uSpin; // @control min=0 max=10 default=2 label="Spin"
|
|
|
13225
13651
|
uniform float uShine; // @control min=0 max=1 default=0.7 label="Shine"
|
|
13226
13652
|
uniform float uLife; // @control min=0.1 max=3 default=1.1 label="Fade time"
|
|
13227
13653
|
uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
13228
|
-
trailHelper(1 / 30, 1.5),
|
|
13654
|
+
trailHelper(1 / 30, 1.5, true),
|
|
13229
13655
|
SEGMENT3,
|
|
13230
13656
|
CATMULL,
|
|
13657
|
+
CATMULL_BOX,
|
|
13231
13658
|
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
13232
13659
|
float px = 1.0 / iResolution.y;
|
|
13233
13660
|
vec2 p = fragCoord * px;
|
|
13234
|
-
|
|
13235
|
-
vec4
|
|
13661
|
+
bool live = trailLive(uDemo);
|
|
13662
|
+
vec4 a = trailAt(0, live);
|
|
13663
|
+
vec4 b = trailAt(1, live);
|
|
13236
13664
|
if (a.w < 0.5 || b.w < 0.5) {
|
|
13237
13665
|
fragColor = vec4(0.0);
|
|
13238
13666
|
return;
|
|
@@ -13243,14 +13671,16 @@ uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
|
13243
13671
|
float bestFace = 1.0;
|
|
13244
13672
|
float bestV = 0.0;
|
|
13245
13673
|
float bestFade = 0.0;
|
|
13674
|
+
trailFrom(2, live);
|
|
13246
13675
|
for (int k = 0; k < TRAIL_POINTS - 1; k++) {
|
|
13247
|
-
b = trailPoint(k + 1, uDemo);
|
|
13248
13676
|
if (b.w < 0.5 || trailJump(a, b)) break;
|
|
13249
|
-
|
|
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);
|
|
13250
13679
|
vec2 next = c.w > 0.5 && !trailJump(b, c) ? c.xy : 2.0 * b.xy - a.xy;
|
|
13251
13680
|
vec2 lo = min(min(prev, a.xy), min(b.xy, next)) * px - uWidth - 2.0 * px;
|
|
13252
13681
|
vec2 hi = max(max(prev, a.xy), max(b.xy, next)) * px + uWidth + 2.0 * px;
|
|
13253
|
-
|
|
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) {
|
|
13254
13684
|
vec2 q0 = a.xy * px;
|
|
13255
13685
|
for (int j = 1; j <= 4; j++) {
|
|
13256
13686
|
float t1 = float(j) / 4.0;
|
|
@@ -13276,6 +13706,7 @@ uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
|
13276
13706
|
}
|
|
13277
13707
|
prev = a.xy;
|
|
13278
13708
|
a = b;
|
|
13709
|
+
b = c;
|
|
13279
13710
|
}
|
|
13280
13711
|
float cover = (1.0 - smoothstep(-px, px, best)) * smoothstep(0.0, 0.15, bestFade);
|
|
13281
13712
|
if (cover <= 0.0) {
|
|
@@ -13320,21 +13751,31 @@ uniform float uTwinkle; // @control min=0 max=1 default=0.7 label="Twinkle"
|
|
|
13320
13751
|
uniform float uIntensity; // @control min=0.2 max=3 default=1.2 label="Intensity"
|
|
13321
13752
|
uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
13322
13753
|
HASH,
|
|
13323
|
-
trailHelper(1 / 40, 1.4),
|
|
13754
|
+
trailHelper(1 / 40, 1.4, true),
|
|
13324
13755
|
GLOW5,
|
|
13325
13756
|
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
13326
13757
|
float px = 1.0 / iResolution.y;
|
|
13327
13758
|
vec2 p = fragCoord * px;
|
|
13328
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;
|
|
13329
13764
|
// Point 0 is still moving with the pointer, so sparkles come from the settled points after it.
|
|
13765
|
+
trailFrom(1, live);
|
|
13330
13766
|
for (int k = 1; k < TRAIL_POINTS; k++) {
|
|
13331
|
-
vec4 q =
|
|
13767
|
+
vec4 q = trailNext(live);
|
|
13332
13768
|
if (q.w < 0.5) break;
|
|
13333
13769
|
float age = iTime - q.z;
|
|
13334
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;
|
|
13335
13775
|
float life = age / uLife;
|
|
13336
13776
|
// Points drop off the end of the trail: fade those about to go.
|
|
13337
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;
|
|
13338
13779
|
for (int j = 0; j < 4; j++) {
|
|
13339
13780
|
if (j >= uCount) break;
|
|
13340
13781
|
vec2 h = hash22(vec2(q.z * 61.37 + float(j) * 7.13, float(j) + 0.5));
|
|
@@ -13347,13 +13788,12 @@ uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
|
13347
13788
|
// Turn some of them a little.
|
|
13348
13789
|
float an = (h.y - 0.5) * 0.9;
|
|
13349
13790
|
d = mat2(cos(an), sin(an), -sin(an), cos(an)) * d;
|
|
13350
|
-
float env = smoothstep(0.0, 0.08, life) * (1.0 - life) * (1.0 - life);
|
|
13351
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);
|
|
13352
13792
|
float rays = exp(-abs(d.x) / (s * 0.05)) * exp(-abs(d.y) / (s * 0.45));
|
|
13353
13793
|
rays += exp(-abs(d.y) / (s * 0.05)) * exp(-abs(d.x) / (s * 0.45));
|
|
13354
13794
|
float core = exp(-dot(d, d) / (s * s * 0.02));
|
|
13355
13795
|
vec3 tint = h3 < 0.5 ? uColorA : uColorB;
|
|
13356
|
-
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;
|
|
13357
13797
|
}
|
|
13358
13798
|
}
|
|
13359
13799
|
fragColor = glow(col * uIntensity);
|
|
@@ -13386,7 +13826,7 @@ uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
|
13386
13826
|
HASH,
|
|
13387
13827
|
NOISE,
|
|
13388
13828
|
FBM,
|
|
13389
|
-
trailHelper(1 / 30, 1.3),
|
|
13829
|
+
trailHelper(1 / 30, 1.3, true),
|
|
13390
13830
|
SEGMENT3,
|
|
13391
13831
|
`// The stroke's width at a point from how fast the segment from a to b was drawn: faster, thinner.
|
|
13392
13832
|
float pressWidth(vec4 a, vec4 b, float px) {
|
|
@@ -13397,47 +13837,61 @@ float pressWidth(vec4 a, vec4 b, float px) {
|
|
|
13397
13837
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
13398
13838
|
float px = 1.0 / iResolution.y;
|
|
13399
13839
|
vec2 p = fragCoord * px;
|
|
13400
|
-
|
|
13401
|
-
vec4
|
|
13840
|
+
bool live = trailLive(uDemo);
|
|
13841
|
+
vec4 a = trailAt(0, live);
|
|
13842
|
+
vec4 b = trailAt(1, live);
|
|
13402
13843
|
float best = 1e9;
|
|
13403
13844
|
float bestU = 1.0;
|
|
13404
13845
|
float bestV = 0.0;
|
|
13405
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);
|
|
13406
13849
|
if (a.w > 0.5 && b.w > 0.5) {
|
|
13407
|
-
|
|
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);
|
|
13408
13853
|
for (int k = 0; k < TRAIL_POINTS - 1; k++) {
|
|
13409
|
-
b = trailPoint(k + 1, uDemo);
|
|
13410
13854
|
if (b.w < 0.5 || trailJump(a, b)) break;
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
|
|
13414
|
-
vec2
|
|
13415
|
-
float
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
|
|
13424
|
-
float
|
|
13425
|
-
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
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
|
+
}
|
|
13429
13879
|
}
|
|
13430
13880
|
a = b;
|
|
13431
|
-
|
|
13881
|
+
b = next;
|
|
13432
13882
|
}
|
|
13433
13883
|
}
|
|
13434
13884
|
// Bristles streak along the stroke (they vary across it); the paper's tooth breaks them up.
|
|
13435
|
-
|
|
13436
|
-
float
|
|
13437
|
-
|
|
13438
|
-
|
|
13439
|
-
|
|
13440
|
-
|
|
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
|
+
}
|
|
13441
13895
|
if (uPaper) {
|
|
13442
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)));
|
|
13443
13897
|
fragColor = vec4(mix(paper, col, cover * 0.95), 1.0);
|
|
@@ -13555,9 +14009,10 @@ uniform float uLife; // @control min=0.5 max=10 default=4 label="Fade time"
|
|
|
13555
14009
|
uniform float uFlicker; // @control min=0 max=1 default=0.25 label="Flicker"
|
|
13556
14010
|
uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
13557
14011
|
HASH,
|
|
13558
|
-
trailHelper(0.05, 1.1),
|
|
14012
|
+
trailHelper(0.05, 1.1, true),
|
|
13559
14013
|
SEGMENT3,
|
|
13560
14014
|
CATMULL,
|
|
14015
|
+
CATMULL_BOX,
|
|
13561
14016
|
`vec3 spectrum(float h) {
|
|
13562
14017
|
return 0.5 + 0.5 * cos(6.28318 * (h + vec3(0.0, 0.33, 0.67)));
|
|
13563
14018
|
}
|
|
@@ -13570,8 +14025,9 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
13570
14025
|
vec3 col = uBackground * (1.0 - 0.45 * dot(uv - 0.5, uv - 0.5) * 2.0);
|
|
13571
14026
|
col += (hash12(fragCoord + fract(iTime) * 113.0) - 0.5) * 0.012;
|
|
13572
14027
|
|
|
13573
|
-
|
|
13574
|
-
vec4
|
|
14028
|
+
bool live = trailLive(uDemo);
|
|
14029
|
+
vec4 a = trailAt(0, live);
|
|
14030
|
+
vec4 b = trailAt(1, live);
|
|
13575
14031
|
if (a.w > 0.5 && b.w > 0.5) {
|
|
13576
14032
|
float glowR = uWidth * 4.0 * max(uGlow, 0.05);
|
|
13577
14033
|
float margin = uWidth * 3.0 + glowR * 6.0 + 2.0 * px;
|
|
@@ -13580,35 +14036,44 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
13580
14036
|
float halo = 0.0;
|
|
13581
14037
|
float best = 1e9;
|
|
13582
14038
|
float bestWhen = a.z;
|
|
14039
|
+
trailFrom(2, live);
|
|
13583
14040
|
for (int k = 0; k < TRAIL_POINTS - 1; k++) {
|
|
13584
|
-
b = trailPoint(k + 1, uDemo);
|
|
13585
14041
|
if (b.w < 0.5) break;
|
|
13586
14042
|
// Earlier strokes stay lit, but the jump between strokes is not drawn.
|
|
13587
14043
|
bool jump = trailJump(a, b);
|
|
13588
|
-
|
|
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);
|
|
13589
14046
|
vec2 next = c.w > 0.5 && !trailJump(b, c) ? c.xy : 2.0 * b.xy - a.xy;
|
|
13590
14047
|
vec2 lo = min(min(prev, a.xy), min(b.xy, next)) * px - margin;
|
|
13591
14048
|
vec2 hi = max(max(prev, a.xy), max(b.xy, next)) * px + margin;
|
|
13592
14049
|
if (!jump && all(greaterThan(p, lo)) && all(lessThan(p, hi))) {
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
|
|
13598
|
-
|
|
13599
|
-
|
|
13600
|
-
|
|
13601
|
-
|
|
13602
|
-
|
|
13603
|
-
|
|
13604
|
-
|
|
13605
|
-
|
|
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;
|
|
13606
14071
|
}
|
|
13607
|
-
q0 = q1;
|
|
13608
14072
|
}
|
|
13609
14073
|
}
|
|
13610
14074
|
prev = jump ? 2.0 * b.xy - next : a.xy;
|
|
13611
14075
|
a = b;
|
|
14076
|
+
b = c;
|
|
13612
14077
|
}
|
|
13613
14078
|
// Colour from when the light passed, so each part keeps its colour as it fades.
|
|
13614
14079
|
vec3 tint = spectrum(uHue + bestWhen * uCycle);
|
|
@@ -13657,6 +14122,16 @@ vec4 clickAt(int i, bool demo, float period) {
|
|
|
13657
14122
|
vec2 h = hash22(vec2(k * 1.37 + 0.5, 3.7));
|
|
13658
14123
|
return vec4((0.2 + 0.6 * h) * iResolution.xy, k * period, 1.0);
|
|
13659
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();
|
|
13660
14135
|
var flowParticles = {
|
|
13661
14136
|
kind: "shader",
|
|
13662
14137
|
id: "flow-particles",
|
|
@@ -13679,19 +14154,25 @@ uniform vec3 uFast; // @control color default=#7CFFE0 label="Fast colour
|
|
|
13679
14154
|
uniform vec3 uBackground; // @control color default=#04060E label="Background"
|
|
13680
14155
|
uniform bool uTransparent; // @control default=false label="Transparent"`,
|
|
13681
14156
|
HASH,
|
|
13682
|
-
|
|
14157
|
+
CORNER_HASHES,
|
|
13683
14158
|
GLOW6,
|
|
13684
|
-
|
|
13685
|
-
|
|
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);
|
|
13686
14168
|
}
|
|
13687
14169
|
|
|
13688
|
-
// 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.
|
|
13689
14172
|
vec2 flow(vec2 p, float t) {
|
|
13690
|
-
|
|
13691
|
-
|
|
13692
|
-
|
|
13693
|
-
float dy = potential(p + vec2(0.0, e), t) - c;
|
|
13694
|
-
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);
|
|
13695
14176
|
}
|
|
13696
14177
|
|
|
13697
14178
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
@@ -13701,22 +14182,38 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
13701
14182
|
float ft = iTime * uSpeed * 0.04;
|
|
13702
14183
|
float tail = max(uTail * float(STEPS), 1.0);
|
|
13703
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;
|
|
13704
14192
|
for (int k = 0; k < STEPS; k++) {
|
|
13705
|
-
|
|
13706
|
-
|
|
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
|
+
}
|
|
13707
14198
|
// One particle per cell at most; each life starts somewhere new in the cell.
|
|
13708
14199
|
vec2 c = floor(q * uDensity);
|
|
13709
|
-
|
|
13710
|
-
|
|
13711
|
-
|
|
13712
|
-
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
|
|
13716
|
-
|
|
13717
|
-
|
|
13718
|
-
|
|
13719
|
-
|
|
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);
|
|
13720
14217
|
float w = 1.0 - behind / tail;
|
|
13721
14218
|
float fade = smoothstep(0.0, 0.08, life) * (1.0 - smoothstep(0.75, 1.0, life));
|
|
13722
14219
|
vec3 hue = mix(uSlow, uFast, smoothstep(0.3, 2.2, sp));
|
|
@@ -13984,12 +14481,40 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
13984
14481
|
float t = iTime * uSpeed * 0.5;
|
|
13985
14482
|
float len = 0.018 * uSize;
|
|
13986
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
|
+
}
|
|
13987
14501
|
for (int i = 0; i < 120; i++) {
|
|
13988
|
-
if (i >= uCount) break;
|
|
14502
|
+
if (near == 0 || i >= uCount) break;
|
|
13989
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;
|
|
13990
14516
|
float scale = 0.7 + 0.5 * hash12(vec2(fi, 6.6));
|
|
13991
14517
|
float l = len * scale;
|
|
13992
|
-
vec2 pos = birdAt(fi, t, aspect);
|
|
13993
14518
|
vec2 dp = p - pos;
|
|
13994
14519
|
if (abs(dp.x) > l * 1.2 || abs(dp.y) > l * 1.2) continue;
|
|
13995
14520
|
vec2 v = pos - birdAt(fi, t - 0.03, aspect);
|
|
@@ -14048,7 +14573,7 @@ uniform float uDensity; // @control min=40 max=240 default=120 step=1 label
|
|
|
14048
14573
|
uniform float uScatter; // @control min=0.1 max=1.5 default=0.4 label="Scatter distance"
|
|
14049
14574
|
uniform float uDirection; // @control min=-180 max=180 default=20 label="Drift direction"`,
|
|
14050
14575
|
HASH,
|
|
14051
|
-
|
|
14576
|
+
CORNER_HASHES,
|
|
14052
14577
|
`// The text image's alpha at frame point p (height 1, centred), the image contained in uSize of the frame.
|
|
14053
14578
|
float letters(vec2 p) {
|
|
14054
14579
|
vec2 size = vec2(textureSize(uText, 0));
|
|
@@ -14060,6 +14585,15 @@ float letters(vec2 p) {
|
|
|
14060
14585
|
return texture(uText, uv).a;
|
|
14061
14586
|
}
|
|
14062
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
|
+
|
|
14063
14597
|
// How far through dissolving the particle at home is (0 in place, 1 gone): left to right, then back together.
|
|
14064
14598
|
float scattered(vec2 home, float phase) {
|
|
14065
14599
|
float frame = iResolution.x / iResolution.y;
|
|
@@ -14070,16 +14604,40 @@ float scattered(vec2 home, float phase) {
|
|
|
14070
14604
|
return a * a * (3.0 - 2.0 * a);
|
|
14071
14605
|
}
|
|
14072
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
|
+
|
|
14073
14631
|
// Where the wind has carried a particle of group g from home: smooth in home, so it can be inverted.
|
|
14074
14632
|
// Each of the six groups rides its own gust (speed, heading and eddies), so they spread apart.
|
|
14075
|
-
vec2 carried(vec2 home, float phase, float g) {
|
|
14633
|
+
vec2 carried(vec2 home, float phase, float g, vec3 gust) {
|
|
14076
14634
|
float a = scattered(home, phase);
|
|
14077
|
-
|
|
14078
|
-
|
|
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);
|
|
14079
14637
|
float t = phase * 3.0;
|
|
14080
14638
|
vec2 q = home * 3.0 + g * 5.3;
|
|
14081
|
-
vec2 swirl = vec2(
|
|
14082
|
-
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));
|
|
14083
14641
|
}
|
|
14084
14642
|
|
|
14085
14643
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
@@ -14093,22 +14651,28 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
14093
14651
|
vec3 col = uColor;
|
|
14094
14652
|
float cover = solid;
|
|
14095
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;
|
|
14096
14657
|
for (int gi = 0; gi < 6; gi++) {
|
|
14658
|
+
if (!moving) break;
|
|
14097
14659
|
float g = float(gi);
|
|
14660
|
+
vec3 gust = gustOf(g);
|
|
14098
14661
|
// Trace back to the home a particle of this group here would have come from, then search the cells round it.
|
|
14099
14662
|
vec2 home = p;
|
|
14100
|
-
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;
|
|
14101
14666
|
vec2 base = floor(home / cell);
|
|
14102
14667
|
for (int y = -1; y <= 1; y++) {
|
|
14103
14668
|
for (int x = -1; x <= 1; x++) {
|
|
14104
14669
|
vec2 c = base + vec2(float(x), float(y));
|
|
14105
14670
|
if (floor(hash12(c + 0.73) * 6.0) != g) continue;
|
|
14106
14671
|
vec2 hc = (c + 0.5) * cell;
|
|
14107
|
-
if (letters(hc) < 0.5) continue;
|
|
14108
14672
|
float a = scattered(hc, phase);
|
|
14109
|
-
if (a < 0.002) continue;
|
|
14673
|
+
if (a < 0.002 || letters(hc) < 0.5) continue;
|
|
14110
14674
|
vec2 j = hash22(c);
|
|
14111
|
-
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;
|
|
14112
14676
|
float r = cell * (0.5 - 0.1 * a) * (0.75 + 0.5 * j.x);
|
|
14113
14677
|
float d = length(p - pos);
|
|
14114
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);
|
|
@@ -14166,14 +14730,32 @@ vec4 blast(vec4 acc, vec2 p, vec2 origin, float angle, float spread, float speed
|
|
|
14166
14730
|
float e = (1.0 - exp(-k * age)) / k;
|
|
14167
14731
|
float fade = 1.0 - smoothstep(LIFE - 1.0, LIFE, age);
|
|
14168
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
|
+
}
|
|
14169
14746
|
for (int j = 0; j < 80; j++) {
|
|
14170
14747
|
if (j >= count) break;
|
|
14171
14748
|
float fj = float(j);
|
|
14172
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;
|
|
14173
14757
|
vec2 h2 = hash22(h * 53.1 + seed);
|
|
14174
14758
|
float h3 = hash12(h2 * 17.7 + fj);
|
|
14175
|
-
float a = angle + (h.x - 0.5) * spread;
|
|
14176
|
-
vec2 v0 = vec2(cos(a), sin(a)) * speed * (0.5 + 0.5 * h.y);
|
|
14177
14759
|
float fall = 0.16 * uFall * (0.7 + 0.6 * h2.x);
|
|
14178
14760
|
vec2 pos = origin + v0 * e - vec2(0.0, fall * (age - e));
|
|
14179
14761
|
pos.x += 0.035 * sin(age * (2.0 + 2.0 * h2.y) + h.x * 6.28) * smoothstep(0.3, 1.2, age);
|
|
@@ -14586,18 +15168,6 @@ vec3 shardOf(vec2 q) {
|
|
|
14586
15168
|
return vec3(sid, ring, edge);
|
|
14587
15169
|
}
|
|
14588
15170
|
|
|
14589
|
-
// A shard's middle, which it turns about, and how far it reaches from there.
|
|
14590
|
-
vec3 shardCentre(float sid, float ring) {
|
|
14591
|
-
float a0 = spoke(sid);
|
|
14592
|
-
float a1 = spoke(sid + 1.0);
|
|
14593
|
-
float mid = 0.5 * (a0 + a1);
|
|
14594
|
-
float inner = ring > 0.0 ? ringAt(sid, ring) : 0.0;
|
|
14595
|
-
float outer = ring + 1.0 < float(RINGS) ? ringAt(sid, ring + 1.0) : inner * uGrowth;
|
|
14596
|
-
float rc = ring > 0.0 ? 0.5 * (inner + outer) : 0.66 * outer;
|
|
14597
|
-
float reach = length(vec2(max(outer - rc, rc - inner), outer * tan(0.5 * (a1 - a0))));
|
|
14598
|
-
return vec3(vec2(cos(mid), sin(mid)) * rc, reach + 0.01);
|
|
14599
|
-
}
|
|
14600
|
-
|
|
14601
15171
|
// When a shard lets go: the middle first, the outer pane last.
|
|
14602
15172
|
float release(float sid, float ring) {
|
|
14603
15173
|
return 0.3 + 0.16 * ring + 0.25 * hash12(vec2(sid, ring + 9.0));
|
|
@@ -14619,33 +15189,109 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
14619
15189
|
vec4 col = vec4(uBackground, 1.0);
|
|
14620
15190
|
|
|
14621
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.
|
|
14622
15195
|
float best = 1e3;
|
|
14623
15196
|
vec4 hit = vec4(0.0);
|
|
14624
15197
|
vec2 hitCentre = vec2(0.0);
|
|
14625
|
-
|
|
14626
|
-
|
|
14627
|
-
|
|
14628
|
-
|
|
14629
|
-
|
|
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);
|
|
14630
15240
|
float tr = release(sid, ring);
|
|
14631
15241
|
float dt = age - tr;
|
|
14632
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;
|
|
14633
15265
|
vec2 h = hash22(vec2(sid, ring) + 17.3);
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
vec2 rel = q - cen.xy - ballistic(v0, 0.6, uGravity, dt);
|
|
14637
|
-
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;
|
|
14638
15268
|
float ang = (h.y - 0.5) * 2.0 * uSpin * dt;
|
|
14639
15269
|
// It also turns about its own axis, narrowing and catching the light.
|
|
14640
15270
|
float tilt = cos(dt * (0.6 + 1.2 * h.x) * uSpin);
|
|
14641
15271
|
vec2 local = rot(-ang) * rel;
|
|
14642
15272
|
local.x /= sign(tilt) * max(abs(tilt), 0.05);
|
|
14643
|
-
vec2 orig = cen
|
|
14644
|
-
|
|
14645
|
-
|
|
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);
|
|
14646
15292
|
best = tr;
|
|
14647
|
-
hit = vec4(orig, tilt,
|
|
14648
|
-
hitCentre = cen
|
|
15293
|
+
hit = vec4(orig, tilt, edge);
|
|
15294
|
+
hitCentre = cen;
|
|
14649
15295
|
}
|
|
14650
15296
|
}
|
|
14651
15297
|
|
|
@@ -15127,6 +15773,14 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
15127
15773
|
float b = now - float(k);
|
|
15128
15774
|
float a = e - b * BATCH;
|
|
15129
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;
|
|
15130
15784
|
// Back from where a grain is to where it came from.
|
|
15131
15785
|
vec2 s = p - blown(a, p);
|
|
15132
15786
|
s = p - blown(a, s);
|
|
@@ -16052,71 +16706,2188 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
|
16052
16706
|
};
|
|
16053
16707
|
var comicShaders = [comicBook, celShade, focusLines, actionLines, transitionComicPanels];
|
|
16054
16708
|
|
|
16055
|
-
// src/catalog/
|
|
16056
|
-
var
|
|
16057
|
-
|
|
16058
|
-
|
|
16059
|
-
|
|
16060
|
-
|
|
16061
|
-
"space",
|
|
16062
|
-
"retro",
|
|
16063
|
-
"image",
|
|
16064
|
-
"overlay",
|
|
16065
|
-
"text",
|
|
16066
|
-
"morph",
|
|
16067
|
-
"interaction",
|
|
16068
|
-
"pyrotechnics",
|
|
16069
|
-
"timewarp",
|
|
16070
|
-
"melt",
|
|
16071
|
-
"electric",
|
|
16072
|
-
"splatter",
|
|
16073
|
-
"bubble",
|
|
16074
|
-
"sticker",
|
|
16075
|
-
"trail",
|
|
16076
|
-
"particles",
|
|
16077
|
-
"debris",
|
|
16078
|
-
"impact",
|
|
16079
|
-
"comic",
|
|
16080
|
-
"transition",
|
|
16081
|
-
"loader",
|
|
16082
|
-
"data",
|
|
16083
|
-
"icon"
|
|
16084
|
-
];
|
|
16085
|
-
var VIDEO_USES = ["filter", "transition", "interactive", "stills", "none"];
|
|
16086
|
-
var VECTOR_CONTROL_TYPES = ["float", "int", "bool", "color", "text"];
|
|
16087
|
-
var PLACEHOLDER = /\{\{\s*([A-Za-z_]\w*)\s*(?::\s*([A-Za-z0-9]*)([^}]*))?\}\}/g;
|
|
16088
|
-
function motionStrings(motion) {
|
|
16089
|
-
const out = [];
|
|
16090
|
-
const walk = (v) => {
|
|
16091
|
-
if (typeof v === "string") out.push(v);
|
|
16092
|
-
else if (Array.isArray(v)) v.forEach(walk);
|
|
16093
|
-
else if (v && typeof v === "object") Object.values(v).forEach(walk);
|
|
16094
|
-
};
|
|
16095
|
-
walk(motion);
|
|
16096
|
-
return out;
|
|
16709
|
+
// src/catalog/shaders/atmosphere.ts
|
|
16710
|
+
var NOISE_3D = `
|
|
16711
|
+
float hash13(vec3 p3) {
|
|
16712
|
+
p3 = fract(p3 * 0.1031);
|
|
16713
|
+
p3 += dot(p3, p3.zyx + 31.32);
|
|
16714
|
+
return fract((p3.x + p3.y) * p3.z);
|
|
16097
16715
|
}
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
16101
|
-
|
|
16102
|
-
|
|
16103
|
-
|
|
16104
|
-
|
|
16105
|
-
|
|
16106
|
-
|
|
16107
|
-
}
|
|
16108
|
-
}
|
|
16109
|
-
return controls;
|
|
16716
|
+
float vnoise3(vec3 p) {
|
|
16717
|
+
vec3 i = floor(p);
|
|
16718
|
+
vec3 f = fract(p);
|
|
16719
|
+
vec3 u = f * f * (3.0 - 2.0 * f);
|
|
16720
|
+
float a = mix(hash13(i), hash13(i + vec3(1.0, 0.0, 0.0)), u.x);
|
|
16721
|
+
float b = mix(hash13(i + vec3(0.0, 1.0, 0.0)), hash13(i + vec3(1.0, 1.0, 0.0)), u.x);
|
|
16722
|
+
float c = mix(hash13(i + vec3(0.0, 0.0, 1.0)), hash13(i + vec3(1.0, 0.0, 1.0)), u.x);
|
|
16723
|
+
float d = mix(hash13(i + vec3(0.0, 1.0, 1.0)), hash13(i + vec3(1.0, 1.0, 1.0)), u.x);
|
|
16724
|
+
return mix(mix(a, b, u.y), mix(c, d, u.y), u.z);
|
|
16110
16725
|
}
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
16114
|
-
|
|
16115
|
-
|
|
16726
|
+
float fbm3(vec3 p, int octaves) {
|
|
16727
|
+
float sum = 0.0;
|
|
16728
|
+
float amp = 0.5;
|
|
16729
|
+
for (int i = 0; i < 6; i++) {
|
|
16730
|
+
if (i >= octaves) break;
|
|
16731
|
+
sum += amp * vnoise3(p);
|
|
16732
|
+
p = p * 2.03 + vec3(11.7, 3.1, 7.9);
|
|
16733
|
+
amp *= 0.5;
|
|
16734
|
+
}
|
|
16735
|
+
return sum;
|
|
16736
|
+
}`.trim();
|
|
16737
|
+
var OVER10 = `
|
|
16738
|
+
vec4 over(vec4 acc, vec3 c, float a) {
|
|
16739
|
+
return vec4(acc.rgb * (1.0 - a) + c * a, acc.a * (1.0 - a) + a);
|
|
16116
16740
|
}
|
|
16117
|
-
|
|
16118
|
-
|
|
16119
|
-
|
|
16741
|
+
vec4 unpremultiply(vec4 acc) {
|
|
16742
|
+
return vec4(acc.rgb / max(acc.a, 1e-4), acc.a);
|
|
16743
|
+
}`.trim();
|
|
16744
|
+
var volumetricClouds = {
|
|
16745
|
+
kind: "shader",
|
|
16746
|
+
id: "volumetric-clouds",
|
|
16747
|
+
name: "Volumetric clouds",
|
|
16748
|
+
description: "A ray-marched layer of soft, self-shadowed clouds drifting overhead, rimmed with a silver lining toward the sun.",
|
|
16749
|
+
category: "atmosphere",
|
|
16750
|
+
tags: ["atmosphere", "clouds", "sky", "background", "3d", "weather"],
|
|
16751
|
+
source: glsl(
|
|
16752
|
+
`// Volumetric clouds: a cloud slab ray-marched with fbm density and a short march toward the sun.
|
|
16753
|
+
uniform vec3 uSkyTop; // @control color default=#3A78C9 label="Sky (top)"
|
|
16754
|
+
uniform vec3 uSkyHorizon; // @control color default=#B8D8F2 label="Sky (horizon)"
|
|
16755
|
+
uniform vec3 uCloudColor; // @control color default=#FFFFFF label="Cloud colour"
|
|
16756
|
+
uniform vec3 uShadowColor; // @control color default=#5E6F8A label="Shadow colour"
|
|
16757
|
+
uniform vec3 uSunColor; // @control color default=#FFF3DC label="Sun colour"
|
|
16758
|
+
uniform float uSunAzimuth; // @control min=-3.1416 max=3.1416 default=0.4 label="Sun direction"
|
|
16759
|
+
uniform float uSunHeight; // @control min=0 max=1.5 default=0.35 label="Sun height"
|
|
16760
|
+
uniform float uCoverage; // @control min=0 max=1 default=0.5 label="Coverage"
|
|
16761
|
+
uniform float uDensity; // @control min=0.1 max=3 default=1 label="Density"
|
|
16762
|
+
uniform float uSpeed; // @control min=0 max=3 default=0.6 label="Drift speed"`,
|
|
16763
|
+
NOISE_3D,
|
|
16764
|
+
`const float BOTTOM = 1.5;
|
|
16765
|
+
const float TOP = 3.2;
|
|
16766
|
+
|
|
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) {
|
|
16818
|
+
float h = clamp((p.y - BOTTOM) / (TOP - BOTTOM), 0.0, 1.0);
|
|
16819
|
+
// Rounded tops and flat-ish bases.
|
|
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;
|
|
16825
|
+
return clamp((n - (1.0 - uCoverage) * 0.75) * 3.0 * shape, 0.0, 1.0) * uDensity;
|
|
16826
|
+
}
|
|
16827
|
+
|
|
16828
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
16829
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
|
|
16830
|
+
vec3 rd = normalize(vec3(p.x, p.y + 0.28, 1.0));
|
|
16831
|
+
vec3 sun = normalize(vec3(sin(uSunAzimuth), uSunHeight, cos(uSunAzimuth)));
|
|
16832
|
+
float mu = dot(rd, sun);
|
|
16833
|
+
|
|
16834
|
+
vec3 sky = mix(uSkyHorizon, uSkyTop, pow(clamp(rd.y, 0.0, 1.0), 0.6));
|
|
16835
|
+
sky += uSunColor * (pow(max(mu, 0.0), 600.0) * 2.0 + pow(max(mu, 0.0), 12.0) * 0.25);
|
|
16836
|
+
if (rd.y < 0.0) sky = mix(uSkyHorizon, uShadowColor, clamp(-rd.y * 4.0, 0.0, 0.6));
|
|
16837
|
+
|
|
16838
|
+
vec3 col = vec3(0.0);
|
|
16839
|
+
float trans = 1.0;
|
|
16840
|
+
if (rd.y > 0.02) {
|
|
16841
|
+
float t0 = BOTTOM / rd.y;
|
|
16842
|
+
float t1 = min(TOP / rd.y, t0 + 14.0);
|
|
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;
|
|
16847
|
+
float t = t0 + dt * fract(sin(dot(fragCoord, vec2(12.9898, 78.233))) * 43758.5453);
|
|
16848
|
+
// Henyey-Greenstein: a forward lobe for the silver lining plus a soft back lobe.
|
|
16849
|
+
float g = 0.65;
|
|
16850
|
+
float hg = (1.0 - g * g) / pow(1.0 + g * g - 2.0 * g * mu, 1.5) / 12.566;
|
|
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();
|
|
16859
|
+
for (int i = 0; i < 40; i++) {
|
|
16860
|
+
if (trans < 0.02 || t > 60.0 || float(i) >= steps) break;
|
|
16861
|
+
vec3 pos = rd * t;
|
|
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);
|
|
16867
|
+
if (d > 0.01) {
|
|
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.
|
|
16870
|
+
float shade = 0.0;
|
|
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);
|
|
16872
|
+
float light = exp(-shade * 1.4);
|
|
16873
|
+
float powder = 1.0 - exp(-d * 4.0);
|
|
16874
|
+
vec3 lit = mix(uShadowColor, uCloudColor, light) + uSunColor * light * powder * phase * 2.5;
|
|
16875
|
+
float a = 1.0 - exp(-d * dt * 2.2);
|
|
16876
|
+
col += trans * a * lit;
|
|
16877
|
+
trans *= 1.0 - a;
|
|
16878
|
+
}
|
|
16879
|
+
t += dt;
|
|
16880
|
+
}
|
|
16881
|
+
// Far clouds fade into the horizon haze.
|
|
16882
|
+
float haze = exp(-max(t0 - 4.0, 0.0) * 0.06);
|
|
16883
|
+
col = mix(sky * (1.0 - trans), col, haze);
|
|
16884
|
+
}
|
|
16885
|
+
fragColor = vec4(sky * trans + col, 1.0);
|
|
16886
|
+
}`
|
|
16887
|
+
),
|
|
16888
|
+
presets: [
|
|
16889
|
+
{ name: "Midday", values: { uSunHeight: 1.2, uCoverage: 0.45, uDensity: 0.9 } },
|
|
16890
|
+
{
|
|
16891
|
+
name: "Sunset",
|
|
16892
|
+
values: {
|
|
16893
|
+
uSkyTop: [0.24, 0.27, 0.52],
|
|
16894
|
+
uSkyHorizon: [1, 0.62, 0.38],
|
|
16895
|
+
uCloudColor: [1, 0.78, 0.6],
|
|
16896
|
+
uShadowColor: [0.36, 0.25, 0.38],
|
|
16897
|
+
uSunColor: [1, 0.55, 0.25],
|
|
16898
|
+
uSunAzimuth: 0,
|
|
16899
|
+
uSunHeight: 0.08,
|
|
16900
|
+
uCoverage: 0.5
|
|
16901
|
+
}
|
|
16902
|
+
},
|
|
16903
|
+
{
|
|
16904
|
+
name: "Storm",
|
|
16905
|
+
values: {
|
|
16906
|
+
uSkyTop: [0.22, 0.25, 0.3],
|
|
16907
|
+
uSkyHorizon: [0.45, 0.48, 0.52],
|
|
16908
|
+
uCloudColor: [0.62, 0.65, 0.7],
|
|
16909
|
+
uShadowColor: [0.12, 0.13, 0.17],
|
|
16910
|
+
uSunColor: [0.75, 0.8, 0.9],
|
|
16911
|
+
uCoverage: 0.85,
|
|
16912
|
+
uDensity: 2.2,
|
|
16913
|
+
uSpeed: 1.8
|
|
16914
|
+
}
|
|
16915
|
+
}
|
|
16916
|
+
]
|
|
16917
|
+
};
|
|
16918
|
+
var fogBanks = {
|
|
16919
|
+
kind: "shader",
|
|
16920
|
+
id: "fog-banks",
|
|
16921
|
+
name: "Fog banks",
|
|
16922
|
+
description: "Horizontal banks of fog drifting at different depths and speeds, thickest near the ground, to lay over any scene.",
|
|
16923
|
+
category: "atmosphere",
|
|
16924
|
+
tags: ["atmosphere", "fog", "overlay", "moody", "weather"],
|
|
16925
|
+
transparent: true,
|
|
16926
|
+
source: glsl(
|
|
16927
|
+
`// Fog banks: layered bands of fbm fog sliding sideways, near ones lower, faster and denser.
|
|
16928
|
+
uniform vec3 uColor; // @control color default=#DCE3EA label="Colour"
|
|
16929
|
+
uniform float uDensity; // @control min=0 max=1.5 default=0.8 label="Density"
|
|
16930
|
+
uniform float uHeight; // @control min=0.1 max=1 default=0.55 label="Height"
|
|
16931
|
+
uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"
|
|
16932
|
+
uniform int uLayers; // @control min=1 max=6 default=4 label="Layers"`,
|
|
16933
|
+
HASH,
|
|
16934
|
+
NOISE,
|
|
16935
|
+
FBM,
|
|
16936
|
+
OVER10,
|
|
16937
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
16938
|
+
vec2 p = fragCoord / iResolution.y;
|
|
16939
|
+
float y = fragCoord.y / iResolution.y;
|
|
16940
|
+
vec4 acc = vec4(0.0);
|
|
16941
|
+
// Far layers first (high on screen, slow, thin), near layers painted over them.
|
|
16942
|
+
for (int i = 0; i < 6; i++) {
|
|
16943
|
+
if (i >= uLayers) break;
|
|
16944
|
+
float fi = float(i);
|
|
16945
|
+
float near = (fi + 1.0) / float(uLayers);
|
|
16946
|
+
float centre = uHeight * mix(0.85, 0.12, near);
|
|
16947
|
+
float thick = uHeight * mix(0.12, 0.3, near);
|
|
16948
|
+
float band = exp(-pow((y - centre) / thick, 2.0));
|
|
16949
|
+
band = max(band, step(y, centre) * mix(0.3, 1.0, near));
|
|
16950
|
+
float scale = mix(1.2, 2.6, near);
|
|
16951
|
+
vec2 q = vec2(p.x * scale + iTime * uSpeed * mix(0.015, 0.08, near) + fi * 7.3, p.y * scale * 3.0 + fi * 2.1);
|
|
16952
|
+
float n = fbm(q + 0.6 * vec2(fbm(q * 0.7 - iTime * 0.02, 3), 0.0), 5);
|
|
16953
|
+
float a = smoothstep(0.3, 0.8, n) * band * uDensity * mix(0.35, 0.8, near);
|
|
16954
|
+
// Ground haze: everything thickens toward the bottom.
|
|
16955
|
+
a = clamp(a + (1.0 - smoothstep(0.0, uHeight * 0.4, y)) * 0.12 * uDensity * near, 0.0, 1.0);
|
|
16956
|
+
vec3 c = uColor * mix(0.85, 1.0, n);
|
|
16957
|
+
acc = over(acc, c, a);
|
|
16958
|
+
}
|
|
16959
|
+
fragColor = unpremultiply(acc);
|
|
16960
|
+
}`
|
|
16961
|
+
),
|
|
16962
|
+
presets: [
|
|
16963
|
+
{ name: "Valley morning", values: { uColor: [0.96, 0.93, 0.88], uDensity: 0.7, uHeight: 0.45, uSpeed: 0.6 } },
|
|
16964
|
+
{ name: "Moor", values: { uColor: [0.66, 0.72, 0.75], uDensity: 1.2, uHeight: 0.8, uSpeed: 1.4, uLayers: 6 } }
|
|
16965
|
+
]
|
|
16966
|
+
};
|
|
16967
|
+
var risingSteam = {
|
|
16968
|
+
kind: "shader",
|
|
16969
|
+
id: "rising-steam",
|
|
16970
|
+
name: "Rising steam",
|
|
16971
|
+
description: "Wisps of steam curling up from a cup or vent, thinning and fading as they rise.",
|
|
16972
|
+
category: "atmosphere",
|
|
16973
|
+
tags: ["atmosphere", "steam", "overlay", "coffee", "warm"],
|
|
16974
|
+
transparent: true,
|
|
16975
|
+
source: glsl(
|
|
16976
|
+
`// Rising steam: stretched noise wisps advected upward from a source, swaying and dissolving.
|
|
16977
|
+
uniform vec2 uSource; // @control min=-1 max=1 default=0,-0.6 label="Source"
|
|
16978
|
+
uniform vec3 uColor; // @control color default=#FFFFFF label="Colour"
|
|
16979
|
+
uniform float uWidth; // @control min=0.02 max=0.6 default=0.12 label="Width"
|
|
16980
|
+
uniform float uHeight; // @control min=0.2 max=1.5 default=0.8 label="Height"
|
|
16981
|
+
uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"
|
|
16982
|
+
uniform float uTurbulence; // @control min=0 max=2 default=0.8 label="Curl"
|
|
16983
|
+
uniform float uOpacity; // @control min=0 max=1 default=0.7 label="Opacity"`,
|
|
16984
|
+
HASH,
|
|
16985
|
+
NOISE,
|
|
16986
|
+
FBM,
|
|
16987
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
16988
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
|
|
16989
|
+
float aspect = iResolution.x / iResolution.y;
|
|
16990
|
+
vec2 src = uSource * vec2(aspect, 1.0) * 0.5;
|
|
16991
|
+
vec2 q = p - src;
|
|
16992
|
+
float h = q.y / uHeight;
|
|
16993
|
+
if (h < -0.05 || h > 1.2) { fragColor = vec4(uColor, 0.0); return; }
|
|
16994
|
+
float hc = clamp(h, 0.0, 1.0);
|
|
16995
|
+
float t = iTime * uSpeed;
|
|
16996
|
+
// The plume widens and sways more as it rises.
|
|
16997
|
+
float w = uWidth * (0.6 + 1.6 * hc);
|
|
16998
|
+
float sway = (sin(q.y * 5.0 - t * 1.1) * 0.5 + sin(q.y * 9.0 - t * 1.7 + 1.3) * 0.3) * uTurbulence * 0.06 * hc;
|
|
16999
|
+
vec2 flow = vec2(q.x, q.y - t * 0.25);
|
|
17000
|
+
vec2 warp = vec2(fbm(flow * 3.0 + vec2(0.0, -t * 0.2), 4), fbm(flow * 3.0 + vec2(5.2, 1.3), 4)) - 0.5;
|
|
17001
|
+
float x = (q.x - sway + warp.x * uTurbulence * 0.25 * hc) / w;
|
|
17002
|
+
// Wisps: noise stretched along the rise, so it reads as strands.
|
|
17003
|
+
vec2 sq = vec2(x * 2.2, (q.y - t * 0.3) / uHeight * 1.6) + warp * uTurbulence * 1.5;
|
|
17004
|
+
float strands = fbm(sq * vec2(1.0, 0.45), 5);
|
|
17005
|
+
float body = exp(-x * x * 1.6);
|
|
17006
|
+
float wisp = smoothstep(0.42, 0.75, strands + 0.25 * body);
|
|
17007
|
+
float a = wisp * body;
|
|
17008
|
+
// Born at the source, thinning and fading as it rises.
|
|
17009
|
+
a *= smoothstep(-0.05, 0.12, h) * (1.0 - smoothstep(0.35, 1.05, h));
|
|
17010
|
+
a *= mix(1.0, 0.55, hc);
|
|
17011
|
+
fragColor = vec4(uColor, clamp(a * uOpacity * 1.4, 0.0, 1.0));
|
|
17012
|
+
}`
|
|
17013
|
+
),
|
|
17014
|
+
presets: [
|
|
17015
|
+
{ name: "Coffee cup", values: { uSource: [0, -0.5], uWidth: 0.08, uHeight: 0.6, uSpeed: 0.7, uTurbulence: 1, uOpacity: 0.55 } },
|
|
17016
|
+
{ name: "Vent", values: { uSource: [0, -1], uWidth: 0.3, uHeight: 1.3, uSpeed: 1.4, uTurbulence: 0.6, uOpacity: 0.8 } },
|
|
17017
|
+
{ name: "Kettle burst", values: { uSource: [-0.4, -0.3], uWidth: 0.05, uHeight: 1.1, uSpeed: 2.6, uTurbulence: 1.8, uOpacity: 0.95 } }
|
|
17018
|
+
]
|
|
17019
|
+
};
|
|
17020
|
+
var mistOverImage = {
|
|
17021
|
+
kind: "shader",
|
|
17022
|
+
id: "mist-over-image",
|
|
17023
|
+
name: "Mist over image",
|
|
17024
|
+
description: "Slow mist drifting over a picture, thickest low down and in the distance, with light softly scattering through it.",
|
|
17025
|
+
category: "atmosphere",
|
|
17026
|
+
video: "filter",
|
|
17027
|
+
tags: ["atmosphere", "mist", "image", "fog", "moody", "photo"],
|
|
17028
|
+
source: glsl(
|
|
17029
|
+
`// Mist over image: drifting fbm haze over a picture, weighted to the ground and the horizon, with a soft bloom.
|
|
17030
|
+
uniform sampler2D uImage; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.landscape} label="Image"
|
|
17031
|
+
uniform vec3 uMistColor; // @control color default=#E4EAF0 label="Mist colour"
|
|
17032
|
+
uniform float uDensity; // @control min=0 max=1 default=0.6 label="Density"
|
|
17033
|
+
uniform float uHorizon; // @control min=0 max=1 default=0.4 label="Horizon"
|
|
17034
|
+
uniform float uGround; // @control min=0 max=1 default=0.6 label="Ground mist"
|
|
17035
|
+
uniform float uScatter; // @control min=0 max=1.5 default=0.6 label="Light scatter"
|
|
17036
|
+
uniform float uSpeed; // @control min=0 max=3 default=1 label="Drift speed"
|
|
17037
|
+
${BACKDROP_CONTROLS}`,
|
|
17038
|
+
PICTURE,
|
|
17039
|
+
HASH,
|
|
17040
|
+
NOISE,
|
|
17041
|
+
FBM,
|
|
17042
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
17043
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
17044
|
+
vec4 pic = pictureRGBA(uImage, uv);
|
|
17045
|
+
float aspect = iResolution.x / iResolution.y;
|
|
17046
|
+
float t = iTime * uSpeed;
|
|
17047
|
+
|
|
17048
|
+
// Depth proxy: hazier toward the horizon (far away) and right at the bottom (ground mist).
|
|
17049
|
+
float far = exp(-pow((uv.y - uHorizon) / 0.18, 2.0));
|
|
17050
|
+
float ground = (1.0 - smoothstep(0.0, 0.45, uv.y)) * uGround;
|
|
17051
|
+
vec2 q = vec2(uv.x * aspect, uv.y);
|
|
17052
|
+
float n1 = fbm(vec2(q.x * 2.0 - t * 0.04, q.y * 6.0) + 3.0, 5);
|
|
17053
|
+
float n2 = fbm(vec2(q.x * 3.5 + t * 0.025, q.y * 9.0 - t * 0.01) + 11.0, 4);
|
|
17054
|
+
float drift = smoothstep(0.25, 0.8, 0.6 * n1 + 0.4 * n2);
|
|
17055
|
+
float mist = clamp((far * 0.7 + ground) * mix(0.45, 1.25, drift) + 0.12, 0.0, 1.0) * uDensity;
|
|
17056
|
+
|
|
17057
|
+
// Soft scattering: bright parts of the picture bleed into the mist around them.
|
|
17058
|
+
vec3 glow = vec3(0.0);
|
|
17059
|
+
for (int i = 0; i < 8; i++) {
|
|
17060
|
+
float a = float(i) * 0.785398 + 0.4;
|
|
17061
|
+
vec2 o = vec2(cos(a), sin(a) * aspect) * 0.035;
|
|
17062
|
+
vec3 s = picture(uImage, uv + o);
|
|
17063
|
+
vec3 s2 = picture(uImage, uv + o * 2.4);
|
|
17064
|
+
glow += max(s - 0.55, 0.0) + max(s2 - 0.55, 0.0) * 0.6;
|
|
17065
|
+
}
|
|
17066
|
+
glow /= 8.0;
|
|
17067
|
+
vec3 haze = uMistColor + glow * uScatter * 1.6;
|
|
17068
|
+
// Mist lifts the blacks and washes out the colour behind it.
|
|
17069
|
+
vec3 col = mix(pic.rgb, haze, mist);
|
|
17070
|
+
col += glow * uScatter * 0.5 * (1.0 - mist);
|
|
17071
|
+
fragColor = vec4(col, pic.a);
|
|
17072
|
+
}`
|
|
17073
|
+
),
|
|
17074
|
+
presets: [
|
|
17075
|
+
{ name: "Dawn", values: { uMistColor: [1, 0.9, 0.8], uDensity: 0.7, uScatter: 1.1, uGround: 0.7 } },
|
|
17076
|
+
{ name: "Cold valley", values: { uMistColor: [0.78, 0.84, 0.9], uDensity: 0.85, uHorizon: 0.5, uScatter: 0.3, uSpeed: 0.6 } }
|
|
17077
|
+
]
|
|
17078
|
+
};
|
|
17079
|
+
var skyTimelapse = {
|
|
17080
|
+
kind: "shader",
|
|
17081
|
+
id: "sky-timelapse",
|
|
17082
|
+
name: "Sky timelapse",
|
|
17083
|
+
description: "A timelapse sky: layers of clouds racing over at different speeds as the light turns from day to golden hour and back.",
|
|
17084
|
+
category: "atmosphere",
|
|
17085
|
+
tags: ["atmosphere", "clouds", "sky", "background", "timelapse"],
|
|
17086
|
+
source: glsl(
|
|
17087
|
+
`// Sky timelapse: parallax cloud layers on perspective planes, under a sky cycling through the day.
|
|
17088
|
+
uniform vec3 uDayTop; // @control color default=#2F6FD0 label="Day sky (top)"
|
|
17089
|
+
uniform vec3 uDayHorizon; // @control color default=#A9D3F5 label="Day sky (horizon)"
|
|
17090
|
+
uniform vec3 uGoldTop; // @control color default=#3E3A7A label="Golden sky (top)"
|
|
17091
|
+
uniform vec3 uGoldHorizon; // @control color default=#FFA45C label="Golden sky (horizon)"
|
|
17092
|
+
uniform float uSpeed; // @control min=0 max=4 default=1.5 label="Cloud speed"
|
|
17093
|
+
uniform float uCycle; // @control min=0 max=1 default=0.08 label="Day cycle speed"
|
|
17094
|
+
uniform float uCoverage; // @control min=0 max=1 default=0.5 label="Coverage"
|
|
17095
|
+
uniform int uLayers; // @control min=1 max=4 default=3 label="Layers"`,
|
|
17096
|
+
HASH,
|
|
17097
|
+
NOISE,
|
|
17098
|
+
FBM,
|
|
17099
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
17100
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
17101
|
+
float x = (fragCoord.x - 0.5 * iResolution.x) / iResolution.y;
|
|
17102
|
+
float y = uv.y;
|
|
17103
|
+
// 0 at midday, 1 at golden hour.
|
|
17104
|
+
float gold = 0.5 - 0.5 * cos(iTime * uCycle * 6.28318);
|
|
17105
|
+
vec3 top = mix(uDayTop, uGoldTop, gold);
|
|
17106
|
+
vec3 horizon = mix(uDayHorizon, uGoldHorizon, gold);
|
|
17107
|
+
vec3 sky = mix(horizon, top, pow(y, 0.7));
|
|
17108
|
+
vec2 sunPos = vec2(0.35, mix(0.85, 0.08, gold));
|
|
17109
|
+
float sd = length(vec2(x, y) - sunPos);
|
|
17110
|
+
vec3 sunCol = mix(vec3(1.0, 0.97, 0.9), vec3(1.0, 0.6, 0.3), gold);
|
|
17111
|
+
sky += sunCol * (smoothstep(0.045, 0.04, sd) + exp(-sd * 6.0) * 0.35);
|
|
17112
|
+
|
|
17113
|
+
vec3 litDay = vec3(1.0);
|
|
17114
|
+
vec3 litGold = vec3(1.0, 0.72, 0.5);
|
|
17115
|
+
vec3 shadowDay = vec3(0.6, 0.66, 0.76);
|
|
17116
|
+
vec3 shadowGold = vec3(0.42, 0.3, 0.42);
|
|
17117
|
+
vec3 col = sky;
|
|
17118
|
+
// Layer 0 is the highest: farthest, smallest-looking and slowest across the screen.
|
|
17119
|
+
for (int i = 0; i < 4; i++) {
|
|
17120
|
+
if (i >= uLayers) break;
|
|
17121
|
+
float fi = float(i);
|
|
17122
|
+
float alt = mix(2.4, 0.7, fi / 3.0);
|
|
17123
|
+
float z = alt / (y + 0.12);
|
|
17124
|
+
vec2 plane = vec2(x * z, z);
|
|
17125
|
+
plane += vec2(iTime * uSpeed * 0.35, iTime * uSpeed * 0.12) + fi * 13.1;
|
|
17126
|
+
float scale = mix(0.9, 0.5, fi / 3.0);
|
|
17127
|
+
vec2 cq = plane * scale;
|
|
17128
|
+
int oct = z > 8.0 ? 3 : 5;
|
|
17129
|
+
float n = fbm(cq, oct);
|
|
17130
|
+
float cover = mix(0.75, 0.35, uCoverage);
|
|
17131
|
+
float a = smoothstep(cover, cover + 0.22, n);
|
|
17132
|
+
// Toward the sun: a lighter side; a sample offset sunward darkens bellies.
|
|
17133
|
+
float n2 = fbm(cq + vec2(0.08, 0.12), oct);
|
|
17134
|
+
float shade = clamp(0.5 + (n - n2) * 6.0, 0.0, 1.0);
|
|
17135
|
+
vec3 lit = mix(litDay, litGold, gold);
|
|
17136
|
+
vec3 shadow = mix(shadowDay, shadowGold, gold) * mix(1.0, 0.8, fi / 3.0);
|
|
17137
|
+
vec3 cc = mix(shadow, lit, shade);
|
|
17138
|
+
cc += sunCol * exp(-sd * 3.0) * 0.4 * (1.0 - a);
|
|
17139
|
+
// Clouds dissolve into the horizon haze.
|
|
17140
|
+
a *= smoothstep(0.0, 0.25, y);
|
|
17141
|
+
col = mix(col, mix(cc, horizon, exp(-y * 8.0) * 0.6), a);
|
|
17142
|
+
}
|
|
17143
|
+
fragColor = vec4(col, 1.0);
|
|
17144
|
+
}`
|
|
17145
|
+
),
|
|
17146
|
+
presets: [
|
|
17147
|
+
{ name: "Fair weather", values: { uSpeed: 1.2, uCoverage: 0.35, uCycle: 0.05 } },
|
|
17148
|
+
{ name: "Racing clouds", values: { uSpeed: 3.5, uCoverage: 0.65, uCycle: 0.15, uLayers: 4 } }
|
|
17149
|
+
]
|
|
17150
|
+
};
|
|
17151
|
+
var smokeCurl = {
|
|
17152
|
+
kind: "shader",
|
|
17153
|
+
id: "smoke-curl",
|
|
17154
|
+
name: "Smoke curl",
|
|
17155
|
+
description: "A thin thread of incense or cigarette smoke rising smooth and straight, then breaking into curls and turbulence.",
|
|
17156
|
+
category: "atmosphere",
|
|
17157
|
+
tags: ["atmosphere", "smoke", "incense", "overlay", "elegant", "moody"],
|
|
17158
|
+
transparent: true,
|
|
17159
|
+
source: glsl(
|
|
17160
|
+
`// Smoke curl: a laminar filament whose path and strands are warped more the higher it rises.
|
|
17161
|
+
uniform vec2 uSource; // @control min=-1 max=1 default=0,-0.85 label="Source"
|
|
17162
|
+
uniform vec3 uColor; // @control color default=#D9DEE6 label="Colour"
|
|
17163
|
+
uniform float uHeight; // @control min=0.3 max=1.6 default=1.1 label="Height"
|
|
17164
|
+
uniform float uBreakup; // @control min=0 max=1 default=0.35 label="Breakup height"
|
|
17165
|
+
uniform float uTurbulence; // @control min=0 max=2 default=1 label="Turbulence"
|
|
17166
|
+
uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"
|
|
17167
|
+
uniform float uThickness; // @control min=0.3 max=3 default=1 label="Thickness"
|
|
17168
|
+
uniform float uOpacity; // @control min=0 max=1 default=0.85 label="Opacity"`,
|
|
17169
|
+
HASH,
|
|
17170
|
+
NOISE,
|
|
17171
|
+
FBM,
|
|
17172
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
17173
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
|
|
17174
|
+
float px = 1.0 / iResolution.y;
|
|
17175
|
+
float aspect = iResolution.x / iResolution.y;
|
|
17176
|
+
vec2 src = uSource * vec2(aspect, 1.0) * 0.5;
|
|
17177
|
+
vec2 q = p - src;
|
|
17178
|
+
float h = q.y / uHeight;
|
|
17179
|
+
if (h < -0.02 || h > 1.15) { fragColor = vec4(uColor, 0.0); return; }
|
|
17180
|
+
float hc = clamp(h, 0.0, 1.0);
|
|
17181
|
+
float t = iTime * uSpeed;
|
|
17182
|
+
// 0 while the stream is laminar, rising to 1 where it has broken up.
|
|
17183
|
+
float turb = smoothstep(uBreakup - 0.05, uBreakup + 0.45, hc) * uTurbulence;
|
|
17184
|
+
|
|
17185
|
+
// Advected warp: noise carried up with the smoke, swirling the filament into curls.
|
|
17186
|
+
vec2 fq = vec2(q.x * 4.0, q.y * 3.0 - t * 0.6);
|
|
17187
|
+
vec2 warp = vec2(fbm(fq, 4), fbm(fq + vec2(4.7, 1.9), 4)) - 0.5;
|
|
17188
|
+
vec2 warp2 = vec2(fbm(fq * 2.3 + warp * 2.0, 3), fbm(fq * 2.3 + vec2(2.1, 8.4) - warp * 2.0, 3)) - 0.5;
|
|
17189
|
+
vec2 w = q + (warp * 0.18 + warp2 * 0.08) * turb * (0.4 + hc);
|
|
17190
|
+
|
|
17191
|
+
// The base path: a gentle sway low down, more after the breakup.
|
|
17192
|
+
float sway = 0.012 * sin(q.y * 7.0 - t * 1.2) * hc + turb * 0.05 * sin(q.y * 4.0 - t * 0.9 + 1.7);
|
|
17193
|
+
float a = 0.0;
|
|
17194
|
+
for (int i = 0; i < 3; i++) {
|
|
17195
|
+
float fi = float(i);
|
|
17196
|
+
// The strands start as one and peel apart above the breakup.
|
|
17197
|
+
float split = (fi - 1.0) * turb * 0.035 * hc * (1.0 + sin(w.y * 6.0 + fi * 2.3 - t * 0.7));
|
|
17198
|
+
float d = abs(w.x - sway - split);
|
|
17199
|
+
float width = (mix(0.0015, 0.016, hc) + turb * 0.008 * hc) * uThickness + px * 0.75;
|
|
17200
|
+
float strand = exp(-d * d / (width * width));
|
|
17201
|
+
a += strand * (fi == 1.0 ? 1.0 : turb * 0.6);
|
|
17202
|
+
}
|
|
17203
|
+
// A faint diffuse veil around the curls, and dissolving gaps higher up.
|
|
17204
|
+
float veil = smoothstep(0.45, 0.8, fbm(w * 6.0 + vec2(0.0, -t * 0.4), 4)) * turb * 0.25 * hc;
|
|
17205
|
+
a = a * mix(1.0, 0.5, hc) + veil;
|
|
17206
|
+
float gaps = mix(1.0, smoothstep(0.25, 0.6, fbm(w * 3.0 + vec2(1.3, -t * 0.5), 3)), hc * min(turb, 1.0));
|
|
17207
|
+
a *= gaps;
|
|
17208
|
+
a *= smoothstep(-0.02, 0.01, h) * (1.0 - smoothstep(0.6, 1.1, h));
|
|
17209
|
+
fragColor = vec4(uColor, clamp(a * uOpacity, 0.0, 1.0));
|
|
17210
|
+
}`
|
|
17211
|
+
),
|
|
17212
|
+
presets: [
|
|
17213
|
+
{ name: "Incense", values: { uColor: [0.86, 0.88, 0.92], uBreakup: 0.45, uTurbulence: 0.8, uSpeed: 0.7, uThickness: 0.8 } },
|
|
17214
|
+
{ name: "Draughty room", values: { uBreakup: 0.15, uTurbulence: 1.8, uSpeed: 1.4, uThickness: 1.3 } }
|
|
17215
|
+
]
|
|
17216
|
+
};
|
|
17217
|
+
var dryIceFog = {
|
|
17218
|
+
kind: "shader",
|
|
17219
|
+
id: "dry-ice-fog",
|
|
17220
|
+
name: "Dry ice fog",
|
|
17221
|
+
description: "Dense, low stage fog rolling along the floor and spilling over the edge in a slow, tumbling curtain.",
|
|
17222
|
+
category: "atmosphere",
|
|
17223
|
+
tags: ["atmosphere", "fog", "overlay", "stage", "spooky", "concert"],
|
|
17224
|
+
transparent: true,
|
|
17225
|
+
source: glsl(
|
|
17226
|
+
`// Dry ice fog: rolling fbm fog hugging a floor, pouring over its edge and pooling below.
|
|
17227
|
+
uniform vec3 uColor; // @control color default=#EEF2F7 label="Colour"
|
|
17228
|
+
uniform float uFloor; // @control min=0 max=0.8 default=0.38 label="Floor height"
|
|
17229
|
+
uniform float uEdge; // @control min=0 max=1.2 default=0.68 label="Edge position"
|
|
17230
|
+
uniform float uDepth; // @control min=0.03 max=0.5 default=0.14 label="Fog depth"
|
|
17231
|
+
uniform float uDensity; // @control min=0 max=1.5 default=1 label="Density"
|
|
17232
|
+
uniform float uSpeed; // @control min=0 max=3 default=1 label="Flow speed"
|
|
17233
|
+
uniform float uTurbulence; // @control min=0 max=2 default=1 label="Turbulence"`,
|
|
17234
|
+
HASH,
|
|
17235
|
+
NOISE,
|
|
17236
|
+
FBM,
|
|
17237
|
+
`float rolling(vec2 q, float t) {
|
|
17238
|
+
vec2 warp = vec2(fbm(q * 0.8 + vec2(-t * 0.3, t * 0.1), 4), fbm(q * 0.8 + vec2(3.1, -t * 0.2), 4)) - 0.5;
|
|
17239
|
+
return fbm(q + warp * 2.2 * uTurbulence, 5);
|
|
17240
|
+
}
|
|
17241
|
+
|
|
17242
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
17243
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
17244
|
+
float aspect = iResolution.x / iResolution.y;
|
|
17245
|
+
float x = uv.x;
|
|
17246
|
+
float y = uv.y;
|
|
17247
|
+
float t = iTime * uSpeed;
|
|
17248
|
+
float a = 0.0;
|
|
17249
|
+
|
|
17250
|
+
// On the stage: a dense layer above the floor, billowing higher in places, soft at the top.
|
|
17251
|
+
float stage = 1.0 - smoothstep(uEdge - 0.04, uEdge + 0.01, x);
|
|
17252
|
+
float n = 0.5;
|
|
17253
|
+
if (stage > 0.0) {
|
|
17254
|
+
float hy = (y - uFloor) / uDepth;
|
|
17255
|
+
n = rolling(vec2(x * aspect * 3.0 - t * 0.35, max(hy, 0.0) * 0.9 + t * 0.05), t);
|
|
17256
|
+
float d = n * 1.5 + 0.15 - max(hy, 0.0) * 0.8 - max(-hy, 0.0) * 2.5;
|
|
17257
|
+
a = max(a, smoothstep(0.0, 0.7, d) * stage);
|
|
17258
|
+
}
|
|
17259
|
+
// Over the edge: a curtain falling in an arc, widening as it drops, pooling on the ground.
|
|
17260
|
+
if (x > uEdge - 0.04) {
|
|
17261
|
+
float dx = max(x - uEdge, 0.0) * aspect;
|
|
17262
|
+
float fall = uFloor - dx * dx * 6.0 - dx * 0.4;
|
|
17263
|
+
float spread = uDepth * (0.5 + 2.0 * max(uFloor - fall, 0.0));
|
|
17264
|
+
float off = (y - fall) / spread;
|
|
17265
|
+
float nc = rolling(vec2(dx * 4.0 + off * 0.3, y * 3.0 + t * 0.6), t);
|
|
17266
|
+
float curtain = smoothstep(0.0, 0.7, nc * 1.5 + 0.1 - abs(off) * 0.9) * smoothstep(uEdge - 0.04, uEdge, x);
|
|
17267
|
+
curtain *= 1.0 - smoothstep(uDepth * 0.5, uDepth * 1.5, y - uFloor);
|
|
17268
|
+
float np = rolling(vec2(x * aspect * 3.0 - t * 0.2, y / uDepth * 0.8), t);
|
|
17269
|
+
float pool = smoothstep(0.0, 0.7, np * 1.5 + 0.1 - y / (uDepth * 0.8) * 0.8) * smoothstep(uEdge, uEdge + 0.1, x);
|
|
17270
|
+
float spill = max(curtain, pool * 0.85);
|
|
17271
|
+
if (spill > a) n = mix(n, x > uEdge + 0.05 ? max(nc, np) : nc, 0.5);
|
|
17272
|
+
a = max(a, spill);
|
|
17273
|
+
}
|
|
17274
|
+
a = clamp(a * uDensity, 0.0, 1.0);
|
|
17275
|
+
// Denser billows catch more light; thin edges fall slightly into shadow.
|
|
17276
|
+
vec3 c = uColor * mix(0.8, 1.05, smoothstep(0.3, 0.75, n));
|
|
17277
|
+
fragColor = vec4(c, a);
|
|
17278
|
+
}`
|
|
17279
|
+
),
|
|
17280
|
+
presets: [
|
|
17281
|
+
{ name: "Concert stage", values: { uFloor: 0.25, uEdge: 1.2, uDepth: 0.2, uDensity: 1.2, uSpeed: 0.8 } },
|
|
17282
|
+
{ name: "Haunted ledge", values: { uColor: [0.72, 0.9, 0.82], uFloor: 0.5, uEdge: 0.55, uDepth: 0.1, uTurbulence: 1.6 } }
|
|
17283
|
+
]
|
|
17284
|
+
};
|
|
17285
|
+
var atmosphereShaders = [
|
|
17286
|
+
volumetricClouds,
|
|
17287
|
+
fogBanks,
|
|
17288
|
+
risingSteam,
|
|
17289
|
+
mistOverImage,
|
|
17290
|
+
skyTimelapse,
|
|
17291
|
+
smokeCurl,
|
|
17292
|
+
dryIceFog
|
|
17293
|
+
];
|
|
17294
|
+
|
|
17295
|
+
// src/catalog/shaders/glow.ts
|
|
17296
|
+
var GLOW7 = `
|
|
17297
|
+
vec4 glow(vec3 c) {
|
|
17298
|
+
c = 1.0 - exp(-max(c, 0.0));
|
|
17299
|
+
float a = max(c.r, max(c.g, c.b));
|
|
17300
|
+
return vec4(c / max(a, 1e-4), a);
|
|
17301
|
+
}`.trim();
|
|
17302
|
+
var MASK = `
|
|
17303
|
+
vec3 gKeyColour;
|
|
17304
|
+
float gKeyTolerance;
|
|
17305
|
+
|
|
17306
|
+
// The colour in the picture's top-left corner: the ground to key out.
|
|
17307
|
+
void setKey(float tolerance) {
|
|
17308
|
+
gKeyTolerance = tolerance;
|
|
17309
|
+
vec4 c = textureLod(uMask, vec2(0.01, 0.99), 0.0);
|
|
17310
|
+
gKeyColour = c.rgb / max(c.a, 1e-3);
|
|
17311
|
+
}
|
|
17312
|
+
|
|
17313
|
+
// Where frame point p falls in the picture (xy) and texels per frame unit (z).
|
|
17314
|
+
vec3 maskPlace(vec2 p) {
|
|
17315
|
+
vec2 size = vec2(textureSize(uMask, 0));
|
|
17316
|
+
float aspect = size.x / size.y;
|
|
17317
|
+
float h = min(uSize, iResolution.x / iResolution.y * uSize / aspect);
|
|
17318
|
+
return vec3(p / vec2(h * aspect, h) + 0.5, size.y / h);
|
|
17319
|
+
}
|
|
17320
|
+
|
|
17321
|
+
float shapeAt(vec2 p, float blur) {
|
|
17322
|
+
if (textureSize(uMask, 0).x <= 1) {
|
|
17323
|
+
float d = length(p) - 0.3 * uSize;
|
|
17324
|
+
return clamp(0.5 - d / max(blur, 0.004), 0.0, 1.0);
|
|
17325
|
+
}
|
|
17326
|
+
vec3 m = maskPlace(p);
|
|
17327
|
+
if (m.x < 0.0 || m.x > 1.0 || m.y < 0.0 || m.y > 1.0) return 0.0;
|
|
17328
|
+
vec4 c = textureLod(uMask, m.xy, log2(max(blur * m.z, 1.0)));
|
|
17329
|
+
float a = c.a;
|
|
17330
|
+
if (gKeyTolerance > 0.0) {
|
|
17331
|
+
float away = distance(c.rgb / max(c.a, 1e-3), gKeyColour);
|
|
17332
|
+
a *= smoothstep(gKeyTolerance, gKeyTolerance + 0.12, away);
|
|
17333
|
+
}
|
|
17334
|
+
return a;
|
|
17335
|
+
}
|
|
17336
|
+
|
|
17337
|
+
float coverage(vec2 p, float radius) {
|
|
17338
|
+
float sum = 0.0;
|
|
17339
|
+
for (int i = 0; i < 16; i++) {
|
|
17340
|
+
float r = radius * sqrt((float(i) + 0.5) / 16.0);
|
|
17341
|
+
float a = float(i) * 2.39996;
|
|
17342
|
+
sum += shapeAt(p + r * vec2(cos(a), sin(a)), radius * 0.4);
|
|
17343
|
+
}
|
|
17344
|
+
return sum / 16.0;
|
|
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();
|
|
17415
|
+
var aura = {
|
|
17416
|
+
kind: "shader",
|
|
17417
|
+
id: "aura",
|
|
17418
|
+
name: "Aura",
|
|
17419
|
+
description: "A silhouette wreathed in an energetic aura, flame-like wisps of light rising off its outline and pulsing.",
|
|
17420
|
+
category: "glow",
|
|
17421
|
+
video: "stills",
|
|
17422
|
+
tags: ["glow", "aura", "energy", "flame", "silhouette", "anime", "power-up"],
|
|
17423
|
+
source: glsl(
|
|
17424
|
+
`// Aura: a blurred silhouette, lifted and torn by rising fbm, read as tongues of light round the outline.
|
|
17425
|
+
uniform sampler2D uMask; // @control filter=mipmap wrap=clamp default=${SAMPLE_IMAGES.portrait} label="Silhouette"
|
|
17426
|
+
uniform float uKey; // @control min=0 max=0.5 default=0.12 label="Key out the corner colour (0 off)"
|
|
17427
|
+
uniform float uSize; // @control min=0.2 max=1 default=1 label="Size"
|
|
17428
|
+
uniform vec3 uColor; // @control color default=#FFC93C label="Aura"
|
|
17429
|
+
uniform vec3 uCore; // @control color default=#FFF6D5 label="Core"
|
|
17430
|
+
uniform vec3 uBackground; // @control color default=#0B0A12 label="Background"
|
|
17431
|
+
uniform float uHeight; // @control min=0 max=0.4 default=0.16 label="Flame height"
|
|
17432
|
+
uniform float uSpread; // @control min=0.01 max=0.15 default=0.06 label="Spread"
|
|
17433
|
+
uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"
|
|
17434
|
+
uniform float uPulse; // @control min=0 max=1 default=0.4 label="Pulse"
|
|
17435
|
+
uniform float uIntensity; // @control min=0 max=3 default=1.1 label="Intensity"`,
|
|
17436
|
+
HASH,
|
|
17437
|
+
NOISE,
|
|
17438
|
+
FBM,
|
|
17439
|
+
MASK_FAST,
|
|
17440
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
17441
|
+
setKey(uKey);
|
|
17442
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
|
|
17443
|
+
float t = iTime * uSpeed;
|
|
17444
|
+
float px = 1.0 / iResolution.y;
|
|
17445
|
+
// Rising noise: tongues flow up and sway.
|
|
17446
|
+
vec2 q = p * 6.0;
|
|
17447
|
+
float n = fbm(vec2(q.x, q.y - t * 2.0), 5);
|
|
17448
|
+
float n2 = fbm(vec2(q.x * 1.7 + 4.1, q.y * 0.9 - t * 2.9), 4);
|
|
17449
|
+
float pulse = 1.0 + uPulse * (0.3 * sin(t * 3.0) + 0.12 * sin(t * 7.3 + 1.0));
|
|
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.
|
|
17452
|
+
vec2 warp = vec2((n2 - 0.5) * uSpread * 1.5, -(n - 0.3) * uHeight);
|
|
17453
|
+
float inner = coverage(p + warp * 0.3, uSpread * 0.5);
|
|
17454
|
+
float outer = coverageLite(p + warp, uSpread * 1.4);
|
|
17455
|
+
float tall = coverageLite(p + warp * 1.5 - vec2(0.0, uHeight), uSpread * 2.2);
|
|
17456
|
+
float field = (inner * 0.9 + outer * 0.8 + tall * 0.6) * pulse;
|
|
17457
|
+
// Torn into tongues: the noise breaks the thin outer field into licks, the thick inner field holds.
|
|
17458
|
+
float tongues = smoothstep(0.12, 0.95, field * (0.45 + 1.1 * n));
|
|
17459
|
+
float wisps = smoothstep(0.55, 0.75, n2) * smoothstep(0.02, 0.3, tall) * 0.5;
|
|
17460
|
+
float body = shapeAt(p, 1.5 * px);
|
|
17461
|
+
vec3 light = uColor * (tongues * 1.3 + wisps + field * 0.25) + uCore * smoothstep(0.5, 0.9, inner * pulse) * 0.8;
|
|
17462
|
+
light *= uIntensity;
|
|
17463
|
+
vec3 col = uBackground + (1.0 - exp(-light));
|
|
17464
|
+
// The figure in front, its own colours, rim-lit by the aura.
|
|
17465
|
+
if (gMaskPlace.w > 0.5 && body > 0.0) {
|
|
17466
|
+
vec3 m = maskPlace(p);
|
|
17467
|
+
vec4 c = texture(uMask, m.xy);
|
|
17468
|
+
vec3 own = c.rgb / max(c.a, 1e-3);
|
|
17469
|
+
float rim = 1.0 - coverage(p, uSpread * 0.25);
|
|
17470
|
+
vec3 lit = own * mix(vec3(0.75), uColor, 0.25) + uColor * rim * 1.6 * uIntensity * pulse * 0.5;
|
|
17471
|
+
col = mix(col, lit, body);
|
|
17472
|
+
} else {
|
|
17473
|
+
col = mix(col, uBackground * 0.6 + uColor * 0.15, body);
|
|
17474
|
+
}
|
|
17475
|
+
fragColor = vec4(col, 1.0);
|
|
17476
|
+
}`
|
|
17477
|
+
),
|
|
17478
|
+
presets: [
|
|
17479
|
+
{ name: "Super saiyan", values: { uColor: [1, 0.79, 0.24], uCore: [1, 0.97, 0.84], uHeight: 0.22, uSpeed: 1.6, uPulse: 0.6, uIntensity: 1.8 } },
|
|
17480
|
+
{ name: "Spirit", values: { uColor: [0.25, 0.85, 1], uCore: [0.85, 1, 1], uBackground: [0.02, 0.05, 0.09], uHeight: 0.1, uSpeed: 0.6, uPulse: 0.3 } },
|
|
17481
|
+
{ name: "Dark", values: { uColor: [0.55, 0.2, 0.95], uCore: [0.95, 0.75, 1], uBackground: [0.02, 0.01, 0.04], uHeight: 0.18, uSpread: 0.07, uIntensity: 1.6 } }
|
|
17482
|
+
]
|
|
17483
|
+
};
|
|
17484
|
+
var bloom = {
|
|
17485
|
+
kind: "shader",
|
|
17486
|
+
id: "bloom",
|
|
17487
|
+
name: "Bloom",
|
|
17488
|
+
description: "A picture whose bright areas bleed soft light into their surroundings, with an optional anamorphic lens streak.",
|
|
17489
|
+
category: "glow",
|
|
17490
|
+
video: "filter",
|
|
17491
|
+
tags: ["glow", "bloom", "image", "lens", "light", "filter", "anamorphic"],
|
|
17492
|
+
source: glsl(
|
|
17493
|
+
`// Bloom: the brightest parts of the image, blurred at two scales (and streaked sideways), added back as light.
|
|
17494
|
+
uniform sampler2D uImage; // @control filter=mipmap wrap=clamp default=${SAMPLE_IMAGES.landscape} label="Image"
|
|
17495
|
+
uniform float uThreshold; // @control min=0 max=1 default=0.6 label="Threshold"
|
|
17496
|
+
uniform float uKnee; // @control min=0.01 max=0.5 default=0.2 label="Softness"
|
|
17497
|
+
uniform float uRadius; // @control min=0.01 max=0.2 default=0.08 label="Radius"
|
|
17498
|
+
uniform float uIntensity; // @control min=0 max=4 default=2.5 label="Intensity"
|
|
17499
|
+
uniform vec3 uTint; // @control color default=#FFFFFF label="Tint"
|
|
17500
|
+
uniform float uStreak; // @control min=0 max=2 default=0 label="Anamorphic streak"
|
|
17501
|
+
uniform vec3 uStreakColor; // @control color default=#7FB2FF label="Streak colour"
|
|
17502
|
+
${BACKDROP_CONTROLS}`,
|
|
17503
|
+
PICTURE,
|
|
17504
|
+
`// The light above the threshold at uv, read at a mip level (a soft knee, so light fades in smoothly). A coarse
|
|
17505
|
+
// level has averaged small lights with their dark surroundings, so its threshold is lowered a little to match.
|
|
17506
|
+
vec3 bright(vec2 uv, float lod) {
|
|
17507
|
+
vec3 c = textureSize(uImage, 0).x > 1 ? textureLod(uImage, uv, lod).rgb : testCard(fract(uv));
|
|
17508
|
+
float l = dot(c, vec3(0.2126, 0.7152, 0.0722));
|
|
17509
|
+
float thr = uThreshold * exp2(-max(lod, 0.0) * 0.1);
|
|
17510
|
+
float knee = uKnee * exp2(-max(lod, 0.0) * 0.1);
|
|
17511
|
+
float k = clamp((l - thr + knee) / (2.0 * knee), 0.0, 1.0);
|
|
17512
|
+
float soft = l > thr ? l - thr : k * k * knee * 0.5;
|
|
17513
|
+
return c * max(soft, 0.0) / max(l, 1e-4);
|
|
17514
|
+
}
|
|
17515
|
+
|
|
17516
|
+
// A disc blur of the bright pass, radius r (uv height units): 20 golden-angle taps at a mip level about as
|
|
17517
|
+
// coarse as their spacing, so they run together.
|
|
17518
|
+
vec3 blurBright(vec2 uv, float r, float texels) {
|
|
17519
|
+
float lod = log2(max(r * 0.4 * texels, 1.0));
|
|
17520
|
+
vec3 sum = vec3(0.0);
|
|
17521
|
+
vec2 aspect = vec2(iResolution.y / iResolution.x, 1.0);
|
|
17522
|
+
for (int i = 0; i < 20; i++) {
|
|
17523
|
+
float rr = r * sqrt((float(i) + 0.5) / 20.0);
|
|
17524
|
+
float a = float(i) * 2.39996;
|
|
17525
|
+
sum += bright(uv + rr * vec2(cos(a), sin(a)) * aspect, lod);
|
|
17526
|
+
}
|
|
17527
|
+
return sum / 20.0;
|
|
17528
|
+
}
|
|
17529
|
+
|
|
17530
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
17531
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
17532
|
+
float texels = textureSize(uImage, 0).y > 1 ? float(textureSize(uImage, 0).y) : iResolution.y;
|
|
17533
|
+
vec3 light = blurBright(uv, uRadius * 0.3, texels) * 0.3 + blurBright(uv, uRadius, texels) * 0.4 + blurBright(uv, uRadius * 3.0, texels) * 0.5;
|
|
17534
|
+
light *= uTint;
|
|
17535
|
+
if (uStreak > 0.0) {
|
|
17536
|
+
// A long horizontal smear of the bright pass, read a little blurred so its taps run together.
|
|
17537
|
+
vec3 streak = vec3(0.0);
|
|
17538
|
+
float w = 0.0;
|
|
17539
|
+
float jitter = fract(sin(dot(fragCoord, vec2(12.9898, 78.233))) * 43758.5453) - 0.5;
|
|
17540
|
+
float texelsX = textureSize(uImage, 0).x > 1 ? float(textureSize(uImage, 0).x) : iResolution.x;
|
|
17541
|
+
float lodStreak = log2(max(0.0125 * texelsX, 1.0));
|
|
17542
|
+
for (int i = -24; i <= 24; i++) {
|
|
17543
|
+
float f = (float(i) + jitter) / 24.0;
|
|
17544
|
+
float k = exp(-abs(f) * 3.5);
|
|
17545
|
+
streak += bright(uv + vec2(f * 0.3, 0.0), lodStreak) * k;
|
|
17546
|
+
w += k;
|
|
17547
|
+
}
|
|
17548
|
+
light += dot(streak / w, vec3(0.33)) * uStreakColor * uStreak * 4.0;
|
|
17549
|
+
}
|
|
17550
|
+
light *= uIntensity;
|
|
17551
|
+
vec4 pic = pictureRGBA(uImage, uv);
|
|
17552
|
+
// The bloom is light added over the picture, so it also shows where the picture is clear.
|
|
17553
|
+
float glowA = clamp(max(light.r, max(light.g, light.b)), 0.0, 1.0);
|
|
17554
|
+
float alpha = max(pic.a, glowA);
|
|
17555
|
+
vec3 col = (pic.rgb * pic.a + light) / max(alpha, 1e-4);
|
|
17556
|
+
fragColor = vec4(col, alpha);
|
|
17557
|
+
}`
|
|
17558
|
+
),
|
|
17559
|
+
presets: [
|
|
17560
|
+
{ name: "Dreamy", values: { uThreshold: 0.45, uRadius: 0.12, uIntensity: 1.8, uTint: [1, 0.9, 0.95] } },
|
|
17561
|
+
{ name: "Anamorphic night", values: { uImage: SAMPLE_IMAGES.city, uStreak: 1.2, uThreshold: 0.6, uIntensity: 3 } },
|
|
17562
|
+
{ name: "Subtle", values: { uThreshold: 0.75, uRadius: 0.04, uIntensity: 1.2 } }
|
|
17563
|
+
]
|
|
17564
|
+
};
|
|
17565
|
+
var pulsingHalo = {
|
|
17566
|
+
kind: "shader",
|
|
17567
|
+
id: "pulsing-halo",
|
|
17568
|
+
name: "Pulsing halo",
|
|
17569
|
+
description: "Soft halo rings behind a centre spot, breathing slowly in and out with a gentle shimmer running round them.",
|
|
17570
|
+
category: "glow",
|
|
17571
|
+
tags: ["glow", "halo", "ring", "breathe", "overlay", "pulse", "ui"],
|
|
17572
|
+
transparent: true,
|
|
17573
|
+
loop: 4,
|
|
17574
|
+
source: glsl(
|
|
17575
|
+
`// Pulsing halo: soft rings round the centre that grow and brighten with each breath, shimmering round.
|
|
17576
|
+
uniform vec3 uColor; // @control color default=#7DD3FC label="Outer colour"
|
|
17577
|
+
uniform vec3 uInner; // @control color default=#E0F2FE label="Inner colour"
|
|
17578
|
+
uniform float uRadius; // @control min=0.05 max=0.45 default=0.2 label="Radius"
|
|
17579
|
+
uniform float uWidth; // @control min=0.004 max=0.12 default=0.025 label="Ring width"
|
|
17580
|
+
uniform int uRings; // @control min=1 max=4 default=2 label="Rings"
|
|
17581
|
+
uniform float uSpacing; // @control min=0.02 max=0.2 default=0.07 label="Ring spacing"
|
|
17582
|
+
uniform float uBreath; // @control min=0 max=0.5 default=0.12 label="Breath"
|
|
17583
|
+
uniform float uPeriod; // @control min=1 max=10 default=4 label="Seconds per breath"
|
|
17584
|
+
uniform float uShimmer; // @control min=0 max=1 default=0.5 label="Shimmer"
|
|
17585
|
+
uniform float uIntensity; // @control min=0 max=3 default=1.2 label="Intensity"`,
|
|
17586
|
+
HASH,
|
|
17587
|
+
NOISE,
|
|
17588
|
+
GLOW7,
|
|
17589
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
17590
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
|
|
17591
|
+
float d = length(p);
|
|
17592
|
+
vec2 dir = p / max(d, 1e-4);
|
|
17593
|
+
float phase = 6.28318 * iTime / uPeriod;
|
|
17594
|
+
float b = 0.5 - 0.5 * cos(phase);
|
|
17595
|
+
float scale = 1.0 + uBreath * (b - 0.3);
|
|
17596
|
+
// The shimmer: noise round the ring, drifting on a circle so it repeats every breath.
|
|
17597
|
+
vec2 drift = vec2(cos(phase), sin(phase));
|
|
17598
|
+
vec3 light = vec3(0.0);
|
|
17599
|
+
for (int i = 0; i < 4; i++) {
|
|
17600
|
+
if (i >= uRings) break;
|
|
17601
|
+
float fi = float(i);
|
|
17602
|
+
float r = (uRadius + fi * uSpacing * (1.0 + 0.5 * b)) * scale;
|
|
17603
|
+
float w = uWidth * (1.0 + 0.5 * fi);
|
|
17604
|
+
float ring = exp(-pow((d - r) / w, 2.0)) + 0.4 * exp(-abs(d - r) / (w * 3.0));
|
|
17605
|
+
float s = vnoise(dir * 3.0 + drift * 0.8 + fi * 7.3) * 0.6 + vnoise(dir * 7.0 - drift * 1.3 + fi * 3.1) * 0.4;
|
|
17606
|
+
float sh = mix(1.0, 0.35 + 1.3 * s, uShimmer);
|
|
17607
|
+
float fade = 1.0 / (1.0 + 0.9 * fi);
|
|
17608
|
+
vec3 tint = mix(uInner, uColor, uRings > 1 ? fi / float(uRings - 1) : 0.6);
|
|
17609
|
+
light += tint * ring * sh * fade;
|
|
17610
|
+
}
|
|
17611
|
+
// A faint glow filling the centre, behind whatever sits there.
|
|
17612
|
+
light += uInner * 0.18 * exp(-pow(d / (uRadius * scale), 2.0) * 1.5);
|
|
17613
|
+
light *= uIntensity * (0.7 + 0.45 * b);
|
|
17614
|
+
fragColor = glow(light);
|
|
17615
|
+
}`
|
|
17616
|
+
),
|
|
17617
|
+
presets: [
|
|
17618
|
+
{ name: "Calm", values: { uRings: 1, uWidth: 0.04, uPeriod: 6, uShimmer: 0.2 } },
|
|
17619
|
+
{ name: "Golden", values: { uColor: [1, 0.66, 0.2], uInner: [1, 0.93, 0.7], uRings: 3, uShimmer: 0.8 } },
|
|
17620
|
+
{ name: "Notify", values: { uColor: [0.98, 0.3, 0.45], uInner: [1, 0.8, 0.85], uRadius: 0.12, uBreath: 0.35, uPeriod: 1.5, uRings: 3 } }
|
|
17621
|
+
]
|
|
17622
|
+
};
|
|
17623
|
+
var neonOutline = {
|
|
17624
|
+
kind: "shader",
|
|
17625
|
+
id: "neon-outline",
|
|
17626
|
+
name: "Neon outline",
|
|
17627
|
+
description: "Any shape or word traced in a crisp neon line with a wide soft glow, its colour flowing along the outline and flickering now and then.",
|
|
17628
|
+
category: "glow",
|
|
17629
|
+
video: "none",
|
|
17630
|
+
tags: ["glow", "neon", "outline", "text", "logo", "overlay", "sign"],
|
|
17631
|
+
transparent: true,
|
|
17632
|
+
source: glsl(
|
|
17633
|
+
`// Neon outline: the mask's edge as a bright tube, a soft halo either side, coloured along the outline.
|
|
17634
|
+
uniform sampler2D uMask; // @control filter=mipmap wrap=clamp default=${textImage("GLOW")} label="Shape"
|
|
17635
|
+
uniform float uSize; // @control min=0.2 max=1 default=0.85 label="Size"
|
|
17636
|
+
uniform vec3 uColorA; // @control color default=#FF3DD8 label="Colour"
|
|
17637
|
+
uniform vec3 uColorB; // @control color default=#38E1FF label="Gradient to"
|
|
17638
|
+
uniform float uGradient; // @control min=0 max=1 default=1 label="Gradient along outline"
|
|
17639
|
+
uniform float uFlow; // @control min=0 max=2 default=0.3 label="Gradient flow"
|
|
17640
|
+
uniform float uLine; // @control min=0.5 max=8 default=2 label="Line width (px)"
|
|
17641
|
+
uniform float uGlow; // @control min=0.01 max=0.2 default=0.05 label="Glow size"
|
|
17642
|
+
uniform float uIntensity; // @control min=0 max=3 default=1.3 label="Intensity"
|
|
17643
|
+
uniform float uFlicker; // @control min=0 max=1 default=0.3 label="Flicker"
|
|
17644
|
+
uniform float uFill; // @control min=0 max=1 default=0 label="Fill"`,
|
|
17645
|
+
HASH,
|
|
17646
|
+
MASK,
|
|
17647
|
+
GLOW7,
|
|
17648
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
17649
|
+
setKey(0.0);
|
|
17650
|
+
float px = 1.0 / iResolution.y;
|
|
17651
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) * px;
|
|
17652
|
+
// The edge, softened a few pixels so its slope gives a smooth distance in pixels.
|
|
17653
|
+
float v = coverage(p, 3.0 * px) - 0.5;
|
|
17654
|
+
vec2 g = vec2(dFdx(v), dFdy(v));
|
|
17655
|
+
float d = abs(v) / max(length(g), 0.02);
|
|
17656
|
+
float tube = exp(-pow(d / uLine, 2.0));
|
|
17657
|
+
float core = exp(-pow(d / (uLine * 0.4), 2.0));
|
|
17658
|
+
// The halo: blurred coverage is half way across the edge, falling off either side.
|
|
17659
|
+
float c1 = coverage(p, uGlow * 0.4);
|
|
17660
|
+
float c2 = coverage(p, uGlow);
|
|
17661
|
+
float halo = (1.0 - abs(2.0 * c1 - 1.0)) * 0.55 + (1.0 - abs(2.0 * c2 - 1.0)) * 0.35;
|
|
17662
|
+
float inside = shapeAt(p, px);
|
|
17663
|
+
// Colour along the outline, flowing.
|
|
17664
|
+
float k = mix(0.0, 0.5 + 0.5 * sin(p.x * 5.0 + p.y * 2.5 - iTime * uFlow * 3.0), uGradient);
|
|
17665
|
+
vec3 tint = mix(uColorA, uColorB, k);
|
|
17666
|
+
// Flicker: now and then a burst of quick dips, as a tired tube does.
|
|
17667
|
+
float burst = step(0.72, hash12(vec2(floor(iTime * 1.7), 3.0)));
|
|
17668
|
+
float dip = step(0.45, hash12(vec2(floor(iTime * 24.0), 7.0)));
|
|
17669
|
+
float flick = 1.0 - uFlicker * burst * (1.0 - dip) * 0.85;
|
|
17670
|
+
vec3 light = tint * (tube * 1.6 + halo) + mix(tint, vec3(1.0), 0.7) * core * 0.9;
|
|
17671
|
+
light += tint * inside * uFill * 0.6;
|
|
17672
|
+
fragColor = glow(light * uIntensity * flick);
|
|
17673
|
+
}`
|
|
17674
|
+
),
|
|
17675
|
+
presets: [
|
|
17676
|
+
{ name: "Bar sign", values: { uColorA: [1, 0.24, 0.4], uGradient: 0, uFlicker: 0.8, uLine: 3 } },
|
|
17677
|
+
{ name: "Heart", values: { uMask: SAMPLE_IMAGES.heart, uSize: 0.7, uColorA: [1, 0.3, 0.55], uColorB: [1, 0.65, 0.3], uFlicker: 0 } },
|
|
17678
|
+
{ name: "Filled", values: { uFill: 0.35, uGlow: 0.09, uColorA: [0.5, 1, 0.4], uColorB: [0.2, 0.8, 1] } }
|
|
17679
|
+
]
|
|
17680
|
+
};
|
|
17681
|
+
var backlightGlow = {
|
|
17682
|
+
kind: "shader",
|
|
17683
|
+
id: "backlight-glow",
|
|
17684
|
+
name: "Backlight glow",
|
|
17685
|
+
description: "A dark silhouette with a light behind it: radiant rays stream past its edges and glow spills round its rim.",
|
|
17686
|
+
category: "glow",
|
|
17687
|
+
video: "none",
|
|
17688
|
+
tags: ["glow", "backlight", "silhouette", "rays", "eclipse", "rim", "logo"],
|
|
17689
|
+
source: glsl(
|
|
17690
|
+
`// Backlight glow: a light behind a mask, marched toward for shafts the shape blocks, with spill and a rim.
|
|
17691
|
+
uniform sampler2D uMask; // @control filter=mipmap wrap=clamp default=${SAMPLE_IMAGES.logo} label="Silhouette"
|
|
17692
|
+
uniform float uKey; // @control min=0 max=0.5 default=0 label="Key out the corner colour (0 off)"
|
|
17693
|
+
uniform float uSize; // @control min=0.2 max=1 default=0.6 label="Size"
|
|
17694
|
+
uniform vec3 uLight; // @control color default=#FFC46B label="Light"
|
|
17695
|
+
uniform vec3 uBackground; // @control color default=#07060D label="Background"
|
|
17696
|
+
uniform vec3 uObject; // @control color default=#040308 label="Object"
|
|
17697
|
+
uniform float uLightX; // @control min=-0.5 max=0.5 default=0 label="Light across"
|
|
17698
|
+
uniform float uLightY; // @control min=-0.5 max=0.5 default=0.04 label="Light up"
|
|
17699
|
+
uniform float uGlowSize; // @control min=0.05 max=0.8 default=0.3 label="Light size"
|
|
17700
|
+
uniform float uRays; // @control min=0 max=1 default=0.8 label="Rays"
|
|
17701
|
+
uniform float uRim; // @control min=0 max=1 default=0.6 label="Rim light"
|
|
17702
|
+
uniform float uSpeed; // @control min=0 max=2 default=0.4 label="Ray drift"
|
|
17703
|
+
uniform float uIntensity; // @control min=0 max=3 default=1.3 label="Intensity"`,
|
|
17704
|
+
HASH,
|
|
17705
|
+
NOISE,
|
|
17706
|
+
MASK_FAST,
|
|
17707
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
17708
|
+
setKey(uKey);
|
|
17709
|
+
float px = 1.0 / iResolution.y;
|
|
17710
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) * px;
|
|
17711
|
+
vec2 L = vec2(uLightX, uLightY);
|
|
17712
|
+
vec2 toL = L - p;
|
|
17713
|
+
float dl = length(toL);
|
|
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.
|
|
17717
|
+
float shafts = 0.0;
|
|
17718
|
+
float decay = 1.0;
|
|
17719
|
+
float jitter = hash12(fragCoord);
|
|
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;
|
|
17730
|
+
// Streaks round the light, drifting slowly.
|
|
17731
|
+
vec2 dir = -toL / max(dl, 1e-4);
|
|
17732
|
+
float a = iTime * uSpeed * 0.2;
|
|
17733
|
+
float streak = vnoise(dir * 6.0 + vec2(a, -a)) * 0.6 + vnoise(dir * 15.0 - vec2(a * 2.0, a)) * 0.4;
|
|
17734
|
+
float rays = shafts * mix(1.0, 0.25 + 1.5 * streak * streak, uRays);
|
|
17735
|
+
// Spill: light wrapping just round the edges.
|
|
17736
|
+
float body = shapeAt(p, 1.2 * px);
|
|
17737
|
+
float spill = coverage(p, 0.03) * 0.8 + coverageLite(p, 0.08) * 0.5;
|
|
17738
|
+
float glowAt = exp(-pow(dl / (uGlowSize * 1.6), 2.0));
|
|
17739
|
+
vec3 light = uLight * (rays * 1.4 + spill * (1.0 - body) * glowAt * 1.2) * uIntensity;
|
|
17740
|
+
vec3 col = uBackground + (1.0 - exp(-light));
|
|
17741
|
+
// The object, dark, its edges facing the light lit.
|
|
17742
|
+
float edge = 1.0 - coverage(p, 0.012);
|
|
17743
|
+
vec2 grad = vec2(dFdx(edge), dFdy(edge));
|
|
17744
|
+
float facing = 0.5 + 0.5 * dot(normalize(grad + 1e-6), normalize(toL + 1e-6));
|
|
17745
|
+
vec3 obj = uObject + uLight * edge * uRim * (0.4 + 0.6 * facing) * glowAt * 1.5 * uIntensity;
|
|
17746
|
+
col = mix(col, obj, body);
|
|
17747
|
+
fragColor = vec4(col, 1.0);
|
|
17748
|
+
}`
|
|
17749
|
+
),
|
|
17750
|
+
presets: [
|
|
17751
|
+
{ name: "Eclipse", values: { uLight: [1, 0.95, 0.85], uGlowSize: 0.22, uRays: 0.9, uRim: 0.8 } },
|
|
17752
|
+
{ name: "Hero portrait", values: { uMask: SAMPLE_IMAGES.portrait, uKey: 0.12, uSize: 1, uLightY: 0.12, uLight: [0.6, 0.8, 1] } },
|
|
17753
|
+
{ name: "Sunset", values: { uLight: [1, 0.45, 0.2], uBackground: [0.12, 0.04, 0.1], uLightY: -0.08, uGlowSize: 0.5 } }
|
|
17754
|
+
]
|
|
17755
|
+
};
|
|
17756
|
+
var rimLight = {
|
|
17757
|
+
kind: "shader",
|
|
17758
|
+
id: "rim-light",
|
|
17759
|
+
name: "Rim light",
|
|
17760
|
+
description: "A picture given a coloured studio kicker light along the edges that face a chosen direction.",
|
|
17761
|
+
category: "glow",
|
|
17762
|
+
video: "filter",
|
|
17763
|
+
tags: ["glow", "rim", "light", "image", "portrait", "studio", "filter"],
|
|
17764
|
+
source: glsl(
|
|
17765
|
+
`// Rim light: luminance edges, kept where they face the light, glowing in its colour over the dimmed picture.
|
|
17766
|
+
uniform sampler2D uImage; // @control filter=linear wrap=clamp default=${SAMPLE_IMAGES.portrait} label="Image"
|
|
17767
|
+
uniform vec3 uColor; // @control color default=#4FD1FF label="Light colour"
|
|
17768
|
+
uniform float uAngle; // @control min=0 max=360 default=35 label="Light from (\xB0)"
|
|
17769
|
+
uniform float uWidth; // @control min=0.5 max=8 default=3 label="Rim width (px)"
|
|
17770
|
+
uniform float uFocus; // @control min=0 max=1 default=1 label="Focus on the light side"
|
|
17771
|
+
uniform float uThreshold; // @control min=0 max=0.5 default=0.06 label="Threshold"
|
|
17772
|
+
uniform float uIntensity; // @control min=0 max=4 default=2.4 label="Intensity"
|
|
17773
|
+
uniform float uDim; // @control min=0 max=1 default=0.3 label="Darken picture"
|
|
17774
|
+
uniform bool uBrightSubject; // @control default=false label="Subject brighter than background"
|
|
17775
|
+
${BACKDROP_CONTROLS}`,
|
|
17776
|
+
PICTURE,
|
|
17777
|
+
`float luma(vec2 uv) {
|
|
17778
|
+
vec4 c = pictureRGBA(uImage, uv);
|
|
17779
|
+
return dot(c.rgb, vec3(0.2126, 0.7152, 0.0722)) * c.a;
|
|
17780
|
+
}
|
|
17781
|
+
|
|
17782
|
+
// The luminance gradient (Sobel), sampling d apart in uv: it points from dark to bright.
|
|
17783
|
+
vec2 gradient(vec2 uv, vec2 d) {
|
|
17784
|
+
float tl = luma(uv + vec2(-d.x, d.y));
|
|
17785
|
+
float t = luma(uv + vec2(0.0, d.y));
|
|
17786
|
+
float tr = luma(uv + d);
|
|
17787
|
+
float l = luma(uv - vec2(d.x, 0.0));
|
|
17788
|
+
float r = luma(uv + vec2(d.x, 0.0));
|
|
17789
|
+
float bl = luma(uv - d);
|
|
17790
|
+
float b = luma(uv - vec2(0.0, d.y));
|
|
17791
|
+
float br = luma(uv + vec2(d.x, -d.y));
|
|
17792
|
+
return vec2(tr + 2.0 * r + br - tl - 2.0 * l - bl, tl + 2.0 * t + tr - bl - 2.0 * b - br) * 0.25;
|
|
17793
|
+
}
|
|
17794
|
+
|
|
17795
|
+
// How much rim light an edge sampled d apart gets: its strength, toward the light.
|
|
17796
|
+
float rimAt(vec2 uv, vec2 d, vec2 toLight) {
|
|
17797
|
+
vec2 g = gradient(uv, d);
|
|
17798
|
+
float s = length(g);
|
|
17799
|
+
// A dark subject on a light ground: its outward normal points to the brighter side.
|
|
17800
|
+
vec2 n = (uBrightSubject ? -g : g) / max(s, 1e-5);
|
|
17801
|
+
float facing = mix(1.0, smoothstep(0.25, 0.95, dot(n, toLight)), uFocus);
|
|
17802
|
+
return smoothstep(uThreshold, uThreshold + 0.12, s) * facing;
|
|
17803
|
+
}
|
|
17804
|
+
|
|
17805
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
17806
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
17807
|
+
float a = radians(uAngle);
|
|
17808
|
+
vec2 toLight = vec2(cos(a), sin(a));
|
|
17809
|
+
vec2 d = uWidth / iResolution.xy;
|
|
17810
|
+
// The light falls on the subject's side of the edge: read it a little toward the light, and keep it where
|
|
17811
|
+
// this pixel is on the subject's side (darker than what lies toward the light, or brighter if so set).
|
|
17812
|
+
float crisp = rimAt(uv + toLight * d, d, toLight);
|
|
17813
|
+
float soft = rimAt(uv + toLight * d * 2.5, d * 3.0, toLight);
|
|
17814
|
+
float rise = luma(uv + toLight * d * 3.0) - luma(uv);
|
|
17815
|
+
float side = smoothstep(0.0, 0.06, uBrightSubject ? -rise : rise);
|
|
17816
|
+
crisp *= side;
|
|
17817
|
+
soft *= mix(0.5, 1.0, side);
|
|
17818
|
+
vec4 pic = pictureRGBA(uImage, uv);
|
|
17819
|
+
vec3 light = uColor * (crisp + 0.45 * soft) * uIntensity + vec3(1.0) * crisp * crisp * 0.12 * uIntensity;
|
|
17820
|
+
vec3 col = pic.rgb * (1.0 - uDim) + light * (1.0 - 0.5 * pic.rgb);
|
|
17821
|
+
fragColor = vec4(col, pic.a);
|
|
17822
|
+
}`
|
|
17823
|
+
),
|
|
17824
|
+
presets: [
|
|
17825
|
+
{ name: "Kicker", values: { uColor: [1, 0.86, 0.66], uAngle: 160, uDim: 0.2 } },
|
|
17826
|
+
{ name: "Cyberpunk", values: { uColor: [1, 0.2, 0.7], uAngle: 20, uDim: 0.55, uIntensity: 2.5, uWidth: 3.5 } },
|
|
17827
|
+
{ name: "Top light", values: { uColor: [0.85, 0.95, 1], uAngle: 90, uFocus: 0.7 } }
|
|
17828
|
+
]
|
|
17829
|
+
};
|
|
17830
|
+
var glowShaders = [aura, bloom, pulsingHalo, neonOutline, backlightGlow, rimLight];
|
|
17831
|
+
|
|
17832
|
+
// src/catalog/shaders/shadow.ts
|
|
17833
|
+
var SHAPE = `
|
|
17834
|
+
vec4 shapeAt(vec2 p, float spacing) {
|
|
17835
|
+
vec2 size = vec2(textureSize(uMask, 0));
|
|
17836
|
+
if (size.x <= 1.0) {
|
|
17837
|
+
vec2 q = abs(p) - 0.18 * uSize;
|
|
17838
|
+
float a = step(length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - 0.08 * uSize, 0.0);
|
|
17839
|
+
return vec4(vec3(a), a);
|
|
17840
|
+
}
|
|
17841
|
+
float aspect = size.x / size.y;
|
|
17842
|
+
float h = min(uSize, iResolution.x / iResolution.y * uSize / aspect);
|
|
17843
|
+
vec2 uv = p / vec2(h * aspect, h) + 0.5;
|
|
17844
|
+
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) return vec4(0.0);
|
|
17845
|
+
vec4 c = textureLod(uMask, uv, log2(max(spacing * size.y / h, 1.0)));
|
|
17846
|
+
// Clear texels upload black, so filtered colour is about colour \xD7 alpha already.
|
|
17847
|
+
return vec4(clamp(c.rgb / max(c.a, 1e-3), 0.0, 1.0) * c.a, c.a);
|
|
17848
|
+
}`.trim();
|
|
17849
|
+
var SDF = `
|
|
17850
|
+
float capsule(vec2 p, vec2 a, vec2 b, float r) {
|
|
17851
|
+
vec2 pa = p - a;
|
|
17852
|
+
vec2 ba = b - a;
|
|
17853
|
+
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
|
|
17854
|
+
return length(pa - ba * h) - r;
|
|
17855
|
+
}
|
|
17856
|
+
float box(vec2 p, vec2 b, float r) {
|
|
17857
|
+
vec2 q = abs(p) - b + r;
|
|
17858
|
+
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
|
|
17859
|
+
}
|
|
17860
|
+
float smin(float a, float b, float k) {
|
|
17861
|
+
float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
|
|
17862
|
+
return mix(b, a, h) - k * h * (1.0 - h);
|
|
17863
|
+
}`.trim();
|
|
17864
|
+
var longShadow = {
|
|
17865
|
+
kind: "shader",
|
|
17866
|
+
id: "long-shadow",
|
|
17867
|
+
name: "Long shadow",
|
|
17868
|
+
description: "The flat-design long shadow: a word or icon casting a long diagonal band across a flat colour, fading along its length and slowly swinging round.",
|
|
17869
|
+
category: "shadow",
|
|
17870
|
+
video: "none",
|
|
17871
|
+
tags: ["shadow", "long-shadow", "flat", "text", "logo", "icon", "mask"],
|
|
17872
|
+
source: glsl(
|
|
17873
|
+
`// Long shadow: the shape's mask marched back along a direction, so it casts a long band that fades as it goes.
|
|
17874
|
+
uniform sampler2D uMask; // @control filter=mipmap wrap=clamp default=${textImage("LONG")} label="Shape (its alpha)"
|
|
17875
|
+
uniform float uSize; // @control min=0.2 max=1 default=0.8 label="Shape size"
|
|
17876
|
+
uniform float uAngle; // @control min=0 max=360 default=315 label="Direction (\xB0)"
|
|
17877
|
+
uniform float uSwing; // @control min=0 max=60 default=15 label="Swing (\xB0)"
|
|
17878
|
+
uniform float uSpin; // @control min=-30 max=30 default=0 label="Turn (\xB0/s)"
|
|
17879
|
+
uniform float uLength; // @control min=0 max=1.5 default=0.7 label="Length"
|
|
17880
|
+
uniform float uFade; // @control min=0 max=1 default=0.85 label="Fade along"
|
|
17881
|
+
uniform vec3 uBackground; // @control color default=#EF5A5A label="Background"
|
|
17882
|
+
uniform vec3 uShadow; // @control color default=#B8383F label="Shadow"
|
|
17883
|
+
uniform vec3 uColour; // @control color default=#FFFFFF label="Shape colour"
|
|
17884
|
+
uniform bool uImageColours; // @control default=false label="Use the shape's own colours"`,
|
|
17885
|
+
SHAPE,
|
|
17886
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
17887
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
|
|
17888
|
+
float a = radians(uAngle + uSpin * iTime + uSwing * sin(iTime * 0.4));
|
|
17889
|
+
vec2 dir = vec2(cos(a), sin(a));
|
|
17890
|
+
// Look back toward the shape: any copy of it along the way covers this point.
|
|
17891
|
+
const int STEPS = 96;
|
|
17892
|
+
float spacing = uLength / float(STEPS);
|
|
17893
|
+
float shade = 0.0;
|
|
17894
|
+
for (int i = 1; i <= STEPS; i++) {
|
|
17895
|
+
float m = shapeAt(p - dir * (float(i) * spacing), spacing).a;
|
|
17896
|
+
shade = max(shade, m);
|
|
17897
|
+
if (shade > 0.99) break;
|
|
17898
|
+
}
|
|
17899
|
+
// The band fades in one smooth gradient along the direction, from the back of the shape's box to the shadow's end.
|
|
17900
|
+
vec2 size = vec2(textureSize(uMask, 0));
|
|
17901
|
+
float aspect = size.x <= 1.0 ? 1.0 : size.x / size.y;
|
|
17902
|
+
float h = size.x <= 1.0 ? 0.52 * uSize : min(uSize, iResolution.x / iResolution.y * uSize / aspect);
|
|
17903
|
+
float reach = 0.5 * h * (abs(dir.x) * aspect + abs(dir.y));
|
|
17904
|
+
shade *= 1.0 - uFade * clamp((dot(p, dir) + reach) / (2.0 * reach + uLength), 0.0, 1.0);
|
|
17905
|
+
vec3 col = mix(uBackground, uShadow, shade);
|
|
17906
|
+
vec4 c = shapeAt(p, 1.0 / iResolution.y);
|
|
17907
|
+
vec3 ink = uImageColours ? c.rgb / max(c.a, 1e-4) : uColour;
|
|
17908
|
+
fragColor = vec4(mix(col, ink, c.a), 1.0);
|
|
17909
|
+
}`
|
|
17910
|
+
),
|
|
17911
|
+
presets: [
|
|
17912
|
+
{ name: "Flat", values: { uFade: 0, uLength: 1.5, uSwing: 0, uBackground: [0.16, 0.71, 0.65], uShadow: [0.11, 0.55, 0.5] } },
|
|
17913
|
+
{ name: "Icon", values: { uMask: SAMPLE_IMAGES.heart, uImageColours: true, uSize: 0.5, uBackground: [0.99, 0.82, 0.32], uShadow: [0.86, 0.62, 0.14] } },
|
|
17914
|
+
{ name: "Spinning", values: { uSwing: 0, uSpin: 20, uFade: 1, uBackground: [0.2, 0.22, 0.32], uShadow: [0.1, 0.11, 0.18] } }
|
|
17915
|
+
]
|
|
17916
|
+
};
|
|
17917
|
+
var dropShadowLift = {
|
|
17918
|
+
kind: "shader",
|
|
17919
|
+
id: "drop-shadow-lift",
|
|
17920
|
+
name: "Drop shadow lift",
|
|
17921
|
+
description: "A logo or icon resting on a soft drop shadow that lifts off the page when hovered, its shadow growing larger, softer and further below as it scales up.",
|
|
17922
|
+
category: "shadow",
|
|
17923
|
+
video: "none",
|
|
17924
|
+
tags: ["shadow", "drop-shadow", "hover", "interactive", "elevation", "logo", "icon", "mask"],
|
|
17925
|
+
source: glsl(
|
|
17926
|
+
`// Drop shadow lift: a shape on a blurred copy of its own mask, which spreads and drops as the shape lifts (on hover).
|
|
17927
|
+
uniform sampler2D uMask; // @control filter=mipmap wrap=clamp default=${SAMPLE_IMAGES.logo} label="Object (its alpha)"
|
|
17928
|
+
uniform float uSize; // @control min=0.2 max=1 default=0.55 label="Object size"
|
|
17929
|
+
uniform bool uImageColours; // @control default=true label="Use the object's own colours"
|
|
17930
|
+
uniform vec3 uColour; // @control color default=#6366F1 label="Object colour"
|
|
17931
|
+
uniform vec3 uBackground; // @control color default=#EEF1F7 label="Background"
|
|
17932
|
+
uniform vec3 uShadow; // @control color default=#1E2340 label="Shadow colour"
|
|
17933
|
+
uniform float uOpacity; // @control min=0 max=1 default=0.45 label="Shadow strength"
|
|
17934
|
+
uniform float uRestBlur; // @control min=0 max=0.1 default=0.012 label="Blur at rest"
|
|
17935
|
+
uniform float uLiftBlur; // @control min=0 max=0.2 default=0.07 label="Blur when lifted"
|
|
17936
|
+
uniform float uLiftDrop; // @control min=0 max=0.15 default=0.06 label="Shadow drop when lifted"
|
|
17937
|
+
uniform float uLiftScale; // @control min=0 max=0.2 default=0.06 label="Scale when lifted"
|
|
17938
|
+
uniform bool uDemo; // @control default=true label="Demo when idle"`,
|
|
17939
|
+
SHAPE,
|
|
17940
|
+
`// The mask blurred over a disc of the radius: 24 taps on a golden-angle spiral, each from a mip level as coarse as their spacing.
|
|
17941
|
+
float blurred(vec2 p, float radius) {
|
|
17942
|
+
float sum = 0.0;
|
|
17943
|
+
for (int i = 0; i < 24; i++) {
|
|
17944
|
+
float r = radius * sqrt((float(i) + 0.5) / 24.0);
|
|
17945
|
+
float a = float(i) * 2.39996;
|
|
17946
|
+
sum += shapeAt(p + r * vec2(cos(a), sin(a)), radius * 0.35).a;
|
|
17947
|
+
}
|
|
17948
|
+
return sum / 24.0;
|
|
17949
|
+
}
|
|
17950
|
+
|
|
17951
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
17952
|
+
float px = 1.0 / iResolution.y;
|
|
17953
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) * px;
|
|
17954
|
+
// Hovering lifts it; idle, the demo breathes it up and down.
|
|
17955
|
+
float idle = uDemo ? smoothstep(0.0, 1.0, 0.5 - 0.5 * cos(iTime * 1.3)) : 0.0;
|
|
17956
|
+
float lift = mix(idle, 1.0, clamp(iPointer.z, 0.0, 1.0));
|
|
17957
|
+
float k = 1.0 + uLiftScale * lift;
|
|
17958
|
+
vec2 rise = vec2(0.0, 0.012 * lift);
|
|
17959
|
+
// The shadow spreads a little wider than the object, drops below it and softens.
|
|
17960
|
+
float ks = k * (1.0 + 0.05 * lift);
|
|
17961
|
+
vec2 drop = vec2(0.0, -(0.008 + uLiftDrop * lift));
|
|
17962
|
+
float radius = mix(uRestBlur, uLiftBlur, lift) + px;
|
|
17963
|
+
float shadow = blurred((p - drop) / ks, radius / ks) * uOpacity * mix(1.0, 0.7, lift);
|
|
17964
|
+
vec3 col = mix(uBackground, uShadow, shadow);
|
|
17965
|
+
vec4 c = shapeAt((p - rise) / k, px / k);
|
|
17966
|
+
vec3 ink = uImageColours ? c.rgb / max(c.a, 1e-4) : uColour;
|
|
17967
|
+
fragColor = vec4(mix(col, ink, c.a), 1.0);
|
|
17968
|
+
}`
|
|
17969
|
+
),
|
|
17970
|
+
presets: [
|
|
17971
|
+
{ name: "Heart", values: { uMask: SAMPLE_IMAGES.heart, uBackground: [1, 0.94, 0.95], uShadow: [0.45, 0.05, 0.15], uOpacity: 0.4 } },
|
|
17972
|
+
{ name: "Dramatic", values: { uOpacity: 0.7, uLiftBlur: 0.12, uLiftDrop: 0.12, uLiftScale: 0.1, uBackground: [0.85, 0.87, 0.9] } }
|
|
17973
|
+
]
|
|
17974
|
+
};
|
|
17975
|
+
var lightSweepShadows = {
|
|
17976
|
+
kind: "shader",
|
|
17977
|
+
id: "light-sweep-shadows",
|
|
17978
|
+
name: "Light sweep shadows",
|
|
17979
|
+
description: "A coloured light drifting across a floor of shapes (or following the pointer), each casting a real soft shadow that swings round as the light moves.",
|
|
17980
|
+
category: "shadow",
|
|
17981
|
+
tags: ["shadow", "light", "soft-shadows", "sdf", "lamp", "interactive", "hover"],
|
|
17982
|
+
source: glsl(
|
|
17983
|
+
`// Light sweep shadows: soft 2D shadows marched from each point toward a moving light through the shapes' distance field.
|
|
17984
|
+
uniform vec3 uLight; // @control color default=#FFC46E label="Light colour"
|
|
17985
|
+
uniform float uIntensity; // @control min=0 max=4 default=1.8 label="Light strength"
|
|
17986
|
+
uniform float uReach; // @control min=0.1 max=2 default=0.45 label="Light reach"
|
|
17987
|
+
uniform float uSoftness; // @control min=0.005 max=0.3 default=0.06 label="Shadow softness"
|
|
17988
|
+
uniform float uSpeed; // @control min=0 max=2 default=0.5 label="Speed"
|
|
17989
|
+
uniform vec3 uFloor; // @control color default=#3A3F52 label="Floor"
|
|
17990
|
+
uniform vec3 uShapes; // @control color default=#ECE6DA label="Shapes"
|
|
17991
|
+
uniform float uAmbient; // @control min=0 max=1 default=0.12 label="Ambient light"
|
|
17992
|
+
uniform bool uFollow; // @control default=true label="Follow the pointer"`,
|
|
17993
|
+
ROTATE,
|
|
17994
|
+
SDF,
|
|
17995
|
+
`float scene(vec2 p) {
|
|
17996
|
+
float d = length(p - vec2(-0.45, 0.13)) - 0.09;
|
|
17997
|
+
d = min(d, length(p - vec2(0.36, -0.17)) - 0.065);
|
|
17998
|
+
d = min(d, length(p - vec2(-0.2, -0.28)) - 0.045);
|
|
17999
|
+
d = min(d, box(rot(0.5) * (p - vec2(0.04, 0.0)), vec2(0.1, 0.05), 0.012));
|
|
18000
|
+
d = min(d, box(rot(-0.3) * (p - vec2(0.55, 0.2)), vec2(0.03, 0.12), 0.01));
|
|
18001
|
+
d = min(d, box(rot(0.9) * (p - vec2(-0.62, -0.22)), vec2(0.05, 0.05), 0.008));
|
|
18002
|
+
return d;
|
|
18003
|
+
}
|
|
18004
|
+
|
|
18005
|
+
vec2 sceneNormal(vec2 p) {
|
|
18006
|
+
vec2 e = vec2(0.002, 0.0);
|
|
18007
|
+
return normalize(vec2(scene(p + e.xy) - scene(p - e.xy), scene(p + e.yx) - scene(p - e.yx)) + 1e-6);
|
|
18008
|
+
}
|
|
18009
|
+
|
|
18010
|
+
// How much of the light reaches p: march toward it, keeping the narrowest miss relative to how far along.
|
|
18011
|
+
float softShadow(vec2 p, vec2 light) {
|
|
18012
|
+
vec2 to = light - p;
|
|
18013
|
+
float dist = length(to);
|
|
18014
|
+
vec2 dir = to / max(dist, 1e-5);
|
|
18015
|
+
float res = 1.0;
|
|
18016
|
+
float t = 0.004;
|
|
18017
|
+
for (int i = 0; i < 56; i++) {
|
|
18018
|
+
if (t >= dist) break;
|
|
18019
|
+
float d = scene(p + dir * t);
|
|
18020
|
+
res = min(res, d / (uSoftness * t));
|
|
18021
|
+
if (res < -1.0) break;
|
|
18022
|
+
t += clamp(d, 0.004, 0.08);
|
|
18023
|
+
}
|
|
18024
|
+
res = max(res, -1.0);
|
|
18025
|
+
return 0.25 * (1.0 + res) * (1.0 + res) * (2.0 - res);
|
|
18026
|
+
}
|
|
18027
|
+
|
|
18028
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18029
|
+
float px = 1.0 / iResolution.y;
|
|
18030
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) * px;
|
|
18031
|
+
float aspect = iResolution.x / iResolution.y;
|
|
18032
|
+
float t = iTime * uSpeed;
|
|
18033
|
+
vec2 light = vec2(0.42 * aspect * sin(t * 1.06), 0.36 * sin(t * 1.62 + 1.0));
|
|
18034
|
+
if (uFollow) light = mix(light, (iPointer.xy - 0.5 * iResolution.xy) * px, clamp(iPointer.z, 0.0, 1.0));
|
|
18035
|
+
// Keep the light out of the shapes.
|
|
18036
|
+
float inside = scene(light);
|
|
18037
|
+
if (inside < 0.03) light += sceneNormal(light) * (0.03 - inside);
|
|
18038
|
+
|
|
18039
|
+
vec2 to = light - p;
|
|
18040
|
+
float r = length(to);
|
|
18041
|
+
float falloff = 1.0 / (1.0 + r * r / (uReach * uReach));
|
|
18042
|
+
vec3 lamp = uLight * uIntensity * falloff;
|
|
18043
|
+
float d = scene(p);
|
|
18044
|
+
// Floor: ambient, plus the light where it gets through.
|
|
18045
|
+
vec3 floorCol = uFloor * (uAmbient + lamp * softShadow(p, light));
|
|
18046
|
+
// Shapes are lit on top, brighter on the rim that faces the light.
|
|
18047
|
+
vec2 n = sceneNormal(p);
|
|
18048
|
+
float rim = smoothstep(-0.025, 0.0, d) * max(dot(n, to / max(r, 1e-5)), 0.0);
|
|
18049
|
+
vec3 shapeCol = uShapes * (uAmbient + 0.25 + lamp * (0.45 + 0.8 * rim));
|
|
18050
|
+
vec3 col = mix(shapeCol, floorCol, smoothstep(-px, px, d));
|
|
18051
|
+
// The bulb itself.
|
|
18052
|
+
col += uLight * (exp(-r * r / 0.00025) * 2.0 + exp(-r * r / 0.004) * 0.25);
|
|
18053
|
+
fragColor = vec4(1.0 - exp(-col * 1.3), 1.0);
|
|
18054
|
+
}`
|
|
18055
|
+
),
|
|
18056
|
+
presets: [
|
|
18057
|
+
{ name: "Candle", values: { uLight: [1, 0.6, 0.25], uReach: 0.3, uSoftness: 0.12, uSpeed: 0.3, uFloor: [0.22, 0.17, 0.14] } },
|
|
18058
|
+
{ name: "Neon", values: { uLight: [0.3, 0.85, 1], uIntensity: 2.5, uSoftness: 0.02, uFloor: [0.1, 0.1, 0.18], uShapes: [0.9, 0.3, 0.7] } }
|
|
18059
|
+
]
|
|
18060
|
+
};
|
|
18061
|
+
var contactShadow = {
|
|
18062
|
+
kind: "shader",
|
|
18063
|
+
id: "contact-shadow",
|
|
18064
|
+
name: "Contact shadow",
|
|
18065
|
+
description: "An object floating up and down above the floor, its contact shadow tightening and darkening as it nears the ground and spreading and fading as it rises.",
|
|
18066
|
+
category: "shadow",
|
|
18067
|
+
tags: ["shadow", "contact-shadow", "float", "ground", "product", "3d"],
|
|
18068
|
+
source: glsl(
|
|
18069
|
+
`// Contact shadow: an object bobbing over a floor; a tight dark core and a wide soft shadow, both spreading and fading with height.
|
|
18070
|
+
uniform int uShape; // @control min=0 max=2 default=0 label="Object (0 ball, 1 cube, 2 pill)"
|
|
18071
|
+
uniform vec3 uObject; // @control color default=#FF7A59 label="Object"
|
|
18072
|
+
uniform vec3 uBackground; // @control color default=#F3EEE6 label="Wall"
|
|
18073
|
+
uniform vec3 uGround; // @control color default=#E2D8C9 label="Floor"
|
|
18074
|
+
uniform vec3 uShadow; // @control color default=#3A2C22 label="Shadow colour"
|
|
18075
|
+
uniform float uHeight; // @control min=0 max=0.4 default=0.2 label="Float height"
|
|
18076
|
+
uniform float uSpeed; // @control min=0 max=3 default=0.8 label="Speed"
|
|
18077
|
+
uniform float uDarkness; // @control min=0 max=1 default=0.8 label="Contact darkness"
|
|
18078
|
+
uniform float uSpread; // @control min=0 max=3 default=1.4 label="Spread when high"`,
|
|
18079
|
+
SDF,
|
|
18080
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18081
|
+
float px = 1.0 / iResolution.y;
|
|
18082
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) * px;
|
|
18083
|
+
const float horizon = -0.08;
|
|
18084
|
+
const float groundY = -0.26;
|
|
18085
|
+
const float R = 0.12;
|
|
18086
|
+
// Lingers at the top and bottom of each bob, and comes down to just touch.
|
|
18087
|
+
float s = 0.5 + 0.5 * sin(iTime * uSpeed * 1.6);
|
|
18088
|
+
float h = 0.003 + uHeight * s * s * (3.0 - 2.0 * s);
|
|
18089
|
+
|
|
18090
|
+
// Wall above, floor below, a soft crease between.
|
|
18091
|
+
vec3 wall = uBackground * (0.92 + 0.08 * smoothstep(horizon, 0.5, p.y));
|
|
18092
|
+
vec3 floorCol = uGround * (0.94 + 0.06 * smoothstep(horizon, -0.5, p.y));
|
|
18093
|
+
vec3 col = mix(floorCol, wall, smoothstep(-px, px, p.y - horizon));
|
|
18094
|
+
col *= 1.0 - 0.06 * exp(-abs(p.y - horizon) * 40.0);
|
|
18095
|
+
|
|
18096
|
+
vec2 ext = uShape == 0 ? vec2(R) : (uShape == 1 ? vec2(0.85 * R) : vec2(1.35 * R, 0.55 * R));
|
|
18097
|
+
// The shadow on the floor: a squashed core that tightens and darkens near the ground, and a broad soft one.
|
|
18098
|
+
vec2 g = p - vec2(0.0, groundY);
|
|
18099
|
+
float coreW = ext.x * 0.8 + h * uSpread * 0.35;
|
|
18100
|
+
float core = exp(-dot(g / vec2(coreW, coreW * 0.16), g / vec2(coreW, coreW * 0.16)) * 1.6) * exp(-h * 10.0);
|
|
18101
|
+
float wideW = ext.x * 1.15 + h * uSpread;
|
|
18102
|
+
float wide = exp(-dot(g / vec2(wideW, wideW * 0.22), g / vec2(wideW, wideW * 0.22)) * 1.2) * 0.45 / (1.0 + h * 8.0);
|
|
18103
|
+
float shade = uDarkness * (1.0 - (1.0 - core) * (1.0 - wide));
|
|
18104
|
+
col = mix(col, uShadow, shade);
|
|
18105
|
+
|
|
18106
|
+
// The object.
|
|
18107
|
+
vec2 q = p - vec2(0.0, groundY + ext.y + h);
|
|
18108
|
+
float d = uShape == 0 ? length(q) - R : (uShape == 1 ? box(q, ext, 0.035) : box(q, ext, ext.y));
|
|
18109
|
+
vec2 u = clamp(q / ext, -1.0, 1.0);
|
|
18110
|
+
vec3 obj;
|
|
18111
|
+
if (uShape == 0) {
|
|
18112
|
+
vec3 n = vec3(u, sqrt(max(1.0 - dot(u, u), 0.0)));
|
|
18113
|
+
vec3 l = normalize(vec3(-0.45, 0.6, 0.65));
|
|
18114
|
+
float diff = max(dot(n, l), 0.0);
|
|
18115
|
+
float spec = pow(max(dot(reflect(-l, n), vec3(0.0, 0.0, 1.0)), 0.0), 24.0);
|
|
18116
|
+
obj = uObject * (0.35 + 0.75 * diff) + 0.35 * spec;
|
|
18117
|
+
} else {
|
|
18118
|
+
obj = uObject * (0.88 + 0.16 * u.y - 0.06 * u.x);
|
|
18119
|
+
obj += 0.12 * smoothstep(0.7, 1.0, u.y) * smoothstep(1.0, 0.6, abs(u.x));
|
|
18120
|
+
}
|
|
18121
|
+
// Near the ground the floor shades its underside.
|
|
18122
|
+
obj *= 1.0 - 0.35 * exp(-h * 14.0) * smoothstep(-0.2, -1.0, u.y);
|
|
18123
|
+
col = mix(obj, col, smoothstep(-px, px, d));
|
|
18124
|
+
fragColor = vec4(col, 1.0);
|
|
18125
|
+
}`
|
|
18126
|
+
),
|
|
18127
|
+
presets: [
|
|
18128
|
+
{ name: "Cube", values: { uShape: 1, uObject: [0.38, 0.4, 0.95], uBackground: [0.93, 0.94, 0.97], uGround: [0.85, 0.87, 0.92], uShadow: [0.1, 0.12, 0.25] } },
|
|
18129
|
+
{ name: "Pill on dark", values: { uShape: 2, uObject: [0.98, 0.84, 0.3], uBackground: [0.13, 0.14, 0.17], uGround: [0.18, 0.19, 0.23], uShadow: [0, 0, 0], uDarkness: 0.9 } }
|
|
18130
|
+
]
|
|
18131
|
+
};
|
|
18132
|
+
var windowShadows = {
|
|
18133
|
+
kind: "shader",
|
|
18134
|
+
id: "window-shadows",
|
|
18135
|
+
name: "Window shadows",
|
|
18136
|
+
description: "Warm sunlight through a window falling across a wall or a picture, dappled by the shadows of slowly tilting blinds or rustling leaves.",
|
|
18137
|
+
category: "shadow",
|
|
18138
|
+
video: "filter",
|
|
18139
|
+
tags: ["shadow", "light", "dappled", "window", "blinds", "leaves", "sunlight", "warm", "image"],
|
|
18140
|
+
source: glsl(
|
|
18141
|
+
`// Window shadows: a patch of sunlight from a window, striped by blinds or broken up by leaves, laid over a wall or picture.
|
|
18142
|
+
uniform sampler2D uImage; // @control filter=linear wrap=clamp default=${SAMPLE_IMAGES.portrait} label="Image"
|
|
18143
|
+
uniform bool uPlainWall; // @control default=false label="Plain wall (no image)"
|
|
18144
|
+
uniform vec3 uWall; // @control color default=#E8DCCB label="Wall"
|
|
18145
|
+
uniform int uPattern; // @control min=0 max=1 default=1 label="Shadows of (0 blinds, 1 leaves)"
|
|
18146
|
+
uniform vec3 uLight; // @control color default=#FFC27A label="Light colour"
|
|
18147
|
+
uniform float uIntensity; // @control min=0 max=2 default=0.9 label="Light strength"
|
|
18148
|
+
uniform float uShade; // @control min=0 max=1 default=0.45 label="Shadow depth"
|
|
18149
|
+
uniform float uAngle; // @control min=-60 max=60 default=-22 label="Light angle (\xB0)"
|
|
18150
|
+
uniform float uScale; // @control min=0.4 max=2.5 default=1 label="Scale"
|
|
18151
|
+
uniform float uSoftness; // @control min=0 max=1 default=0.35 label="Softness"
|
|
18152
|
+
uniform float uSway; // @control min=0 max=2 default=1 label="Sway"
|
|
18153
|
+
${BACKDROP_CONTROLS}`,
|
|
18154
|
+
PICTURE,
|
|
18155
|
+
HASH,
|
|
18156
|
+
NOISE,
|
|
18157
|
+
FBM,
|
|
18158
|
+
ROTATE,
|
|
18159
|
+
`// The sunlit patch the window throws, slanted, with soft edges.
|
|
18160
|
+
float windowPatch(vec2 q, float soft) {
|
|
18161
|
+
vec2 e = abs(q) - vec2(0.5, 0.4);
|
|
18162
|
+
return smoothstep(soft, -soft, max(e.x, e.y));
|
|
18163
|
+
}
|
|
18164
|
+
|
|
18165
|
+
float blinds(vec2 q, float t, float soft) {
|
|
18166
|
+
q.x += 0.006 * sin(t * 0.7) * uSway;
|
|
18167
|
+
// The slats tilt slowly open and shut.
|
|
18168
|
+
float open = clamp(0.5 + 0.22 * sin(t * 0.35 * uSway + 0.4), 0.15, 0.85);
|
|
18169
|
+
float f = fract(q.y * 14.0 + 0.04 * sin(q.x * 3.0 + t * 0.5) * uSway);
|
|
18170
|
+
float w = 0.03 + soft * 0.25;
|
|
18171
|
+
float lit = smoothstep(0.0, w, f) * smoothstep(open + w, open, f);
|
|
18172
|
+
// The window's frame and cross bar.
|
|
18173
|
+
float bars = smoothstep(0.012 + soft * 0.03, 0.006, abs(q.x)) + smoothstep(0.012 + soft * 0.03, 0.006, abs(q.y - 0.02));
|
|
18174
|
+
return lit * (1.0 - clamp(bars, 0.0, 1.0)) * windowPatch(q, 0.02 + soft * 0.06);
|
|
18175
|
+
}
|
|
18176
|
+
|
|
18177
|
+
float leaves(vec2 q, float t, float soft) {
|
|
18178
|
+
// Branches sway slowly and the leaves rustle quickly on top.
|
|
18179
|
+
vec2 sway = uSway * (vec2(0.05 * sin(t * 0.6 + q.y * 1.5), 0.03 * cos(t * 0.45 + q.x * 1.2))
|
|
18180
|
+
+ 0.012 * vec2(vnoise(q * 6.0 + t * 1.3), vnoise(q * 6.0 - t * 1.1 + 7.0)));
|
|
18181
|
+
float n = fbm((q + sway) * 3.2 + 4.0, 5);
|
|
18182
|
+
float s = 0.025 + soft * 0.1;
|
|
18183
|
+
float lit = smoothstep(0.5 - s, 0.5 + s, n);
|
|
18184
|
+
return lit * windowPatch(q * 0.75, 0.08 + soft * 0.1);
|
|
18185
|
+
}
|
|
18186
|
+
|
|
18187
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18188
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
18189
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
|
|
18190
|
+
// Light comes in at an angle, so the window's shape is turned and sheared across the wall.
|
|
18191
|
+
vec2 q = rot(radians(uAngle)) * p / uScale;
|
|
18192
|
+
q.x += q.y * 0.35;
|
|
18193
|
+
float lit = uPattern == 0 ? blinds(q, iTime, uSoftness) : leaves(q, iTime, uSoftness);
|
|
18194
|
+
|
|
18195
|
+
vec4 base;
|
|
18196
|
+
if (uPlainWall) {
|
|
18197
|
+
float plaster = vnoise(fragCoord * 0.35) * 0.5 + vnoise(fragCoord * 0.05) * 0.5;
|
|
18198
|
+
base = vec4(uWall * (0.96 + 0.06 * plaster), 1.0);
|
|
18199
|
+
} else {
|
|
18200
|
+
base = pictureRGBA(uImage, uv);
|
|
18201
|
+
}
|
|
18202
|
+
vec3 light = (1.0 - uShade) * vec3(0.95, 0.96, 1.0) + lit * uShade * uLight * (0.6 + uIntensity);
|
|
18203
|
+
vec3 col = base.rgb * light;
|
|
18204
|
+
col = col / (1.0 + max(col - 1.0, 0.0));
|
|
18205
|
+
fragColor = vec4(col, base.a);
|
|
18206
|
+
}`
|
|
18207
|
+
),
|
|
18208
|
+
presets: [
|
|
18209
|
+
{ name: "Blinds", values: { uPattern: 0, uPlainWall: true, uAngle: -30, uSoftness: 0.2, uLight: [1, 0.82, 0.6] } },
|
|
18210
|
+
{ name: "Plain wall", values: { uPlainWall: true } },
|
|
18211
|
+
{ name: "Golden hour", values: { uPlainWall: true, uLight: [1, 0.6, 0.3], uIntensity: 1.4, uShade: 0.7, uSoftness: 0.6, uWall: [0.93, 0.85, 0.78] } }
|
|
18212
|
+
]
|
|
18213
|
+
};
|
|
18214
|
+
var shadowPuppets = {
|
|
18215
|
+
kind: "shader",
|
|
18216
|
+
id: "shadow-puppets",
|
|
18217
|
+
name: "Shadow puppets",
|
|
18218
|
+
description: "Hand shadow puppets on a lamp-lit wall: a flapping bird, a barking dog or a twitching rabbit, softening as the hand moves away from the wall.",
|
|
18219
|
+
category: "shadow",
|
|
18220
|
+
tags: ["shadow", "puppet", "hands", "silhouette", "bird", "dog", "rabbit", "warm", "playful"],
|
|
18221
|
+
source: glsl(
|
|
18222
|
+
`// Shadow puppets: hand shapes built from soft-unioned capsules, cast on a warmly lit wall; the further the hand from the wall, the bigger and softer.
|
|
18223
|
+
uniform int uPuppet; // @control min=0 max=2 default=0 label="Puppet (0 bird, 1 dog, 2 rabbit)"
|
|
18224
|
+
uniform vec3 uWall; // @control color default=#F2DDB8 label="Wall"
|
|
18225
|
+
uniform vec3 uLight; // @control color default=#FFB75E label="Lamp colour"
|
|
18226
|
+
uniform vec3 uShadow; // @control color default=#2B1B12 label="Shadow colour"
|
|
18227
|
+
uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"
|
|
18228
|
+
uniform float uDepth; // @control min=0 max=1 default=0.6 label="Hand moving to and from the wall"
|
|
18229
|
+
uniform float uSoftness; // @control min=0 max=0.1 default=0.035 label="Softness away from the wall"
|
|
18230
|
+
uniform float uSize; // @control min=0.5 max=1.5 default=1 label="Size"`,
|
|
18231
|
+
HASH,
|
|
18232
|
+
NOISE,
|
|
18233
|
+
ROTATE,
|
|
18234
|
+
SDF,
|
|
18235
|
+
`float ellipse(vec2 p, vec2 r) {
|
|
18236
|
+
return (length(p / r) - 1.0) * min(r.x, r.y);
|
|
18237
|
+
}
|
|
18238
|
+
|
|
18239
|
+
// Two hands linked at the thumbs (the head), fingers fanned out as wings.
|
|
18240
|
+
float bird(vec2 p, float t) {
|
|
18241
|
+
float flap = 0.45 * sin(t * 4.0);
|
|
18242
|
+
float d = capsule(p, vec2(0.0, -0.02), vec2(0.0, 0.13), 0.028);
|
|
18243
|
+
d = smin(d, ellipse(p - vec2(0.0, 0.165), vec2(0.04, 0.035)), 0.02);
|
|
18244
|
+
d = min(d, capsule(p, vec2(0.025, 0.17), vec2(0.075, 0.158), 0.009));
|
|
18245
|
+
for (int s = 0; s < 2; s++) {
|
|
18246
|
+
vec2 q = vec2(s == 0 ? p.x : -p.x, p.y);
|
|
18247
|
+
d = smin(d, ellipse(q - vec2(0.08, 0.0), vec2(0.075, 0.045)), 0.03);
|
|
18248
|
+
d = smin(d, capsule(q, vec2(0.08, -0.01), vec2(0.3, -0.65), 0.06), 0.03);
|
|
18249
|
+
for (int k = 0; k < 4; k++) {
|
|
18250
|
+
float fk = float(k);
|
|
18251
|
+
float a = 0.55 - fk * 0.22 + flap * (1.0 - fk * 0.12);
|
|
18252
|
+
vec2 root = vec2(0.13, 0.02 - fk * 0.018);
|
|
18253
|
+
d = smin(d, capsule(q, root, root + (0.2 - fk * 0.02) * vec2(cos(a), sin(a)), 0.019 - fk * 0.002), 0.012);
|
|
18254
|
+
}
|
|
18255
|
+
}
|
|
18256
|
+
return d;
|
|
18257
|
+
}
|
|
18258
|
+
|
|
18259
|
+
// The back of the hand is the head, the thumb an ear, the little finger a jaw that drops as it barks.
|
|
18260
|
+
float dog(vec2 p, float t) {
|
|
18261
|
+
float bark = max(sin(t * 3.0), 0.0);
|
|
18262
|
+
bark *= bark;
|
|
18263
|
+
float d = box(p - vec2(-0.02, 0.03), vec2(0.12, 0.065), 0.05);
|
|
18264
|
+
d = smin(d, capsule(p, vec2(0.06, 0.04), vec2(0.24, 0.025), 0.038), 0.03);
|
|
18265
|
+
vec2 pivot = vec2(0.0, -0.03);
|
|
18266
|
+
vec2 jaw = rot(0.4 * bark) * (p - pivot) + pivot;
|
|
18267
|
+
d = smin(d, capsule(jaw, vec2(0.02, -0.04), vec2(0.2, -0.045), 0.024), 0.012);
|
|
18268
|
+
d = smin(d, capsule(p, vec2(-0.06, 0.08), vec2(-0.1 + 0.04 * sin(t * 1.3), 0.2), 0.042), 0.03);
|
|
18269
|
+
d = smin(d, capsule(p, vec2(-0.08, 0.0), vec2(-0.5, -0.65), 0.075), 0.04);
|
|
18270
|
+
// A gap between the fingers for an eye.
|
|
18271
|
+
return max(d, -(length(p - vec2(0.07, 0.07)) - 0.013));
|
|
18272
|
+
}
|
|
18273
|
+
|
|
18274
|
+
// Two fingers up as ears, the fist a head with a twitching nose.
|
|
18275
|
+
float rabbit(vec2 p, float t) {
|
|
18276
|
+
float nose = 0.006 * sin(t * 18.0) * step(0.3, sin(t * 0.9));
|
|
18277
|
+
float d = ellipse(p, vec2(0.12, 0.085));
|
|
18278
|
+
d = smin(d, length(p - vec2(0.11 + nose, -0.025)) - 0.05, 0.04);
|
|
18279
|
+
d = smin(d, capsule(p, vec2(-0.04, 0.05), vec2(-0.09 + 0.08 * sin(t * 1.7), 0.32), 0.04), 0.03);
|
|
18280
|
+
d = smin(d, capsule(p, vec2(0.0, 0.06), vec2(0.03 + 0.08 * sin(t * 1.7 + 1.2), 0.3), 0.037), 0.03);
|
|
18281
|
+
d = smin(d, capsule(p, vec2(-0.06, -0.03), vec2(-0.45, -0.65), 0.075), 0.04);
|
|
18282
|
+
return max(d, -(length(p - vec2(0.045, 0.025)) - 0.013));
|
|
18283
|
+
}
|
|
18284
|
+
|
|
18285
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18286
|
+
float px = 1.0 / iResolution.y;
|
|
18287
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) * px;
|
|
18288
|
+
float t = iTime * uSpeed;
|
|
18289
|
+
// The lamp's pool of light on the wall, and a little plaster.
|
|
18290
|
+
float spot = smoothstep(0.95, 0.05, length((p - vec2(0.04, 0.05)) * vec2(0.75, 1.0)));
|
|
18291
|
+
vec3 wall = uWall * mix(vec3(0.16, 0.11, 0.09), uLight * 1.3, spot);
|
|
18292
|
+
wall *= 0.95 + 0.07 * vnoise(fragCoord * 0.3) + 0.04 * vnoise(fragCoord * 0.03);
|
|
18293
|
+
// Nearer the lamp the shadow grows and its edge blurs.
|
|
18294
|
+
float z = uDepth * (0.5 + 0.5 * sin(iTime * 0.45));
|
|
18295
|
+
float scale = uSize * (1.0 + 0.3 * z);
|
|
18296
|
+
vec2 drift = vec2(0.03 * sin(iTime * 0.4), 0.02 * sin(iTime * 0.7) - 0.04);
|
|
18297
|
+
vec2 q = (p - drift) / scale;
|
|
18298
|
+
float d = (uPuppet == 0 ? bird(q, t) : (uPuppet == 1 ? dog(q, t) : rabbit(q, t))) * scale;
|
|
18299
|
+
float soft = max(0.002 + uSoftness * z, px);
|
|
18300
|
+
float cover = 1.0 - smoothstep(-soft, soft, d);
|
|
18301
|
+
vec3 col = mix(wall, uShadow * (0.6 + 0.4 * spot), cover * 0.92);
|
|
18302
|
+
fragColor = vec4(col, 1.0);
|
|
18303
|
+
}`
|
|
18304
|
+
),
|
|
18305
|
+
presets: [
|
|
18306
|
+
{ name: "Dog", values: { uPuppet: 1 } },
|
|
18307
|
+
{ name: "Rabbit by candle", values: { uPuppet: 2, uLight: [1, 0.55, 0.25], uWall: [0.95, 0.8, 0.62], uSoftness: 0.06 } },
|
|
18308
|
+
{ name: "Moonlit bird", values: { uPuppet: 0, uLight: [0.7, 0.8, 1], uWall: [0.78, 0.82, 0.9], uShadow: [0.05, 0.07, 0.13] } }
|
|
18309
|
+
]
|
|
18310
|
+
};
|
|
18311
|
+
var shadowShaders = [longShadow, dropShadowLift, lightSweepShadows, contactShadow, windowShadows, shadowPuppets];
|
|
18312
|
+
|
|
18313
|
+
// src/catalog/shaders/distortion.ts
|
|
18314
|
+
var CENTRE = `
|
|
18315
|
+
vec2 centreAt(vec2 centre, bool follow) {
|
|
18316
|
+
vec2 drift = vec2(0.03 * sin(iTime * 0.37), 0.025 * cos(iTime * 0.29));
|
|
18317
|
+
vec2 idle = (centre + drift) * iResolution.xy;
|
|
18318
|
+
float h = follow ? clamp(iPointer.z, 0.0, 1.0) : 0.0;
|
|
18319
|
+
return mix(idle, iPointer.xy, h);
|
|
18320
|
+
}`.trim();
|
|
18321
|
+
var twirl = {
|
|
18322
|
+
kind: "shader",
|
|
18323
|
+
id: "twirl",
|
|
18324
|
+
name: "Twirl",
|
|
18325
|
+
description: "A picture twisted into a swirl that winds and unwinds around the pointer or a centre, strongest in the middle.",
|
|
18326
|
+
category: "distortion",
|
|
18327
|
+
video: "interactive",
|
|
18328
|
+
tags: ["distortion", "swirl", "image", "interactive", "hover"],
|
|
18329
|
+
source: glsl(
|
|
18330
|
+
`// Twirl: the picture swirled around a centre, the twist falling off with radius.
|
|
18331
|
+
uniform sampler2D uImage; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.landscape} label="Image"
|
|
18332
|
+
uniform float uTwist; // @control min=-12 max=12 default=5 label="Twist (radians)"
|
|
18333
|
+
uniform float uRadius; // @control min=0.05 max=1.2 default=0.45 label="Radius"
|
|
18334
|
+
uniform float uSpeed; // @control min=0 max=3 default=0.6 label="Wind speed (0 holds it)"
|
|
18335
|
+
uniform vec2 uCentre; // @control min=0 max=1 default=0.5,0.5 label="Centre"
|
|
18336
|
+
uniform bool uFollow; // @control default=true label="Follow the pointer"
|
|
18337
|
+
${BACKDROP_CONTROLS}`,
|
|
18338
|
+
ROTATE,
|
|
18339
|
+
PICTURE,
|
|
18340
|
+
CENTRE,
|
|
18341
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18342
|
+
vec2 c = centreAt(uCentre, uFollow);
|
|
18343
|
+
vec2 d = fragCoord - c;
|
|
18344
|
+
float R = uRadius * iResolution.y;
|
|
18345
|
+
float f = clamp(1.0 - length(d) / R, 0.0, 1.0);
|
|
18346
|
+
f = f * f * (3.0 - 2.0 * f);
|
|
18347
|
+
float swing = cos(iTime * uSpeed);
|
|
18348
|
+
d = rot(uTwist * swing * f * f) * d;
|
|
18349
|
+
fragColor = pictureRGBA(uImage, (c + d) / iResolution.xy);
|
|
18350
|
+
}`
|
|
18351
|
+
),
|
|
18352
|
+
presets: [
|
|
18353
|
+
{ name: "Whirlpool", values: { uTwist: 10, uRadius: 0.8, uSpeed: 0.3 } },
|
|
18354
|
+
{ name: "Flick", values: { uTwist: -3, uRadius: 0.25, uSpeed: 2 } }
|
|
18355
|
+
]
|
|
18356
|
+
};
|
|
18357
|
+
var pinchBulge = {
|
|
18358
|
+
kind: "shader",
|
|
18359
|
+
id: "pinch-bulge",
|
|
18360
|
+
name: "Pinch & bulge",
|
|
18361
|
+
description: "A picture swelling out like a bubble or sucked in to a point around the pointer or a centre.",
|
|
18362
|
+
category: "distortion",
|
|
18363
|
+
video: "interactive",
|
|
18364
|
+
tags: ["distortion", "bulge", "pinch", "image", "interactive", "hover"],
|
|
18365
|
+
source: glsl(
|
|
18366
|
+
`// Pinch & bulge: the picture bulged out (positive) or pinched in (negative) inside a radius.
|
|
18367
|
+
uniform sampler2D uImage; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.portrait} label="Image"
|
|
18368
|
+
uniform float uStrength; // @control min=-1 max=1 default=0.7 label="Pinch \u2190 \u2192 Bulge"
|
|
18369
|
+
uniform float uRadius; // @control min=0.05 max=1 default=0.4 label="Radius"
|
|
18370
|
+
uniform bool uAnimate; // @control default=true label="Animate (swing between pinch and bulge)"
|
|
18371
|
+
uniform float uSpeed; // @control min=0.1 max=4 default=1 label="Swing speed"
|
|
18372
|
+
uniform vec2 uCentre; // @control min=0 max=1 default=0.5,0.5 label="Centre"
|
|
18373
|
+
uniform bool uFollow; // @control default=true label="Follow the pointer"
|
|
18374
|
+
${BACKDROP_CONTROLS}`,
|
|
18375
|
+
PICTURE,
|
|
18376
|
+
CENTRE,
|
|
18377
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18378
|
+
vec2 c = centreAt(uCentre, uFollow);
|
|
18379
|
+
vec2 d = fragCoord - c;
|
|
18380
|
+
float R = uRadius * iResolution.y;
|
|
18381
|
+
float t = length(d) / R;
|
|
18382
|
+
float s = uAnimate ? uStrength * cos(iTime * uSpeed) : uStrength;
|
|
18383
|
+
float shade = 1.0;
|
|
18384
|
+
if (t < 1.0) {
|
|
18385
|
+
// A power curve that meets the untouched picture at the rim: above 1 magnifies the middle, below 1 shrinks it.
|
|
18386
|
+
float e = exp(s * 1.3);
|
|
18387
|
+
d *= pow(max(t, 1e-4), e - 1.0);
|
|
18388
|
+
shade = 1.0 + 0.18 * s * (1.0 - t) * (1.0 - t);
|
|
18389
|
+
}
|
|
18390
|
+
vec4 pic = pictureRGBA(uImage, (c + d) / iResolution.xy);
|
|
18391
|
+
fragColor = vec4(pic.rgb * shade, pic.a);
|
|
18392
|
+
}`
|
|
18393
|
+
),
|
|
18394
|
+
presets: [
|
|
18395
|
+
{ name: "Bubble", values: { uStrength: 1, uRadius: 0.3, uAnimate: false } },
|
|
18396
|
+
{ name: "Black hole", values: { uStrength: -1, uRadius: 0.6, uAnimate: false } }
|
|
18397
|
+
]
|
|
18398
|
+
};
|
|
18399
|
+
var fisheye = {
|
|
18400
|
+
kind: "shader",
|
|
18401
|
+
id: "fisheye",
|
|
18402
|
+
name: "Fisheye",
|
|
18403
|
+
description: "A picture seen through a fisheye lens, bulging in the middle and curving to the corners, over the whole frame or in a round lens.",
|
|
18404
|
+
category: "distortion",
|
|
18405
|
+
video: "filter",
|
|
18406
|
+
tags: ["distortion", "lens", "fisheye", "barrel", "image"],
|
|
18407
|
+
source: glsl(
|
|
18408
|
+
`// Fisheye: an equidistant fisheye projection of a flat picture, framed or in a circle.
|
|
18409
|
+
uniform sampler2D uImage; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.city} label="Image"
|
|
18410
|
+
uniform float uStrength; // @control min=0 max=1 default=0.75 label="Strength (field of view)"
|
|
18411
|
+
uniform float uZoom; // @control min=0.5 max=2 default=1 label="Zoom"
|
|
18412
|
+
uniform bool uLens; // @control default=false label="Round lens"
|
|
18413
|
+
uniform float uRadius; // @control min=0.1 max=0.7 default=0.42 label="Lens radius"
|
|
18414
|
+
uniform float uVignette; // @control min=0 max=1 default=0.35 label="Vignette"
|
|
18415
|
+
${BACKDROP_CONTROLS}`,
|
|
18416
|
+
PICTURE,
|
|
18417
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18418
|
+
vec2 mid = 0.5 * iResolution.xy;
|
|
18419
|
+
vec2 d = fragCoord - mid;
|
|
18420
|
+
// The unit radius: the half diagonal (corners stay corners) or the lens.
|
|
18421
|
+
float unit = uLens ? uRadius * iResolution.y : 0.5 * length(iResolution.xy);
|
|
18422
|
+
float r = length(d) / unit;
|
|
18423
|
+
vec4 col = pictureRGBA(uImage, fragCoord / iResolution.xy);
|
|
18424
|
+
if (r < 1.0) {
|
|
18425
|
+
// A ray at angle r * fov / 2 lands at tan(angle) on the flat picture.
|
|
18426
|
+
float fov = mix(0.02, 2.9, uStrength);
|
|
18427
|
+
float src = tan(r * 0.5 * fov) / tan(0.5 * fov);
|
|
18428
|
+
vec2 p = mid + d / max(length(d), 1e-5) * src * unit / uZoom;
|
|
18429
|
+
vec4 lens = pictureRGBA(uImage, p / iResolution.xy);
|
|
18430
|
+
lens.rgb *= 1.0 - uVignette * pow(r, 2.5);
|
|
18431
|
+
float edge = uLens ? 1.0 - smoothstep(1.0 - 2.0 / unit, 1.0, r) : 1.0;
|
|
18432
|
+
col = mix(col, lens, edge);
|
|
18433
|
+
}
|
|
18434
|
+
if (uLens) {
|
|
18435
|
+
float rim = exp(-pow((r - 1.0) * unit / max(unit * 0.02, 1.5), 2.0));
|
|
18436
|
+
col.rgb = mix(col.rgb, vec3(0.06), rim * 0.6);
|
|
18437
|
+
} else {
|
|
18438
|
+
col.rgb *= 1.0 - uVignette * smoothstep(0.98, 1.0, r);
|
|
18439
|
+
}
|
|
18440
|
+
fragColor = col;
|
|
18441
|
+
}`
|
|
18442
|
+
),
|
|
18443
|
+
presets: [
|
|
18444
|
+
{ name: "Doorway peephole", values: { uLens: true, uStrength: 1, uRadius: 0.48, uVignette: 0.6 } },
|
|
18445
|
+
{ name: "Action cam", values: { uStrength: 0.45, uZoom: 1.1, uVignette: 0.15 } }
|
|
18446
|
+
]
|
|
18447
|
+
};
|
|
18448
|
+
var mirrorTiles = {
|
|
18449
|
+
kind: "shader",
|
|
18450
|
+
id: "mirror-tiles",
|
|
18451
|
+
name: "Mirror tiles",
|
|
18452
|
+
description: "A picture folded into mirrored kaleidoscope tiles, square or hexagonal, slowly turning and drifting.",
|
|
18453
|
+
category: "distortion",
|
|
18454
|
+
video: "filter",
|
|
18455
|
+
tags: ["distortion", "kaleidoscope", "mirror", "tiles", "image"],
|
|
18456
|
+
source: glsl(
|
|
18457
|
+
`// Mirror tiles: each tile folds the picture about its centre into mirrored wedges.
|
|
18458
|
+
uniform sampler2D uImage; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.landscape} label="Image"
|
|
18459
|
+
uniform int uFold; // @control min=2 max=8 step=2 default=6 label="Mirror fold"
|
|
18460
|
+
uniform bool uHex; // @control default=false label="Hexagonal tiles"
|
|
18461
|
+
uniform float uTiles; // @control min=1 max=8 default=2 label="Tiles down"
|
|
18462
|
+
uniform float uZoom; // @control min=0.2 max=2 default=1 label="Picture scale"
|
|
18463
|
+
uniform float uSpin; // @control min=-1 max=1 default=0.1 label="Spin"
|
|
18464
|
+
uniform float uDrift; // @control min=0 max=1 default=0.2 label="Drift"
|
|
18465
|
+
${BACKDROP_CONTROLS}`,
|
|
18466
|
+
PICTURE,
|
|
18467
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18468
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y * uTiles;
|
|
18469
|
+
p += uDrift * iTime * vec2(0.13, 0.07);
|
|
18470
|
+
vec2 q;
|
|
18471
|
+
if (uHex) {
|
|
18472
|
+
vec2 s = vec2(1.0, 1.7320508);
|
|
18473
|
+
vec4 hc = floor(vec4(p, p - vec2(0.5, 0.866)) / s.xyxy) + 0.5;
|
|
18474
|
+
vec4 h = vec4(p - hc.xy * s, p - (hc.zw + vec2(0.5, 0.5)) * s);
|
|
18475
|
+
q = dot(h.xy, h.xy) < dot(h.zw, h.zw) ? h.xy : h.zw;
|
|
18476
|
+
} else {
|
|
18477
|
+
q = fract(p) - 0.5;
|
|
18478
|
+
}
|
|
18479
|
+
float sector = 6.28318 / float(uFold);
|
|
18480
|
+
float a = mod(atan(q.y, q.x) + uSpin * iTime, sector);
|
|
18481
|
+
a = min(a, sector - a);
|
|
18482
|
+
q = length(q) * vec2(cos(a), sin(a));
|
|
18483
|
+
vec2 pan = uDrift * 0.15 * vec2(sin(iTime * 0.21), cos(iTime * 0.17));
|
|
18484
|
+
fragColor = pictureRGBA(uImage, 0.5 + pan + q * uZoom);
|
|
18485
|
+
}`
|
|
18486
|
+
),
|
|
18487
|
+
presets: [
|
|
18488
|
+
{ name: "Hex jewel", values: { uHex: true, uFold: 6, uTiles: 2.5, uZoom: 0.5 } },
|
|
18489
|
+
{ name: "Four-way", values: { uFold: 4, uTiles: 2, uSpin: 0, uDrift: 0.4 } }
|
|
18490
|
+
]
|
|
18491
|
+
};
|
|
18492
|
+
var waveWarp = {
|
|
18493
|
+
kind: "shader",
|
|
18494
|
+
id: "wave-warp",
|
|
18495
|
+
name: "Wave warp",
|
|
18496
|
+
description: "A picture rippling in sine waves sideways, up and down or both, like a flag or a reflection on water, with lit crests.",
|
|
18497
|
+
category: "distortion",
|
|
18498
|
+
video: "filter",
|
|
18499
|
+
tags: ["distortion", "wave", "flag", "water", "image"],
|
|
18500
|
+
source: glsl(
|
|
18501
|
+
`// Wave warp: rows and/or columns of the picture shifted along travelling sine waves.
|
|
18502
|
+
uniform sampler2D uImage; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.landscape} label="Image"
|
|
18503
|
+
uniform bool uRows; // @control default=true label="Shift rows sideways"
|
|
18504
|
+
uniform bool uColumns; // @control default=false label="Shift columns up and down"
|
|
18505
|
+
uniform float uAmplitude; // @control min=0 max=0.08 default=0.02 label="Amplitude"
|
|
18506
|
+
uniform float uFrequency; // @control min=0.5 max=40 default=6 label="Waves across"
|
|
18507
|
+
uniform float uSpeed; // @control min=-6 max=6 default=2 label="Speed"
|
|
18508
|
+
uniform float uShade; // @control min=0 max=1 default=0.3 label="Crest shading"
|
|
18509
|
+
${BACKDROP_CONTROLS}`,
|
|
18510
|
+
PICTURE,
|
|
18511
|
+
`float wave(float x) {
|
|
18512
|
+
return sin(x) + 0.3 * sin(2.3 * x + 1.0);
|
|
18513
|
+
}
|
|
18514
|
+
float slope(float x) {
|
|
18515
|
+
return cos(x) + 0.69 * cos(2.3 * x + 1.0);
|
|
18516
|
+
}
|
|
18517
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18518
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
18519
|
+
float py = uv.y * uFrequency * 6.28318 - iTime * uSpeed;
|
|
18520
|
+
float px = uv.x * uFrequency * 6.28318 - iTime * uSpeed;
|
|
18521
|
+
vec2 offset = vec2(uRows ? wave(py) : 0.0, uColumns ? wave(px) : 0.0) * uAmplitude;
|
|
18522
|
+
float light = (uRows ? slope(py) : 0.0) + (uColumns ? slope(px) : 0.0);
|
|
18523
|
+
vec4 pic = pictureRGBA(uImage, uv + offset);
|
|
18524
|
+
float k = uShade * 0.18 * light * clamp(uAmplitude * 40.0, 0.0, 1.0);
|
|
18525
|
+
fragColor = vec4(pic.rgb * (1.0 + k) + max(k, 0.0) * 0.4, pic.a);
|
|
18526
|
+
}`
|
|
18527
|
+
),
|
|
18528
|
+
presets: [
|
|
18529
|
+
{ name: "Flag", values: { uRows: false, uColumns: true, uAmplitude: 0.03, uFrequency: 2, uSpeed: 3, uShade: 0.8 } },
|
|
18530
|
+
{ name: "Reflection", values: { uRows: true, uColumns: false, uAmplitude: 6e-3, uFrequency: 32, uSpeed: 3, uShade: 0.2 } },
|
|
18531
|
+
{ name: "Jelly", values: { uRows: true, uColumns: true, uAmplitude: 0.015, uFrequency: 3, uSpeed: 1.5 } }
|
|
18532
|
+
]
|
|
18533
|
+
};
|
|
18534
|
+
var ribbedGlass = {
|
|
18535
|
+
kind: "shader",
|
|
18536
|
+
id: "ribbed-glass",
|
|
18537
|
+
name: "Ribbed glass",
|
|
18538
|
+
description: "A picture seen through fluted glass: strips of refracted image with bright and dark rib edges, clear or frosted.",
|
|
18539
|
+
category: "distortion",
|
|
18540
|
+
video: "filter",
|
|
18541
|
+
tags: ["distortion", "glass", "fluted", "reeded", "frosted", "image"],
|
|
18542
|
+
source: glsl(
|
|
18543
|
+
`// Ribbed glass: each rib is a little cylinder lens that flips and squeezes the picture behind it.
|
|
18544
|
+
uniform sampler2D uImage; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.city} label="Image"
|
|
18545
|
+
uniform float uWidth; // @control min=4 max=120 default=28 label="Rib width (px)"
|
|
18546
|
+
uniform float uAngle; // @control min=-90 max=90 default=0 label="Rib angle (\xB0)"
|
|
18547
|
+
uniform float uRefraction; // @control min=0 max=1 default=0.5 label="Refraction"
|
|
18548
|
+
uniform float uFrost; // @control min=0 max=1 default=0.15 label="Frost"
|
|
18549
|
+
uniform float uHighlight; // @control min=0 max=1 default=0.4 label="Edge highlights"
|
|
18550
|
+
uniform vec3 uTint; // @control color default=#F2F7FA label="Glass tint"
|
|
18551
|
+
${BACKDROP_CONTROLS}`,
|
|
18552
|
+
HASH,
|
|
18553
|
+
PICTURE,
|
|
18554
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18555
|
+
float a = radians(uAngle);
|
|
18556
|
+
vec2 across = vec2(cos(a), sin(a));
|
|
18557
|
+
float s = dot(fragCoord - 0.5 * iResolution.xy, across) / uWidth;
|
|
18558
|
+
float f = fract(s) - 0.5;
|
|
18559
|
+
vec2 bend = -across * f * uWidth * 3.0 * uRefraction;
|
|
18560
|
+
// Frost: a small disc of samples, jittered per pixel.
|
|
18561
|
+
float blur = uFrost * 14.0;
|
|
18562
|
+
float spin = hash12(fragCoord) * 6.28318;
|
|
18563
|
+
vec4 acc = vec4(0.0);
|
|
18564
|
+
for (int i = 0; i < 9; i++) {
|
|
18565
|
+
float fi = float(i);
|
|
18566
|
+
float ang = fi * 2.39996 + spin;
|
|
18567
|
+
vec2 o = blur * sqrt((fi + 0.5) / 9.0) * vec2(cos(ang), sin(ang));
|
|
18568
|
+
acc += pictureRGBA(uImage, (fragCoord + bend + o) / iResolution.xy);
|
|
18569
|
+
}
|
|
18570
|
+
vec4 pic = acc / 9.0;
|
|
18571
|
+
vec3 col = pic.rgb * uTint;
|
|
18572
|
+
col = mix(col, vec3(dot(col, vec3(0.299, 0.587, 0.114))) + 0.08, uFrost * 0.2);
|
|
18573
|
+
float hi = exp(-pow((f - 0.3) * 12.0, 2.0));
|
|
18574
|
+
float lo = smoothstep(0.38, 0.5, abs(f));
|
|
18575
|
+
col = col * (1.0 - 0.25 * lo * uHighlight) + hi * uHighlight * 0.35;
|
|
18576
|
+
fragColor = vec4(col, pic.a);
|
|
18577
|
+
}`
|
|
18578
|
+
),
|
|
18579
|
+
presets: [
|
|
18580
|
+
{ name: "Reeded", values: { uWidth: 14, uRefraction: 0.7, uFrost: 0 } },
|
|
18581
|
+
{ name: "Wide flutes", values: { uWidth: 70, uRefraction: 0.4, uHighlight: 0.6 } },
|
|
18582
|
+
{ name: "Frosted diagonal", values: { uAngle: 30, uFrost: 0.8, uWidth: 36 } }
|
|
18583
|
+
]
|
|
18584
|
+
};
|
|
18585
|
+
var showerGlass = {
|
|
18586
|
+
kind: "shader",
|
|
18587
|
+
id: "shower-glass",
|
|
18588
|
+
name: "Shower glass",
|
|
18589
|
+
description: "A picture blurred and bent behind textured shower-door glass, pebbled or streaked with rain, catching small highlights.",
|
|
18590
|
+
category: "distortion",
|
|
18591
|
+
video: "filter",
|
|
18592
|
+
tags: ["distortion", "glass", "privacy", "rain", "pebble", "image"],
|
|
18593
|
+
source: glsl(
|
|
18594
|
+
`// Shower glass: a bumpy height field refracts and blurs the picture, lit from the top left.
|
|
18595
|
+
uniform sampler2D uImage; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.portrait2} label="Image"
|
|
18596
|
+
uniform bool uRain; // @control default=false label="Rain pattern (else pebbled)"
|
|
18597
|
+
uniform float uScale; // @control min=4 max=60 default=16 label="Bumps down"
|
|
18598
|
+
uniform float uRefraction; // @control min=0 max=1 default=0.5 label="Refraction"
|
|
18599
|
+
uniform float uBlur; // @control min=0 max=1 default=0.35 label="Blur"
|
|
18600
|
+
uniform float uDim; // @control min=0 max=1 default=0.15 label="Dim"
|
|
18601
|
+
uniform float uHighlight; // @control min=0 max=1 default=0.5 label="Highlights"
|
|
18602
|
+
uniform float uFlow; // @control min=0 max=1 default=0.1 label="Flow"
|
|
18603
|
+
${BACKDROP_CONTROLS}`,
|
|
18604
|
+
HASH,
|
|
18605
|
+
NOISE,
|
|
18606
|
+
PICTURE,
|
|
18607
|
+
`// Round domes on a jittered grid: 1 at a pebble's top, 0 between.
|
|
18608
|
+
float pebbles(vec2 p) {
|
|
18609
|
+
vec2 i = floor(p);
|
|
18610
|
+
vec2 f = fract(p);
|
|
18611
|
+
float d = 8.0;
|
|
18612
|
+
for (int y = -1; y <= 1; y++) {
|
|
18613
|
+
for (int x = -1; x <= 1; x++) {
|
|
18614
|
+
vec2 g = vec2(float(x), float(y));
|
|
18615
|
+
vec2 o = 0.15 + 0.7 * hash22(i + g);
|
|
18616
|
+
d = min(d, length(g + o - f));
|
|
18617
|
+
}
|
|
18618
|
+
}
|
|
18619
|
+
return 1.0 - smoothstep(0.0, 0.75, d);
|
|
18620
|
+
}
|
|
18621
|
+
float height(vec2 p) {
|
|
18622
|
+
float t = iTime * uFlow;
|
|
18623
|
+
if (uRain) {
|
|
18624
|
+
float streak = vnoise(vec2(p.x * 2.5, p.y * 0.3 + t * 2.0));
|
|
18625
|
+
return 0.55 * streak + 0.45 * pebbles(p * 1.7 + vec2(0.0, t * 3.0));
|
|
18626
|
+
}
|
|
18627
|
+
return pebbles(p + vec2(0.0, t) + 0.3 * vnoise(p * 0.7));
|
|
18628
|
+
}
|
|
18629
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18630
|
+
vec2 p = fragCoord / iResolution.y * uScale;
|
|
18631
|
+
float e = 0.03;
|
|
18632
|
+
vec2 grad = vec2(height(p + vec2(e, 0.0)) - height(p - vec2(e, 0.0)), height(p + vec2(0.0, e)) - height(p - vec2(0.0, e))) / (2.0 * e);
|
|
18633
|
+
vec2 uv = fragCoord / iResolution.xy - grad * uRefraction * 0.02;
|
|
18634
|
+
float blur = uBlur * 0.012;
|
|
18635
|
+
float spin = hash12(fragCoord) * 6.28318;
|
|
18636
|
+
vec4 acc = vec4(0.0);
|
|
18637
|
+
for (int i = 0; i < 8; i++) {
|
|
18638
|
+
float fi = float(i);
|
|
18639
|
+
float ang = fi * 2.39996 + spin;
|
|
18640
|
+
acc += pictureRGBA(uImage, uv + blur * sqrt((fi + 0.5) / 8.0) * vec2(cos(ang) * iResolution.y / iResolution.x, sin(ang)));
|
|
18641
|
+
}
|
|
18642
|
+
vec4 pic = acc / 8.0;
|
|
18643
|
+
vec3 n = normalize(vec3(-grad * 0.12, 1.0));
|
|
18644
|
+
vec3 h = normalize(normalize(vec3(-0.5, 0.6, 0.6)) + vec3(0.0, 0.0, 1.0));
|
|
18645
|
+
float spec = pow(max(dot(n, h), 0.0), 60.0);
|
|
18646
|
+
float rim = clamp(dot(n.xy, vec2(-0.6, 0.6)) * 2.0, -1.0, 1.0);
|
|
18647
|
+
vec3 col = pic.rgb * (1.0 - uDim) * (1.0 + 0.12 * rim * uHighlight) + spec * uHighlight * 0.8;
|
|
18648
|
+
fragColor = vec4(col, pic.a);
|
|
18649
|
+
}`
|
|
18650
|
+
),
|
|
18651
|
+
presets: [
|
|
18652
|
+
{ name: "Rain door", values: { uRain: true, uScale: 10, uFlow: 0.3, uBlur: 0.3 } },
|
|
18653
|
+
{ name: "Privacy", values: { uScale: 28, uRefraction: 0.8, uBlur: 0.9, uDim: 0.25 } }
|
|
18654
|
+
]
|
|
18655
|
+
};
|
|
18656
|
+
var datamosh = {
|
|
18657
|
+
kind: "shader",
|
|
18658
|
+
id: "datamosh",
|
|
18659
|
+
name: "Datamosh",
|
|
18660
|
+
description: "A picture breaking down like a corrupted video stream in bursts: macroblocks smear along a false motion field, stick in the wrong place and bleed colour.",
|
|
18661
|
+
category: "distortion",
|
|
18662
|
+
video: "filter",
|
|
18663
|
+
tags: ["distortion", "datamosh", "glitch", "compression", "image"],
|
|
18664
|
+
source: glsl(
|
|
18665
|
+
`// Datamosh: block motion vectors applied to a stale frame, stuck blocks and smeared chroma, in bursts.
|
|
18666
|
+
uniform sampler2D uImage; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.portrait} label="Image"
|
|
18667
|
+
uniform float uBlock; // @control min=8 max=64 default=24 step=1 label="Macroblock (px)"
|
|
18668
|
+
uniform float uAmount; // @control min=0 max=1 default=0.6 label="Amount"
|
|
18669
|
+
uniform float uSmear; // @control min=0 max=0.4 default=0.12 label="Smear length"
|
|
18670
|
+
uniform float uRate; // @control min=0.2 max=4 default=0.8 label="Bursts per second"
|
|
18671
|
+
uniform float uBleed; // @control min=0 max=1 default=0.6 label="Colour bleed"
|
|
18672
|
+
${BACKDROP_CONTROLS}`,
|
|
18673
|
+
HASH,
|
|
18674
|
+
NOISE,
|
|
18675
|
+
PICTURE,
|
|
18676
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18677
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
18678
|
+
float t = iTime * uRate;
|
|
18679
|
+
float epoch = floor(t);
|
|
18680
|
+
float ph = fract(t);
|
|
18681
|
+
float on = step(1.0 - uAmount * 0.85, hash12(vec2(epoch, 7.0))) * smoothstep(0.0, 0.08, ph);
|
|
18682
|
+
vec2 block = floor(fragCoord / uBlock);
|
|
18683
|
+
// One motion vector per macroblock, from a smooth field that changes each burst.
|
|
18684
|
+
vec2 field = block * 0.12 + epoch * vec2(3.1, 1.7);
|
|
18685
|
+
vec2 mv = vec2(vnoise(field), vnoise(field + 9.7)) * 2.0 - 1.0;
|
|
18686
|
+
vec2 disp = mv * uSmear * ph * on * vec2(iResolution.y / iResolution.x, 1.0);
|
|
18687
|
+
// Stuck blocks: content from the wrong place, sliding slowly.
|
|
18688
|
+
vec2 bh = hash22(block + epoch * 13.0);
|
|
18689
|
+
float stuck = on * step(1.0 - 0.3 * uAmount, bh.x);
|
|
18690
|
+
vec2 wrong = (floor(bh * 6.0) - 2.5) * uBlock / iResolution.xy;
|
|
18691
|
+
vec2 st = uv + stuck * (wrong + mv * 0.02 * ph);
|
|
18692
|
+
// Smear: samples along the motion, weighted towards the oldest.
|
|
18693
|
+
vec4 acc = vec4(0.0);
|
|
18694
|
+
float wsum = 0.0;
|
|
18695
|
+
for (int i = 0; i < 6; i++) {
|
|
18696
|
+
float k = float(i) / 5.0;
|
|
18697
|
+
float w = 0.4 + k;
|
|
18698
|
+
acc += w * pictureRGBA(uImage, st - disp * k);
|
|
18699
|
+
wsum += w;
|
|
18700
|
+
}
|
|
18701
|
+
vec4 pic = acc / wsum;
|
|
18702
|
+
// Bleed: chroma from the block's corner, dragged further along the motion.
|
|
18703
|
+
vec2 chromaUV = (block * uBlock + 0.5) / iResolution.xy - disp * 1.6;
|
|
18704
|
+
vec3 smeared = picture(uImage, chromaUV);
|
|
18705
|
+
float y = dot(pic.rgb, vec3(0.299, 0.587, 0.114));
|
|
18706
|
+
vec3 chroma = mix(pic.rgb - y, smeared - dot(smeared, vec3(0.299, 0.587, 0.114)), uBleed * on);
|
|
18707
|
+
vec3 col = y + chroma * (1.0 + 0.4 * on * uBleed);
|
|
18708
|
+
// A faint block grid where the stream is broken.
|
|
18709
|
+
vec2 edge = abs(fract(fragCoord / uBlock) - 0.5);
|
|
18710
|
+
col *= 1.0 - 0.06 * on * step(0.47, max(edge.x, edge.y));
|
|
18711
|
+
fragColor = vec4(clamp(col, 0.0, 1.0), pic.a);
|
|
18712
|
+
}`
|
|
18713
|
+
),
|
|
18714
|
+
presets: [
|
|
18715
|
+
{ name: "Melted stream", values: { uAmount: 1, uSmear: 0.3, uRate: 0.4, uBleed: 1 } },
|
|
18716
|
+
{ name: "Hiccup", values: { uAmount: 0.4, uSmear: 0.05, uRate: 2.5, uBlock: 16 } }
|
|
18717
|
+
]
|
|
18718
|
+
};
|
|
18719
|
+
var polarWarp = {
|
|
18720
|
+
kind: "shader",
|
|
18721
|
+
id: "polar-warp",
|
|
18722
|
+
name: "Polar warp",
|
|
18723
|
+
description: "A picture wrapped into a circle, as a tiny planet or a tunnel, or a circle unrolled into a strip, slowly turning.",
|
|
18724
|
+
category: "distortion",
|
|
18725
|
+
video: "filter",
|
|
18726
|
+
tags: ["distortion", "polar", "tiny-planet", "tunnel", "image"],
|
|
18727
|
+
source: glsl(
|
|
18728
|
+
`// Polar warp: rectangular \u2192 polar (the picture's bottom edge at the centre), or polar \u2192 rectangular.
|
|
18729
|
+
uniform sampler2D uImage; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.landscape} label="Image"
|
|
18730
|
+
uniform bool uUnwrap; // @control default=false label="Unwrap (polar \u2192 rectangular)"
|
|
18731
|
+
uniform bool uInsideOut; // @control default=false label="Inside out (tunnel)"
|
|
18732
|
+
uniform float uZoom; // @control min=0.3 max=3 default=1 label="Zoom"
|
|
18733
|
+
uniform float uSpin; // @control min=-0.5 max=0.5 default=0.04 label="Spin (turns/s)"
|
|
18734
|
+
uniform float uTwist; // @control min=-2 max=2 default=0 label="Twist"
|
|
18735
|
+
${BACKDROP_CONTROLS}`,
|
|
18736
|
+
PICTURE,
|
|
18737
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18738
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
18739
|
+
vec2 st;
|
|
18740
|
+
if (uUnwrap) {
|
|
18741
|
+
// Across is the angle, up is the radius of the circle inscribed in the picture.
|
|
18742
|
+
float r = (uInsideOut ? 1.0 - uv.y : uv.y) * 0.5 / uZoom;
|
|
18743
|
+
float a = (uv.x + iTime * uSpin + uTwist * r) * 6.28318;
|
|
18744
|
+
st = 0.5 + r * vec2(cos(a), sin(a));
|
|
18745
|
+
} else {
|
|
18746
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
|
|
18747
|
+
float r = length(p) * 2.0 / uZoom;
|
|
18748
|
+
// The angle runs across the picture and back, so the seam is a mirror and does not show.
|
|
18749
|
+
float u = fract(atan(p.y, p.x) / 6.28318 + iTime * uSpin + uTwist * r * 0.25);
|
|
18750
|
+
u = 1.0 - abs(1.0 - 2.0 * u);
|
|
18751
|
+
float v = uInsideOut ? 1.0 - min(r, 1.0) : min(r, 1.0);
|
|
18752
|
+
st = vec2(u, v);
|
|
18753
|
+
}
|
|
18754
|
+
fragColor = pictureRGBA(uImage, st);
|
|
18755
|
+
}`
|
|
18756
|
+
),
|
|
18757
|
+
presets: [
|
|
18758
|
+
{ name: "Tiny planet", values: { uUnwrap: false, uInsideOut: false, uZoom: 1.1, uSpin: 0.03 } },
|
|
18759
|
+
{ name: "Tunnel", values: { uUnwrap: false, uInsideOut: true, uZoom: 0.9, uSpin: -0.05, uTwist: 0.8 } },
|
|
18760
|
+
{ name: "Unrolled", values: { uUnwrap: true, uZoom: 1, uSpin: 0.02 } }
|
|
18761
|
+
]
|
|
18762
|
+
};
|
|
18763
|
+
var textureDisplace = {
|
|
18764
|
+
kind: "shader",
|
|
18765
|
+
id: "texture-displace",
|
|
18766
|
+
name: "Texture displace",
|
|
18767
|
+
description: "A picture pushed around by a second, scrolling texture, by its red and green or by its light and shade.",
|
|
18768
|
+
category: "distortion",
|
|
18769
|
+
video: "filter",
|
|
18770
|
+
tags: ["distortion", "displacement", "map", "image"],
|
|
18771
|
+
source: glsl(
|
|
18772
|
+
`// Texture displace: each pixel moved by a displacement map (or its brightness slope) that scrolls.
|
|
18773
|
+
uniform sampler2D uImage; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.city} label="Image"
|
|
18774
|
+
uniform sampler2D uMap; // @control filter=linear wrap=mirror default=${SAMPLE_IMAGES.landscape} label="Displacement map"
|
|
18775
|
+
uniform float uStrength; // @control min=0 max=0.2 default=0.06 label="Strength"
|
|
18776
|
+
uniform float uScale; // @control min=0.2 max=4 default=1.5 label="Map scale"
|
|
18777
|
+
uniform vec2 uScroll; // @control min=-0.3 max=0.3 default=0.03,0.02 label="Scroll"
|
|
18778
|
+
uniform bool uSlope; // @control default=false label="Push by brightness slope (else by red/green)"
|
|
18779
|
+
${BACKDROP_CONTROLS}`,
|
|
18780
|
+
HASH,
|
|
18781
|
+
NOISE,
|
|
18782
|
+
PICTURE,
|
|
18783
|
+
`// The map, or soft noise while there is none (an empty image control is a 1 \xD7 1 placeholder).
|
|
18784
|
+
vec3 mapAt(vec2 p) {
|
|
18785
|
+
if (textureSize(uMap, 0).x > 1) return texture(uMap, p).rgb;
|
|
18786
|
+
return vec3(vnoise(p * 6.0), vnoise(p * 6.0 + 11.3), vnoise(p * 6.0 + 23.1));
|
|
18787
|
+
}
|
|
18788
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
18789
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
18790
|
+
vec2 m = uv * uScale + uScroll * iTime;
|
|
18791
|
+
vec2 offset;
|
|
18792
|
+
if (uSlope) {
|
|
18793
|
+
float e = 0.03;
|
|
18794
|
+
vec3 w = vec3(0.299, 0.587, 0.114);
|
|
18795
|
+
float dx = dot(mapAt(m + vec2(e, 0.0)) - mapAt(m - vec2(e, 0.0)), w);
|
|
18796
|
+
float dy = dot(mapAt(m + vec2(0.0, e)) - mapAt(m - vec2(0.0, e)), w);
|
|
18797
|
+
offset = vec2(dx, dy) / (2.0 * e) * uStrength * 0.1;
|
|
18798
|
+
} else {
|
|
18799
|
+
offset = (mapAt(m).rg - 0.5) * 2.0 * uStrength;
|
|
18800
|
+
}
|
|
18801
|
+
fragColor = pictureRGBA(uImage, uv + offset);
|
|
18802
|
+
}`
|
|
18803
|
+
),
|
|
18804
|
+
presets: [
|
|
18805
|
+
{ name: "Embossed", values: { uSlope: true, uStrength: 0.08, uScale: 1, uScroll: [0, 0] } },
|
|
18806
|
+
{ name: "Drifting channels", values: { uSlope: false, uStrength: 0.03, uScale: 2, uScroll: [0.05, -0.04] } }
|
|
18807
|
+
]
|
|
18808
|
+
};
|
|
18809
|
+
var distortionShaders = [
|
|
18810
|
+
twirl,
|
|
18811
|
+
pinchBulge,
|
|
18812
|
+
fisheye,
|
|
18813
|
+
mirrorTiles,
|
|
18814
|
+
waveWarp,
|
|
18815
|
+
ribbedGlass,
|
|
18816
|
+
showerGlass,
|
|
18817
|
+
datamosh,
|
|
18818
|
+
polarWarp,
|
|
18819
|
+
textureDisplace
|
|
18820
|
+
];
|
|
18821
|
+
|
|
18822
|
+
// src/catalog/types.ts
|
|
18823
|
+
var EFFECT_CATEGORIES = [
|
|
18824
|
+
"gradient",
|
|
18825
|
+
"organic",
|
|
18826
|
+
"pattern",
|
|
18827
|
+
"light",
|
|
18828
|
+
"space",
|
|
18829
|
+
"retro",
|
|
18830
|
+
"image",
|
|
18831
|
+
"overlay",
|
|
18832
|
+
"text",
|
|
18833
|
+
"morph",
|
|
18834
|
+
"interaction",
|
|
18835
|
+
"pyrotechnics",
|
|
18836
|
+
"timewarp",
|
|
18837
|
+
"melt",
|
|
18838
|
+
"electric",
|
|
18839
|
+
"splatter",
|
|
18840
|
+
"bubble",
|
|
18841
|
+
"sticker",
|
|
18842
|
+
"trail",
|
|
18843
|
+
"particles",
|
|
18844
|
+
"debris",
|
|
18845
|
+
"impact",
|
|
18846
|
+
"comic",
|
|
18847
|
+
"atmosphere",
|
|
18848
|
+
"glow",
|
|
18849
|
+
"shadow",
|
|
18850
|
+
"distortion",
|
|
18851
|
+
"transition",
|
|
18852
|
+
"loader",
|
|
18853
|
+
"data",
|
|
18854
|
+
"icon"
|
|
18855
|
+
];
|
|
18856
|
+
var VIDEO_USES = ["filter", "transition", "interactive", "stills", "none"];
|
|
18857
|
+
var VECTOR_CONTROL_TYPES = ["float", "int", "bool", "color", "text"];
|
|
18858
|
+
var PLACEHOLDER = /\{\{\s*([A-Za-z_]\w*)\s*(?::\s*([A-Za-z0-9]*)([^}]*))?\}\}/g;
|
|
18859
|
+
function motionStrings(motion) {
|
|
18860
|
+
const out = [];
|
|
18861
|
+
const walk = (v) => {
|
|
18862
|
+
if (typeof v === "string") out.push(v);
|
|
18863
|
+
else if (Array.isArray(v)) v.forEach(walk);
|
|
18864
|
+
else if (v && typeof v === "object") Object.values(v).forEach(walk);
|
|
18865
|
+
};
|
|
18866
|
+
walk(motion);
|
|
18867
|
+
return out;
|
|
18868
|
+
}
|
|
18869
|
+
function vectorControls(motion) {
|
|
18870
|
+
const controls = [];
|
|
18871
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18872
|
+
for (const text of motionStrings(motion)) {
|
|
18873
|
+
for (const [, name, type, attributes] of text.matchAll(PLACEHOLDER)) {
|
|
18874
|
+
if (type === void 0 || seen.has(name) || !VECTOR_CONTROL_TYPES.includes(type)) continue;
|
|
18875
|
+
seen.add(name);
|
|
18876
|
+
const colour = type === "color";
|
|
18877
|
+
controls.push(controlFromDeclaration(name, colour ? "vec3" : type, `${attributes}${colour ? " color" : ""}`, controls.length + 1));
|
|
18878
|
+
}
|
|
18879
|
+
}
|
|
18880
|
+
return controls;
|
|
18881
|
+
}
|
|
18882
|
+
function vectorEffectControls(effect) {
|
|
18883
|
+
const declared = vectorControls(effect.motion);
|
|
18884
|
+
const names = new Set(declared.map((c) => c.name));
|
|
18885
|
+
const listed = (effect.controls ?? []).filter((c) => !names.has(c.name));
|
|
18886
|
+
return [...declared, ...listed.map((c, i) => vectorControlDef(c, declared.length + i + 1))];
|
|
18887
|
+
}
|
|
18888
|
+
function vectorControlIssues(motion) {
|
|
18889
|
+
const issues = [];
|
|
18890
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
16120
18891
|
for (const text of motionStrings(motion)) {
|
|
16121
18892
|
for (const [whole, name, type] of text.matchAll(PLACEHOLDER)) {
|
|
16122
18893
|
if (type === void 0) continue;
|
|
@@ -16194,14 +18965,14 @@ function numberControl(name, value, min, max, label, step) {
|
|
|
16194
18965
|
}
|
|
16195
18966
|
|
|
16196
18967
|
// src/catalog/text.ts
|
|
16197
|
-
var
|
|
16198
|
-
function
|
|
18968
|
+
var r110 = (n11) => Math.round(n11 * 10) / 10;
|
|
18969
|
+
function fit5(text, width, max, ems = textWidthEm(text)) {
|
|
16199
18970
|
const e = Math.max(0.6, ems);
|
|
16200
18971
|
const size = Math.min(max, width / e);
|
|
16201
|
-
return { size:
|
|
18972
|
+
return { size: r110(size), length: r110(e * size), left: r110(200 - e * size / 2) };
|
|
16202
18973
|
}
|
|
16203
18974
|
var lettersOf = (text) => Array.from(String(text));
|
|
16204
|
-
var
|
|
18975
|
+
var mixHex3 = (a, b, t) => {
|
|
16205
18976
|
if (isCurrentColor(a) || isCurrentColor(b)) {
|
|
16206
18977
|
const css = (c) => isCurrentColor(c) ? CURRENT_COLOR : rgbToHex(Array.isArray(c) ? c : [1, 1, 1]);
|
|
16207
18978
|
return `color-mix(in srgb, ${css(a)}, ${css(b)} ${Math.round(t * 100)}%)`;
|
|
@@ -16211,17 +18982,17 @@ var mixHex2 = (a, b, t) => {
|
|
|
16211
18982
|
return rgbToHex([0, 1, 2].map((i) => x[i] + (y[i] - x[i]) * t));
|
|
16212
18983
|
};
|
|
16213
18984
|
var holds = (id, attr, keys2) => keys2.map(([delay, value]) => ({ id, attrs: { [attr]: [value, value] }, delay, ms: 0 }));
|
|
16214
|
-
var
|
|
18985
|
+
var n10 = (name) => `{{${name}}}`;
|
|
16215
18986
|
var MONO = `ui-monospace, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace`;
|
|
16216
18987
|
var MONO_EM = 0.6;
|
|
16217
18988
|
var HEAVY2 = `'Arial Black', 'Helvetica Neue', Impact, system-ui, sans-serif`;
|
|
16218
|
-
var
|
|
18989
|
+
var SANS7 = `system-ui, 'Helvetica Neue', Arial, sans-serif`;
|
|
16219
18990
|
var SCRIPT = `'Pacifico', 'Brush Script MT', 'Segoe Script', 'Snell Roundhand', cursive`;
|
|
16220
18991
|
var CAP = 0.72;
|
|
16221
18992
|
var typeface = (font, weight) => `{{font: text default="${font}" label='Font'}} {{weight: int min=100 max=900 default=${weight} step=100 label='Weight'}} {{inline: bool default=false label='Inline'}}`;
|
|
16222
18993
|
function frame(v, left, width, base, size, overhang = [0.05, 0, DESCENT, 0]) {
|
|
16223
18994
|
const spill = overhang.map((e) => Math.round(Math.max(0, e) * 100) / 100).join(" ");
|
|
16224
|
-
return v.inline ? { viewBox: `${
|
|
18995
|
+
return v.inline ? { viewBox: `${r110(left)} ${r110(base - size * CAP)} ${r110(width)} ${r110(size * CAP)}`, overflow: "visible", backdrop: 0, overhang: spill } : { viewBox: "0 0 400 200", overflow: "hidden", backdrop: 1, overhang: spill };
|
|
16225
18996
|
}
|
|
16226
18997
|
var DESCENT = 0.25;
|
|
16227
18998
|
var glowing = (overhang, by) => overhang.map((e) => e + by);
|
|
@@ -16242,16 +19013,16 @@ var typewriter = {
|
|
|
16242
19013
|
// Monospace, so the caret lands after the last letter however long the text is.
|
|
16243
19014
|
derived: (v) => {
|
|
16244
19015
|
const count = Math.max(1, lettersOf(v.text).length);
|
|
16245
|
-
const size =
|
|
16246
|
-
const left =
|
|
19016
|
+
const size = r110(Math.min(44, room(v, 340) / ((count + 1) * MONO_EM)));
|
|
19017
|
+
const left = r110(200 - (count + 1) * MONO_EM * size / 2);
|
|
16247
19018
|
const typing = Math.round(count * Number(v.pace));
|
|
16248
|
-
const base =
|
|
19019
|
+
const base = r110(100 + size * 0.35);
|
|
16249
19020
|
return {
|
|
16250
19021
|
...frame(v, left, (count + 1) * MONO_EM * size, base, size, [0.08, 0, DESCENT, 0]),
|
|
16251
19022
|
size,
|
|
16252
19023
|
left,
|
|
16253
19024
|
base,
|
|
16254
|
-
end:
|
|
19025
|
+
end: r110(left + count * MONO_EM * size),
|
|
16255
19026
|
typing,
|
|
16256
19027
|
...Object.fromEntries(Array.from({ length: BLINKS }, (_, k) => [`blink${k}`, typing + k * 500]))
|
|
16257
19028
|
};
|
|
@@ -16259,9 +19030,9 @@ var typewriter = {
|
|
|
16259
19030
|
motion: {
|
|
16260
19031
|
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Hello, world.' label='Text'}} {{paper: color default=#111827 label='Background'}} {{ink: color default=#F9FAFB label='Text colour'}} {{pace: float min=30 max=300 default=90 step=5 label='Ms per letter'}} ${typeface(MONO, 400)} --><rect width="400" height="200" fill="{{paper}}" opacity="{{backdrop}}"/><text id="typed" x="{{left}}" y="{{base}}" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" xml:space="preserve">{{text}}</text><text id="caret" x="{{end}}" y="{{base}}" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" opacity="0">\u258C</text></svg>`,
|
|
16261
19032
|
animate: [
|
|
16262
|
-
{ id: "typed", write: "{{text}}", caret: "\u258C", ms:
|
|
19033
|
+
{ id: "typed", write: "{{text}}", caret: "\u258C", ms: n10("typing"), ease: "linear" },
|
|
16263
19034
|
// Hidden while the writer shows its own caret, then blinking where it stopped.
|
|
16264
|
-
...holds("caret", "opacity", [[0, 0], ...Array.from({ length: BLINKS }, (_, k) => [
|
|
19035
|
+
...holds("caret", "opacity", [[0, 0], ...Array.from({ length: BLINKS }, (_, k) => [n10(`blink${k}`), k % 2 ? 0 : 1])])
|
|
16265
19036
|
]
|
|
16266
19037
|
}
|
|
16267
19038
|
};
|
|
@@ -16279,17 +19050,17 @@ var scrambleText = {
|
|
|
16279
19050
|
],
|
|
16280
19051
|
derived: (v) => {
|
|
16281
19052
|
const count = Math.max(1, lettersOf(v.text).length);
|
|
16282
|
-
const size =
|
|
16283
|
-
const width =
|
|
16284
|
-
const base =
|
|
16285
|
-
return { size, base, barX:
|
|
19053
|
+
const size = r110(Math.min(60, room(v, 360) / (count * MONO_EM)));
|
|
19054
|
+
const width = r110(count * MONO_EM * size);
|
|
19055
|
+
const base = r110(100 + size * 0.35);
|
|
19056
|
+
return { size, base, barX: r110(200 - width / 2), barY: r110(base + size * 0.3), barW: width, decode: Math.round(count * Number(v.pace)), glowWide: Number(v.glow) * 2, ...frame(v, 200 - width / 2, width, base, size, glowing([0.08, 0, Math.max(DESCENT, 0.3 + 3 / size), 0], Number(v.glow) * 4 / size)) };
|
|
16286
19057
|
},
|
|
16287
19058
|
motion: {
|
|
16288
19059
|
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='ACCESS GRANTED' label='Text'}} {{glyphs: text default='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&@$*+' label='Scramble glyphs'}} {{background: color default=#05070D label='Background'}} {{ink: color default=#E2E8F0 label='Text colour'}} {{accent: color default=#22D3EE label='Accent'}} {{pace: float min=20 max=250 default=70 step=5 label='Ms per letter'}} {{glow: float min=0 max=6 default=2 step=0.5 label='Glow'}} ${typeface(MONO, 700)} --><defs><filter id="glow" x="-20%" y="-50%" width="140%" height="200%"><feGaussianBlur stdDeviation="{{glowWide}}" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text id="word" x="200" y="{{base}}" text-anchor="middle" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" filter="url(#glow)" xml:space="preserve">{{text}}</text><rect id="bar" x="{{barX}}" y="{{barY}}" width="0" height="3" fill="{{accent}}"/></svg>`,
|
|
16289
19060
|
animate: [
|
|
16290
19061
|
// Pure noise for a moment (the scramble runs before the change starts), then it decodes.
|
|
16291
|
-
{ id: "word", write: "{{text}}", scramble: "{{glyphs}}", delay: 500, ms:
|
|
16292
|
-
{ id: "bar", attrs: { width: [0,
|
|
19062
|
+
{ id: "word", write: "{{text}}", scramble: "{{glyphs}}", delay: 500, ms: n10("decode"), ease: "linear" },
|
|
19063
|
+
{ id: "bar", attrs: { width: [0, n10("barW")] }, delay: 500, ms: n10("decode"), ease: "linear" }
|
|
16293
19064
|
]
|
|
16294
19065
|
}
|
|
16295
19066
|
};
|
|
@@ -16316,20 +19087,20 @@ var kineticStagger = {
|
|
|
16316
19087
|
const stagger = Number(v.stagger);
|
|
16317
19088
|
const out = {
|
|
16318
19089
|
...frame(v, 200 - ems * size / 2, ems * size, base, size, [0.15, 0, Math.max(DESCENT, 0.2 + 6 / size), 0]),
|
|
16319
|
-
size:
|
|
16320
|
-
base:
|
|
16321
|
-
rise:
|
|
16322
|
-
drop:
|
|
16323
|
-
lineX:
|
|
16324
|
-
lineY:
|
|
16325
|
-
lineW:
|
|
19090
|
+
size: r110(size),
|
|
19091
|
+
base: r110(base),
|
|
19092
|
+
rise: r110(base - size * 0.1),
|
|
19093
|
+
drop: r110(base + size * 0.6),
|
|
19094
|
+
lineX: r110(200 - ems * size / 2),
|
|
19095
|
+
lineY: r110(base + size * 0.2),
|
|
19096
|
+
lineW: r110(ems * size),
|
|
16326
19097
|
lineAt: Math.round(letters.length * stagger + 400)
|
|
16327
19098
|
};
|
|
16328
19099
|
let x = 200 - ems * size / 2;
|
|
16329
19100
|
for (let i = 0; i < SLOTS2; i++) {
|
|
16330
19101
|
const w = (widths[i] ?? 0) * size;
|
|
16331
19102
|
out[`c${i}`] = letters[i]?.trim() ?? "";
|
|
16332
|
-
out[`x${i}`] =
|
|
19103
|
+
out[`x${i}`] = r110(x + w / 2);
|
|
16333
19104
|
out[`d${i}`] = Math.round(i * stagger);
|
|
16334
19105
|
out[`e${i}`] = Math.round(i * stagger + 380);
|
|
16335
19106
|
x += w;
|
|
@@ -16346,13 +19117,13 @@ var kineticStagger = {
|
|
|
16346
19117
|
{
|
|
16347
19118
|
id: `l${i}`,
|
|
16348
19119
|
attrs: { transform: [`translate({{x${i}}} {{drop}}) rotate(-28) scale(0.3)`, `translate({{x${i}}} {{rise}}) rotate(5) scale(1.12)`], opacity: [0, 1] },
|
|
16349
|
-
delay:
|
|
19120
|
+
delay: n10(`d${i}`),
|
|
16350
19121
|
ms: 380,
|
|
16351
19122
|
ease: "out"
|
|
16352
19123
|
},
|
|
16353
|
-
{ id: `l${i}`, attrs: { transform: [`translate({{x${i}}} {{rise}}) rotate(5) scale(1.12)`, `translate({{x${i}}} {{base}}) rotate(0) scale(1)`] }, delay:
|
|
19124
|
+
{ id: `l${i}`, attrs: { transform: [`translate({{x${i}}} {{rise}}) rotate(5) scale(1.12)`, `translate({{x${i}}} {{base}}) rotate(0) scale(1)`] }, delay: n10(`e${i}`), ms: 260, ease: "in-out" }
|
|
16354
19125
|
]).flat(),
|
|
16355
|
-
{ id: "line", attrs: { width: [0,
|
|
19126
|
+
{ id: "line", attrs: { width: [0, n10("lineW")] }, delay: n10("lineAt"), ms: 500, ease: "in-out" }
|
|
16356
19127
|
]
|
|
16357
19128
|
}
|
|
16358
19129
|
};
|
|
@@ -16373,18 +19144,18 @@ var neonSign = {
|
|
|
16373
19144
|
derived: (v) => {
|
|
16374
19145
|
const letters = lettersOf(v.text);
|
|
16375
19146
|
const k = Math.min(Math.max(0, Math.round(Number(v.faulty)) - 1), Math.max(0, letters.length - 1));
|
|
16376
|
-
const { size, length, left } =
|
|
19147
|
+
const { size, length, left } = fit5(String(v.text), room(v, 340), 84);
|
|
16377
19148
|
return {
|
|
16378
19149
|
...frame(v, left, length, 100 + size * 0.3, size, glowing([0.1, 0, DESCENT, 0], Number(v.glow) * 6 / size)),
|
|
16379
|
-
hazeX:
|
|
19150
|
+
hazeX: r110(Math.max(170, length / 2 + 30)),
|
|
16380
19151
|
head: letters.slice(0, k).join(""),
|
|
16381
19152
|
mid: letters[k] ?? "",
|
|
16382
19153
|
tail: letters.slice(k + 1).join(""),
|
|
16383
19154
|
size,
|
|
16384
19155
|
length,
|
|
16385
|
-
base:
|
|
19156
|
+
base: r110(100 + size * 0.3),
|
|
16386
19157
|
far: Number(v.glow) * 3,
|
|
16387
|
-
core:
|
|
19158
|
+
core: mixHex3(v.tube, [1, 1, 1], 0.6)
|
|
16388
19159
|
};
|
|
16389
19160
|
},
|
|
16390
19161
|
motion: (() => {
|
|
@@ -16416,15 +19187,15 @@ var strokeDrawText = {
|
|
|
16416
19187
|
],
|
|
16417
19188
|
// <text> takes no pathLength, so `draw` cannot measure it: the dash grows instead, longer than any letter's outline.
|
|
16418
19189
|
derived: (v) => {
|
|
16419
|
-
const { size, length, left } =
|
|
19190
|
+
const { size, length, left } = fit5(String(v.text), room(v, 360), 100);
|
|
16420
19191
|
const edge = Number(v.width) / 2 / size;
|
|
16421
|
-
return { size, length, base:
|
|
19192
|
+
return { size, length, base: r110(100 + size * 0.35), dash: Math.round(size * 6), ...frame(v, left, length, 100 + size * 0.35, size, glowing([0.05, 0, DESCENT, 0], edge)) };
|
|
16422
19193
|
},
|
|
16423
19194
|
motion: {
|
|
16424
19195
|
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Draw' label='Text'}} {{background: color default=#FFFBF5 label='Background'}} {{outline: color default=#1E293B label='Outline'}} {{fill: color default=#F97316 label='Fill'}} {{width: float min=0.5 max=6 default=2 step=0.5 label='Outline width'}} {{fillOpacity: float min=0 max=1 default=1 label='Fill opacity'}} ${typeface(HEAVY2, 900)} --><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text id="word" x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{fill}}" fill-opacity="0" stroke="{{outline}}" stroke-width="{{width}}" stroke-linejoin="round" stroke-dasharray="0 {{dash}}">{{text}}</text></svg>`,
|
|
16425
19196
|
animate: [
|
|
16426
19197
|
{ id: "word", attrs: { "stroke-dasharray": ["0 {{dash}}", "{{dash}} 0"] }, ms: 2e3, ease: "in-out" },
|
|
16427
|
-
{ id: "word", attrs: { "fill-opacity": [0,
|
|
19198
|
+
{ id: "word", attrs: { "fill-opacity": [0, n10("fillOpacity")] }, delay: 1500, ms: 700, ease: "out" }
|
|
16428
19199
|
]
|
|
16429
19200
|
}
|
|
16430
19201
|
};
|
|
@@ -16441,22 +19212,22 @@ var splitReveal = {
|
|
|
16441
19212
|
{ name: "Gold on black", values: { background: [0.02, 0.02, 0.02], ink: [0.96, 0.84, 0.55], line: [0.96, 0.84, 0.55] } }
|
|
16442
19213
|
],
|
|
16443
19214
|
derived: (v) => {
|
|
16444
|
-
const { size, length, left } =
|
|
19215
|
+
const { size, length, left } = fit5(String(v.text), room(v, 360), 88);
|
|
16445
19216
|
return {
|
|
16446
19217
|
...frame(v, left, length, 100 + size * 0.36, size, [0.05, 10 / size, Math.max(DESCENT, 34 / size), 10 / size]),
|
|
16447
19218
|
size,
|
|
16448
19219
|
length,
|
|
16449
|
-
base:
|
|
16450
|
-
shift:
|
|
16451
|
-
lineX1:
|
|
16452
|
-
lineX2:
|
|
16453
|
-
captionY:
|
|
19220
|
+
base: r110(100 + size * 0.36),
|
|
19221
|
+
shift: r110(size * 0.8),
|
|
19222
|
+
lineX1: r110(left - 10),
|
|
19223
|
+
lineX2: r110(400 - left + 10),
|
|
19224
|
+
captionY: r110(100 + size * 0.36 + 30)
|
|
16454
19225
|
};
|
|
16455
19226
|
},
|
|
16456
19227
|
motion: (() => {
|
|
16457
19228
|
const word = `<text x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}">{{text}}</text>`;
|
|
16458
19229
|
return {
|
|
16459
|
-
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Chapter One' label='Text'}} {{caption: text default='The beginning' label='Caption'}} {{background: color default=#0F172A label='Background'}} {{ink: color default=#F8FAFC label='Text colour'}} {{line: color default=#38BDF8 label='Line'}} ${typeface(
|
|
19230
|
+
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Chapter One' label='Text'}} {{caption: text default='The beginning' label='Caption'}} {{background: color default=#0F172A label='Background'}} {{ink: color default=#F8FAFC label='Text colour'}} {{line: color default=#38BDF8 label='Line'}} ${typeface(SANS7, 800)} --><defs><clipPath id="above"><rect x="-5000" y="-5000" width="10400" height="5100"/></clipPath><clipPath id="below"><rect x="-5000" y="100" width="10400" height="5100"/></clipPath></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><g clip-path="url(#above)"><g id="top" transform="translate(0 {{shift}})">${word}</g></g><g clip-path="url(#below)"><g id="bottom" transform="translate(0 -{{shift}})">${word}</g></g><path id="line" d="M{{lineX1}} 100 H{{lineX2}}" stroke="{{line}}" stroke-width="2.5" stroke-linecap="round"/><text id="caption" x="200" y="{{captionY}}" text-anchor="middle" font-family="{{font}}" font-size="13" letter-spacing="3" fill="{{ink}}" opacity="0">{{caption}}</text></svg>`,
|
|
16460
19231
|
animate: [
|
|
16461
19232
|
{ id: "line", draw: true, ms: 600, ease: "in-out" },
|
|
16462
19233
|
{ id: "top", attrs: { transform: ["translate(0 {{shift}})", "translate(0 0)"] }, delay: 450, ms: 800, ease: "out" },
|
|
@@ -16482,7 +19253,7 @@ var highlighter = {
|
|
|
16482
19253
|
],
|
|
16483
19254
|
// The swipe runs a little past the phrase at both ends, across its lower half, with a ragged second pass.
|
|
16484
19255
|
derived: (v) => {
|
|
16485
|
-
const { size, length, left } =
|
|
19256
|
+
const { size, length, left } = fit5(String(v.text), room(v, 330), 56);
|
|
16486
19257
|
const base = 100 + size * 0.35;
|
|
16487
19258
|
const x0 = left - size * 0.25;
|
|
16488
19259
|
const x1 = 400 - left + size * 0.2;
|
|
@@ -16491,15 +19262,15 @@ var highlighter = {
|
|
|
16491
19262
|
...frame(v, left, length, base, size, [0.05, 0.2, DESCENT, 0.25]),
|
|
16492
19263
|
size,
|
|
16493
19264
|
length,
|
|
16494
|
-
base:
|
|
16495
|
-
swipe: `M${
|
|
16496
|
-
ragged: `M${
|
|
16497
|
-
thick:
|
|
16498
|
-
thin:
|
|
19265
|
+
base: r110(base),
|
|
19266
|
+
swipe: `M${r110(x0)} ${r110(y + size * 0.05)} Q${r110((x0 + x1) / 2)} ${r110(y - size * 0.06)} ${r110(x1)} ${r110(y - size * 0.02)}`,
|
|
19267
|
+
ragged: `M${r110(x0 + size * 0.15)} ${r110(y + size * 0.3)} L${r110(x1 - size * 0.1)} ${r110(y + size * 0.26)}`,
|
|
19268
|
+
thick: r110(size * 0.72),
|
|
19269
|
+
thin: r110(size * 0.16)
|
|
16499
19270
|
};
|
|
16500
19271
|
},
|
|
16501
19272
|
motion: {
|
|
16502
|
-
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Ship it faster' label='Phrase'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#111827 label='Text colour'}} {{marker: color default=#FDE047 label='Marker'}} ${typeface(
|
|
19273
|
+
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Ship it faster' label='Phrase'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#111827 label='Text colour'}} {{marker: color default=#FDE047 label='Marker'}} ${typeface(SANS7, 700)} --><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><g transform="rotate(-1.2 200 100)" opacity="0.9"><path id="swipe" d="{{swipe}}" fill="none" stroke="{{marker}}" stroke-width="{{thick}}"/><path id="ragged" d="{{ragged}}" fill="none" stroke="{{marker}}" stroke-width="{{thin}}" stroke-linecap="round"/></g><text id="phrase" x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" opacity="0">{{text}}</text></svg>`,
|
|
16503
19274
|
animate: [
|
|
16504
19275
|
{ id: "phrase", attrs: { opacity: [0, 1] }, ms: 300, ease: "out" },
|
|
16505
19276
|
{ id: "swipe", draw: true, delay: 350, ms: 650, ease: "in-out" },
|
|
@@ -16536,28 +19307,28 @@ var wordCycle = {
|
|
|
16536
19307
|
return {
|
|
16537
19308
|
...frame(v, left, total * size, base, size, [0.08, 0, Math.max(DESCENT, 0.18 + 4 / size), 0]),
|
|
16538
19309
|
bold: Math.min(900, Number(v.weight) + 100),
|
|
16539
|
-
size:
|
|
16540
|
-
base:
|
|
16541
|
-
px:
|
|
16542
|
-
plen:
|
|
16543
|
-
wx:
|
|
16544
|
-
clipX:
|
|
16545
|
-
clipY:
|
|
16546
|
-
clipW:
|
|
16547
|
-
clipH:
|
|
16548
|
-
underY:
|
|
19310
|
+
size: r110(size),
|
|
19311
|
+
base: r110(base),
|
|
19312
|
+
px: r110(left),
|
|
19313
|
+
plen: r110(Math.max(0.6, lead) * size),
|
|
19314
|
+
wx: r110(wx),
|
|
19315
|
+
clipX: r110(wx - 4),
|
|
19316
|
+
clipY: r110(base - size * 1),
|
|
19317
|
+
clipW: r110(Math.max(...ems) * size + 8),
|
|
19318
|
+
clipH: r110(size * 1.32),
|
|
19319
|
+
underY: r110(base + size * 0.18),
|
|
16549
19320
|
...Object.fromEntries(
|
|
16550
19321
|
[...words, words[0]].flatMap((_, k) => [
|
|
16551
|
-
[`y${k}`,
|
|
16552
|
-
[`o${k}`,
|
|
16553
|
-
[`len${k}`,
|
|
19322
|
+
[`y${k}`, r110(base + k * step)],
|
|
19323
|
+
[`o${k}`, r110(k * step)],
|
|
19324
|
+
[`len${k}`, r110(ems[k % CYCLE_WORDS] * size)]
|
|
16554
19325
|
])
|
|
16555
19326
|
),
|
|
16556
19327
|
...Object.fromEntries(words.map((w, k) => [`w${k}`, w]))
|
|
16557
19328
|
};
|
|
16558
19329
|
},
|
|
16559
19330
|
motion: {
|
|
16560
|
-
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{prefix: text default='We build' label='Lead-in'}} {{word1: text default=apps label='Word 1'}} {{word2: text default=brands label='Word 2'}} {{word3: text default=websites label='Word 3'}} {{word4: text default=games label='Word 4'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#0F172A label='Text colour'}} {{accent: color default=#6366F1 label='Accent'}} ${typeface(
|
|
19331
|
+
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{prefix: text default='We build' label='Lead-in'}} {{word1: text default=apps label='Word 1'}} {{word2: text default=brands label='Word 2'}} {{word3: text default=websites label='Word 3'}} {{word4: text default=games label='Word 4'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#0F172A label='Text colour'}} {{accent: color default=#6366F1 label='Accent'}} ${typeface(SANS7, 700)} --><defs><clipPath id="window"><rect x="{{clipX}}" y="{{clipY}}" width="{{clipW}}" height="{{clipH}}"/></clipPath></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text x="{{px}}" y="{{base}}" textLength="{{plen}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}">{{prefix}}</text><g clip-path="url(#window)"><g id="reel" transform="translate(0 -0)">${Array.from(
|
|
16561
19332
|
{ length: CYCLE_WORDS + 1 },
|
|
16562
19333
|
(_, k) => `<text x="{{wx}}" y="{{y${k}}}" textLength="{{len${k}}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{bold}}" fill="{{accent}}">{{w${k % CYCLE_WORDS}}}</text>`
|
|
16563
19334
|
).join("")}</g></g><rect id="under" x="{{wx}}" y="{{underY}}" width="{{len0}}" height="4" rx="2" fill="{{accent}}"/></svg>`,
|
|
@@ -16565,7 +19336,7 @@ var wordCycle = {
|
|
|
16565
19336
|
const at3 = { delay: (i + 1) * SHOW_MS + i * ROLL_MS, ms: ROLL_MS, ease: "in-out" };
|
|
16566
19337
|
return [
|
|
16567
19338
|
{ id: "reel", attrs: { transform: [`translate(0 -{{o${i}}})`, `translate(0 -{{o${i + 1}}})`] }, ...at3 },
|
|
16568
|
-
{ id: "under", attrs: { width: [
|
|
19339
|
+
{ id: "under", attrs: { width: [n10(`len${i}`), n10(`len${i + 1}`)] }, ...at3 }
|
|
16569
19340
|
];
|
|
16570
19341
|
}).flat()
|
|
16571
19342
|
}
|
|
@@ -16586,9 +19357,9 @@ var liquidFill = {
|
|
|
16586
19357
|
],
|
|
16587
19358
|
// The surface starts under the letters and ends above their caps.
|
|
16588
19359
|
derived: (v) => {
|
|
16589
|
-
const { size, length, left } =
|
|
19360
|
+
const { size, length, left } = fit5(String(v.text), room(v, 360), 110);
|
|
16590
19361
|
const base = 100 + size * 0.36;
|
|
16591
|
-
return { ...frame(v, left, length, base, size), size, length, base:
|
|
19362
|
+
return { ...frame(v, left, length, base, size), size, length, base: r110(base), low: r110(base + 12), high: r110(base - size * 0.82 - 12) };
|
|
16592
19363
|
},
|
|
16593
19364
|
motion: (() => {
|
|
16594
19365
|
const word = `x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}"`;
|
|
@@ -16635,19 +19406,19 @@ var glitchText = {
|
|
|
16635
19406
|
{ name: "Vapor", values: { left: [1, 0.3, 0.85], right: [0.4, 0.6, 1], background: [0.08, 0.02, 0.12], amount: 1.4 } }
|
|
16636
19407
|
],
|
|
16637
19408
|
derived: (v) => {
|
|
16638
|
-
const { size, length, left } =
|
|
19409
|
+
const { size, length, left } = fit5(String(v.text), room(v, 340), 100);
|
|
16639
19410
|
const a = Number(v.amount);
|
|
16640
19411
|
const base = 100 + size * 0.36;
|
|
16641
19412
|
const top = base - size * 0.78;
|
|
16642
|
-
const cuts = [-1e3, ...BANDS.map((f) =>
|
|
19413
|
+
const cuts = [-1e3, ...BANDS.map((f) => r110(top + f * (base - top))), 1200];
|
|
16643
19414
|
const unit = size * Number(v.amount);
|
|
16644
|
-
const shift = ([x, y]) => `translate(${
|
|
19415
|
+
const shift = ([x, y]) => `translate(${r110(x * unit)} ${r110(y * unit)})`;
|
|
16645
19416
|
return {
|
|
16646
19417
|
...frame(v, left, length, base, size, [0.05 + 0.04 * a, 0.25 * a, DESCENT + 0.04 * a, 0.25 * a]),
|
|
16647
19418
|
size,
|
|
16648
19419
|
length,
|
|
16649
|
-
base:
|
|
16650
|
-
...Object.fromEntries([0, 1, 2, 3].flatMap((b) => [[`cut${b}`, cuts[b]], [`band${b}`,
|
|
19420
|
+
base: r110(base),
|
|
19421
|
+
...Object.fromEntries([0, 1, 2, 3].flatMap((b) => [[`cut${b}`, cuts[b]], [`band${b}`, r110(cuts[b + 1] - cuts[b])]])),
|
|
16651
19422
|
...Object.fromEntries(
|
|
16652
19423
|
GLITCH.flatMap((key, k) => [
|
|
16653
19424
|
[`red${k}`, shift(key.red)],
|
|
@@ -16687,23 +19458,23 @@ var extrudedText = {
|
|
|
16687
19458
|
],
|
|
16688
19459
|
// Each layer sits a step further back along the extrude direction, shaded from the side colour to the shadow.
|
|
16689
19460
|
derived: (v) => {
|
|
16690
|
-
const { size, length, left } =
|
|
19461
|
+
const { size, length, left } = fit5(String(v.text), room(v, 320), 96);
|
|
16691
19462
|
const d = 0.36 * Number(v.depth);
|
|
16692
19463
|
const step = size * 0.03 * Number(v.depth);
|
|
16693
19464
|
const at3 = (deg, i) => {
|
|
16694
19465
|
const a = deg * Math.PI / 180;
|
|
16695
|
-
return `translate(${
|
|
19466
|
+
return `translate(${r110(Math.cos(a) * step * i)} ${r110(Math.sin(a) * step * i)})`;
|
|
16696
19467
|
};
|
|
16697
19468
|
return {
|
|
16698
19469
|
...frame(v, left, length, 92 + size * 0.35, size, [0.05, 0.77 * d, DESCENT + d, 0.77 * d]),
|
|
16699
19470
|
size,
|
|
16700
19471
|
length,
|
|
16701
|
-
base:
|
|
19472
|
+
base: r110(92 + size * 0.35),
|
|
16702
19473
|
...Object.fromEntries(
|
|
16703
19474
|
Array.from({ length: LAYERS }, (_, k) => {
|
|
16704
19475
|
const i = LAYERS - k;
|
|
16705
19476
|
return [
|
|
16706
|
-
[`fill${i}`,
|
|
19477
|
+
[`fill${i}`, mixHex3(v.side, v.shade, i / LAYERS)],
|
|
16707
19478
|
[`a${i}`, at3(EXTRUDE_FROM, i)],
|
|
16708
19479
|
[`b${i}`, at3(EXTRUDE_TO, i)]
|
|
16709
19480
|
];
|
|
@@ -17183,7 +19954,7 @@ var dripText = {
|
|
|
17183
19954
|
const left = 200 - width / 2;
|
|
17184
19955
|
const runs = Math.min(DRIP_RUNS, Math.max(1, Math.round(width / DRIP_RUN)));
|
|
17185
19956
|
const run = width / runs;
|
|
17186
|
-
const
|
|
19957
|
+
const r111 = (n11) => Math.round(n11 * 10) / 10;
|
|
17187
19958
|
const edge = Number(v.stroke) / 2 / DRIP_SIZE;
|
|
17188
19959
|
const tip = (DRIP_TOP + Math.max(...DRIPS2.map((d) => d.length)) * Number(v.drip) + 13 * 0.72 - DRIP_BASELINE) / DRIP_SIZE;
|
|
17189
19960
|
const overhang = [edge, edge, Math.max(0.05, tip) + edge, edge].map((e) => Math.round(e * 100) / 100).join(" ");
|
|
@@ -17197,15 +19968,15 @@ var dripText = {
|
|
|
17197
19968
|
overhang,
|
|
17198
19969
|
...Object.fromEntries(
|
|
17199
19970
|
DRIP_SLOTS.flatMap((d) => {
|
|
17200
|
-
const x =
|
|
17201
|
-
const length =
|
|
19971
|
+
const x = r111(left + (d.run + d.at) * run);
|
|
19972
|
+
const length = r111(d.length * Number(v.drip));
|
|
17202
19973
|
return [
|
|
17203
19974
|
[`show${d.i}`, d.run < runs ? "inline" : "none"],
|
|
17204
19975
|
[`x${d.i}`, x],
|
|
17205
|
-
[`left${d.i}`,
|
|
17206
|
-
[`shine${d.i}`,
|
|
19976
|
+
[`left${d.i}`, r111(x - d.width / 2)],
|
|
19977
|
+
[`shine${d.i}`, r111(x - d.width * 0.25)],
|
|
17207
19978
|
[`len${d.i}`, length],
|
|
17208
|
-
[`tip${d.i}`,
|
|
19979
|
+
[`tip${d.i}`, r111(DRIP_TOP + length)]
|
|
17209
19980
|
];
|
|
17210
19981
|
})
|
|
17211
19982
|
)
|
|
@@ -17347,9 +20118,13 @@ var SHADER_EFFECTS = [
|
|
|
17347
20118
|
...debrisShaders,
|
|
17348
20119
|
...impactShaders,
|
|
17349
20120
|
...comicShaders,
|
|
20121
|
+
...atmosphereShaders,
|
|
20122
|
+
...glowShaders,
|
|
20123
|
+
...shadowShaders,
|
|
20124
|
+
...distortionShaders,
|
|
17350
20125
|
...transitions
|
|
17351
20126
|
];
|
|
17352
|
-
var EFFECTS = [...SHADER_EFFECTS, ...vectorEffects, ...textEffects, ...morphEffects, ...interactionEffects, ...pyroEffects, ...timewarpEffects, ...meltEffects, ...electricEffects, ...pixelEffects, ...splatterEffects, ...bubbleEffects, ...stickerEffects, ...trailEffects, ...impactEffects, ...comicEffects].sort(
|
|
20127
|
+
var EFFECTS = [...SHADER_EFFECTS, ...vectorEffects, ...textEffects, ...morphEffects, ...interactionEffects, ...pyroEffects, ...timewarpEffects, ...meltEffects, ...electricEffects, ...pixelEffects, ...splatterEffects, ...bubbleEffects, ...stickerEffects, ...trailEffects, ...impactEffects, ...comicEffects, ...glowEffects, ...shadowEffects].sort(
|
|
17353
20128
|
(a, b) => EFFECT_CATEGORIES.indexOf(a.category) - EFFECT_CATEGORIES.indexOf(b.category)
|
|
17354
20129
|
);
|
|
17355
20130
|
var byId = new Map(EFFECTS.map((e) => [e.id, e]));
|
|
@@ -17407,4 +20182,4 @@ export {
|
|
|
17407
20182
|
effectControls,
|
|
17408
20183
|
effectDefaults
|
|
17409
20184
|
};
|
|
17410
|
-
//# sourceMappingURL=chunk-
|
|
20185
|
+
//# sourceMappingURL=chunk-ECBG5K6E.js.map
|