kmcom-nuxt-layers 2.2.6 → 2.2.9

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.
Files changed (202) hide show
  1. package/docs/FEEDS.md +197 -0
  2. package/docs/LAYERS-FIXES.md +101 -0
  3. package/docs/MIGRATION.md +627 -0
  4. package/docs/feed-layer.md +374 -0
  5. package/docs/patch-picture-provider-type.md +52 -0
  6. package/docs/shaderGuide.md +2071 -0
  7. package/docs/types-architecture.md +234 -0
  8. package/layers/animations/app/components/Motion/CountUp.vue +1 -2
  9. package/layers/animations/app/components/Motion/Magnetic.vue +1 -2
  10. package/layers/animations/app/components/Motion/Marquee.vue +2 -5
  11. package/layers/animations/app/components/Motion/MarqueeText.vue +1 -2
  12. package/layers/animations/app/components/Motion/Tilt.vue +1 -2
  13. package/layers/animations/app/composables/useCountUp.ts +4 -1
  14. package/layers/animations/app/composables/useMagneticElement.ts +1 -3
  15. package/layers/animations/app/composables/useMarqueeCopies.ts +3 -3
  16. package/layers/animations/app/types/animations.ts +8 -0
  17. package/layers/animations/app/types/index.ts +1 -0
  18. package/layers/animations/package.json +4 -1
  19. package/layers/canvas/app/components/ShaderCanvas.vue +4 -4
  20. package/layers/canvas/app/composables/useRendererCapabilities.ts +19 -15
  21. package/layers/canvas/app/types/index.ts +1 -0
  22. package/layers/canvas/package.json +2 -1
  23. package/layers/canvas/tsconfig.json +2 -1
  24. package/layers/content/app/components/Blog/Card.vue +5 -5
  25. package/layers/content/app/components/Gallery/AmbientImage.vue +3 -3
  26. package/layers/content/app/components/Gallery/Card.vue +3 -3
  27. package/layers/content/app/components/NuxtContent/Detail.vue +5 -1
  28. package/layers/content/app/components/NuxtContent/Surround.vue +5 -3
  29. package/layers/content/app/components/NuxtContent/Toc.vue +1 -1
  30. package/layers/content/app/components/Portfolio/Card.vue +5 -5
  31. package/layers/content/app/components/content/Figure.vue +3 -3
  32. package/layers/content/app/types/index.ts +1 -0
  33. package/layers/content/package.json +2 -1
  34. package/layers/core/app/composables/useErrorLog.ts +9 -11
  35. package/layers/core/app/utils/helpers.ts +14 -12
  36. package/layers/core/nuxt.config.ts +1 -0
  37. package/layers/feeds/app/plugins/feed-head.ts +1 -2
  38. package/layers/feeds/package.json +2 -1
  39. package/layers/feeds/public/feed/style.css +256 -0
  40. package/layers/feeds/server/routes/feed/discovery.get.ts +1 -2
  41. package/layers/feeds/server/utils/content-adapter.ts +3 -2
  42. package/layers/forms/app/components/Form/Field.vue +4 -4
  43. package/layers/forms/app/types/index.ts +1 -0
  44. package/layers/forms/package.json +2 -1
  45. package/layers/layout/app/components/Layout/Grid/Item.vue +33 -19
  46. package/layers/layout/app/components/Layout/Page/Container.vue +11 -11
  47. package/layers/layout/app/components/Layout/Page/Header.vue +1 -1
  48. package/layers/layout/app/components/Layout/Page/index.vue +1 -1
  49. package/layers/layout/app/components/Layout/Section/Gallery.vue +6 -1
  50. package/layers/layout/app/components/Layout/Section/Title.vue +1 -1
  51. package/layers/layout/app/types/index.ts +1 -0
  52. package/layers/layout/package.json +2 -1
  53. package/layers/mailer/app/types/index.ts +1 -0
  54. package/layers/mailer/app/types/mailer.ts +25 -0
  55. package/layers/mailer/package.json +2 -1
  56. package/layers/motion/package.json +2 -1
  57. package/layers/navigation/app/components/Links/Group.vue +1 -0
  58. package/layers/navigation/app/components/Links/Named.vue +2 -0
  59. package/layers/navigation/app/types/index.ts +1 -0
  60. package/layers/navigation/package.json +4 -1
  61. package/layers/page-transitions/package.json +4 -1
  62. package/layers/routing/app/types/app-config.d.ts +3 -1
  63. package/layers/routing/app/types/index.ts +1 -0
  64. package/layers/routing/package.json +2 -1
  65. package/layers/scripts/app/composables/useGtm.ts +1 -1
  66. package/layers/scripts/app/types/index.ts +1 -0
  67. package/layers/scripts/app/types/scripts.ts +14 -0
  68. package/layers/scripts/package.json +2 -1
  69. package/layers/scroll/app/components/Motion/ScrollScene.vue +3 -1
  70. package/layers/scroll/app/composables/useScrollSteps.ts +2 -9
  71. package/layers/scroll/app/composables/useSectionProgress.ts +2 -1
  72. package/layers/scroll/app/plugins/locomotive-scroll.client.ts +1 -8
  73. package/layers/scroll/app/types/index.ts +1 -0
  74. package/layers/scroll/app/types/scroll.ts +32 -0
  75. package/layers/scroll/package.json +3 -0
  76. package/layers/seo/package.json +2 -1
  77. package/layers/shader/app/components/Material/Fresnel.client.vue +1 -1
  78. package/layers/shader/app/components/Material/Image.client.vue +1 -1
  79. package/layers/shader/app/components/Material/Node.client.vue +7 -7
  80. package/layers/shader/app/components/Material/Noise.client.vue +1 -1
  81. package/layers/shader/app/components/Node/Color.client.vue +17 -20
  82. package/layers/shader/app/components/Node/Noise.client.vue +31 -34
  83. package/layers/shader/app/components/Pipeline/Aurora.client.vue +4 -8
  84. package/layers/shader/app/components/Pipeline/BilinearGradient.client.vue +8 -11
  85. package/layers/shader/app/components/Pipeline/BillowNoise.client.vue +4 -8
  86. package/layers/shader/app/components/Pipeline/BrightnessContrast.client.vue +6 -2
  87. package/layers/shader/app/components/Pipeline/CellularNoise.client.vue +4 -8
  88. package/layers/shader/app/components/Pipeline/Checkerboard.client.vue +4 -8
  89. package/layers/shader/app/components/Pipeline/Circle.client.vue +5 -8
  90. package/layers/shader/app/components/Pipeline/Clouds.client.vue +4 -8
  91. package/layers/shader/app/components/Pipeline/ColorBurnBlend.client.vue +2 -19
  92. package/layers/shader/app/components/Pipeline/ColorDodgeBlend.client.vue +2 -19
  93. package/layers/shader/app/components/Pipeline/ColourRamp.client.vue +5 -9
  94. package/layers/shader/app/components/Pipeline/ConicGradient.client.vue +5 -8
  95. package/layers/shader/app/components/Pipeline/Cross.client.vue +4 -8
  96. package/layers/shader/app/components/Pipeline/CurlNoise.client.vue +3 -7
  97. package/layers/shader/app/components/Pipeline/DarkenBlend.client.vue +2 -19
  98. package/layers/shader/app/components/Pipeline/DayNightCycle.client.vue +5 -8
  99. package/layers/shader/app/components/Pipeline/DiagonalGradient.client.vue +5 -8
  100. package/layers/shader/app/components/Pipeline/DiamondGradient.client.vue +5 -8
  101. package/layers/shader/app/components/Pipeline/DifferenceBlend.client.vue +2 -19
  102. package/layers/shader/app/components/Pipeline/DomainWarpedNoise.client.vue +4 -8
  103. package/layers/shader/app/components/Pipeline/Dots.client.vue +4 -8
  104. package/layers/shader/app/components/Pipeline/DuoTone.client.vue +5 -9
  105. package/layers/shader/app/components/Pipeline/ExclusionBlend.client.vue +3 -23
  106. package/layers/shader/app/components/Pipeline/ExponentialFog.client.vue +4 -7
  107. package/layers/shader/app/components/Pipeline/FilmBurn.client.vue +4 -7
  108. package/layers/shader/app/components/Pipeline/Flame.client.vue +4 -8
  109. package/layers/shader/app/components/Pipeline/FocalGradient.client.vue +5 -8
  110. package/layers/shader/app/components/Pipeline/GodRays.client.vue +4 -7
  111. package/layers/shader/app/components/Pipeline/GradientNoise.client.vue +4 -8
  112. package/layers/shader/app/components/Pipeline/Grid.client.vue +4 -8
  113. package/layers/shader/app/components/Pipeline/Halation.client.vue +3 -7
  114. package/layers/shader/app/components/Pipeline/HardLightBlend.client.vue +2 -19
  115. package/layers/shader/app/components/Pipeline/Haze.client.vue +4 -7
  116. package/layers/shader/app/components/Pipeline/Hexagon.client.vue +4 -8
  117. package/layers/shader/app/components/Pipeline/LensFlare.client.vue +4 -7
  118. package/layers/shader/app/components/Pipeline/LightenBlend.client.vue +2 -19
  119. package/layers/shader/app/components/Pipeline/LinearGradient4.client.vue +2 -2
  120. package/layers/shader/app/components/Pipeline/Marble.client.vue +4 -8
  121. package/layers/shader/app/components/Pipeline/MonochromeTint.client.vue +3 -7
  122. package/layers/shader/app/components/Pipeline/MultiplyBlend.client.vue +2 -19
  123. package/layers/shader/app/components/Pipeline/NoisyGradient.client.vue +4 -8
  124. package/layers/shader/app/components/Pipeline/NoisyGradientBlend.client.vue +4 -8
  125. package/layers/shader/app/components/Pipeline/OverlayBlend.client.vue +2 -19
  126. package/layers/shader/app/components/Pipeline/Polygon.client.vue +4 -8
  127. package/layers/shader/app/components/Pipeline/RaymarchTunnel.client.vue +4 -7
  128. package/layers/shader/app/components/Pipeline/Rectangle.client.vue +4 -8
  129. package/layers/shader/app/components/Pipeline/RidgedNoise.client.vue +4 -8
  130. package/layers/shader/app/components/Pipeline/Ring.client.vue +4 -8
  131. package/layers/shader/app/components/Pipeline/ScreenBlend.client.vue +2 -19
  132. package/layers/shader/app/components/Pipeline/SkyAtmosphere.client.vue +6 -9
  133. package/layers/shader/app/components/Pipeline/SoftLightBlend.client.vue +2 -19
  134. package/layers/shader/app/components/Pipeline/SplitTone.client.vue +4 -8
  135. package/layers/shader/app/components/Pipeline/Star.client.vue +4 -8
  136. package/layers/shader/app/components/Pipeline/Stripes.client.vue +4 -8
  137. package/layers/shader/app/components/Pipeline/Tint.client.vue +4 -7
  138. package/layers/shader/app/components/Pipeline/Triangle.client.vue +4 -8
  139. package/layers/shader/app/components/Pipeline/ValueNoise.client.vue +4 -8
  140. package/layers/shader/app/components/Pipeline/VoronoiEdges.client.vue +4 -8
  141. package/layers/shader/app/components/Pipeline/Water.client.vue +5 -8
  142. package/layers/shader/app/components/Pipeline/WaveBendLayer.client.vue +4 -7
  143. package/layers/shader/app/components/Pipeline/WaveColourLayer.client.vue +3 -7
  144. package/layers/shader/app/components/Pipeline/Wood.client.vue +4 -8
  145. package/layers/shader/app/components/Preset/Aurora.client.vue +15 -21
  146. package/layers/shader/app/components/Preset/Flow.client.vue +2 -1
  147. package/layers/shader/app/components/Preset/GradientMesh.client.vue +2 -1
  148. package/layers/shader/app/components/Preset/Nebula.client.vue +2 -1
  149. package/layers/shader/app/components/Preset/Ocean.client.vue +2 -1
  150. package/layers/shader/app/components/Preset/ThemeAurora.client.vue +30 -90
  151. package/layers/shader/app/components/Preset/ThemeBubble.client.vue +30 -91
  152. package/layers/shader/app/components/Preset/ThemeFlow.client.vue +30 -90
  153. package/layers/shader/app/components/Preset/ThemeGradient.client.vue +30 -91
  154. package/layers/shader/app/components/Preset/ThemeLavaLamp.client.vue +30 -90
  155. package/layers/shader/app/components/Preset/ThemePlasma.client.vue +30 -90
  156. package/layers/shader/app/components/Preset/ThemeWave.client.vue +30 -90
  157. package/layers/shader/app/components/Shader/Background.client.vue +4 -4
  158. package/layers/shader/app/components/Shader/Host.client.vue +31 -33
  159. package/layers/shader/app/components/Shader/Runtime.client.vue +15 -23
  160. package/layers/shader/app/composables/useAmbientMaterials.ts +53 -51
  161. package/layers/shader/app/composables/useShaderMixBlend.ts +26 -0
  162. package/layers/shader/app/composables/useThemePreset.ts +75 -0
  163. package/layers/shader/app/shaders/common/noise.ts +2 -7
  164. package/layers/shader/app/shaders/types.ts +6 -6
  165. package/layers/shader/app/types/tsl.ts +7 -25
  166. package/layers/shader/app/types/uniforms.ts +2 -1
  167. package/layers/shader/app/utils/tsl/color.ts +7 -1
  168. package/layers/shader/package.json +2 -1
  169. package/layers/theme/app/components/ThemePicker/Colors.vue +1 -3
  170. package/layers/theme/app/types/app-config.d.ts +4 -2
  171. package/layers/theme/app/types/index.ts +1 -0
  172. package/layers/theme/app/types/theme.ts +3 -18
  173. package/layers/theme/package.json +2 -1
  174. package/layers/theme/server/plugins/theme-fouc.ts +1 -1
  175. package/layers/transitions/package.json +4 -1
  176. package/layers/typography/app/components/Typography/CodeBlock.vue +2 -2
  177. package/layers/typography/app/components/Typography/Headline.vue +2 -2
  178. package/layers/typography/app/components/Typography/HeadlineScreen.vue +1 -1
  179. package/layers/typography/app/components/Typography/QuoteBlock.vue +4 -1
  180. package/layers/typography/app/components/Typography/TextStroke.vue +2 -0
  181. package/layers/typography/app/components/Typography/index.vue +36 -27
  182. package/layers/typography/app/composables/typography.ts +27 -21
  183. package/layers/typography/app/types/colors.ts +9 -29
  184. package/layers/typography/app/types/index.ts +2 -0
  185. package/layers/typography/package.json +4 -1
  186. package/layers/ui/package.json +2 -1
  187. package/layers/visual/app/app.config.ts +5 -2
  188. package/layers/visual/app/components/Accent/Blob.vue +20 -20
  189. package/layers/visual/app/components/Accent/Scene.vue +2 -2
  190. package/layers/visual/app/components/Base/Modal.vue +2 -2
  191. package/layers/visual/app/components/Gradient/Background.vue +2 -2
  192. package/layers/visual/app/components/Gradient/Text.vue +2 -2
  193. package/layers/visual/app/components/Media/Picture.vue +3 -1
  194. package/layers/visual/app/components/Progress/Bar.vue +6 -6
  195. package/layers/visual/app/components/Tint/Overlay.vue +14 -14
  196. package/layers/visual/app/composables/accent.ts +10 -8
  197. package/layers/visual/app/composables/tint.ts +7 -7
  198. package/layers/visual/app/types/index.ts +6 -0
  199. package/layers/visual/app/types/media.ts +4 -2
  200. package/layers/visual/app/types/tint.ts +2 -1
  201. package/layers/visual/package.json +4 -1
  202. package/package.json +6 -2
