@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,416 @@
|
|
|
1
|
+
import * as THREE from "three/webgpu"
|
|
2
|
+
import { bloom } from "three/examples/jsm/tsl/display/BloomNode.js"
|
|
3
|
+
import {
|
|
4
|
+
clamp,
|
|
5
|
+
float,
|
|
6
|
+
floor,
|
|
7
|
+
mix,
|
|
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 {
|
|
19
|
+
ASCII_CHARSETS,
|
|
20
|
+
buildAsciiAtlas,
|
|
21
|
+
type AsciiFontWeight,
|
|
22
|
+
DEFAULT_ASCII_CHARS,
|
|
23
|
+
} from "@/renderer/ascii-atlas"
|
|
24
|
+
import { PassNode } from "@/renderer/pass-node"
|
|
25
|
+
import type { LayerParameterValues } from "@/types/editor"
|
|
26
|
+
|
|
27
|
+
type Node = TSLNode
|
|
28
|
+
type AsciiColorMode = "green-terminal" | "monochrome" | "source"
|
|
29
|
+
type AsciiCharset = keyof typeof ASCII_CHARSETS | "custom"
|
|
30
|
+
|
|
31
|
+
function clamp01(value: number): number {
|
|
32
|
+
return Math.max(0, Math.min(1, value))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseCssColorRgb(value: string): [number, number, number] {
|
|
36
|
+
const rgba = value.match(
|
|
37
|
+
/rgba?\s*\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*[\d.]+)?\s*\)/i,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if (rgba) {
|
|
41
|
+
return [
|
|
42
|
+
clamp01(Number.parseFloat(rgba[1] ?? "0") / 255),
|
|
43
|
+
clamp01(Number.parseFloat(rgba[2] ?? "0") / 255),
|
|
44
|
+
clamp01(Number.parseFloat(rgba[3] ?? "0") / 255),
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const hex = value.trim().replace("#", "")
|
|
49
|
+
|
|
50
|
+
if (hex.length === 6) {
|
|
51
|
+
return [
|
|
52
|
+
Number.parseInt(hex.slice(0, 2), 16) / 255,
|
|
53
|
+
Number.parseInt(hex.slice(2, 4), 16) / 255,
|
|
54
|
+
Number.parseInt(hex.slice(4, 6), 16) / 255,
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (hex.length === 3) {
|
|
59
|
+
return [
|
|
60
|
+
Number.parseInt(`${hex[0] ?? "0"}${hex[0] ?? "0"}`, 16) / 255,
|
|
61
|
+
Number.parseInt(`${hex[1] ?? "0"}${hex[1] ?? "0"}`, 16) / 255,
|
|
62
|
+
Number.parseInt(`${hex[2] ?? "0"}${hex[2] ?? "0"}`, 16) / 255,
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return [1, 1, 1]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class AsciiPass extends PassNode {
|
|
70
|
+
private atlasTexture: THREE.CanvasTexture | null = null
|
|
71
|
+
private atlasTextureNodes: Node[] = []
|
|
72
|
+
private bloomEnabled = false
|
|
73
|
+
private bloomNode: ReturnType<typeof bloom> | null = null
|
|
74
|
+
private readonly bloomIntensityUniform: Node
|
|
75
|
+
private readonly bloomRadiusUniform: Node
|
|
76
|
+
private readonly bloomSoftnessUniform: Node
|
|
77
|
+
private readonly bloomThresholdUniform: Node
|
|
78
|
+
private readonly cellSizeUniform: Node
|
|
79
|
+
private readonly bgOpacityUniform: Node
|
|
80
|
+
private readonly colorModeUniform: Node
|
|
81
|
+
private readonly invertUniform: Node
|
|
82
|
+
private readonly monoBlueUniform: Node
|
|
83
|
+
private readonly monoGreenUniform: Node
|
|
84
|
+
private readonly logicalHeightUniform: Node
|
|
85
|
+
private readonly logicalWidthUniform: Node
|
|
86
|
+
private readonly monoRedUniform: Node
|
|
87
|
+
private readonly numCharsUniform: Node
|
|
88
|
+
private readonly placeholder: THREE.Texture
|
|
89
|
+
private sourceTextureNodes: Node[] = []
|
|
90
|
+
|
|
91
|
+
private currentCellSize = 12
|
|
92
|
+
private currentCharset: AsciiCharset = "light"
|
|
93
|
+
private currentCustomChars = DEFAULT_ASCII_CHARS
|
|
94
|
+
private currentFontWeight: AsciiFontWeight = "regular"
|
|
95
|
+
|
|
96
|
+
constructor(layerId: string) {
|
|
97
|
+
super(layerId)
|
|
98
|
+
this.placeholder = new THREE.Texture()
|
|
99
|
+
this.bloomIntensityUniform = uniform(1.25)
|
|
100
|
+
this.bloomRadiusUniform = uniform(6)
|
|
101
|
+
this.bloomSoftnessUniform = uniform(0.35)
|
|
102
|
+
this.bloomThresholdUniform = uniform(0.6)
|
|
103
|
+
this.cellSizeUniform = uniform(12)
|
|
104
|
+
this.logicalWidthUniform = uniform(1)
|
|
105
|
+
this.logicalHeightUniform = uniform(1)
|
|
106
|
+
this.numCharsUniform = uniform(DEFAULT_ASCII_CHARS.length)
|
|
107
|
+
this.colorModeUniform = uniform(1)
|
|
108
|
+
this.monoRedUniform = uniform(0.96)
|
|
109
|
+
this.monoGreenUniform = uniform(0.96)
|
|
110
|
+
this.monoBlueUniform = uniform(0.94)
|
|
111
|
+
this.bgOpacityUniform = uniform(0)
|
|
112
|
+
this.invertUniform = uniform(0)
|
|
113
|
+
this.atlasTexture = buildAsciiAtlas(DEFAULT_ASCII_CHARS, "regular", this.currentCellSize)
|
|
114
|
+
this.rebuildEffectNode()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override render(
|
|
118
|
+
renderer: THREE.WebGPURenderer,
|
|
119
|
+
inputTexture: THREE.Texture,
|
|
120
|
+
outputTarget: THREE.WebGLRenderTarget,
|
|
121
|
+
time: number,
|
|
122
|
+
delta: number,
|
|
123
|
+
): void {
|
|
124
|
+
for (const sourceTextureNode of this.sourceTextureNodes) {
|
|
125
|
+
sourceTextureNode.value = inputTexture
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (this.atlasTexture) {
|
|
129
|
+
for (const atlasTextureNode of this.atlasTextureNodes) {
|
|
130
|
+
atlasTextureNode.value = this.atlasTexture
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
super.render(renderer, inputTexture, outputTarget, time, delta)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
override updateParams(params: LayerParameterValues): void {
|
|
138
|
+
const nextCellSize =
|
|
139
|
+
typeof params.cellSize === "number" ? Math.max(4, Math.round(params.cellSize)) : 12
|
|
140
|
+
const nextCharset = this.resolveCharset(params.charset)
|
|
141
|
+
const nextCustomChars =
|
|
142
|
+
typeof params.customChars === "string" ? params.customChars : DEFAULT_ASCII_CHARS
|
|
143
|
+
const nextFontWeight = this.resolveFontWeight(params.fontWeight)
|
|
144
|
+
const nextColorMode = this.resolveColorMode(params.colorMode)
|
|
145
|
+
const nextBgOpacity =
|
|
146
|
+
typeof params.bgOpacity === "number" ? clamp01(params.bgOpacity) : 0
|
|
147
|
+
const nextBloomEnabled = params.bloomEnabled === true
|
|
148
|
+
const nextBloomIntensity =
|
|
149
|
+
typeof params.bloomIntensity === "number" ? Math.max(0, params.bloomIntensity) : 1.25
|
|
150
|
+
const nextBloomThreshold =
|
|
151
|
+
typeof params.bloomThreshold === "number" ? clamp01(params.bloomThreshold) : 0.6
|
|
152
|
+
const nextBloomRadius =
|
|
153
|
+
typeof params.bloomRadius === "number" ? Math.max(0, params.bloomRadius) : 6
|
|
154
|
+
const nextBloomSoftness =
|
|
155
|
+
typeof params.bloomSoftness === "number" ? clamp01(params.bloomSoftness) : 0.35
|
|
156
|
+
const [red, green, blue] = parseCssColorRgb(
|
|
157
|
+
typeof params.monoColor === "string" ? params.monoColor : "#f5f5f0",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
this.cellSizeUniform.value = nextCellSize
|
|
161
|
+
this.bgOpacityUniform.value = nextBgOpacity
|
|
162
|
+
this.bloomIntensityUniform.value = nextBloomIntensity
|
|
163
|
+
this.bloomRadiusUniform.value = nextBloomRadius
|
|
164
|
+
this.bloomSoftnessUniform.value = nextBloomSoftness
|
|
165
|
+
this.bloomThresholdUniform.value = nextBloomThreshold
|
|
166
|
+
this.invertUniform.value = params.invert === true ? 1 : 0
|
|
167
|
+
this.monoRedUniform.value = red
|
|
168
|
+
this.monoGreenUniform.value = green
|
|
169
|
+
this.monoBlueUniform.value = blue
|
|
170
|
+
|
|
171
|
+
this.colorModeUniform.value = this.getColorModeValue(nextColorMode)
|
|
172
|
+
|
|
173
|
+
const needsAtlasRebuild =
|
|
174
|
+
nextCellSize !== this.currentCellSize ||
|
|
175
|
+
nextCharset !== this.currentCharset ||
|
|
176
|
+
nextFontWeight !== this.currentFontWeight ||
|
|
177
|
+
(nextCharset === "custom" && nextCustomChars !== this.currentCustomChars)
|
|
178
|
+
|
|
179
|
+
this.currentCellSize = nextCellSize
|
|
180
|
+
this.currentCharset = nextCharset
|
|
181
|
+
this.currentCustomChars = nextCustomChars
|
|
182
|
+
this.currentFontWeight = nextFontWeight
|
|
183
|
+
|
|
184
|
+
if (nextBloomEnabled !== this.bloomEnabled) {
|
|
185
|
+
this.bloomEnabled = nextBloomEnabled
|
|
186
|
+
this.rebuildEffectNode()
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (this.bloomNode) {
|
|
191
|
+
this.bloomNode.strength.value = nextBloomIntensity
|
|
192
|
+
this.bloomNode.radius.value = this.normalizeBloomRadius(nextBloomRadius)
|
|
193
|
+
this.bloomNode.threshold.value = nextBloomThreshold
|
|
194
|
+
this.bloomNode.smoothWidth.value = this.normalizeBloomSoftness(nextBloomSoftness)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (needsAtlasRebuild) {
|
|
198
|
+
this.rebuildAtlas()
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
override dispose(): void {
|
|
203
|
+
this.disposeBloomNode()
|
|
204
|
+
this.placeholder.dispose()
|
|
205
|
+
this.atlasTexture?.dispose()
|
|
206
|
+
super.dispose()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
override updateLogicalSize(width: number, height: number): void {
|
|
210
|
+
this.logicalWidthUniform.value = Math.max(1, width)
|
|
211
|
+
this.logicalHeightUniform.value = Math.max(1, height)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
protected override buildEffectNode(): Node {
|
|
215
|
+
if (!(this.cellSizeUniform && this.numCharsUniform && this.placeholder)) {
|
|
216
|
+
return this.inputNode
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.disposeBloomNode()
|
|
220
|
+
this.bloomNode = null
|
|
221
|
+
this.sourceTextureNodes = []
|
|
222
|
+
this.atlasTextureNodes = []
|
|
223
|
+
|
|
224
|
+
const renderTargetUv = vec2(uv().x, float(1).sub(uv().y))
|
|
225
|
+
const logicalScreenSize = vec2(this.logicalWidthUniform, this.logicalHeightUniform)
|
|
226
|
+
const normalizedCellSize = vec2(this.cellSizeUniform, this.cellSizeUniform).div(
|
|
227
|
+
logicalScreenSize,
|
|
228
|
+
)
|
|
229
|
+
const sampleAscii = (sampleUv: Node) => {
|
|
230
|
+
const safeUv = clamp(sampleUv, vec2(float(0), float(0)), vec2(float(1), float(1)))
|
|
231
|
+
const screenPixel = floor(safeUv.mul(logicalScreenSize))
|
|
232
|
+
const cellCenterUv = floor(safeUv.div(normalizedCellSize))
|
|
233
|
+
.add(vec2(0.5, 0.5))
|
|
234
|
+
.mul(normalizedCellSize)
|
|
235
|
+
const localCellPixel = vec2(
|
|
236
|
+
mod(screenPixel.x, this.cellSizeUniform),
|
|
237
|
+
mod(screenPixel.y, this.cellSizeUniform),
|
|
238
|
+
)
|
|
239
|
+
const sampledColor = this.trackSourceTextureNode(cellCenterUv)
|
|
240
|
+
const luma = float(sampledColor.r)
|
|
241
|
+
.mul(float(0.2126))
|
|
242
|
+
.add(float(sampledColor.g).mul(float(0.7152)))
|
|
243
|
+
.add(float(sampledColor.b).mul(float(0.0722)))
|
|
244
|
+
const adjustedLuma = select(
|
|
245
|
+
this.invertUniform.greaterThan(float(0.5)),
|
|
246
|
+
float(1).sub(luma),
|
|
247
|
+
luma,
|
|
248
|
+
)
|
|
249
|
+
const charIndex = floor(
|
|
250
|
+
clamp(
|
|
251
|
+
adjustedLuma.mul(this.numCharsUniform.sub(float(1))),
|
|
252
|
+
float(0),
|
|
253
|
+
this.numCharsUniform.sub(float(1)),
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
const atlasUv = vec2(
|
|
257
|
+
charIndex
|
|
258
|
+
.mul(this.cellSizeUniform)
|
|
259
|
+
.add(localCellPixel.x)
|
|
260
|
+
.add(float(0.5))
|
|
261
|
+
.div(this.numCharsUniform.mul(this.cellSizeUniform)),
|
|
262
|
+
localCellPixel.y.add(float(0.5)).div(this.cellSizeUniform),
|
|
263
|
+
)
|
|
264
|
+
const characterMask = float(this.trackAtlasTextureNode(atlasUv).r)
|
|
265
|
+
const sourceColor = vec3(
|
|
266
|
+
float(sampledColor.r),
|
|
267
|
+
float(sampledColor.g),
|
|
268
|
+
float(sampledColor.b),
|
|
269
|
+
)
|
|
270
|
+
const monoTint = vec3(
|
|
271
|
+
this.monoRedUniform,
|
|
272
|
+
this.monoGreenUniform,
|
|
273
|
+
this.monoBlueUniform,
|
|
274
|
+
)
|
|
275
|
+
const monochromeColor = monoTint.mul(adjustedLuma)
|
|
276
|
+
const greenTerminalColor = vec3(float(0), adjustedLuma, float(0))
|
|
277
|
+
const glyphColor = select(
|
|
278
|
+
this.colorModeUniform.lessThan(float(0.5)),
|
|
279
|
+
sourceColor,
|
|
280
|
+
select(
|
|
281
|
+
this.colorModeUniform.lessThan(float(1.5)),
|
|
282
|
+
monochromeColor,
|
|
283
|
+
greenTerminalColor,
|
|
284
|
+
),
|
|
285
|
+
)
|
|
286
|
+
const sourceBackground = sourceColor.mul(this.bgOpacityUniform)
|
|
287
|
+
const backgroundColor = select(
|
|
288
|
+
this.colorModeUniform.lessThan(float(0.5)),
|
|
289
|
+
sourceBackground,
|
|
290
|
+
vec3(float(0), float(0), float(0)),
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
baseColor: mix(backgroundColor, glyphColor, characterMask),
|
|
295
|
+
emissiveColor: glyphColor.mul(characterMask),
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const baseSample = sampleAscii(renderTargetUv)
|
|
300
|
+
|
|
301
|
+
if (!this.bloomEnabled) {
|
|
302
|
+
return vec4(baseSample.baseColor, float(1))
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const bloomInput = vec4(baseSample.emissiveColor, float(1))
|
|
306
|
+
this.bloomNode = bloom(
|
|
307
|
+
bloomInput,
|
|
308
|
+
this.bloomIntensityUniform.value as number,
|
|
309
|
+
this.normalizeBloomRadius(this.bloomRadiusUniform.value as number),
|
|
310
|
+
this.bloomThresholdUniform.value as number,
|
|
311
|
+
)
|
|
312
|
+
this.bloomNode.smoothWidth.value = this.normalizeBloomSoftness(
|
|
313
|
+
this.bloomSoftnessUniform.value as number,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
return vec4(
|
|
317
|
+
clamp(
|
|
318
|
+
baseSample.baseColor.add(this.getBloomTextureNode().rgb),
|
|
319
|
+
vec3(float(0), float(0), float(0)),
|
|
320
|
+
vec3(float(1), float(1), float(1)),
|
|
321
|
+
),
|
|
322
|
+
float(1),
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private getActiveChars(): string {
|
|
327
|
+
return this.currentCharset === "custom"
|
|
328
|
+
? this.currentCustomChars || " "
|
|
329
|
+
: ASCII_CHARSETS[this.currentCharset] ?? DEFAULT_ASCII_CHARS
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private getColorModeValue(colorMode: AsciiColorMode): number {
|
|
333
|
+
switch (colorMode) {
|
|
334
|
+
case "source":
|
|
335
|
+
return 0
|
|
336
|
+
case "green-terminal":
|
|
337
|
+
return 2
|
|
338
|
+
default:
|
|
339
|
+
return 1
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private rebuildAtlas(): void {
|
|
344
|
+
const chars = this.getActiveChars()
|
|
345
|
+
this.atlasTexture?.dispose()
|
|
346
|
+
this.atlasTexture = buildAsciiAtlas(chars, this.currentFontWeight, this.currentCellSize)
|
|
347
|
+
this.numCharsUniform.value = chars.length
|
|
348
|
+
this.rebuildEffectNode()
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private resolveCharset(value: unknown): AsciiCharset {
|
|
352
|
+
return value === "binary" ||
|
|
353
|
+
value === "blocks" ||
|
|
354
|
+
value === "custom" ||
|
|
355
|
+
value === "dense" ||
|
|
356
|
+
value === "hatching" ||
|
|
357
|
+
value === "light"
|
|
358
|
+
? value
|
|
359
|
+
: "light"
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private resolveColorMode(value: unknown): AsciiColorMode {
|
|
363
|
+
return value === "green-terminal" || value === "source" ? value : "monochrome"
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private resolveFontWeight(value: unknown): AsciiFontWeight {
|
|
367
|
+
return value === "bold" || value === "thin" ? value : "regular"
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private normalizeBloomRadius(value: number): number {
|
|
371
|
+
return clamp01(value / 24)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private normalizeBloomSoftness(value: number): number {
|
|
375
|
+
return Math.max(0.001, value * 0.25)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private disposeBloomNode(): void {
|
|
379
|
+
;(this.bloomNode as { dispose?: () => void } | null)?.dispose?.()
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private getBloomTextureNode(): Node {
|
|
383
|
+
const bloomNode = this.bloomNode as
|
|
384
|
+
| ({
|
|
385
|
+
getTexture?: () => Node
|
|
386
|
+
getTextureNode?: () => Node
|
|
387
|
+
} & object)
|
|
388
|
+
| null
|
|
389
|
+
|
|
390
|
+
if (!bloomNode) {
|
|
391
|
+
throw new Error("Bloom node is not initialized")
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if ("getTextureNode" in bloomNode && typeof bloomNode.getTextureNode === "function") {
|
|
395
|
+
return bloomNode.getTextureNode()
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if ("getTexture" in bloomNode && typeof bloomNode.getTexture === "function") {
|
|
399
|
+
return bloomNode.getTexture()
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
throw new Error("Bloom node does not expose a texture getter")
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private trackAtlasTextureNode(uvNode: Node): Node {
|
|
406
|
+
const atlasTextureNode = tslTexture(this.atlasTexture ?? new THREE.Texture(), uvNode)
|
|
407
|
+
this.atlasTextureNodes.push(atlasTextureNode)
|
|
408
|
+
return atlasTextureNode
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private trackSourceTextureNode(uvNode: Node): Node {
|
|
412
|
+
const sourceTextureNode = tslTexture(this.placeholder, uvNode)
|
|
413
|
+
this.sourceTextureNodes.push(sourceTextureNode)
|
|
414
|
+
return sourceTextureNode
|
|
415
|
+
}
|
|
416
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import {
|
|
2
|
+
abs,
|
|
3
|
+
clamp,
|
|
4
|
+
dot,
|
|
5
|
+
float,
|
|
6
|
+
max,
|
|
7
|
+
min,
|
|
8
|
+
mix,
|
|
9
|
+
select,
|
|
10
|
+
sqrt,
|
|
11
|
+
type TSLNode,
|
|
12
|
+
vec3,
|
|
13
|
+
vec4,
|
|
14
|
+
} from "three/tsl"
|
|
15
|
+
|
|
16
|
+
type Node = TSLNode
|
|
17
|
+
|
|
18
|
+
function normal(_base: Node, blend: Node): Node {
|
|
19
|
+
return blend
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function multiply(base: Node, blend: Node): Node {
|
|
23
|
+
return base.mul(blend)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function screen(base: Node, blend: Node): Node {
|
|
27
|
+
return float(1).sub(float(1).sub(base).mul(float(1).sub(blend)))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function overlay(base: Node, blend: Node): Node {
|
|
31
|
+
const dark = float(2).mul(base).mul(blend)
|
|
32
|
+
const light = float(1).sub(
|
|
33
|
+
float(2).mul(float(1).sub(base)).mul(float(1).sub(blend)),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return select(base.lessThan(float(0.5)), dark, light)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function darken(base: Node, blend: Node): Node {
|
|
40
|
+
return min(base, blend)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function lighten(base: Node, blend: Node): Node {
|
|
44
|
+
return max(base, blend)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function colorDodge(base: Node, blend: Node): Node {
|
|
48
|
+
return clamp(base.div(max(float(1).sub(blend), float(1e-6))), vec3(0), vec3(1))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function colorBurn(base: Node, blend: Node): Node {
|
|
52
|
+
return clamp(
|
|
53
|
+
float(1).sub(float(1).sub(base).div(max(blend, float(1e-6)))),
|
|
54
|
+
vec3(0),
|
|
55
|
+
vec3(1),
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function hardLight(base: Node, blend: Node): Node {
|
|
60
|
+
return overlay(blend, base)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function softLight(base: Node, blend: Node): Node {
|
|
64
|
+
const darkResult = base.sub(
|
|
65
|
+
float(1).sub(float(2).mul(blend)).mul(base).mul(float(1).sub(base)),
|
|
66
|
+
)
|
|
67
|
+
const twoBlendMinusOne = float(2).mul(blend).sub(float(1))
|
|
68
|
+
const dLow = float(16).mul(base).sub(float(12)).mul(base).add(float(4)).mul(base)
|
|
69
|
+
const dHigh = sqrt(base)
|
|
70
|
+
const d = select(base.lessThanEqual(float(0.25)), dLow, dHigh)
|
|
71
|
+
const lightResult = base.add(twoBlendMinusOne.mul(d.sub(base)))
|
|
72
|
+
|
|
73
|
+
return select(blend.lessThanEqual(float(0.5)), darkResult, lightResult)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function difference(base: Node, blend: Node): Node {
|
|
77
|
+
return abs(base.sub(blend))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function exclusion(base: Node, blend: Node): Node {
|
|
81
|
+
return base.add(blend).sub(float(2).mul(base).mul(blend))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function lum(color: Node): Node {
|
|
85
|
+
return dot(color, vec3(0.2126, 0.7152, 0.0722))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function clipColor(color: Node): Node {
|
|
89
|
+
const luma = lum(color)
|
|
90
|
+
const colorMin = min(color.x, min(color.y, color.z))
|
|
91
|
+
const lowClipped = color
|
|
92
|
+
.sub(luma)
|
|
93
|
+
.mul(luma.div(max(luma.sub(colorMin), float(1e-6))))
|
|
94
|
+
.add(luma)
|
|
95
|
+
const lowAdjusted = select(colorMin.lessThan(float(0)), lowClipped, color)
|
|
96
|
+
const maxAfterLow = max(lowAdjusted.x, max(lowAdjusted.y, lowAdjusted.z))
|
|
97
|
+
const excess = maxAfterLow.sub(float(1))
|
|
98
|
+
const highClipped = lowAdjusted
|
|
99
|
+
.sub(luma)
|
|
100
|
+
.mul(float(1).sub(luma).div(max(excess, float(1e-6))))
|
|
101
|
+
.add(luma)
|
|
102
|
+
|
|
103
|
+
return select(maxAfterLow.greaterThan(float(1)), highClipped, lowAdjusted)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function setLum(color: Node, nextLum: Node): Node {
|
|
107
|
+
return clipColor(color.add(nextLum.sub(lum(color))))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function sat(color: Node): Node {
|
|
111
|
+
return max(color.x, max(color.y, color.z)).sub(min(color.x, min(color.y, color.z)))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function setSat(color: Node, nextSat: Node): Node {
|
|
115
|
+
const red = color.x
|
|
116
|
+
const green = color.y
|
|
117
|
+
const blue = color.z
|
|
118
|
+
const colorMin = min(red, min(green, blue))
|
|
119
|
+
const colorMax = max(red, max(green, blue))
|
|
120
|
+
const delta = colorMax.sub(colorMin)
|
|
121
|
+
const scale = select(delta.greaterThan(float(0)), nextSat.div(delta), float(0))
|
|
122
|
+
const redOut = select(
|
|
123
|
+
red.lessThanEqual(colorMin),
|
|
124
|
+
float(0),
|
|
125
|
+
select(red.greaterThanEqual(colorMax), nextSat, red.sub(colorMin).mul(scale)),
|
|
126
|
+
)
|
|
127
|
+
const greenOut = select(
|
|
128
|
+
green.lessThanEqual(colorMin),
|
|
129
|
+
float(0),
|
|
130
|
+
select(green.greaterThanEqual(colorMax), nextSat, green.sub(colorMin).mul(scale)),
|
|
131
|
+
)
|
|
132
|
+
const blueOut = select(
|
|
133
|
+
blue.lessThanEqual(colorMin),
|
|
134
|
+
float(0),
|
|
135
|
+
select(blue.greaterThanEqual(colorMax), nextSat, blue.sub(colorMin).mul(scale)),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return vec3(redOut, greenOut, blueOut)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function hue(base: Node, blend: Node): Node {
|
|
142
|
+
return setLum(setSat(blend, sat(base)), lum(base))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function saturation(base: Node, blend: Node): Node {
|
|
146
|
+
return setLum(setSat(base, sat(blend)), lum(base))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function color(base: Node, blend: Node): Node {
|
|
150
|
+
return setLum(blend, lum(base))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function luminosity(base: Node, blend: Node): Node {
|
|
154
|
+
return setLum(base, lum(blend))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function buildBlendNode(
|
|
158
|
+
mode: string,
|
|
159
|
+
base: Node,
|
|
160
|
+
blend: Node,
|
|
161
|
+
opacity: Node,
|
|
162
|
+
compositeMode: "filter" | "mask" = "filter",
|
|
163
|
+
): Node {
|
|
164
|
+
const baseRgb = base.rgb
|
|
165
|
+
const blendRgb = blend.rgb
|
|
166
|
+
const normalizedOpacity = float(clamp(opacity, float(0), float(1)))
|
|
167
|
+
|
|
168
|
+
let composited: Node
|
|
169
|
+
|
|
170
|
+
switch (mode) {
|
|
171
|
+
case "multiply":
|
|
172
|
+
composited = multiply(baseRgb, blendRgb)
|
|
173
|
+
break
|
|
174
|
+
case "screen":
|
|
175
|
+
composited = screen(baseRgb, blendRgb)
|
|
176
|
+
break
|
|
177
|
+
case "overlay":
|
|
178
|
+
composited = overlay(baseRgb, blendRgb)
|
|
179
|
+
break
|
|
180
|
+
case "darken":
|
|
181
|
+
composited = darken(baseRgb, blendRgb)
|
|
182
|
+
break
|
|
183
|
+
case "lighten":
|
|
184
|
+
composited = lighten(baseRgb, blendRgb)
|
|
185
|
+
break
|
|
186
|
+
case "color-dodge":
|
|
187
|
+
composited = colorDodge(baseRgb, blendRgb)
|
|
188
|
+
break
|
|
189
|
+
case "color-burn":
|
|
190
|
+
composited = colorBurn(baseRgb, blendRgb)
|
|
191
|
+
break
|
|
192
|
+
case "hard-light":
|
|
193
|
+
composited = hardLight(baseRgb, blendRgb)
|
|
194
|
+
break
|
|
195
|
+
case "soft-light":
|
|
196
|
+
composited = softLight(baseRgb, blendRgb)
|
|
197
|
+
break
|
|
198
|
+
case "difference":
|
|
199
|
+
composited = difference(baseRgb, blendRgb)
|
|
200
|
+
break
|
|
201
|
+
case "exclusion":
|
|
202
|
+
composited = exclusion(baseRgb, blendRgb)
|
|
203
|
+
break
|
|
204
|
+
case "hue":
|
|
205
|
+
composited = hue(baseRgb, blendRgb)
|
|
206
|
+
break
|
|
207
|
+
case "saturation":
|
|
208
|
+
composited = saturation(baseRgb, blendRgb)
|
|
209
|
+
break
|
|
210
|
+
case "color":
|
|
211
|
+
composited = color(baseRgb, blendRgb)
|
|
212
|
+
break
|
|
213
|
+
case "luminosity":
|
|
214
|
+
composited = luminosity(baseRgb, blendRgb)
|
|
215
|
+
break
|
|
216
|
+
default:
|
|
217
|
+
composited = normal(baseRgb, blendRgb)
|
|
218
|
+
break
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (compositeMode === "filter") {
|
|
222
|
+
return vec4(mix(baseRgb, composited, normalizedOpacity), float(1))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const maskLuma = float(dot(blendRgb, vec3(0.2126, 0.7152, 0.0722)))
|
|
226
|
+
const maskStrength = mix(float(1), clamp(maskLuma, float(0), float(1)), normalizedOpacity)
|
|
227
|
+
|
|
228
|
+
return vec4(baseRgb.mul(maskStrength), float(1))
|
|
229
|
+
}
|