@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,1040 @@
|
|
|
1
|
+
import * as THREE from "three/webgpu"
|
|
2
|
+
import { bloom } from "three/examples/jsm/tsl/display/BloomNode.js"
|
|
3
|
+
import {
|
|
4
|
+
abs,
|
|
5
|
+
clamp,
|
|
6
|
+
dot,
|
|
7
|
+
float,
|
|
8
|
+
floor,
|
|
9
|
+
fract,
|
|
10
|
+
max,
|
|
11
|
+
min,
|
|
12
|
+
mix,
|
|
13
|
+
mod,
|
|
14
|
+
pow,
|
|
15
|
+
select,
|
|
16
|
+
smoothstep,
|
|
17
|
+
texture as tslTexture,
|
|
18
|
+
type TSLNode,
|
|
19
|
+
uniform,
|
|
20
|
+
uv,
|
|
21
|
+
vec2,
|
|
22
|
+
vec3,
|
|
23
|
+
vec4,
|
|
24
|
+
} from "three/tsl"
|
|
25
|
+
import { simplexNoise3d } from "./shaders/tsl/noise/simplex-noise-3d"
|
|
26
|
+
import { PassNode } from "./pass-node"
|
|
27
|
+
import type { LayerParameterValues } from "../types/editor"
|
|
28
|
+
|
|
29
|
+
type Node = TSLNode
|
|
30
|
+
|
|
31
|
+
const CRT_MODE_SLOT_MASK = 0
|
|
32
|
+
const CRT_MODE_APERTURE_GRILLE = 1
|
|
33
|
+
const CRT_MODE_COMPOSITE_TV = 2
|
|
34
|
+
|
|
35
|
+
const HISTORY_TARGET_OPTIONS = {
|
|
36
|
+
depthBuffer: false,
|
|
37
|
+
format: THREE.RGBAFormat,
|
|
38
|
+
generateMipmaps: false,
|
|
39
|
+
magFilter: THREE.LinearFilter,
|
|
40
|
+
minFilter: THREE.LinearFilter,
|
|
41
|
+
stencilBuffer: false,
|
|
42
|
+
type: THREE.HalfFloatType,
|
|
43
|
+
} as const
|
|
44
|
+
|
|
45
|
+
function clamp01(value: number): number {
|
|
46
|
+
return Math.max(0, Math.min(1, value))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function toModeValue(value: unknown): number {
|
|
50
|
+
switch (value) {
|
|
51
|
+
case "aperture-grille":
|
|
52
|
+
return CRT_MODE_APERTURE_GRILLE
|
|
53
|
+
case "composite-tv":
|
|
54
|
+
return CRT_MODE_COMPOSITE_TV
|
|
55
|
+
default:
|
|
56
|
+
return CRT_MODE_SLOT_MASK
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class CrtPass extends PassNode {
|
|
61
|
+
private bloomEnabled = true
|
|
62
|
+
private bloomNode: ReturnType<typeof bloom> | null = null
|
|
63
|
+
private readonly bloomIntensityUniform: Node
|
|
64
|
+
private readonly bloomRadiusUniform: Node
|
|
65
|
+
private readonly bloomSoftnessUniform: Node
|
|
66
|
+
private readonly bloomThresholdUniform: Node
|
|
67
|
+
|
|
68
|
+
private readonly crtModeUniform: Node
|
|
69
|
+
private readonly cellSizeUniform: Node
|
|
70
|
+
private readonly scanlineIntensityUniform: Node
|
|
71
|
+
private readonly maskIntensityUniform: Node
|
|
72
|
+
private readonly barrelDistortionUniform: Node
|
|
73
|
+
private readonly chromaticAberrationUniform: Node
|
|
74
|
+
private readonly beamFocusUniform: Node
|
|
75
|
+
private readonly brightnessUniform: Node
|
|
76
|
+
private readonly highlightDriveUniform: Node
|
|
77
|
+
private readonly highlightThresholdUniform: Node
|
|
78
|
+
private readonly shoulderUniform: Node
|
|
79
|
+
private readonly chromaRetentionUniform: Node
|
|
80
|
+
private readonly shadowLiftUniform: Node
|
|
81
|
+
private readonly persistenceUniform: Node
|
|
82
|
+
private readonly vignetteIntensityUniform: Node
|
|
83
|
+
private readonly flickerIntensityUniform: Node
|
|
84
|
+
private readonly glitchIntensityUniform: Node
|
|
85
|
+
private readonly glitchSpeedUniform: Node
|
|
86
|
+
private readonly signalArtifactsUniform: Node
|
|
87
|
+
private readonly widthUniform: Node
|
|
88
|
+
private readonly heightUniform: Node
|
|
89
|
+
private readonly timeUniform: Node
|
|
90
|
+
|
|
91
|
+
private readonly placeholder: THREE.Texture
|
|
92
|
+
private historyReadTarget: THREE.WebGLRenderTarget
|
|
93
|
+
private historyWriteTarget: THREE.WebGLRenderTarget
|
|
94
|
+
private historyValid = false
|
|
95
|
+
|
|
96
|
+
private sourceTextureNodes: Node[] = []
|
|
97
|
+
private historyTextureNodes: Node[] = []
|
|
98
|
+
private renderWidth = 1
|
|
99
|
+
private renderHeight = 1
|
|
100
|
+
|
|
101
|
+
constructor(layerId: string) {
|
|
102
|
+
super(layerId)
|
|
103
|
+
|
|
104
|
+
this.placeholder = new THREE.Texture()
|
|
105
|
+
this.historyReadTarget = new THREE.WebGLRenderTarget(1, 1, HISTORY_TARGET_OPTIONS)
|
|
106
|
+
this.historyWriteTarget = new THREE.WebGLRenderTarget(1, 1, HISTORY_TARGET_OPTIONS)
|
|
107
|
+
|
|
108
|
+
this.crtModeUniform = uniform(CRT_MODE_SLOT_MASK)
|
|
109
|
+
this.cellSizeUniform = uniform(3)
|
|
110
|
+
this.scanlineIntensityUniform = uniform(0.17)
|
|
111
|
+
this.maskIntensityUniform = uniform(1)
|
|
112
|
+
this.barrelDistortionUniform = uniform(0.15)
|
|
113
|
+
this.chromaticAberrationUniform = uniform(2)
|
|
114
|
+
this.beamFocusUniform = uniform(0.58)
|
|
115
|
+
this.brightnessUniform = uniform(1.8)
|
|
116
|
+
this.highlightDriveUniform = uniform(1)
|
|
117
|
+
this.highlightThresholdUniform = uniform(0.62)
|
|
118
|
+
this.shoulderUniform = uniform(0.25)
|
|
119
|
+
this.chromaRetentionUniform = uniform(1.15)
|
|
120
|
+
this.shadowLiftUniform = uniform(0.16)
|
|
121
|
+
this.persistenceUniform = uniform(0.18)
|
|
122
|
+
this.vignetteIntensityUniform = uniform(0.45)
|
|
123
|
+
this.flickerIntensityUniform = uniform(0.2)
|
|
124
|
+
this.glitchIntensityUniform = uniform(0.13)
|
|
125
|
+
this.glitchSpeedUniform = uniform(5)
|
|
126
|
+
this.signalArtifactsUniform = uniform(0.45)
|
|
127
|
+
this.widthUniform = uniform(1)
|
|
128
|
+
this.heightUniform = uniform(1)
|
|
129
|
+
this.timeUniform = uniform(0)
|
|
130
|
+
|
|
131
|
+
this.bloomIntensityUniform = uniform(1.93)
|
|
132
|
+
this.bloomRadiusUniform = uniform(8)
|
|
133
|
+
this.bloomSoftnessUniform = uniform(0.31)
|
|
134
|
+
this.bloomThresholdUniform = uniform(0)
|
|
135
|
+
|
|
136
|
+
this.rebuildEffectNode()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
override render(
|
|
140
|
+
renderer: THREE.WebGPURenderer,
|
|
141
|
+
inputTexture: THREE.Texture,
|
|
142
|
+
outputTarget: THREE.WebGLRenderTarget,
|
|
143
|
+
time: number,
|
|
144
|
+
delta: number,
|
|
145
|
+
): void {
|
|
146
|
+
this.inputNode.value = inputTexture
|
|
147
|
+
|
|
148
|
+
for (const node of this.sourceTextureNodes) {
|
|
149
|
+
node.value = inputTexture
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const historyTexture = this.historyValid ? this.historyReadTarget.texture : this.placeholder
|
|
153
|
+
for (const node of this.historyTextureNodes) {
|
|
154
|
+
node.value = historyTexture
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.beforeRender(time, delta)
|
|
158
|
+
|
|
159
|
+
renderer.setRenderTarget(outputTarget)
|
|
160
|
+
renderer.render(this.scene, this.camera)
|
|
161
|
+
|
|
162
|
+
renderer.setRenderTarget(this.historyWriteTarget)
|
|
163
|
+
renderer.render(this.scene, this.camera)
|
|
164
|
+
|
|
165
|
+
const previousRead = this.historyReadTarget
|
|
166
|
+
this.historyReadTarget = this.historyWriteTarget
|
|
167
|
+
this.historyWriteTarget = previousRead
|
|
168
|
+
this.historyValid = true
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
protected override beforeRender(time: number, _delta: number): void {
|
|
172
|
+
this.timeUniform.value = time
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
override needsContinuousRender(): boolean {
|
|
176
|
+
const modeValue = this.crtModeUniform.value as number
|
|
177
|
+
return (
|
|
178
|
+
(this.flickerIntensityUniform.value as number) > 0 ||
|
|
179
|
+
(this.glitchIntensityUniform.value as number) > 0 ||
|
|
180
|
+
(this.persistenceUniform.value as number) > 0 ||
|
|
181
|
+
(modeValue === CRT_MODE_COMPOSITE_TV &&
|
|
182
|
+
(this.signalArtifactsUniform.value as number) > 0)
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
override updateParams(params: LayerParameterValues): void {
|
|
187
|
+
this.crtModeUniform.value = toModeValue(params.crtMode)
|
|
188
|
+
this.cellSizeUniform.value =
|
|
189
|
+
typeof params.cellSize === "number" ? Math.max(2, params.cellSize) : 3
|
|
190
|
+
this.scanlineIntensityUniform.value =
|
|
191
|
+
typeof params.scanlineIntensity === "number" ? clamp01(params.scanlineIntensity) : 0.17
|
|
192
|
+
this.maskIntensityUniform.value =
|
|
193
|
+
typeof params.maskIntensity === "number" ? clamp01(params.maskIntensity) : 1
|
|
194
|
+
this.barrelDistortionUniform.value =
|
|
195
|
+
typeof params.barrelDistortion === "number"
|
|
196
|
+
? Math.max(0, Math.min(0.3, params.barrelDistortion))
|
|
197
|
+
: 0.15
|
|
198
|
+
this.chromaticAberrationUniform.value =
|
|
199
|
+
typeof params.chromaticAberration === "number"
|
|
200
|
+
? Math.max(0, Math.min(2, params.chromaticAberration))
|
|
201
|
+
: 2
|
|
202
|
+
this.beamFocusUniform.value =
|
|
203
|
+
typeof params.beamFocus === "number" ? clamp01(params.beamFocus) : 0.58
|
|
204
|
+
this.brightnessUniform.value =
|
|
205
|
+
typeof params.brightness === "number"
|
|
206
|
+
? Math.max(0.5, params.brightness)
|
|
207
|
+
: 1.8
|
|
208
|
+
this.highlightDriveUniform.value =
|
|
209
|
+
typeof params.highlightDrive === "number"
|
|
210
|
+
? Math.max(1, params.highlightDrive)
|
|
211
|
+
: 1
|
|
212
|
+
this.highlightThresholdUniform.value =
|
|
213
|
+
typeof params.highlightThreshold === "number"
|
|
214
|
+
? clamp01(params.highlightThreshold)
|
|
215
|
+
: 0.62
|
|
216
|
+
this.shoulderUniform.value =
|
|
217
|
+
typeof params.shoulder === "number" ? Math.max(0, params.shoulder) : 0.25
|
|
218
|
+
this.chromaRetentionUniform.value =
|
|
219
|
+
typeof params.chromaRetention === "number"
|
|
220
|
+
? Math.max(0, Math.min(2, params.chromaRetention))
|
|
221
|
+
: 1.15
|
|
222
|
+
this.shadowLiftUniform.value =
|
|
223
|
+
typeof params.shadowLift === "number" ? clamp01(params.shadowLift) : 0.16
|
|
224
|
+
this.persistenceUniform.value =
|
|
225
|
+
typeof params.persistence === "number" ? clamp01(params.persistence) : 0.18
|
|
226
|
+
this.vignetteIntensityUniform.value =
|
|
227
|
+
typeof params.vignetteIntensity === "number" ? clamp01(params.vignetteIntensity) : 0.45
|
|
228
|
+
this.flickerIntensityUniform.value =
|
|
229
|
+
typeof params.flickerIntensity === "number"
|
|
230
|
+
? Math.max(0, Math.min(0.2, params.flickerIntensity))
|
|
231
|
+
: 0.03
|
|
232
|
+
this.glitchIntensityUniform.value =
|
|
233
|
+
typeof params.glitchIntensity === "number" ? clamp01(params.glitchIntensity) : 0
|
|
234
|
+
this.glitchSpeedUniform.value =
|
|
235
|
+
typeof params.glitchSpeed === "number"
|
|
236
|
+
? Math.max(0.1, Math.min(5, params.glitchSpeed))
|
|
237
|
+
: 1
|
|
238
|
+
this.signalArtifactsUniform.value =
|
|
239
|
+
typeof params.signalArtifacts === "number" ? clamp01(params.signalArtifacts) : 0.45
|
|
240
|
+
|
|
241
|
+
const nextBloomEnabled = params.bloomEnabled !== false
|
|
242
|
+
const nextBloomIntensity =
|
|
243
|
+
typeof params.bloomIntensity === "number" ? Math.max(0, params.bloomIntensity) : 1.5
|
|
244
|
+
const nextBloomThreshold =
|
|
245
|
+
typeof params.bloomThreshold === "number" ? clamp01(params.bloomThreshold) : 0.4
|
|
246
|
+
const nextBloomRadius =
|
|
247
|
+
typeof params.bloomRadius === "number" ? Math.max(0, params.bloomRadius) : 8
|
|
248
|
+
const nextBloomSoftness =
|
|
249
|
+
typeof params.bloomSoftness === "number" ? clamp01(params.bloomSoftness) : 0.4
|
|
250
|
+
|
|
251
|
+
this.bloomIntensityUniform.value = nextBloomIntensity
|
|
252
|
+
this.bloomRadiusUniform.value = nextBloomRadius
|
|
253
|
+
this.bloomSoftnessUniform.value = nextBloomSoftness
|
|
254
|
+
this.bloomThresholdUniform.value = nextBloomThreshold
|
|
255
|
+
|
|
256
|
+
if (nextBloomEnabled !== this.bloomEnabled) {
|
|
257
|
+
this.bloomEnabled = nextBloomEnabled
|
|
258
|
+
this.rebuildEffectNode()
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (this.bloomNode) {
|
|
263
|
+
this.bloomNode.strength.value = nextBloomIntensity
|
|
264
|
+
this.bloomNode.radius.value = this.normalizeBloomRadius(nextBloomRadius)
|
|
265
|
+
this.bloomNode.threshold.value = nextBloomThreshold
|
|
266
|
+
this.bloomNode.smoothWidth.value = this.normalizeBloomSoftness(nextBloomSoftness)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
override resize(width: number, height: number): void {
|
|
271
|
+
this.renderWidth = Math.max(1, width)
|
|
272
|
+
this.renderHeight = Math.max(1, height)
|
|
273
|
+
this.widthUniform.value = this.renderWidth
|
|
274
|
+
this.heightUniform.value = this.renderHeight
|
|
275
|
+
this.historyReadTarget.setSize(this.renderWidth, this.renderHeight)
|
|
276
|
+
this.historyWriteTarget.setSize(this.renderWidth, this.renderHeight)
|
|
277
|
+
this.historyValid = false
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
override updateLogicalSize(width: number, height: number): void {
|
|
281
|
+
this.widthUniform.value = Math.max(1, width)
|
|
282
|
+
this.heightUniform.value = Math.max(1, height)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
override dispose(): void {
|
|
286
|
+
this.disposeBloomNode()
|
|
287
|
+
this.placeholder.dispose()
|
|
288
|
+
this.historyReadTarget.dispose()
|
|
289
|
+
this.historyWriteTarget.dispose()
|
|
290
|
+
super.dispose()
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
protected override buildEffectNode(): Node {
|
|
294
|
+
if (!(this.cellSizeUniform && this.placeholder)) {
|
|
295
|
+
return this.inputNode
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this.disposeBloomNode()
|
|
299
|
+
this.bloomNode = null
|
|
300
|
+
this.sourceTextureNodes = []
|
|
301
|
+
this.historyTextureNodes = []
|
|
302
|
+
|
|
303
|
+
const renderTargetUv = vec2(uv().x, float(1).sub(uv().y))
|
|
304
|
+
const dims = vec2(this.widthUniform, this.heightUniform)
|
|
305
|
+
const texel = vec2(
|
|
306
|
+
float(1).div(max(this.widthUniform, float(1))),
|
|
307
|
+
float(1).div(max(this.heightUniform, float(1))),
|
|
308
|
+
)
|
|
309
|
+
const centered = renderTargetUv.sub(vec2(0.5, 0.5))
|
|
310
|
+
const distSq = dot(centered, centered)
|
|
311
|
+
|
|
312
|
+
const slotWeight = this.modeWeight(CRT_MODE_SLOT_MASK)
|
|
313
|
+
const apertureWeight = this.modeWeight(CRT_MODE_APERTURE_GRILLE)
|
|
314
|
+
const compositeWeight = this.modeWeight(CRT_MODE_COMPOSITE_TV)
|
|
315
|
+
|
|
316
|
+
const distortionBias = slotWeight
|
|
317
|
+
.mul(1)
|
|
318
|
+
.add(apertureWeight.mul(0.72))
|
|
319
|
+
.add(compositeWeight.mul(1.16))
|
|
320
|
+
const distortedUv = renderTargetUv.add(
|
|
321
|
+
centered.mul(distSq).mul(this.barrelDistortionUniform).mul(distortionBias),
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
const insideScreen = distortedUv.x
|
|
325
|
+
.greaterThanEqual(float(0))
|
|
326
|
+
.and(distortedUv.x.lessThanEqual(float(1)))
|
|
327
|
+
.and(distortedUv.y.greaterThanEqual(float(0)))
|
|
328
|
+
.and(distortedUv.y.lessThanEqual(float(1)))
|
|
329
|
+
|
|
330
|
+
const pitch = max(this.cellSizeUniform, float(2))
|
|
331
|
+
const screenPixel = renderTargetUv.mul(dims)
|
|
332
|
+
const cellCoord = floor(screenPixel.div(pitch))
|
|
333
|
+
const localCellUv = vec2(
|
|
334
|
+
fract(screenPixel.x.div(pitch)),
|
|
335
|
+
fract(screenPixel.y.div(pitch)),
|
|
336
|
+
)
|
|
337
|
+
const cellCenterUv = cellCoord.add(vec2(0.5, 0.5)).mul(pitch).div(dims)
|
|
338
|
+
const centeredCell = cellCenterUv.sub(vec2(0.5, 0.5))
|
|
339
|
+
const cellDistSq = dot(centeredCell, centeredCell)
|
|
340
|
+
const distortedCellUv = cellCenterUv.add(
|
|
341
|
+
centeredCell.mul(cellDistSq).mul(this.barrelDistortionUniform).mul(distortionBias),
|
|
342
|
+
)
|
|
343
|
+
const row = cellCoord.y
|
|
344
|
+
const timeDrift = this.timeUniform.mul(this.glitchSpeedUniform)
|
|
345
|
+
const drift = simplexNoise3d(vec3(float(0), row.mul(float(0.1)), timeDrift))
|
|
346
|
+
.mul(this.glitchIntensityUniform)
|
|
347
|
+
.mul(slotWeight.mul(0.003).add(compositeWeight.mul(0.007)).add(apertureWeight.mul(0.0025)))
|
|
348
|
+
const samplingUv = vec2(distortedCellUv.x.add(drift), distortedCellUv.y)
|
|
349
|
+
const clampedSamplingUv = clamp(samplingUv, vec2(0, 0), vec2(1, 1))
|
|
350
|
+
|
|
351
|
+
const baseSignal = this.sampleSignalColor(clampedSamplingUv, texel, compositeWeight)
|
|
352
|
+
const baseLuma = this.luma(baseSignal)
|
|
353
|
+
const brightnessSpread = mix(
|
|
354
|
+
float(0.82),
|
|
355
|
+
float(1.34),
|
|
356
|
+
smoothstep(float(0.12), float(0.95), baseLuma),
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
const beamWidthX = mix(float(1.6), float(0.58), this.beamFocusUniform)
|
|
360
|
+
.mul(brightnessSpread.mul(float(0.15)).add(float(0.92)))
|
|
361
|
+
.mul(
|
|
362
|
+
slotWeight.mul(1)
|
|
363
|
+
.add(apertureWeight.mul(0.72))
|
|
364
|
+
.add(compositeWeight.mul(1.4)),
|
|
365
|
+
)
|
|
366
|
+
const beamWidthY = mix(float(1.95), float(0.62), this.beamFocusUniform)
|
|
367
|
+
.mul(mix(float(1.12), float(0.46), this.scanlineIntensityUniform))
|
|
368
|
+
.mul(brightnessSpread)
|
|
369
|
+
.mul(
|
|
370
|
+
slotWeight.mul(1)
|
|
371
|
+
.add(apertureWeight.mul(0.84))
|
|
372
|
+
.add(compositeWeight.mul(1.3)),
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
const edgeFactor = pow(clamp(centered.length().mul(float(1.82)), float(0), float(1)), float(2))
|
|
376
|
+
const convergenceShape = vec2(
|
|
377
|
+
centered.x.mul(abs(centered.x).add(float(0.16))),
|
|
378
|
+
centered.y.mul(abs(centered.y).add(float(0.08))),
|
|
379
|
+
)
|
|
380
|
+
const convergenceScale = this.chromaticAberrationUniform
|
|
381
|
+
.mul(edgeFactor)
|
|
382
|
+
.mul(slotWeight.mul(1).add(apertureWeight.mul(0.82)).add(compositeWeight.mul(1.18)))
|
|
383
|
+
.div(dims)
|
|
384
|
+
.mul(float(1.6))
|
|
385
|
+
const convergenceOffset = convergenceShape.mul(convergenceScale)
|
|
386
|
+
const greenOffset = vec2(float(0), convergenceScale.y.mul(float(-0.2)))
|
|
387
|
+
|
|
388
|
+
const redBeam = this.sampleBeamColor(
|
|
389
|
+
clamp(clampedSamplingUv.add(convergenceOffset), vec2(0, 0), vec2(1, 1)),
|
|
390
|
+
texel,
|
|
391
|
+
beamWidthX,
|
|
392
|
+
beamWidthY,
|
|
393
|
+
compositeWeight,
|
|
394
|
+
)
|
|
395
|
+
const greenBeam = this.sampleBeamColor(
|
|
396
|
+
clamp(clampedSamplingUv.add(greenOffset), vec2(0, 0), vec2(1, 1)),
|
|
397
|
+
texel,
|
|
398
|
+
beamWidthX,
|
|
399
|
+
beamWidthY,
|
|
400
|
+
compositeWeight,
|
|
401
|
+
)
|
|
402
|
+
const blueBeam = this.sampleBeamColor(
|
|
403
|
+
clamp(clampedSamplingUv.sub(convergenceOffset), vec2(0, 0), vec2(1, 1)),
|
|
404
|
+
texel,
|
|
405
|
+
beamWidthX,
|
|
406
|
+
beamWidthY,
|
|
407
|
+
compositeWeight,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
const beamEnergy = vec3(float(redBeam.r), float(greenBeam.g), float(blueBeam.b))
|
|
411
|
+
const feedResponse = this.buildFeedResponse(
|
|
412
|
+
beamEnergy,
|
|
413
|
+
cellCoord,
|
|
414
|
+
slotWeight,
|
|
415
|
+
apertureWeight,
|
|
416
|
+
compositeWeight,
|
|
417
|
+
)
|
|
418
|
+
const scanlineEnvelope = this.buildScanlineEnvelope(screenPixel, baseLuma, compositeWeight)
|
|
419
|
+
|
|
420
|
+
const halationSignal = this.sampleHalation(clampedSamplingUv, texel, compositeWeight)
|
|
421
|
+
const halation = vec3(
|
|
422
|
+
float(halationSignal.r),
|
|
423
|
+
float(halationSignal.g),
|
|
424
|
+
float(halationSignal.b),
|
|
425
|
+
).mul(
|
|
426
|
+
smoothstep(float(0.45), float(1), baseLuma).mul(
|
|
427
|
+
slotWeight.mul(0.04).add(apertureWeight.mul(0.03)).add(compositeWeight.mul(0.07)),
|
|
428
|
+
),
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
let color = this.renderPhosphorCell(
|
|
432
|
+
localCellUv,
|
|
433
|
+
cellCoord,
|
|
434
|
+
beamEnergy,
|
|
435
|
+
baseLuma,
|
|
436
|
+
feedResponse,
|
|
437
|
+
slotWeight,
|
|
438
|
+
apertureWeight,
|
|
439
|
+
compositeWeight,
|
|
440
|
+
)
|
|
441
|
+
const tubeGlow = this.buildTubeGlow(
|
|
442
|
+
localCellUv,
|
|
443
|
+
baseSignal,
|
|
444
|
+
baseLuma,
|
|
445
|
+
slotWeight,
|
|
446
|
+
apertureWeight,
|
|
447
|
+
compositeWeight,
|
|
448
|
+
)
|
|
449
|
+
const specularLift = this.buildSpecularLift(
|
|
450
|
+
localCellUv,
|
|
451
|
+
baseSignal,
|
|
452
|
+
baseLuma,
|
|
453
|
+
slotWeight,
|
|
454
|
+
apertureWeight,
|
|
455
|
+
compositeWeight,
|
|
456
|
+
)
|
|
457
|
+
color = color.mul(mix(vec3(1, 1, 1), scanlineEnvelope, this.scanlineIntensityUniform))
|
|
458
|
+
color = color.add(halation).add(tubeGlow).add(specularLift)
|
|
459
|
+
|
|
460
|
+
const vignetteStrength = this.vignetteIntensityUniform.mul(
|
|
461
|
+
slotWeight.mul(1).add(apertureWeight.mul(0.82)).add(compositeWeight.mul(1.1)),
|
|
462
|
+
)
|
|
463
|
+
const vignetteDistance = centered.length().mul(float(2))
|
|
464
|
+
const vignette = clamp(
|
|
465
|
+
float(1).sub(vignetteDistance.mul(vignetteDistance).mul(vignetteStrength)),
|
|
466
|
+
float(0),
|
|
467
|
+
float(1),
|
|
468
|
+
)
|
|
469
|
+
color = color.mul(vignette)
|
|
470
|
+
|
|
471
|
+
const flickerNoise = simplexNoise3d(vec3(float(0), float(0), this.timeUniform.mul(float(8))))
|
|
472
|
+
const flicker = float(1).add(flickerNoise.mul(this.flickerIntensityUniform))
|
|
473
|
+
color = color.mul(flicker)
|
|
474
|
+
color = color.mul(select(insideScreen, float(1), float(0)))
|
|
475
|
+
|
|
476
|
+
const historySample = this.trackHistoryTextureNode(renderTargetUv)
|
|
477
|
+
const historyColor = vec3(
|
|
478
|
+
float(historySample.r),
|
|
479
|
+
float(historySample.g),
|
|
480
|
+
float(historySample.b),
|
|
481
|
+
)
|
|
482
|
+
const historyDecay = historyColor.mul(
|
|
483
|
+
vec3(
|
|
484
|
+
mix(float(0.62), float(0.92), this.persistenceUniform),
|
|
485
|
+
mix(float(0.68), float(0.96), this.persistenceUniform),
|
|
486
|
+
mix(float(0.56), float(0.88), this.persistenceUniform),
|
|
487
|
+
),
|
|
488
|
+
)
|
|
489
|
+
color = clamp(
|
|
490
|
+
color.add(historyDecay.mul(this.persistenceUniform).mul(float(0.55))),
|
|
491
|
+
vec3(0, 0, 0),
|
|
492
|
+
vec3(1, 1, 1),
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
if (!this.bloomEnabled) {
|
|
496
|
+
return vec4(color, float(1))
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const bloomInput = vec4(color, float(1))
|
|
500
|
+
this.bloomNode = bloom(
|
|
501
|
+
bloomInput,
|
|
502
|
+
this.bloomIntensityUniform.value as number,
|
|
503
|
+
this.normalizeBloomRadius(this.bloomRadiusUniform.value as number),
|
|
504
|
+
this.bloomThresholdUniform.value as number,
|
|
505
|
+
)
|
|
506
|
+
this.bloomNode.smoothWidth.value = this.normalizeBloomSoftness(
|
|
507
|
+
this.bloomSoftnessUniform.value as number,
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
return vec4(
|
|
511
|
+
clamp(
|
|
512
|
+
color.add(this.getBloomTextureNode().rgb),
|
|
513
|
+
vec3(float(0), float(0), float(0)),
|
|
514
|
+
vec3(float(1), float(1), float(1)),
|
|
515
|
+
),
|
|
516
|
+
float(1),
|
|
517
|
+
)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private buildBand(position: Node, center: Node | number, halfWidth: Node | number, softEdge: Node | number): Node {
|
|
521
|
+
const bandCenter = this.toNode(center)
|
|
522
|
+
const halfWidthNode = this.toNode(halfWidth)
|
|
523
|
+
const softEdgeNode = this.toNode(softEdge)
|
|
524
|
+
const low = bandCenter.sub(halfWidthNode)
|
|
525
|
+
const high = bandCenter.add(halfWidthNode)
|
|
526
|
+
return smoothstep(low.sub(softEdgeNode), low, position).mul(
|
|
527
|
+
float(1).sub(smoothstep(high, high.add(softEdgeNode), position)),
|
|
528
|
+
)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private buildRoundedPhosphor(
|
|
532
|
+
localX: Node,
|
|
533
|
+
localY: Node,
|
|
534
|
+
centerX: Node | number,
|
|
535
|
+
halfWidth: Node | number,
|
|
536
|
+
halfHeight: Node | number,
|
|
537
|
+
softEdge: Node | number,
|
|
538
|
+
taper: Node | number,
|
|
539
|
+
): Node {
|
|
540
|
+
const halfHeightNode = this.toNode(halfHeight)
|
|
541
|
+
const softEdgeNode = this.toNode(softEdge)
|
|
542
|
+
const taperNode = this.toNode(taper)
|
|
543
|
+
const yDistance = abs(localY.sub(float(0.5)))
|
|
544
|
+
const widthTaper = mix(
|
|
545
|
+
float(1),
|
|
546
|
+
taperNode,
|
|
547
|
+
smoothstep(halfHeightNode.mul(0.42), float(0.5), yDistance),
|
|
548
|
+
)
|
|
549
|
+
return this.buildBand(localX, centerX, this.toNode(halfWidth).mul(widthTaper), softEdgeNode).mul(
|
|
550
|
+
this.buildBand(localY, 0.5, halfHeightNode, softEdgeNode),
|
|
551
|
+
)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private buildFeedResponse(
|
|
555
|
+
beamEnergy: Node,
|
|
556
|
+
cellCoord: Node,
|
|
557
|
+
slotWeight: Node,
|
|
558
|
+
apertureWeight: Node,
|
|
559
|
+
compositeWeight: Node,
|
|
560
|
+
): Node {
|
|
561
|
+
const feedMax = max(float(beamEnergy.r), max(float(beamEnergy.g), float(beamEnergy.b)))
|
|
562
|
+
const safeFeedMax = max(feedMax, float(1e-4))
|
|
563
|
+
const channelSeparation = slotWeight.mul(0.86).add(apertureWeight.mul(0.78)).add(compositeWeight.mul(0.58))
|
|
564
|
+
const responseCurve = slotWeight.mul(0.92).add(apertureWeight.mul(0.88)).add(compositeWeight.mul(1.04))
|
|
565
|
+
const spill = compositeWeight.mul(0.12).add(slotWeight.mul(0.04)).add(apertureWeight.mul(0.03))
|
|
566
|
+
|
|
567
|
+
const rNoise = simplexNoise3d(vec3(cellCoord.x.add(float(0.19)), cellCoord.y, float(7.1)))
|
|
568
|
+
.mul(0.5)
|
|
569
|
+
.add(0.5)
|
|
570
|
+
const gNoise = simplexNoise3d(vec3(cellCoord.x.add(float(0.41)), cellCoord.y, float(13.7)))
|
|
571
|
+
.mul(0.5)
|
|
572
|
+
.add(0.5)
|
|
573
|
+
const bNoise = simplexNoise3d(vec3(cellCoord.x.add(float(0.73)), cellCoord.y, float(19.9)))
|
|
574
|
+
.mul(0.5)
|
|
575
|
+
.add(0.5)
|
|
576
|
+
|
|
577
|
+
const rResponse = mix(float(0.82), float(1.18), rNoise)
|
|
578
|
+
const gResponse = mix(float(0.84), float(1.16), gNoise)
|
|
579
|
+
const bResponse = mix(float(0.8), float(1.2), bNoise)
|
|
580
|
+
|
|
581
|
+
const rDominance = pow(clamp(float(beamEnergy.r).div(safeFeedMax), float(0), float(1)), float(0.82))
|
|
582
|
+
const gDominance = pow(clamp(float(beamEnergy.g).div(safeFeedMax), float(0), float(1)), float(0.82))
|
|
583
|
+
const bDominance = pow(clamp(float(beamEnergy.b).div(safeFeedMax), float(0), float(1)), float(0.82))
|
|
584
|
+
|
|
585
|
+
const rDrive = clamp(
|
|
586
|
+
beamEnergy.r.mul(rResponse).mul(mix(float(1), rDominance, channelSeparation)).add(feedMax.mul(spill)),
|
|
587
|
+
float(0),
|
|
588
|
+
float(1),
|
|
589
|
+
)
|
|
590
|
+
const gDrive = clamp(
|
|
591
|
+
beamEnergy.g.mul(gResponse).mul(mix(float(1), gDominance, channelSeparation)).add(feedMax.mul(spill)),
|
|
592
|
+
float(0),
|
|
593
|
+
float(1),
|
|
594
|
+
)
|
|
595
|
+
const bDrive = clamp(
|
|
596
|
+
beamEnergy.b.mul(bResponse).mul(mix(float(1), bDominance, channelSeparation)).add(feedMax.mul(spill)),
|
|
597
|
+
float(0),
|
|
598
|
+
float(1),
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
return clamp(
|
|
602
|
+
vec3(
|
|
603
|
+
pow(rDrive, responseCurve),
|
|
604
|
+
pow(gDrive, responseCurve),
|
|
605
|
+
pow(bDrive, responseCurve),
|
|
606
|
+
),
|
|
607
|
+
vec3(0, 0, 0),
|
|
608
|
+
vec3(1, 1, 1),
|
|
609
|
+
)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
private renderPhosphorCell(
|
|
613
|
+
localCellUv: Node,
|
|
614
|
+
cellCoord: Node,
|
|
615
|
+
beamEnergy: Node,
|
|
616
|
+
baseLuma: Node,
|
|
617
|
+
feedResponse: Node,
|
|
618
|
+
slotWeight: Node,
|
|
619
|
+
apertureWeight: Node,
|
|
620
|
+
compositeWeight: Node,
|
|
621
|
+
): Node {
|
|
622
|
+
const rowIndex = cellCoord.y
|
|
623
|
+
const oddRow = mod(rowIndex, float(2)).greaterThan(float(0.5))
|
|
624
|
+
const slotX = fract(localCellUv.x.add(select(oddRow, float(0.5), float(0))))
|
|
625
|
+
const slotY = localCellUv.y
|
|
626
|
+
|
|
627
|
+
const definition = mix(float(0.72), float(1.18), this.maskIntensityUniform)
|
|
628
|
+
const rowJitter = simplexNoise3d(vec3(float(0), rowIndex, float(3.7))).mul(0.5).add(0.5).sub(0.5)
|
|
629
|
+
const slotJitter = simplexNoise3d(vec3(cellCoord.x, cellCoord.y, float(9.3))).mul(0.5).add(0.5).sub(0.5)
|
|
630
|
+
const apertureJitter = simplexNoise3d(vec3(cellCoord.x, cellCoord.y, float(15.1)))
|
|
631
|
+
.mul(0.5)
|
|
632
|
+
.add(0.5)
|
|
633
|
+
.sub(0.5)
|
|
634
|
+
const tvJitter = simplexNoise3d(vec3(cellCoord.x, cellCoord.y, float(21.4))).mul(0.5).add(0.5).sub(0.5)
|
|
635
|
+
|
|
636
|
+
const slotShape = vec3(
|
|
637
|
+
this.buildRoundedPhosphor(
|
|
638
|
+
slotX,
|
|
639
|
+
slotY,
|
|
640
|
+
float(1 / 6).sub(rowJitter.mul(0.01)),
|
|
641
|
+
float(0.12).mul(definition).add(slotJitter.mul(0.01)),
|
|
642
|
+
float(0.38).add(abs(slotJitter).mul(0.03)),
|
|
643
|
+
float(0.015),
|
|
644
|
+
float(0.38),
|
|
645
|
+
),
|
|
646
|
+
this.buildRoundedPhosphor(
|
|
647
|
+
slotX,
|
|
648
|
+
slotY,
|
|
649
|
+
float(0.5),
|
|
650
|
+
float(0.115).mul(definition).add(slotJitter.mul(0.008)),
|
|
651
|
+
float(0.38).add(abs(slotJitter).mul(0.03)),
|
|
652
|
+
float(0.015),
|
|
653
|
+
float(0.38),
|
|
654
|
+
),
|
|
655
|
+
this.buildRoundedPhosphor(
|
|
656
|
+
slotX,
|
|
657
|
+
slotY,
|
|
658
|
+
float(5 / 6).add(rowJitter.mul(0.01)),
|
|
659
|
+
float(0.12).mul(definition).add(slotJitter.mul(0.01)),
|
|
660
|
+
float(0.38).add(abs(slotJitter).mul(0.03)),
|
|
661
|
+
float(0.015),
|
|
662
|
+
float(0.38),
|
|
663
|
+
),
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
const apertureX = localCellUv.x
|
|
667
|
+
const apertureSegment = this.buildBand(slotY, 0.5, float(0.41), float(0.06))
|
|
668
|
+
const apertureShape = vec3(
|
|
669
|
+
this.buildRoundedPhosphor(
|
|
670
|
+
apertureX,
|
|
671
|
+
slotY,
|
|
672
|
+
float(1 / 6).add(apertureJitter.mul(0.007)),
|
|
673
|
+
float(0.14).mul(definition).add(apertureJitter.mul(0.008)),
|
|
674
|
+
float(0.44),
|
|
675
|
+
float(0.008),
|
|
676
|
+
float(0.82),
|
|
677
|
+
).mul(apertureSegment),
|
|
678
|
+
this.buildRoundedPhosphor(
|
|
679
|
+
apertureX,
|
|
680
|
+
slotY,
|
|
681
|
+
float(0.5),
|
|
682
|
+
float(0.135).mul(definition).add(apertureJitter.mul(0.006)),
|
|
683
|
+
float(0.44),
|
|
684
|
+
float(0.008),
|
|
685
|
+
float(0.82),
|
|
686
|
+
).mul(apertureSegment),
|
|
687
|
+
this.buildRoundedPhosphor(
|
|
688
|
+
apertureX,
|
|
689
|
+
slotY,
|
|
690
|
+
float(5 / 6).sub(apertureJitter.mul(0.007)),
|
|
691
|
+
float(0.14).mul(definition).add(apertureJitter.mul(0.008)),
|
|
692
|
+
float(0.44),
|
|
693
|
+
float(0.008),
|
|
694
|
+
float(0.82),
|
|
695
|
+
).mul(apertureSegment),
|
|
696
|
+
)
|
|
697
|
+
|
|
698
|
+
const tvX = fract(localCellUv.x.add(select(oddRow, float(0.25), float(0))))
|
|
699
|
+
const tvShape = vec3(
|
|
700
|
+
this.buildRoundedPhosphor(
|
|
701
|
+
tvX,
|
|
702
|
+
slotY,
|
|
703
|
+
float(1 / 6).add(tvJitter.mul(0.015)),
|
|
704
|
+
float(0.11).mul(definition).add(tvJitter.mul(0.012)),
|
|
705
|
+
float(0.34).add(abs(tvJitter).mul(0.04)),
|
|
706
|
+
float(0.02),
|
|
707
|
+
float(0.44),
|
|
708
|
+
),
|
|
709
|
+
this.buildRoundedPhosphor(
|
|
710
|
+
tvX,
|
|
711
|
+
slotY,
|
|
712
|
+
float(0.5),
|
|
713
|
+
float(0.105).mul(definition).add(tvJitter.mul(0.01)),
|
|
714
|
+
float(0.34).add(abs(tvJitter).mul(0.04)),
|
|
715
|
+
float(0.02),
|
|
716
|
+
float(0.44),
|
|
717
|
+
),
|
|
718
|
+
this.buildRoundedPhosphor(
|
|
719
|
+
tvX,
|
|
720
|
+
slotY,
|
|
721
|
+
float(5 / 6).sub(tvJitter.mul(0.015)),
|
|
722
|
+
float(0.11).mul(definition).add(tvJitter.mul(0.012)),
|
|
723
|
+
float(0.34).add(abs(tvJitter).mul(0.04)),
|
|
724
|
+
float(0.02),
|
|
725
|
+
float(0.44),
|
|
726
|
+
),
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
const combinedShape = slotShape.mul(slotWeight)
|
|
730
|
+
.add(apertureShape.mul(apertureWeight))
|
|
731
|
+
.add(tvShape.mul(compositeWeight))
|
|
732
|
+
|
|
733
|
+
const channelResponse = vec3(
|
|
734
|
+
beamEnergy.r.mul(feedResponse.r),
|
|
735
|
+
beamEnergy.g.mul(feedResponse.g),
|
|
736
|
+
beamEnergy.b.mul(feedResponse.b),
|
|
737
|
+
)
|
|
738
|
+
const responseLuma = this.luma(channelResponse)
|
|
739
|
+
const responseNeutral = vec3(responseLuma, responseLuma, responseLuma)
|
|
740
|
+
const responseChroma = channelResponse.sub(responseNeutral)
|
|
741
|
+
const highlightMask = smoothstep(this.highlightThresholdUniform, float(1), baseLuma)
|
|
742
|
+
const brightnessGain = mix(
|
|
743
|
+
float(1),
|
|
744
|
+
this.brightnessUniform,
|
|
745
|
+
smoothstep(float(0.22), float(0.98), baseLuma),
|
|
746
|
+
)
|
|
747
|
+
const lumaDrive = brightnessGain.mul(
|
|
748
|
+
mix(float(1), this.highlightDriveUniform, highlightMask),
|
|
749
|
+
)
|
|
750
|
+
const shadowGain = mix(
|
|
751
|
+
float(1),
|
|
752
|
+
this.shadowLiftUniform.mul(float(0.35)).add(float(1)),
|
|
753
|
+
smoothstep(float(0.42), float(0.02), baseLuma),
|
|
754
|
+
)
|
|
755
|
+
const drivenLuma = responseLuma.mul(lumaDrive).mul(shadowGain)
|
|
756
|
+
const shoulderStrength = this.shoulderUniform.mul(highlightMask).mul(float(0.15))
|
|
757
|
+
const compressedLuma = drivenLuma.div(
|
|
758
|
+
drivenLuma.mul(shoulderStrength).add(float(1)),
|
|
759
|
+
)
|
|
760
|
+
const shapedLuma = mix(
|
|
761
|
+
drivenLuma,
|
|
762
|
+
compressedLuma,
|
|
763
|
+
smoothstep(float(0.05), float(1), highlightMask),
|
|
764
|
+
)
|
|
765
|
+
const chromaGain = mix(
|
|
766
|
+
float(1),
|
|
767
|
+
this.chromaRetentionUniform.mul(mix(float(1), lumaDrive, float(0.18))),
|
|
768
|
+
smoothstep(float(0.18), float(0.95), baseLuma),
|
|
769
|
+
)
|
|
770
|
+
const boostedResponse = vec3(shapedLuma, shapedLuma, shapedLuma).add(
|
|
771
|
+
responseChroma.mul(chromaGain),
|
|
772
|
+
)
|
|
773
|
+
const phosphorBrightness = slotWeight.mul(2.8).add(apertureWeight.mul(2.4)).add(compositeWeight.mul(2.6))
|
|
774
|
+
const baseEmission = vec3(
|
|
775
|
+
combinedShape.r.mul(boostedResponse.r),
|
|
776
|
+
combinedShape.g.mul(boostedResponse.g),
|
|
777
|
+
combinedShape.b.mul(boostedResponse.b),
|
|
778
|
+
).mul(phosphorBrightness)
|
|
779
|
+
const channelMin = min(boostedResponse.r, min(boostedResponse.g, boostedResponse.b))
|
|
780
|
+
const channelMax = max(boostedResponse.r, max(boostedResponse.g, boostedResponse.b))
|
|
781
|
+
const channelAvg = boostedResponse.r.add(boostedResponse.g).add(boostedResponse.b).div(3)
|
|
782
|
+
const sharedDrive = clamp(channelAvg.div(max(channelMax, float(1e-4))), float(0), float(1))
|
|
783
|
+
const neutrality = clamp(channelMin.div(max(channelMax, float(1e-4))), float(0), float(1))
|
|
784
|
+
const whiteHot = smoothstep(float(0.24), float(0.82), channelAvg).mul(
|
|
785
|
+
smoothstep(float(0.55), float(0.96), neutrality),
|
|
786
|
+
).mul(
|
|
787
|
+
mix(float(0.03), float(0.18), pow(sharedDrive, float(0.72))),
|
|
788
|
+
)
|
|
789
|
+
const sharedCore = combinedShape.add(vec3(channelMin, channelMin, channelMin)).div(4)
|
|
790
|
+
const whiteCore = vec3(whiteHot, whiteHot, whiteHot).mul(sharedCore).mul(
|
|
791
|
+
slotWeight.mul(0.34).add(apertureWeight.mul(0.28)).add(compositeWeight.mul(0.46)),
|
|
792
|
+
).mul(mix(float(1), lumaDrive, smoothstep(float(0.52), float(1), channelAvg)))
|
|
793
|
+
|
|
794
|
+
return clamp(
|
|
795
|
+
baseEmission.add(whiteCore),
|
|
796
|
+
vec3(0, 0, 0),
|
|
797
|
+
vec3(1, 1, 1),
|
|
798
|
+
)
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
private buildTubeGlow(
|
|
802
|
+
localCellUv: Node,
|
|
803
|
+
baseSignal: Node,
|
|
804
|
+
baseLuma: Node,
|
|
805
|
+
slotWeight: Node,
|
|
806
|
+
apertureWeight: Node,
|
|
807
|
+
compositeWeight: Node,
|
|
808
|
+
): Node {
|
|
809
|
+
const channelMax = max(baseSignal.r, max(baseSignal.g, baseSignal.b))
|
|
810
|
+
const channelMin = min(baseSignal.r, min(baseSignal.g, baseSignal.b))
|
|
811
|
+
const neutrality = clamp(channelMin.div(max(channelMax, float(1e-4))), float(0), float(1))
|
|
812
|
+
const highlightMix = smoothstep(float(0.58), float(0.98), channelMax)
|
|
813
|
+
const whiteness = smoothstep(float(0.55), float(0.94), baseLuma).mul(
|
|
814
|
+
smoothstep(float(0.62), float(0.98), neutrality),
|
|
815
|
+
).mul(
|
|
816
|
+
mix(float(0.04), float(0.18), highlightMix),
|
|
817
|
+
)
|
|
818
|
+
const neutral = vec3(baseLuma, baseLuma, baseLuma)
|
|
819
|
+
const warmedNeutral = mix(
|
|
820
|
+
neutral,
|
|
821
|
+
vec3(baseLuma.mul(1.03), baseLuma.mul(0.99), baseLuma.mul(0.96)),
|
|
822
|
+
slotWeight.mul(0.18).add(compositeWeight.mul(0.28)),
|
|
823
|
+
)
|
|
824
|
+
const neutralMix = whiteness.mul(
|
|
825
|
+
mix(
|
|
826
|
+
float(1),
|
|
827
|
+
float(0.55),
|
|
828
|
+
clamp(this.chromaRetentionUniform.sub(float(1)), float(0), float(1)),
|
|
829
|
+
),
|
|
830
|
+
)
|
|
831
|
+
const drivenSignal = mix(baseSignal, warmedNeutral, neutralMix)
|
|
832
|
+
const centerDistance = localCellUv.sub(vec2(0.5, 0.5)).length()
|
|
833
|
+
const spread = smoothstep(float(0.42), float(0.18), centerDistance)
|
|
834
|
+
const glowStrength = highlightMix.mul(baseLuma).mul(
|
|
835
|
+
slotWeight.mul(0.04).add(apertureWeight.mul(0.03)).add(compositeWeight.mul(0.08)),
|
|
836
|
+
)
|
|
837
|
+
return drivenSignal.mul(spread).mul(glowStrength)
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
private buildSpecularLift(
|
|
841
|
+
localCellUv: Node,
|
|
842
|
+
baseSignal: Node,
|
|
843
|
+
baseLuma: Node,
|
|
844
|
+
slotWeight: Node,
|
|
845
|
+
apertureWeight: Node,
|
|
846
|
+
compositeWeight: Node,
|
|
847
|
+
): Node {
|
|
848
|
+
const channelMax = max(baseSignal.r, max(baseSignal.g, baseSignal.b))
|
|
849
|
+
const channelMin = min(baseSignal.r, min(baseSignal.g, baseSignal.b))
|
|
850
|
+
const neutrality = clamp(channelMin.div(max(channelMax, float(1e-4))), float(0), float(1))
|
|
851
|
+
const highlightGate = smoothstep(float(0.62), float(0.98), channelMax)
|
|
852
|
+
.mul(smoothstep(float(0.42), float(0.92), baseLuma))
|
|
853
|
+
const coreDistance = localCellUv.sub(vec2(0.5, 0.5)).length()
|
|
854
|
+
const core = smoothstep(float(0.44), float(0.02), coreDistance)
|
|
855
|
+
const liftStrength = highlightGate.mul(
|
|
856
|
+
mix(float(0.32), float(0.9), neutrality)
|
|
857
|
+
).mul(
|
|
858
|
+
slotWeight.mul(0.95).add(apertureWeight.mul(0.78)).add(compositeWeight.mul(1.08)),
|
|
859
|
+
)
|
|
860
|
+
const neutralLift = mix(baseSignal, vec3(channelMax, channelMax, channelMax), float(0.86))
|
|
861
|
+
return neutralLift.mul(core).mul(liftStrength)
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
private buildScanlineEnvelope(screenPixel: Node, luma: Node, compositeWeight: Node): Node {
|
|
865
|
+
const pitch = max(this.cellSizeUniform, float(2))
|
|
866
|
+
const localY = fract(screenPixel.y.div(pitch))
|
|
867
|
+
const halfWidth = mix(float(0.48), float(0.21), this.scanlineIntensityUniform)
|
|
868
|
+
.mul(mix(float(0.82), float(1.18), smoothstep(float(0.08), float(1), luma)))
|
|
869
|
+
.mul(mix(float(1), float(1.12), compositeWeight))
|
|
870
|
+
const envelope = this.buildBand(localY, 0.5, halfWidth, 0.12)
|
|
871
|
+
return vec3(envelope, envelope, envelope)
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
private luma(color: Node): Node {
|
|
875
|
+
return dot(color, vec3(0.2126, 0.7152, 0.0722))
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
private modeWeight(targetMode: number): Node {
|
|
879
|
+
return select(this.crtModeUniform.equal(float(targetMode)), float(1), float(0))
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
private toNode(value: Node | number): Node {
|
|
883
|
+
return typeof value === "number" ? float(value) : value
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
private normalizeBloomRadius(value: number): number {
|
|
887
|
+
return clamp01(value / 24)
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
private normalizeBloomSoftness(value: number): number {
|
|
891
|
+
return Math.max(0.001, value * 0.25)
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
private disposeBloomNode(): void {
|
|
895
|
+
;(this.bloomNode as { dispose?: () => void } | null)?.dispose?.()
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
private getBloomTextureNode(): Node {
|
|
899
|
+
const bloomNode = this.bloomNode as
|
|
900
|
+
| ({
|
|
901
|
+
getTexture?: () => Node
|
|
902
|
+
getTextureNode?: () => Node
|
|
903
|
+
} & object)
|
|
904
|
+
| null
|
|
905
|
+
|
|
906
|
+
if (!bloomNode) {
|
|
907
|
+
throw new Error("Bloom node is not initialized")
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
if ("getTextureNode" in bloomNode && typeof bloomNode.getTextureNode === "function") {
|
|
911
|
+
return bloomNode.getTextureNode()
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if ("getTexture" in bloomNode && typeof bloomNode.getTexture === "function") {
|
|
915
|
+
return bloomNode.getTexture()
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
throw new Error("Bloom node does not expose a texture getter")
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
private sampleBeamColor(
|
|
922
|
+
sampleUv: Node,
|
|
923
|
+
texel: Node,
|
|
924
|
+
beamWidthX: Node,
|
|
925
|
+
beamWidthY: Node,
|
|
926
|
+
compositeWeight: Node,
|
|
927
|
+
): Node {
|
|
928
|
+
const xOffset = vec2(texel.x.mul(beamWidthX), float(0))
|
|
929
|
+
const yOffset = vec2(float(0), texel.y.mul(beamWidthY))
|
|
930
|
+
|
|
931
|
+
const center = this.sampleSignalColor(sampleUv, texel, compositeWeight)
|
|
932
|
+
const left = this.sampleSignalColor(
|
|
933
|
+
clamp(sampleUv.sub(xOffset), vec2(0, 0), vec2(1, 1)),
|
|
934
|
+
texel,
|
|
935
|
+
compositeWeight,
|
|
936
|
+
)
|
|
937
|
+
const right = this.sampleSignalColor(
|
|
938
|
+
clamp(sampleUv.add(xOffset), vec2(0, 0), vec2(1, 1)),
|
|
939
|
+
texel,
|
|
940
|
+
compositeWeight,
|
|
941
|
+
)
|
|
942
|
+
const up = this.sampleSignalColor(
|
|
943
|
+
clamp(sampleUv.sub(yOffset), vec2(0, 0), vec2(1, 1)),
|
|
944
|
+
texel,
|
|
945
|
+
compositeWeight,
|
|
946
|
+
)
|
|
947
|
+
const down = this.sampleSignalColor(
|
|
948
|
+
clamp(sampleUv.add(yOffset), vec2(0, 0), vec2(1, 1)),
|
|
949
|
+
texel,
|
|
950
|
+
compositeWeight,
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
return center.mul(0.42)
|
|
954
|
+
.add(left.add(right).mul(0.2))
|
|
955
|
+
.add(up.add(down).mul(0.09))
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
private sampleHalation(sampleUv: Node, texel: Node, compositeWeight: Node): Node {
|
|
959
|
+
const haloX = vec2(texel.x.mul(2.2), float(0))
|
|
960
|
+
const haloY = vec2(float(0), texel.y.mul(1.6))
|
|
961
|
+
const left = this.sampleSignalColor(
|
|
962
|
+
clamp(sampleUv.sub(haloX), vec2(0, 0), vec2(1, 1)),
|
|
963
|
+
texel,
|
|
964
|
+
compositeWeight,
|
|
965
|
+
)
|
|
966
|
+
const right = this.sampleSignalColor(
|
|
967
|
+
clamp(sampleUv.add(haloX), vec2(0, 0), vec2(1, 1)),
|
|
968
|
+
texel,
|
|
969
|
+
compositeWeight,
|
|
970
|
+
)
|
|
971
|
+
const up = this.sampleSignalColor(
|
|
972
|
+
clamp(sampleUv.sub(haloY), vec2(0, 0), vec2(1, 1)),
|
|
973
|
+
texel,
|
|
974
|
+
compositeWeight,
|
|
975
|
+
)
|
|
976
|
+
const down = this.sampleSignalColor(
|
|
977
|
+
clamp(sampleUv.add(haloY), vec2(0, 0), vec2(1, 1)),
|
|
978
|
+
texel,
|
|
979
|
+
compositeWeight,
|
|
980
|
+
)
|
|
981
|
+
return left.add(right).add(up).add(down).mul(0.25)
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
private sampleSignalColor(sampleUv: Node, texel: Node, compositeWeight: Node): Node {
|
|
985
|
+
const sampleOffset = vec2(texel.x.mul(1.35), float(0))
|
|
986
|
+
const center = this.trackSourceTextureNode(sampleUv)
|
|
987
|
+
const left = this.trackSourceTextureNode(
|
|
988
|
+
clamp(sampleUv.sub(sampleOffset), vec2(0, 0), vec2(1, 1)),
|
|
989
|
+
)
|
|
990
|
+
const right = this.trackSourceTextureNode(
|
|
991
|
+
clamp(sampleUv.add(sampleOffset), vec2(0, 0), vec2(1, 1)),
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
const centerColor = vec3(float(center.r), float(center.g), float(center.b))
|
|
995
|
+
const leftColor = vec3(float(left.r), float(left.g), float(left.b))
|
|
996
|
+
const rightColor = vec3(float(right.r), float(right.g), float(right.b))
|
|
997
|
+
const avgColor = centerColor.mul(2).add(leftColor).add(rightColor).div(4)
|
|
998
|
+
|
|
999
|
+
const lumaCenter = this.luma(centerColor)
|
|
1000
|
+
const lumaAvg = this.luma(avgColor)
|
|
1001
|
+
const chromaBlur = avgColor.sub(vec3(lumaAvg, lumaAvg, lumaAvg))
|
|
1002
|
+
const lumaSignal = vec3(
|
|
1003
|
+
mix(lumaCenter, lumaAvg, this.signalArtifactsUniform.mul(0.28)),
|
|
1004
|
+
mix(lumaCenter, lumaAvg, this.signalArtifactsUniform.mul(0.28)),
|
|
1005
|
+
mix(lumaCenter, lumaAvg, this.signalArtifactsUniform.mul(0.28)),
|
|
1006
|
+
)
|
|
1007
|
+
const ringing = centerColor.sub(avgColor).mul(this.signalArtifactsUniform.mul(0.3))
|
|
1008
|
+
const compositeColor = clamp(
|
|
1009
|
+
lumaSignal.add(chromaBlur.mul(0.92)).add(ringing),
|
|
1010
|
+
vec3(0, 0, 0),
|
|
1011
|
+
vec3(1, 1, 1),
|
|
1012
|
+
)
|
|
1013
|
+
const signalColor = mix(centerColor, compositeColor, compositeWeight.mul(this.signalArtifactsUniform))
|
|
1014
|
+
const signalLuma = this.luma(signalColor)
|
|
1015
|
+
const neutralSignal = vec3(signalLuma, signalLuma, signalLuma)
|
|
1016
|
+
const signalChroma = signalColor.sub(neutralSignal)
|
|
1017
|
+
const liftedLuma = signalLuma.add(
|
|
1018
|
+
smoothstep(float(0.38), float(0.02), signalLuma)
|
|
1019
|
+
.mul(this.shadowLiftUniform)
|
|
1020
|
+
.mul(float(0.08)),
|
|
1021
|
+
)
|
|
1022
|
+
return clamp(
|
|
1023
|
+
vec3(liftedLuma, liftedLuma, liftedLuma).add(signalChroma),
|
|
1024
|
+
vec3(0, 0, 0),
|
|
1025
|
+
vec3(1, 1, 1),
|
|
1026
|
+
)
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
private trackHistoryTextureNode(uvNode: Node): Node {
|
|
1030
|
+
const historyTextureNode = tslTexture(this.placeholder, uvNode)
|
|
1031
|
+
this.historyTextureNodes.push(historyTextureNode)
|
|
1032
|
+
return historyTextureNode
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
private trackSourceTextureNode(uvNode: Node): Node {
|
|
1036
|
+
const sourceTextureNode = tslTexture(this.placeholder, uvNode)
|
|
1037
|
+
this.sourceTextureNodes.push(sourceTextureNode)
|
|
1038
|
+
return sourceTextureNode
|
|
1039
|
+
}
|
|
1040
|
+
}
|