instantshader 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  # instantshader
4
4
 
5
5
  Animated WebGL gradient shaders with zero dependencies. Mount a live, resizable
6
- gradient into any DOM element, or render a single frame to a detached canvas
6
+ gradient into any DOM element, put dither, pixelate, halftone or ASCII effects
7
+ over it or over your own image, or render a single frame to a detached canvas
7
8
  for export pipelines. Built by [InstantGradient](https://instantgradient.com/app).
8
9
 
9
10
  ## Install
@@ -39,6 +40,10 @@ tree-shake away.
39
40
  | `strata` | Stacked cut-paper sheets, one per palette step, each casting a soft shadow on the one below. `ridges` and `stretch` reshape the relief itself, from round islands to branching spines to long agate bands. | `scale`, `layers`, `warp`, `ridges`, `stretch`, `depth`, `blend`, `angle`, `grain` |
40
41
  | `dune` | Overlapping crests rolling across the frame, back to front. Each has one crisp top edge and airbrushes away below it; neighbouring crests slide against each other. | `layers`, `swell`, `waves`, `fade`, `soft`, `angle`, `grain` |
41
42
  | `whorl` | A logarithmic spiral of curved blades, each with one sharp leading edge and a soft fade behind it. `x` / `y` place the centre anywhere, including off-frame. | `blades`, `twist`, `depth`, `scale`, `wobble`, `x`, `y`, `grain` |
43
+ | `silk` | Satin folds lit from one side, with an anisotropic sheen that streaks along the threads. The palette is printed on the cloth; light only reveals the relief. | `scale`, `folds`, `depth`, `warp`, `sheen`, `light`, `grain` |
44
+ | `nacre` | Glassy liquid folds whose colour walks through the neighbouring palette stops on every slope, the way mother of pearl shifts with the viewing angle. Only ever shows the ramp's own colours. | `scale`, `flow`, `crease`, `depth`, `iridescence`, `light`, `grain` |
45
+ | `burst` | Thin grainy rays out of one point, each ray its own stop, streaming outward in packets, from a dark tunnel to a white-hot star, straight or twisted into a pinwheel. | `rays`, `sharp`, `glow`, `twist`, `x`, `y`, `grain` |
46
+ | `glint` | A shoal of small angular shards streaming along one curved current on dark, spinning and flashing, each edge fringed with colour. The only particle look. | `size`, `density`, `bend`, `angle`, `speed`, `grain` |
42
47
 
43
48
  Previews of every look, with full param ranges, are in the
44
49
  [repository README](https://github.com/ugolbck/instantshader#readme).
@@ -51,20 +56,22 @@ import { mountGradient, bloom } from "instantshader";
51
56
  mountGradient(el, {
52
57
  shader: bloom,
53
58
  colors: ["#ffd9e8", "#ffb300", "#ff8fc0", "#3d7bff", "#0a2e14"],
54
- // bloom has two independent motion modes — petals that breathe and lean,
59
+ // bloom has two independent motion modes: petals that breathe and lean,
55
60
  // and colours that travel outward through a still pattern. Mix freely.
56
61
  params: { sway: 0.5, colorflow: 0.4 },
57
62
  });
58
63
  ```
59
64
 
60
- Ranges, defaults and labels are all discoverable at runtime — `shader.params`
65
+ Ranges, defaults and labels are all discoverable at runtime. `shader.params`
61
66
  is an array of `ParamDef`, and `shader.randomParams(rand)` produces a full,
62
67
  sensible param set for "randomize" flows. `shaders` and `getShader(id)` expose
63
68
  the whole registry (importing either pulls every shader).
64
69
 
65
70
  ## Effects
66
71
 
67
- Effects redraw an existing picture: a generator's output or your own image, canvas or video frame.
72
+ An effect redraws a picture: a shader's output, or an image, canvas or video
73
+ frame you supply. Four are included: `pixelate`, `dither`, `halftone` and
74
+ `ascii`.
68
75
 
69
76
  ```ts
70
77
  import { mountStack, bloom, dither } from "instantshader";
@@ -77,14 +84,30 @@ mountStack(el, {
77
84
  });
78
85
  ```
79
86
 
80
- `pixelate`, `dither`, `halftone` and `ascii` ship today; `effects` and `getEffect(id)` list them, and each `EffectDef.params` describes its controls (float, enum, bool and color, with a `when` hint for conditional ones). Use `{ kind: "media", media }` as the source for an image. `createStackRenderer` is the seekable renderer for export and `renderStackFrame` the one-shot.
81
-
82
- Effect sizes are in pixels at 1080p and effects work on a per-cell buffer that is identical at every output size, so an export matches its preview cell for cell. The [main README](../../README.md#effects) has the details.
87
+ For an image, the source is `{ kind: "media", media: img }` with an optional
88
+ `fit` of `"cover"` (default) or `"contain"`. `effects` is a list, bottom
89
+ layer first. `mountStack` returns the `mountGradient` handle plus
90
+ `setSource`, `setSourceParams`, `setEffects`, `setEffectParams(index, params)`,
91
+ `refreshMedia()` and `getGridInfo()`. `renderStackFrame` renders one frame to
92
+ a detached canvas and `createStackRenderer` is the seekable renderer for
93
+ video export, both with the same options.
94
+
95
+ Each effect's `params` array describes its controls the way `shader.params`
96
+ does, with four kinds: float, enum (a string value from `options`), bool and
97
+ colour (a `#rrggbb` string). A `when` field on a param says which other
98
+ param's value makes it relevant, for building UI. `effects` and
99
+ `getEffect(id)` expose the registry, and importing either pulls every effect.
100
+
101
+ Effect sizes are in pixels at 1080p, and each effect computes one value per
102
+ cell into a buffer that is the same at every output size, so a preview and a
103
+ 4K export contain identical cells. The [repository
104
+ README](https://github.com/ugolbck/instantshader#effects) has images, every
105
+ param with its range, and the details of how preview and export line up.
83
106
 
84
107
  ## Seamless loops
85
108
 
86
109
  Set `loopSeconds` and the animation repeats exactly, with no visible seam at
87
- the wrap — the frame at `t` and at `t + loopSeconds` are identical pixel for
110
+ the wrap. The frame at `t` and at `t + loopSeconds` are identical pixel for
88
111
  pixel. Built for video export and for backgrounds that must not betray a
89
112
  restart.
90
113
 
@@ -119,7 +142,7 @@ Notes:
119
142
  light video file without slowing the animation down.
120
143
  - **`flow` ties its travel speed to the loop length.** It animates by
121
144
  translating in a straight line through a noise field that tiles, and it
122
- covers exactly one tile per cycle — so a short loop flows fast and a long
145
+ covers exactly one tile per cycle, so a short loop flows fast and a long
123
146
  one flows slowly. The hand-tuned drift rate corresponds to a period around
124
147
  60–90s; below ~30s the currents move noticeably faster than the look was
125
148
  designed for. Compensate with `speed` rather than by shortening the loop.
@@ -130,9 +153,11 @@ Notes:
130
153
  slow oscillations (petal breathing ~20s, fan lean ~30s, petal flex ~42s)
131
154
  layered over a noise wander. Each oscillation holds still once the loop is
132
155
  shorter than about half its own period, so below ~10s the wander is the only
133
- thing left moving. `colorflow` is unaffected — it always fits at least one
156
+ thing left moving. `colorflow` is unaffected. It always fits at least one
134
157
  full cycle into the loop, flowing faster on a short one.
135
- - `halo`, `dune` and `whorl` always complete at least one full cycle of their
136
- main motion per loop, so a short loop simply runs them faster.
158
+ - `halo`, `dune` and `whorl` always complete at least one full cycle of
159
+ their main motion per loop, so a short loop simply runs them faster.
160
+ `burst` never turns; its rays stream outward one tile per loop. `glint`
161
+ travels one pattern period per loop, half a period under 12 s.
137
162
  - Any loop necessarily revisits the same state every N seconds; a long period
138
163
  is what buys the impression of never repeating.
package/dist/index.d.ts CHANGED
@@ -340,6 +340,18 @@ declare const dune: ShaderDef;
340
340
  //#region src/shaders/whorl.d.ts
341
341
  declare const whorl: ShaderDef;
342
342
  //#endregion
343
+ //#region src/shaders/silk.d.ts
344
+ declare const silk: ShaderDef;
345
+ //#endregion
346
+ //#region src/shaders/nacre.d.ts
347
+ declare const nacre: ShaderDef;
348
+ //#endregion
349
+ //#region src/shaders/burst.d.ts
350
+ declare const burst: ShaderDef;
351
+ //#endregion
352
+ //#region src/shaders/glint.d.ts
353
+ declare const glint: ShaderDef;
354
+ //#endregion
343
355
  //#region src/effects/pixelate.d.ts
344
356
  declare const pixelate: EffectDef;
345
357
  //#endregion
@@ -473,4 +485,4 @@ declare function gridFor(width: number, height: number, cell: [number, number],
473
485
  */
474
486
  declare function buildPaletteRamp(colors: string[]): Uint8Array;
475
487
  //#endregion
476
- export { type BoolParamDef, type ColorParamDef, type EffectDef, type EffectLayer, type EffectParamDef, type EffectTexture, type EnumParamDef, type FloatParamDef, type GeneratorDef, type Grid, type GridInfo, type MountHandle, type MountOptions, type ParamDef, type ParamValue, type RenderFrameResult, type Renderer, type RendererOptions, type ShaderDef, type Source, type StackHandle, type StackMountOptions, type StackOptions, type StackRenderer, type TextureData, type TextureEnv, ascii, beam, bloom, buildPaletteRamp, createRenderer, createStackRenderer, dither, dune, effects, flow, getEffect, getShader, gridFor, halftone, halo, mountGradient, mountStack, pixelate, renderGradientFrame, renderStackFrame, shaders, strata, whorl };
488
+ export { type BoolParamDef, type ColorParamDef, type EffectDef, type EffectLayer, type EffectParamDef, type EffectTexture, type EnumParamDef, type FloatParamDef, type GeneratorDef, type Grid, type GridInfo, type MountHandle, type MountOptions, type ParamDef, type ParamValue, type RenderFrameResult, type Renderer, type RendererOptions, type ShaderDef, type Source, type StackHandle, type StackMountOptions, type StackOptions, type StackRenderer, type TextureData, type TextureEnv, ascii, beam, bloom, buildPaletteRamp, burst, createRenderer, createStackRenderer, dither, dune, effects, flow, getEffect, getShader, glint, gridFor, halftone, halo, mountGradient, mountStack, nacre, pixelate, renderGradientFrame, renderStackFrame, shaders, silk, strata, whorl };
package/dist/index.js CHANGED
@@ -254,7 +254,7 @@ vec2 seedOffset() {
254
254
 
255
255
  //#endregion
256
256
  //#region src/shaders/flow.ts
257
- const FRAGMENT$6 = `
257
+ const FRAGMENT$10 = `
258
258
  uniform float u_scale;
259
259
  uniform float u_curl;
260
260
  uniform float u_drift;
@@ -391,7 +391,7 @@ void main() {
391
391
  const flow = {
392
392
  id: "flow",
393
393
  label: "Flow",
394
- fragment: FRAGMENT$6,
394
+ fragment: FRAGMENT$10,
395
395
  params: [
396
396
  {
397
397
  key: "scale",
@@ -447,7 +447,7 @@ const flow = {
447
447
 
448
448
  //#endregion
449
449
  //#region src/shaders/beam.ts
450
- const FRAGMENT$5 = `
450
+ const FRAGMENT$9 = `
451
451
  uniform float u_scale;
452
452
  uniform float u_width;
453
453
  uniform float u_glow;
@@ -701,7 +701,7 @@ void main() {
701
701
  const beam = {
702
702
  id: "beam",
703
703
  label: "Beam",
704
- fragment: FRAGMENT$5,
704
+ fragment: FRAGMENT$9,
705
705
  params: [
706
706
  {
707
707
  key: "scale",
@@ -757,7 +757,7 @@ const beam = {
757
757
 
758
758
  //#endregion
759
759
  //#region src/shaders/bloom.ts
760
- const FRAGMENT$4 = `
760
+ const FRAGMENT$8 = `
761
761
  uniform float u_scale;
762
762
  uniform float u_petals;
763
763
  uniform float u_pinch;
@@ -916,7 +916,7 @@ void main() {
916
916
  const bloom = {
917
917
  id: "bloom",
918
918
  label: "Bloom",
919
- fragment: FRAGMENT$4,
919
+ fragment: FRAGMENT$8,
920
920
  params: [
921
921
  {
922
922
  key: "scale",
@@ -990,7 +990,7 @@ const bloom = {
990
990
 
991
991
  //#endregion
992
992
  //#region src/shaders/halo.ts
993
- const FRAGMENT$3 = `
993
+ const FRAGMENT$7 = `
994
994
  uniform float u_radius;
995
995
  uniform float u_x;
996
996
  uniform float u_y;
@@ -1103,7 +1103,7 @@ void main() {
1103
1103
  const halo = {
1104
1104
  id: "halo",
1105
1105
  label: "Halo",
1106
- fragment: FRAGMENT$3,
1106
+ fragment: FRAGMENT$7,
1107
1107
  params: [
1108
1108
  {
1109
1109
  key: "radius",
@@ -1178,7 +1178,7 @@ const halo = {
1178
1178
 
1179
1179
  //#endregion
1180
1180
  //#region src/shaders/strata.ts
1181
- const FRAGMENT$2 = `
1181
+ const FRAGMENT$6 = `
1182
1182
  uniform float u_scale;
1183
1183
  uniform float u_layers;
1184
1184
  uniform float u_warp;
@@ -1283,7 +1283,7 @@ void main() {
1283
1283
  const strata = {
1284
1284
  id: "strata",
1285
1285
  label: "Strata",
1286
- fragment: FRAGMENT$2,
1286
+ fragment: FRAGMENT$6,
1287
1287
  params: [
1288
1288
  {
1289
1289
  key: "scale",
@@ -1375,7 +1375,7 @@ const strata = {
1375
1375
 
1376
1376
  //#endregion
1377
1377
  //#region src/shaders/dune.ts
1378
- const FRAGMENT$1 = `
1378
+ const FRAGMENT$5 = `
1379
1379
  uniform float u_layers;
1380
1380
  uniform float u_swell;
1381
1381
  uniform float u_waves;
@@ -1452,7 +1452,7 @@ void main() {
1452
1452
  const dune = {
1453
1453
  id: "dune",
1454
1454
  label: "Dune",
1455
- fragment: FRAGMENT$1,
1455
+ fragment: FRAGMENT$5,
1456
1456
  params: [
1457
1457
  {
1458
1458
  key: "layers",
@@ -1526,7 +1526,7 @@ const dune = {
1526
1526
 
1527
1527
  //#endregion
1528
1528
  //#region src/shaders/whorl.ts
1529
- const FRAGMENT = `
1529
+ const FRAGMENT$4 = `
1530
1530
  uniform float u_blades;
1531
1531
  uniform float u_twist;
1532
1532
  uniform float u_depth;
@@ -1579,7 +1579,7 @@ void main() {
1579
1579
  const whorl = {
1580
1580
  id: "whorl",
1581
1581
  label: "Whorl",
1582
- fragment: FRAGMENT,
1582
+ fragment: FRAGMENT$4,
1583
1583
  params: [
1584
1584
  {
1585
1585
  key: "blades",
@@ -1660,6 +1660,646 @@ const whorl = {
1660
1660
  }
1661
1661
  };
1662
1662
 
1663
+ //#endregion
1664
+ //#region src/shaders/silk.ts
1665
+ const FRAGMENT$3 = `
1666
+ uniform float u_scale;
1667
+ uniform float u_folds;
1668
+ uniform float u_depth;
1669
+ uniform float u_warp;
1670
+ uniform float u_sheen;
1671
+ uniform float u_light;
1672
+ uniform float u_grain;
1673
+
1674
+ ${SIMPLEX_2D}
1675
+ ${FBM}
1676
+ ${SHAPE}
1677
+ ${GRAIN}
1678
+ ${ISO}
1679
+ ${SEED}
1680
+
1681
+ const float PI = 3.14159265;
1682
+
1683
+ vec2 toField(vec2 v, vec2 axis) {
1684
+ return vec2(dot(v, axis) / (1.0 + 4.0 * u_folds), dot(v, vec2(-axis.y, axis.x)));
1685
+ }
1686
+
1687
+ // Fold height at frame point p. The warp is passed in, computed once at the
1688
+ // pixel: it varies over ~2 frame units, the taps are 0.006 apart.
1689
+ float height(vec2 p, vec2 axis, float freq, vec2 so, vec2 drift, float warp) {
1690
+ vec2 q = toField(p, axis) * freq + so + drift;
1691
+ q.y += warp;
1692
+ return fbm2(q);
1693
+ }
1694
+
1695
+ void main() {
1696
+ vec2 uv = worldUv();
1697
+ vec2 p = isoCoord(uv);
1698
+
1699
+ vec2 so = seedOffset();
1700
+ float axisAngle = seedHash(3.0) * PI;
1701
+ vec2 axis = vec2(cos(axisAngle), sin(axisAngle));
1702
+ float freq = 1.6 * u_scale;
1703
+
1704
+ vec2 drift = loopDrift(0.025, vec2(1.0, 0.0));
1705
+ // Warp: one coarse octave, shifts the across-fold coordinate so ridges
1706
+ // bend, converge and fork.
1707
+ float warp = u_warp * 1.4 * snoise(toField(p, axis) * (0.45 * freq) + so.yx + 31.0 + drift.yx);
1708
+
1709
+ float eps = 0.006;
1710
+ float hx1 = height(p + vec2(eps, 0.0), axis, freq, so, drift, warp);
1711
+ float hx0 = height(p - vec2(eps, 0.0), axis, freq, so, drift, warp);
1712
+ float hy1 = height(p + vec2(0.0, eps), axis, freq, so, drift, warp);
1713
+ float hy0 = height(p - vec2(0.0, eps), axis, freq, so, drift, warp);
1714
+ float h = 0.25 * (hx1 + hx0 + hy1 + hy0);
1715
+
1716
+ // Slope scale: fbm2 spans ~[-1, 1] over ~1/freq frame units, so its raw
1717
+ // gradient is O(freq); 0.35 * depth brings the default to slopes near 1.
1718
+ float amp = 0.35 * u_depth / eps;
1719
+ vec3 n = normalize(vec3(-(hx1 - hx0) * amp * 0.5, -(hy1 - hy0) * amp * 0.5, 1.0));
1720
+
1721
+ // Light: azimuth measured from the ACROSS-fold direction, so u_light = 0
1722
+ // always rakes across the folds whatever axis the seed picked (a light
1723
+ // along the folds shows no relief at all). Slow sway; elevation 40.
1724
+ float az = axisAngle + 0.5 * PI + u_light * PI / 180.0 + 0.14 * sin(loopFreq(0.12) * u_time + u_seed);
1725
+ vec3 L = vec3(cos(az) * 0.766, sin(az) * 0.766, 0.643);
1726
+ vec3 V = vec3(0.0, 0.0, 1.0);
1727
+ vec3 H = normalize(L + V);
1728
+ vec3 T = normalize(vec3(axis, 0.0) - n * dot(vec3(axis, 0.0), n));
1729
+
1730
+ float ndl = dot(n, L);
1731
+ float diff = 0.5 + 0.5 * ndl;
1732
+ float th = sqrt(max(1.0 - dot(T, H) * dot(T, H), 0.0));
1733
+ float sheen = (0.55 * pow(th, 48.0) + 0.3 * pow(th, 7.0)) * max(ndl, 0.0);
1734
+ // Valleys sit in shadow from the folds above them.
1735
+ float cav = 0.75 + 0.25 * (0.5 + 0.5 * h);
1736
+
1737
+ // Colour: its own coarse field, drifting on its own path.
1738
+ vec2 drift2 = loopDrift(0.02, vec2(0.0, 1.0));
1739
+ float t = spread(fbm2(p * (0.55 * u_scale) + so + 17.0 + drift2), 0.35);
1740
+ vec3 cloth = palette(t);
1741
+
1742
+ vec3 color = cloth * (0.35 + 0.65 * diff) * cav;
1743
+ color += mix(cloth, vec3(1.0), 0.5) * sheen * u_sheen;
1744
+
1745
+ float g = grain(uv, u_time) - 0.5;
1746
+ color += g * u_grain;
1747
+
1748
+ gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);
1749
+ }
1750
+ `;
1751
+ const silk = {
1752
+ id: "silk",
1753
+ label: "Silk",
1754
+ fragment: FRAGMENT$3,
1755
+ params: [
1756
+ {
1757
+ key: "scale",
1758
+ label: "Scale",
1759
+ min: .3,
1760
+ max: 2.5,
1761
+ step: .05,
1762
+ default: 1
1763
+ },
1764
+ {
1765
+ key: "folds",
1766
+ label: "Folds",
1767
+ min: 0,
1768
+ max: 1,
1769
+ step: .01,
1770
+ default: .6
1771
+ },
1772
+ {
1773
+ key: "depth",
1774
+ label: "Depth",
1775
+ min: .2,
1776
+ max: 2,
1777
+ step: .01,
1778
+ default: 1
1779
+ },
1780
+ {
1781
+ key: "warp",
1782
+ label: "Warp",
1783
+ min: 0,
1784
+ max: 1,
1785
+ step: .01,
1786
+ default: .5
1787
+ },
1788
+ {
1789
+ key: "sheen",
1790
+ label: "Sheen",
1791
+ min: 0,
1792
+ max: 1,
1793
+ step: .01,
1794
+ default: .6
1795
+ },
1796
+ {
1797
+ key: "light",
1798
+ label: "Light",
1799
+ min: 0,
1800
+ max: 360,
1801
+ step: 1,
1802
+ default: 20
1803
+ },
1804
+ {
1805
+ key: "grain",
1806
+ label: "Grain",
1807
+ min: 0,
1808
+ max: .5,
1809
+ step: .01,
1810
+ default: .06
1811
+ }
1812
+ ],
1813
+ randomParams(rand) {
1814
+ return {
1815
+ scale: .7 + rand() * 1.1,
1816
+ folds: .3 + rand() * .7,
1817
+ depth: .6 + rand() * 1,
1818
+ warp: rand(),
1819
+ sheen: .3 + rand() * .6000000000000001,
1820
+ light: Math.floor((rand() < .5 ? 0 : 180) + (rand() - .5) * 100 + 360) % 360,
1821
+ grain: .06
1822
+ };
1823
+ }
1824
+ };
1825
+
1826
+ //#endregion
1827
+ //#region src/shaders/nacre.ts
1828
+ const FRAGMENT$2 = `
1829
+ uniform float u_scale;
1830
+ uniform float u_flow;
1831
+ uniform float u_crease;
1832
+ uniform float u_depth;
1833
+ uniform float u_iridescence;
1834
+ uniform float u_light;
1835
+ uniform float u_grain;
1836
+
1837
+ ${SIMPLEX_2D}
1838
+ ${FBM}
1839
+ ${SHAPE}
1840
+ ${GRAIN}
1841
+ ${ISO}
1842
+ ${SEED}
1843
+
1844
+ const float PI = 3.14159265;
1845
+
1846
+ float tri(float x) {
1847
+ return 1.0 - abs(1.0 - 2.0 * fract(0.5 * x));
1848
+ }
1849
+
1850
+ // One smooth octave with a faint second: pooled lobes, not texture. The
1851
+ // creases are narrow ridges where the base field crosses a level, so they
1852
+ // are contours of the lobes themselves: they hug each lobe's edge and, as
1853
+ // level sets of one field, never cross each other or cut through a lobe.
1854
+ float height(vec2 p, float f, vec2 so, vec2 drift, vec2 warp) {
1855
+ vec2 q = (p + warp) * f + so + drift;
1856
+ float base = snoise(q) + 0.18 * snoise(q * 2.1 + 5.0);
1857
+ float d0 = base;
1858
+ float d1 = abs(base) - 0.6;
1859
+ float crease = exp(-(d0 * d0) / 0.012) + 0.6 * exp(-(d1 * d1) / 0.01);
1860
+ return base * 0.7 + crease * u_crease * 0.3;
1861
+ }
1862
+
1863
+ void main() {
1864
+ vec2 uv = worldUv();
1865
+ vec2 p = isoCoord(uv);
1866
+
1867
+ vec2 so = seedOffset();
1868
+ float f = 0.55 * u_scale;
1869
+ vec2 drift = loopDrift(0.02, vec2(1.0, 0.0));
1870
+
1871
+ // One warp for every tap: it varies over ~2 frame units, the taps are
1872
+ // 0.006 apart.
1873
+ vec2 wq = p * (0.45 * f) + so.yx + 23.0 + drift.yx;
1874
+ vec2 warp = u_flow * 0.5 * vec2(snoise(wq), snoise(wq + vec2(9.2, 4.1)));
1875
+
1876
+ float eps = 0.006;
1877
+ float hx1 = height(p + vec2(eps, 0.0), f, so, drift, warp);
1878
+ float hx0 = height(p - vec2(eps, 0.0), f, so, drift, warp);
1879
+ float hy1 = height(p + vec2(0.0, eps), f, so, drift, warp);
1880
+ float hy0 = height(p - vec2(0.0, eps), f, so, drift, warp);
1881
+
1882
+ float amp = 0.5 * u_depth / eps;
1883
+ vec3 n = normalize(vec3(-(hx1 - hx0) * amp * 0.5, -(hy1 - hy0) * amp * 0.5, 1.0));
1884
+ float tilt = 1.0 - n.z;
1885
+
1886
+ float az = u_light * PI / 180.0 + 0.12 * sin(loopFreq(0.1) * u_time + u_seed);
1887
+ vec3 L = vec3(cos(az) * 0.643, sin(az) * 0.643, 0.766);
1888
+ float diff = 0.7 + 0.3 * dot(n, L);
1889
+ float rim = pow(tilt, 6.0) * 0.35;
1890
+
1891
+ vec2 drift2 = loopDrift(0.015, vec2(0.0, 1.0));
1892
+ float t0 = spread(fbm2(p * (0.4 * f) + so + 17.0 + drift2), 0.35);
1893
+ float t = tri(t0 + u_iridescence * 1.5 * tilt);
1894
+ vec3 color = palette(t) * diff + vec3(rim);
1895
+
1896
+ float g = grain(uv, u_time) - 0.5;
1897
+ color += g * u_grain;
1898
+
1899
+ gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);
1900
+ }
1901
+ `;
1902
+ const nacre = {
1903
+ id: "nacre",
1904
+ label: "Nacre",
1905
+ fragment: FRAGMENT$2,
1906
+ params: [
1907
+ {
1908
+ key: "scale",
1909
+ label: "Scale",
1910
+ min: .4,
1911
+ max: 2,
1912
+ step: .05,
1913
+ default: .9
1914
+ },
1915
+ {
1916
+ key: "flow",
1917
+ label: "Flow",
1918
+ min: 0,
1919
+ max: 1,
1920
+ step: .01,
1921
+ default: .5
1922
+ },
1923
+ {
1924
+ key: "crease",
1925
+ label: "Crease",
1926
+ min: 0,
1927
+ max: 1,
1928
+ step: .01,
1929
+ default: .6
1930
+ },
1931
+ {
1932
+ key: "depth",
1933
+ label: "Depth",
1934
+ min: .2,
1935
+ max: 2,
1936
+ step: .01,
1937
+ default: 1
1938
+ },
1939
+ {
1940
+ key: "iridescence",
1941
+ label: "Iridescence",
1942
+ min: 0,
1943
+ max: 1,
1944
+ step: .01,
1945
+ default: .6
1946
+ },
1947
+ {
1948
+ key: "light",
1949
+ label: "Light",
1950
+ min: 0,
1951
+ max: 360,
1952
+ step: 1,
1953
+ default: 40
1954
+ },
1955
+ {
1956
+ key: "grain",
1957
+ label: "Grain",
1958
+ min: 0,
1959
+ max: .3,
1960
+ step: .01,
1961
+ default: .04
1962
+ }
1963
+ ],
1964
+ randomParams(rand) {
1965
+ return {
1966
+ scale: .5 + rand() * 1.1,
1967
+ flow: rand(),
1968
+ crease: .2 + rand() * .8,
1969
+ depth: .6 + rand() * 1,
1970
+ iridescence: .3 + rand() * .7,
1971
+ light: Math.floor(rand() * 360),
1972
+ grain: .04
1973
+ };
1974
+ }
1975
+ };
1976
+
1977
+ //#endregion
1978
+ //#region src/shaders/burst.ts
1979
+ const FRAGMENT$1 = `
1980
+ uniform float u_rays;
1981
+ uniform float u_sharp;
1982
+ uniform float u_glow;
1983
+ uniform float u_twist;
1984
+ uniform float u_x;
1985
+ uniform float u_y;
1986
+ uniform float u_grain;
1987
+
1988
+ ${PERIODIC_2D}
1989
+ ${GRAIN}
1990
+ ${ISO}
1991
+ ${SEED}
1992
+
1993
+ void main() {
1994
+ vec2 uv = worldUv();
1995
+ vec2 p = isoCoord(uv);
1996
+ vec2 halfIso = isoHalf();
1997
+
1998
+ vec2 c = vec2(u_x, u_y) * halfIso;
1999
+ vec2 rel = p - c;
2000
+ float r = max(length(rel), 0.0005);
2001
+ float theta = atan(rel.y, rel.x);
2002
+ float rho = log(r / 0.1) * 0.6;
2003
+ float th = theta + u_twist * 1.2 * rho;
2004
+
2005
+ // Ray count: the angular period of the noise, integral so the field
2006
+ // closes on itself around the centre.
2007
+ float A = floor(8.0 + 72.0 * u_rays);
2008
+ vec2 so = seedOffset();
2009
+ float flowOut = loopTravel(0.05, vec2(0.0, 1.0), 4.0).y;
2010
+ float flowPk = loopTravel(0.2, vec2(0.0, 1.0), 12.0).y;
2011
+
2012
+ vec2 sp = vec2(th / TAU * A, rho - flowOut + so.x);
2013
+ float rays = pnoise(sp, vec2(A, 4.0)) + 0.5 * pnoise(sp * 2.0 + vec2(0.0, so.y), vec2(2.0 * A, 8.0));
2014
+ // Packets: shorter along the ray and streaming out four times faster.
2015
+ float pk = pnoise(vec2(sp.x, rho * 3.0 - flowPk + so.y), vec2(A, 12.0));
2016
+ // Sector field: a few broad angular sectors that keep neighbouring rays
2017
+ // in one family, and the mask's contrast.
2018
+ float sector = pnoise(vec2(th / TAU * 6.0 + seedHash(3.0) * 6.0, 0.5), vec2(6.0, 1024.0));
2019
+ // Softer toward the centre, where the rays converge.
2020
+ float edge = mix(0.8, 0.2, u_sharp) + 0.4 * exp(-r / 0.3);
2021
+ float mask = smoothstep(-edge, edge, rays + 0.4 * pk + 0.3 * sector);
2022
+
2023
+ float hole = 1.0 - exp(-(r * r) / (0.25 * 0.25));
2024
+ float env = mix(hole, 1.0 + 0.6 * exp(-r / 0.35), u_glow) * exp(-0.3 * r);
2025
+ float hot = u_glow * exp(-(r * r) / (0.16 * 0.16));
2026
+
2027
+ float sway = 0.35 * sin(loopFreq(0.08) * u_time + u_seed);
2028
+ float theta0 = seedHash(2.0) * TAU + sway;
2029
+ float t = 0.5 + 0.5 * cos(th - theta0 + 2.0 * rays + 1.5 * sector);
2030
+ vec3 tint = palette(t);
2031
+ vec3 color = tint * (0.15 + 0.85 * mask * env);
2032
+ color = mix(color, vec3(1.0), hot * 0.85);
2033
+
2034
+ float g = grain(uv, u_time) - 0.5;
2035
+ color += g * u_grain;
2036
+
2037
+ gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);
2038
+ }
2039
+ `;
2040
+ const burst = {
2041
+ id: "burst",
2042
+ label: "Burst",
2043
+ fragment: FRAGMENT$1,
2044
+ params: [
2045
+ {
2046
+ key: "rays",
2047
+ label: "Rays",
2048
+ min: 0,
2049
+ max: 1,
2050
+ step: .01,
2051
+ default: .45
2052
+ },
2053
+ {
2054
+ key: "sharp",
2055
+ label: "Sharp",
2056
+ min: 0,
2057
+ max: 1,
2058
+ step: .01,
2059
+ default: .6
2060
+ },
2061
+ {
2062
+ key: "glow",
2063
+ label: "Glow",
2064
+ min: 0,
2065
+ max: 1,
2066
+ step: .01,
2067
+ default: .7
2068
+ },
2069
+ {
2070
+ key: "twist",
2071
+ label: "Twist",
2072
+ min: 0,
2073
+ max: 1,
2074
+ step: .01,
2075
+ default: .2
2076
+ },
2077
+ {
2078
+ key: "x",
2079
+ label: "X",
2080
+ min: -1,
2081
+ max: 1,
2082
+ step: .01,
2083
+ default: .1
2084
+ },
2085
+ {
2086
+ key: "y",
2087
+ label: "Y",
2088
+ min: -1,
2089
+ max: 1,
2090
+ step: .01,
2091
+ default: .05
2092
+ },
2093
+ {
2094
+ key: "grain",
2095
+ label: "Grain",
2096
+ min: 0,
2097
+ max: .3,
2098
+ step: .01,
2099
+ default: .14
2100
+ }
2101
+ ],
2102
+ randomParams(rand) {
2103
+ return {
2104
+ rays: .2 + rand() * .7,
2105
+ sharp: .3 + rand() * .7,
2106
+ glow: .3 + rand() * .7,
2107
+ twist: rand() * .7,
2108
+ x: -.7 + rand() * 1.4,
2109
+ y: -.7 + rand() * 1.4,
2110
+ grain: .14
2111
+ };
2112
+ }
2113
+ };
2114
+
2115
+ //#endregion
2116
+ //#region src/shaders/glint.ts
2117
+ const FRAGMENT = `
2118
+ uniform float u_size;
2119
+ uniform float u_density;
2120
+ uniform float u_bend;
2121
+ uniform float u_angle;
2122
+ uniform float u_speed;
2123
+ uniform float u_grain;
2124
+
2125
+ ${SIMPLEX_2D}
2126
+ ${GRAIN}
2127
+ ${ISO}
2128
+ ${LOOP_ANGLE}
2129
+ ${SEED}
2130
+
2131
+ const float PI = 3.14159265;
2132
+ const vec3 LUMA = vec3(0.2126, 0.7152, 0.0722);
2133
+
2134
+ float hash21(vec2 c) {
2135
+ return fract(sin(dot(c, vec2(127.1, 311.7)) + u_seed * 0.37) * 43758.5453);
2136
+ }
2137
+
2138
+ // Rhombus coverage at local offset d for half-extents a (along) and b
2139
+ // (across), edge ramped over aa.
2140
+ float rhombus(vec2 d, float a, float b, float aa) {
2141
+ float s = abs(d.x) / a + abs(d.y) / b - 1.0;
2142
+ return 1.0 - smoothstep(-aa, aa, s);
2143
+ }
2144
+
2145
+ void main() {
2146
+ vec2 uv = worldUv();
2147
+ vec2 p = isoCoord(uv);
2148
+ vec2 halfIso = isoHalf();
2149
+ float pixel = 2.0 * halfIso.y / u_resolution.y;
2150
+
2151
+ float ang = u_angle * PI / 180.0;
2152
+ vec2 q = vec2(p.x * cos(ang) + p.y * sin(ang), -p.x * sin(ang) + p.y * cos(ang));
2153
+ vec2 so = seedOffset();
2154
+ // The current: one slow bend, so the shoal arcs across the frame.
2155
+ float bend = 0.35 * u_bend * snoise(vec2(q.x * 0.9 + so.x, so.y));
2156
+ float across = q.y - bend;
2157
+
2158
+ // Background and haze.
2159
+ vec3 c0 = palette(0.0);
2160
+ vec3 c1 = palette(0.5);
2161
+ vec3 c2 = palette(1.0);
2162
+ float l0 = dot(c0, LUMA);
2163
+ float l1 = dot(c1, LUMA);
2164
+ float l2 = dot(c2, LUMA);
2165
+ vec3 dk = mix(c0, c1, step(l1, l0));
2166
+ dk = mix(dk, c2, step(l2, min(l0, l1)));
2167
+ vec3 color = dk * 0.1;
2168
+ color += palette(0.5) * 0.05 * exp(-(across * across) / 0.1);
2169
+
2170
+ float tw = loopFreq(1.2) * u_time;
2171
+ // Hash period along the stream, in cells. The lattice travels one period
2172
+ // per loop, so a short loop would race; halving the period halves that at
2173
+ // the cost of the pattern repeating every 8 cells instead of 16.
2174
+ float TILE = (u_loop > 0.0 && u_loop < 12.0) ? 8.0 : 16.0;
2175
+
2176
+ for (int layer = 0; layer < 3; layer++) {
2177
+ float fl = float(layer);
2178
+ float sizeMul = fl < 0.5 ? 0.5 : (fl < 1.5 ? 0.85 : 1.5);
2179
+ float speedMul = fl < 0.5 ? 0.5 : (fl < 1.5 ? 0.8 : 1.2);
2180
+ float bright = fl < 0.5 ? 0.45 : (fl < 1.5 ? 0.7 : 1.0);
2181
+ float soft = fl < 0.5 ? 2.5 : (fl < 1.5 ? 1.5 : 1.0);
2182
+
2183
+ float shard = u_size * sizeMul;
2184
+ float cs = shard * 3.2;
2185
+ // The static u_speed term gives the knob a visible effect at t = 0.
2186
+ float travel = loopTravel(0.08 * u_speed * speedMul, vec2(1.0, 0.0), cs * TILE).x + 0.3 * u_speed;
2187
+ vec2 l = vec2(q.x + travel, across) / cs;
2188
+ vec2 cell0 = floor(l);
2189
+
2190
+ for (int j = -1; j <= 1; j++) {
2191
+ for (int i = -1; i <= 1; i++) {
2192
+ vec2 cell = cell0 + vec2(float(i), float(j));
2193
+ vec2 key = vec2(mod(cell.x, TILE), cell.y + fl * 100.0);
2194
+ float h1 = hash21(key);
2195
+ float h2 = hash21(key + 7.0);
2196
+ float h3 = hash21(key + 19.0);
2197
+ float h4 = hash21(key + 31.0);
2198
+ float h5 = hash21(key + 47.0);
2199
+
2200
+ // Denser along the current, sparser off it.
2201
+ float rowY = (cell.y + 0.5) * cs;
2202
+ float present = step(h1, u_density * (0.12 + 0.88 * exp(-(rowY * rowY) / 0.05)));
2203
+ if (present < 0.5) continue;
2204
+
2205
+ vec2 centre = (cell + vec2(0.15 + 0.7 * h2, 0.15 + 0.7 * h3)) * cs;
2206
+ float sz = shard * (0.4 + 0.6 * h4);
2207
+ float dir = h5 < 0.5 ? -1.0 : 1.0;
2208
+ float ori = h5 * TAU + dir * loopAngle(0.25 + 0.5 * h4);
2209
+ float ca = cos(ori);
2210
+ float sa = sin(ori);
2211
+
2212
+ vec2 d = vec2(l.x * cs, across) - centre;
2213
+ vec2 dl = vec2(d.x * ca + d.y * sa, -d.x * sa + d.y * ca);
2214
+ float aa = pixel * soft / sz;
2215
+ float fr = 0.08 * sz;
2216
+ float covR = rhombus(vec2((d.x - fr) * ca + d.y * sa, -(d.x - fr) * sa + d.y * ca) / sz, 1.0, 0.35, aa);
2217
+ float covG = rhombus(dl / sz, 1.0, 0.35, aa);
2218
+ float covB = rhombus(vec2((d.x + fr) * ca + d.y * sa, -(d.x + fr) * sa + d.y * ca) / sz, 1.0, 0.35, aa);
2219
+
2220
+ float twinkle = 0.5 + 0.5 * sin(tw + h2 * TAU + ori * 2.0);
2221
+ float flash = twinkle * twinkle * twinkle;
2222
+ vec3 col = palette(fract(h3 + 0.13 * fl));
2223
+ float lum = dot(col, LUMA);
2224
+ col = col * (1.0 + 2.0 * (1.0 - lum) * (1.0 - lum)) * (0.5 + 0.5 * twinkle) + vec3(0.6) * flash;
2225
+ col *= bright;
2226
+ color += vec3(covR, covG, covB) * col;
2227
+ }
2228
+ }
2229
+ }
2230
+
2231
+ float g = grain(uv, u_time) - 0.5;
2232
+ color += g * u_grain;
2233
+
2234
+ gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);
2235
+ }
2236
+ `;
2237
+ const glint = {
2238
+ id: "glint",
2239
+ label: "Glint",
2240
+ fragment: FRAGMENT,
2241
+ params: [
2242
+ {
2243
+ key: "size",
2244
+ label: "Size",
2245
+ min: .01,
2246
+ max: .06,
2247
+ step: .001,
2248
+ default: .03
2249
+ },
2250
+ {
2251
+ key: "density",
2252
+ label: "Density",
2253
+ min: .2,
2254
+ max: 1,
2255
+ step: .01,
2256
+ default: .6
2257
+ },
2258
+ {
2259
+ key: "bend",
2260
+ label: "Bend",
2261
+ min: 0,
2262
+ max: 1,
2263
+ step: .01,
2264
+ default: .5
2265
+ },
2266
+ {
2267
+ key: "angle",
2268
+ label: "Angle",
2269
+ min: 0,
2270
+ max: 360,
2271
+ step: 1,
2272
+ default: 10
2273
+ },
2274
+ {
2275
+ key: "speed",
2276
+ label: "Speed",
2277
+ min: .3,
2278
+ max: 2,
2279
+ step: .01,
2280
+ default: 1
2281
+ },
2282
+ {
2283
+ key: "grain",
2284
+ label: "Grain",
2285
+ min: 0,
2286
+ max: .3,
2287
+ step: .01,
2288
+ default: .06
2289
+ }
2290
+ ],
2291
+ randomParams(rand) {
2292
+ return {
2293
+ size: .015 + rand() * .03,
2294
+ density: .4 + rand() * .6,
2295
+ bend: rand(),
2296
+ angle: Math.floor(rand() * 360),
2297
+ speed: .6 + rand() * 1,
2298
+ grain: .06
2299
+ };
2300
+ }
2301
+ };
2302
+
1663
2303
  //#endregion
1664
2304
  //#region src/effects/pixelate.ts
1665
2305
  const CELL_FRAGMENT$3 = `
@@ -1913,6 +2553,13 @@ void main() {
1913
2553
  * over another effect, there is no ramp coordinate to recover and tone stays
1914
2554
  * luma (a gradient map).
1915
2555
  *
2556
+ * paletteStop() snaps a tone to the nearest of the palette's stops
2557
+ * (u_paletteStops is the stop count). Halftone and ASCII use it in palette
2558
+ * mode: they do not quantize tone the way Dither's `levels` does, so
2559
+ * without the snap, palette mode over a generator handed back the exact
2560
+ * source color and looked identical to source mode. Snapped, each cell is
2561
+ * one of the palette's own colors, flat.
2562
+ *
1916
2563
  * The search is 33 coarse + 17 fine ramp samples. That is affordable
1917
2564
  * because tone is evaluated per cell or per lattice point, not per pixel.
1918
2565
  * The ends snap to exactly 0 and 1: generators clamp their coordinate, so
@@ -1925,6 +2572,7 @@ uniform vec3 u_ink;
1925
2572
  uniform vec3 u_paper;
1926
2573
  uniform float u_invert;
1927
2574
  uniform float u_sourceIsRamp; // 1 when this layer reads a generator directly
2575
+ uniform float u_paletteStops; // number of colors in the palette
1928
2576
  float rampPosition(vec3 c) {
1929
2577
  float best = 0.0;
1930
2578
  float bd = 1e9;
@@ -1950,6 +2598,10 @@ float toneOf(vec3 c) {
1950
2598
  float t = toneIsRamp() ? rampPosition(c) : luma(c);
1951
2599
  return u_invert > 0.5 ? 1.0 - t : t;
1952
2600
  }
2601
+ vec3 paletteStop(float tone) {
2602
+ float n = max(u_paletteStops - 1.0, 1.0);
2603
+ return palette(floor(tone * n + 0.5) / n);
2604
+ }
1953
2605
  vec3 colorize(float tone, vec3 src) {
1954
2606
  if (u_colorMode < 0.5) return src;
1955
2607
  if (u_colorMode < 1.5) return mix(u_ink, u_paper, tone);
@@ -2228,9 +2880,11 @@ void main() {
2228
2880
 
2229
2881
  // Ink color. Duotone inks every shape with u_ink (colorize() would mix
2230
2882
  // toward paper by tone, which is dither's meaning of duotone). Palette
2231
- // colors by the un-inverted tone, so Invert flips sizes and leaves colors.
2883
+ // inks each dot with the nearest palette STOP to the un-inverted tone, so
2884
+ // Invert flips sizes and leaves colors, and the dots are flat palette
2885
+ // colors rather than a copy of the source.
2232
2886
  vec3 color = sc;
2233
- if (u_colorMode > 1.5) color = palette(toneIsRamp() ? rampPosition(sc) : luma(sc));
2887
+ if (u_colorMode > 1.5) color = paletteStop(toneIsRamp() ? rampPosition(sc) : luma(sc));
2234
2888
  else if (u_colorMode > 0.5) color = u_ink;
2235
2889
 
2236
2890
  gl_FragColor = vec4(color, ink);
@@ -2401,7 +3055,7 @@ const halftone = {
2401
3055
  min: 6,
2402
3056
  max: 160,
2403
3057
  step: .5,
2404
- default: 28
3058
+ default: 10
2405
3059
  },
2406
3060
  {
2407
3061
  key: "angle",
@@ -2417,7 +3071,7 @@ const halftone = {
2417
3071
  min: .2,
2418
3072
  max: 1.5,
2419
3073
  step: .01,
2420
- default: 1
3074
+ default: 1.4
2421
3075
  },
2422
3076
  {
2423
3077
  key: "softness",
@@ -2425,7 +3079,7 @@ const halftone = {
2425
3079
  min: 0,
2426
3080
  max: 1,
2427
3081
  step: .01,
2428
- default: 0
3082
+ default: .1
2429
3083
  },
2430
3084
  {
2431
3085
  key: "contrast",
@@ -2433,7 +3087,7 @@ const halftone = {
2433
3087
  min: 0,
2434
3088
  max: 2,
2435
3089
  step: .01,
2436
- default: 1
3090
+ default: 1.15
2437
3091
  },
2438
3092
  {
2439
3093
  key: "pulse",
@@ -2444,7 +3098,7 @@ const halftone = {
2444
3098
  default: 0
2445
3099
  },
2446
3100
  ...colorModeParams({
2447
- mode: "duotone",
3101
+ mode: "palette",
2448
3102
  paperAlways: true
2449
3103
  })
2450
3104
  ],
@@ -2463,11 +3117,11 @@ const halftone = {
2463
3117
  return {
2464
3118
  grid: rand() < .5 ? "square" : "hex",
2465
3119
  shape: shapes[Math.floor(rand() * shapes.length)],
2466
- size: Math.round(14 + rand() * 50),
3120
+ size: Math.round(8 + rand() * 30),
2467
3121
  angle: Math.round(rand() * 180),
2468
- radius: .8 + rand() * .5,
2469
- softness: rand() < .7 ? 0 : rand() * .5,
2470
- contrast: 1,
3122
+ radius: 1.2 + rand() * .3,
3123
+ softness: rand() < .7 ? .1 : rand() * .5,
3124
+ contrast: 1.15,
2471
3125
  pulse: 0,
2472
3126
  colorMode: modes[Math.floor(rand() * modes.length)],
2473
3127
  ink: "#111111",
@@ -2745,7 +3399,8 @@ void main() {
2745
3399
  } else if (u_colorMode < 1.5) {
2746
3400
  color = u_ink;
2747
3401
  } else {
2748
- color = palette(toneIsRamp() ? rampPosition(sc) : luma(sc));
3402
+ // Nearest palette stop, flat: see paletteStop() in chunks.ts.
3403
+ color = paletteStop(toneIsRamp() ? rampPosition(sc) : luma(sc));
2749
3404
  }
2750
3405
 
2751
3406
  gl_FragColor = vec4(color, glyph / 255.0);
@@ -2927,7 +3582,11 @@ const shaders = [
2927
3582
  halo,
2928
3583
  strata,
2929
3584
  dune,
2930
- whorl
3585
+ whorl,
3586
+ silk,
3587
+ nacre,
3588
+ burst,
3589
+ glint
2931
3590
  ];
2932
3591
  function getShader(id) {
2933
3592
  return shaders.find((s) => s.id === id);
@@ -3941,6 +4600,7 @@ function createStackRenderer(opts) {
3941
4600
  function applyEffectUniforms(p, state, grid, overGenerator) {
3942
4601
  const ref = refSize(canvas.width, canvas.height);
3943
4602
  gl.uniform1f(p.loc("u_sourceIsRamp"), overGenerator ? 1 : 0);
4603
+ gl.uniform1f(p.loc("u_paletteStops"), colors.length);
3944
4604
  gl.uniform2f(p.loc("u_refSize"), ref[0], ref[1]);
3945
4605
  gl.uniform1f(p.loc("u_outputScale"), outputScale(canvas.width, canvas.height));
3946
4606
  if (grid) {
@@ -4364,4 +5024,4 @@ function createRenderer(opts) {
4364
5024
  }
4365
5025
 
4366
5026
  //#endregion
4367
- export { ascii, beam, bloom, buildPaletteRamp, createRenderer, createStackRenderer, dither, dune, effects, flow, getEffect, getShader, gridFor, halftone, halo, mountGradient, mountStack, pixelate, renderGradientFrame, renderStackFrame, shaders, strata, whorl };
5027
+ export { ascii, beam, bloom, buildPaletteRamp, burst, createRenderer, createStackRenderer, dither, dune, effects, flow, getEffect, getShader, glint, gridFor, halftone, halo, mountGradient, mountStack, nacre, pixelate, renderGradientFrame, renderStackFrame, shaders, silk, strata, whorl };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "instantshader",
3
- "version": "0.6.0",
4
- "description": "Animated WebGL gradient shaders. Zero dependencies.",
3
+ "version": "0.7.0",
4
+ "description": "Animated WebGL gradient shaders, plus dither, pixelate, halftone and ASCII effects. Zero dependencies.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -33,7 +33,11 @@
33
33
  "gradient",
34
34
  "mesh-gradient",
35
35
  "animated-gradient",
36
- "background"
36
+ "background",
37
+ "dither",
38
+ "halftone",
39
+ "pixelate",
40
+ "ascii"
37
41
  ],
38
42
  "scripts": {
39
43
  "build": "tsdown",