@@ -0,0 +1,2071 @@
1
+ # Modular Shader Block System -- v5
2
+
3
+ ## Changelog from v4
4
+
5
+ - **`CosinePalette`** added as a first-class Scalar Generator -- the single most reusable block in the entire system, backbone of all phobon TSL shaders
6
+ - New pipeline pattern formalised: **Scalar Pipeline** (UV → float → colour)
7
+ - New Math Primitives: `ComplexDiv`, `ComplexLog` (complex number arithmetic in TSL)
8
+ - New Generators: `CosinePalette`, `RingField`, `ComplexPlaneField`, `ChebyshevNoiseField`
9
+ - New UV Transformers: `UVColumnOffset`, `UVFractBand`
10
+ - New Colour Ops: `TanhTonemap`, `SDFColourMask`
11
+ - Shader conversions: imaginary2, dawn2, dawn4, dawn5, flare8, flare9, genuary22
12
+
13
+ ---
14
+
15
+ ## Architecture Overview
16
+
17
+ Primitives Raw values, uniforms, constants, samplers
18
+ Generators UV → colour (starting point of most pipelines)
19
+ Scalar Generators UV → float → colour (CosinePalette family) ← NEW
20
+ UV Transformers UV → UV (warp, scroll, tile, distort)
21
+ Ray Transformers Ray → Ray (tilt, reflect, fisheye)
22
+ Colour Ops colour → colour (blend, grade, shift, remap)
23
+ Overlays colour → colour (composited effects, post-process style)
24
+
25
+ ### The Scalar Pipeline
26
+
27
+ All 7 phobon shaders share the same pipeline shape: compute a meaningful float from UV (distance, noise, SDF, imaginary component), feed it into `cosinePalette`, multiply the result by a mask float. This is a distinct pattern from the UV→colour generators and worth formalising:
28
+
29
+ UV
30
+ ├─ ScalarSource (uv.y / length(uv) / SDF / noise → float) ← float lane
31
+ │ └─ CosinePalette (float → colour)
32
+ └─ MaskSource (SDF / noise → float) ← mask lane
33
+ └─ SDFColourMask (colour × mask → colour)
34
+ └─ Overlays
35
+
36
+ `CosinePalette` is the bridge between the scalar lane and the colour lane. Any float can drive it -- that's what makes it so broadly composable.
37
+
38
+ ---
39
+
40
+ ## System Layers
41
+
42
+ ### Core Infrastructure
43
+
44
+ - `ShaderPipeline` -- fragment compositor, owns material.colorNode, reduces fragment stages
45
+ - `VertexPipeline` -- vertex compositor, owns material.positionNode, reduces vertex stages
46
+ - `BackdropPass` -- renders scene to a RenderTarget, exposes as texture for blur/glass effects
47
+ - `useShaderStage(fn, order, stage?)` -- registers a block into the correct pipeline
48
+ - `useCSSColourUniform(varName)` -- CSS var → TSL uniform, theme-reactive via inject
49
+ - `useCSSFloatUniform(varName)` -- CSS numeric var → float uniform
50
+ - `useThemeProvider` -- provides `{ mode, palette, contrast }` to the component tree
51
+ - `colour.js` -- resolveColour() handles hex, rgb, hsl, hsla, oklch → THREE.Color
52
+ - `useWaveLayerDefaults(index, total)` -- generates offset noise params for wave stacking
53
+
54
+ ### Stage Routing
55
+
56
+ // useShaderStage.js -- stage: 'fragment' | 'vertex' | 'ray'
57
+ export function useShaderStage(stageFn, order = 0, stage = 'fragment') {
58
+ const { register, unregister } = inject('shaderPipeline')
59
+ onMounted(() => register(stageFn, order, stage))
60
+ onUnmounted(() => unregister(stageFn))
61
+ }
62
+
63
+ // ShaderPipeline reduces three targets
64
+ material.colorNode = fragmentStages.reduce((n, { fn }) => fn(n), vec4(0, 0, 0, 1))
65
+ material.positionNode = vertexStages.reduce((n, { fn }) => fn(n), positionLocal)
66
+ // Ray pipeline: screen coord → normalised ray → consumed by terminal generator
67
+ const rayNode = rayStages.reduce((n, { fn }) => fn(n), screenRayNode)
68
+
69
+ ### Ray Pipeline
70
+
71
+ Ray-based generators (SkyAtmosphere, RaymarchTunnel) consume a 3D ray direction rather than a 2D UV. The Ray Pipeline converts screen coordinates to a ray, applies 3D transforms (tilt, fisheye, reflection), then feeds into a terminal generator. Ray transformer blocks use `stage: 'ray'`.
72
+
73
+ **Terminal blocks** -- blocks marked `terminal: true` always sit at `order: 0` and consume the ray output directly. UV transformers have no effect on them. The pipeline compositor warns if a UV transformer is registered before a terminal block.
74
+
75
+ ### Debug / DX
76
+
77
+ - `ShaderDebugger` -- visualises output of any named stage mid-pipeline
78
+ - `ShaderInspector` -- UV coords, stage names, node graph overlay in dev mode
79
+ - `useShaderPerf` -- tracks GPU frame time per pipeline
80
+
81
+ ---
82
+
83
+ ## Block Registry
84
+
85
+ ---
86
+
87
+ ## I. Primitives
88
+
89
+ > Raw values, constants, and data sources.
90
+
91
+ ### Constants & Values
92
+
93
+ 1. `FloatConst` -- static float value
94
+ 2. `Vec2Const` -- static 2D vector
95
+ 3. `Vec3Const` -- static 3D vector
96
+ 4. `Vec4Const` -- static RGBA vector
97
+ 5. `ColourConst` -- THREE.Color → vec3 uniform
98
+ 6. `BoolConst` -- 0 or 1 float
99
+ 7. `IntConst` -- integer uniform
100
+ 8. `AngleConst` -- degrees-in, radians-out convenience wrapper
101
+
102
+ ### Reactive Uniforms
103
+
104
+ 9. `FloatUniform` -- JS-reactive float (prop-driven)
105
+ 10. `ColourUniform` -- JS-reactive colour (prop-driven)
106
+ 11. `Vec2Uniform` -- JS-reactive 2D vector
107
+ 12. `CSSColourUniform` -- reads from a CSS custom property
108
+ 13. `CSSFloatUniform` -- reads a numeric CSS custom property
109
+ 14. `TimeUniform` -- exposes `time` node, speed and offset controls
110
+ 15. `DeltaTimeUniform` -- frame delta for frame-rate-independent effects
111
+ 16. `MouseUniform` -- normalised mouse/pointer position vec2 (NDC -1..1)
112
+ 17. `ScrollUniform` -- normalised scroll progress float (Lenis integration)
113
+ 18. `ViewportUniform` -- resolution vec2, updates on resize
114
+ 19. `AspectUniform` -- viewport width/height ratio float
115
+ 20. `DevicePixelRatioUniform` -- DPR float
116
+
117
+ ### Coordinate Sources
118
+
119
+ 21. `UVSource` -- base uv(), entry point for UV chains
120
+ 22. `WorldPositionSource` -- vertex world position
121
+ 23. `NormalSource` -- vertex/fragment normal vec3
122
+ 24. `DepthSource` -- fragment depth float
123
+ 25. `ScreenUVSource` -- screen-space UV (0--1 across viewport)
124
+ 26. `TiledUVSource` -- uv() pre-tiled by a repeat vec2
125
+
126
+ ### Texture Samplers
127
+
128
+ 27. `TextureSampler` -- standard texture2D lookup
129
+ 28. `VideoSampler` -- HTMLVideoElement as texture
130
+ 29. `CanvasSampler` -- HTMLCanvasElement as texture (live 2D canvas)
131
+ 30. `CubemapSampler` -- environment map sampler
132
+ 31. `NoiseSampler` -- samples a pre-baked noise texture
133
+ 32. `LUTSampler` -- 3D LUT texture for colour grading
134
+ 33. `GradientMapSampler` -- 1D gradient texture lookup by luminance
135
+
136
+ ### Math Primitives
137
+
138
+ 34. `Sine` -- sin(x) node
139
+ 35. `Cosine` -- cos(x) node
140
+ 36. `Abs` -- abs(x) node
141
+ 37. `Floor` -- floor(x) node
142
+ 38. `Fract` -- fract(x) node
143
+ 39. `Clamp01` -- clamp(x, 0, 1)
144
+ 40. `Remap` -- remaps a value from one range to another
145
+ 41. `OneMinus` -- 1.0 - x
146
+ 42. `Saturate` -- alias for Clamp01
147
+ 43. `Step` -- step(edge, x)
148
+ 44. `Smoothstep` -- smoothstep(a, b, x)
149
+ 45. `Lerp` -- mix(a, b, t) as an explicit primitive
150
+
151
+ ### Channel Ops
152
+
153
+ 46. `SplitChannels` -- exposes .r .g .b .a as individual float nodes
154
+ 47. `MergeChannels` -- combines float nodes into a vec4
155
+ 48. `SwizzleXY` -- returns .xy from a vec4
156
+ 49. `SwizzleZW` -- returns .zw from a vec4
157
+ 50. `AlphaExtract` -- isolates alpha channel as float
158
+
159
+ ### Alpha / Compositing
160
+
161
+ 51. `CoverageAlpha` -- ★ NEW -- transparency based on max channel coverage (used by ColorBends transparent mode). Alpha = max(r, g, b) rather than a fixed 1.0. Essential for shaders that generate shape from colour intensity.
162
+ 52. `SunDirectionUniform` ★ NEW -- animated or mouse-driven sun elevation. Inactive: `sin(time)/8 - 0.2`. Active: `-mouse.y / res.y`. Outputs normalised vec3 via `rotX(vec3(0,0,1), angle * 5 + 0.5)`. Used by SkyAtmosphere.
163
+ 53. `RayCameraUniform` ★ NEW -- source node for the Ray Pipeline. Converts screen fragment coord to a centred, aspect-corrected 3D direction with a fixed z depth.
164
+ 54. `StateBufferSampler` ★ NEW -- reads vec3 values from specific texels of a small state texture written by a previous render pass. Used by HeightmapTerrain to read camera position from texel (0.5, 0.5) and rotation from texel (1.5, 0). This is the multi-pass state pattern: one pass writes simulation state as pixel values, the next pass reads it back. Takes `texture`, `texelCoord`.
165
+ 55. `HeightmapSampler` ★ NEW -- samples a height/noise texture with LOD and a coordinate scale factor. Distinct from `NoiseSampler` in that it's specifically for terrain elevation lookup with explicit `textureLod(..., 0)` to prevent mip-blurring artefacts on terrain edges. Takes `texture`, `scale`, `offset`.
166
+
167
+ ### Complex Number Math ★ NEW
168
+
169
+ 56. `ComplexDiv(a, b)` -- complex number division. `vec2((a.x*b.x + a.y*b.y) / dot(b,b), (a.y*b.x - a.x*b.y) / dot(b,b))`. Used in imaginary2 for the Möbius transformation `(z-p)/(z-q)`.
170
+ 57. `ComplexLog(z)` -- complex natural logarithm. `vec2(log(length(z)), atan(z.y, z.x))`. Produces `(ln|z|, arg(z))` -- the magnitude and angle of a complex number. Used in imaginary2 to compute the winding angle between two poles.
171
+
172
+ ---
173
+
174
+ ## IIa. Ray Transformers ★ NEW CATEGORY
175
+
176
+ > Ray → Ray. Operate in 3D ray space rather than 2D UV space. All blocks here use `stage: 'ray'` and feed into terminal ray-based generators.
177
+
178
+ 1. `FisheyeRay` -- applies fisheye lens distortion to a ray direction before normalisation. `ray.z -= dot(ray.xy, ray.xy) * 0.5`. Produces the characteristic wide-angle sky dome look.
179
+ 2. `RayTiltBasis` -- tilts the ray via an orthonormal basis (i, j, k) constructed from a target forward direction. `ray = ray.x*i + ray.y*j + ray.z*k`. Controls camera elevation. Used in SkyAtmosphere to angle the view upward.
180
+ 3. `RaySphereReflect` -- reflects the ray off a virtual sphere at a given centre and radius. Used in SkyAtmosphere's sphere mode. The reflection creates a mirrored panoramic ball effect.
181
+ 4. `RayRotateX` -- rotates ray around the X axis by a given angle
182
+ 5. `RayRotateY` -- rotates ray around the Y axis
183
+ 6. `RayRotateZ` -- rotates ray around the Z axis
184
+ 7. `RayMouseOrbit` -- orbits ray direction based on mouse position (horizontal = yaw, vertical = pitch)
185
+ 8. `RayAutoOrbit` -- continuously orbits ray direction over time at a given speed
186
+ 9. `RayFromStateBuffer` ★ NEW -- reads camera position and rotation from a StateBufferSampler texture rather than from uniforms. Constructs the view ray using `rotateZ(rot.x) * rotateX(rot.y) * screenCoord`. Used by HeightmapTerrain for the multi-pass camera state pattern where position is simulated separately and written to a texture each frame.
187
+
188
+ ---
189
+
190
+ ## II. Generators
191
+
192
+ > UV → colour. The starting point of every pipeline.
193
+
194
+ ### Gradients
195
+
196
+ 1. `LinearGradient` -- two-colour gradient along an axis
197
+ 2. `RadialGradient` -- circular gradient from a centre point
198
+ 3. `ConicGradient` -- angular sweep gradient
199
+ 4. `DiamondGradient` -- Chebyshev-distance based diamond shape
200
+ 5. `SweepGradient` -- atan2-based full 360° sweep
201
+ 6. `MultiStopGradient` -- n-stop gradient via 1D LUT texture
202
+ 7. `DiagonalGradient` -- gradient along an arbitrary angle
203
+ 8. `FocalGradient` -- radial with offset focal point
204
+ 9. `MeshGradient` -- interpolation across a 2D grid of colour points
205
+ 10. `NoisyGradient` -- linear gradient with per-pixel noise offset
206
+
207
+ ### Wave & Bend
208
+
209
+ 11. `WaveColourLayer` -- noise-driven colour band (Stripe-style). Each layer has its own `noiseFreq`, `noiseSpeed`, `noiseFlow`, `noiseSeed`, `noiseFloor`, `noiseCeil`. Stack multiple instances with offset params.
210
+ 12. `WaveBendLayer` ★ NEW -- ColorBends-style per-colour wave. Uses `sin(s.yx * freq)`
211
+ - length-based intensity with optional warpStrength domain feedback loop. Different aesthetic to WaveColourLayer -- produces broader, bending colour bands rather than flowing noise ribbons. Stack one per colour.
212
+
213
+ ### Solid / Flat
214
+
215
+ 13. `SolidColour` -- flat colour fill
216
+ 14. `Checkerboard` -- two-colour tile grid
217
+ 15. `Stripes` -- repeating stripe pattern, controllable angle and width
218
+ 16. `Dots` -- polka dot grid, smooth or hard edge
219
+ 17. `Grid` -- line grid overlay
220
+
221
+ ### Noise & Organic
222
+
223
+ 18. `ValueNoise` -- classic smooth value noise
224
+ 19. `GradientNoise` -- gradient noise (the `hash()` + smooth interpolation pattern used in Grainient and Shadertoy snippets 3 & 4)
225
+ 20. `SimplexNoise` -- simplex noise
226
+ 21. `FBMNoise` -- fractal Brownian motion (layered octaves)
227
+ 22. `DomainWarpedNoise` -- FBM with domain warping
228
+ 23. `CellularNoise` -- Worley/Voronoi noise
229
+ 24. `VoronoiEdges` -- Voronoi cell edge lines only
230
+ 25. `RidgedNoise` -- abs() of noise for mountain-ridge look
231
+ 26. `BillowNoise` -- folded noise for cloud-like forms
232
+ 27. `CurlNoise` -- divergence-free noise for fluid-like motion
233
+
234
+ ### Gradient Blend (Noise-Rotated)
235
+
236
+ 28. `NoisyGradientBlend` ★ NEW -- the full Grainient/Shadertoy gradient pattern as a single generator: GradientNoise → noise-driven UV rotation → sine warp → rotated two-layer colour blend. Exposes all params individually so the pipeline can still intercept at any stage, but provides a convenient self-contained starting point.
237
+
238
+ ### Geometric
239
+
240
+ 29. `Circle` -- smooth SDF circle
241
+ 30. `Rectangle` -- SDF rounded rectangle
242
+ 31. `Triangle` -- SDF equilateral or arbitrary triangle
243
+ 32. `Hexagon` -- SDF hexagon
244
+ 33. `Star` -- SDF n-pointed star
245
+ 34. `Ring` -- annulus/donut SDF
246
+ 35. `Line` -- SDF line segment
247
+ 36. `Cross` -- SDF plus/cross shape
248
+ 37. `Arrow` -- SDF directional arrow
249
+ 38. `Polygon` -- SDF regular n-gon
250
+
251
+ ### Patterns
252
+
253
+ 39. `HexGrid` -- hexagonal tiling pattern
254
+ 40. `TriangleGrid` -- triangular tiling
255
+ 41. `PenroseTiling` -- quasi-periodic Penrose pattern
256
+ 42. `TruchetTiles` -- randomised quarter-circle tile pattern
257
+ 43. `WavyStripes` -- sine-modulated stripe generator
258
+ 44. `Chevron` -- repeating chevron/zigzag
259
+ 45. `Houndstooth` -- classic houndstooth textile pattern
260
+ 46. `Tartan` -- crossed stripe pattern
261
+ 47. `Halftone` -- dot-grid halftone screen
262
+ 48. `CrossHatch` -- angled line cross-hatch
263
+
264
+ ### Procedural Natural
265
+
266
+ 49. `Marble` -- sine + FBM marble veining
267
+ 50. `Wood` -- concentric ring wood grain
268
+ 51. `Flame` -- animated upward noise-driven flame
269
+ 52. `Water` -- animated layered sine water surface
270
+ 53. `Clouds` -- FBM + remap cloud formation
271
+
272
+ ### Volumetric / Raymarching
273
+
274
+ 54. `RaymarchTunnel` ★ NEW -- the Shadertoy tunnel effect. Raymarching loop in a fragment shader: SDF sphere + fractional XY tiling + additive volumetric glow per step + chromatic time-offset colour. Self-contained, terminal: true. Exposes: `glowColour`, `glowFalloff`, `speed`, `tiling`, `sphereRadius`.
275
+
276
+ 55. `SkyAtmosphere` ★ NEW -- physically-approximated sky dome. Consumes a ray direction from the Ray Pipeline plus a sun direction vec3. Core is an exponential atmospheric gradient with per-channel Rayleigh scattering coefficients `vec3(0.1, 0.3, 0.6)`: `exp2(-(ray.y - raySun*0.5) / scatterCoeffs)`. Darkens sky when sun is low, adds sun disc via `smoothstep(0.9995, 1.0, raySun)`. terminal: true, stage: 'ray'. Exposes: `scatterCoeffs`, `sunDiscSharpness`, `sunDiscColour`, `groundFade`.
277
+
278
+ 56. `BilinearGradient` ★ NEW -- four-corner colour interpolation. The simplest possible 2D colour field: mix bottom-left/right along X, mix top-left/right along X, then mix the two results along Y. Zero noise, zero animation, zero distortion. `mix(mix(bl, br, uv.x), mix(tl, tr, uv.x), uv.y)` Exposes: `colorBL`, `colorBR`, `colorTL`, `colorTR`. Accepts CSS var props for all four corners for theme reactivity.
279
+
280
+ 57. `HeightmapTerrain` ★ NEW -- DDA (Digital Differential Analysis) heightmap traversal producing a voxel-style terrain view. Consumes camera position (vec3) and ray direction (vec3) from the Ray Pipeline or StateBufferSampler.
281
+
282
+ Internally:
283
+
284
+ - Applies a hexagonal grid skew via `F = (sqrt(3)-1)/2` -- skews the XY coordinate space so a square grid behaves like a hexagonal one, giving the terrain its distinctive isometric diamond facets
285
+ - Marches through 2D grid cells using DDA, at each boundary comparing ray Z against the elevation function to find intersection
286
+ - Elevation is `cos`-based terrain shape + distance falloff + HeightmapSampler noise
287
+ - Shading: white/blue facets from `fract(pos.xy)` proximity to grid edges and diagonal lines; shadow tones from adjacent cell elevation difference
288
+
289
+ Exposes: `heightTexture`, `gridScale`, `terrainAmplitude`, `terrainRadius`, `facetColourA` (light), `facetColourB` (water/shadow), `marchSteps`. terminal: true, hdr: false, stage: 'ray'.
290
+
291
+ ### Scalar Generators ★ NEW CATEGORY
292
+
293
+ > These take a float input and output a colour. They sit between a scalar source (uv.y, length(uv), SDF result, noise value) and the colour pipeline. The float input can come from any upstream node -- that composability is the point.
294
+
295
+ 58. `CosinePalette` ★ NEW -- **the most reusable block in the system**. Iq's cosine palette formula: `a + b * cos(TAU * (c*t + d))` where a, b, c, d are vec3 parameters and t is a scalar float input. The four parameter vectors control:
296
+ - `a` -- colour offset (brightness centre)
297
+ - `b` -- colour amplitude (contrast)
298
+ - `c` -- frequency per channel (how fast each channel oscillates)
299
+ - `d` -- phase offset per channel (colour shift) Accepts CSS var props for all four vectors for full theme reactivity. Has a `frequency` multiplier prop for the `mul(0.6 * 2.0, PI)` pattern in imaginary2. Input `t` is wired from the previous pipeline stage or an explicit `scalarInput` prop.
300
+
301
+ 59. `RingField` ★ NEW -- one or more ring-shaped SDF fields accumulated into a single float mask. Each ring is `abs(length(uv + offset) - radius)`. Multiple rings are additive. The flare8/flare9 pattern. Accepts an array of ring descriptors `[{ offset: vec2, radius: float, scale: float }]`. Outputs a float mask consumed downstream by `CosinePalette` and `SDFColourMask`. Also includes a built-in vignette term: `oneMinus(length(uv * vignetteScale))`.
302
+
303
+ 60. `ChebyshevNoiseField` ★ NEW -- noise-distorted Chebyshev box shape. The genuary22 pattern: `1 - max(abs(uv.x * scaleX * n), abs(uv.y * scaleY * n2))` where n and n2 are two octaves of simplex noise at different frequencies. Produces a noise-eroded cross/box silhouette. Outputs a float mask.
304
+
305
+ 61. `ComplexPlaneField` ★ NEW -- complex number plane visualisation. The imaginary2 effect: places two poles p and q at angle/distance from origin, computes the Möbius transformation `(z-p)/(z-q)`, takes the complex log, extracts the imaginary component (winding angle), combines with `exp(z.x)/exp(z.y)` for the radial term. Outputs a float t for `CosinePalette`. terminal: true. Exposes: `poleAngle`, `poleDistance`, `imaginaryWeight`, `radialWeight`.
306
+
307
+ ---
308
+
309
+ ## III. UV Transformers
310
+
311
+ > UV → UV. These never produce colour.
312
+
313
+ ### Basic Transforms
314
+
315
+ 1. `UVScale` -- uniform or non-uniform scale around a pivot
316
+ 2. `UVTranslate` -- offset UV by a vec2
317
+ 3. `UVRotate` -- rotate around a pivot point
318
+ 4. `UVFlipX` -- mirror horizontally
319
+ 5. `UVFlipY` -- mirror vertically
320
+ 6. `UVFlipXY` -- mirror both axes
321
+ 7. `UVSwapAxes` -- transpose x and y
322
+ 8. `UVCentre` -- recentres UV to -0.5…0.5 space and back
323
+ 9. `UVAspectCorrect` ★ NEW -- multiplies UV x by (width/height) so downstream effects are circular rather than elliptical. Essential whenever working with length() or radial operations. Used in ColorBends, Grainient, and most noise-rotation patterns.
324
+
325
+ ### Mouse / Pointer
326
+
327
+ 10. `UVParallax` ★ NEW -- offsets UV by pointer NDC position scaled by a parallax factor. Creates the illusion of depth as the mouse moves. Used in ColorBends. `p += uPointer * parallax * 0.1`
328
+ 11. `UVMousePull` ★ NEW -- additive UV pull toward the current pointer position, scaled by an influence factor. Different from Parallax -- this warps the UV toward the cursor rather than shifting the whole field. Used in ColorBends. `q += (pointer - rp) * mouseInfluence * 0.2`
329
+
330
+ ### Noise-Driven Rotation
331
+
332
+ 12. `UVNoiseRotate` ★ NEW -- rotates UV by an angle derived from GradientNoise sampled at `(time * speed, uv.x * uv.y)`. The core of the Grainient and Shadertoy gradient looks. Exposes `noiseScale`, `rotationAmount` (degrees of max rotation), `speed`. The noise output remapped to `(degree - 0.5) * rotationAmount + 180` prevents the rotation from snapping back to zero.
333
+
334
+ ### Tiling & Repetition
335
+
336
+ 13. `UVTile` -- tile by integer repeat count
337
+ 14. `UVMirrorTile` -- alternating mirrored tiles
338
+ 15. `UVOffsetTile` -- brick-offset tiling
339
+ 16. `UVHexTile` -- hexagonal tiling coordinate system
340
+ 17. `UVClamp` -- clamps UV to 0--1 (no repeat)
341
+ 18. `UVCrop` -- maps a sub-region to full 0--1 space
342
+
343
+ ### Distortion
344
+
345
+ 19. `UVWarp` -- sine-based two-axis warp
346
+ 20. `UVSineWarpXY` ★ NEW -- independent sine warp on both axes simultaneously with frequency multiplier on Y (the `frequency * 1.5` pattern). This is the specific warp used in Grainient and Shadertoy snippets 3 & 4: `tuv.x += sin(tuv.y * freq + speed) / amplitude` `tuv.y += sin(tuv.x * freq * 1.5 + speed) / (amplitude * 0.5)` Distinct from `UVWarp` (which is a single vec2 sine) -- this is asymmetric by design.
347
+ 21. `UVTwirl` -- spiral distortion from centre
348
+ 22. `UVFisheye` -- barrel/fisheye lens distortion
349
+ 23. `UVPinch` -- pinch toward or away from a point
350
+ 24. `UVBulge` -- radial bulge/push from centre
351
+ 25. `UVSinWave` -- single-axis sine wave displacement
352
+ 26. `UVRipple` -- radial ripple from a point
353
+ 27. `UVShear` -- shear/skew transform
354
+ 28. `UVPolar` -- converts Cartesian UV to polar coordinates
355
+ 29. `UVCartesian` -- converts polar back to Cartesian
356
+
357
+ ### Noise-Driven
358
+
359
+ 30. `UVNoiseWarp` -- FBM-driven domain warp
360
+ 31. `UVTurbulence` -- high-frequency noise displacement
361
+ 32. `UVCurlWarp` -- curl noise UV advection
362
+ 33. `UVCrystal` -- Voronoi-warped UV
363
+ 34. `UVMelt` -- slow downward gravity-like noise drip
364
+
365
+ ### Time-Driven
366
+
367
+ 35. `UVScroll` -- constant UV translation over time
368
+ 36. `UVScrollX` -- horizontal scroll only
369
+ 37. `UVScrollY` -- vertical scroll only
370
+ 38. `UVPulse` -- oscillating scale over time
371
+ 39. `UVOrbit` -- UV rotates around a centre point over time
372
+ 40. `UVBreath` -- gentle scale oscillation
373
+
374
+ ### Mapping
375
+
376
+ 41. `UVSpherical` -- spherical projection mapping
377
+ 42. `UVCylindrical` -- cylindrical projection
378
+ 43. `UVPlanar` -- planar world-space projection
379
+ 44. `UVTriplanar` -- world-normal-blended triplanar mapping
380
+ 45. `UVScreenSpace` -- projects to screen-space UV regardless of geometry
381
+ 46. `UVReflect` -- reflection vector-based UV
382
+ 47. `UVMatCap` -- MatCap/LitSphere normal-based UV
383
+ 48. `UVFlowMap` -- samples a flow map texture to drive UV offset
384
+
385
+ ### Experimental
386
+
387
+ 49. `UVKaleidoscope` -- mirror-fold UV into kaleidoscope symmetry
388
+ 50. `UVFractalZoom` -- recursive self-similar UV zoom
389
+ 51. `UVPixelate` -- floor UV to a pixel grid
390
+ 52. `UVScan` -- animated scanline sweep
391
+ 53. `UVShatter` -- Voronoi-cell-based UV fragmentation
392
+ 54. `UVMosaic` -- large-block mosaic coordinate snapping
393
+ 55. `UVFeedback` -- previous frame UV offset (requires render target pass)
394
+
395
+ ### Banding & Repetition ★ NEW
396
+
397
+ 56. `UVColumnOffset` ★ NEW -- offsets UV Y by a column-index-derived value, creating vertical bands that are each slightly offset. The dawn4/5 pattern: `floor(uv.x * repetitions) * repetitions * factor`. Combined with sine: `uv.y += columnOffset + sin(s * softness)`. Creates the vertical stripe morphing effect. Exposes: `repetitions`, `offsetFactor`, `sineSoftness`.
398
+ 57. `UVFractBand` ★ NEW -- applies `fract(uv.y * frequency) * scale` to create horizontal banding via the fractional part. Outputs a float that adds to colour via `pow(s, 2.0)`. The dawn2 shimmer band pattern. Exposes: `frequency`, `scale`, `power`. Note: this outputs a float additive term, not a UV -- it straddles the UV/Scalar boundary, best used as a `ScalarAddend` passed to `CosinePalette` or added to the final colour directly.
399
+
400
+ ---
401
+
402
+ ## IV. Colour Ops
403
+
404
+ > colour → colour. Applied to the output of generators or previous ops.
405
+
406
+ ### Blending
407
+
408
+ 1. `MixBlend` -- linear interpolation between two colour inputs
409
+ 2. `MultiplyBlend` -- Multiply mode
410
+ 3. `ScreenBlend` -- Screen mode
411
+ 4. `OverlayBlend` -- Overlay mode
412
+ 5. `SoftLightBlend` -- Soft Light
413
+ 6. `HardLightBlend` -- Hard Light
414
+ 7. `DifferenceBlend` -- Difference (abs subtraction)
415
+ 8. `ExclusionBlend` -- softer Difference
416
+ 9. `DodgeBlend` -- colour dodge
417
+ 10. `BurnBlend` -- colour burn
418
+ 11. `AddBlend` -- additive blend
419
+ 12. `SubtractBlend` -- subtractive blend
420
+ 13. `DivideBlend` -- division blend
421
+ 14. `DarkenBlend` -- min() per channel
422
+ 15. `LightenBlend` -- max() per channel
423
+
424
+ ### Gradient Blend Ops
425
+
426
+ 16. `RotatedGradientBlend` ★ NEW -- blends two colours along a rotated axis using smoothstep. The `(tuv * Rot(angle)).x` pattern from Grainient and Shadertoy snippets 3 & 4. Takes `colorA`, `colorB`, `angle`, `edge0`, `edge1`. Used as the base colour layer before the Y-axis layer mix.
427
+
428
+ ### Grading
429
+
430
+ 17. `BrightnessContrast` -- lift and compress tonal range
431
+ 18. `Exposure` -- multiply-based exposure stop adjustment
432
+ 19. `Gamma` -- power-curve gamma correction. `pow(max(col, 0), 1/gamma)`
433
+ 20. `Levels` -- input/output range remapping
434
+ 21. `Curves` -- arbitrary tone curve via 1D LUT texture
435
+ 22. `WhiteBalance` -- colour temperature and tint adjustment
436
+ 23. `LiftGammaGain` -- cinematic three-way colour grade
437
+ 24. `Hue` -- hue rotation in HSL space
438
+ 25. `Saturation` -- HSL saturation adjustment. `mix(vec3(luma), col, saturation)`
439
+ 26. `Vibrance` -- saturation boost that protects already-saturated colours
440
+ 27. `Lightness` -- HSL lightness adjustment
441
+
442
+ ### Channel Operations
443
+
444
+ 28. `ChannelMixer` -- arbitrary RGB channel mixing matrix
445
+ 29. `SwapChannels` -- remap colour channels
446
+ 30. `Tint` -- multiply colour by a tint colour
447
+ 31. `ColourBalance` -- shadows/midtones/highlights colour push
448
+ 32. `SelectiveColour` -- adjust a specific hue range only
449
+
450
+ ### Remapping
451
+
452
+ 33. `Invert` -- 1 - colour
453
+ 34. `Posterise` -- quantise to n colour steps
454
+ 35. `Threshold` -- binary black/white from luminance
455
+ 36. `ColourRamp` -- map luminance to a colour gradient
456
+ 37. `DuoTone` -- map shadows/highlights to two colours
457
+ 38. `Tritone` -- three-stop colour mapping
458
+ 39. `FalseColour` -- scientific false colour mapping
459
+ 40. `ThermalMap` -- cool-to-hot colour ramp
460
+ 41. `Solarise` -- Sabattier photographic effect
461
+
462
+ ### Colour Space
463
+
464
+ 42. `LinearToSRGB` -- gamma encode for display
465
+ 43. `SRGBToLinear` -- gamma decode to linear light
466
+ 44. `RGBToHSL` -- colour space conversion
467
+ 45. `HSLToRGB` -- colour space conversion
468
+ 46. `RGBToOKLab` -- perceptually uniform colour space
469
+ 47. `OKLabToRGB` -- back to RGB from OKLab
470
+ 48. `Desaturate` -- perceptual luminance-preserving greyscale
471
+ 49. `MonochromeTint` -- desaturate then tint with a single colour
472
+ 50. `SplitTone` -- separate tint for shadows vs highlights
473
+
474
+ ### Time-Based Colour
475
+
476
+ 51. `DayNightCycle` ★ NEW -- sinusoidal interpolation between two sets of colours over time. `sin(time * speed)` remapped to 0--1 with a power curve for easing. Used in Shadertoy snippet 4 to cycle between light and dark palettes. `t = (sign(cycle) * pow(abs(cycle), 0.6) + 1.) / 2.` Takes `colorsLight[]`, `colorsDark[]`, `speed`. Returns the time-blended colour set.
477
+
478
+ 52. `ChromaticAberration` -- per-channel UV offset
479
+
480
+ ### Tonemapping & Colour Science
481
+
482
+ 53. `ACESTonemap` ★ NEW -- ACES filmic tone mapping via two 3x3 matrix transforms. Industry-standard operator that compresses HDR values to display range with a characteristic S-curve and warm highlight rolloff. Essential companion to any HDR-range generator (SkyAtmosphere, RaymarchTunnel, Bloom). Input/output both vec3 RGB. No uniforms required -- the matrices are constant.
483
+ 54. `ReinhardTonemap` -- simple Reinhard `col / (col + 1)` tone compression
484
+ 55. `TanhTonemap` ★ NEW -- `tanh(col * exposure)` soft sigmoid compression. Gentler than ACES -- preserves saturation and has no warm rolloff. Used in dawn5. Particularly good for procedural colour fields that can briefly exceed 1.0. `exposure` prop controls how aggressively values are compressed (dawn5 uses 2.5).
485
+ 56. `ExponentialFog` ★ NEW -- depth/height based fog blend to a fog colour using `exp2(-depth * density)` falloff.
486
+
487
+ ### SDF-to-Colour ★ NEW
488
+
489
+ 57. `SDFColourMask` ★ NEW -- multiplies a colour by a float mask value. The `col.mul(t)` pattern that appears in every phobon shader -- applies a SDF or noise-derived float as a multiplicative mask to the colour. While technically equivalent to `MultiplyBlend` with a greyscale input, naming it explicitly makes the scalar-pipeline intent clear. Exposes: `maskInput` (float node), `clampMask` (bool, default true).
490
+ 58. `SDFRadialMask` ★ NEW -- `oneMinus(length(uv * scale))` -- simple radial falloff from centre without smoothstep. Softer than Vignette, additive rather than darkening. The `r.addAssign(oneMinus(length(_uv.mul(vignette))))` pattern in flare8/9. Exposes: `scale`, `offset`.
491
+
492
+ ---
493
+
494
+ ## V. Overlays
495
+
496
+ > colour → colour, additive or composited, post-process in nature.
497
+
498
+ ### Film & Analogue
499
+
500
+ 1. `Grain` -- white noise film grain (fract/sin/dot pattern)
501
+ 2. `FilmGrain` ★ NEW -- hash-based film grain using length(hash(uv)) rather than the fract(sin(dot())) pattern. Produces a slightly coarser, more organic texture. Used in Shadertoy snippet 4. `filmGrainNoise = length(hash(vec2(uv.x, uv.y)))`. Subtracted from colour rather than added, darkening rather than brightening.
502
+ 3. `ColourGrain` -- per-channel coloured grain
503
+ 4. `FilmBurn` -- orange-edge light leak effect
504
+ 5. `Halation` -- red glow bleed around highlights
505
+ 6. `Dust` -- random static dust particle overlay
506
+ 7. `Scratches` -- animated vertical film scratch lines
507
+ 8. `AgedFilm` -- combined grain + scratch + tone shift preset
508
+ 9. `CRTScanlines` -- horizontal scanline darkening. The simple vertical sine `sin(uv.y) / 2 + 0.7` pattern from the CRT snippet.
509
+ 10. `CRTCurvature` -- barrel distortion + edge darkening
510
+ 11. `VHSBleed` -- colour bleed + horizontal noise drift
511
+
512
+ ### CRT / Screen Effects
513
+
514
+ 12. `ChromaticScreenWaves` ★ NEW -- per-channel animated sine waves along the Y axis, producing a chromatic oscillation effect. Each RGB channel gets its own phase offset: `R: sin(20/res.y * uv.y + (-time*2 - 0.4)) / 10 + 0.85` `G: sin(20/res.y * uv.y + (-time*2)) / 10 + 0.85` `B: sin(20/res.y * uv.y + (-time*2 + 0.4)) / 10 + 0.85` Combined with screen stripe and applied as a multiplicative overlay. From the CRT Shadertoy snippet. Takes `amplitude`, `frequency`, `speed`, `phaseOffset` (the 0.4 channel separation).
515
+
516
+ ### Spatial
517
+
518
+ 13. `Vignette` -- edge darkening
519
+ 14. `ColourVignette` -- vignette that shifts colour rather than just darkens
520
+ 15. `Bloom` -- glow on bright areas (requires BackdropPass)
521
+ 16. `LensFlare` -- procedural lens flare
522
+ 17. `GodRays` -- volumetric light shaft from a point
523
+ 18. `DepthFog` -- depth-based fog blend
524
+ 19. `ChromaticAberration` -- RGB fringe at screen edges. The subtle UV offset pattern from the CRT snippet: `UR = U + vec2(0.001, 0)` etc.
525
+ 20. `MotionBlur` -- velocity-based directional blur
526
+ 21. `RadialBlur` -- zoom blur from centre
527
+ 22. `TiltShift` -- simulated tilt-shift focus blur bands
528
+
529
+ ### Texture & Surface
530
+
531
+ 23. `Roughness` -- micro-detail noise
532
+ 24. `PaperTexture` -- overlaid paper grain texture
533
+ 25. `CanvasTexture` -- woven canvas fibre texture
534
+ 26. `WatercolourEdge` -- soft paint bleed at edges
535
+ 27. `HalftoneOverlay` -- dot-screen halftone blend
536
+ 28. `CrossHatchOverlay` -- ink crosshatch texture
537
+ 29. `RisographGrain` -- coarse risograph-style grain per layer
538
+ 30. `PrintNoise` -- ink spread simulation
539
+ 31. `FabricWeave` -- fine woven texture overlay
540
+ 32. `ConcreteSurface` -- rough mineral surface texture
541
+
542
+ ### Atmosphere
543
+
544
+ 33. `Haze` -- additive white haze toward screen edges
545
+ 34. `DustAtmosphere` -- floating particles in depth
546
+ 35. `Bokeh` -- out-of-focus circular light discs
547
+ 36. `Starfield` -- procedural star point field
548
+ 37. `Aurora` -- layered animated colour bands
549
+ 38. `LightShaft` -- single animated crepuscular ray
550
+ 39. `Caustics` -- animated refracted light patterns
551
+ 40. `HeatHaze` -- animated UV distortion simulating heat
552
+ 41. `RainDrops` -- animated spherical lens drops on surface
553
+ 42. `SnowOverlay` -- drifting particle snow
554
+
555
+ ### Edge & Shape
556
+
557
+ 43. `EdgeGlow` -- glow along detected colour edges
558
+ 44. `OuterGlow` -- glow emanating outward from shapes
559
+ 45. `InnerGlow` -- glow emanating inward from edges
560
+ 46. `Outline` -- hard edge detection outline
561
+ 47. `Emboss` -- directional normal-map style emboss
562
+ 48. `NeonGlow` -- intense additive bloom on saturated colours
563
+ 49. `DropShadow` -- procedural directional shadow offset
564
+ 50. `FrostEdge` -- crystalline frost texture toward edges
565
+ 51. `BurnEdge` -- darkened scorch mark at edges
566
+ 52. `GlitchEdge` -- pixel displacement noise on detected edges
567
+
568
+ ---
569
+
570
+ ## VI. Shader Conversions
571
+
572
+ > Each existing shader mapped to block compositions.
573
+
574
+ ---
575
+
576
+ ### 1. ColorBends (vue-bits)
577
+
578
+ **What it does:** Rotatable, aspect-corrected, mouse-reactive field of colour bands. Each colour generates a sine-based wave layer. Blended with a length-based intensity and optional coverage-based transparency.
579
+
580
+ **Key insight:** The `q /= 0.5 + 0.2 * dot(q, q)` line is a lens/fisheye distortion applied in UV space before the wave layers. Worth exposing as a standalone UV op.
581
+
582
+ <ShaderPipeline>
583
+ <!-- UV prep -->
584
+ <UVAspectCorrect :order="0" />
585
+ <UVCentre :order="1" />
586
+ <UVRotate :angle-prop="rotation" :auto-rotate="autoRotate" :order="2" />
587
+ <UVScale :scale="scale" :order="3" />
588
+ <UVFisheye :strength="0.2" :order="4" /> <!-- q /= 0.5 + 0.2*dot(q,q) -->
589
+ <UVParallax :factor="parallax" :order="5" />
590
+ <UVMousePull :influence="mouseInfluence" :order="6" />
591
+
592
+ <!-- One WaveBendLayer per colour, stacked -->
593
+ <WaveBendLayer
594
+ v-for="(color, i) in colors"
595
+ :key="i"
596
+ :color="color"
597
+ :frequency="frequency"
598
+ :warp-strength="warpStrength"
599
+ :index="i"
600
+ :order="7 + i"
601
+ />
602
+
603
+ <!-- Alpha mode -->
604
+ <CoverageAlpha v-if="transparent" :order="20" />
605
+
606
+ <!-- Overlays -->
607
+ <Grain :intensity="noise" :animated="true" :order="21" />
608
+ </ShaderPipeline>
609
+
610
+ **Uniform reactivity:** `rotation` and `autoRotate` drive a computed vec2 `(cos(rad), sin(rad))` uniform updated in the render loop. The rotation is not a UV transform in the traditional sense -- it's a rotation matrix applied before the wave layers, so it lives in the UV chain. `UVRotate` handles this with a `time-driven` prop for auto-rotation.
611
+
612
+ **New blocks identified:** `UVAspectCorrect`, `UVParallax`, `UVMousePull`, `WaveBendLayer`, `CoverageAlpha`
613
+
614
+ ---
615
+
616
+ ### 2. Grainient (vue-bits / OGL)
617
+
618
+ **What it does:** Noise-driven UV rotation, asymmetric sine warp, two-layer rotated gradient blend, grain + contrast/gamma/saturation post-processing.
619
+
620
+ <ShaderPipeline>
621
+ <!-- UV transforms -->
622
+ <UVAspectCorrect :order="0" />
623
+ <UVCentre :order="1" />
624
+ <UVScale :scale="zoom" :offset-x="centerX" :offset-y="centerY" :order="2" />
625
+ <UVNoiseRotate
626
+ :noise-scale="noiseScale"
627
+ :rotation-amount="rotationAmount"
628
+ :speed="0.1"
629
+ :order="3"
630
+ />
631
+ <UVSineWarpXY
632
+ :frequency="warpFrequency"
633
+ :amplitude="warpAmplitude"
634
+ :speed="warpSpeed"
635
+ :order="4"
636
+ />
637
+
638
+ <!-- Two colour layers blended by Y axis -->
639
+ <RotatedGradientBlend
640
+ :color-a="color3"
641
+ :color-b="color2"
642
+ :angle="-blendAngle"
643
+ :edge0="edge0"
644
+ :edge1="edge1"
645
+ :layer="1"
646
+ :order="5"
647
+ />
648
+ <RotatedGradientBlend
649
+ :color-a="color2"
650
+ :color-b="color1"
651
+ :angle="-blendAngle"
652
+ :edge0="edge0"
653
+ :edge1="edge1"
654
+ :layer="2"
655
+ :order="6"
656
+ />
657
+ <!-- Mix the two layers by tuv.y -->
658
+ <MixBlend :axis="'y'" :edge0="blendSoftness" :order="7" />
659
+
660
+ <!-- Post-processing -->
661
+ <BrightnessContrast :contrast="contrast" :order="8" />
662
+ <Saturation :amount="saturation" :order="9" />
663
+ <Gamma :value="gamma" :order="10" />
664
+
665
+ <!-- Overlay -->
666
+ <Grain
667
+ :intensity="grainAmount"
668
+ :scale="grainScale"
669
+ :animated="grainAnimated"
670
+ :order="11"
671
+ />
672
+ </ShaderPipeline>
673
+
674
+ **New blocks identified:** `UVNoiseRotate`, `UVSineWarpXY`, `RotatedGradientBlend`
675
+
676
+ ---
677
+
678
+ ### 3. Shadertoy -- Basic Gradient (snippet 3)
679
+
680
+ **What it does:** Same core as Grainient but no grain, no post-processing, hardcoded two colour pairs, single blend rotation angle.
681
+
682
+ <ShaderPipeline>
683
+ <UVAspectCorrect :order="0" />
684
+ <UVCentre :order="1" />
685
+ <UVNoiseRotate :rotation-amount="720" :speed="0.1" :order="2" />
686
+ <UVSineWarpXY :frequency="5" :amplitude="30" :speed="2" :order="3" />
687
+
688
+ <RotatedGradientBlend
689
+ color-a="#f4cd9f"
690
+ color-b="#3162ee"
691
+ :angle="-5"
692
+ :edge0="-0.3"
693
+ :edge1="0.2"
694
+ :layer="1"
695
+ :order="4"
696
+ />
697
+ <RotatedGradientBlend
698
+ color-a="#e882cc"
699
+ color-b="#59b5f3"
700
+ :angle="-5"
701
+ :edge0="-0.3"
702
+ :edge1="0.2"
703
+ :layer="2"
704
+ :order="5"
705
+ />
706
+ <MixBlend :axis="'y'" :edge0="0.5" :edge1="-0.3" :order="6" />
707
+ </ShaderPipeline>
708
+
709
+ This is the minimal form of the Grainient pattern -- useful as a named preset `<BasicGradient />`.
710
+
711
+ ---
712
+
713
+ ### 4. Shadertoy -- Grainient + Day/Night Cycle (snippet 4)
714
+
715
+ **What it does:** Same warp as snippet 3, but colours cycle between light and dark palettes using a sinusoidal time function, and adds hash-based film grain.
716
+
717
+ <ShaderPipeline>
718
+ <UVAspectCorrect :order="0" />
719
+ <UVCentre :order="1" />
720
+ <UVNoiseRotate :rotation-amount="720" :speed="0.05" :order="2" />
721
+ <UVSineWarpXY :frequency="5" :amplitude="30" :speed="2" :order="3" />
722
+
723
+ <!-- Day/night colour cycle -->
724
+ <DayNightCycle
725
+ :colors-light="['#4bba89', '#3162ee', '#f69292', '#59b5f3']"
726
+ :colors-dark="['#6931f5', '#202a32', '#e93334', '#e9a04b']"
727
+ :speed="0.5"
728
+ :order="4"
729
+ />
730
+ <!-- The cycle outputs 4 resolved colours used by the gradient layers below -->
731
+
732
+ <RotatedGradientBlend :angle="-5" :edge0="-0.3" :edge1="0.2" :layer="1" :order="5" />
733
+ <RotatedGradientBlend :angle="-5" :edge0="-0.3" :edge1="0.2" :layer="2" :order="6" />
734
+ <MixBlend :axis="'y'" :edge0="0.5" :edge1="-0.3" :order="7" />
735
+
736
+ <!-- Hash-based film grain, subtractive -->
737
+ <FilmGrain :intensity="0.05" :subtractive="true" :order="8" />
738
+ </ShaderPipeline>
739
+
740
+ **New blocks identified:** `DayNightCycle`, `FilmGrain`
741
+
742
+ ---
743
+
744
+ ### 5. Shadertoy -- Volumetric Tunnel (snippet 1)
745
+
746
+ **What it does:** A raymarching loop producing a glowing volumetric tunnel. SDF sphere at z=15, fractional XY tiling with a rotation matrix driven by sin(z)\*sin(time), additive colour accumulation per step with chromatic time offsets.
747
+
748
+ This cannot be meaningfully decomposed -- the raymarching loop is load-bearing. It lives as a self-contained `RaymarchTunnel` generator.
749
+
750
+ <ShaderPipeline>
751
+ <RaymarchTunnel
752
+ :glow-color="[0.2, 0.1, 0.04]"
753
+ :glow-falloff="0.1"
754
+ :steps="100"
755
+ :sphere-radius="1.0"
756
+ :sphere-z="15.0"
757
+ :tiling-rotation="true"
758
+ :speed="0.5"
759
+ :chromatic-spread="0.15"
760
+ :order="0"
761
+ />
762
+
763
+ <!-- Overlays are still composable on top -->
764
+ <Vignette :strength="0.3" :order="1" />
765
+ </ShaderPipeline>
766
+
767
+ **Architecture note:** `RaymarchTunnel` is the first generator that uses a loop internally. TSL supports `Loop()` nodes natively, so this compiles correctly -- but it means the block cannot feed into or receive from UV transformers in the normal pipeline sense. It reads `uv()` and `time` directly and outputs the final colour. Mark this block as `terminal: true` -- it should always be `order: 0` and UV transformers have no effect on it.
768
+
769
+ ---
770
+
771
+ ### 6. Shadertoy -- CRT / Chromatic Screen Effect (snippet 2)
772
+
773
+ **What it does:** Takes a background texture (iChannel0), applies subtle per-channel UV offset for chromatic aberration, multiplies by per-channel animated sine waves plus a screen stripe, and adjusts for contrast loss.
774
+
775
+ <ShaderPipeline>
776
+ <!-- Base texture with RGB channel split -->
777
+ <TextureSampler :src="backgroundTexture" :order="0" />
778
+ <ChromaticAberration :red-offset="[0.001, 0]" :blue-offset="[-0.001, 0]" :order="1" />
779
+
780
+ <!-- CRT screen overlays -->
781
+ <CRTScanlines :order="2" />
782
+ <ChromaticScreenWaves
783
+ :amplitude="0.1"
784
+ :frequency="20"
785
+ :speed="2"
786
+ :phase-offset="0.4"
787
+ :order="3"
788
+ />
789
+
790
+ <!-- Compensate for contrast loss from blending -->
791
+ <BrightnessContrast :contrast="1.25" :order="4" />
792
+ </ShaderPipeline>
793
+
794
+ **New blocks identified:** `ChromaticScreenWaves`
795
+
796
+ ---
797
+
798
+ ### 7. Stripe Gradient
799
+
800
+ **What it does:** Subdivided plane with vertex noise displacement, per-colour simplex noise wave layers in the fragment shader, diagonal cut via CSS.
801
+
802
+ <!-- CSS container: transform: skewY(-12deg); overflow: hidden -->
803
+ <TresCanvas>
804
+ <TresMesh>
805
+ <TresPlaneGeometry :args="[2, 2, 320, 80]" />
806
+ <ShaderPipeline>
807
+ <!-- Vertex stage -->
808
+ <MeshWarp
809
+ :noise-freq="[2, 3]"
810
+ :noise-amp="320"
811
+ :noise-speed="0.2"
812
+ :noise-flow="3"
813
+ :incline="0.4"
814
+ stage="vertex"
815
+ :order="0"
816
+ />
817
+
818
+ <!-- Fragment stages -->
819
+ <SolidColour css-color="--color-stripe-base" :order="0" />
820
+ <WaveColourLayer
821
+ v-for="(color, i) in stripeColors"
822
+ :key="i"
823
+ :css-color="`--color-stripe-${i + 1}`"
824
+ v-bind="useWaveLayerDefaults(i, stripeColors.length)"
825
+ :order="1 + i"
826
+ />
827
+ </ShaderPipeline>
828
+ </TresMesh>
829
+ </TresCanvas>
830
+
831
+ ---
832
+
833
+ ### 8. Heatmap
834
+
835
+ **What it does:** Pre-processed image texture (R=contour, G=outerBlur, B=innerBlur), animated organic shadow shapes (3 time-offset copies), heat float → n-colour gradient.
836
+
837
+ <ShaderPipeline>
838
+ <ProcessedImageSampler :src="imageSrc" :order="0" />
839
+ <ShadowShape
840
+ :inner-glow="innerGlow"
841
+ :contour="contour"
842
+ :order="1"
843
+ />
844
+ <OuterHeatGlow
845
+ :outer-glow="outerGlow"
846
+ :angle="angle"
847
+ :order="2"
848
+ />
849
+ <HeatToGradient
850
+ :colors="colors"
851
+ css-color-back="--color-bg-base"
852
+ :order="3"
853
+ />
854
+ <Grain :intensity="noise" :order="4" />
855
+ </ShaderPipeline>
856
+
857
+ **CPU pre-processing step lives outside the shader system:**
858
+
859
+ const { blob } = await processHeatmapImage(src)
860
+ // Bakes contour(R), outerBlur(G), innerBlur(B) into a single texture
861
+
862
+ ---
863
+
864
+ ### 9. Glass / Wave Distortion
865
+
866
+ **What it does:** FBM-derived normal map drives refraction UV offset into a blurred backdrop sample. Fresnel edge brightening, tint, chromatic aberration.
867
+
868
+ <TresCanvas>
869
+ <SceneContent />
870
+ <BackdropPass :blur="12" id="scene-backdrop" />
871
+
872
+ <TresMesh>
873
+ <ShaderPipeline backdrop="scene-backdrop">
874
+ <FBMNoise :octaves="3" :scale="2.0" :order="0" />
875
+ <NormalFromHeight :strength="0.04" :order="1" />
876
+ <RefractionOffset :ior="0.08" :order="2" />
877
+ <BackdropBlur :order="3" />
878
+ <MixBlend css-color="--color-glass-tint" :factor="0.08" :order="4" />
879
+ <FresnelBlend css-color="--color-glass-edge" :power="2.0" :order="5" />
880
+ <ChromaticAberration :strength="0.003" :order="6" />
881
+ <EdgeGlow :strength="0.15" :order="7" />
882
+ </ShaderPipeline>
883
+ </TresMesh>
884
+ </TresCanvas>
885
+
886
+ ---
887
+
888
+ ## Block Interaction Map
889
+
890
+ Reliable composition patterns:
891
+
892
+ UVNoiseRotate + UVSineWarpXY + RotatedGradientBlend → Grainient family
893
+ UVNoiseRotate + UVSineWarpXY + DayNightCycle → animated palette gradient
894
+ WaveBendLayer × n + CoverageAlpha → ColorBends
895
+ WaveColourLayer × n + MeshWarp (vertex) → Stripe gradient
896
+ FBMNoise + NormalFromHeight + RefractionOffset → glass refraction
897
+ TextureSampler + ChromaticAberration + ChromaticScreenWaves + CRTScanlines → CRT
898
+ UVPolar + Stripes → radial spokes
899
+ DomainWarpedNoise + ColourRamp → flowing lava / aurora
900
+ UVKaleidoscope + any generator → mandala
901
+ Halation + Bloom + ColourGrain → cinematic film look
902
+ DuoTone + Grain + Vignette → risograph print preset
903
+ DayNightCycle + Grain + FilmGrain → animated film look
904
+ RaymarchTunnel + Vignette → tunnel scene
905
+ FisheyeRay + RayTiltBasis + SkyAtmosphere + ACESTonemap → sky dome
906
+ FisheyeRay + RaySphereReflect + SkyAtmosphere → mirrored sky ball
907
+ BilinearGradient (4 CSS vars) → fully theme-reactive background
908
+ SkyAtmosphere + ExponentialFog + Grain → hazy atmospheric scene
909
+
910
+ // CosinePalette scalar pipeline patterns
911
+ CosinePalette(uv.y) + UVFractBand + Grain → dawn2 animated shimmer
912
+ CosinePalette(sdSphere * noise) + UVColumnOffset + SDFColourMask → dawn4/5 family
913
+ CosinePalette(length(uv) * noise) + ChebyshevNoiseField → genuary22
914
+ CosinePalette(imaginary) + ComplexPlaneField + Grain → imaginary2 complex field
915
+ CosinePalette(uv.y) + RingField × n + SDFRadialMask → flare8/9 ring gradient
916
+ any SDF + CosinePalette + SDFColourMask → any shape with palette colouring
917
+ SimplexNoise → CosinePalette → animated noise gradient
918
+
919
+ ---
920
+
921
+ ### 13. imaginary2 (phobon TSL)
922
+
923
+ **What it does:** Visualises the imaginary component of a complex logarithm of a Möbius transformation. Two poles at `p` and `q` (placed by angle/distance) divide the screen. For every pixel `z`, computes `log((z-p)/(z-q))` -- the imaginary part gives the winding angle around the two poles, creating smooth bands that wrap the plane. Combined with a radial `exp(z.x)/exp(z.y)` term for the hyperbolic texture.
924
+
925
+ <ShaderPipeline>
926
+ <!-- Scalar pipeline: UV → complex field float → palette → colour -->
927
+ <ComplexPlaneField
928
+ :pole-angle="PI * 1.8"
929
+ :pole-distance="0.25"
930
+ :imaginary-weight="0.75"
931
+ :radial-weight="1.0"
932
+ :order="0"
933
+ />
934
+ <!-- t from ComplexPlaneField feeds CosinePalette -->
935
+ <CosinePalette
936
+ :a="[0.5, 0.52, 0.53]"
937
+ :b="[0.46, 0.32, 0.35]"
938
+ :c="[0.82, 0.84, 0.65]"
939
+ :d="[0.53, 0.23, 0.22]"
940
+ :frequency="0.6 * 2 * PI"
941
+ :order="1"
942
+ />
943
+ <Grain :intensity="0.2" :order="2" />
944
+ </ShaderPipeline>
945
+
946
+ ---
947
+
948
+ ### 14. dawn2 (phobon TSL)
949
+
950
+ **What it does:** Animated cosine palette scrolling vertically. Adds a fract-banding shimmer: `fract(uv.y * 24) * 0.3` raised to power 2 creates bright horizontal flicker lines that sit on top of the smooth gradient.
951
+
952
+ <ShaderPipeline>
953
+ <!-- Base: time-scrolling cosine palette on Y -->
954
+ <CosinePalette
955
+ :a="[0.5, 0.5, 0.5]"
956
+ :b="[0.5, 0.5, 0.5]"
957
+ :c="[1.0, 0.7, 0.4]"
958
+ :d="[0.0, 0.15, 0.2]"
959
+ scalar-input="uv.y - time * 0.1"
960
+ :order="0"
961
+ />
962
+ <!-- Additive shimmer bands -->
963
+ <UVFractBand
964
+ :frequency="24"
965
+ :scale="0.3"
966
+ :power="2.0"
967
+ :order="1"
968
+ />
969
+ <Grain :intensity="0.2" :order="2" />
970
+ </ShaderPipeline>
971
+
972
+ **Note:** `scalar-input="uv.y - time * 0.1"` is a string expression shorthand. In practice this would be a TSL node: `uv().y.sub(time.mul(0.1))` passed as a prop.
973
+
974
+ ---
975
+
976
+ ### 15. dawn4 (phobon TSL)
977
+
978
+ **What it does:** Column-offset UV creates vertical banding, simplex noise drives both a box SDF mask and the cosine palette. Two noise octaves at different frequencies.
979
+
980
+ <ShaderPipeline>
981
+ <!-- UV: column offset + sine softening -->
982
+ <UVAspectCorrect :order="0" />
983
+ <UVColumnOffset
984
+ :repetitions="12"
985
+ :offset-factor="0.005"
986
+ :sine-softness="0.05"
987
+ :order="1"
988
+ />
989
+
990
+ <!-- Scalar pipeline: sdSphere * noise → palette -->
991
+ <CosinePalette
992
+ :a="[0.8, 0.5, 0.4]"
993
+ :b="[0.2, 0.4, 0.2]"
994
+ :c="[2.0, 1.0, 1.0]"
995
+ :d="[0.0, 0.25, 0.25]"
996
+ scalar-input="sdSphere(uv0) * noise3d(offsetUv)"
997
+ :order="2"
998
+ />
999
+
1000
+ <!-- Mask: 1 - sdBox2d(uv * noise) applied multiplicatively -->
1001
+ <SDFColourMask
1002
+ mask-input="oneMinus(sdBox2d(offsetUv * noiseScale))"
1003
+ :order="3"
1004
+ />
1005
+
1006
+ <Grain :intensity="0.2" :order="4" />
1007
+ </ShaderPipeline>
1008
+
1009
+ ---
1010
+
1011
+ ### 16. dawn5 (phobon TSL)
1012
+
1013
+ **What it does:** Like dawn4 but uses `abs(sdSphere) + noise` for the mask (creates a ring-like silhouette rather than box), and applies `tanh` tonemapping.
1014
+
1015
+ <ShaderPipeline>
1016
+ <UVAspectCorrect :order="0" />
1017
+ <UVColumnOffset :repetitions="12" :offset-factor="0.005" :order="1" />
1018
+
1019
+ <CosinePalette
1020
+ :a="[0.5, 0.5, 0.5]"
1021
+ :b="[0.5, 0.5, 0.5]"
1022
+ :c="[1.0, 1.0, 0.5]"
1023
+ :d="[0.8, 0.9, 0.3]"
1024
+ scalar-input="sdSphere(uv0) + noise3d(offsetUv * 3, time * 0.25) * 0.12"
1025
+ :order="2"
1026
+ />
1027
+
1028
+ <!-- Ring mask: 1 - |sdSphere(offsetUv)| - noise -->
1029
+ <SDFColourMask
1030
+ mask-input="oneMinus(abs(sdSphere(offsetUv)) + noise)"
1031
+ :order="3"
1032
+ />
1033
+
1034
+ <!-- Tanh tone compression before grain -->
1035
+ <TanhTonemap :exposure="2.5" :order="4" />
1036
+ <Grain :intensity="0.2" :order="5" />
1037
+ </ShaderPipeline>
1038
+
1039
+ ---
1040
+
1041
+ ### 17. flare9 (phobon TSL)
1042
+
1043
+ **What it does:** Single ring field (`abs(length(uv + offset) - radius)`) combined with a radial vignette mask. Cosine palette on uv.y.
1044
+
1045
+ <ShaderPipeline>
1046
+ <UVAspectCorrect :order="0" />
1047
+
1048
+ <!-- Ring SDF accumulated with radial vignette into a float mask -->
1049
+ <RingField
1050
+ :rings="[{ offset: [0.5, -0.5], radius: 0.55, scale: 1.75 }]"
1051
+ :vignette-scale="3"
1052
+ :order="1"
1053
+ />
1054
+
1055
+ <!-- Palette driven by uv.y -->
1056
+ <CosinePalette
1057
+ :a="[0.5, 0.5, 0.5]"
1058
+ :b="[0.5, 0.5, 0.5]"
1059
+ :c="[1.0, 1.0, 1.0]"
1060
+ :d="[0.0, 0.33, 0.67]"
1061
+ scalar-input="uv.y * 0.25 + 2.25"
1062
+ :order="2"
1063
+ />
1064
+
1065
+ <!-- Ring mask applied multiplicatively to palette colour -->
1066
+ <SDFColourMask :order="3" />
1067
+ <Grain :intensity="0.2" :order="4" />
1068
+ </ShaderPipeline>
1069
+
1070
+ ---
1071
+
1072
+ ### 18. flare8 (phobon TSL)
1073
+
1074
+ **What it does:** Three overlapping rings at different positions, scales, and radii, accumulated into a single mask. More complex ring field than flare9. Uniform-driven so all parameters are live-adjustable.
1075
+
1076
+ <ShaderPipeline>
1077
+ <UVAspectCorrect :order="0" />
1078
+
1079
+ <!-- Three rings accumulated + radial vignette -->
1080
+ <RingField
1081
+ :rings="[
1082
+ { offset: [0.1, 0.5], radius: 0.25, scale: 1.5, mode: 'abs' },
1083
+ { offset: [-0.25,-0.3], radius: 0.45, scale: 1.75, mode: 'oneMinus' },
1084
+ { offset: [0.25, -0.5], radius: 0.75, scale: 0.5, mode: 'abs' }
1085
+ ]"
1086
+ :vignette-scale="3"
1087
+ :order="1"
1088
+ />
1089
+
1090
+ <CosinePalette
1091
+ css-a="--shader-palette-a"
1092
+ css-b="--shader-palette-b"
1093
+ css-c="--shader-palette-c"
1094
+ css-d="--shader-palette-d"
1095
+ :offset="0.5"
1096
+ scalar-input="uv.y * 0.5 + offset"
1097
+ :order="2"
1098
+ />
1099
+
1100
+ <SDFColourMask :order="3" />
1101
+ <Grain :intensity="0.2" :order="4" />
1102
+ </ShaderPipeline>
1103
+
1104
+ **Note:** flare8 uses `uniform()` for its palette parameters -- mapped directly to CSS var props on `CosinePalette` (`css-a`, `css-b` etc.) for full theme reactivity.
1105
+
1106
+ ---
1107
+
1108
+ ### 19. genuary22 (phobon TSL)
1109
+
1110
+ **What it does:** Noise-distorted Chebyshev box shape coloured by cosine palette. Two simplex noise octaves modulate both the mask and the palette input. No grain.
1111
+
1112
+ <ShaderPipeline>
1113
+ <UVAspectCorrect :order="0" />
1114
+
1115
+ <!-- Noise-eroded Chebyshev cross mask -->
1116
+ <ChebyshevNoiseField
1117
+ :scale-x="2.5"
1118
+ :scale-y="1.5"
1119
+ :noise-frequency-2="2.0"
1120
+ :time-speed="0.1"
1121
+ :order="1"
1122
+ />
1123
+
1124
+ <!-- Palette: length(uv) * noise as input -->
1125
+ <CosinePalette
1126
+ :a="[0.5, 0.5, 0.5]"
1127
+ :b="[0.5, 0.5, 0.5]"
1128
+ :c="[1.0, 0.7, 0.4]"
1129
+ :d="[0.0, 0.15, 0.2]"
1130
+ scalar-input="length(uv) * noise"
1131
+ :order="2"
1132
+ />
1133
+
1134
+ <SDFColourMask :order="3" />
1135
+ </ShaderPipeline>
1136
+
1137
+ **New blocks identified:** `CosinePalette`, `RingField`, `ChebyshevNoiseField`, `ComplexPlaneField`, `SDFColourMask`, `SDFRadialMask`, `TanhTonemap`, `UVColumnOffset`, `UVFractBand`, `ComplexDiv`, `ComplexLog`
1138
+
1139
+ ---
1140
+
1141
+ ### 10. Four-Corner Gradient (Shadertoy snippet 2)
1142
+
1143
+ **What it does:** The simplest possible 2D colour field. Four colours at each corner, bilinearly interpolated across the UV space. No animation, no noise, no distortion.
1144
+
1145
+ <ShaderPipeline>
1146
+ <BilinearGradient
1147
+ css-color-bl="--color-bg-base"
1148
+ css-color-br="--color-accent"
1149
+ css-color-tl="--color-surface"
1150
+ css-color-tr="--color-highlight"
1151
+ :order="0"
1152
+ />
1153
+ </ShaderPipeline>
1154
+
1155
+ This is the atomic baseline generator -- worth having as a named block purely because it composes so well with UV transformers. Run it through `UVNoiseRotate` + `UVSineWarpXY` and you get a variant of the Grainient family with distinct corner anchors.
1156
+
1157
+ ---
1158
+
1159
+ ### 11. Sky Gradient (Shadertoy -- Hazel Quantock)
1160
+
1161
+ **What it does:** Physically-approximated sky dome using exponential atmospheric scattering. Ray-based -- converts screen coords to a 3D ray, tilts it upward, applies optional fisheye and sphere reflection, evaluates sky colour + sun disc, ACES tonemaps.
1162
+
1163
+ **Key insight:** The sky is built from three separable concerns -- the ray setup, the atmospheric function, and the tonemapping. Each maps cleanly to a block.
1164
+
1165
+ <ShaderPipeline>
1166
+ <!-- Ray Pipeline (stage: 'ray') -- builds and transforms the view ray -->
1167
+ <FisheyeRay :strength="0.5" stage="ray" :order="0" />
1168
+ <RayTiltBasis :forward="[0, 0.8, 1]" stage="ray" :order="1" />
1169
+ <!-- Optional: -->
1170
+ <!-- <RaySphereReflect :centre-z="1.8" :radius="1.0" stage="ray" :order="2" /> -->
1171
+
1172
+ <!-- Terminal generator -- consumes ray, outputs sky colour (HDR range) -->
1173
+ <SkyAtmosphere
1174
+ :scatter-coeffs="[0.1, 0.3, 0.6]"
1175
+ :sun-disc-sharpness="0.9995"
1176
+ :sun-disc-colour="[10, 3, 0.2]"
1177
+ :order="0"
1178
+ >
1179
+ <!-- Sun direction driven by mouse or time -->
1180
+ <SunDirectionUniform slot="sunDir" :speed="0.5" :mouse-driven="true" />
1181
+ </SkyAtmosphere>
1182
+
1183
+ <!-- Tonemapping -- ACES compresses HDR sky values to display range -->
1184
+ <ACESTonemap :order="1" />
1185
+
1186
+ <!-- Overlays still composable on top -->
1187
+ <ExponentialFog css-color="--color-fog" :density="0.1" :order="2" />
1188
+ </ShaderPipeline>
1189
+
1190
+ **The sun direction slot** is a new pattern -- `SkyAtmosphere` exposes a named input slot for its sun direction uniform rather than taking it as a plain prop. This lets you swap between `SunDirectionUniform` (mouse/time driven), `CSSFloatUniform` (theme driven), or a static `AngleConst` without changing the generator itself.
1191
+
1192
+ **ACES is mandatory here** -- `SkyAtmosphere` outputs HDR values (sun disc peaks at `vec3(10, 3, 0.2)`). Without tonemapping the canvas clips to white. The pipeline compositor should warn if a terminal HDR generator has no tonemap op downstream.
1193
+
1194
+ **New blocks identified:** `SkyAtmosphere`, `BilinearGradient`, `FisheyeRay`, `RayTiltBasis`, `RaySphereReflect`, `SunDirectionUniform`, `RayCameraUniform`, `ACESTonemap`, `ExponentialFog`, `ReinhardTonemap`
1195
+
1196
+ ---
1197
+
1198
+ ### 12. Mountain Terrain (Shadertoy -- DDA Heightmap Raymarcher)
1199
+
1200
+ **What it does:** First-person voxel terrain using 2D DDA traversal. A separate buffer pass (iChannel0) stores and updates camera position and rotation each frame. The main pass reads that state, builds a view ray, then marches through a hexagonally-skewed grid, sampling a noise texture for height at each cell boundary until the ray intersects the terrain surface.
1201
+
1202
+ **The three non-obvious parts:**
1203
+
1204
+ 1. **Hexagonal grid skew** -- `F = (sqrt(3)-1)/2` is the skew factor that transforms a square grid into one where diagonal traversal is equal cost to axis-aligned traversal. It gives the terrain its diamond/isometric facet appearance without using actual hexagonal geometry.
1205
+
1206
+ 2. **DDA traversal** -- not raymarching in the SDF sense. The loop steps cell by cell in 2D, choosing X or Y boundary based on which is closer, then comparing the ray Z against the elevation at that cell. When `e1 >= fpos.z` the ray has gone underground -- binary interpolate back to the surface.
1207
+
1208
+ 3. **Multi-pass state** -- the camera controller lives in a separate buffer shader that reads keyboard/mouse input and integrates position over time, writing the result as pixel colours. The terrain shader reads this back via `texture(iChannel0, ...)`. This is outside the block system's scope -- it's a simulation pass, not a shader effect. The block system receives the final position/rotation as uniforms.
1209
+
1210
+ **Block composition:**
1211
+
1212
+ <!--
1213
+ Camera state comes from outside the shader system --
1214
+ a separate simulation pass or JS-driven uniforms.
1215
+ The terrain block receives position and rotation as plain uniforms.
1216
+ -->
1217
+ <ShaderPipeline>
1218
+
1219
+ <!-- Ray Pipeline: build view ray from camera state -->
1220
+ <RayFromStateBuffer
1221
+ :position-uniform="cameraPosition"
1222
+ :rotation-uniform="cameraRotation"
1223
+ stage="ray"
1224
+ :order="0"
1225
+ />
1226
+
1227
+ <!-- Terminal generator: DDA terrain traversal -->
1228
+ <HeightmapTerrain
1229
+ :height-texture="noiseTexture"
1230
+ :grid-scale="100"
1231
+ :terrain-amplitude="5"
1232
+ :terrain-radius="20"
1233
+ :facet-colour-a="[1.0, 1.0, 1.0]"
1234
+ :facet-colour-b="[0.15, 0.3, 0.5]"
1235
+ :march-steps="32"
1236
+ :order="0"
1237
+ />
1238
+
1239
+ <!-- Overlays still fully composable -->
1240
+ <ExponentialFog
1241
+ css-color="--color-sky-horizon"
1242
+ :density="0.04"
1243
+ :order="1"
1244
+ />
1245
+ <Vignette :strength="0.25" :order="2" />
1246
+
1247
+ </ShaderPipeline>
1248
+
1249
+ **The simulation pass** (camera movement) sits outside `ShaderPipeline` entirely. In a TresJS context this is a `useRenderTarget` composable that runs each frame, writing position/rotation into a 2×1 float texture that gets passed to the terrain block as a uniform. It does not participate in the block pipeline -- it's infrastructure.
1250
+
1251
+ // Outside the shader system -- camera simulation
1252
+ const { texture: cameraState } = useSimulationPass({
1253
+ width: 2, height: 1,
1254
+ shader: cameraSimulationGLSL, // integrates velocity, handles input
1255
+ floatTexture: true
1256
+ })
1257
+ // cameraState passed as :position-uniform and :rotation-uniform to RayFromStateBuffer
1258
+
1259
+ **New blocks identified:** `HeightmapTerrain`, `HeightmapSampler`, `StateBufferSampler`, `RayFromStateBuffer`
1260
+
1261
+ **New system concept:** `useSimulationPass` -- a composable for multi-pass state simulation that sits outside the block pipeline but feeds into it via uniforms. This is the same pattern needed for fluid simulation, particle systems, and anything that requires frame-to-frame state accumulation.
1262
+
1263
+ ---
1264
+
1265
+ ## Implementation Priority
1266
+
1267
+ Reliable composition patterns worth noting:
1268
+
1269
+ RayFromStateBuffer + HeightmapTerrain + ExponentialFog → terrain flythrough
1270
+ RayFromStateBuffer + HeightmapTerrain + Vignette → minimal terrain view
1271
+ FisheyeRay + RayTiltBasis + SkyAtmosphere + ACESTonemap → sky dome
1272
+ BilinearGradient + UVNoiseRotate + UVSineWarpXY → theme-anchored gradient
1273
+ useSimulationPass → StateBufferSampler → any ray generator → stateful camera
1274
+
1275
+ ### Phase 1 -- Foundation
1276
+
1277
+ Primitives (values, uniforms, CSS bridge, CoverageAlpha), SolidColour, LinearGradient, RadialGradient, GradientNoise, FBMNoise, SimplexNoise, UVAspectCorrect, UVCentre, UVScale, UVRotate, UVNoiseRotate, UVSineWarpXY, UVScroll, **CosinePalette** ← promote to Phase 1 -- backbone of the scalar pipeline, SDFColourMask, SDFRadialMask, RotatedGradientBlend, MixBlend, BrightnessContrast, Gamma, Saturation, TanhTonemap, Grain, FilmGrain, Vignette
1278
+
1279
+ ### Phase 2 -- Expression
1280
+
1281
+ WaveBendLayer, WaveColourLayer, MeshWarp (vertex), UVParallax, UVMousePull, UVWarp, UVTwirl, UVPolar, UVColumnOffset, UVFractBand, DayNightCycle, ColourRamp, DuoTone, Desaturate, Tint, RingField, ChebyshevNoiseField, ChromaticAberration, ChromaticScreenWaves, CRTScanlines, Bloom, Halation
1282
+
1283
+ ### Phase 3 -- Depth
1284
+
1285
+ ProcessedImageSampler, ShadowShape, HeatToGradient, BackdropPass, BackdropBlur, RefractionOffset, FresnelBlend, NormalFromHeight, MultiStopGradient, MeshGradient, DomainWarpedNoise, UVKaleidoscope, UVFeedback, CurlNoise, LiftGammaGain, Curves (LUT), FilmBurn, RaymarchTunnel, BilinearGradient, ACESTonemap, ReinhardTonemap, ComplexPlaneField, ComplexDiv, ComplexLog
1286
+
1287
+ ### Phase 4 -- Ray & Atmosphere
1288
+
1289
+ Ray Pipeline infrastructure (RayCameraUniform, FisheyeRay, RayTiltBasis, RayFromStateBuffer), SkyAtmosphere, SunDirectionUniform, RaySphereReflect, ExponentialFog, RayMouseOrbit, RayAutoOrbit
1290
+
1291
+ ### Phase 5 -- Terrain & Simulation
1292
+
1293
+ HeightmapTerrain, HeightmapSampler, StateBufferSampler, useSimulationPass, UVFeedback (render target), fluid simulation pass, particle simulation pass
1294
+
1295
+ ### Phase 6 -- Surface & Detail
1296
+
1297
+ All texture overlays, GodRays, LensFlare, Caustics, Bokeh, FabricWeave, WatercolourEdge, TriplanarMapping, FlowMap, MotionBlur, full film grain suite, CRTCurvature, VHSBleed, AgedFilm preset, RisographGrain preset
1298
+
1299
+ ---
1300
+
1301
+ ## VII. Implementation Guide
1302
+
1303
+ > Complete code for core infrastructure plus one reference implementation per category. All remaining blocks follow the same patterns -- build these first, then use them as templates.
1304
+
1305
+ ---
1306
+
1307
+ ### File Structure
1308
+
1309
+ layers/shader/
1310
+ ├── index.ts # barrel export
1311
+ ├── composables/
1312
+ │ ├── useShaderStage.ts
1313
+ │ ├── useCSSUniform.ts
1314
+ │ └── useThemeProvider.ts
1315
+ ├── components/
1316
+ │ ├── ShaderPipeline.vue # core compositor
1317
+ │ ├── primitives/
1318
+ │ │ ├── FloatUniform.vue
1319
+ │ │ └── ColourUniform.vue
1320
+ │ ├── generators/
1321
+ │ │ ├── SolidColour.vue
1322
+ │ │ ├── LinearGradient.vue
1323
+ │ │ └── CosinePalette.vue # scalar generator
1324
+ │ ├── uvTransformers/
1325
+ │ │ ├── UVAspectCorrect.vue
1326
+ │ │ ├── UVNoiseRotate.vue
1327
+ │ │ └── UVSineWarpXY.vue
1328
+ │ ├── colourOps/
1329
+ │ │ ├── BrightnessContrast.vue
1330
+ │ │ ├── TanhTonemap.vue
1331
+ │ │ └── SDFColourMask.vue
1332
+ │ └── overlays/
1333
+ │ ├── Grain.vue
1334
+ │ └── Vignette.vue
1335
+ ├── tsl/
1336
+ │ ├── utils/
1337
+ │ │ ├── screenAspectUV.ts
1338
+ │ │ └── colour.ts # CSS colour resolution
1339
+ │ ├── generators/
1340
+ │ │ ├── solidColour.ts
1341
+ │ │ ├── linearGradient.ts
1342
+ │ │ └── cosinePalette.ts
1343
+ │ ├── uvTransformers/
1344
+ │ │ ├── uvAspectCorrect.ts
1345
+ │ │ ├── uvNoiseRotate.ts
1346
+ │ │ └── uvSineWarpXY.ts
1347
+ │ ├── colourOps/
1348
+ │ │ ├── brightnessContrast.ts
1349
+ │ │ └── tanhTonemap.ts
1350
+ │ ├── overlays/
1351
+ │ │ ├── grain.ts
1352
+ │ │ └── vignette.ts
1353
+ │ ├── noise/
1354
+ │ │ └── gradientNoise.ts
1355
+ │ └── sdf/
1356
+ │ └── shapes.ts
1357
+ └── types.ts
1358
+
1359
+ ---
1360
+
1361
+ ### 1. Types
1362
+
1363
+ // layers/shader/types.ts
1364
+ import type { Node } from 'three/tsl'
1365
+
1366
+ export type ShaderStage = 'fragment' | 'vertex' | 'ray'
1367
+
1368
+ export interface StageEntry {
1369
+ fn: (input: Node) => Node
1370
+ order: number
1371
+ stage: ShaderStage
1372
+ }
1373
+
1374
+ export interface ShaderPipelineContext {
1375
+ register: (fn: StageEntry['fn'], order: number, stage: ShaderStage) => void
1376
+ unregister: (fn: StageEntry['fn']) => void
1377
+ }
1378
+
1379
+ export interface CosineParams {
1380
+ a: [number, number, number]
1381
+ b: [number, number, number]
1382
+ c: [number, number, number]
1383
+ d: [number, number, number]
1384
+ }
1385
+
1386
+ ---
1387
+
1388
+ ### 2. Core Composables
1389
+
1390
+ // layers/shader/composables/useThemeProvider.ts
1391
+ import { ref, provide, watch } from 'vue'
1392
+ import { useColorMode } from '@vueuse/core'
1393
+
1394
+ export function useThemeProvider() {
1395
+ const mode = useColorMode()
1396
+ const palette = ref('default')
1397
+ const contrast = ref('standard')
1398
+
1399
+ provide('shaderTheme', { mode, palette, contrast })
1400
+ return { mode, palette, contrast }
1401
+ }
1402
+
1403
+
1404
+
1405
+ // layers/shader/composables/useShaderStage.ts
1406
+ import { inject, onMounted, onUnmounted } from 'vue'
1407
+ import type { Node } from 'three/tsl'
1408
+ import type { ShaderPipelineContext, ShaderStage } from '../types'
1409
+
1410
+ export function useShaderStage(
1411
+ stageFn: (input: Node) => Node,
1412
+ order = 0,
1413
+ stage: ShaderStage = 'fragment'
1414
+ ) {
1415
+ const pipeline = inject<ShaderPipelineContext>('shaderPipeline')
1416
+ if (!pipeline) {
1417
+ console.warn('[useShaderStage] No ShaderPipeline found in parent tree')
1418
+ return
1419
+ }
1420
+ onMounted(() => pipeline.register(stageFn, order, stage))
1421
+ onUnmounted(() => pipeline.unregister(stageFn))
1422
+ }
1423
+
1424
+
1425
+
1426
+ // layers/shader/composables/useCSSUniform.ts
1427
+ import { uniform } from 'three/tsl'
1428
+ import { Color } from 'three'
1429
+ import { inject, watch, nextTick, onUnmounted } from 'vue'
1430
+ import { resolveColour } from '../tsl/utils/colour'
1431
+
1432
+ function readColourVar(varName: string) {
1433
+ const raw = getComputedStyle(document.documentElement)
1434
+ .getPropertyValue(varName).trim()
1435
+ return new Color(resolveColour(raw || '#000000'))
1436
+ }
1437
+
1438
+ function readFloatVar(varName: string) {
1439
+ return parseFloat(
1440
+ getComputedStyle(document.documentElement).getPropertyValue(varName)
1441
+ ) || 0
1442
+ }
1443
+
1444
+ export function useCSSColourUniform(varName: string) {
1445
+ const { mode, palette, contrast } = inject<any>('shaderTheme')
1446
+ const node = uniform(readColourVar(varName))
1447
+
1448
+ const stop = watch([mode, palette, contrast], () => {
1449
+ nextTick(() => { node.value.set(readColourVar(varName)) })
1450
+ })
1451
+
1452
+ onUnmounted(stop)
1453
+ return node
1454
+ }
1455
+
1456
+ export function useCSSFloatUniform(varName: string) {
1457
+ const { mode, palette, contrast } = inject<any>('shaderTheme')
1458
+ const node = uniform(readFloatVar(varName))
1459
+
1460
+ const stop = watch([mode, palette, contrast], () => {
1461
+ nextTick(() => { node.value = readFloatVar(varName) })
1462
+ })
1463
+
1464
+ onUnmounted(stop)
1465
+ return node
1466
+ }
1467
+
1468
+ ---
1469
+
1470
+ ### 3. Colour Utility
1471
+
1472
+ // layers/shader/tsl/utils/colour.ts
1473
+ export function resolveColour(raw: string): string {
1474
+ const str = raw.trim()
1475
+ if (/^oklch\(/i.test(str)) return oklchToHex(str)
1476
+ if (/^hsla?\(/i.test(str)) return hslaToHex(str)
1477
+ return canvasResolve(str)
1478
+ }
1479
+
1480
+ function canvasResolve(str: string): string {
1481
+ const ctx = Object.assign(
1482
+ document.createElement('canvas'), { width: 1, height: 1 }
1483
+ ).getContext('2d')!
1484
+ ctx.fillStyle = str
1485
+ return ctx.fillStyle
1486
+ }
1487
+
1488
+ function oklchToHex(str: string): string {
1489
+ const [l, c, h] = parseArgs(str).map(Number)
1490
+ const hRad = (h * Math.PI) / 180
1491
+ const a = c * Math.cos(hRad)
1492
+ const b = c * Math.sin(hRad)
1493
+ const lr = l + 0.3963377774 * a + 0.2158037573 * b
1494
+ const lg = l - 0.1055613458 * a - 0.0638541728 * b
1495
+ const lb = l - 0.0894841775 * a - 1.2914855480 * b
1496
+ const r = gamma(lr**3 * 4.0767416621 + lg**3 * -3.3077115913 + lb**3 * 0.2309699292)
1497
+ const g = gamma(lr**3 * -1.2684380046 + lg**3 * 2.6097574011 + lb**3 * -0.3413193965)
1498
+ const bv = gamma(lr**3 * -0.0041960863 + lg**3 * -0.7034186147 + lb**3 * 1.7076147010)
1499
+ return toHex(r, g, bv)
1500
+ }
1501
+
1502
+ function hslaToHex(str: string): string {
1503
+ let [h, s, l] = parseArgs(str).map(Number)
1504
+ s /= 100; l /= 100
1505
+ const k = (n: number) => (n + h / 30) % 12
1506
+ const f = (n: number) => l - s * Math.min(l, 1 - l) * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1))
1507
+ return toHex(f(0), f(8), f(4))
1508
+ }
1509
+
1510
+ const parseArgs = (s: string) =>
1511
+ s.replace(/^[\w-]+\(/, '').replace(')', '').split(/[\s,\/]+/).filter(Boolean)
1512
+ const gamma = (x: number) => x <= 0.0031308 ? 12.92 * x : 1.055 * x ** (1/2.4) - 0.055
1513
+ const sat = (x: number) => Math.max(0, Math.min(1, x))
1514
+ const toHex = (r: number, g: number, b: number) =>
1515
+ '#' + [r, g, b].map(v => Math.round(sat(v) * 255).toString(16).padStart(2, '0')).join('')
1516
+
1517
+ ---
1518
+
1519
+ ### 4. TSL Utilities
1520
+
1521
+ // layers/shader/tsl/utils/screenAspectUV.ts
1522
+ import { Fn, uv, vec2, screenSize } from 'three/tsl'
1523
+
1524
+ // Aspect-corrected UV centred at origin (-0.5..0.5 on short axis)
1525
+ export const screenAspectUV = Fn(() => {
1526
+ const aspect = screenSize.x.div(screenSize.y)
1527
+ return uv().sub(0.5).mul(vec2(aspect, 1.0))
1528
+ })
1529
+
1530
+
1531
+
1532
+ // layers/shader/tsl/noise/gradientNoise.ts
1533
+ import { Fn, vec2, dot, fract, sin, floor, mix } from 'three/tsl'
1534
+
1535
+ // Inigo Quilez gradient noise -- same hash used across phobon shaders
1536
+ const hash2 = Fn(([p]: [any]) => {
1537
+ const q = vec2(
1538
+ dot(p, vec2(2127.1, 81.17)),
1539
+ dot(p, vec2(1269.5, 283.37))
1540
+ )
1541
+ return fract(sin(q).mul(43758.5453))
1542
+ })
1543
+
1544
+ export const gradientNoise = Fn(([p]: [any]) => {
1545
+ const i = floor(p)
1546
+ const f = fract(p)
1547
+ const u = f.mul(f).mul(f.oneMinus().mul(2).add(1)) // smoothstep
1548
+
1549
+ const n = mix(
1550
+ mix(
1551
+ dot(hash2(i.add(vec2(0, 0))).mul(2).sub(1), f.sub(vec2(0, 0))),
1552
+ dot(hash2(i.add(vec2(1, 0))).mul(2).sub(1), f.sub(vec2(1, 0))),
1553
+ u.x
1554
+ ),
1555
+ mix(
1556
+ dot(hash2(i.add(vec2(0, 1))).mul(2).sub(1), f.sub(vec2(0, 1))),
1557
+ dot(hash2(i.add(vec2(1, 1))).mul(2).sub(1), f.sub(vec2(1, 1))),
1558
+ u.x
1559
+ ),
1560
+ u.y
1561
+ )
1562
+ return n.mul(0.5).add(0.5) // remap to 0..1
1563
+ })
1564
+
1565
+
1566
+
1567
+ // layers/shader/tsl/sdf/shapes.ts
1568
+ import { Fn, length, abs, max, vec2 } from 'three/tsl'
1569
+
1570
+ // Signed distance to a sphere/circle at origin
1571
+ export const sdSphere = Fn(([p]: [any]) => length(p))
1572
+
1573
+ // Signed distance to a 2D box centred at origin, half-extents b
1574
+ export const sdBox2d = Fn(([p, b]: [any, any]) => {
1575
+ const d = abs(p).sub(b)
1576
+ return length(max(d, 0.0)).add(max(d.x, d.y).min(0.0))
1577
+ })
1578
+
1579
+ ---
1580
+
1581
+ ### 5. ShaderPipeline Component
1582
+
1583
+ <!-- layers/shader/components/ShaderPipeline.vue -->
1584
+ <script setup lang="ts">
1585
+ import { provide, shallowRef, watch } from 'vue'
1586
+ import * as THREE from 'three'
1587
+ import { vec4, positionLocal } from 'three/tsl'
1588
+ import type { Node } from 'three/tsl'
1589
+ import type { StageEntry, ShaderPipelineContext } from '../types'
1590
+
1591
+ const fragmentStages = shallowRef<StageEntry[]>([])
1592
+ const vertexStages = shallowRef<StageEntry[]>([])
1593
+
1594
+ // MeshBasicNodeMaterial works with WebGPU and WebGL2 via TSL
1595
+ const material = new THREE.MeshBasicNodeMaterial({ transparent: true })
1596
+
1597
+ function sortedInsert(arr: StageEntry[], entry: StageEntry): StageEntry[] {
1598
+ return [...arr, entry].sort((a, b) => a.order - b.order)
1599
+ }
1600
+
1601
+ const ctx: ShaderPipelineContext = {
1602
+ register(fn, order = 0, stage = 'fragment') {
1603
+ const entry: StageEntry = { fn, order, stage }
1604
+ if (stage === 'vertex') {
1605
+ vertexStages.value = sortedInsert(vertexStages.value, entry)
1606
+ } else {
1607
+ fragmentStages.value = sortedInsert(fragmentStages.value, entry)
1608
+ }
1609
+ },
1610
+ unregister(fn) {
1611
+ fragmentStages.value = fragmentStages.value.filter(s => s.fn !== fn)
1612
+ vertexStages.value = vertexStages.value.filter(s => s.fn !== fn)
1613
+ }
1614
+ }
1615
+
1616
+ provide('shaderPipeline', ctx)
1617
+
1618
+ // Rebuild colorNode whenever fragment stages change
1619
+ watch(fragmentStages, (stages) => {
1620
+ material.colorNode = stages.reduce<Node>(
1621
+ (node, { fn }) => fn(node),
1622
+ vec4(0, 0, 0, 1)
1623
+ )
1624
+ material.needsUpdate = true
1625
+ }, { deep: true })
1626
+
1627
+ // Rebuild positionNode whenever vertex stages change
1628
+ watch(vertexStages, (stages) => {
1629
+ if (stages.length === 0) {
1630
+ material.positionNode = null
1631
+ return
1632
+ }
1633
+ material.positionNode = stages.reduce<Node>(
1634
+ (node, { fn }) => fn(node),
1635
+ positionLocal
1636
+ )
1637
+ material.needsUpdate = true
1638
+ }, { deep: true })
1639
+
1640
+ defineExpose({ material })
1641
+ </script>
1642
+
1643
+ <template>
1644
+ <primitive :object="material" />
1645
+ <slot />
1646
+ </template>
1647
+
1648
+ ---
1649
+
1650
+ ### 6. Generators
1651
+
1652
+ #### TSL functions
1653
+
1654
+ // layers/shader/tsl/generators/solidColour.ts
1655
+ import { Fn, vec4 } from 'three/tsl'
1656
+
1657
+ export const solidColour = Fn(([colour]: [any]) => vec4(colour, 1.0))
1658
+
1659
+
1660
+
1661
+ // layers/shader/tsl/generators/linearGradient.ts
1662
+ import { Fn, mix, uv, vec4, smoothstep } from 'three/tsl'
1663
+
1664
+ export const linearGradient = Fn(([colourA, colourB, axis, smooth]: [any, any, any, any]) => {
1665
+ const t = axis // pass uv().y or uv().x from the component
1666
+ const factor = smooth.equal(1).select(t.smoothstep(0, 1), t)
1667
+ return mix(vec4(colourA, 1), vec4(colourB, 1), factor)
1668
+ })
1669
+
1670
+
1671
+
1672
+ // layers/shader/tsl/generators/cosinePalette.ts
1673
+ import { Fn, add, mul, cos, vec3 } from 'three/tsl'
1674
+
1675
+ // IQ cosine palette: a + b * cos(TAU * (c*t + d))
1676
+ // All params are vec3 nodes; t is a float node
1677
+ export const cosinePalette = Fn(([t, a, b, c, d]: [any, any, any, any, any]) => {
1678
+ const TAU = 6.28318530718
1679
+ return add(a, mul(b, cos(mul(TAU, add(mul(c, t), d)))))
1680
+ })
1681
+
1682
+ #### Vue components
1683
+
1684
+ <!-- layers/shader/components/generators/SolidColour.vue -->
1685
+ <script setup lang="ts">
1686
+ import { uniform, vec3, vec4 } from 'three/tsl'
1687
+ import { Color } from 'three'
1688
+ import { watch } from 'vue'
1689
+ import { useShaderStage } from '../../composables/useShaderStage'
1690
+ import { useCSSColourUniform } from '../../composables/useCSSUniform'
1691
+ import { resolveColour } from '../../tsl/utils/colour'
1692
+
1693
+ const props = defineProps<{
1694
+ color?: string
1695
+ cssColor?: string
1696
+ order?: number
1697
+ }>()
1698
+
1699
+ const colorNode = props.cssColor
1700
+ ? useCSSColourUniform(props.cssColor)
1701
+ : uniform(new Color(resolveColour(props.color ?? '#000000')))
1702
+
1703
+ if (!props.cssColor && props.color) {
1704
+ watch(() => props.color, v => {
1705
+ if (v) (colorNode as any).value.set(resolveColour(v))
1706
+ })
1707
+ }
1708
+
1709
+ useShaderStage(
1710
+ (_prev) => vec4(colorNode, 1.0),
1711
+ props.order ?? 0
1712
+ )
1713
+ </script>
1714
+
1715
+ <template />
1716
+
1717
+
1718
+
1719
+ <!-- layers/shader/components/generators/CosinePalette.vue -->
1720
+ <script setup lang="ts">
1721
+ import { uniform, vec3, vec4, uv, time, length } from 'three/tsl'
1722
+ import { Color } from 'three'
1723
+ import { watch } from 'vue'
1724
+ import { useShaderStage } from '../../composables/useShaderStage'
1725
+ import { useCSSColourUniform } from '../../composables/useCSSUniform'
1726
+ import { cosinePalette as cosinePaletteFn } from '../../tsl/generators/cosinePalette'
1727
+
1728
+ type Vec3Tuple = [number, number, number]
1729
+
1730
+ const props = defineProps<{
1731
+ // Palette vectors -- static or CSS var
1732
+ a?: Vec3Tuple; cssA?: string
1733
+ b?: Vec3Tuple; cssB?: string
1734
+ c?: Vec3Tuple; cssC?: string
1735
+ d?: Vec3Tuple; cssD?: string
1736
+ // Scalar input source
1737
+ scalarSource?: 'uv.y' | 'uv.x' | 'radial' | 'prev.r'
1738
+ offset?: number
1739
+ timeScale?: number
1740
+ order?: number
1741
+ }>()
1742
+
1743
+ const toNode = (css: string | undefined, val: Vec3Tuple = [0.5, 0.5, 0.5]) =>
1744
+ css
1745
+ ? useCSSColourUniform(css)
1746
+ : uniform(new Color().setRGB(...val))
1747
+
1748
+ const aNode = toNode(props.cssA, props.a)
1749
+ const bNode = toNode(props.cssB, props.b)
1750
+ const cNode = toNode(props.cssC, props.c)
1751
+ const dNode = toNode(props.cssD, props.d)
1752
+
1753
+ const offsetU = uniform(props.offset ?? 0)
1754
+ const timeScaleU = uniform(props.timeScale ?? 0)
1755
+
1756
+ watch(() => props.offset, v => { offsetU.value = v ?? 0 })
1757
+ watch(() => props.timeScale, v => { timeScaleU.value = v ?? 0 })
1758
+
1759
+ useShaderStage((_prev) => {
1760
+ // Derive t from chosen scalar source
1761
+ let t: any
1762
+ switch (props.scalarSource ?? 'uv.y') {
1763
+ case 'uv.x': t = uv().x.add(offsetU).sub(time.mul(timeScaleU)); break
1764
+ case 'radial': t = length(uv().sub(0.5)).add(offsetU); break
1765
+ case 'prev.r': t = _prev.r.add(offsetU); break
1766
+ default: t = uv().y.add(offsetU).sub(time.mul(timeScaleU))
1767
+ }
1768
+
1769
+ const col = cosinePaletteFn(t, aNode, bNode, cNode, dNode)
1770
+ return vec4(col, 1.0)
1771
+ }, props.order ?? 0)
1772
+ </script>
1773
+
1774
+ <template />
1775
+
1776
+ ---
1777
+
1778
+ ### 7. UV Transformers
1779
+
1780
+ #### TSL functions
1781
+
1782
+ // layers/shader/tsl/uvTransformers/uvAspectCorrect.ts
1783
+ import { Fn, uv, vec2, screenSize } from 'three/tsl'
1784
+
1785
+ export const uvAspectCorrect = Fn(() => {
1786
+ const aspect = screenSize.x.div(screenSize.y)
1787
+ return uv().sub(0.5).mul(vec2(aspect, 1.0))
1788
+ })
1789
+
1790
+
1791
+
1792
+ // layers/shader/tsl/uvTransformers/uvNoiseRotate.ts
1793
+ import { Fn, vec2, cos, sin, time } from 'three/tsl'
1794
+ import { gradientNoise } from '../noise/gradientNoise'
1795
+
1796
+ // Rotate UV by a noise-derived angle -- the Grainient core
1797
+ export const uvNoiseRotate = Fn(([uvIn, noiseScale, rotationAmount, speed]: [any, any, any, any]) => {
1798
+ const t = time.mul(speed)
1799
+ const deg = gradientNoise(vec2(t.mul(0.1), uvIn.x.mul(uvIn.y)).mul(noiseScale))
1800
+ const angle = deg.sub(0.5).mul(rotationAmount).add(180).mul(0.017453293) // to radians
1801
+
1802
+ const aspect = vec2(1.0) // already aspect-corrected upstream
1803
+ const c = cos(angle)
1804
+ const s = sin(angle)
1805
+
1806
+ const centred = uvIn
1807
+ const rotated = vec2(
1808
+ c.mul(centred.x).sub(s.mul(centred.y)),
1809
+ s.mul(centred.x).add(c.mul(centred.y))
1810
+ )
1811
+ // Restore aspect
1812
+ return rotated
1813
+ })
1814
+
1815
+
1816
+
1817
+ // layers/shader/tsl/uvTransformers/uvSineWarpXY.ts
1818
+ import { Fn, vec2, sin, time } from 'three/tsl'
1819
+
1820
+ // Asymmetric sine warp -- the Grainient/dawn family pattern
1821
+ export const uvSineWarpXY = Fn(([uvIn, frequency, amplitude, speed]: [any, any, any, any]) => {
1822
+ const t = time.mul(speed)
1823
+ const wx = sin(uvIn.y.mul(frequency).add(t)).div(amplitude)
1824
+ const wy = sin(uvIn.x.mul(frequency.mul(1.5)).add(t)).div(amplitude.mul(0.5))
1825
+ return uvIn.add(vec2(wx, wy))
1826
+ })
1827
+
1828
+ #### Vue component pattern
1829
+
1830
+ <!-- layers/shader/components/uvTransformers/UVAspectCorrect.vue -->
1831
+ <script setup lang="ts">
1832
+ import { uv, vec2, screenSize } from 'three/tsl'
1833
+ import { useShaderStage } from '../../composables/useShaderStage'
1834
+
1835
+ const props = defineProps<{ order?: number }>()
1836
+
1837
+ useShaderStage(
1838
+ (_prev) => {
1839
+ const aspect = screenSize.x.div(screenSize.y)
1840
+ return uv().sub(0.5).mul(vec2(aspect, 1.0))
1841
+ },
1842
+ props.order ?? 0
1843
+ )
1844
+ </script>
1845
+
1846
+ <template />
1847
+
1848
+
1849
+
1850
+ <!-- layers/shader/components/uvTransformers/UVSineWarpXY.vue -->
1851
+ <script setup lang="ts">
1852
+ import { uniform } from 'three/tsl'
1853
+ import { watch } from 'vue'
1854
+ import { useShaderStage } from '../../composables/useShaderStage'
1855
+ import { uvSineWarpXY } from '../../tsl/uvTransformers/uvSineWarpXY'
1856
+
1857
+ const props = defineProps<{
1858
+ frequency?: number
1859
+ amplitude?: number
1860
+ speed?: number
1861
+ order?: number
1862
+ }>()
1863
+
1864
+ const freqU = uniform(props.frequency ?? 5.0)
1865
+ const ampU = uniform(props.amplitude ?? 30.0)
1866
+ const spdU = uniform(props.speed ?? 2.0)
1867
+
1868
+ watch(() => props.frequency, v => { freqU.value = v ?? 5.0 })
1869
+ watch(() => props.amplitude, v => { ampU.value = v ?? 30.0 })
1870
+ watch(() => props.speed, v => { spdU.value = v ?? 2.0 })
1871
+
1872
+ useShaderStage(
1873
+ (prev) => uvSineWarpXY(prev, freqU, ampU, spdU),
1874
+ props.order ?? 0
1875
+ )
1876
+ </script>
1877
+
1878
+ <template />
1879
+
1880
+ ---
1881
+
1882
+ ### 8. Colour Ops
1883
+
1884
+ // layers/shader/tsl/colourOps/brightnessContrast.ts
1885
+ import { Fn, clamp } from 'three/tsl'
1886
+
1887
+ export const brightnessContrast = Fn(([col, brightness, contrast]: [any, any, any]) =>
1888
+ clamp(col.add(brightness).sub(0.5).mul(contrast).add(0.5), 0, 1)
1889
+ )
1890
+
1891
+
1892
+
1893
+ // layers/shader/tsl/colourOps/tanhTonemap.ts
1894
+ import { Fn, tanh } from 'three/tsl'
1895
+
1896
+ // Soft sigmoid tone compression -- gentler than ACES, preserves saturation
1897
+ export const tanhTonemap = Fn(([col, exposure]: [any, any]) =>
1898
+ tanh(col.mul(exposure))
1899
+ )
1900
+
1901
+
1902
+
1903
+ <!-- layers/shader/components/colourOps/TanhTonemap.vue -->
1904
+ <script setup lang="ts">
1905
+ import { uniform } from 'three/tsl'
1906
+ import { watch } from 'vue'
1907
+ import { vec4 } from 'three/tsl'
1908
+ import { useShaderStage } from '../../composables/useShaderStage'
1909
+ import { tanhTonemap } from '../../tsl/colourOps/tanhTonemap'
1910
+
1911
+ const props = defineProps<{ exposure?: number; order?: number }>()
1912
+
1913
+ const expU = uniform(props.exposure ?? 1.0)
1914
+ watch(() => props.exposure, v => { expU.value = v ?? 1.0 })
1915
+
1916
+ useShaderStage(
1917
+ (prev) => vec4(tanhTonemap(prev.rgb, expU), prev.a),
1918
+ props.order ?? 0
1919
+ )
1920
+ </script>
1921
+
1922
+ <template />
1923
+
1924
+ ---
1925
+
1926
+ ### 9. Overlays
1927
+
1928
+ // layers/shader/tsl/overlays/grain.ts
1929
+ import { Fn, fract, sin, dot, uv, time, vec2, clamp } from 'three/tsl'
1930
+
1931
+ export const grain = Fn(([col, intensity, animated]: [any, any, any]) => {
1932
+ const seed = animated.equal(1).select(uv().add(time.mul(0.001)), uv())
1933
+ const noise = fract(sin(dot(seed, vec2(12.9898, 78.233))).mul(43758.5453))
1934
+ return clamp(col.add(noise.sub(0.5).mul(intensity)), 0, 1)
1935
+ })
1936
+
1937
+
1938
+
1939
+ // layers/shader/tsl/overlays/vignette.ts
1940
+ import { Fn, uv, vec2, length, smoothstep, mix } from 'three/tsl'
1941
+
1942
+ export const vignette = Fn(([col, strength, softness]: [any, any, any]) => {
1943
+ const dist = length(uv().sub(0.5)).mul(1.4142) // normalise: corner = 1
1944
+ const vig = dist.smoothstep(strength.oneMinus().mul(softness), 1.0).mul(strength)
1945
+ return mix(col, col.mul(0), vig)
1946
+ })
1947
+
1948
+
1949
+
1950
+ <!-- layers/shader/components/overlays/Grain.vue -->
1951
+ <script setup lang="ts">
1952
+ import { uniform, vec4 } from 'three/tsl'
1953
+ import { watch } from 'vue'
1954
+ import { useShaderStage } from '../../composables/useShaderStage'
1955
+ import { grain as grainFn } from '../../tsl/overlays/grain'
1956
+
1957
+ const props = defineProps<{
1958
+ intensity?: number
1959
+ animated?: boolean
1960
+ order?: number
1961
+ }>()
1962
+
1963
+ const intU = uniform(props.intensity ?? 0.04)
1964
+ const animU = uniform(props.animated ? 1 : 0)
1965
+
1966
+ watch(() => props.intensity, v => { intU.value = v ?? 0.04 })
1967
+ watch(() => props.animated, v => { animU.value = v ? 1 : 0 })
1968
+
1969
+ useShaderStage(
1970
+ (prev) => vec4(grainFn(prev.rgb, intU, animU), prev.a),
1971
+ props.order ?? 0
1972
+ )
1973
+ </script>
1974
+
1975
+ <template />
1976
+
1977
+ ---
1978
+
1979
+ ### 10. Barrel Export
1980
+
1981
+ // layers/shader/index.ts
1982
+ // Core
1983
+ export { default as ShaderPipeline } from './components/ShaderPipeline.vue'
1984
+ export { useShaderStage } from './composables/useShaderStage'
1985
+ export { useThemeProvider } from './composables/useThemeProvider'
1986
+ export { useCSSColourUniform, useCSSFloatUniform } from './composables/useCSSUniform'
1987
+
1988
+ // Generators
1989
+ export { default as SolidColour } from './components/generators/SolidColour.vue'
1990
+ export { default as CosinePalette } from './components/generators/CosinePalette.vue'
1991
+ export { default as LinearGradient } from './components/generators/LinearGradient.vue'
1992
+
1993
+ // UV Transformers
1994
+ export { default as UVAspectCorrect } from './components/uvTransformers/UVAspectCorrect.vue'
1995
+ export { default as UVSineWarpXY } from './components/uvTransformers/UVSineWarpXY.vue'
1996
+
1997
+ // Colour Ops
1998
+ export { default as TanhTonemap } from './components/colourOps/TanhTonemap.vue'
1999
+
2000
+ // Overlays
2001
+ export { default as Grain } from './components/overlays/Grain.vue'
2002
+ export { default as Vignette } from './components/overlays/Vignette.vue'
2003
+
2004
+ // TSL primitives (for building custom blocks)
2005
+ export { cosinePalette } from './tsl/generators/cosinePalette'
2006
+ export { gradientNoise } from './tsl/noise/gradientNoise'
2007
+ export { screenAspectUV } from './tsl/utils/screenAspectUV'
2008
+ export { sdSphere, sdBox2d } from './tsl/sdf/shapes'
2009
+ export { resolveColour } from './tsl/utils/colour'
2010
+
2011
+ ---
2012
+
2013
+ ### 11. Usage Example -- Dawn2 Recreation
2014
+
2015
+ <!-- A complete working shader using the block system -->
2016
+ <script setup lang="ts">
2017
+ import { TresCanvas } from '@tresjs/core'
2018
+ import {
2019
+ ShaderPipeline,
2020
+ CosinePalette,
2021
+ UVAspectCorrect,
2022
+ Grain,
2023
+ useThemeProvider
2024
+ } from '@/layers/shader'
2025
+
2026
+ useThemeProvider()
2027
+ </script>
2028
+
2029
+ <template>
2030
+ <TresCanvas>
2031
+ <TresMesh>
2032
+ <TresPlaneGeometry :args="[2, 2]" />
2033
+ <ShaderPipeline>
2034
+ <UVAspectCorrect :order="0" />
2035
+ <CosinePalette
2036
+ :a="[0.5, 0.5, 0.5]"
2037
+ :b="[0.5, 0.5, 0.5]"
2038
+ :c="[1.0, 0.7, 0.4]"
2039
+ :d="[0.0, 0.15, 0.2]"
2040
+ scalar-source="uv.y"
2041
+ :time-scale="0.1"
2042
+ :order="1"
2043
+ />
2044
+ <Grain :intensity="0.2" :animated="true" :order="2" />
2045
+ </ShaderPipeline>
2046
+ </TresMesh>
2047
+ </TresCanvas>
2048
+ </template>
2049
+
2050
+ ---
2051
+
2052
+ ### 12. Building New Blocks -- Rules
2053
+
2054
+ Every block follows this exact pattern:
2055
+
2056
+ **TSL function** (`tsl/<category>/<name>.ts`)
2057
+
2058
+ - Pure TSL node function, no Vue, no Three scene knowledge
2059
+ - Takes node params, returns a node
2060
+ - Exported as a named `Fn`
2061
+
2062
+ **Vue component** (`components/<category>/<Name>.vue`)
2063
+
2064
+ - `<template />` -- always empty
2065
+ - Props → `uniform()` nodes with `watch` for reactivity
2066
+ - CSS var props → `useCSSColourUniform()` / `useCSSFloatUniform()`
2067
+ - Calls `useShaderStage(stageFn, order, stage)` in `<script setup>`
2068
+ - `stageFn` signature: `(prev: Node) => Node`
2069
+ - Generators ignore `prev` and return a new colour node
2070
+ - UV Transformers return a new UV node (the whole pipeline is UV-shaped at that point)
2071
+ - Colour Ops and Overlays receive and return a `vec4` colour node