@basementstudio/shader-lab 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/.biome/plugins/README.md +21 -0
  2. package/.biome/plugins/no-anchor-element.grit +12 -0
  3. package/.biome/plugins/no-relative-parent-imports.grit +10 -0
  4. package/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  5. package/.changeset/README.md +17 -0
  6. package/.changeset/config.json +11 -0
  7. package/.editorconfig +40 -0
  8. package/.env.example +81 -0
  9. package/.gitattributes +19 -0
  10. package/.github/workflows/canary.yml +80 -0
  11. package/.github/workflows/ci.yml +37 -0
  12. package/.github/workflows/release.yml +56 -0
  13. package/.tldrignore +84 -0
  14. package/.vscode/extensions.json +20 -0
  15. package/.vscode/settings.json +105 -0
  16. package/README.md +119 -0
  17. package/biome.json +249 -0
  18. package/bun.lock +1224 -0
  19. package/next.config.ts +131 -0
  20. package/package.json +73 -0
  21. package/packages/shader-lab-react/CHANGELOG.md +9 -0
  22. package/packages/shader-lab-react/README.md +119 -0
  23. package/packages/shader-lab-react/assets/patterns/bars/1.svg +3 -0
  24. package/packages/shader-lab-react/assets/patterns/bars/2.svg +3 -0
  25. package/packages/shader-lab-react/assets/patterns/bars/3.svg +3 -0
  26. package/packages/shader-lab-react/assets/patterns/bars/4.svg +3 -0
  27. package/packages/shader-lab-react/assets/patterns/bars/5.svg +3 -0
  28. package/packages/shader-lab-react/assets/patterns/bars/6.svg +3 -0
  29. package/packages/shader-lab-react/assets/patterns/candles/1.svg +3 -0
  30. package/packages/shader-lab-react/assets/patterns/candles/2.svg +3 -0
  31. package/packages/shader-lab-react/assets/patterns/candles/3.svg +3 -0
  32. package/packages/shader-lab-react/assets/patterns/candles/4.svg +3 -0
  33. package/packages/shader-lab-react/assets/patterns/shapes/1.svg +3 -0
  34. package/packages/shader-lab-react/assets/patterns/shapes/2.svg +3 -0
  35. package/packages/shader-lab-react/assets/patterns/shapes/3.svg +3 -0
  36. package/packages/shader-lab-react/assets/patterns/shapes/4.svg +4 -0
  37. package/packages/shader-lab-react/assets/patterns/shapes/5.svg +3 -0
  38. package/packages/shader-lab-react/assets/patterns/shapes/6.svg +4 -0
  39. package/packages/shader-lab-react/assets/textures/blue-noise.png +0 -0
  40. package/packages/shader-lab-react/package.json +36 -0
  41. package/packages/shader-lab-react/scripts/fix-esm-specifiers.mjs +57 -0
  42. package/packages/shader-lab-react/scripts/prepare-dist.mjs +4 -0
  43. package/packages/shader-lab-react/src/ambient/three-tsl.d.ts +146 -0
  44. package/packages/shader-lab-react/src/ambient/three-webgpu.d.ts +51 -0
  45. package/packages/shader-lab-react/src/easings.ts +4 -0
  46. package/packages/shader-lab-react/src/index.ts +35 -0
  47. package/packages/shader-lab-react/src/lib/editor/custom-shader/shared.ts +2 -0
  48. package/packages/shader-lab-react/src/renderer/ascii-atlas.ts +83 -0
  49. package/packages/shader-lab-react/src/renderer/ascii-pass.ts +416 -0
  50. package/packages/shader-lab-react/src/renderer/asset-url.ts +3 -0
  51. package/packages/shader-lab-react/src/renderer/blend-modes.ts +229 -0
  52. package/packages/shader-lab-react/src/renderer/contracts.ts +54 -0
  53. package/packages/shader-lab-react/src/renderer/create-webgpu-renderer.ts +48 -0
  54. package/packages/shader-lab-react/src/renderer/crt-pass.ts +1040 -0
  55. package/packages/shader-lab-react/src/renderer/custom-shader-pass.ts +108 -0
  56. package/packages/shader-lab-react/src/renderer/custom-shader-runtime.ts +309 -0
  57. package/packages/shader-lab-react/src/renderer/dither-textures.ts +99 -0
  58. package/packages/shader-lab-react/src/renderer/dithering-pass.ts +322 -0
  59. package/packages/shader-lab-react/src/renderer/gradient-pass.ts +521 -0
  60. package/packages/shader-lab-react/src/renderer/halftone-pass.ts +932 -0
  61. package/packages/shader-lab-react/src/renderer/ink-pass.ts +802 -0
  62. package/packages/shader-lab-react/src/renderer/live-pass.ts +194 -0
  63. package/packages/shader-lab-react/src/renderer/media-pass.ts +187 -0
  64. package/packages/shader-lab-react/src/renderer/media-texture.ts +66 -0
  65. package/packages/shader-lab-react/src/renderer/particle-grid-pass.ts +389 -0
  66. package/packages/shader-lab-react/src/renderer/pass-node.ts +209 -0
  67. package/packages/shader-lab-react/src/renderer/pattern-atlas.ts +133 -0
  68. package/packages/shader-lab-react/src/renderer/pattern-pass.ts +552 -0
  69. package/packages/shader-lab-react/src/renderer/pipeline-manager.ts +369 -0
  70. package/packages/shader-lab-react/src/renderer/pixel-sorting-pass.ts +277 -0
  71. package/packages/shader-lab-react/src/renderer/shaders/tsl/color/tonemapping.ts +87 -0
  72. package/packages/shader-lab-react/src/renderer/shaders/tsl/cosine-palette.ts +9 -0
  73. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/common.ts +31 -0
  74. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +36 -0
  75. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +36 -0
  76. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/fbm.ts +13 -0
  77. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +96 -0
  78. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/ridge-noise.ts +24 -0
  79. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +79 -0
  80. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +89 -0
  81. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/turbulence.ts +56 -0
  82. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/value-noise-3d.ts +32 -0
  83. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +60 -0
  84. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +15 -0
  85. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom.ts +11 -0
  86. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +24 -0
  87. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +9 -0
  88. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +11 -0
  89. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/atan2.ts +9 -0
  90. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-conj.ts +9 -0
  91. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-cos.ts +10 -0
  92. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-div.ts +11 -0
  93. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-log.ts +7 -0
  94. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mobius.ts +12 -0
  95. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mul.ts +9 -0
  96. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-pow.ts +16 -0
  97. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sin.ts +10 -0
  98. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sqrt.ts +18 -0
  99. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-tan.ts +12 -0
  100. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-to-polar.ts +10 -0
  101. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/hyperbolic.ts +20 -0
  102. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/index.ts +48 -0
  103. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/rotate.ts +15 -0
  104. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +15 -0
  105. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-box-2d.ts +6 -0
  106. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-diamond.ts +6 -0
  107. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-rhombus.ts +27 -0
  108. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-sphere.ts +6 -0
  109. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smax.ts +7 -0
  110. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smin.ts +7 -0
  111. package/packages/shader-lab-react/src/renderer/text-pass.ts +176 -0
  112. package/packages/shader-lab-react/src/runtime-clock.ts +42 -0
  113. package/packages/shader-lab-react/src/runtime-frame.ts +29 -0
  114. package/packages/shader-lab-react/src/shader-lab-composition.tsx +163 -0
  115. package/packages/shader-lab-react/src/timeline.ts +283 -0
  116. package/packages/shader-lab-react/src/types/editor.ts +5 -0
  117. package/packages/shader-lab-react/src/types.ts +141 -0
  118. package/packages/shader-lab-react/tsconfig.build.json +8 -0
  119. package/packages/shader-lab-react/tsconfig.json +21 -0
  120. package/postcss.config.mjs +5 -0
  121. package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf-atlas.png +0 -0
  122. package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf.json +1412 -0
  123. package/public/assets/patterns/bars/1.svg +3 -0
  124. package/public/assets/patterns/bars/2.svg +3 -0
  125. package/public/assets/patterns/bars/3.svg +3 -0
  126. package/public/assets/patterns/bars/4.svg +3 -0
  127. package/public/assets/patterns/bars/5.svg +3 -0
  128. package/public/assets/patterns/bars/6.svg +3 -0
  129. package/public/assets/patterns/candles/1.svg +3 -0
  130. package/public/assets/patterns/candles/2.svg +3 -0
  131. package/public/assets/patterns/candles/3.svg +3 -0
  132. package/public/assets/patterns/candles/4.svg +3 -0
  133. package/public/assets/patterns/shapes/1.svg +3 -0
  134. package/public/assets/patterns/shapes/2.svg +3 -0
  135. package/public/assets/patterns/shapes/3.svg +3 -0
  136. package/public/assets/patterns/shapes/4.svg +4 -0
  137. package/public/assets/patterns/shapes/5.svg +3 -0
  138. package/public/assets/patterns/shapes/6.svg +4 -0
  139. package/public/fonts/geist/Geist-Mono.woff2 +0 -0
  140. package/public/textures/blue-noise.png +0 -0
  141. package/public/textures/crt-mask.png +0 -0
  142. package/src/app/design/page.tsx +398 -0
  143. package/src/app/favicon.ico +0 -0
  144. package/src/app/globals.css +280 -0
  145. package/src/app/layout.tsx +89 -0
  146. package/src/app/page.tsx +20 -0
  147. package/src/app/robots.ts +13 -0
  148. package/src/app/sitemap.ts +13 -0
  149. package/src/components/editor/editor-canvas-viewport.tsx +116 -0
  150. package/src/components/editor/editor-export-dialog.tsx +1177 -0
  151. package/src/components/editor/editor-timeline-overlay.tsx +983 -0
  152. package/src/components/editor/editor-topbar.tsx +287 -0
  153. package/src/components/editor/layer-sidebar.tsx +738 -0
  154. package/src/components/editor/properties-sidebar-content.tsx +574 -0
  155. package/src/components/editor/properties-sidebar-fields.tsx +389 -0
  156. package/src/components/editor/properties-sidebar-utils.ts +178 -0
  157. package/src/components/editor/properties-sidebar.tsx +421 -0
  158. package/src/components/ui/button/index.tsx +57 -0
  159. package/src/components/ui/color-picker/index.tsx +358 -0
  160. package/src/components/ui/glass-panel/index.tsx +45 -0
  161. package/src/components/ui/icon-button/index.tsx +46 -0
  162. package/src/components/ui/select/index.tsx +136 -0
  163. package/src/components/ui/slider/index.tsx +192 -0
  164. package/src/components/ui/toggle/index.tsx +34 -0
  165. package/src/components/ui/typography/index.tsx +61 -0
  166. package/src/components/ui/xy-pad/index.tsx +160 -0
  167. package/src/features/editor/components/editor-export-dialog.module.css +271 -0
  168. package/src/hooks/use-editor-renderer.ts +182 -0
  169. package/src/lib/app.ts +6 -0
  170. package/src/lib/cn.ts +7 -0
  171. package/src/lib/easings.ts +240 -0
  172. package/src/lib/editor/config/layer-registry.ts +2434 -0
  173. package/src/lib/editor/custom-shader/shared.ts +28 -0
  174. package/src/lib/editor/export.ts +420 -0
  175. package/src/lib/editor/history.ts +71 -0
  176. package/src/lib/editor/layers.ts +76 -0
  177. package/src/lib/editor/parameter-schema.ts +75 -0
  178. package/src/lib/editor/project-file.ts +145 -0
  179. package/src/lib/editor/shader-export-snippet.ts +37 -0
  180. package/src/lib/editor/shader-export.ts +315 -0
  181. package/src/lib/editor/timeline/evaluate.ts +252 -0
  182. package/src/lib/editor/view-transform.ts +58 -0
  183. package/src/lib/fonts.ts +28 -0
  184. package/src/renderer/ascii-atlas.ts +83 -0
  185. package/src/renderer/ascii-pass.ts +416 -0
  186. package/src/renderer/blend-modes.ts +229 -0
  187. package/src/renderer/contracts.ts +161 -0
  188. package/src/renderer/create-webgpu-renderer.ts +48 -0
  189. package/src/renderer/crt-pass.ts +1040 -0
  190. package/src/renderer/custom-shader-pass.ts +117 -0
  191. package/src/renderer/custom-shader-runtime.ts +309 -0
  192. package/src/renderer/dither-textures.ts +99 -0
  193. package/src/renderer/dithering-pass.ts +322 -0
  194. package/src/renderer/gradient-pass.ts +520 -0
  195. package/src/renderer/halftone-pass.ts +932 -0
  196. package/src/renderer/ink-pass.ts +683 -0
  197. package/src/renderer/live-pass.ts +194 -0
  198. package/src/renderer/media-pass.ts +187 -0
  199. package/src/renderer/media-texture.ts +66 -0
  200. package/src/renderer/particle-grid-pass.ts +389 -0
  201. package/src/renderer/pass-node-factory.ts +33 -0
  202. package/src/renderer/pass-node.ts +209 -0
  203. package/src/renderer/pattern-atlas.ts +97 -0
  204. package/src/renderer/pattern-pass.ts +552 -0
  205. package/src/renderer/pipeline-manager.ts +343 -0
  206. package/src/renderer/pixel-sorting-pass.ts +277 -0
  207. package/src/renderer/project-clock.ts +57 -0
  208. package/src/renderer/shaders/tsl/color/tonemapping.ts +86 -0
  209. package/src/renderer/shaders/tsl/cosine-palette.ts +8 -0
  210. package/src/renderer/shaders/tsl/noise/common.ts +30 -0
  211. package/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +35 -0
  212. package/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +35 -0
  213. package/src/renderer/shaders/tsl/noise/fbm.ts +12 -0
  214. package/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +97 -0
  215. package/src/renderer/shaders/tsl/noise/ridge-noise.ts +23 -0
  216. package/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +78 -0
  217. package/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +88 -0
  218. package/src/renderer/shaders/tsl/noise/turbulence.ts +55 -0
  219. package/src/renderer/shaders/tsl/noise/value-noise-3d.ts +31 -0
  220. package/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +59 -0
  221. package/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +14 -0
  222. package/src/renderer/shaders/tsl/patterns/bloom.ts +10 -0
  223. package/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +23 -0
  224. package/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +8 -0
  225. package/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +10 -0
  226. package/src/renderer/shaders/tsl/utils/atan2.ts +8 -0
  227. package/src/renderer/shaders/tsl/utils/complex-conj.ts +8 -0
  228. package/src/renderer/shaders/tsl/utils/complex-cos.ts +9 -0
  229. package/src/renderer/shaders/tsl/utils/complex-div.ts +10 -0
  230. package/src/renderer/shaders/tsl/utils/complex-log.ts +6 -0
  231. package/src/renderer/shaders/tsl/utils/complex-mobius.ts +11 -0
  232. package/src/renderer/shaders/tsl/utils/complex-mul.ts +8 -0
  233. package/src/renderer/shaders/tsl/utils/complex-pow.ts +15 -0
  234. package/src/renderer/shaders/tsl/utils/complex-sin.ts +9 -0
  235. package/src/renderer/shaders/tsl/utils/complex-sqrt.ts +17 -0
  236. package/src/renderer/shaders/tsl/utils/complex-tan.ts +11 -0
  237. package/src/renderer/shaders/tsl/utils/complex-to-polar.ts +9 -0
  238. package/src/renderer/shaders/tsl/utils/hyperbolic.ts +19 -0
  239. package/src/renderer/shaders/tsl/utils/index.ts +47 -0
  240. package/src/renderer/shaders/tsl/utils/rotate.ts +14 -0
  241. package/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +14 -0
  242. package/src/renderer/shaders/tsl/utils/sd-box-2d.ts +5 -0
  243. package/src/renderer/shaders/tsl/utils/sd-diamond.ts +5 -0
  244. package/src/renderer/shaders/tsl/utils/sd-rhombus.ts +26 -0
  245. package/src/renderer/shaders/tsl/utils/sd-sphere.ts +5 -0
  246. package/src/renderer/shaders/tsl/utils/smax.ts +7 -0
  247. package/src/renderer/shaders/tsl/utils/smin.ts +6 -0
  248. package/src/renderer/text-pass.ts +176 -0
  249. package/src/store/asset-store.ts +193 -0
  250. package/src/store/editor-store.ts +223 -0
  251. package/src/store/history-store.ts +172 -0
  252. package/src/store/index.ts +31 -0
  253. package/src/store/layer-store.ts +675 -0
  254. package/src/store/timeline-store.ts +572 -0
  255. package/src/types/assets.d.ts +6 -0
  256. package/src/types/css.d.ts +21 -0
  257. package/src/types/editor.ts +357 -0
  258. package/src/types/react.d.ts +15 -0
  259. package/src/types/three-tsl.d.ts +146 -0
  260. package/src/types/three-webgpu.d.ts +51 -0
  261. package/tsconfig.json +49 -0
