@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.
- package/.biome/plugins/README.md +21 -0
- package/.biome/plugins/no-anchor-element.grit +12 -0
- package/.biome/plugins/no-relative-parent-imports.grit +10 -0
- package/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
- package/.changeset/README.md +17 -0
- package/.changeset/config.json +11 -0
- package/.editorconfig +40 -0
- package/.env.example +81 -0
- package/.gitattributes +19 -0
- package/.github/workflows/canary.yml +80 -0
- package/.github/workflows/ci.yml +37 -0
- package/.github/workflows/release.yml +56 -0
- package/.tldrignore +84 -0
- package/.vscode/extensions.json +20 -0
- package/.vscode/settings.json +105 -0
- package/README.md +119 -0
- package/biome.json +249 -0
- package/bun.lock +1224 -0
- package/next.config.ts +131 -0
- package/package.json +73 -0
- package/packages/shader-lab-react/CHANGELOG.md +9 -0
- package/packages/shader-lab-react/README.md +119 -0
- package/packages/shader-lab-react/assets/patterns/bars/1.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/2.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/3.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/4.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/5.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/6.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/candles/1.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/candles/2.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/candles/3.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/candles/4.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/1.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/2.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/3.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/4.svg +4 -0
- package/packages/shader-lab-react/assets/patterns/shapes/5.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/6.svg +4 -0
- package/packages/shader-lab-react/assets/textures/blue-noise.png +0 -0
- package/packages/shader-lab-react/package.json +36 -0
- package/packages/shader-lab-react/scripts/fix-esm-specifiers.mjs +57 -0
- package/packages/shader-lab-react/scripts/prepare-dist.mjs +4 -0
- package/packages/shader-lab-react/src/ambient/three-tsl.d.ts +146 -0
- package/packages/shader-lab-react/src/ambient/three-webgpu.d.ts +51 -0
- package/packages/shader-lab-react/src/easings.ts +4 -0
- package/packages/shader-lab-react/src/index.ts +35 -0
- package/packages/shader-lab-react/src/lib/editor/custom-shader/shared.ts +2 -0
- package/packages/shader-lab-react/src/renderer/ascii-atlas.ts +83 -0
- package/packages/shader-lab-react/src/renderer/ascii-pass.ts +416 -0
- package/packages/shader-lab-react/src/renderer/asset-url.ts +3 -0
- package/packages/shader-lab-react/src/renderer/blend-modes.ts +229 -0
- package/packages/shader-lab-react/src/renderer/contracts.ts +54 -0
- package/packages/shader-lab-react/src/renderer/create-webgpu-renderer.ts +48 -0
- package/packages/shader-lab-react/src/renderer/crt-pass.ts +1040 -0
- package/packages/shader-lab-react/src/renderer/custom-shader-pass.ts +108 -0
- package/packages/shader-lab-react/src/renderer/custom-shader-runtime.ts +309 -0
- package/packages/shader-lab-react/src/renderer/dither-textures.ts +99 -0
- package/packages/shader-lab-react/src/renderer/dithering-pass.ts +322 -0
- package/packages/shader-lab-react/src/renderer/gradient-pass.ts +521 -0
- package/packages/shader-lab-react/src/renderer/halftone-pass.ts +932 -0
- package/packages/shader-lab-react/src/renderer/ink-pass.ts +802 -0
- package/packages/shader-lab-react/src/renderer/live-pass.ts +194 -0
- package/packages/shader-lab-react/src/renderer/media-pass.ts +187 -0
- package/packages/shader-lab-react/src/renderer/media-texture.ts +66 -0
- package/packages/shader-lab-react/src/renderer/particle-grid-pass.ts +389 -0
- package/packages/shader-lab-react/src/renderer/pass-node.ts +209 -0
- package/packages/shader-lab-react/src/renderer/pattern-atlas.ts +133 -0
- package/packages/shader-lab-react/src/renderer/pattern-pass.ts +552 -0
- package/packages/shader-lab-react/src/renderer/pipeline-manager.ts +369 -0
- package/packages/shader-lab-react/src/renderer/pixel-sorting-pass.ts +277 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/color/tonemapping.ts +87 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/cosine-palette.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/common.ts +31 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +36 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +36 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/fbm.ts +13 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +96 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/ridge-noise.ts +24 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +79 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +89 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/turbulence.ts +56 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/value-noise-3d.ts +32 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +60 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +15 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom.ts +11 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +24 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +11 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/atan2.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-conj.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-cos.ts +10 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-div.ts +11 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-log.ts +7 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mobius.ts +12 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mul.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-pow.ts +16 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sin.ts +10 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sqrt.ts +18 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-tan.ts +12 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-to-polar.ts +10 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/hyperbolic.ts +20 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/index.ts +48 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/rotate.ts +15 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +15 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-box-2d.ts +6 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-diamond.ts +6 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-rhombus.ts +27 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-sphere.ts +6 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smax.ts +7 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smin.ts +7 -0
- package/packages/shader-lab-react/src/renderer/text-pass.ts +176 -0
- package/packages/shader-lab-react/src/runtime-clock.ts +42 -0
- package/packages/shader-lab-react/src/runtime-frame.ts +29 -0
- package/packages/shader-lab-react/src/shader-lab-composition.tsx +163 -0
- package/packages/shader-lab-react/src/timeline.ts +283 -0
- package/packages/shader-lab-react/src/types/editor.ts +5 -0
- package/packages/shader-lab-react/src/types.ts +141 -0
- package/packages/shader-lab-react/tsconfig.build.json +8 -0
- package/packages/shader-lab-react/tsconfig.json +21 -0
- package/postcss.config.mjs +5 -0
- package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf-atlas.png +0 -0
- package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf.json +1412 -0
- package/public/assets/patterns/bars/1.svg +3 -0
- package/public/assets/patterns/bars/2.svg +3 -0
- package/public/assets/patterns/bars/3.svg +3 -0
- package/public/assets/patterns/bars/4.svg +3 -0
- package/public/assets/patterns/bars/5.svg +3 -0
- package/public/assets/patterns/bars/6.svg +3 -0
- package/public/assets/patterns/candles/1.svg +3 -0
- package/public/assets/patterns/candles/2.svg +3 -0
- package/public/assets/patterns/candles/3.svg +3 -0
- package/public/assets/patterns/candles/4.svg +3 -0
- package/public/assets/patterns/shapes/1.svg +3 -0
- package/public/assets/patterns/shapes/2.svg +3 -0
- package/public/assets/patterns/shapes/3.svg +3 -0
- package/public/assets/patterns/shapes/4.svg +4 -0
- package/public/assets/patterns/shapes/5.svg +3 -0
- package/public/assets/patterns/shapes/6.svg +4 -0
- package/public/fonts/geist/Geist-Mono.woff2 +0 -0
- package/public/textures/blue-noise.png +0 -0
- package/public/textures/crt-mask.png +0 -0
- package/src/app/design/page.tsx +398 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +280 -0
- package/src/app/layout.tsx +89 -0
- package/src/app/page.tsx +20 -0
- package/src/app/robots.ts +13 -0
- package/src/app/sitemap.ts +13 -0
- package/src/components/editor/editor-canvas-viewport.tsx +116 -0
- package/src/components/editor/editor-export-dialog.tsx +1177 -0
- package/src/components/editor/editor-timeline-overlay.tsx +983 -0
- package/src/components/editor/editor-topbar.tsx +287 -0
- package/src/components/editor/layer-sidebar.tsx +738 -0
- package/src/components/editor/properties-sidebar-content.tsx +574 -0
- package/src/components/editor/properties-sidebar-fields.tsx +389 -0
- package/src/components/editor/properties-sidebar-utils.ts +178 -0
- package/src/components/editor/properties-sidebar.tsx +421 -0
- package/src/components/ui/button/index.tsx +57 -0
- package/src/components/ui/color-picker/index.tsx +358 -0
- package/src/components/ui/glass-panel/index.tsx +45 -0
- package/src/components/ui/icon-button/index.tsx +46 -0
- package/src/components/ui/select/index.tsx +136 -0
- package/src/components/ui/slider/index.tsx +192 -0
- package/src/components/ui/toggle/index.tsx +34 -0
- package/src/components/ui/typography/index.tsx +61 -0
- package/src/components/ui/xy-pad/index.tsx +160 -0
- package/src/features/editor/components/editor-export-dialog.module.css +271 -0
- package/src/hooks/use-editor-renderer.ts +182 -0
- package/src/lib/app.ts +6 -0
- package/src/lib/cn.ts +7 -0
- package/src/lib/easings.ts +240 -0
- package/src/lib/editor/config/layer-registry.ts +2434 -0
- package/src/lib/editor/custom-shader/shared.ts +28 -0
- package/src/lib/editor/export.ts +420 -0
- package/src/lib/editor/history.ts +71 -0
- package/src/lib/editor/layers.ts +76 -0
- package/src/lib/editor/parameter-schema.ts +75 -0
- package/src/lib/editor/project-file.ts +145 -0
- package/src/lib/editor/shader-export-snippet.ts +37 -0
- package/src/lib/editor/shader-export.ts +315 -0
- package/src/lib/editor/timeline/evaluate.ts +252 -0
- package/src/lib/editor/view-transform.ts +58 -0
- package/src/lib/fonts.ts +28 -0
- package/src/renderer/ascii-atlas.ts +83 -0
- package/src/renderer/ascii-pass.ts +416 -0
- package/src/renderer/blend-modes.ts +229 -0
- package/src/renderer/contracts.ts +161 -0
- package/src/renderer/create-webgpu-renderer.ts +48 -0
- package/src/renderer/crt-pass.ts +1040 -0
- package/src/renderer/custom-shader-pass.ts +117 -0
- package/src/renderer/custom-shader-runtime.ts +309 -0
- package/src/renderer/dither-textures.ts +99 -0
- package/src/renderer/dithering-pass.ts +322 -0
- package/src/renderer/gradient-pass.ts +520 -0
- package/src/renderer/halftone-pass.ts +932 -0
- package/src/renderer/ink-pass.ts +683 -0
- package/src/renderer/live-pass.ts +194 -0
- package/src/renderer/media-pass.ts +187 -0
- package/src/renderer/media-texture.ts +66 -0
- package/src/renderer/particle-grid-pass.ts +389 -0
- package/src/renderer/pass-node-factory.ts +33 -0
- package/src/renderer/pass-node.ts +209 -0
- package/src/renderer/pattern-atlas.ts +97 -0
- package/src/renderer/pattern-pass.ts +552 -0
- package/src/renderer/pipeline-manager.ts +343 -0
- package/src/renderer/pixel-sorting-pass.ts +277 -0
- package/src/renderer/project-clock.ts +57 -0
- package/src/renderer/shaders/tsl/color/tonemapping.ts +86 -0
- package/src/renderer/shaders/tsl/cosine-palette.ts +8 -0
- package/src/renderer/shaders/tsl/noise/common.ts +30 -0
- package/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +35 -0
- package/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +35 -0
- package/src/renderer/shaders/tsl/noise/fbm.ts +12 -0
- package/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +97 -0
- package/src/renderer/shaders/tsl/noise/ridge-noise.ts +23 -0
- package/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +78 -0
- package/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +88 -0
- package/src/renderer/shaders/tsl/noise/turbulence.ts +55 -0
- package/src/renderer/shaders/tsl/noise/value-noise-3d.ts +31 -0
- package/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +59 -0
- package/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +14 -0
- package/src/renderer/shaders/tsl/patterns/bloom.ts +10 -0
- package/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +23 -0
- package/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +8 -0
- package/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +10 -0
- package/src/renderer/shaders/tsl/utils/atan2.ts +8 -0
- package/src/renderer/shaders/tsl/utils/complex-conj.ts +8 -0
- package/src/renderer/shaders/tsl/utils/complex-cos.ts +9 -0
- package/src/renderer/shaders/tsl/utils/complex-div.ts +10 -0
- package/src/renderer/shaders/tsl/utils/complex-log.ts +6 -0
- package/src/renderer/shaders/tsl/utils/complex-mobius.ts +11 -0
- package/src/renderer/shaders/tsl/utils/complex-mul.ts +8 -0
- package/src/renderer/shaders/tsl/utils/complex-pow.ts +15 -0
- package/src/renderer/shaders/tsl/utils/complex-sin.ts +9 -0
- package/src/renderer/shaders/tsl/utils/complex-sqrt.ts +17 -0
- package/src/renderer/shaders/tsl/utils/complex-tan.ts +11 -0
- package/src/renderer/shaders/tsl/utils/complex-to-polar.ts +9 -0
- package/src/renderer/shaders/tsl/utils/hyperbolic.ts +19 -0
- package/src/renderer/shaders/tsl/utils/index.ts +47 -0
- package/src/renderer/shaders/tsl/utils/rotate.ts +14 -0
- package/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +14 -0
- package/src/renderer/shaders/tsl/utils/sd-box-2d.ts +5 -0
- package/src/renderer/shaders/tsl/utils/sd-diamond.ts +5 -0
- package/src/renderer/shaders/tsl/utils/sd-rhombus.ts +26 -0
- package/src/renderer/shaders/tsl/utils/sd-sphere.ts +5 -0
- package/src/renderer/shaders/tsl/utils/smax.ts +7 -0
- package/src/renderer/shaders/tsl/utils/smin.ts +6 -0
- package/src/renderer/text-pass.ts +176 -0
- package/src/store/asset-store.ts +193 -0
- package/src/store/editor-store.ts +223 -0
- package/src/store/history-store.ts +172 -0
- package/src/store/index.ts +31 -0
- package/src/store/layer-store.ts +675 -0
- package/src/store/timeline-store.ts +572 -0
- package/src/types/assets.d.ts +6 -0
- package/src/types/css.d.ts +21 -0
- package/src/types/editor.ts +357 -0
- package/src/types/react.d.ts +15 -0
- package/src/types/three-tsl.d.ts +146 -0
- package/src/types/three-webgpu.d.ts +51 -0
- package/tsconfig.json +49 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { float, type TSLNode, texture as tslTexture, uv, vec2 } from "three/tsl"
|
|
2
|
+
import * as THREE from "three/webgpu"
|
|
3
|
+
import { AsciiPass } from "./ascii-pass"
|
|
4
|
+
import { CrtPass } from "./crt-pass"
|
|
5
|
+
import { CustomShaderPass } from "./custom-shader-pass"
|
|
6
|
+
import { DitheringPass } from "./dithering-pass"
|
|
7
|
+
import { GradientPass } from "./gradient-pass"
|
|
8
|
+
import { HalftonePass } from "./halftone-pass"
|
|
9
|
+
import { InkPass } from "./ink-pass"
|
|
10
|
+
import { LivePass } from "./live-pass"
|
|
11
|
+
import { MediaPass } from "./media-pass"
|
|
12
|
+
import { ParticleGridPass } from "./particle-grid-pass"
|
|
13
|
+
import type { PassNode } from "./pass-node"
|
|
14
|
+
import { PatternPass } from "./pattern-pass"
|
|
15
|
+
import { PixelSortingPass } from "./pixel-sorting-pass"
|
|
16
|
+
import { TextPass } from "./text-pass"
|
|
17
|
+
import type { ShaderLabCompositeMode, ShaderLabLayerConfig } from "../types"
|
|
18
|
+
|
|
19
|
+
type LayerPassNode =
|
|
20
|
+
| AsciiPass
|
|
21
|
+
| CrtPass
|
|
22
|
+
| CustomShaderPass
|
|
23
|
+
| DitheringPass
|
|
24
|
+
| GradientPass
|
|
25
|
+
| HalftonePass
|
|
26
|
+
| InkPass
|
|
27
|
+
| LivePass
|
|
28
|
+
| MediaPass
|
|
29
|
+
| ParticleGridPass
|
|
30
|
+
| PassNode
|
|
31
|
+
| PatternPass
|
|
32
|
+
| PixelSortingPass
|
|
33
|
+
| TextPass
|
|
34
|
+
|
|
35
|
+
const RENDER_TARGET_OPTIONS = {
|
|
36
|
+
depthBuffer: false,
|
|
37
|
+
format: THREE.RGBAFormat,
|
|
38
|
+
generateMipmaps: false,
|
|
39
|
+
magFilter: THREE.NearestFilter,
|
|
40
|
+
minFilter: THREE.NearestFilter,
|
|
41
|
+
stencilBuffer: false,
|
|
42
|
+
type: THREE.HalfFloatType,
|
|
43
|
+
} as const
|
|
44
|
+
|
|
45
|
+
function clampUnit(value: number): number {
|
|
46
|
+
return Math.max(0, Math.min(1, value))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parameterValuesSignature(params: ShaderLabLayerConfig["params"]): string {
|
|
50
|
+
return JSON.stringify(
|
|
51
|
+
Object.entries(params)
|
|
52
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
53
|
+
.map(([key, value]) => [key, value]),
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createLayerSignature(layer: ShaderLabLayerConfig): string {
|
|
58
|
+
if (layer.type === "custom-shader") {
|
|
59
|
+
return [
|
|
60
|
+
layer.id,
|
|
61
|
+
layer.kind,
|
|
62
|
+
layer.type,
|
|
63
|
+
layer.visible ? "1" : "0",
|
|
64
|
+
layer.opacity.toFixed(4),
|
|
65
|
+
layer.hue.toFixed(4),
|
|
66
|
+
layer.saturation.toFixed(4),
|
|
67
|
+
layer.blendMode,
|
|
68
|
+
layer.compositeMode,
|
|
69
|
+
typeof layer.params.sourceRevision === "number"
|
|
70
|
+
? String(layer.params.sourceRevision)
|
|
71
|
+
: "0",
|
|
72
|
+
typeof layer.params.sourceMode === "string" ? layer.params.sourceMode : "paste",
|
|
73
|
+
typeof layer.params.entryExport === "string" ? layer.params.entryExport : "sketch",
|
|
74
|
+
typeof layer.params.sourceFileName === "string" ? layer.params.sourceFileName : "",
|
|
75
|
+
].join("|")
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return [
|
|
79
|
+
layer.id,
|
|
80
|
+
layer.kind,
|
|
81
|
+
layer.type,
|
|
82
|
+
layer.asset?.kind ?? "no-asset",
|
|
83
|
+
layer.asset?.src ?? "no-src",
|
|
84
|
+
layer.visible ? "1" : "0",
|
|
85
|
+
layer.opacity.toFixed(4),
|
|
86
|
+
layer.hue.toFixed(4),
|
|
87
|
+
layer.saturation.toFixed(4),
|
|
88
|
+
layer.blendMode,
|
|
89
|
+
layer.compositeMode,
|
|
90
|
+
parameterValuesSignature(layer.params),
|
|
91
|
+
].join("|")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class PipelineManager {
|
|
95
|
+
private readonly renderer: THREE.WebGPURenderer
|
|
96
|
+
private readonly baseScene: THREE.Scene
|
|
97
|
+
private readonly baseCamera: THREE.OrthographicCamera
|
|
98
|
+
private readonly blitScene: THREE.Scene
|
|
99
|
+
private readonly blitCamera: THREE.OrthographicCamera
|
|
100
|
+
private readonly blitInputNode: TSLNode
|
|
101
|
+
private readonly blitMaterial: THREE.MeshBasicNodeMaterial
|
|
102
|
+
private readonly onRuntimeError: ((message: string | null) => void) | undefined
|
|
103
|
+
|
|
104
|
+
private passMap = new Map<string, LayerPassNode>()
|
|
105
|
+
private passes: LayerPassNode[] = []
|
|
106
|
+
private layerSignatures = new Map<string, string>()
|
|
107
|
+
private dirty = true
|
|
108
|
+
private width: number
|
|
109
|
+
private height: number
|
|
110
|
+
private logicalWidth: number
|
|
111
|
+
private logicalHeight: number
|
|
112
|
+
private rtA: THREE.WebGLRenderTarget
|
|
113
|
+
private rtB: THREE.WebGLRenderTarget
|
|
114
|
+
|
|
115
|
+
constructor(
|
|
116
|
+
renderer: THREE.WebGPURenderer,
|
|
117
|
+
size: { height: number; width: number },
|
|
118
|
+
onRuntimeError?: (message: string | null) => void,
|
|
119
|
+
) {
|
|
120
|
+
this.renderer = renderer
|
|
121
|
+
this.onRuntimeError = onRuntimeError
|
|
122
|
+
this.width = Math.max(1, size.width)
|
|
123
|
+
this.height = Math.max(1, size.height)
|
|
124
|
+
this.logicalWidth = this.width
|
|
125
|
+
this.logicalHeight = this.height
|
|
126
|
+
|
|
127
|
+
this.baseScene = new THREE.Scene()
|
|
128
|
+
this.baseCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
|
|
129
|
+
const baseMaterial = new THREE.MeshBasicMaterial({ color: "#080808" })
|
|
130
|
+
const baseMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), baseMaterial)
|
|
131
|
+
baseMesh.frustumCulled = false
|
|
132
|
+
this.baseScene.add(baseMesh)
|
|
133
|
+
|
|
134
|
+
this.rtA = new THREE.WebGLRenderTarget(this.width, this.height, RENDER_TARGET_OPTIONS)
|
|
135
|
+
this.rtB = new THREE.WebGLRenderTarget(this.width, this.height, RENDER_TARGET_OPTIONS)
|
|
136
|
+
|
|
137
|
+
this.blitScene = new THREE.Scene()
|
|
138
|
+
this.blitCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
|
|
139
|
+
const blitUv = vec2(uv().x, float(1).sub(uv().y))
|
|
140
|
+
this.blitInputNode = tslTexture(new THREE.Texture(), blitUv)
|
|
141
|
+
this.blitMaterial = new THREE.MeshBasicNodeMaterial()
|
|
142
|
+
this.blitMaterial.colorNode = this.blitInputNode
|
|
143
|
+
const blitMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), this.blitMaterial)
|
|
144
|
+
blitMesh.frustumCulled = false
|
|
145
|
+
this.blitScene.add(blitMesh)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
syncLayers(layers: ShaderLabLayerConfig[]): void {
|
|
149
|
+
const incomingIds = new Set(layers.map((layer) => layer.id))
|
|
150
|
+
|
|
151
|
+
for (const [layerId, pass] of this.passMap) {
|
|
152
|
+
if (incomingIds.has(layerId)) {
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
pass.dispose()
|
|
157
|
+
this.passMap.delete(layerId)
|
|
158
|
+
this.layerSignatures.delete(layerId)
|
|
159
|
+
this.dirty = true
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const orderedPasses: LayerPassNode[] = []
|
|
163
|
+
|
|
164
|
+
for (const layer of layers) {
|
|
165
|
+
const signature = createLayerSignature(layer)
|
|
166
|
+
let pass = this.passMap.get(layer.id)
|
|
167
|
+
|
|
168
|
+
if (!pass) {
|
|
169
|
+
pass = this.createPass(layer)
|
|
170
|
+
pass.resize(this.width, this.height)
|
|
171
|
+
pass.updateLogicalSize(this.logicalWidth, this.logicalHeight)
|
|
172
|
+
this.passMap.set(layer.id, pass)
|
|
173
|
+
this.dirty = true
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (this.layerSignatures.get(layer.id) !== signature) {
|
|
177
|
+
this.layerSignatures.set(layer.id, signature)
|
|
178
|
+
this.applyLayerState(pass, layer)
|
|
179
|
+
this.dirty = true
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
orderedPasses.push(pass)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (
|
|
186
|
+
orderedPasses.length !== this.passes.length ||
|
|
187
|
+
orderedPasses.some((pass, index) => this.passes[index] !== pass)
|
|
188
|
+
) {
|
|
189
|
+
this.passes = orderedPasses
|
|
190
|
+
this.dirty = true
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
render(time: number, delta: number): boolean {
|
|
195
|
+
const activePasses = this.passes.filter((pass) => pass.enabled)
|
|
196
|
+
const needsContinuousRender = activePasses.some((pass) => pass.needsContinuousRender())
|
|
197
|
+
|
|
198
|
+
if (!(this.dirty || needsContinuousRender)) {
|
|
199
|
+
return false
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (activePasses.length === 0) {
|
|
203
|
+
this.renderer.setRenderTarget(null)
|
|
204
|
+
this.renderer.render(this.baseScene, this.baseCamera)
|
|
205
|
+
this.dirty = false
|
|
206
|
+
return true
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
this.renderer.setRenderTarget(this.rtA)
|
|
210
|
+
this.renderer.render(this.baseScene, this.baseCamera)
|
|
211
|
+
|
|
212
|
+
let readTarget = this.rtA
|
|
213
|
+
let writeTarget = this.rtB
|
|
214
|
+
|
|
215
|
+
for (const pass of activePasses) {
|
|
216
|
+
pass.render(this.renderer, readTarget.texture, writeTarget, time, delta)
|
|
217
|
+
const previousRead = readTarget
|
|
218
|
+
readTarget = writeTarget
|
|
219
|
+
writeTarget = previousRead
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
this.blitInputNode.value = readTarget.texture
|
|
223
|
+
this.renderer.setRenderTarget(null)
|
|
224
|
+
this.renderer.render(this.blitScene, this.blitCamera)
|
|
225
|
+
this.dirty = false
|
|
226
|
+
return true
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
resize(size: { height: number; width: number }): void {
|
|
230
|
+
this.width = Math.max(1, size.width)
|
|
231
|
+
this.height = Math.max(1, size.height)
|
|
232
|
+
this.rtA.setSize(this.width, this.height)
|
|
233
|
+
this.rtB.setSize(this.width, this.height)
|
|
234
|
+
|
|
235
|
+
for (const pass of this.passMap.values()) {
|
|
236
|
+
pass.resize(this.width, this.height)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
this.dirty = true
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
updateLogicalSize(size: { height: number; width: number }): void {
|
|
243
|
+
const nextWidth = Math.max(1, size.width)
|
|
244
|
+
const nextHeight = Math.max(1, size.height)
|
|
245
|
+
|
|
246
|
+
if (nextWidth === this.logicalWidth && nextHeight === this.logicalHeight) {
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
this.logicalWidth = nextWidth
|
|
251
|
+
this.logicalHeight = nextHeight
|
|
252
|
+
|
|
253
|
+
for (const pass of this.passMap.values()) {
|
|
254
|
+
pass.updateLogicalSize(this.logicalWidth, this.logicalHeight)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this.dirty = true
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
dispose(): void {
|
|
261
|
+
this.rtA.dispose()
|
|
262
|
+
this.rtB.dispose()
|
|
263
|
+
this.blitMaterial.dispose()
|
|
264
|
+
|
|
265
|
+
for (const pass of this.passMap.values()) {
|
|
266
|
+
pass.dispose()
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
this.passMap.clear()
|
|
270
|
+
this.passes = []
|
|
271
|
+
this.layerSignatures.clear()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private applyLayerState(pass: LayerPassNode, layer: ShaderLabLayerConfig): void {
|
|
275
|
+
pass.enabled = layer.visible
|
|
276
|
+
pass.updateOpacity(clampUnit(layer.opacity))
|
|
277
|
+
pass.updateBlendMode(layer.blendMode)
|
|
278
|
+
const compositeMode: ShaderLabCompositeMode =
|
|
279
|
+
layer.compositeMode === "mask" ? "mask" : "filter"
|
|
280
|
+
pass.updateCompositeMode(compositeMode)
|
|
281
|
+
pass.updateLayerColorAdjustments(layer.hue, layer.saturation)
|
|
282
|
+
pass.updateParams(layer.params)
|
|
283
|
+
|
|
284
|
+
if (pass instanceof MediaPass) {
|
|
285
|
+
const asset = layer.asset
|
|
286
|
+
if (asset?.kind === "image" || asset?.kind === "video") {
|
|
287
|
+
void pass
|
|
288
|
+
.setMedia(asset.src, asset.kind)
|
|
289
|
+
.then(() => {
|
|
290
|
+
this.dirty = true
|
|
291
|
+
})
|
|
292
|
+
.catch((error) => {
|
|
293
|
+
this.onRuntimeError?.(
|
|
294
|
+
error instanceof Error ? error.message : "Failed to load media asset.",
|
|
295
|
+
)
|
|
296
|
+
this.dirty = true
|
|
297
|
+
})
|
|
298
|
+
} else {
|
|
299
|
+
pass.clearMedia()
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (pass instanceof LivePass) {
|
|
304
|
+
const facingMode =
|
|
305
|
+
typeof layer.params.facingMode === "string" ? layer.params.facingMode : "user"
|
|
306
|
+
|
|
307
|
+
if (facingMode !== pass.getFacingMode() || !pass.needsContinuousRender()) {
|
|
308
|
+
void pass
|
|
309
|
+
.startCamera(facingMode)
|
|
310
|
+
.then(() => {
|
|
311
|
+
this.dirty = true
|
|
312
|
+
})
|
|
313
|
+
.catch((error) => {
|
|
314
|
+
this.onRuntimeError?.(
|
|
315
|
+
error instanceof Error ? error.message : "Failed to start live camera input.",
|
|
316
|
+
)
|
|
317
|
+
this.dirty = true
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private createPass(layer: ShaderLabLayerConfig): LayerPassNode {
|
|
324
|
+
if (layer.kind === "effect") {
|
|
325
|
+
switch (layer.type) {
|
|
326
|
+
case "ascii":
|
|
327
|
+
return new AsciiPass(layer.id)
|
|
328
|
+
case "crt":
|
|
329
|
+
return new CrtPass(layer.id)
|
|
330
|
+
case "dithering":
|
|
331
|
+
return new DitheringPass(layer.id)
|
|
332
|
+
case "halftone":
|
|
333
|
+
return new HalftonePass(layer.id)
|
|
334
|
+
case "ink":
|
|
335
|
+
return new InkPass(layer.id)
|
|
336
|
+
case "particle-grid":
|
|
337
|
+
return new ParticleGridPass(layer.id)
|
|
338
|
+
case "pattern":
|
|
339
|
+
return new PatternPass(layer.id)
|
|
340
|
+
case "pixel-sorting":
|
|
341
|
+
return new PixelSortingPass(layer.id)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (layer.kind === "source" && (layer.type === "image" || layer.type === "video")) {
|
|
346
|
+
return new MediaPass(layer.id)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (layer.kind === "source" && layer.type === "gradient") {
|
|
350
|
+
return new GradientPass(layer.id)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (layer.kind === "source" && layer.type === "text") {
|
|
354
|
+
return new TextPass(layer.id)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (layer.kind === "source" && layer.type === "custom-shader") {
|
|
358
|
+
return new CustomShaderPass(layer.id, this.onRuntimeError)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (layer.kind === "source" && layer.type === "live") {
|
|
362
|
+
return new LivePass(layer.id)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
throw new Error(
|
|
366
|
+
`Layer "${layer.name}" of type "${layer.type}" is not supported by the package runtime yet.`,
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import * as THREE from "three/webgpu"
|
|
2
|
+
import {
|
|
3
|
+
dot,
|
|
4
|
+
float,
|
|
5
|
+
floor,
|
|
6
|
+
max,
|
|
7
|
+
min,
|
|
8
|
+
mod,
|
|
9
|
+
select,
|
|
10
|
+
texture as tslTexture,
|
|
11
|
+
type TSLNode,
|
|
12
|
+
uniform,
|
|
13
|
+
uv,
|
|
14
|
+
vec2,
|
|
15
|
+
vec3,
|
|
16
|
+
vec4,
|
|
17
|
+
} from "three/tsl"
|
|
18
|
+
import { PassNode } from "./pass-node"
|
|
19
|
+
import type { LayerParameterValues } from "../types/editor"
|
|
20
|
+
|
|
21
|
+
type Node = TSLNode
|
|
22
|
+
|
|
23
|
+
export class PixelSortingPass extends PassNode {
|
|
24
|
+
private readonly sortScene: THREE.Scene
|
|
25
|
+
private readonly sortCamera: THREE.OrthographicCamera
|
|
26
|
+
private readonly sortMaterial: THREE.MeshBasicNodeMaterial
|
|
27
|
+
private sortRtA: THREE.WebGLRenderTarget
|
|
28
|
+
private sortRtB: THREE.WebGLRenderTarget
|
|
29
|
+
|
|
30
|
+
private readonly blitInputNode: Node
|
|
31
|
+
private readonly sortTexNodeA: Node
|
|
32
|
+
private readonly sortTexNodeB: Node
|
|
33
|
+
|
|
34
|
+
private readonly passOffsetUniform: Node
|
|
35
|
+
private readonly widthUniform: Node
|
|
36
|
+
private readonly heightUniform: Node
|
|
37
|
+
private readonly thresholdUniform: Node
|
|
38
|
+
private readonly upperThresholdUniform: Node
|
|
39
|
+
private readonly directionUniform: Node
|
|
40
|
+
private readonly modeUniform: Node
|
|
41
|
+
private readonly reverseUniform: Node
|
|
42
|
+
|
|
43
|
+
private passCount = 150
|
|
44
|
+
private width = 1
|
|
45
|
+
private height = 1
|
|
46
|
+
|
|
47
|
+
constructor(layerId: string) {
|
|
48
|
+
super(layerId)
|
|
49
|
+
|
|
50
|
+
this.sortScene = new THREE.Scene()
|
|
51
|
+
this.sortCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
|
|
52
|
+
|
|
53
|
+
this.passOffsetUniform = uniform(0)
|
|
54
|
+
this.widthUniform = uniform(1)
|
|
55
|
+
this.heightUniform = uniform(1)
|
|
56
|
+
this.thresholdUniform = uniform(0.25)
|
|
57
|
+
this.upperThresholdUniform = uniform(1)
|
|
58
|
+
this.directionUniform = uniform(0)
|
|
59
|
+
this.modeUniform = uniform(0)
|
|
60
|
+
this.reverseUniform = uniform(0)
|
|
61
|
+
|
|
62
|
+
const placeholder = new THREE.Texture()
|
|
63
|
+
|
|
64
|
+
// Compute pixel coordinates from flipped UVs
|
|
65
|
+
const texUv = vec2(uv().x, float(1).sub(uv().y))
|
|
66
|
+
const dims = vec2(this.widthUniform, this.heightUniform)
|
|
67
|
+
const pixelCoord = floor(texUv.mul(dims))
|
|
68
|
+
|
|
69
|
+
// Sort axis: horizontal (x) or vertical (y)
|
|
70
|
+
const isHorizontal = this.directionUniform.lessThan(float(0.5))
|
|
71
|
+
const sortIdx = select(isHorizontal, pixelCoord.x, pixelCoord.y)
|
|
72
|
+
const maxIdx = select(isHorizontal, this.widthUniform, this.heightUniform)
|
|
73
|
+
|
|
74
|
+
// Odd-even transposition: alternate pair groupings each pass
|
|
75
|
+
const pairMod = mod(sortIdx.add(this.passOffsetUniform), float(2))
|
|
76
|
+
const isLeft = pairMod.lessThan(float(1))
|
|
77
|
+
|
|
78
|
+
// Neighbor pixel coordinate
|
|
79
|
+
const neighborDir = select(isLeft, float(1), float(-1))
|
|
80
|
+
const neighborCoord = vec2(
|
|
81
|
+
select(isHorizontal, pixelCoord.x.add(neighborDir), pixelCoord.x),
|
|
82
|
+
select(isHorizontal, pixelCoord.y, pixelCoord.y.add(neighborDir)),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
// Bounds check
|
|
86
|
+
const neighborIdx = select(isHorizontal, neighborCoord.x, neighborCoord.y)
|
|
87
|
+
const inBounds = neighborIdx
|
|
88
|
+
.greaterThanEqual(float(0))
|
|
89
|
+
.and(neighborIdx.lessThan(maxIdx))
|
|
90
|
+
|
|
91
|
+
// Sample both pixels (snapped to texel centers)
|
|
92
|
+
const mySnapUv = pixelCoord.add(0.5).div(dims)
|
|
93
|
+
const neighborSnapUv = neighborCoord.add(0.5).div(dims)
|
|
94
|
+
|
|
95
|
+
this.sortTexNodeA = tslTexture(placeholder, mySnapUv)
|
|
96
|
+
this.sortTexNodeB = tslTexture(placeholder, neighborSnapUv)
|
|
97
|
+
|
|
98
|
+
const myColor = this.sortTexNodeA
|
|
99
|
+
const neighborColor = this.sortTexNodeB
|
|
100
|
+
|
|
101
|
+
// Sort value: luminance
|
|
102
|
+
const lumaW = vec3(0.2126, 0.7152, 0.0722)
|
|
103
|
+
const myLuma = dot(vec3(myColor.r, myColor.g, myColor.b), lumaW)
|
|
104
|
+
const neighborLuma = dot(
|
|
105
|
+
vec3(neighborColor.r, neighborColor.g, neighborColor.b),
|
|
106
|
+
lumaW,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
// Sort value: warmth (R-B, approximates hue/temperature)
|
|
110
|
+
const myWarmth = myColor.r.sub(myColor.b)
|
|
111
|
+
const neighborWarmth = neighborColor.r.sub(neighborColor.b)
|
|
112
|
+
|
|
113
|
+
// Sort value: saturation (HSV)
|
|
114
|
+
const myMax = max(max(myColor.r, myColor.g), myColor.b)
|
|
115
|
+
const myMin = min(min(myColor.r, myColor.g), myColor.b)
|
|
116
|
+
const mySat = select(
|
|
117
|
+
myMax.greaterThan(float(0.001)),
|
|
118
|
+
myMax.sub(myMin).div(myMax),
|
|
119
|
+
float(0),
|
|
120
|
+
)
|
|
121
|
+
const nMax = max(max(neighborColor.r, neighborColor.g), neighborColor.b)
|
|
122
|
+
const nMin = min(min(neighborColor.r, neighborColor.g), neighborColor.b)
|
|
123
|
+
const nSat = select(
|
|
124
|
+
nMax.greaterThan(float(0.001)),
|
|
125
|
+
nMax.sub(nMin).div(nMax),
|
|
126
|
+
float(0),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
// Select sort value based on mode (0=luma, 1=hue/warmth, 2=saturation)
|
|
130
|
+
const isSatMode = this.modeUniform.greaterThan(float(1.5))
|
|
131
|
+
const isHueMode = this.modeUniform.greaterThan(float(0.5))
|
|
132
|
+
const myValue = select(isSatMode, mySat, select(isHueMode, myWarmth, myLuma))
|
|
133
|
+
const neighborValue = select(
|
|
134
|
+
isSatMode,
|
|
135
|
+
nSat,
|
|
136
|
+
select(isHueMode, neighborWarmth, neighborLuma),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
// Validation: both pixels must be within threshold band (luma-based)
|
|
140
|
+
const myInBand = myLuma
|
|
141
|
+
.greaterThan(this.thresholdUniform)
|
|
142
|
+
.and(myLuma.lessThan(this.upperThresholdUniform))
|
|
143
|
+
const neighborInBand = neighborLuma
|
|
144
|
+
.greaterThan(this.thresholdUniform)
|
|
145
|
+
.and(neighborLuma.lessThan(this.upperThresholdUniform))
|
|
146
|
+
const valid = myInBand.or(neighborInBand)
|
|
147
|
+
|
|
148
|
+
// Sort: swap if left > right (ascending) or left < right (descending/reverse)
|
|
149
|
+
const leftValue = select(isLeft, myValue, neighborValue)
|
|
150
|
+
const rightValue = select(isLeft, neighborValue, myValue)
|
|
151
|
+
const isReverse = this.reverseUniform.greaterThan(float(0.5))
|
|
152
|
+
const shouldSwap = select(
|
|
153
|
+
isReverse,
|
|
154
|
+
rightValue.greaterThan(leftValue),
|
|
155
|
+
leftValue.greaterThan(rightValue),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
const doSwap = inBounds.and(valid).and(shouldSwap)
|
|
159
|
+
const result = vec4(
|
|
160
|
+
select(doSwap, neighborColor.r, myColor.r),
|
|
161
|
+
select(doSwap, neighborColor.g, myColor.g),
|
|
162
|
+
select(doSwap, neighborColor.b, myColor.b),
|
|
163
|
+
float(1),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
this.sortMaterial = new THREE.MeshBasicNodeMaterial()
|
|
167
|
+
this.sortMaterial.colorNode = result as Node
|
|
168
|
+
|
|
169
|
+
const sortMesh = new THREE.Mesh(
|
|
170
|
+
new THREE.PlaneGeometry(2, 2),
|
|
171
|
+
this.sortMaterial,
|
|
172
|
+
)
|
|
173
|
+
sortMesh.frustumCulled = false
|
|
174
|
+
this.sortScene.add(sortMesh)
|
|
175
|
+
|
|
176
|
+
// Internal ping-pong render targets
|
|
177
|
+
const rtOptions = {
|
|
178
|
+
depthBuffer: false,
|
|
179
|
+
format: THREE.RGBAFormat,
|
|
180
|
+
generateMipmaps: false,
|
|
181
|
+
magFilter: THREE.NearestFilter,
|
|
182
|
+
minFilter: THREE.NearestFilter,
|
|
183
|
+
stencilBuffer: false,
|
|
184
|
+
type: THREE.HalfFloatType,
|
|
185
|
+
}
|
|
186
|
+
this.sortRtA = new THREE.WebGLRenderTarget(1, 1, rtOptions)
|
|
187
|
+
this.sortRtB = new THREE.WebGLRenderTarget(1, 1, rtOptions)
|
|
188
|
+
|
|
189
|
+
// Blit node for PassNode pipeline
|
|
190
|
+
const blitUv = vec2(uv().x, float(1).sub(uv().y))
|
|
191
|
+
this.blitInputNode = tslTexture(new THREE.Texture(), blitUv)
|
|
192
|
+
|
|
193
|
+
this.rebuildEffectNode()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
override render(
|
|
197
|
+
renderer: THREE.WebGPURenderer,
|
|
198
|
+
inputTexture: THREE.Texture,
|
|
199
|
+
outputTarget: THREE.WebGLRenderTarget,
|
|
200
|
+
time: number,
|
|
201
|
+
delta: number,
|
|
202
|
+
): void {
|
|
203
|
+
let readTexture: THREE.Texture = inputTexture
|
|
204
|
+
let writeTarget = this.sortRtA
|
|
205
|
+
|
|
206
|
+
for (let i = 0; i < this.passCount; i++) {
|
|
207
|
+
this.passOffsetUniform.value = i % 2
|
|
208
|
+
this.sortTexNodeA.value = readTexture
|
|
209
|
+
this.sortTexNodeB.value = readTexture
|
|
210
|
+
renderer.setRenderTarget(writeTarget)
|
|
211
|
+
renderer.render(this.sortScene, this.sortCamera)
|
|
212
|
+
|
|
213
|
+
if (writeTarget === this.sortRtA) {
|
|
214
|
+
readTexture = this.sortRtA.texture
|
|
215
|
+
writeTarget = this.sortRtB
|
|
216
|
+
} else {
|
|
217
|
+
readTexture = this.sortRtB.texture
|
|
218
|
+
writeTarget = this.sortRtA
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
this.blitInputNode.value = readTexture
|
|
223
|
+
super.render(renderer, inputTexture, outputTarget, time, delta)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
override updateParams(params: LayerParameterValues): void {
|
|
227
|
+
this.thresholdUniform.value =
|
|
228
|
+
typeof params.threshold === "number" ? params.threshold : 0.25
|
|
229
|
+
|
|
230
|
+
this.upperThresholdUniform.value =
|
|
231
|
+
typeof params.upperThreshold === "number" ? params.upperThreshold : 1
|
|
232
|
+
|
|
233
|
+
this.directionUniform.value = params.direction === "vertical" ? 1 : 0
|
|
234
|
+
|
|
235
|
+
this.reverseUniform.value = params.reverse === true ? 1 : 0
|
|
236
|
+
|
|
237
|
+
if (params.mode === "hue") {
|
|
238
|
+
this.modeUniform.value = 1
|
|
239
|
+
} else if (params.mode === "saturation") {
|
|
240
|
+
this.modeUniform.value = 2
|
|
241
|
+
} else {
|
|
242
|
+
this.modeUniform.value = 0
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const range = typeof params.range === "number" ? params.range : 0.3
|
|
246
|
+
this.passCount = Math.max(1, Math.round(range * 300))
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
override resize(width: number, height: number): void {
|
|
250
|
+
this.width = Math.max(1, width)
|
|
251
|
+
this.height = Math.max(1, height)
|
|
252
|
+
this.widthUniform.value = this.width
|
|
253
|
+
this.heightUniform.value = this.height
|
|
254
|
+
this.sortRtA.setSize(this.width, this.height)
|
|
255
|
+
this.sortRtB.setSize(this.width, this.height)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
override dispose(): void {
|
|
259
|
+
this.sortRtA.dispose()
|
|
260
|
+
this.sortRtB.dispose()
|
|
261
|
+
this.sortMaterial.dispose()
|
|
262
|
+
super.dispose()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
protected override buildEffectNode(): Node {
|
|
266
|
+
if (!this.blitInputNode) {
|
|
267
|
+
return this.inputNode
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return vec4(
|
|
271
|
+
this.blitInputNode.r,
|
|
272
|
+
this.blitInputNode.g,
|
|
273
|
+
this.blitInputNode.b,
|
|
274
|
+
float(1),
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
}
|