instantshader 0.3.0 → 0.4.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/dist/index.d.ts CHANGED
@@ -123,6 +123,9 @@ declare const flow: ShaderDef;
123
123
  //#region src/shaders/beam.d.ts
124
124
  declare const beam: ShaderDef;
125
125
  //#endregion
126
+ //#region src/shaders/bloom.d.ts
127
+ declare const bloom: ShaderDef;
128
+ //#endregion
126
129
  //#region src/registry.d.ts
127
130
  declare const shaders: readonly ShaderDef[];
128
131
  declare function getShader(id: string): ShaderDef | undefined;
@@ -188,4 +191,4 @@ declare function renderGradientFrame(opts: {
188
191
  */
189
192
  declare function buildPaletteRamp(colors: string[]): Uint8Array;
190
193
  //#endregion
191
- export { type MountHandle, type MountOptions, type ParamDef, type RenderFrameResult, type Renderer, type RendererOptions, type ShaderDef, beam, buildPaletteRamp, createRenderer, flow, getShader, mountGradient, renderGradientFrame, shaders };
194
+ export { type MountHandle, type MountOptions, type ParamDef, type RenderFrameResult, type Renderer, type RendererOptions, type ShaderDef, beam, bloom, buildPaletteRamp, createRenderer, flow, getShader, mountGradient, renderGradientFrame, shaders };
package/dist/index.js CHANGED
@@ -190,7 +190,7 @@ float grain(vec2 uv, float time) {
190
190
 
191
191
  //#endregion
192
192
  //#region src/shaders/flow.ts
193
- const FRAGMENT$1 = `
193
+ const FRAGMENT$2 = `
194
194
  uniform float u_scale;
195
195
  uniform float u_curl;
196
196
  uniform float u_drift;
@@ -327,7 +327,7 @@ void main() {
327
327
  const flow = {
328
328
  id: "flow",
329
329
  label: "Flow",
330
- fragment: FRAGMENT$1,
330
+ fragment: FRAGMENT$2,
331
331
  params: [
332
332
  {
333
333
  key: "scale",
@@ -383,7 +383,7 @@ const flow = {
383
383
 
384
384
  //#endregion
385
385
  //#region src/shaders/beam.ts
386
- const FRAGMENT = `
386
+ const FRAGMENT$1 = `
387
387
  uniform float u_scale;
388
388
  uniform float u_width;
389
389
  uniform float u_glow;
@@ -637,7 +637,7 @@ void main() {
637
637
  const beam = {
638
638
  id: "beam",
639
639
  label: "Beam",
640
- fragment: FRAGMENT,
640
+ fragment: FRAGMENT$1,
641
641
  params: [
642
642
  {
643
643
  key: "scale",
@@ -691,9 +691,246 @@ const beam = {
691
691
  }
692
692
  };
693
693
 
694
+ //#endregion
695
+ //#region src/shaders/bloom.ts
696
+ const FRAGMENT = `
697
+ uniform float u_scale;
698
+ uniform float u_petals;
699
+ uniform float u_pinch;
700
+ uniform float u_bend;
701
+ uniform float u_sway;
702
+ uniform float u_colorflow;
703
+ uniform float u_grain;
704
+
705
+ ${SIMPLEX_2D}
706
+ ${GRAIN}
707
+
708
+ const float PI = 3.14159265;
709
+
710
+ // Triangle-wave ping-pong: period 2, range [0,1], IDENTITY on [0,1] when the
711
+ // phase is zero -- so colorflow=off renders the plain radial ramp, and any
712
+ // moving phase slides color bands through the pattern with no fract() seam
713
+ // anywhere (the wave reflects instead of jumping).
714
+ float tri(float x) {
715
+ return 1.0 - abs(1.0 - 2.0 * fract(0.5 * x));
716
+ }
717
+
718
+ void main() {
719
+ vec2 uv = worldUv();
720
+
721
+ // Isotropic frame, same construction as beam: worldUv() is 0-1 on both
722
+ // axes of a 16:9 world, so raw uv would render every petal 1.78x wider
723
+ // than tall and the fan would smear sideways. Scaling x by the world
724
+ // aspect makes a unit circle a screen circle; halfIso is the frame's
725
+ // visible half-extents in that space (cover-fit aware), used to place the
726
+ // origin against the actual frame edges at any canvas aspect.
727
+ float worldAspect = 1000.0 / 562.5;
728
+ float canvasAspect = u_resolution.x / u_resolution.y;
729
+ vec2 iso = (uv - 0.5) * vec2(worldAspect, 1.0);
730
+ vec2 halfIso = vec2(0.5 * min(1.0, canvasAspect / worldAspect),
731
+ 0.5 * min(1.0, worldAspect / canvasAspect))
732
+ * vec2(worldAspect, 1.0);
733
+
734
+ // ---- per-instance composition, all derived from u_seed ----------------
735
+ float seedRow = u_seed * 0.61 + 7.0;
736
+
737
+ // Origin: anywhere along the bottom edge (references use bottom-left and
738
+ // bottom-center), from slightly inside the frame (the hot core peeks in,
739
+ // as in all three references) to a bit below it.
740
+ float ox = snoise(vec2(seedRow, 1.3)) * 0.8 * halfIso.x;
741
+ // At or just below the edge -- never inside: an origin inside the frame
742
+ // shows petals radiating in every direction (a starburst), while the
743
+ // references always crop the fan at its base.
744
+ float oy = -halfIso.y * (1.02 + 0.11 * (1.0 + snoise(vec2(seedRow, 9.1))));
745
+ vec2 origin = vec2(ox, oy);
746
+
747
+ // Fan rotation. The rose envelope has k lobes evenly spaced around the
748
+ // full circle, so any rotation leaves several pointing into the frame;
749
+ // this just varies WHICH part of the fan the frame crops.
750
+ float theta0 = snoise(vec2(seedRow, 5.2)) * PI;
751
+
752
+ vec2 rel = iso - origin;
753
+ float r = length(rel);
754
+ float theta = atan(rel.y, rel.x);
755
+
756
+ // ---- sway: the shapes move ---------------------------------------------
757
+ // Lean: the whole fan pivots a few degrees around the origin. Shear pulse:
758
+ // the bend term below oscillates, so petals flex along their length rather
759
+ // than rotating rigidly. Both sinusoidal through loopFreq, so they are
760
+ // exactly periodic when looping (and freeze, rather than speed up, when
761
+ // the loop is too short for their tuned rates -- see loopFreq).
762
+ // The sines alone are not enough: loopFreq freezes them at loops shorter
763
+ // than ~half their period (its documented anti-throb choice), which left
764
+ // sway with NO motion at all at 4-8s loops. wdrift is the non-freezing
765
+ // half -- a loopDrift walk through the per-petal noise (below) and the
766
+ // lean, exactly periodic at ANY loop length by construction, same as the
767
+ // siblings' primary motion.
768
+ vec2 wdrift = loopDrift(0.05 * u_sway, vec2(1.0, 0.0));
769
+ float lean = u_sway * (0.06 * sin(loopFreq(0.21) * u_time + u_seed)
770
+ + 0.05 * snoise(vec2(seedRow * 0.47, 9.3) + wdrift));
771
+ float shearPulse = u_sway * 0.08 * sin(loopFreq(0.15) * u_time + 2.3 + u_seed);
772
+
773
+ // Bend: angular shear proportional to r curves each petal progressively
774
+ // toward its tip -- the gentle bow the reference petals have -- instead of
775
+ // rotating the fan rigidly.
776
+ float th = theta - theta0 - lean + (u_bend + shearPulse) * r;
777
+
778
+ // ---- rose-curve envelope ------------------------------------------------
779
+ // k is snapped to an integer: |cos| has period pi, so integer k makes the
780
+ // envelope exactly 2pi-periodic and the atan branch cut at theta = +-pi is
781
+ // invisible. A fractional k would draw a hard radial seam there.
782
+ float k = floor(u_petals + 0.5);
783
+ float a = 0.5 * k * th;
784
+ float e = pow(abs(cos(a)), u_pinch);
785
+
786
+ // Per-petal identity: which lobe this pixel belongs to, expressed as the
787
+ // lobe's center ANGLE and hashed through cos/sin so it is periodic around
788
+ // the circle -- a raw lobe index would jump by k across the branch cut and
789
+ // tear the per-petal variation along that ray. The discontinuity between
790
+ // ADJACENT lobes is harmless: it sits exactly in the crease, where e ~ 0
791
+ // pushes both sides to the background color anyway.
792
+ float lobe = floor(a / PI + 0.5);
793
+ float cAngle = theta0 + 2.0 * PI * lobe / k;
794
+ float perLobe = 0.28 * snoise(vec2(cos(cAngle), sin(cAngle)) * 1.9
795
+ + vec2(seedRow * 0.13, seedRow * 0.29) + wdrift);
796
+
797
+ // Breathing: each petal's length swells ~10%, phase-offset around the fan
798
+ // by 2*cAngle (integer multiple of the angle, so it survives the branch
799
+ // cut) -- the petals ripple in sequence instead of pulsing in lockstep.
800
+ float breathe = u_sway * 0.10 * sin(loopFreq(0.32) * u_time + 2.0 * cAngle + u_seed * 0.7);
801
+ float len = 1.0 + perLobe + breathe;
802
+
803
+ // ---- the field ----------------------------------------------------------
804
+ // eps = 0.075 is the crease floor (see header). R = 1.4 at scale 1 puts
805
+ // the default tips just past the frame's top edge with the origin at the
806
+ // bottom, matching the references' crop.
807
+ float R = 1.4 * u_scale;
808
+ float t = r / (R * len * (e + 0.075));
809
+
810
+ // Radial shaping: pow < 1 compresses the ramp's early stops into a small
811
+ // hot core at the origin and hands most of each petal's length to the
812
+ // later stops -- in the references the petals are dominated by ONE body
813
+ // color (the second-to-last stop) for ~70% of their length, with the core
814
+ // colors confined to the base. Linear t spread the early stops across half
815
+ // the petal and the body color only appeared at the rim.
816
+ t = pow(t, 0.5);
817
+
818
+ // ---- colorflow: the colors move ----------------------------------------
819
+ // Phase in tri-units (period 2 = one full out-and-back palette cycle).
820
+ // Not looping: linear crawl; the +37 base offset means the knob has a
821
+ // visible effect even at t=0 (and 37 is deliberately not a multiple of the
822
+ // period). Looping: exactly n cycles per loop, floored at ONE -- unlike
823
+ // the sway sines, colorflow is a headline motion, and freezing it at
824
+ // common loop lengths (loopFreq's choice) would read as the feature being
825
+ // broken rather than as restraint. One cycle over a short loop is simply a
826
+ // faster flow, which is what a short loop asks for.
827
+ float pRate = 0.05 * u_colorflow;
828
+ float p;
829
+ if (u_loop <= 0.0) {
830
+ p = pRate * (u_time + 37.0);
831
+ } else {
832
+ p = 2.0 * max(1.0, floor(pRate * u_loop * 0.5 + 0.5)) * (u_time / u_loop);
833
+ }
834
+
835
+ // The flow lives INSIDE the silhouette: the mask fades it out across the
836
+ // tips so the background (t >= 1) stays pinned to the last stop and the
837
+ // negative space never pulses. Gated on u_colorflow so 0 is exactly the
838
+ // static field even while looping (where p above moves regardless).
839
+ float flowMix = (1.0 - smoothstep(0.82, 1.0, t)) * smoothstep(0.0, 0.08, u_colorflow);
840
+ float tc = mix(min(t, 1.0), tri(t - p), flowMix);
841
+
842
+ vec3 color = palette(tc);
843
+
844
+ // Grain: cheap per-pixel dither, centered at 0 so it can darken or
845
+ // lighten symmetrically instead of just brightening the whole frame.
846
+ float g = grain(uv, u_time) - 0.5;
847
+ color += g * u_grain;
848
+
849
+ gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);
850
+ }
851
+ `;
852
+ const bloom = {
853
+ id: "bloom",
854
+ label: "Bloom",
855
+ fragment: FRAGMENT,
856
+ params: [
857
+ {
858
+ key: "scale",
859
+ label: "Scale",
860
+ min: .5,
861
+ max: 2,
862
+ step: .05,
863
+ default: .9
864
+ },
865
+ {
866
+ key: "petals",
867
+ label: "Petals",
868
+ min: 6,
869
+ max: 16,
870
+ step: 1,
871
+ default: 11
872
+ },
873
+ {
874
+ key: "pinch",
875
+ label: "Pinch",
876
+ min: .35,
877
+ max: 2.5,
878
+ step: .05,
879
+ default: .6
880
+ },
881
+ {
882
+ key: "bend",
883
+ label: "Bend",
884
+ min: -.9,
885
+ max: .9,
886
+ step: .05,
887
+ default: .25
888
+ },
889
+ {
890
+ key: "sway",
891
+ label: "Sway",
892
+ min: 0,
893
+ max: 1,
894
+ step: .01,
895
+ default: .5
896
+ },
897
+ {
898
+ key: "colorflow",
899
+ label: "Color flow",
900
+ min: 0,
901
+ max: 1,
902
+ step: .01,
903
+ default: 0
904
+ },
905
+ {
906
+ key: "grain",
907
+ label: "Grain",
908
+ min: 0,
909
+ max: .3,
910
+ step: .01,
911
+ default: .08
912
+ }
913
+ ],
914
+ randomParams(rand) {
915
+ return {
916
+ scale: .7 + rand() * .9000000000000001,
917
+ petals: Math.floor(8 + rand() * 7),
918
+ pinch: .5 + rand() * 1.1,
919
+ bend: -.6 + rand() * 1.2,
920
+ sway: .2 + rand() * .7,
921
+ colorflow: rand() * .8,
922
+ grain: .08
923
+ };
924
+ }
925
+ };
926
+
694
927
  //#endregion
695
928
  //#region src/registry.ts
696
- const shaders = [flow, beam];
929
+ const shaders = [
930
+ flow,
931
+ beam,
932
+ bloom
933
+ ];
697
934
  function getShader(id) {
698
935
  return shaders.find((s) => s.id === id);
699
936
  }
@@ -1343,4 +1580,4 @@ function renderGradientFrame(opts) {
1343
1580
  }
1344
1581
 
1345
1582
  //#endregion
1346
- export { beam, buildPaletteRamp, createRenderer, flow, getShader, mountGradient, renderGradientFrame, shaders };
1583
+ export { beam, bloom, buildPaletteRamp, createRenderer, flow, getShader, mountGradient, renderGradientFrame, shaders };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instantshader",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Animated WebGL gradient shaders. Zero dependencies.",
5
5
  "type": "module",
6
6
  "license": "MIT",