@@ -0,0 +1,389 @@
1
+ import * as THREE from "three/webgpu"
2
+ import { bloom } from "three/examples/jsm/tsl/display/BloomNode.js"
3
+ import {
4
+ attribute,
5
+ clamp,
6
+ float,
7
+ positionLocal,
8
+ smoothstep,
9
+ texture as tslTexture,
10
+ type TSLNode,
11
+ uniform,
12
+ uv,
13
+ vec2,
14
+ vec3,
15
+ vec4,
16
+ } from "three/tsl"
17
+ import { PassNode } from "./pass-node"
18
+ import { simplexNoise3d } from "./shaders/tsl/noise/simplex-noise-3d"
19
+ import type { LayerParameterValues } from "../types/editor"
20
+
21
+ type Node = TSLNode
22
+
23
+ function clamp01(value: number): number {
24
+ return Math.max(0, Math.min(1, value))
25
+ }
26
+
27
+ export class ParticleGridPass extends PassNode {
28
+ private readonly perspScene: THREE.Scene
29
+ private readonly perspCamera: THREE.PerspectiveCamera
30
+ private readonly internalRT: THREE.WebGLRenderTarget
31
+ private readonly blitInputNode: Node
32
+
33
+ private inputSamplerNode: Node | null = null
34
+ private readonly displacementUniform: Node
35
+ private readonly pointSizeUniform: Node
36
+ private readonly timeUniform: Node
37
+ private readonly noiseAmountUniform: Node
38
+ private readonly noiseScaleUniform: Node
39
+ private readonly noiseSpeedUniform: Node
40
+
41
+ // Bloom
42
+ private bloomEnabled = false
43
+ private bloomNode: ReturnType<typeof bloom> | null = null
44
+ private readonly bloomIntensityUniform: Node
45
+ private readonly bloomRadiusUniform: Node
46
+ private readonly bloomSoftnessUniform: Node
47
+ private readonly bloomThresholdUniform: Node
48
+
49
+ private mesh: THREE.Mesh | null = null
50
+ private meshMaterial: THREE.MeshBasicNodeMaterial | null = null
51
+ private readonly bgColor = new THREE.Color(0x000000)
52
+ private gridResolution = 64
53
+ private isAnimated = false
54
+ private needsRebuild = true
55
+ private width = 1
56
+ private height = 1
57
+ private readonly placeholder: THREE.Texture
58
+
59
+ constructor(layerId: string) {
60
+ super(layerId)
61
+
62
+ this.perspScene = new THREE.Scene()
63
+ this.perspCamera = new THREE.PerspectiveCamera(45, 1, 0.01, 100)
64
+ this.perspCamera.position.set(0, 0, 1.2)
65
+ this.perspCamera.lookAt(0, 0, 0)
66
+
67
+ this.placeholder = new THREE.Texture()
68
+
69
+ this.internalRT = new THREE.WebGLRenderTarget(1, 1, {
70
+ depthBuffer: true,
71
+ format: THREE.RGBAFormat,
72
+ generateMipmaps: false,
73
+ magFilter: THREE.LinearFilter,
74
+ minFilter: THREE.LinearFilter,
75
+ stencilBuffer: false,
76
+ type: THREE.HalfFloatType,
77
+ })
78
+
79
+ this.displacementUniform = uniform(0.5)
80
+ this.pointSizeUniform = uniform(3.0)
81
+ this.timeUniform = uniform(0.0)
82
+ this.noiseAmountUniform = uniform(0.0)
83
+ this.noiseScaleUniform = uniform(3.0)
84
+ this.noiseSpeedUniform = uniform(0.5)
85
+
86
+ this.bloomIntensityUniform = uniform(1.25)
87
+ this.bloomRadiusUniform = uniform(6)
88
+ this.bloomSoftnessUniform = uniform(0.35)
89
+ this.bloomThresholdUniform = uniform(0.6)
90
+
91
+ const blitUv = vec2(uv().x, float(1).sub(uv().y))
92
+ this.blitInputNode = tslTexture(new THREE.Texture(), blitUv)
93
+
94
+ this.rebuildEffectNode()
95
+ }
96
+
97
+ override render(
98
+ renderer: THREE.WebGPURenderer,
99
+ inputTexture: THREE.Texture,
100
+ outputTarget: THREE.WebGLRenderTarget,
101
+ time: number,
102
+ delta: number,
103
+ ): void {
104
+ if (this.needsRebuild) {
105
+ this.rebuildGrid()
106
+ this.needsRebuild = false
107
+ }
108
+
109
+ if (this.inputSamplerNode) {
110
+ this.inputSamplerNode.value = inputTexture
111
+ }
112
+
113
+ renderer.setClearColor(this.bgColor, 1)
114
+ renderer.setRenderTarget(this.internalRT)
115
+ renderer.render(this.perspScene, this.perspCamera)
116
+
117
+ this.blitInputNode.value = this.internalRT.texture
118
+ super.render(renderer, inputTexture, outputTarget, time, delta)
119
+ }
120
+
121
+ protected override beforeRender(time: number, _delta: number): void {
122
+ this.timeUniform.value = time
123
+ }
124
+
125
+ override needsContinuousRender(): boolean {
126
+ return this.isAnimated
127
+ }
128
+
129
+ override updateParams(params: LayerParameterValues): void {
130
+ const nextResolution =
131
+ typeof params.gridResolution === "number"
132
+ ? Math.max(16, Math.min(512, Math.round(params.gridResolution)))
133
+ : 64
134
+ const nextPointSize =
135
+ typeof params.pointSize === "number" ? params.pointSize : 3
136
+ const nextBloomEnabled = params.bloomEnabled === true
137
+
138
+ if (nextResolution !== this.gridResolution || nextPointSize !== (this.pointSizeUniform.value as number)) {
139
+ this.gridResolution = nextResolution
140
+ this.pointSizeUniform.value = nextPointSize
141
+ this.needsRebuild = true
142
+ }
143
+
144
+ this.displacementUniform.value =
145
+ typeof params.displacement === "number" ? params.displacement : 0.5
146
+
147
+ this.bgColor.set(typeof params.backgroundColor === "string" ? params.backgroundColor : "#000000")
148
+
149
+ const noiseAmount = typeof params.noiseAmount === "number" ? params.noiseAmount : 0
150
+ this.noiseAmountUniform.value = noiseAmount
151
+ this.noiseScaleUniform.value =
152
+ typeof params.noiseScale === "number" ? params.noiseScale : 3
153
+ this.noiseSpeedUniform.value =
154
+ typeof params.noiseSpeed === "number" ? params.noiseSpeed : 0.5
155
+ this.isAnimated = noiseAmount > 0
156
+
157
+ this.bloomIntensityUniform.value =
158
+ typeof params.bloomIntensity === "number" ? Math.max(0, params.bloomIntensity) : 1.25
159
+ this.bloomThresholdUniform.value =
160
+ typeof params.bloomThreshold === "number" ? clamp01(params.bloomThreshold) : 0.6
161
+ this.bloomRadiusUniform.value =
162
+ typeof params.bloomRadius === "number" ? Math.max(0, params.bloomRadius) : 6
163
+ this.bloomSoftnessUniform.value =
164
+ typeof params.bloomSoftness === "number" ? clamp01(params.bloomSoftness) : 0.35
165
+
166
+ if (nextBloomEnabled !== this.bloomEnabled) {
167
+ this.bloomEnabled = nextBloomEnabled
168
+ this.rebuildEffectNode()
169
+ }
170
+
171
+ if (this.bloomNode) {
172
+ this.bloomNode.strength.value = this.bloomIntensityUniform.value as number
173
+ this.bloomNode.radius.value = this.normalizeBloomRadius(this.bloomRadiusUniform.value as number)
174
+ this.bloomNode.threshold.value = this.bloomThresholdUniform.value as number
175
+ this.bloomNode.smoothWidth.value = this.normalizeBloomSoftness(this.bloomSoftnessUniform.value as number)
176
+ }
177
+ }
178
+
179
+ override resize(width: number, height: number): void {
180
+ this.width = Math.max(1, width)
181
+ this.height = Math.max(1, height)
182
+ this.internalRT.setSize(this.width, this.height)
183
+ this.perspCamera.aspect = this.width / this.height
184
+ this.perspCamera.updateProjectionMatrix()
185
+ this.needsRebuild = true
186
+ }
187
+
188
+ override dispose(): void {
189
+ this.disposeBloomNode()
190
+ this.clearGrid()
191
+ this.placeholder.dispose()
192
+ this.internalRT.dispose()
193
+ super.dispose()
194
+ }
195
+
196
+ protected override buildEffectNode(): Node {
197
+ if (!this.blitInputNode) {
198
+ return this.inputNode
199
+ }
200
+
201
+ this.disposeBloomNode()
202
+ this.bloomNode = null
203
+
204
+ const baseColor = vec3(this.blitInputNode.r, this.blitInputNode.g, this.blitInputNode.b)
205
+
206
+ if (!this.bloomEnabled) {
207
+ return vec4(baseColor, float(1))
208
+ }
209
+
210
+ const bloomInput = vec4(baseColor, float(1))
211
+ this.bloomNode = bloom(
212
+ bloomInput,
213
+ this.bloomIntensityUniform.value as number,
214
+ this.normalizeBloomRadius(this.bloomRadiusUniform.value as number),
215
+ this.bloomThresholdUniform.value as number,
216
+ )
217
+ this.bloomNode.smoothWidth.value = this.normalizeBloomSoftness(
218
+ this.bloomSoftnessUniform.value as number,
219
+ )
220
+
221
+ return vec4(
222
+ clamp(
223
+ baseColor.add(this.getBloomTextureNode().rgb),
224
+ vec3(float(0), float(0), float(0)),
225
+ vec3(float(1), float(1), float(1)),
226
+ ),
227
+ float(1),
228
+ )
229
+ }
230
+
231
+ private rebuildGrid(): void {
232
+ this.clearGrid()
233
+
234
+ const res = this.gridResolution
235
+ const count = res * res
236
+ const aspect = this.width / this.height
237
+ const pointSize = this.pointSizeUniform.value as number
238
+
239
+ // Camera frustum at z=0
240
+ const halfH = Math.tan((45 * Math.PI) / 360) * 1.2
241
+ const halfW = halfH * aspect
242
+
243
+ // Size of each quad in world units — convert point size from pixels to world
244
+ // At z=0 with camera at 1.2, 1 world unit = canvas_height / (2 * halfH) pixels
245
+ const pixelsPerUnit = this.height / (2 * halfH)
246
+ const quadWorldSize = pointSize / pixelsPerUnit
247
+
248
+ // Base quad: unit plane centered at origin
249
+ const baseGeo = new THREE.PlaneGeometry(1, 1)
250
+
251
+ // Instance attributes
252
+ const offsets = new Float32Array(count * 3)
253
+ const gridUvs = new Float32Array(count * 2)
254
+
255
+ for (let row = 0; row < res; row++) {
256
+ for (let col = 0; col < res; col++) {
257
+ const i = row * res + col
258
+ const u = col / (res - 1)
259
+ const v = row / (res - 1)
260
+
261
+ offsets[i * 3] = (u * 2 - 1) * halfW
262
+ offsets[i * 3 + 1] = (v * 2 - 1) * halfH
263
+ offsets[i * 3 + 2] = 0
264
+
265
+ gridUvs[i * 2] = u
266
+ gridUvs[i * 2 + 1] = 1 - v
267
+ }
268
+ }
269
+
270
+ const instancedGeo = new THREE.InstancedBufferGeometry()
271
+ instancedGeo.index = baseGeo.index
272
+ instancedGeo.setAttribute("position", baseGeo.getAttribute("position")!)
273
+ instancedGeo.setAttribute("normal", baseGeo.getAttribute("normal")!)
274
+ instancedGeo.setAttribute("uv", baseGeo.getAttribute("uv")!)
275
+ instancedGeo.setAttribute("instanceOffset", new THREE.InstancedBufferAttribute(offsets, 3))
276
+ instancedGeo.setAttribute("instanceGridUv", new THREE.InstancedBufferAttribute(gridUvs, 2))
277
+ instancedGeo.instanceCount = count
278
+
279
+ // GPU material
280
+ const instanceOffset = attribute("instanceOffset", "vec3")
281
+ const instanceGridUv = attribute("instanceGridUv", "vec2")
282
+
283
+ // Sample input texture per instance
284
+ this.inputSamplerNode = tslTexture(this.placeholder, instanceGridUv)
285
+ const sampledColor = this.inputSamplerNode
286
+
287
+ // Luma for Z displacement
288
+ const luma = sampledColor.r
289
+ .mul(0.2126)
290
+ .add(sampledColor.g.mul(0.7152))
291
+ .add(sampledColor.b.mul(0.0722))
292
+
293
+ // Per-particle noise using instance grid UV scaled by resolution
294
+ const noiseUv = vec2(
295
+ instanceGridUv.x.mul(float(res)).mul(this.noiseScaleUniform),
296
+ instanceGridUv.y.mul(float(res)).mul(this.noiseScaleUniform),
297
+ )
298
+ const noiseInputX = vec3(
299
+ noiseUv.x,
300
+ noiseUv.y,
301
+ this.timeUniform.mul(this.noiseSpeedUniform),
302
+ )
303
+ const noiseInputY = vec3(
304
+ noiseUv.x,
305
+ noiseUv.y,
306
+ this.timeUniform.mul(this.noiseSpeedUniform).add(float(100)),
307
+ )
308
+ const noiseOffsetX = simplexNoise3d(noiseInputX).mul(this.noiseAmountUniform).mul(0.01)
309
+ const noiseOffsetY = simplexNoise3d(noiseInputY).mul(this.noiseAmountUniform).mul(0.01)
310
+
311
+ // Scale quad vertices by world size, then offset to instance position + noise + displacement
312
+ const scaledPos = positionLocal.mul(float(quadWorldSize))
313
+ const finalPos = vec3(
314
+ scaledPos.x.add(instanceOffset.x).add(noiseOffsetX),
315
+ scaledPos.y.add(instanceOffset.y).add(noiseOffsetY),
316
+ scaledPos.z.add(instanceOffset.z).add(luma.mul(this.displacementUniform)),
317
+ )
318
+
319
+ // Circle mask using quad UV (0–1 per quad)
320
+ // Edge width scales with point size so anti-aliasing is always ~1.5px
321
+ const quadUv = uv()
322
+ const dist = vec2(quadUv.x.sub(0.5), quadUv.y.sub(0.5)).length()
323
+ const aaWidth = float(1.5).div(this.pointSizeUniform)
324
+ const circleMask = smoothstep(float(0.5), float(0.5).sub(aaWidth), dist)
325
+
326
+ const material = new THREE.MeshBasicNodeMaterial()
327
+ material.positionNode = finalPos as Node
328
+ material.colorNode = vec4(sampledColor.r, sampledColor.g, sampledColor.b, circleMask) as Node
329
+ material.transparent = true
330
+ material.alphaTest = 0.01
331
+ material.depthWrite = false
332
+ material.side = THREE.DoubleSide
333
+
334
+ this.meshMaterial = material
335
+ this.mesh = new THREE.Mesh(instancedGeo, material)
336
+ this.mesh.frustumCulled = false
337
+ this.perspScene.add(this.mesh)
338
+
339
+ baseGeo.dispose()
340
+ }
341
+
342
+ private clearGrid(): void {
343
+ if (this.mesh) {
344
+ this.perspScene.remove(this.mesh)
345
+ this.mesh.geometry.dispose()
346
+ this.mesh = null
347
+ }
348
+ if (this.meshMaterial) {
349
+ this.meshMaterial.dispose()
350
+ this.meshMaterial = null
351
+ }
352
+ this.inputSamplerNode = null
353
+ }
354
+
355
+ private normalizeBloomRadius(value: number): number {
356
+ return clamp01(value / 24)
357
+ }
358
+
359
+ private normalizeBloomSoftness(value: number): number {
360
+ return Math.max(0.001, value * 0.25)
361
+ }
362
+
363
+ private disposeBloomNode(): void {
364
+ ;(this.bloomNode as { dispose?: () => void } | null)?.dispose?.()
365
+ }
366
+
367
+ private getBloomTextureNode(): Node {
368
+ const bloomNode = this.bloomNode as
369
+ | ({
370
+ getTexture?: () => Node
371
+ getTextureNode?: () => Node
372
+ } & object)
373
+ | null
374
+
375
+ if (!bloomNode) {
376
+ throw new Error("Bloom node is not initialized")
377
+ }
378
+
379
+ if ("getTextureNode" in bloomNode && typeof bloomNode.getTextureNode === "function") {
380
+ return bloomNode.getTextureNode()
381
+ }
382
+
383
+ if ("getTexture" in bloomNode && typeof bloomNode.getTexture === "function") {
384
+ return bloomNode.getTexture()
385
+ }
386
+
387
+ throw new Error("Bloom node does not expose a texture getter")
388
+ }
389
+ }
@@ -0,0 +1,209 @@
1
+ import * as THREE from "three/webgpu"
2
+ import {
3
+ clamp,
4
+ cos,
5
+ float,
6
+ mix,
7
+ sin,
8
+ texture as tslTexture,
9
+ type TSLNode,
10
+ uniform,
11
+ uv,
12
+ vec2,
13
+ vec3,
14
+ vec4,
15
+ } from "three/tsl"
16
+ import { buildBlendNode } from "./blend-modes"
17
+ import type { LayerCompositeMode, LayerParameterValues } from "../types/editor"
18
+
19
+ type Node = TSLNode
20
+
21
+ export class PassNode {
22
+ readonly layerId: string
23
+
24
+ enabled = true
25
+
26
+ protected readonly scene: THREE.Scene
27
+ protected readonly camera: THREE.OrthographicCamera
28
+ protected readonly material: THREE.MeshBasicNodeMaterial
29
+ protected readonly inputNode: Node
30
+ protected effectNode: Node
31
+ protected readonly hueUniform: Node
32
+ protected readonly saturationUniform: Node
33
+
34
+ private readonly opacityUniform: Node
35
+ private blendMode = "normal"
36
+ private compositeMode: LayerCompositeMode = "filter"
37
+
38
+ constructor(layerId: string) {
39
+ this.layerId = layerId
40
+ this.scene = new THREE.Scene()
41
+ this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
42
+ this.material = new THREE.MeshBasicNodeMaterial()
43
+ this.opacityUniform = uniform(1)
44
+ this.hueUniform = uniform(0)
45
+ this.saturationUniform = uniform(1)
46
+
47
+ const placeholder = new THREE.Texture()
48
+ const renderTargetUv = vec2(uv().x, float(1).sub(uv().y))
49
+ this.inputNode = tslTexture(placeholder, renderTargetUv)
50
+ this.effectNode = this.buildEffectNode()
51
+ this.rebuildColorNode()
52
+
53
+ const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), this.material)
54
+ mesh.frustumCulled = false
55
+ this.scene.add(mesh)
56
+ }
57
+
58
+ render(
59
+ renderer: THREE.WebGPURenderer,
60
+ inputTexture: THREE.Texture,
61
+ outputTarget: THREE.WebGLRenderTarget,
62
+ time: number,
63
+ delta: number,
64
+ ): void {
65
+ this.inputNode.value = inputTexture
66
+ this.beforeRender(time, delta)
67
+ renderer.setRenderTarget(outputTarget)
68
+ renderer.render(this.scene, this.camera)
69
+ }
70
+
71
+ updateOpacity(opacity: number): void {
72
+ this.opacityUniform.value = opacity
73
+ }
74
+
75
+ updateBlendMode(blendMode: string): boolean {
76
+ if (blendMode === this.blendMode) {
77
+ return false
78
+ }
79
+
80
+ this.blendMode = blendMode
81
+ this.rebuildColorNode()
82
+ this.material.needsUpdate = true
83
+ return true
84
+ }
85
+
86
+ updateCompositeMode(compositeMode: LayerCompositeMode): boolean {
87
+ if (compositeMode === this.compositeMode) {
88
+ return false
89
+ }
90
+
91
+ this.compositeMode = compositeMode
92
+ this.rebuildColorNode()
93
+ this.material.needsUpdate = true
94
+ return true
95
+ }
96
+
97
+ updateLayerColorAdjustments(hue: number, saturation: number): void {
98
+ this.hueUniform.value = (hue * Math.PI) / 180
99
+ this.saturationUniform.value = Math.max(0, saturation)
100
+ }
101
+
102
+ updateParams(_params: LayerParameterValues): void {
103
+ // Default pass has no per-layer parameter handling.
104
+ }
105
+
106
+ resize(_width: number, _height: number): void {
107
+ // Default pass has no resize-dependent uniforms.
108
+ }
109
+
110
+ updateLogicalSize(_width: number, _height: number): void {
111
+ // Default pass has no logical-size-dependent uniforms.
112
+ }
113
+
114
+ needsContinuousRender(): boolean {
115
+ return false
116
+ }
117
+
118
+ dispose(): void {
119
+ this.scene.clear()
120
+ this.material.dispose()
121
+ }
122
+
123
+ getMaterialVersion(): number {
124
+ return this.material.version
125
+ }
126
+
127
+ protected beforeRender(_time: number, _delta: number): void {
128
+ // Default pass has no per-frame work.
129
+ }
130
+
131
+ protected buildEffectNode(): Node {
132
+ return this.inputNode
133
+ }
134
+
135
+ protected rebuildEffectNode(): void {
136
+ this.effectNode = this.buildEffectNode()
137
+ this.rebuildColorNode()
138
+ this.material.needsUpdate = true
139
+ }
140
+
141
+ protected rebuildColorNode(): void {
142
+ const adjustedEffectNode = this.applySharedColorAdjustments(this.effectNode)
143
+ this.material.colorNode = buildBlendNode(
144
+ this.blendMode,
145
+ this.inputNode,
146
+ adjustedEffectNode,
147
+ this.opacityUniform,
148
+ this.compositeMode,
149
+ ) as Node
150
+ }
151
+
152
+ private applySharedColorAdjustments(sourceNode: Node): Node {
153
+ const sourceColor = vec3(
154
+ float(sourceNode.r),
155
+ float(sourceNode.g),
156
+ float(sourceNode.b),
157
+ )
158
+ const luma = float(sourceColor.x)
159
+ .mul(float(0.2126))
160
+ .add(float(sourceColor.y).mul(float(0.7152)))
161
+ .add(float(sourceColor.z).mul(float(0.0722)))
162
+ const saturated = mix(vec3(luma, luma, luma), sourceColor, this.saturationUniform)
163
+ const hueCos = float(cos(this.hueUniform))
164
+ const hueSin = float(sin(this.hueUniform))
165
+ const rotated = vec3(
166
+ float(saturated.x)
167
+ .mul(float(0.213).add(hueCos.mul(float(0.787))).sub(hueSin.mul(float(0.213))))
168
+ .add(
169
+ float(saturated.y).mul(
170
+ float(0.715).sub(hueCos.mul(float(0.715))).sub(hueSin.mul(float(0.715))),
171
+ ),
172
+ )
173
+ .add(
174
+ float(saturated.z).mul(
175
+ float(0.072).sub(hueCos.mul(float(0.072))).add(hueSin.mul(float(0.928))),
176
+ ),
177
+ ),
178
+ float(saturated.x)
179
+ .mul(float(0.213).sub(hueCos.mul(float(0.213))).add(hueSin.mul(float(0.143))))
180
+ .add(
181
+ float(saturated.y).mul(
182
+ float(0.715).add(hueCos.mul(float(0.285))).add(hueSin.mul(float(0.14))),
183
+ ),
184
+ )
185
+ .add(
186
+ float(saturated.z).mul(
187
+ float(0.072).sub(hueCos.mul(float(0.072))).sub(hueSin.mul(float(0.283))),
188
+ ),
189
+ ),
190
+ float(saturated.x)
191
+ .mul(float(0.213).sub(hueCos.mul(float(0.213))).sub(hueSin.mul(float(0.787))))
192
+ .add(
193
+ float(saturated.y).mul(
194
+ float(0.715).sub(hueCos.mul(float(0.715))).add(hueSin.mul(float(0.715))),
195
+ ),
196
+ )
197
+ .add(
198
+ float(saturated.z).mul(
199
+ float(0.072).add(hueCos.mul(float(0.928))).add(hueSin.mul(float(0.072))),
200
+ ),
201
+ ),
202
+ )
203
+
204
+ return vec4(
205
+ clamp(rotated, vec3(float(0), float(0), float(0)), vec3(float(1), float(1), float(1))),
206
+ float(1),
207
+ )
208
+ }
209
+ }