instantshader 0.6.1 → 0.8.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 +42 -18
- package/dist/index.d.ts +28 -1
- package/dist/index.js +1360 -184
- package/package.json +1 -1
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$
|
|
257
|
+
const FRAGMENT$11 = `
|
|
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$
|
|
394
|
+
fragment: FRAGMENT$11,
|
|
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$
|
|
450
|
+
const FRAGMENT$10 = `
|
|
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$
|
|
704
|
+
fragment: FRAGMENT$10,
|
|
705
705
|
params: [
|
|
706
706
|
{
|
|
707
707
|
key: "scale",
|
|
@@ -709,7 +709,7 @@ const beam = {
|
|
|
709
709
|
min: .5,
|
|
710
710
|
max: 2,
|
|
711
711
|
step: .05,
|
|
712
|
-
default:
|
|
712
|
+
default: .75
|
|
713
713
|
},
|
|
714
714
|
{
|
|
715
715
|
key: "width",
|
|
@@ -717,7 +717,7 @@ const beam = {
|
|
|
717
717
|
min: .04,
|
|
718
718
|
max: .6,
|
|
719
719
|
step: .005,
|
|
720
|
-
default: .
|
|
720
|
+
default: .4
|
|
721
721
|
},
|
|
722
722
|
{
|
|
723
723
|
key: "glow",
|
|
@@ -757,7 +757,7 @@ const beam = {
|
|
|
757
757
|
|
|
758
758
|
//#endregion
|
|
759
759
|
//#region src/shaders/bloom.ts
|
|
760
|
-
const FRAGMENT$
|
|
760
|
+
const FRAGMENT$9 = `
|
|
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$
|
|
919
|
+
fragment: FRAGMENT$9,
|
|
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$
|
|
993
|
+
const FRAGMENT$8 = `
|
|
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$
|
|
1106
|
+
fragment: FRAGMENT$8,
|
|
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$
|
|
1181
|
+
const FRAGMENT$7 = `
|
|
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$
|
|
1286
|
+
fragment: FRAGMENT$7,
|
|
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$
|
|
1378
|
+
const FRAGMENT$6 = `
|
|
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$
|
|
1455
|
+
fragment: FRAGMENT$6,
|
|
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$5 = `
|
|
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$5,
|
|
1583
1583
|
params: [
|
|
1584
1584
|
{
|
|
1585
1585
|
key: "blades",
|
|
@@ -1619,7 +1619,7 @@ const whorl = {
|
|
|
1619
1619
|
min: 0,
|
|
1620
1620
|
max: 1,
|
|
1621
1621
|
step: .01,
|
|
1622
|
-
default:
|
|
1622
|
+
default: 0
|
|
1623
1623
|
},
|
|
1624
1624
|
{
|
|
1625
1625
|
key: "x",
|
|
@@ -1661,89 +1661,641 @@ const whorl = {
|
|
|
1661
1661
|
};
|
|
1662
1662
|
|
|
1663
1663
|
//#endregion
|
|
1664
|
-
//#region src/
|
|
1665
|
-
const
|
|
1666
|
-
uniform float
|
|
1664
|
+
//#region src/shaders/silk.ts
|
|
1665
|
+
const FRAGMENT$4 = `
|
|
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
|
+
}
|
|
1667
1694
|
|
|
1668
1695
|
void main() {
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
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);
|
|
1677
1749
|
}
|
|
1678
1750
|
`;
|
|
1679
|
-
const
|
|
1680
|
-
|
|
1681
|
-
|
|
1751
|
+
const silk = {
|
|
1752
|
+
id: "silk",
|
|
1753
|
+
label: "Silk",
|
|
1754
|
+
fragment: FRAGMENT$4,
|
|
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$3 = `
|
|
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
|
+
}
|
|
1682
1862
|
|
|
1683
1863
|
void main() {
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
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$3,
|
|
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$2 = `
|
|
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$2,
|
|
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$1 = `
|
|
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
|
+
}
|
|
1697
2229
|
}
|
|
1698
|
-
|
|
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);
|
|
1699
2235
|
}
|
|
1700
2236
|
`;
|
|
1701
|
-
const
|
|
1702
|
-
id: "
|
|
1703
|
-
label: "
|
|
1704
|
-
fragment:
|
|
1705
|
-
grid: {
|
|
1706
|
-
cell: (p) => [p.size, p.size],
|
|
1707
|
-
fragment: CELL_FRAGMENT$3
|
|
1708
|
-
},
|
|
2237
|
+
const glint = {
|
|
2238
|
+
id: "glint",
|
|
2239
|
+
label: "Glint",
|
|
2240
|
+
fragment: FRAGMENT$1,
|
|
1709
2241
|
params: [
|
|
1710
2242
|
{
|
|
1711
2243
|
key: "size",
|
|
1712
2244
|
label: "Size",
|
|
1713
|
-
min:
|
|
1714
|
-
max:
|
|
1715
|
-
step:
|
|
1716
|
-
default:
|
|
2245
|
+
min: .01,
|
|
2246
|
+
max: .06,
|
|
2247
|
+
step: .001,
|
|
2248
|
+
default: .03
|
|
1717
2249
|
},
|
|
1718
2250
|
{
|
|
1719
|
-
key: "
|
|
1720
|
-
label: "
|
|
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",
|
|
1721
2261
|
min: 0,
|
|
1722
|
-
max:
|
|
1723
|
-
step:
|
|
1724
|
-
default:
|
|
2262
|
+
max: 1,
|
|
2263
|
+
step: .01,
|
|
2264
|
+
default: .5
|
|
1725
2265
|
},
|
|
1726
2266
|
{
|
|
1727
|
-
key: "
|
|
1728
|
-
label: "
|
|
2267
|
+
key: "angle",
|
|
2268
|
+
label: "Angle",
|
|
1729
2269
|
min: 0,
|
|
1730
|
-
max:
|
|
2270
|
+
max: 360,
|
|
2271
|
+
step: 1,
|
|
2272
|
+
default: 10
|
|
2273
|
+
},
|
|
2274
|
+
{
|
|
2275
|
+
key: "speed",
|
|
2276
|
+
label: "Speed",
|
|
2277
|
+
min: .3,
|
|
2278
|
+
max: 2,
|
|
1731
2279
|
step: .01,
|
|
1732
|
-
default:
|
|
2280
|
+
default: 1
|
|
1733
2281
|
},
|
|
1734
2282
|
{
|
|
1735
|
-
key: "
|
|
1736
|
-
label: "
|
|
1737
|
-
|
|
1738
|
-
|
|
2283
|
+
key: "grain",
|
|
2284
|
+
label: "Grain",
|
|
2285
|
+
min: 0,
|
|
2286
|
+
max: .3,
|
|
2287
|
+
step: .01,
|
|
2288
|
+
default: .06
|
|
1739
2289
|
}
|
|
1740
2290
|
],
|
|
1741
2291
|
randomParams(rand) {
|
|
1742
2292
|
return {
|
|
1743
|
-
size:
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
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
|
|
1747
2299
|
};
|
|
1748
2300
|
}
|
|
1749
2301
|
};
|
|
@@ -1913,6 +2465,13 @@ void main() {
|
|
|
1913
2465
|
* over another effect, there is no ramp coordinate to recover and tone stays
|
|
1914
2466
|
* luma (a gradient map).
|
|
1915
2467
|
*
|
|
2468
|
+
* paletteStop() snaps a tone to the nearest of the palette's stops
|
|
2469
|
+
* (u_paletteStops is the stop count). Halftone and ASCII use it in palette
|
|
2470
|
+
* mode: they do not quantize tone the way Dither's `levels` does, so
|
|
2471
|
+
* without the snap, palette mode over a generator handed back the exact
|
|
2472
|
+
* source color and looked identical to source mode. Snapped, each cell is
|
|
2473
|
+
* one of the palette's own colors, flat.
|
|
2474
|
+
*
|
|
1916
2475
|
* The search is 33 coarse + 17 fine ramp samples. That is affordable
|
|
1917
2476
|
* because tone is evaluated per cell or per lattice point, not per pixel.
|
|
1918
2477
|
* The ends snap to exactly 0 and 1: generators clamp their coordinate, so
|
|
@@ -1925,6 +2484,7 @@ uniform vec3 u_ink;
|
|
|
1925
2484
|
uniform vec3 u_paper;
|
|
1926
2485
|
uniform float u_invert;
|
|
1927
2486
|
uniform float u_sourceIsRamp; // 1 when this layer reads a generator directly
|
|
2487
|
+
uniform float u_paletteStops; // number of colors in the palette
|
|
1928
2488
|
float rampPosition(vec3 c) {
|
|
1929
2489
|
float best = 0.0;
|
|
1930
2490
|
float bd = 1e9;
|
|
@@ -1950,14 +2510,17 @@ float toneOf(vec3 c) {
|
|
|
1950
2510
|
float t = toneIsRamp() ? rampPosition(c) : luma(c);
|
|
1951
2511
|
return u_invert > 0.5 ? 1.0 - t : t;
|
|
1952
2512
|
}
|
|
2513
|
+
vec3 paletteStop(float tone) {
|
|
2514
|
+
float n = max(u_paletteStops - 1.0, 1.0);
|
|
2515
|
+
return palette(floor(tone * n + 0.5) / n);
|
|
2516
|
+
}
|
|
1953
2517
|
vec3 colorize(float tone, vec3 src) {
|
|
1954
2518
|
if (u_colorMode < 0.5) return src;
|
|
1955
2519
|
if (u_colorMode < 1.5) return mix(u_ink, u_paper, tone);
|
|
1956
2520
|
return palette(tone);
|
|
1957
2521
|
}
|
|
1958
2522
|
`;
|
|
1959
|
-
/** The param defs that go with COLOR_MODE.
|
|
1960
|
-
* where paper is a visible ground in every mode (ASCII, Halftone). */
|
|
2523
|
+
/** The param defs that go with COLOR_MODE. */
|
|
1961
2524
|
function colorModeParams(opts) {
|
|
1962
2525
|
return [
|
|
1963
2526
|
{
|
|
@@ -1971,7 +2534,7 @@ function colorModeParams(opts) {
|
|
|
1971
2534
|
},
|
|
1972
2535
|
{
|
|
1973
2536
|
value: "duotone",
|
|
1974
|
-
label: "Duotone"
|
|
2537
|
+
label: opts.singleColour ? "Single colour" : "Duotone"
|
|
1975
2538
|
},
|
|
1976
2539
|
{
|
|
1977
2540
|
value: "palette",
|
|
@@ -1981,33 +2544,368 @@ function colorModeParams(opts) {
|
|
|
1981
2544
|
default: opts.mode
|
|
1982
2545
|
},
|
|
1983
2546
|
{
|
|
1984
|
-
key: "ink",
|
|
1985
|
-
label: "Ink",
|
|
1986
|
-
type: "color",
|
|
1987
|
-
default: opts.ink ?? "#111111",
|
|
2547
|
+
key: "ink",
|
|
2548
|
+
label: opts.singleColour ? "Colour" : "Ink",
|
|
2549
|
+
type: "color",
|
|
2550
|
+
default: opts.ink ?? "#111111",
|
|
2551
|
+
when: {
|
|
2552
|
+
key: "colorMode",
|
|
2553
|
+
in: ["duotone"]
|
|
2554
|
+
}
|
|
2555
|
+
},
|
|
2556
|
+
{
|
|
2557
|
+
key: "paper",
|
|
2558
|
+
label: "Paper",
|
|
2559
|
+
type: "color",
|
|
2560
|
+
default: opts.paper ?? "#f4f1ea",
|
|
2561
|
+
when: opts.paperWhen ?? {
|
|
2562
|
+
key: "colorMode",
|
|
2563
|
+
in: ["duotone"]
|
|
2564
|
+
}
|
|
2565
|
+
},
|
|
2566
|
+
{
|
|
2567
|
+
key: "invert",
|
|
2568
|
+
label: "Invert",
|
|
2569
|
+
type: "bool",
|
|
2570
|
+
default: false
|
|
2571
|
+
}
|
|
2572
|
+
];
|
|
2573
|
+
}
|
|
2574
|
+
/**
|
|
2575
|
+
* Blend modes, W3C Compositing and Blending Level 1, computed on
|
|
2576
|
+
* gamma-encoded sRGB like CSS and every image editor. b is the backdrop, s
|
|
2577
|
+
* the source (the mark or the filter's colour). Overlay is hard light with
|
|
2578
|
+
* the roles swapped. Color dodge: 0 where b is 0, else min(1, b / (1 - s)).
|
|
2579
|
+
*/
|
|
2580
|
+
const BLEND = `
|
|
2581
|
+
uniform float u_blend;
|
|
2582
|
+
vec3 blendScreen(vec3 b, vec3 s) { return b + s - b * s; }
|
|
2583
|
+
vec3 blendHardLight(vec3 b, vec3 s) {
|
|
2584
|
+
return mix(b * 2.0 * s, blendScreen(b, 2.0 * s - 1.0), step(0.5, s));
|
|
2585
|
+
}
|
|
2586
|
+
vec3 blendSoftLight(vec3 b, vec3 s) {
|
|
2587
|
+
vec3 d = mix(sqrt(b), ((16.0 * b - 12.0) * b + 4.0) * b, step(b, vec3(0.25)));
|
|
2588
|
+
return mix(b - (1.0 - 2.0 * s) * b * (1.0 - b), b + (2.0 * s - 1.0) * (d - b), step(0.5, s));
|
|
2589
|
+
}
|
|
2590
|
+
vec3 blendColorDodge(vec3 b, vec3 s) {
|
|
2591
|
+
return min(vec3(1.0), b / max(1.0 - s, vec3(1e-5))) * step(1e-5, b);
|
|
2592
|
+
}
|
|
2593
|
+
vec3 blendOf(vec3 b, vec3 s) {
|
|
2594
|
+
if (u_blend < 0.5) return s;
|
|
2595
|
+
if (u_blend < 1.5) return b * s;
|
|
2596
|
+
if (u_blend < 2.5) return blendScreen(b, s);
|
|
2597
|
+
if (u_blend < 3.5) return blendHardLight(s, b);
|
|
2598
|
+
if (u_blend < 4.5) return blendSoftLight(b, s);
|
|
2599
|
+
return blendColorDodge(b, s);
|
|
2600
|
+
}
|
|
2601
|
+
`;
|
|
2602
|
+
/** Draw stage: the layer below and the blend-then-fade composite. The
|
|
2603
|
+
* blended colour is faded in by coverage x opacity in linear light, so an
|
|
2604
|
+
* antialiased edge keeps its true weight. */
|
|
2605
|
+
const COMPOSITE = `
|
|
2606
|
+
uniform float u_opacity;
|
|
2607
|
+
uniform sampler2D u_picture;
|
|
2608
|
+
vec3 pictureAt(vec2 fc) { return texture2D(u_picture, fc / u_resolution).rgb; }
|
|
2609
|
+
vec3 compositeLinear(vec3 b, vec3 s, float a) {
|
|
2610
|
+
return mix(toLinear(b), toLinear(blendOf(b, s)), clamp(a * u_opacity, 0.0, 1.0));
|
|
2611
|
+
}
|
|
2612
|
+
`;
|
|
2613
|
+
/** Mark effects: what the marks are drawn on. */
|
|
2614
|
+
const GROUND = `
|
|
2615
|
+
uniform float u_ground;
|
|
2616
|
+
vec3 backdropAt(vec2 fc) { return u_ground < 0.5 ? pictureAt(fc) : u_paper; }
|
|
2617
|
+
`;
|
|
2618
|
+
/** Filter effects (a whole replacement picture per cell) over the layer
|
|
2619
|
+
* below. Normal at full opacity hands the colour back untouched: no linear
|
|
2620
|
+
* round trip, so whole-pixel cells stay exact at 1080p and 4K. */
|
|
2621
|
+
const FILTER_COMPOSITE = `
|
|
2622
|
+
vec3 overPicture(vec3 s) {
|
|
2623
|
+
if (u_blend < 0.5 && u_opacity >= 1.0) return s;
|
|
2624
|
+
return toSrgb(compositeLinear(pictureAt(gl_FragCoord.xy), s, 1.0));
|
|
2625
|
+
}
|
|
2626
|
+
`;
|
|
2627
|
+
/**
|
|
2628
|
+
* Cell stage of a mark effect: the cell's tone becomes a mark strength in
|
|
2629
|
+
* 0..1. See SPEC-marks.md, Response.
|
|
2630
|
+
*
|
|
2631
|
+
* Direction: marks grow where they show. On an image ground a darkening
|
|
2632
|
+
* blend (multiply) grows them in the darks and every other blend in the
|
|
2633
|
+
* brights; on paper they grow away from the paper. Invert flips it.
|
|
2634
|
+
*
|
|
2635
|
+
* Motion multiplies the strength, so blank cells stay blank. Its angular
|
|
2636
|
+
* speed is snapped to whole cycles per loop and floored at one: it is the
|
|
2637
|
+
* effect's own motion, and freezing it on a short loop would read as broken.
|
|
2638
|
+
*
|
|
2639
|
+
* No shipped effect declares motion, motionSpeed or motionAmount yet (see
|
|
2640
|
+
* motionParams() below), so these uniforms are unset and read as 0: motion
|
|
2641
|
+
* is "none" and the strength above is unmodulated. The GLSL stays wired up
|
|
2642
|
+
* because motion ships later, once effects get a clock independent of the
|
|
2643
|
+
* source's own speed and pause; see SPEC-marks.md.
|
|
2644
|
+
*/
|
|
2645
|
+
const RESPONSE = `
|
|
2646
|
+
uniform float u_style;
|
|
2647
|
+
uniform float u_exposure;
|
|
2648
|
+
uniform float u_contrast;
|
|
2649
|
+
uniform float u_density;
|
|
2650
|
+
uniform float u_ground;
|
|
2651
|
+
uniform float u_blend;
|
|
2652
|
+
uniform float u_motion;
|
|
2653
|
+
uniform float u_motionSpeed;
|
|
2654
|
+
uniform float u_motionAmount;
|
|
2655
|
+
|
|
2656
|
+
bool growsInBrights() {
|
|
2657
|
+
if (u_ground > 0.5) return luma(u_paper) < 0.5;
|
|
2658
|
+
return abs(u_blend - 1.0) > 0.5;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
float motionAt(vec2 cell) {
|
|
2662
|
+
if (u_motion < 0.5) return 0.0;
|
|
2663
|
+
float w = TAU * u_motionSpeed;
|
|
2664
|
+
if (u_loop > 0.0) w = TAU * max(1.0, floor(u_motionSpeed * u_loop + 0.5)) / u_loop;
|
|
2665
|
+
float ph = w * u_time;
|
|
2666
|
+
if (u_motion < 1.5) return sin(ph);
|
|
2667
|
+
if (u_motion < 2.5) {
|
|
2668
|
+
// 1.5 waves per frame height, travelling along a fixed diagonal.
|
|
2669
|
+
vec2 pos = cell * u_cellRef / u_refSize.y;
|
|
2670
|
+
return sin(ph - TAU * 1.5 * dot(pos, vec2(0.857, 0.514)));
|
|
2671
|
+
}
|
|
2672
|
+
return sin(ph + TAU * hash12(cell + 13.1));
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
float markStrength(vec3 c) {
|
|
2676
|
+
float tone = u_style < 0.5 ? luma(c) : 0.5;
|
|
2677
|
+
tone = clamp(tone * exp2(2.0 * u_exposure), 0.0, 1.0);
|
|
2678
|
+
float s = growsInBrights() ? tone : 1.0 - tone;
|
|
2679
|
+
if (u_invert > 0.5) s = 1.0 - s;
|
|
2680
|
+
s = clamp((s - 0.5) * u_contrast + 0.5, 0.0, 1.0);
|
|
2681
|
+
s = clamp(s * (1.0 + u_motionAmount * motionAt(cellIndex())), 0.0, 1.0);
|
|
2682
|
+
if (hash12(cellIndex() + vec2(71.3, 19.7)) >= u_density) s = 0.0;
|
|
2683
|
+
return s;
|
|
2684
|
+
}
|
|
2685
|
+
`;
|
|
2686
|
+
/** Append only: the uniform is the option's index. */
|
|
2687
|
+
const BLEND_OPTIONS = [
|
|
2688
|
+
{
|
|
2689
|
+
value: "normal",
|
|
2690
|
+
label: "Normal"
|
|
2691
|
+
},
|
|
2692
|
+
{
|
|
2693
|
+
value: "multiply",
|
|
2694
|
+
label: "Multiply"
|
|
2695
|
+
},
|
|
2696
|
+
{
|
|
2697
|
+
value: "screen",
|
|
2698
|
+
label: "Screen"
|
|
2699
|
+
},
|
|
2700
|
+
{
|
|
2701
|
+
value: "overlay",
|
|
2702
|
+
label: "Overlay"
|
|
2703
|
+
},
|
|
2704
|
+
{
|
|
2705
|
+
value: "softLight",
|
|
2706
|
+
label: "Soft light"
|
|
2707
|
+
},
|
|
2708
|
+
{
|
|
2709
|
+
value: "colorDodge",
|
|
2710
|
+
label: "Color dodge"
|
|
2711
|
+
}
|
|
2712
|
+
];
|
|
2713
|
+
function blendParams(d) {
|
|
2714
|
+
return [{
|
|
2715
|
+
key: "blend",
|
|
2716
|
+
label: "Blend",
|
|
2717
|
+
type: "enum",
|
|
2718
|
+
options: BLEND_OPTIONS,
|
|
2719
|
+
default: d.blend
|
|
2720
|
+
}, {
|
|
2721
|
+
key: "opacity",
|
|
2722
|
+
label: "Opacity",
|
|
2723
|
+
min: 0,
|
|
2724
|
+
max: 1,
|
|
2725
|
+
step: .01,
|
|
2726
|
+
default: d.opacity
|
|
2727
|
+
}];
|
|
2728
|
+
}
|
|
2729
|
+
function groundParams(d) {
|
|
2730
|
+
return [
|
|
2731
|
+
{
|
|
2732
|
+
key: "ground",
|
|
2733
|
+
label: "Ground",
|
|
2734
|
+
type: "enum",
|
|
2735
|
+
options: [{
|
|
2736
|
+
value: "image",
|
|
2737
|
+
label: "Image"
|
|
2738
|
+
}, {
|
|
2739
|
+
value: "paper",
|
|
2740
|
+
label: "Paper"
|
|
2741
|
+
}],
|
|
2742
|
+
default: d.ground
|
|
2743
|
+
},
|
|
2744
|
+
...blendParams(d),
|
|
2745
|
+
{
|
|
2746
|
+
key: "blur",
|
|
2747
|
+
label: "Blur",
|
|
2748
|
+
min: 0,
|
|
2749
|
+
max: 40,
|
|
2750
|
+
step: .5,
|
|
2751
|
+
default: 0,
|
|
1988
2752
|
when: {
|
|
1989
|
-
key: "
|
|
1990
|
-
in: ["
|
|
2753
|
+
key: "ground",
|
|
2754
|
+
in: ["image"]
|
|
1991
2755
|
}
|
|
2756
|
+
}
|
|
2757
|
+
];
|
|
2758
|
+
}
|
|
2759
|
+
function responseParams(d) {
|
|
2760
|
+
return [
|
|
2761
|
+
{
|
|
2762
|
+
key: "style",
|
|
2763
|
+
label: "Style",
|
|
2764
|
+
type: "enum",
|
|
2765
|
+
options: [{
|
|
2766
|
+
value: "filled",
|
|
2767
|
+
label: "Filled"
|
|
2768
|
+
}, {
|
|
2769
|
+
value: "uniform",
|
|
2770
|
+
label: "Uniform"
|
|
2771
|
+
}],
|
|
2772
|
+
default: "filled"
|
|
1992
2773
|
},
|
|
1993
2774
|
{
|
|
1994
|
-
key: "
|
|
1995
|
-
label: "
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
in: ["duotone"]
|
|
2001
|
-
} }
|
|
2775
|
+
key: "exposure",
|
|
2776
|
+
label: "Exposure",
|
|
2777
|
+
min: -1,
|
|
2778
|
+
max: 1,
|
|
2779
|
+
step: .01,
|
|
2780
|
+
default: 0
|
|
2002
2781
|
},
|
|
2003
2782
|
{
|
|
2004
|
-
key: "
|
|
2005
|
-
label: "
|
|
2006
|
-
|
|
2007
|
-
|
|
2783
|
+
key: "contrast",
|
|
2784
|
+
label: "Contrast",
|
|
2785
|
+
min: 0,
|
|
2786
|
+
max: 2,
|
|
2787
|
+
step: .01,
|
|
2788
|
+
default: d.contrast
|
|
2789
|
+
},
|
|
2790
|
+
{
|
|
2791
|
+
key: "density",
|
|
2792
|
+
label: "Density",
|
|
2793
|
+
min: 0,
|
|
2794
|
+
max: 1,
|
|
2795
|
+
step: .01,
|
|
2796
|
+
default: 1
|
|
2008
2797
|
}
|
|
2009
2798
|
];
|
|
2010
2799
|
}
|
|
2800
|
+
/** grid.picture for mark effects. A paper ground never reads the picture,
|
|
2801
|
+
* so it gets none; blur only applies to an image ground. */
|
|
2802
|
+
function groundPicture(p) {
|
|
2803
|
+
return p.ground === "image" ? { blur: p.blur } : null;
|
|
2804
|
+
}
|
|
2805
|
+
/** grid.picture for filter effects (pixelate, dither). Normal at full
|
|
2806
|
+
* opacity replaces the picture outright (FILTER_COMPOSITE), so it is not
|
|
2807
|
+
* read and the stack skips rendering it. */
|
|
2808
|
+
function filterPicture(p) {
|
|
2809
|
+
return p.blend === "normal" && p.opacity >= 1 ? null : { blur: 0 };
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
//#endregion
|
|
2813
|
+
//#region src/effects/pixelate.ts
|
|
2814
|
+
const CELL_FRAGMENT$3 = `
|
|
2815
|
+
uniform float u_levels;
|
|
2816
|
+
|
|
2817
|
+
void main() {
|
|
2818
|
+
vec3 c = cellSource().rgb;
|
|
2819
|
+
// Posterize per channel. Rounds to the nearest level, so black and white
|
|
2820
|
+
// stay put. levels = 1 would divide by zero and means nothing; 0 is "off".
|
|
2821
|
+
if (u_levels >= 2.0) {
|
|
2822
|
+
float n = u_levels - 1.0;
|
|
2823
|
+
c = floor(c * n + 0.5) / n;
|
|
2824
|
+
}
|
|
2825
|
+
gl_FragColor = vec4(c, 1.0);
|
|
2826
|
+
}
|
|
2827
|
+
`;
|
|
2828
|
+
const DRAW_FRAGMENT$3 = `
|
|
2829
|
+
uniform float u_gap;
|
|
2830
|
+
uniform vec3 u_gapColor;
|
|
2831
|
+
${BLEND}
|
|
2832
|
+
${COMPOSITE}
|
|
2833
|
+
${FILTER_COMPOSITE}
|
|
2834
|
+
|
|
2835
|
+
void main() {
|
|
2836
|
+
vec3 color = coverage();
|
|
2837
|
+
if (u_gap > 0.0) {
|
|
2838
|
+
// A gap line is u_gap of a cell wide, centred on every cell boundary.
|
|
2839
|
+
// Its weight is the exact share of this output pixel it covers, and the
|
|
2840
|
+
// blend happens in linear light, for the same reason coverage() works
|
|
2841
|
+
// that way: a smoothstep edge in gamma space draws sub-pixel lines far
|
|
2842
|
+
// too heavy, so a small preview would look darker than its own export.
|
|
2843
|
+
vec2 px = cellPx();
|
|
2844
|
+
vec2 d = min(cellLocal(), 1.0 - cellLocal()) * px; // px to nearest boundary
|
|
2845
|
+
vec2 hw = 0.5 * u_gap * px; // line half-width in px
|
|
2846
|
+
vec2 cov = clamp(min(hw, d + 0.5) - max(-hw, d - 0.5), 0.0, 1.0);
|
|
2847
|
+
float m = 1.0 - (1.0 - cov.x) * (1.0 - cov.y);
|
|
2848
|
+
color = toSrgb(mix(toLinear(color), toLinear(u_gapColor), m));
|
|
2849
|
+
}
|
|
2850
|
+
gl_FragColor = vec4(overPicture(color), 1.0);
|
|
2851
|
+
}
|
|
2852
|
+
`;
|
|
2853
|
+
const pixelate = {
|
|
2854
|
+
id: "pixelate",
|
|
2855
|
+
label: "Pixelate",
|
|
2856
|
+
fragment: DRAW_FRAGMENT$3,
|
|
2857
|
+
grid: {
|
|
2858
|
+
cell: (p) => [p.size, p.size],
|
|
2859
|
+
fragment: CELL_FRAGMENT$3,
|
|
2860
|
+
picture: filterPicture
|
|
2861
|
+
},
|
|
2862
|
+
params: [
|
|
2863
|
+
{
|
|
2864
|
+
key: "size",
|
|
2865
|
+
label: "Size",
|
|
2866
|
+
min: 2,
|
|
2867
|
+
max: 160,
|
|
2868
|
+
step: 1,
|
|
2869
|
+
default: 13
|
|
2870
|
+
},
|
|
2871
|
+
{
|
|
2872
|
+
key: "levels",
|
|
2873
|
+
label: "Levels",
|
|
2874
|
+
min: 0,
|
|
2875
|
+
max: 16,
|
|
2876
|
+
step: 1,
|
|
2877
|
+
default: 0
|
|
2878
|
+
},
|
|
2879
|
+
{
|
|
2880
|
+
key: "gap",
|
|
2881
|
+
label: "Gap",
|
|
2882
|
+
min: 0,
|
|
2883
|
+
max: .4,
|
|
2884
|
+
step: .01,
|
|
2885
|
+
default: .08
|
|
2886
|
+
},
|
|
2887
|
+
{
|
|
2888
|
+
key: "gapColor",
|
|
2889
|
+
label: "Gap color",
|
|
2890
|
+
type: "color",
|
|
2891
|
+
default: "#000000"
|
|
2892
|
+
},
|
|
2893
|
+
...blendParams({
|
|
2894
|
+
blend: "normal",
|
|
2895
|
+
opacity: 1
|
|
2896
|
+
})
|
|
2897
|
+
],
|
|
2898
|
+
randomParams(rand) {
|
|
2899
|
+
return {
|
|
2900
|
+
size: Math.round(10 + rand() * 50),
|
|
2901
|
+
levels: rand() < .5 ? 0 : Math.round(3 + rand() * 5),
|
|
2902
|
+
gap: rand() < .6 ? 0 : .05 + rand() * .15,
|
|
2903
|
+
gapColor: "#000000",
|
|
2904
|
+
blend: "normal",
|
|
2905
|
+
opacity: 1
|
|
2906
|
+
};
|
|
2907
|
+
}
|
|
2908
|
+
};
|
|
2011
2909
|
|
|
2012
2910
|
//#endregion
|
|
2013
2911
|
//#region src/effects/blueNoise.ts
|
|
@@ -2098,12 +2996,23 @@ void main() {
|
|
|
2098
2996
|
gl_FragColor = vec4(color, 1.0);
|
|
2099
2997
|
}
|
|
2100
2998
|
`;
|
|
2999
|
+
/** Coverage upscale, then over the picture (FILTER_COMPOSITE). */
|
|
3000
|
+
const DRAW_FRAGMENT$2 = `
|
|
3001
|
+
${BLEND}
|
|
3002
|
+
${COMPOSITE}
|
|
3003
|
+
${FILTER_COMPOSITE}
|
|
3004
|
+
void main() {
|
|
3005
|
+
gl_FragColor = vec4(overPicture(coverage()), 1.0);
|
|
3006
|
+
}
|
|
3007
|
+
`;
|
|
2101
3008
|
const dither = {
|
|
2102
3009
|
id: "dither",
|
|
2103
3010
|
label: "Dither",
|
|
3011
|
+
fragment: DRAW_FRAGMENT$2,
|
|
2104
3012
|
grid: {
|
|
2105
3013
|
cell: (p) => [p.size, p.size],
|
|
2106
|
-
fragment: CELL_FRAGMENT$2
|
|
3014
|
+
fragment: CELL_FRAGMENT$2,
|
|
3015
|
+
picture: filterPicture
|
|
2107
3016
|
},
|
|
2108
3017
|
textures: [{
|
|
2109
3018
|
uniform: "u_noise",
|
|
@@ -2181,7 +3090,15 @@ const dither = {
|
|
|
2181
3090
|
step: 1,
|
|
2182
3091
|
default: 0
|
|
2183
3092
|
},
|
|
2184
|
-
...colorModeParams({
|
|
3093
|
+
...colorModeParams({
|
|
3094
|
+
mode: "duotone",
|
|
3095
|
+
ink: "#000000",
|
|
3096
|
+
paper: "#ffffff"
|
|
3097
|
+
}),
|
|
3098
|
+
...blendParams({
|
|
3099
|
+
blend: "screen",
|
|
3100
|
+
opacity: .6
|
|
3101
|
+
})
|
|
2185
3102
|
],
|
|
2186
3103
|
randomParams(rand) {
|
|
2187
3104
|
const patterns = [
|
|
@@ -2195,7 +3112,13 @@ const dither = {
|
|
|
2195
3112
|
"duotone",
|
|
2196
3113
|
"palette"
|
|
2197
3114
|
];
|
|
2198
|
-
|
|
3115
|
+
const blends = [
|
|
3116
|
+
"normal",
|
|
3117
|
+
"screen",
|
|
3118
|
+
"overlay",
|
|
3119
|
+
"softLight"
|
|
3120
|
+
];
|
|
3121
|
+
const p = {
|
|
2199
3122
|
pattern: patterns[Math.floor(rand() * patterns.length)],
|
|
2200
3123
|
size: Math.round(2 + rand() * 6),
|
|
2201
3124
|
levels: Math.round(2 + rand() * 3),
|
|
@@ -2205,8 +3128,15 @@ const dither = {
|
|
|
2205
3128
|
colorMode: modes[Math.floor(rand() * modes.length)],
|
|
2206
3129
|
ink: "#111111",
|
|
2207
3130
|
paper: "#f4f1ea",
|
|
2208
|
-
invert: false
|
|
3131
|
+
invert: false,
|
|
3132
|
+
blend: blends[Math.floor(rand() * blends.length)],
|
|
3133
|
+
opacity: .6 + rand() * .4
|
|
2209
3134
|
};
|
|
3135
|
+
if (p.blend === "screen" || p.blend === "colorDodge") {
|
|
3136
|
+
p.ink = "#000000";
|
|
3137
|
+
p.paper = "#ffffff";
|
|
3138
|
+
}
|
|
3139
|
+
return p;
|
|
2210
3140
|
}
|
|
2211
3141
|
};
|
|
2212
3142
|
|
|
@@ -2214,24 +3144,27 @@ const dither = {
|
|
|
2214
3144
|
//#region src/effects/halftone.ts
|
|
2215
3145
|
const ROW = .8660254;
|
|
2216
3146
|
const CELL_FRAGMENT$1 = `
|
|
2217
|
-
uniform float u_contrast;
|
|
2218
3147
|
${COLOR_MODE}
|
|
3148
|
+
${RESPONSE}
|
|
2219
3149
|
|
|
2220
3150
|
void main() {
|
|
2221
3151
|
vec3 sc = cellSource().rgb;
|
|
3152
|
+
float ink = markStrength(sc);
|
|
2222
3153
|
|
|
2223
|
-
// Ink
|
|
2224
|
-
//
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
float ink = clamp((1.0 - tone - 0.5) * u_contrast + 0.5, 0.0, 1.0);
|
|
2228
|
-
|
|
2229
|
-
// Ink color. Duotone inks every shape with u_ink (colorize() would mix
|
|
2230
|
-
// 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.
|
|
3154
|
+
// Ink color. Duotone inks every shape with u_ink. Palette inks each dot
|
|
3155
|
+
// with the nearest palette STOP to the un-inverted tone, so Invert flips
|
|
3156
|
+
// sizes and leaves colors, and the dots are flat palette colors rather
|
|
3157
|
+
// than a copy of the source.
|
|
2232
3158
|
vec3 color = sc;
|
|
2233
|
-
if (u_colorMode > 1.5) color =
|
|
3159
|
+
if (u_colorMode > 1.5) color = paletteStop(toneIsRamp() ? rampPosition(sc) : luma(sc));
|
|
2234
3160
|
else if (u_colorMode > 0.5) color = u_ink;
|
|
3161
|
+
else if (u_ground < 0.5) {
|
|
3162
|
+
// Over the picture, a shape in the picture's own colour blends into
|
|
3163
|
+
// itself and vanishes. Lift it most of the way to full brightness, as
|
|
3164
|
+
// ASCII does, so it reads as a vivid version of what is under it.
|
|
3165
|
+
float peak = max(max(sc.r, sc.g), max(sc.b, 1e-3));
|
|
3166
|
+
color = mix(sc, sc / peak, 0.75);
|
|
3167
|
+
}
|
|
2235
3168
|
|
|
2236
3169
|
gl_FragColor = vec4(color, ink);
|
|
2237
3170
|
}
|
|
@@ -2241,8 +3174,10 @@ uniform float u_grid;
|
|
|
2241
3174
|
uniform float u_shape;
|
|
2242
3175
|
uniform float u_radius;
|
|
2243
3176
|
uniform float u_softness;
|
|
2244
|
-
uniform float u_pulse;
|
|
2245
3177
|
${COLOR_MODE}
|
|
3178
|
+
${BLEND}
|
|
3179
|
+
${COMPOSITE}
|
|
3180
|
+
${GROUND}
|
|
2246
3181
|
|
|
2247
3182
|
const float ROW = ${ROW};
|
|
2248
3183
|
|
|
@@ -2251,9 +3186,10 @@ vec4 lattice(vec2 k) {
|
|
|
2251
3186
|
return texture2D(u_cells, (kk + 0.5) / u_gridSize);
|
|
2252
3187
|
}
|
|
2253
3188
|
|
|
2254
|
-
// The shape covering lattice coordinate g most,
|
|
2255
|
-
// linear light. \`aa\` is the width, in lattice units,
|
|
2256
|
-
|
|
3189
|
+
// The shape covering lattice coordinate g most, composited onto backdrop b
|
|
3190
|
+
// (sRGB), returned in linear light. \`aa\` is the width, in lattice units,
|
|
3191
|
+
// edges are ramped over.
|
|
3192
|
+
vec3 shade(vec2 g, float aa, vec3 b) {
|
|
2257
3193
|
bool hex = u_grid > 0.5;
|
|
2258
3194
|
bool line = u_shape > 0.5 && u_shape < 1.5;
|
|
2259
3195
|
// Texel units -> lattice units (1 = the pitch the user set as Size).
|
|
@@ -2276,8 +3212,7 @@ vec3 shade(vec2 g, float aa, float w) {
|
|
|
2276
3212
|
float kl = stride * floor((g.x - 0.5 - par) / stride) + par;
|
|
2277
3213
|
float f = (g.x - 0.5 - kl) / stride;
|
|
2278
3214
|
vec4 v = mix(lattice(vec2(kl, j)), lattice(vec2(kl + stride, j)), f);
|
|
2279
|
-
float
|
|
2280
|
-
float extent = min(0.5 * v.a * unit.y * u_radius * swell, 1.0);
|
|
3215
|
+
float extent = min(0.5 * v.a * unit.y * u_radius, 1.0);
|
|
2281
3216
|
float ramp = aa + u_softness * extent;
|
|
2282
3217
|
float mask = extent <= 0.0 ? 0.0 : clamp((extent - abs(dy)) / ramp + 0.5, 0.0, 1.0);
|
|
2283
3218
|
if (mask > bestMask) { bestMask = mask; bestInk = v.rgb; }
|
|
@@ -2289,20 +3224,23 @@ vec3 shade(vec2 g, float aa, float w) {
|
|
|
2289
3224
|
vec2 k = vec2(k0 + stride * float(di), j);
|
|
2290
3225
|
vec4 v = lattice(k);
|
|
2291
3226
|
vec2 d = vec2((g.x - (k.x + 0.5)) * unit.x, dy);
|
|
2292
|
-
vec2 id = k - 0.5 * u_gridSize;
|
|
2293
|
-
float swell = 1.0 + 0.25 * u_pulse * sin(w * u_time + dot(id * unit, vec2(0.35, 0.22)) + u_seed);
|
|
2294
|
-
|
|
2295
3227
|
float dist;
|
|
2296
3228
|
float extent;
|
|
3229
|
+
// On paper, shape AREA follows ink: the print rule that reproduces
|
|
3230
|
+
// tone. Over the picture there is no tone to reproduce, and area
|
|
3231
|
+
// sizing squeezes the visible size range (half the ink is still 71%
|
|
3232
|
+
// of the full width), so there the width follows strength directly:
|
|
3233
|
+
// empty cells stay empty and full ones fill.
|
|
3234
|
+
float grow = u_ground < 0.5 ? v.a : sqrt(v.a);
|
|
2297
3235
|
if (u_shape < 0.5) {
|
|
2298
3236
|
// Dot: area pi * rho^2 out of a cell of area 1 (square) or ROW (hex).
|
|
2299
3237
|
dist = length(d);
|
|
2300
|
-
extent =
|
|
3238
|
+
extent = grow * sqrt((hex ? ROW : 1.0) / 3.14159265);
|
|
2301
3239
|
} else {
|
|
2302
3240
|
dist = max(abs(d.x), abs(d.y));
|
|
2303
|
-
extent = 0.5 *
|
|
3241
|
+
extent = 0.5 * grow;
|
|
2304
3242
|
}
|
|
2305
|
-
extent = min(extent * u_radius
|
|
3243
|
+
extent = min(extent * u_radius, 1.0);
|
|
2306
3244
|
|
|
2307
3245
|
// Coverage of a straight edge by a box is linear in the distance to
|
|
2308
3246
|
// it. Softness widens that ramp into a blur.
|
|
@@ -2311,22 +3249,12 @@ vec3 shade(vec2 g, float aa, float w) {
|
|
|
2311
3249
|
if (mask > bestMask) { bestMask = mask; bestInk = v.rgb; }
|
|
2312
3250
|
}
|
|
2313
3251
|
}
|
|
2314
|
-
return
|
|
3252
|
+
return compositeLinear(b, bestInk, bestMask);
|
|
2315
3253
|
}
|
|
2316
3254
|
|
|
2317
3255
|
void main() {
|
|
2318
3256
|
float pitchRef = u_grid > 0.5 ? u_cellRef.x * 2.0 : u_cellRef.x;
|
|
2319
|
-
// One output pixel, in lattice units.
|
|
2320
3257
|
float px = 1.0 / (pitchRef * u_outputScale);
|
|
2321
|
-
|
|
2322
|
-
// Pulse: every shape swells and shrinks, phase-shifted across the lattice
|
|
2323
|
-
// so the swell travels through the frame instead of throbbing in lockstep.
|
|
2324
|
-
// Snapped to whole cycles per loop and floored at one: unlike a generator's
|
|
2325
|
-
// slow secondary swell, this is the effect's only motion, so freezing it at
|
|
2326
|
-
// short loops (loopFreq's choice) would read as the knob being broken.
|
|
2327
|
-
float w = 0.8;
|
|
2328
|
-
if (u_loop > 0.0) w = TAU * max(1.0, floor(w * u_loop / TAU + 0.5)) / u_loop;
|
|
2329
|
-
|
|
2330
3258
|
// Supersampled: n x n samples per pixel, each ramping its edges over 1/n
|
|
2331
3259
|
// of a pixel. One sample with a one-pixel linear ramp is exact only for a
|
|
2332
3260
|
// straight edge parallel to the pixel grid; on a 5px preview dot every
|
|
@@ -2341,8 +3269,8 @@ void main() {
|
|
|
2341
3269
|
for (int sy = 0; sy < 4; sy++) {
|
|
2342
3270
|
for (int sx = 0; sx < 4; sx++) {
|
|
2343
3271
|
if (float(sx) >= n || float(sy) >= n) continue;
|
|
2344
|
-
vec2
|
|
2345
|
-
acc += shade(cellCoordAt(
|
|
3272
|
+
vec2 fc = gl_FragCoord.xy + (vec2(float(sx), float(sy)) + 0.5) / n - 0.5;
|
|
3273
|
+
acc += shade(cellCoordAt(fc), px / n, backdropAt(fc));
|
|
2346
3274
|
}
|
|
2347
3275
|
}
|
|
2348
3276
|
gl_FragColor = vec4(toSrgb(acc / (n * n)), 1.0);
|
|
@@ -2359,9 +3287,15 @@ const halftone = {
|
|
|
2359
3287
|
grid: {
|
|
2360
3288
|
cell: cellOf$1,
|
|
2361
3289
|
lattice: (p) => ({ angle: p.angle }),
|
|
3290
|
+
picture: groundPicture,
|
|
2362
3291
|
fragment: CELL_FRAGMENT$1
|
|
2363
3292
|
},
|
|
2364
3293
|
params: [
|
|
3294
|
+
...groundParams({
|
|
3295
|
+
ground: "image",
|
|
3296
|
+
blend: "screen",
|
|
3297
|
+
opacity: 1
|
|
3298
|
+
}),
|
|
2365
3299
|
{
|
|
2366
3300
|
key: "grid",
|
|
2367
3301
|
label: "Grid",
|
|
@@ -2393,7 +3327,7 @@ const halftone = {
|
|
|
2393
3327
|
label: "Square"
|
|
2394
3328
|
}
|
|
2395
3329
|
],
|
|
2396
|
-
default: "
|
|
3330
|
+
default: "square"
|
|
2397
3331
|
},
|
|
2398
3332
|
{
|
|
2399
3333
|
key: "size",
|
|
@@ -2401,7 +3335,7 @@ const halftone = {
|
|
|
2401
3335
|
min: 6,
|
|
2402
3336
|
max: 160,
|
|
2403
3337
|
step: .5,
|
|
2404
|
-
default:
|
|
3338
|
+
default: 20
|
|
2405
3339
|
},
|
|
2406
3340
|
{
|
|
2407
3341
|
key: "angle",
|
|
@@ -2417,7 +3351,7 @@ const halftone = {
|
|
|
2417
3351
|
min: .2,
|
|
2418
3352
|
max: 1.5,
|
|
2419
3353
|
step: .01,
|
|
2420
|
-
default:
|
|
3354
|
+
default: .75
|
|
2421
3355
|
},
|
|
2422
3356
|
{
|
|
2423
3357
|
key: "softness",
|
|
@@ -2425,27 +3359,16 @@ const halftone = {
|
|
|
2425
3359
|
min: 0,
|
|
2426
3360
|
max: 1,
|
|
2427
3361
|
step: .01,
|
|
2428
|
-
default:
|
|
2429
|
-
},
|
|
2430
|
-
{
|
|
2431
|
-
key: "contrast",
|
|
2432
|
-
label: "Contrast",
|
|
2433
|
-
min: 0,
|
|
2434
|
-
max: 2,
|
|
2435
|
-
step: .01,
|
|
2436
|
-
default: 1
|
|
2437
|
-
},
|
|
2438
|
-
{
|
|
2439
|
-
key: "pulse",
|
|
2440
|
-
label: "Pulse",
|
|
2441
|
-
min: 0,
|
|
2442
|
-
max: 1,
|
|
2443
|
-
step: .01,
|
|
2444
|
-
default: 0
|
|
3362
|
+
default: .1
|
|
2445
3363
|
},
|
|
3364
|
+
...responseParams({ contrast: 1.15 }),
|
|
2446
3365
|
...colorModeParams({
|
|
2447
|
-
mode: "
|
|
2448
|
-
|
|
3366
|
+
mode: "source",
|
|
3367
|
+
paperWhen: {
|
|
3368
|
+
key: "ground",
|
|
3369
|
+
in: ["paper"]
|
|
3370
|
+
},
|
|
3371
|
+
singleColour: true
|
|
2449
3372
|
})
|
|
2450
3373
|
],
|
|
2451
3374
|
randomParams(rand) {
|
|
@@ -2460,17 +3383,34 @@ const halftone = {
|
|
|
2460
3383
|
"duotone",
|
|
2461
3384
|
"palette"
|
|
2462
3385
|
];
|
|
3386
|
+
const ground = rand() < .8 ? "image" : "paper";
|
|
3387
|
+
const blends = ground === "paper" ? ["normal", "multiply"] : [
|
|
3388
|
+
"normal",
|
|
3389
|
+
"multiply",
|
|
3390
|
+
"screen",
|
|
3391
|
+
"screen",
|
|
3392
|
+
"overlay",
|
|
3393
|
+
"softLight",
|
|
3394
|
+
"colorDodge"
|
|
3395
|
+
];
|
|
3396
|
+
const blend = blends[Math.floor(rand() * blends.length)];
|
|
2463
3397
|
return {
|
|
3398
|
+
ground,
|
|
3399
|
+
blend,
|
|
3400
|
+
opacity: .6 + rand() * .4,
|
|
3401
|
+
blur: 0,
|
|
2464
3402
|
grid: rand() < .5 ? "square" : "hex",
|
|
2465
3403
|
shape: shapes[Math.floor(rand() * shapes.length)],
|
|
2466
|
-
size: Math.round(
|
|
3404
|
+
size: Math.round(8 + rand() * 30),
|
|
2467
3405
|
angle: Math.round(rand() * 180),
|
|
2468
|
-
radius: .
|
|
2469
|
-
softness: rand() < .7 ?
|
|
2470
|
-
|
|
2471
|
-
|
|
3406
|
+
radius: .7 + rand() * .7,
|
|
3407
|
+
softness: rand() < .7 ? .1 : rand() * .5,
|
|
3408
|
+
style: "filled",
|
|
3409
|
+
exposure: 0,
|
|
3410
|
+
contrast: 1.15,
|
|
3411
|
+
density: 1,
|
|
2472
3412
|
colorMode: modes[Math.floor(rand() * modes.length)],
|
|
2473
|
-
ink: "#111111",
|
|
3413
|
+
ink: blend === "screen" || blend === "colorDodge" ? "#ffffff" : "#111111",
|
|
2474
3414
|
paper: "#f4f1ea",
|
|
2475
3415
|
invert: false
|
|
2476
3416
|
};
|
|
@@ -2708,11 +3648,13 @@ uniform float u_cycleAmount;
|
|
|
2708
3648
|
uniform float u_glyphCount;
|
|
2709
3649
|
uniform sampler2D u_lut;
|
|
2710
3650
|
${COLOR_MODE}
|
|
3651
|
+
${RESPONSE}
|
|
2711
3652
|
|
|
2712
3653
|
void main() {
|
|
2713
3654
|
vec3 sc = cellSource().rgb;
|
|
2714
|
-
|
|
2715
|
-
|
|
3655
|
+
// Glyph weight is the shared mark strength: a stronger mark is a denser
|
|
3656
|
+
// glyph, and a cell the density knob empties gets the blank glyph.
|
|
3657
|
+
float tone = markStrength(sc);
|
|
2716
3658
|
|
|
2717
3659
|
// The lookup places glyphs by measured ink coverage: R = the glyph just
|
|
2718
3660
|
// below this tone, G = the one just above, B = how far between them.
|
|
@@ -2726,13 +3668,22 @@ void main() {
|
|
|
2726
3668
|
float t = u_smooth > 0.5 ? 0.02 + 0.96 * hash12(cellIndex() + 0.5) : 0.5;
|
|
2727
3669
|
float glyph = l.b > t ? hi : lo;
|
|
2728
3670
|
|
|
2729
|
-
// Cycle: every step
|
|
2730
|
-
// similar weight, so the picture flickers but stays
|
|
3671
|
+
// Cycle: every step a share of the cells re-rolls its glyph among
|
|
3672
|
+
// neighbours of similar weight, so the picture flickers but stays
|
|
3673
|
+
// readable. cycleAmount is that share, and also how far a cell may move
|
|
3674
|
+
// (one glyph at small amounts, up to a quarter of the set at 1): a small
|
|
3675
|
+
// amount must flicker a few cells, not round every move down to nothing,
|
|
3676
|
+
// which is what a pure distance scale did on a 10-glyph set. Blank cells
|
|
2731
3677
|
// stay blank, or dark areas would fill with sparks.
|
|
2732
3678
|
if (u_cycle > 0.0 && glyph > 0.5) {
|
|
2733
|
-
float
|
|
2734
|
-
float
|
|
2735
|
-
|
|
3679
|
+
float st = loopStep(u_cycle);
|
|
3680
|
+
float pick = hash12(cellIndex() + vec2(11.0, 53.0) * st + u_seed + 3.1);
|
|
3681
|
+
if (pick < u_cycleAmount) {
|
|
3682
|
+
float h = hash12(cellIndex() + vec2(37.0, 17.0) * st + u_seed);
|
|
3683
|
+
float reach = max(1.0, floor(u_cycleAmount * u_glyphCount * 0.25));
|
|
3684
|
+
float move = (h < 0.5 ? -1.0 : 1.0) * (1.0 + floor(fract(h * 2.0) * reach));
|
|
3685
|
+
glyph = clamp(glyph + move, 1.0, u_glyphCount - 1.0);
|
|
3686
|
+
}
|
|
2736
3687
|
}
|
|
2737
3688
|
|
|
2738
3689
|
// Glyph color. Tone already lives in the glyph's density, so source colors
|
|
@@ -2745,7 +3696,8 @@ void main() {
|
|
|
2745
3696
|
} else if (u_colorMode < 1.5) {
|
|
2746
3697
|
color = u_ink;
|
|
2747
3698
|
} else {
|
|
2748
|
-
|
|
3699
|
+
// Nearest palette stop, flat: see paletteStop() in chunks.ts.
|
|
3700
|
+
color = paletteStop(toneIsRamp() ? rampPosition(sc) : luma(sc));
|
|
2749
3701
|
}
|
|
2750
3702
|
|
|
2751
3703
|
gl_FragColor = vec4(color, glyph / 255.0);
|
|
@@ -2757,6 +3709,9 @@ uniform vec2 u_atlasGrid; // glyph columns, rows
|
|
|
2757
3709
|
uniform vec2 u_atlasSize; // atlas size in texels
|
|
2758
3710
|
uniform vec2 u_glyphPx; // one glyph's size in atlas texels (without gutter)
|
|
2759
3711
|
${COLOR_MODE}
|
|
3712
|
+
${BLEND}
|
|
3713
|
+
${COMPOSITE}
|
|
3714
|
+
${GROUND}
|
|
2760
3715
|
|
|
2761
3716
|
void main() {
|
|
2762
3717
|
vec4 cell = cellValue(cellIndexAt());
|
|
@@ -2785,8 +3740,7 @@ void main() {
|
|
|
2785
3740
|
}
|
|
2786
3741
|
a /= n * n;
|
|
2787
3742
|
|
|
2788
|
-
|
|
2789
|
-
gl_FragColor = vec4(color, 1.0);
|
|
3743
|
+
gl_FragColor = vec4(toSrgb(compositeLinear(backdropAt(gl_FragCoord.xy), cell.rgb, a)), 1.0);
|
|
2790
3744
|
}
|
|
2791
3745
|
`;
|
|
2792
3746
|
function cellOf(p) {
|
|
@@ -2802,7 +3756,8 @@ const ascii = {
|
|
|
2802
3756
|
fragment: DRAW_FRAGMENT,
|
|
2803
3757
|
grid: {
|
|
2804
3758
|
cell: cellOf,
|
|
2805
|
-
fragment: CELL_FRAGMENT
|
|
3759
|
+
fragment: CELL_FRAGMENT,
|
|
3760
|
+
picture: groundPicture
|
|
2806
3761
|
},
|
|
2807
3762
|
textures: [{
|
|
2808
3763
|
uniform: "u_atlas",
|
|
@@ -2822,6 +3777,11 @@ const ascii = {
|
|
|
2822
3777
|
})
|
|
2823
3778
|
}],
|
|
2824
3779
|
params: [
|
|
3780
|
+
...groundParams({
|
|
3781
|
+
ground: "image",
|
|
3782
|
+
blend: "colorDodge",
|
|
3783
|
+
opacity: 1
|
|
3784
|
+
}),
|
|
2825
3785
|
{
|
|
2826
3786
|
key: "charset",
|
|
2827
3787
|
label: "Characters",
|
|
@@ -2860,7 +3820,7 @@ const ascii = {
|
|
|
2860
3820
|
min: 8,
|
|
2861
3821
|
max: 96,
|
|
2862
3822
|
step: 1,
|
|
2863
|
-
default:
|
|
3823
|
+
default: 10
|
|
2864
3824
|
},
|
|
2865
3825
|
{
|
|
2866
3826
|
key: "smooth",
|
|
@@ -2874,7 +3834,7 @@ const ascii = {
|
|
|
2874
3834
|
min: 0,
|
|
2875
3835
|
max: 12,
|
|
2876
3836
|
step: 1,
|
|
2877
|
-
default:
|
|
3837
|
+
default: 2
|
|
2878
3838
|
},
|
|
2879
3839
|
{
|
|
2880
3840
|
key: "cycleAmount",
|
|
@@ -2882,13 +3842,18 @@ const ascii = {
|
|
|
2882
3842
|
min: 0,
|
|
2883
3843
|
max: 1,
|
|
2884
3844
|
step: .01,
|
|
2885
|
-
default: .
|
|
3845
|
+
default: .2
|
|
2886
3846
|
},
|
|
3847
|
+
...responseParams({ contrast: 1.2 }),
|
|
2887
3848
|
...colorModeParams({
|
|
2888
3849
|
mode: "source",
|
|
2889
3850
|
ink: "#e8ffe8",
|
|
2890
3851
|
paper: "#000000",
|
|
2891
|
-
|
|
3852
|
+
paperWhen: {
|
|
3853
|
+
key: "ground",
|
|
3854
|
+
in: ["paper"]
|
|
3855
|
+
},
|
|
3856
|
+
singleColour: true
|
|
2892
3857
|
})
|
|
2893
3858
|
],
|
|
2894
3859
|
randomParams(rand) {
|
|
@@ -2904,12 +3869,28 @@ const ascii = {
|
|
|
2904
3869
|
"duotone",
|
|
2905
3870
|
"palette"
|
|
2906
3871
|
];
|
|
3872
|
+
const ground = rand() < .8 ? "image" : "paper";
|
|
3873
|
+
const blends = ground === "paper" ? ["normal"] : [
|
|
3874
|
+
"normal",
|
|
3875
|
+
"screen",
|
|
3876
|
+
"screen",
|
|
3877
|
+
"overlay",
|
|
3878
|
+
"colorDodge"
|
|
3879
|
+
];
|
|
2907
3880
|
return {
|
|
3881
|
+
ground,
|
|
3882
|
+
blend: blends[Math.floor(rand() * blends.length)],
|
|
3883
|
+
opacity: .7 + rand() * .3,
|
|
3884
|
+
blur: 0,
|
|
2908
3885
|
charset: sets[Math.floor(rand() * sets.length)],
|
|
2909
3886
|
size: Math.round(14 + rand() * 30),
|
|
2910
3887
|
smooth: true,
|
|
2911
|
-
cycle:
|
|
2912
|
-
cycleAmount: .
|
|
3888
|
+
cycle: 2,
|
|
3889
|
+
cycleAmount: .2,
|
|
3890
|
+
style: "filled",
|
|
3891
|
+
exposure: 0,
|
|
3892
|
+
contrast: 1,
|
|
3893
|
+
density: 1,
|
|
2913
3894
|
colorMode: modes[Math.floor(rand() * modes.length)],
|
|
2914
3895
|
ink: "#e8ffe8",
|
|
2915
3896
|
paper: "#000000",
|
|
@@ -2918,6 +3899,77 @@ const ascii = {
|
|
|
2918
3899
|
}
|
|
2919
3900
|
};
|
|
2920
3901
|
|
|
3902
|
+
//#endregion
|
|
3903
|
+
//#region src/effects/tint.ts
|
|
3904
|
+
const FRAGMENT = `
|
|
3905
|
+
uniform float u_mode;
|
|
3906
|
+
uniform vec3 u_dark;
|
|
3907
|
+
uniform vec3 u_light;
|
|
3908
|
+
uniform float u_amount;
|
|
3909
|
+
void main() {
|
|
3910
|
+
vec3 c = source(v_uv).rgb;
|
|
3911
|
+
float t = luma(c);
|
|
3912
|
+
vec3 mapped = u_mode < 0.5 ? mix(u_dark, u_light, t) : palette(t);
|
|
3913
|
+
gl_FragColor = vec4(toSrgb(mix(toLinear(c), toLinear(mapped), u_amount)), 1.0);
|
|
3914
|
+
}
|
|
3915
|
+
`;
|
|
3916
|
+
const tint = {
|
|
3917
|
+
id: "tint",
|
|
3918
|
+
label: "Tint",
|
|
3919
|
+
fragment: FRAGMENT,
|
|
3920
|
+
params: [
|
|
3921
|
+
{
|
|
3922
|
+
key: "mode",
|
|
3923
|
+
label: "Mode",
|
|
3924
|
+
type: "enum",
|
|
3925
|
+
options: [{
|
|
3926
|
+
value: "duotone",
|
|
3927
|
+
label: "Duotone"
|
|
3928
|
+
}, {
|
|
3929
|
+
value: "palette",
|
|
3930
|
+
label: "Palette"
|
|
3931
|
+
}],
|
|
3932
|
+
default: "duotone"
|
|
3933
|
+
},
|
|
3934
|
+
{
|
|
3935
|
+
key: "dark",
|
|
3936
|
+
label: "Dark",
|
|
3937
|
+
type: "color",
|
|
3938
|
+
default: "#1b1a4a",
|
|
3939
|
+
when: {
|
|
3940
|
+
key: "mode",
|
|
3941
|
+
in: ["duotone"]
|
|
3942
|
+
}
|
|
3943
|
+
},
|
|
3944
|
+
{
|
|
3945
|
+
key: "light",
|
|
3946
|
+
label: "Light",
|
|
3947
|
+
type: "color",
|
|
3948
|
+
default: "#f2c6a0",
|
|
3949
|
+
when: {
|
|
3950
|
+
key: "mode",
|
|
3951
|
+
in: ["duotone"]
|
|
3952
|
+
}
|
|
3953
|
+
},
|
|
3954
|
+
{
|
|
3955
|
+
key: "amount",
|
|
3956
|
+
label: "Amount",
|
|
3957
|
+
min: 0,
|
|
3958
|
+
max: 1,
|
|
3959
|
+
step: .01,
|
|
3960
|
+
default: 1
|
|
3961
|
+
}
|
|
3962
|
+
],
|
|
3963
|
+
randomParams(rand) {
|
|
3964
|
+
return {
|
|
3965
|
+
mode: rand() < .5 ? "duotone" : "palette",
|
|
3966
|
+
dark: "#1b1a4a",
|
|
3967
|
+
light: "#f2c6a0",
|
|
3968
|
+
amount: .6 + rand() * .4
|
|
3969
|
+
};
|
|
3970
|
+
}
|
|
3971
|
+
};
|
|
3972
|
+
|
|
2921
3973
|
//#endregion
|
|
2922
3974
|
//#region src/registry.ts
|
|
2923
3975
|
const shaders = [
|
|
@@ -2927,7 +3979,11 @@ const shaders = [
|
|
|
2927
3979
|
halo,
|
|
2928
3980
|
strata,
|
|
2929
3981
|
dune,
|
|
2930
|
-
whorl
|
|
3982
|
+
whorl,
|
|
3983
|
+
silk,
|
|
3984
|
+
nacre,
|
|
3985
|
+
burst,
|
|
3986
|
+
glint
|
|
2931
3987
|
];
|
|
2932
3988
|
function getShader(id) {
|
|
2933
3989
|
return shaders.find((s) => s.id === id);
|
|
@@ -2939,7 +3995,8 @@ const effects = [
|
|
|
2939
3995
|
pixelate,
|
|
2940
3996
|
dither,
|
|
2941
3997
|
halftone,
|
|
2942
|
-
ascii
|
|
3998
|
+
ascii,
|
|
3999
|
+
tint
|
|
2943
4000
|
];
|
|
2944
4001
|
function getEffect(id) {
|
|
2945
4002
|
return effects.find((e) => e.id === id);
|
|
@@ -3537,6 +4594,41 @@ function resolveParams(def, overrides) {
|
|
|
3537
4594
|
|
|
3538
4595
|
//#endregion
|
|
3539
4596
|
//#region src/stack.ts
|
|
4597
|
+
/** Backdrop blur, pass 1: box-average the frame down by k (4x4 LINEAR taps
|
|
4598
|
+
* spread over one small texel). */
|
|
4599
|
+
const BLUR_DOWN_FRAGMENT = `
|
|
4600
|
+
uniform sampler2D u_source;
|
|
4601
|
+
uniform vec2 u_span;
|
|
4602
|
+
void main() {
|
|
4603
|
+
vec3 acc = vec3(0.0);
|
|
4604
|
+
for (int j = 0; j < 4; j++) {
|
|
4605
|
+
for (int i = 0; i < 4; i++) {
|
|
4606
|
+
vec2 o = ((vec2(float(i), float(j)) + 0.5) / 4.0 - 0.5) * u_span;
|
|
4607
|
+
acc += toLinear(texture2D(u_source, v_uv + o).rgb);
|
|
4608
|
+
}
|
|
4609
|
+
}
|
|
4610
|
+
gl_FragColor = vec4(toSrgb(acc / 16.0), 1.0);
|
|
4611
|
+
}
|
|
4612
|
+
`;
|
|
4613
|
+
/** Backdrop blur, passes 2 and 3: one axis of a 25-tap Gaussian, in linear
|
|
4614
|
+
* light. Sigma is at most 4 small texels (below 2 only when blurPx < 2),
|
|
4615
|
+
* so +-12 taps reach 3 sigma. */
|
|
4616
|
+
const BLUR_PASS_FRAGMENT = `
|
|
4617
|
+
uniform sampler2D u_source;
|
|
4618
|
+
uniform vec2 u_step;
|
|
4619
|
+
uniform float u_sigma;
|
|
4620
|
+
void main() {
|
|
4621
|
+
vec3 acc = vec3(0.0);
|
|
4622
|
+
float wsum = 0.0;
|
|
4623
|
+
for (int i = -12; i <= 12; i++) {
|
|
4624
|
+
float x = float(i);
|
|
4625
|
+
float w = exp(-0.5 * x * x / (u_sigma * u_sigma));
|
|
4626
|
+
acc += w * toLinear(texture2D(u_source, v_uv + x * u_step).rgb);
|
|
4627
|
+
wsum += w;
|
|
4628
|
+
}
|
|
4629
|
+
gl_FragColor = vec4(toSrgb(acc / wsum), 1.0);
|
|
4630
|
+
}
|
|
4631
|
+
`;
|
|
3540
4632
|
const QUAD_VERTICES = new Float32Array([
|
|
3541
4633
|
-1,
|
|
3542
4634
|
-1,
|
|
@@ -3788,6 +4880,69 @@ function createStackRenderer(opts) {
|
|
|
3788
4880
|
}
|
|
3789
4881
|
cellTargetsUsed.clear();
|
|
3790
4882
|
}
|
|
4883
|
+
const blurTargets = /* @__PURE__ */ new Map();
|
|
4884
|
+
const blurTargetsUsed = /* @__PURE__ */ new Set();
|
|
4885
|
+
let blurDownProgram = null;
|
|
4886
|
+
let blurPassProgram = null;
|
|
4887
|
+
/**
|
|
4888
|
+
* `input` blurred by `blurRef` reference px. Done at 1/k resolution with
|
|
4889
|
+
* k chosen so sigma is at most 4 small texels (below 2 only when blurPx
|
|
4890
|
+
* < 2, where k floors to 1): the cost is the same at every output size,
|
|
4891
|
+
* and the small target is ~2 * ref / blur texels whatever the output
|
|
4892
|
+
* scale, so preview and export blur alike.
|
|
4893
|
+
*/
|
|
4894
|
+
function blurred(input, blurRef) {
|
|
4895
|
+
const blurPx = blurRef * outputScale(canvas.width, canvas.height);
|
|
4896
|
+
const k = Math.max(1, Math.floor(blurPx / 2));
|
|
4897
|
+
const w = Math.max(1, Math.round(canvas.width / k));
|
|
4898
|
+
const h = Math.max(1, Math.round(canvas.height / k));
|
|
4899
|
+
const key = `${w}x${h}`;
|
|
4900
|
+
blurTargetsUsed.add(key);
|
|
4901
|
+
let pair = blurTargets.get(key);
|
|
4902
|
+
if (!pair) {
|
|
4903
|
+
pair = [createTarget(gl, w, h, "linear"), createTarget(gl, w, h, "linear")];
|
|
4904
|
+
blurTargets.set(key, pair);
|
|
4905
|
+
}
|
|
4906
|
+
const [a, b] = pair;
|
|
4907
|
+
blurDownProgram ??= createProgram(gl, VERTEX_SHADER, BASE_UNIFORMS + COLOR_MATH + BLUR_DOWN_FRAGMENT);
|
|
4908
|
+
blurPassProgram ??= createProgram(gl, VERTEX_SHADER, BASE_UNIFORMS + COLOR_MATH + BLUR_PASS_FRAGMENT);
|
|
4909
|
+
bindOutput(a);
|
|
4910
|
+
useProgram(blurDownProgram, [w, h]);
|
|
4911
|
+
bindTexture(1, input.texture);
|
|
4912
|
+
gl.uniform1i(blurDownProgram.loc("u_source"), 1);
|
|
4913
|
+
gl.uniform2f(blurDownProgram.loc("u_span"), k / canvas.width, k / canvas.height);
|
|
4914
|
+
draw();
|
|
4915
|
+
const passes = [[
|
|
4916
|
+
a,
|
|
4917
|
+
b,
|
|
4918
|
+
1 / w,
|
|
4919
|
+
0
|
|
4920
|
+
], [
|
|
4921
|
+
b,
|
|
4922
|
+
a,
|
|
4923
|
+
0,
|
|
4924
|
+
1 / h
|
|
4925
|
+
]];
|
|
4926
|
+
for (const [src, dst, dx, dy] of passes) {
|
|
4927
|
+
bindOutput(dst);
|
|
4928
|
+
useProgram(blurPassProgram, [w, h]);
|
|
4929
|
+
bindTexture(1, src.texture);
|
|
4930
|
+
gl.uniform1i(blurPassProgram.loc("u_source"), 1);
|
|
4931
|
+
gl.uniform2f(blurPassProgram.loc("u_step"), dx, dy);
|
|
4932
|
+
gl.uniform1f(blurPassProgram.loc("u_sigma"), blurPx / k);
|
|
4933
|
+
draw();
|
|
4934
|
+
}
|
|
4935
|
+
return a;
|
|
4936
|
+
}
|
|
4937
|
+
function pruneBlurTargets() {
|
|
4938
|
+
for (const [key, pair] of blurTargets) {
|
|
4939
|
+
if (blurTargetsUsed.has(key)) continue;
|
|
4940
|
+
deleteTarget(gl, pair[0]);
|
|
4941
|
+
deleteTarget(gl, pair[1]);
|
|
4942
|
+
blurTargets.delete(key);
|
|
4943
|
+
}
|
|
4944
|
+
blurTargetsUsed.clear();
|
|
4945
|
+
}
|
|
3791
4946
|
let timeSec = 0;
|
|
3792
4947
|
function bindOutput(target) {
|
|
3793
4948
|
gl.bindFramebuffer(gl.FRAMEBUFFER, target ? target.framebuffer : null);
|
|
@@ -3941,6 +5096,7 @@ function createStackRenderer(opts) {
|
|
|
3941
5096
|
function applyEffectUniforms(p, state, grid, overGenerator) {
|
|
3942
5097
|
const ref = refSize(canvas.width, canvas.height);
|
|
3943
5098
|
gl.uniform1f(p.loc("u_sourceIsRamp"), overGenerator ? 1 : 0);
|
|
5099
|
+
gl.uniform1f(p.loc("u_paletteStops"), colors.length);
|
|
3944
5100
|
gl.uniform2f(p.loc("u_refSize"), ref[0], ref[1]);
|
|
3945
5101
|
gl.uniform1f(p.loc("u_outputScale"), outputScale(canvas.width, canvas.height));
|
|
3946
5102
|
if (grid) {
|
|
@@ -3979,7 +5135,6 @@ function createStackRenderer(opts) {
|
|
|
3979
5135
|
let input = null;
|
|
3980
5136
|
active.forEach((state, i) => {
|
|
3981
5137
|
const last = i === active.length - 1;
|
|
3982
|
-
const outIndex = input === frameTargets[0] ? 1 : 0;
|
|
3983
5138
|
const overGenerator = input === null && source.kind === "generator";
|
|
3984
5139
|
syncTextures(state);
|
|
3985
5140
|
if (state.def.grid) {
|
|
@@ -3987,6 +5142,15 @@ function createStackRenderer(opts) {
|
|
|
3987
5142
|
const [cellIn, cellOut] = cellTargetPair(grid);
|
|
3988
5143
|
if (input === null) renderSourceToCells(grid, cellIn);
|
|
3989
5144
|
else downsampleToCells(input, grid, cellIn);
|
|
5145
|
+
const pic = state.def.grid.picture;
|
|
5146
|
+
const want = pic === true ? { blur: 0 } : pic ? pic(state.params) : null;
|
|
5147
|
+
if (want && input === null) {
|
|
5148
|
+
input = frameTarget(0);
|
|
5149
|
+
renderSourceToFrame(input);
|
|
5150
|
+
}
|
|
5151
|
+
let picture = want ? input : null;
|
|
5152
|
+
if (picture && want && want.blur > 0) picture = blurred(picture, want.blur);
|
|
5153
|
+
const outIndex = input === frameTargets[0] ? 1 : 0;
|
|
3990
5154
|
const cellProgram = state.cell;
|
|
3991
5155
|
bindOutput(cellOut);
|
|
3992
5156
|
useProgram(cellProgram, frameSize);
|
|
@@ -4001,6 +5165,10 @@ function createStackRenderer(opts) {
|
|
|
4001
5165
|
applyEffectUniforms(drawProgram, state, grid, overGenerator);
|
|
4002
5166
|
bindTexture(2, cellOut.texture);
|
|
4003
5167
|
gl.uniform1i(drawProgram.loc("u_cells"), 2);
|
|
5168
|
+
if (picture) {
|
|
5169
|
+
bindTexture(1, picture.texture);
|
|
5170
|
+
gl.uniform1i(drawProgram.loc("u_picture"), 1);
|
|
5171
|
+
}
|
|
4004
5172
|
draw();
|
|
4005
5173
|
input = out;
|
|
4006
5174
|
} else {
|
|
@@ -4020,6 +5188,7 @@ function createStackRenderer(opts) {
|
|
|
4020
5188
|
}
|
|
4021
5189
|
});
|
|
4022
5190
|
pruneCellTargets();
|
|
5191
|
+
pruneBlurTargets();
|
|
4023
5192
|
}
|
|
4024
5193
|
function dispose() {
|
|
4025
5194
|
for (const state of layers) disposeLayer(state);
|
|
@@ -4028,6 +5197,8 @@ function createStackRenderer(opts) {
|
|
|
4028
5197
|
mediaFrameProgram?.dispose();
|
|
4029
5198
|
mediaCellProgram?.dispose();
|
|
4030
5199
|
downsampleProgram?.dispose();
|
|
5200
|
+
blurDownProgram?.dispose();
|
|
5201
|
+
blurPassProgram?.dispose();
|
|
4031
5202
|
deleteTarget(gl, frameTargets[0]);
|
|
4032
5203
|
deleteTarget(gl, frameTargets[1]);
|
|
4033
5204
|
frameTargets = [null, null];
|
|
@@ -4036,6 +5207,11 @@ function createStackRenderer(opts) {
|
|
|
4036
5207
|
deleteTarget(gl, pair[1]);
|
|
4037
5208
|
}
|
|
4038
5209
|
cellTargets.clear();
|
|
5210
|
+
for (const pair of blurTargets.values()) {
|
|
5211
|
+
deleteTarget(gl, pair[0]);
|
|
5212
|
+
deleteTarget(gl, pair[1]);
|
|
5213
|
+
}
|
|
5214
|
+
blurTargets.clear();
|
|
4039
5215
|
gl.deleteTexture(mediaTexture);
|
|
4040
5216
|
gl.deleteTexture(paletteTexture);
|
|
4041
5217
|
gl.deleteBuffer(quadBuffer);
|
|
@@ -4364,4 +5540,4 @@ function createRenderer(opts) {
|
|
|
4364
5540
|
}
|
|
4365
5541
|
|
|
4366
5542
|
//#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 };
|
|
5543
|
+
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, tint, whorl };
|