@voluma/vlam 0.1.0 → 0.2.1
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/LICENSE +21 -0
- package/README.md +68 -0
- package/THIRD_PARTY_NOTICES.md +86 -0
- package/dist/budget-governor.d.ts +154 -0
- package/dist/camera-budget-governor.d.ts +234 -0
- package/dist/chunk-cache-budget.d.ts +156 -0
- package/dist/chunk-fetch-scheduler.d.ts +168 -0
- package/dist/chunk-loader.d.ts +85 -0
- package/dist/compute-sorter.d.ts +131 -0
- package/dist/create-splat-renderer.d.ts +99 -0
- package/dist/dataset-source.d.ts +55 -0
- package/dist/depth-of-field.d.ts +73 -0
- package/dist/effects.d.ts +337 -0
- package/dist/effects.js +180 -0
- package/dist/effects.js.map +1 -0
- package/dist/formats/ksplat/index.d.ts +8 -0
- package/dist/formats/ksplat/parse-ksplat.d.ts +9 -0
- package/dist/formats/ksplat.js +167 -0
- package/dist/formats/ksplat.js.map +1 -0
- package/dist/formats/lcc/collision-mesh.d.ts +37 -0
- package/dist/formats/lcc/collision-partition.d.ts +35 -0
- package/dist/formats/lcc/index.d.ts +16 -0
- package/dist/formats/lcc/lcc.d.ts +28 -0
- package/dist/formats/lcc/lcc2-transform.d.ts +18 -0
- package/dist/formats/lcc/lcc2.d.ts +4 -0
- package/dist/formats/lcc/parse-collision-lci.d.ts +28 -0
- package/dist/formats/lcc/parse-lcc.d.ts +177 -0
- package/dist/formats/lcc/parse-mesh-ply.d.ts +21 -0
- package/dist/formats/lcc.js +969 -0
- package/dist/formats/lcc.js.map +1 -0
- package/dist/formats/ply/index.d.ts +12 -0
- package/dist/formats/ply/parse-compressed-ply.d.ts +16 -0
- package/dist/formats/ply/parse-splat-ply.d.ts +46 -0
- package/dist/formats/ply.js +230 -0
- package/dist/formats/ply.js.map +1 -0
- package/dist/formats/rad/frontier-worker-protocol.d.ts +177 -0
- package/dist/formats/rad/index.d.ts +10 -0
- package/dist/formats/rad/parse-rad.d.ts +88 -0
- package/dist/formats/rad/rad-column-decoders.d.ts +15 -0
- package/dist/formats/rad/rad-foveated-source.d.ts +71 -0
- package/dist/formats/rad/rad-parent-sizes.d.ts +45 -0
- package/dist/formats/rad/rad.d.ts +132 -0
- package/dist/formats/rad.js +1098 -0
- package/dist/formats/rad.js.map +1 -0
- package/dist/formats/sog/index.d.ts +9 -0
- package/dist/formats/sog/parse-sog.d.ts +45 -0
- package/dist/formats/sog.js +257 -0
- package/dist/formats/sog.js.map +1 -0
- package/dist/formats/splat/index.d.ts +8 -0
- package/dist/formats/splat/parse-splat.d.ts +10 -0
- package/dist/formats/splat.js +39 -0
- package/dist/formats/splat.js.map +1 -0
- package/dist/formats/spz/index.d.ts +8 -0
- package/dist/formats/spz/parse-spz.d.ts +10 -0
- package/dist/formats/spz.js +218 -0
- package/dist/formats/spz.js.map +1 -0
- package/dist/frontier-worker-CAnK_Cmu.js +566 -0
- package/dist/frontier-worker-CAnK_Cmu.js.map +1 -0
- package/dist/half-float-Cs4FDq8Q.js +30 -0
- package/dist/half-float-Cs4FDq8Q.js.map +1 -0
- package/dist/half-float.d.ts +15 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +9054 -0
- package/dist/index.js.map +1 -0
- package/dist/load-scene.d.ts +50 -0
- package/dist/load-worker-protocol.d.ts +82 -0
- package/dist/loading-wLo8vRbA.js +102 -0
- package/dist/loading-wLo8vRbA.js.map +1 -0
- package/dist/loading.d.ts +150 -0
- package/dist/lod-manifest.d.ts +61 -0
- package/dist/lod-scheduler.d.ts +229 -0
- package/dist/lod-source.d.ts +207 -0
- package/dist/logging-BfPdd7NJ.js +19 -0
- package/dist/logging-BfPdd7NJ.js.map +1 -0
- package/dist/logging.d.ts +37 -0
- package/dist/one-shot-worker-G80btnth.js +664 -0
- package/dist/one-shot-worker-G80btnth.js.map +1 -0
- package/dist/orientation.d.ts +35 -0
- package/dist/ply-header-BwgExCn-.js +125 -0
- package/dist/ply-header-BwgExCn-.js.map +1 -0
- package/dist/ply-header.d.ts +87 -0
- package/dist/radix-sort.d.ts +28 -0
- package/dist/radix-sorter.d.ts +46 -0
- package/dist/relighting.d.ts +50 -0
- package/dist/selection-volume.d.ts +87 -0
- package/dist/sh-pack-D5wAe5gg.js +54 -0
- package/dist/sh-pack-D5wAe5gg.js.map +1 -0
- package/dist/sh-pack.d.ts +49 -0
- package/dist/sort-scheduler.d.ts +62 -0
- package/dist/sort-worker-protocol.d.ts +37 -0
- package/dist/sorter.d.ts +31 -0
- package/dist/source-transform.d.ts +57 -0
- package/dist/splat-budget-PSojLJPO.js +157 -0
- package/dist/splat-budget-PSojLJPO.js.map +1 -0
- package/dist/splat-budget.d.ts +404 -0
- package/dist/splat-data-BZM5dhhx.js +10 -0
- package/dist/splat-data-BZM5dhhx.js.map +1 -0
- package/dist/splat-data.d.ts +150 -0
- package/dist/splat-depth-pack.d.ts +39 -0
- package/dist/splat-mesh-material.d.ts +255 -0
- package/dist/splat-mesh-picking.d.ts +87 -0
- package/dist/splat-mesh-pool.d.ts +241 -0
- package/dist/splat-mesh-types-8QDjTbZO.js +390 -0
- package/dist/splat-mesh-types-8QDjTbZO.js.map +1 -0
- package/dist/splat-mesh-types.d.ts +390 -0
- package/dist/splat-mesh.d.ts +737 -0
- package/dist/splat-modifier-stack.d.ts +38 -0
- package/dist/splat-modifier.d.ts +156 -0
- package/dist/splat-partition.d.ts +37 -0
- package/dist/splat-query.d.ts +51 -0
- package/dist/splat-scene.d.ts +127 -0
- package/dist/splat-sort-bounds.d.ts +11 -0
- package/dist/static-lod-splat-mesh.d.ts +53 -0
- package/dist/static-lod-worker-D2fyqQZO.js +474 -0
- package/dist/static-lod-worker-D2fyqQZO.js.map +1 -0
- package/dist/static-lod-worker-protocol.d.ts +37 -0
- package/dist/static-lod.d.ts +24 -0
- package/dist/storage-attribute-mirror.d.ts +103 -0
- package/dist/streamed-splat-mesh-utils.d.ts +146 -0
- package/dist/streamed-splat-mesh.d.ts +985 -0
- package/dist/unified-splat-renderer.d.ts +205 -0
- package/dist/webgpu-limits.d.ts +133 -0
- package/dist/work-buffer-gather.d.ts +119 -0
- package/dist/work-buffer-material.d.ts +42 -0
- package/dist/worker-sorter.d.ts +57 -0
- package/dist/xr-view.d.ts +92 -0
- package/package.json +135 -21
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type SplatRequestOptions, type StreamedSplatFormat } from './loading';
|
|
2
|
+
/**
|
|
3
|
+
* Where a streamed dataset's files come from.
|
|
4
|
+
*
|
|
5
|
+
* A streamed scene is never one file: it is a manifest plus sidecars and chunk
|
|
6
|
+
* files that the manifest names *relatively* (`data.bin`, `data/3dgs/x.sog`,
|
|
7
|
+
* `0_0/meta.json`). Over HTTP those resolve against the manifest's URL; from a
|
|
8
|
+
* dropped folder there is no URL to resolve against, only `File` objects.
|
|
9
|
+
*
|
|
10
|
+
* This is the seam between the two. Everything downstream - including the
|
|
11
|
+
* worker's ranged chunk reads - keeps working on plain URLs, because a
|
|
12
|
+
* dropped file's `blob:` URL answers `Range` with a real 206 (verified on
|
|
13
|
+
* Chromium/WebKit/Gecko), so only *name resolution* has to differ.
|
|
14
|
+
*/
|
|
15
|
+
export interface SplatDatasetSource {
|
|
16
|
+
/** Fetchable URL of the manifest itself. */
|
|
17
|
+
readonly manifestUrl: string;
|
|
18
|
+
/** Fetchable URL for a dataset-relative path, or null when absent. */
|
|
19
|
+
resolve(path: string): string | null;
|
|
20
|
+
/** Byte length of a file, or null when absent. */
|
|
21
|
+
size(path: string): Promise<number | null>;
|
|
22
|
+
/**
|
|
23
|
+
* The files inside a chunk *directory* (unbundled SOG), as
|
|
24
|
+
* `name → fetchable URL`. Null when the chunk should be fetched by URL
|
|
25
|
+
* instead - the HTTP case, where the directory is a real path.
|
|
26
|
+
*/
|
|
27
|
+
directoryFiles(path: string): Record<string, string> | null;
|
|
28
|
+
/** Releases any resources held for this dataset (e.g. blob URLs). */
|
|
29
|
+
dispose(): void;
|
|
30
|
+
}
|
|
31
|
+
/** A dataset served over HTTP, resolved against the manifest's URL. */
|
|
32
|
+
export declare function httpDatasetSource(manifestUrl: string, request?: SplatRequestOptions): SplatDatasetSource;
|
|
33
|
+
/** A dataset picked out of a dropped folder. */
|
|
34
|
+
export interface LocalDataset {
|
|
35
|
+
readonly source: SplatDatasetSource;
|
|
36
|
+
/** The streamed format the folder's manifest identifies it as. */
|
|
37
|
+
readonly format: Exclude<StreamedSplatFormat, 'auto'>;
|
|
38
|
+
/** The manifest's file name, for display. */
|
|
39
|
+
readonly name: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Builds a dataset from a dropped folder's files, keyed by their paths
|
|
43
|
+
* relative to the folder root.
|
|
44
|
+
*
|
|
45
|
+
* Every file gets a `blob:` URL, so the rest of the pipeline - including the
|
|
46
|
+
* worker's ranged reads into a multi-hundred-megabyte `data.bin` - is
|
|
47
|
+
* identical to the HTTP path. Nothing is copied or read here: a blob URL is a
|
|
48
|
+
* handle to the file on disk, so a 300 MB `data.bin` costs nothing until a
|
|
49
|
+
* chunk actually reads a range out of it.
|
|
50
|
+
*
|
|
51
|
+
* @throws {SplatLoadError} with `phase: 'manifest'` when the folder holds no
|
|
52
|
+
* recognizable manifest, or more than one (which would make the choice
|
|
53
|
+
* arbitrary). Neither is retryable: the same folder fails the same way.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createLocalDataset(files: ReadonlyMap<string, File>): LocalDataset;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core projected-2D depth of field (post-EWA isotropic CoC).
|
|
3
|
+
*
|
|
4
|
+
* Spark-style pinhole CoC:
|
|
5
|
+
* focusBlur = |depth − focus| / depth
|
|
6
|
+
* apertureRadius = focalPx · tan(0.5 · apertureAngle)
|
|
7
|
+
* cocRadius = focusBlur · apertureRadius
|
|
8
|
+
* blurVariance = clamp(cocRadius², …, maxRadius²)
|
|
9
|
+
*
|
|
10
|
+
* `/ depth` is Spark's exact falloff (`splatVertex.glsl`), restored for visual
|
|
11
|
+
* parity: scenes authored against Spark's foreground defocus rendered flat here
|
|
12
|
+
* under the previous `/ max(depth, focus)`, which capped focusBlur at 1 and so
|
|
13
|
+
* limited the CoC to a single apertureRadius - sub-pixel at the aperture sizes
|
|
14
|
+
* ModifyCamera produces. Behind the focus plane the two forms are identical
|
|
15
|
+
* (`max(depth, focus) === depth`), so only the near side changes.
|
|
16
|
+
*
|
|
17
|
+
* Note `/ depth` is unbounded as depth → 0. The fill-rate guard is the same one
|
|
18
|
+
* Spark uses: {@link MAX_DOF_RADIUS_PX} clamps the CoC. Do not "fix" the near
|
|
19
|
+
* side by dividing by focus instead - that pushes every *background* splat to
|
|
20
|
+
* the cap when zooming out with a near focus plane (huge quads → fill-rate
|
|
21
|
+
* death), which is what the bounded form was originally guarding against.
|
|
22
|
+
*
|
|
23
|
+
* Voluma's ModifyCamera `apertureSize` maps to an angle through the *live*
|
|
24
|
+
* focus distance - `apertureAngle = 2·atan(0.5·size / focus)` - matching the
|
|
25
|
+
* host helper Voluma ships for Spark. A fixed reference distance was tried
|
|
26
|
+
* instead to stop near focus planes from
|
|
27
|
+
* ballooning every splat, but it decouples aperture from focus and so flattens
|
|
28
|
+
* the intended effect: racking focus toward the camera is exactly how authors
|
|
29
|
+
* get the whole scene to go soft. Keep the mapping focus-relative; the CoC cap
|
|
30
|
+
* is what bounds fill-rate.
|
|
31
|
+
*
|
|
32
|
+
* Unlike the M13 `depthOfFieldPreset` modifier (3D scale), this path adds
|
|
33
|
+
* `coc²·I` to the projected 2D covariance with `√(det)` opacity fade.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Soft CoC radius cap in pixels (Spark clamps with `maxPixelRadius`).
|
|
37
|
+
* Kept modest for interactive fill-rate; raise only if cinematic stills need it.
|
|
38
|
+
*/
|
|
39
|
+
export declare const MAX_DOF_RADIUS_PX = 24;
|
|
40
|
+
/** Cap on CoC variance (px²) added to the projected 2D covariance diagonal. */
|
|
41
|
+
export declare const MAX_DOF_VARIANCE: number;
|
|
42
|
+
/** Live camera depth-of-field settings for {@link SplatMesh.setDepthOfField}. */
|
|
43
|
+
export type DepthOfFieldSettings = {
|
|
44
|
+
/** View-space distance to the focal plane (world units). Must stay positive. */
|
|
45
|
+
focusDistance: number;
|
|
46
|
+
/** Aperture size in the same units as Voluma `DofSettings.apertureSize`. `0` = off. */
|
|
47
|
+
aperture: number;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Maps Voluma aperture size → full aperture angle (radians) through the focus
|
|
51
|
+
* distance, identical to the host helper Voluma ships for Spark:
|
|
52
|
+
* `2·atan(0.5·size / focus)`. Pulling focus toward the camera widens the angle,
|
|
53
|
+
* which is what makes a near focus plane soften the whole scene.
|
|
54
|
+
*/
|
|
55
|
+
export declare function apertureAngleFromSize(aperture: number, focusDistance: number): number;
|
|
56
|
+
/**
|
|
57
|
+
* Circle-of-confusion variance in px² for a splat at view-space depth.
|
|
58
|
+
* Returns `0` when aperture is disabled. Mirrors Spark's vertex DoF branch.
|
|
59
|
+
*/
|
|
60
|
+
export declare function computeDofCocVariancePx2(options: {
|
|
61
|
+
depth: number;
|
|
62
|
+
focusDistance: number;
|
|
63
|
+
aperture: number;
|
|
64
|
+
focalPx: number;
|
|
65
|
+
maxVariance?: number;
|
|
66
|
+
}): number;
|
|
67
|
+
/**
|
|
68
|
+
* Opacity fade that conserves Gaussian mass after isotropic screen-space
|
|
69
|
+
* dilation: `√(detRaw / detBlur)`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function computeDofOpacityFade(detRaw: number, detBlur: number): number;
|
|
72
|
+
/** Clamps host-supplied DoF settings before writing live uniforms. */
|
|
73
|
+
export declare function clampDepthOfFieldSettings(settings: Partial<DepthOfFieldSettings>, previous?: DepthOfFieldSettings): DepthOfFieldSettings;
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@voluma/vlam/effects` - optional, tree-shakeable effects built on the M7
|
|
3
|
+
* {@link SplatModifier} contract. Import only what you use; nothing here is
|
|
4
|
+
* pulled into the core renderer bundle.
|
|
5
|
+
*
|
|
6
|
+
* - {@link sdfEffects}: a declarative sphere/box/cylinder shape list evaluated
|
|
7
|
+
* in-shader from a bounded uniform array (a fixed unroll over `maxShapes`
|
|
8
|
+
* slots, gated by the live count) - add/move/remove shapes is data-only
|
|
9
|
+
* (no pipeline recompile), on WebGPU and the WebGL2 fallback alike (M7.4).
|
|
10
|
+
* The kinds mirror the CPU selection volumes (`createSelectionVolume`), so
|
|
11
|
+
* a host can highlight exactly what a selection would take.
|
|
12
|
+
* - {@link lightingPreset} / {@link revealPreset}: small reference presets,
|
|
13
|
+
* starting points rather than a framework (M7.5).
|
|
14
|
+
* - {@link createRelightingProxy}: builds a gray lit mesh group from collision
|
|
15
|
+
* tiles (or raw geometries) for PlayCanvas-style {@link SplatMesh.setRelighting}.
|
|
16
|
+
* - {@link createRelightingShadowFactorMaterial}: writes a shadow **multiplier**
|
|
17
|
+
* (1 = lit, <1 = umbra) so coverage does not statically tint splats.
|
|
18
|
+
* - {@link worldWarpPreset}: camera-centered sphere wrap (planet / bowl).
|
|
19
|
+
* - {@link depthOfFieldPreset}: stylized modifier-based depth-of-field (M13).
|
|
20
|
+
* For physically-modeled camera DoF prefer the core
|
|
21
|
+
* {@link SplatMesh.setDepthOfField} / `UnifiedSplatRenderer.setDepthOfField`
|
|
22
|
+
* path - this preset is the soft/approximate alternative built purely on
|
|
23
|
+
* the modifier hook.
|
|
24
|
+
*
|
|
25
|
+
* Every preset here reads positions from `ctx.localCenter` - mesh-local space,
|
|
26
|
+
* which inside a `SplatScene` is the splat's **placed** position. So a shape or
|
|
27
|
+
* a reveal spans every source as one scene instead of travelling with a moved
|
|
28
|
+
* one; see `docs/guide/effects-and-modifiers.md` for what that looks like, and
|
|
29
|
+
* `ctx.sourceCenter` for the opposite behaviour.
|
|
30
|
+
*
|
|
31
|
+
* See `docs/guide/effects-and-modifiers.md`.
|
|
32
|
+
*/
|
|
33
|
+
import * as THREE from 'three/webgpu';
|
|
34
|
+
import type { SplatModifier } from './splat-modifier';
|
|
35
|
+
import type { CollisionMeshTile } from './formats/lcc/collision-mesh';
|
|
36
|
+
/**
|
|
37
|
+
* A signed-distance primitive kind. `cylinder` is capped and runs along the
|
|
38
|
+
* shape-local Y axis (rotate it with {@link SdfShape.rotation}).
|
|
39
|
+
*/
|
|
40
|
+
export type SdfShapeKind = 'sphere' | 'box' | 'cylinder';
|
|
41
|
+
/**
|
|
42
|
+
* What a shape does to the splats it covers:
|
|
43
|
+
* - `tint` - blend the splat color toward the shape color.
|
|
44
|
+
* - `desaturate` - blend the splat color toward its luminance.
|
|
45
|
+
* - `hide` - fade the splat out (soft cutaway).
|
|
46
|
+
* - `rim` - tint only a band around the shape's surface.
|
|
47
|
+
*/
|
|
48
|
+
export type SdfMode = 'tint' | 'desaturate' | 'hide' | 'rim';
|
|
49
|
+
/** One SDF shape in a {@link sdfEffects} list. All spaces are mesh-local. */
|
|
50
|
+
export interface SdfShape {
|
|
51
|
+
kind: SdfShapeKind;
|
|
52
|
+
/** Shape center, mesh-local. Default `[0, 0, 0]`. */
|
|
53
|
+
center?: readonly [number, number, number];
|
|
54
|
+
/**
|
|
55
|
+
* Sphere/cylinder radius. Required for `kind: 'sphere'` and
|
|
56
|
+
* `kind: 'cylinder'`; must be positive - {@link SdfEffect.setShapes} throws
|
|
57
|
+
* otherwise.
|
|
58
|
+
*/
|
|
59
|
+
radius?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Cylinder full height along the shape-local Y axis. Required (and only
|
|
62
|
+
* used) for `kind: 'cylinder'`; must be positive - {@link SdfEffect.setShapes}
|
|
63
|
+
* throws otherwise.
|
|
64
|
+
*/
|
|
65
|
+
height?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Box half-extents. Required (and only used) for `kind: 'box'`; every
|
|
68
|
+
* component must be positive - {@link SdfEffect.setShapes} throws otherwise.
|
|
69
|
+
*/
|
|
70
|
+
halfExtents?: readonly [number, number, number];
|
|
71
|
+
/** Orientation quaternion `[x, y, z, w]` (boxes and cylinders). Default identity. */
|
|
72
|
+
rotation?: readonly [number, number, number, number];
|
|
73
|
+
/** Effect color (tint/rim). Default white. */
|
|
74
|
+
color?: readonly [number, number, number];
|
|
75
|
+
/** Soft-edge width in world units; 0 is a hard edge. Default `0`. */
|
|
76
|
+
falloff?: number;
|
|
77
|
+
/** Invert the test - affect the outside of the shape instead. */
|
|
78
|
+
invert?: boolean;
|
|
79
|
+
/** Effect strength in `[0, 1]`. Default `1`. */
|
|
80
|
+
strength?: number;
|
|
81
|
+
mode: SdfMode;
|
|
82
|
+
}
|
|
83
|
+
/** A live SDF effect: one modifier plus a data-only way to update its shapes. */
|
|
84
|
+
export interface SdfEffect {
|
|
85
|
+
/** Attach to `mesh.modifiers`. Built once; updating shapes never recompiles. */
|
|
86
|
+
readonly modifier: SplatModifier;
|
|
87
|
+
/**
|
|
88
|
+
* Replaces the shape list. Purely a uniform-buffer write, so animating
|
|
89
|
+
* shape poses/colors/thresholds every frame costs no pipeline recompile.
|
|
90
|
+
* Shapes beyond {@link maxShapes} are dropped with a one-time warning.
|
|
91
|
+
*
|
|
92
|
+
* Throws (before writing anything) if a sphere or cylinder lacks a positive
|
|
93
|
+
* `radius`, a cylinder lacks a positive `height`, or a box lacks positive
|
|
94
|
+
* `halfExtents` - a zero-size shape is always a caller bug, never a useful
|
|
95
|
+
* no-op.
|
|
96
|
+
*/
|
|
97
|
+
setShapes(shapes: readonly SdfShape[]): void;
|
|
98
|
+
/** The compiled-in shape capacity (see {@link sdfEffects} `maxShapes`). */
|
|
99
|
+
readonly maxShapes: number;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Builds a {@link SdfEffect} from a declarative shape list. Shapes live in a
|
|
103
|
+
* bounded uniform array and are tested in-shader against each splat's local
|
|
104
|
+
* center as a **fixed unroll over all `maxShapes` slots**, each slot gated by
|
|
105
|
+
* `slot < count` so unused slots contribute nothing. (An in-shader loop is
|
|
106
|
+
* not viable here - see the TSL varying constraint documented inside the
|
|
107
|
+
* modifier.) Adding, moving or removing a shape - or changing the count - is
|
|
108
|
+
* still a pure uniform write with no recompile. Works on WebGPU and the
|
|
109
|
+
* WebGL2 fallback.
|
|
110
|
+
*
|
|
111
|
+
* `maxShapes` is the compiled capacity (default 32). Because every slot is
|
|
112
|
+
* unrolled into the shader, raising it grows the shader's size and compile
|
|
113
|
+
* time (and the uniform array; very large values can exceed WebGL2's uniform
|
|
114
|
+
* limits), so keep it to what a scene needs.
|
|
115
|
+
*
|
|
116
|
+
* Shape positions are mesh-local. In a `SplatScene` that means scene
|
|
117
|
+
* coordinates - a shape overlapping two sources covers both as one continuous
|
|
118
|
+
* shape - so place them with `computeSplatBounds()`, which reports the sources
|
|
119
|
+
* at their current placement.
|
|
120
|
+
*/
|
|
121
|
+
export declare function sdfEffects(initialShapes?: readonly SdfShape[], options?: {
|
|
122
|
+
maxShapes?: number;
|
|
123
|
+
}): SdfEffect;
|
|
124
|
+
/** A preset that exposes a live light direction uniform. */
|
|
125
|
+
export interface LightingPreset {
|
|
126
|
+
readonly modifier: SplatModifier;
|
|
127
|
+
/**
|
|
128
|
+
* Mesh-local light direction; mutate `.value` to animate (no recompile).
|
|
129
|
+
* Inside a `SplatScene` that frame is the scene's, so one direction lights
|
|
130
|
+
* every source consistently however they are posed.
|
|
131
|
+
*/
|
|
132
|
+
readonly direction: {
|
|
133
|
+
value: THREE.Vector3;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Simple per-splat Lambertian shading using the splat's approximate normal
|
|
138
|
+
* (`ctx.normal`, the least-variance covariance axis). A starting point for
|
|
139
|
+
* relit product shots - not a lighting rig. Works on both backends.
|
|
140
|
+
*
|
|
141
|
+
* In a `SplatScene` the normal comes from the *placed* covariance, so a source
|
|
142
|
+
* that rotates relights as it turns.
|
|
143
|
+
*/
|
|
144
|
+
export declare function lightingPreset(options?: {
|
|
145
|
+
direction?: readonly [number, number, number];
|
|
146
|
+
ambient?: number;
|
|
147
|
+
diffuse?: number;
|
|
148
|
+
}): LightingPreset;
|
|
149
|
+
/** A live depth-of-field preset: focus plane and aperture are mutable uniforms. */
|
|
150
|
+
export interface DepthOfFieldPreset {
|
|
151
|
+
readonly modifier: SplatModifier;
|
|
152
|
+
/** World-unit distance to the focal plane; splats there stay sharp. Mutate
|
|
153
|
+
* `.value` to rack focus (no recompile). Must stay positive. */
|
|
154
|
+
readonly focusDistance: {
|
|
155
|
+
value: number;
|
|
156
|
+
};
|
|
157
|
+
/** Blur strength (aperture-like); 0 is no blur. Mutate `.value`. */
|
|
158
|
+
readonly aperture: {
|
|
159
|
+
value: number;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Depth-of-field on the {@link SplatModifier} hook - **stylized / soft** path
|
|
164
|
+
* (M13). Prefer {@link SplatMesh.setDepthOfField} / the core projected-2D
|
|
165
|
+
* aperture model for camera DoF (isotropic screen-space CoC with √det opacity).
|
|
166
|
+
*
|
|
167
|
+
* A splat's circle of confusion grows with how far its view-space depth
|
|
168
|
+
* sits from `focusDistance`; the modifier blurs it by enlarging it
|
|
169
|
+
* (`scale = 1 + coc`) and, because the material scales the projected covariance
|
|
170
|
+
* by `scale²`, divides opacity by `scale²` so the widened splat conserves its
|
|
171
|
+
* integrated mass instead of over-brightening. Focus and aperture are live
|
|
172
|
+
* uniforms, so racking focus every frame costs no recompile. Pure TSL - works
|
|
173
|
+
* on WebGPU and the WebGL2 fallback.
|
|
174
|
+
*
|
|
175
|
+
* This is an *approximate* DoF: `scale` is a uniform 3D-covariance multiplier,
|
|
176
|
+
* so the blur enlarges the splat's own (possibly anisotropic) footprint rather
|
|
177
|
+
* than adding a truly isotropic screen-space disc. The physically exact aperture
|
|
178
|
+
* model adds `coc²·I` to the *projected 2D* covariance with the same √(det)
|
|
179
|
+
* opacity conservation the antialias filter uses - that path is
|
|
180
|
+
* `SplatMesh.setDepthOfField` / `UnifiedSplatRenderer.setDepthOfField`
|
|
181
|
+
* (see `docs/guide/effects-and-modifiers.md`, M13).
|
|
182
|
+
*
|
|
183
|
+
* Opacity conservation is exact only with `antialias` on: without it, the
|
|
184
|
+
* fixed 0.3-px dilation dominates sub-pixel splats, so strong blur *dims*
|
|
185
|
+
* distant splats (their footprint barely grows while alpha still falls by
|
|
186
|
+
* 1/scale²) rather than blurring them.
|
|
187
|
+
*/
|
|
188
|
+
export declare function depthOfFieldPreset(options?: {
|
|
189
|
+
focusDistance?: number;
|
|
190
|
+
aperture?: number;
|
|
191
|
+
maxBlur?: number;
|
|
192
|
+
}): DepthOfFieldPreset;
|
|
193
|
+
/** A preset that exposes a reveal `progress` uniform in `[0, 1]`. */
|
|
194
|
+
export interface RevealPreset {
|
|
195
|
+
readonly modifier: SplatModifier;
|
|
196
|
+
/** Reveal progress; 0 hides everything, 1 shows everything. */
|
|
197
|
+
readonly progress: {
|
|
198
|
+
value: number;
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Time-driven dissolve/reveal. A value-noise field over the splat's local
|
|
203
|
+
* position is thresholded against `progress`: splats whose noise is below the
|
|
204
|
+
* threshold are shown, with a soft alpha edge. The noise is a `wgslFn` chunk -
|
|
205
|
+
* the M7 escape hatch - so this preset is **WebGPU-only** (see the fallback
|
|
206
|
+
* notes); force WebGL2 and it will not compile.
|
|
207
|
+
*
|
|
208
|
+
* The field is anchored to the mesh, so in a `SplatScene` a moved source
|
|
209
|
+
* reveals in step with whatever it now sits beside (and the pattern slides
|
|
210
|
+
* across it as it travels) rather than carrying its own schedule around.
|
|
211
|
+
*/
|
|
212
|
+
export declare function revealPreset(options?: {
|
|
213
|
+
frequency?: number;
|
|
214
|
+
edge?: number;
|
|
215
|
+
}): RevealPreset;
|
|
216
|
+
/** A live world-warp preset: signed intensity and radius are mutable uniforms. */
|
|
217
|
+
export interface WorldWarpPreset {
|
|
218
|
+
readonly modifier: SplatModifier;
|
|
219
|
+
/**
|
|
220
|
+
* Signed warp amount in `[-1, 1]`. Positive wraps the street *down* so you
|
|
221
|
+
* walk on top of the sphere (tiny planet); negative wraps it *up* so you
|
|
222
|
+
* stand in the bowl (Inception fold). `0` is identity. Mutate `.value` to
|
|
223
|
+
* animate (no recompile).
|
|
224
|
+
*/
|
|
225
|
+
readonly intensity: {
|
|
226
|
+
value: number;
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* Distance at which the wrap becomes strong. Nearby splats (`depth ≪
|
|
230
|
+
* radius`) stay put so walk/fly controls still read as moving through the
|
|
231
|
+
* unwarped near field. Must stay positive. Mutate `.value`.
|
|
232
|
+
*/
|
|
233
|
+
readonly radius: {
|
|
234
|
+
value: number;
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Camera-centered splat warp on the {@link SplatModifier} hook: one signed
|
|
239
|
+
* `intensity` uniform, two opposite hinges of the same view-space wrap. The
|
|
240
|
+
* camera is not moved - only splat centers (and their rigid orientation)
|
|
241
|
+
* change, so WASD / orbit stay as they are.
|
|
242
|
+
*
|
|
243
|
+
* Both sides hinge view-space `(y, z)` around camera +X by
|
|
244
|
+
* `(π/2) · atan(depth / radius)`:
|
|
245
|
+
*
|
|
246
|
+
* - `intensity > 0` - planet: the far street wraps *down* under your feet so
|
|
247
|
+
* you walk on top of the sphere.
|
|
248
|
+
* - `intensity < 0` - fold: the far street wraps *up* over your head so you
|
|
249
|
+
* stand in the bowl.
|
|
250
|
+
* - `intensity = 0` is identity (`offset = 0`, rotation unchanged).
|
|
251
|
+
*
|
|
252
|
+
* The shader mixes each full-strength hinge toward the original position by
|
|
253
|
+
* `|intensity|`. Orientation follows the orthonormalized Jacobian so
|
|
254
|
+
* ellipsoids wrap with the surface rather than sliding. Pure TSL - WebGPU
|
|
255
|
+
* and the WebGL2 fallback.
|
|
256
|
+
*
|
|
257
|
+
* Displaced splats keep their **pre-displacement** depth-sort order (the
|
|
258
|
+
* modifier-stack contract). Extreme warps can composite in the wrong order;
|
|
259
|
+
* this preset does not re-sort on warped centers.
|
|
260
|
+
*/
|
|
261
|
+
export declare function worldWarpPreset(options?: {
|
|
262
|
+
intensity?: number;
|
|
263
|
+
radius?: number;
|
|
264
|
+
}): WorldWarpPreset;
|
|
265
|
+
/** Handle returned by {@link createRelightingProxy}. */
|
|
266
|
+
export interface RelightingProxy {
|
|
267
|
+
/** World-space group of lit proxy meshes (hide from the main splat scene). */
|
|
268
|
+
readonly group: THREE.Group;
|
|
269
|
+
/**
|
|
270
|
+
* Configures a material for the relight coverage pass: gray albedo and
|
|
271
|
+
* opaque alpha so uncovered RT pixels stay at clear-alpha 0.
|
|
272
|
+
*/
|
|
273
|
+
configureMaterial(material: THREE.Material): void;
|
|
274
|
+
/** Disposes geometries and the default material owned by this helper. */
|
|
275
|
+
dispose(): void;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Node material for a **shadow-factor** lighting RT used with
|
|
279
|
+
* {@link SplatMesh.setRelighting}.
|
|
280
|
+
*
|
|
281
|
+
* Fragment output is `vec4(vec3(mix(umbra, 1, shadow(light))), 1)`:
|
|
282
|
+
* - covered + lit → RGB ≈ 1 (identity modulate when brightness/background are 1)
|
|
283
|
+
* - covered + umbra → RGB ≈ `umbra` (soft darken; never pure black)
|
|
284
|
+
* - uncovered stays clear-alpha 0 from the RT clear
|
|
285
|
+
*
|
|
286
|
+
* Clear the lighting RT to **RGB 1, A 0** (not black). Softness / bilinear
|
|
287
|
+
* samples at coverage edges otherwise pull in black and draw a dark outline
|
|
288
|
+
* of every collision triangle.
|
|
289
|
+
*
|
|
290
|
+
* Prefer this over a lit MeshStandard / ShadowMaterial pass when the host wants
|
|
291
|
+
* cast umbras without a static dark stamp on the whole collision footprint.
|
|
292
|
+
* Assign to proxy meshes with `castShadow` and `receiveShadow` both true; the
|
|
293
|
+
* umbra shape follows those triangles (splat foliage cannot cast).
|
|
294
|
+
*
|
|
295
|
+
* @param light - Shadow-casting light (typically a {@link THREE.DirectionalLight}).
|
|
296
|
+
* @param options.umbra - Multiplier in full shadow, in `[0, 1]`. Default `0.45`.
|
|
297
|
+
*/
|
|
298
|
+
export declare function createRelightingShadowFactorMaterial(light: THREE.Light, options?: {
|
|
299
|
+
umbra?: number;
|
|
300
|
+
}): THREE.MeshStandardNodeMaterial;
|
|
301
|
+
/**
|
|
302
|
+
* Builds a PlayCanvas-style relighting **proxy** from collision tiles or raw
|
|
303
|
+
* geometries. The host places {@link RelightingProxy.group} on a dedicated
|
|
304
|
+
* layer / scene with lights (never the main splat scene), renders that into
|
|
305
|
+
* an RGBA RT matching the main camera, then calls
|
|
306
|
+
* {@link SplatMesh.setRelighting} with the RT texture.
|
|
307
|
+
*
|
|
308
|
+
* Collision meshes are a convenient stand-in when their silhouette is good
|
|
309
|
+
* enough; a denser reconstructed mesh is often better. This helper does not
|
|
310
|
+
* own lights or the render target.
|
|
311
|
+
*
|
|
312
|
+
* When `matrixWorld` is set (e.g. streamed mesh world matrix with the LCC
|
|
313
|
+
* Z-up→Y-up correction), it is **baked into the geometry** the same way the
|
|
314
|
+
* demo collision BVH path does - so the proxy aligns with splats without
|
|
315
|
+
* depending on a live Object3D transform.
|
|
316
|
+
*
|
|
317
|
+
* Coarse collision proxies leave hard coverage silhouettes; hosts should pass
|
|
318
|
+
* `RelightingSettings.softness` (and often a half-res lighting RT) so those
|
|
319
|
+
* edges do not look like a static shadow.
|
|
320
|
+
*
|
|
321
|
+
* For cast umbras **without** a static dark footprint, render the proxy with
|
|
322
|
+
* {@link createRelightingShadowFactorMaterial} so RT RGB is a multiplier
|
|
323
|
+
* (unshadowed ≈ 1), not a lit-gray appearance.
|
|
324
|
+
*/
|
|
325
|
+
export declare function createRelightingProxy(options?: {
|
|
326
|
+
/** LCC collision tiles (source-local). Prefer with `matrixWorld`. */
|
|
327
|
+
tiles?: readonly CollisionMeshTile[];
|
|
328
|
+
/** Extra or alternative geometries already in the desired frame. */
|
|
329
|
+
geometries?: readonly THREE.BufferGeometry[];
|
|
330
|
+
/**
|
|
331
|
+
* Source-local → world (e.g. streamed mesh `matrixWorld`). Baked into
|
|
332
|
+
* tile / cloned geometries; leave unset when geometries are already world.
|
|
333
|
+
*/
|
|
334
|
+
matrixWorld?: THREE.Matrix4;
|
|
335
|
+
/** Gray albedo in `[0, 1]`. Default `0.5` (PlayCanvas brightness 2). */
|
|
336
|
+
albedo?: number;
|
|
337
|
+
}): RelightingProxy;
|
package/dist/effects.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as g from "three/webgpu";
|
|
2
|
+
import { shadow as te, mix as x, float as i, vec4 as O, vec3 as h, uniform as C, wgslFn as se, uniformArray as ne, smoothstep as X, int as oe, mat3 as re, modelViewMatrix as ae, vec2 as ce } from "three/tsl";
|
|
3
|
+
import { w as ie } from "./logging-BfPdd7NJ.js";
|
|
4
|
+
const Q = 5, le = { tint: 0, desaturate: 1, hide: 2, rim: 3 }, de = [0, 0, 0], ue = [1, 1, 1], fe = [0, 0, 0, 1];
|
|
5
|
+
function ye(o = [], n = {}) {
|
|
6
|
+
const r = Math.max(1, Math.floor(n.maxShapes ?? 32)), t = [];
|
|
7
|
+
for (let e = 0; e < r * Q; e++) t.push(new g.Vector4());
|
|
8
|
+
const d = ne(t, "vec4"), f = C(0, "int");
|
|
9
|
+
let u = !1;
|
|
10
|
+
const l = (e) => {
|
|
11
|
+
const m = Math.min(e.length, r);
|
|
12
|
+
e.length > r && !u && (u = !0, ie(
|
|
13
|
+
`sdfEffects: ${e.length} shapes exceeds maxShapes=${r}; extra shapes are ignored. Raise maxShapes if you need more.`
|
|
14
|
+
));
|
|
15
|
+
for (let c = 0; c < m; c++) {
|
|
16
|
+
const s = e[c];
|
|
17
|
+
if (s.kind === "box") {
|
|
18
|
+
const a = s.halfExtents;
|
|
19
|
+
if (!a || !(a[0] > 0) || !(a[1] > 0) || !(a[2] > 0))
|
|
20
|
+
throw new Error(
|
|
21
|
+
`sdfEffects.setShapes: shape ${c} is a box and requires halfExtents with three positive components (got ${a ? `[${a.join(", ")}]` : "undefined"}).`
|
|
22
|
+
);
|
|
23
|
+
} else if (typeof s.radius == "number" && s.radius > 0) {
|
|
24
|
+
if (s.kind === "cylinder" && !(typeof s.height == "number" && s.height > 0))
|
|
25
|
+
throw new Error(
|
|
26
|
+
`sdfEffects.setShapes: shape ${c} is a cylinder and requires a positive height (got ${String(s.height)}).`
|
|
27
|
+
);
|
|
28
|
+
} else throw new Error(
|
|
29
|
+
`sdfEffects.setShapes: shape ${c} is a ${s.kind} and requires a positive radius (got ${String(s.radius)}).`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
for (let c = 0; c < m; c++) {
|
|
33
|
+
const s = e[c], a = c * Q, w = s.center ?? de, p = s.color ?? ue, y = s.rotation ?? fe, E = s.kind === "box" ? 1 : s.kind === "cylinder" ? 2 : 0;
|
|
34
|
+
if (t[a + 0].set(w[0], w[1], w[2], E), s.kind === "box") {
|
|
35
|
+
const v = s.halfExtents;
|
|
36
|
+
t[a + 1].set(v[0], v[1], v[2], s.falloff ?? 0);
|
|
37
|
+
} else s.kind === "cylinder" ? t[a + 1].set(
|
|
38
|
+
s.radius,
|
|
39
|
+
s.height / 2,
|
|
40
|
+
0,
|
|
41
|
+
s.falloff ?? 0
|
|
42
|
+
) : t[a + 1].set(s.radius, 0, 0, s.falloff ?? 0);
|
|
43
|
+
t[a + 2].set(p[0], p[1], p[2], le[s.mode]), t[a + 3].set(y[0], y[1], y[2], y[3]), t[a + 4].set(s.invert ? 1 : 0, s.strength ?? 1, 0, 0);
|
|
44
|
+
}
|
|
45
|
+
f.value = m;
|
|
46
|
+
};
|
|
47
|
+
return l(o), { modifier: (e) => {
|
|
48
|
+
const m = e.localCenter;
|
|
49
|
+
let c = e.color.rgb, s = i(1);
|
|
50
|
+
for (let a = 0; a < r; a++) {
|
|
51
|
+
const w = a * Q, p = (ee) => d.element(w + ee), y = p(0), E = p(1), v = p(2), R = p(3), D = p(4), U = y.xyz, P = y.w, T = E.xyz, $ = E.w.max(1e-4), B = v.xyz, M = v.w, L = D.x, N = D.y, V = oe(a).lessThan(f).select(i(1), i(0)), I = m.sub(U), z = xe(R, I), A = z.length().sub(T.x), k = me(z, T), q = he(z, T.x, T.y), F = P.greaterThan(0.5).and(P.lessThan(1.5)).select(i(1), i(0)), G = P.greaterThan(1.5).select(i(1), i(0)), W = x(x(A, k, F), q, G), _ = x(W, W.negate(), L), S = i(1).sub(X(0, $, _)).mul(N).mul(V), j = i(1).sub(X(0, $, _.abs())).mul(N).mul(V), H = M.lessThan(0.5).select(i(1), i(0)), J = M.greaterThan(0.5).and(M.lessThan(1.5)).select(i(1), i(0)), K = M.greaterThan(1.5).and(M.lessThan(2.5)).select(i(1), i(0)), Y = M.greaterThan(2.5).select(i(1), i(0)), Z = c.dot(h(0.2126, 0.7152, 0.0722));
|
|
52
|
+
c = x(c, B, S.mul(H)), c = x(c, h(Z), S.mul(J)), c = x(c, B, j.mul(Y)), s = s.mul(i(1).sub(S.mul(K)));
|
|
53
|
+
}
|
|
54
|
+
return { color: O(c, e.color.a.mul(s)) };
|
|
55
|
+
}, setShapes: l, maxShapes: r, _uniforms: { slots: t, count: f } };
|
|
56
|
+
}
|
|
57
|
+
function me(o, n) {
|
|
58
|
+
const r = o.abs().sub(n), t = r.max(h(0, 0, 0)).length(), d = r.x.max(r.y.max(r.z)).min(0);
|
|
59
|
+
return t.add(d);
|
|
60
|
+
}
|
|
61
|
+
function he(o, n, r) {
|
|
62
|
+
const t = o.xz.length().sub(n), d = o.y.abs().sub(r), f = ce(t.max(0), d.max(0)).length(), u = t.max(d).min(0);
|
|
63
|
+
return f.add(u);
|
|
64
|
+
}
|
|
65
|
+
function xe(o, n) {
|
|
66
|
+
const r = o.xyz, t = o.w, d = r.cross(n).mul(2);
|
|
67
|
+
return n.sub(d.mul(t)).add(r.cross(d));
|
|
68
|
+
}
|
|
69
|
+
function we(o = {}) {
|
|
70
|
+
const n = new g.Vector3(...o.direction ?? [0.3, 1, 0.6]).normalize(), r = C(n), t = i(o.ambient ?? 0.35), d = i(o.diffuse ?? 0.75);
|
|
71
|
+
return { modifier: (u) => {
|
|
72
|
+
const l = u.normal.dot(r.normalize()).max(0), b = t.add(l.mul(d));
|
|
73
|
+
return { color: O(u.color.rgb.mul(b), u.color.a) };
|
|
74
|
+
}, direction: r };
|
|
75
|
+
}
|
|
76
|
+
function ve(o = {}) {
|
|
77
|
+
const n = C(Math.max(1e-4, o.focusDistance ?? 10)), r = C(Math.max(0, o.aperture ?? 0.5)), t = i(Math.max(0, o.maxBlur ?? 6));
|
|
78
|
+
return { modifier: (f) => {
|
|
79
|
+
const u = f.viewCenter.z.negate().max(1e-4), l = n.max(1e-4), b = u.sub(l).abs().div(l).mul(r).min(t), e = i(1).add(b), m = f.color.a.div(e.mul(e));
|
|
80
|
+
return { scale: f.scale.mul(e), color: O(f.color.rgb, m) };
|
|
81
|
+
}, focusDistance: n, aperture: r };
|
|
82
|
+
}
|
|
83
|
+
function Me(o = {}) {
|
|
84
|
+
const n = C(0), r = o.frequency ?? 3, t = Math.min(Math.max(o.edge ?? 0.08, 0), 0.5), d = se(
|
|
85
|
+
/* wgsl */
|
|
86
|
+
`
|
|
87
|
+
fn vnoise( x: vec3<f32> ) -> f32 {
|
|
88
|
+
let i = floor( x );
|
|
89
|
+
let g = fract( x );
|
|
90
|
+
let u = g * g * ( 3.0 - 2.0 * g );
|
|
91
|
+
let k = vec3<f32>( 12.9898, 78.233, 37.719 );
|
|
92
|
+
let s = 43758.5453;
|
|
93
|
+
let h000 = fract( sin( dot( i + vec3<f32>( 0.0, 0.0, 0.0 ), k ) ) * s );
|
|
94
|
+
let h100 = fract( sin( dot( i + vec3<f32>( 1.0, 0.0, 0.0 ), k ) ) * s );
|
|
95
|
+
let h010 = fract( sin( dot( i + vec3<f32>( 0.0, 1.0, 0.0 ), k ) ) * s );
|
|
96
|
+
let h110 = fract( sin( dot( i + vec3<f32>( 1.0, 1.0, 0.0 ), k ) ) * s );
|
|
97
|
+
let h001 = fract( sin( dot( i + vec3<f32>( 0.0, 0.0, 1.0 ), k ) ) * s );
|
|
98
|
+
let h101 = fract( sin( dot( i + vec3<f32>( 1.0, 0.0, 1.0 ), k ) ) * s );
|
|
99
|
+
let h011 = fract( sin( dot( i + vec3<f32>( 0.0, 1.0, 1.0 ), k ) ) * s );
|
|
100
|
+
let h111 = fract( sin( dot( i + vec3<f32>( 1.0, 1.0, 1.0 ), k ) ) * s );
|
|
101
|
+
let x00 = mix( h000, h100, u.x );
|
|
102
|
+
let x10 = mix( h010, h110, u.x );
|
|
103
|
+
let x01 = mix( h001, h101, u.x );
|
|
104
|
+
let x11 = mix( h011, h111, u.x );
|
|
105
|
+
let y0 = mix( x00, x10, u.y );
|
|
106
|
+
let y1 = mix( x01, x11, u.y );
|
|
107
|
+
return mix( y0, y1, u.z );
|
|
108
|
+
}
|
|
109
|
+
`
|
|
110
|
+
);
|
|
111
|
+
return { modifier: (u) => {
|
|
112
|
+
const l = d({ x: u.localCenter.mul(r) }), b = l.mul(1 - 2 * t).add(t), e = X(b.sub(t), b.add(t), n), m = n.greaterThan(b.sub(t));
|
|
113
|
+
return { color: O(u.color.rgb, u.color.a.mul(e)), visible: m };
|
|
114
|
+
}, progress: n };
|
|
115
|
+
}
|
|
116
|
+
function ke(o = {}) {
|
|
117
|
+
const n = C(Math.min(1, Math.max(-1, o.intensity ?? 0))), r = C(Math.max(1e-4, o.radius ?? 1));
|
|
118
|
+
return { modifier: (d) => {
|
|
119
|
+
const f = n.clamp(-1, 1), u = r.max(1e-4), l = d.viewCenter, b = f.max(0), e = f.negate().max(0), m = l.z.negate().max(0), c = m.div(u).atan().mul(i(Math.PI * 0.5)), s = c.cos(), a = c.sin(), w = a.negate(), p = h(l.x, l.y.mul(s).sub(l.z.mul(a)), l.y.mul(a).add(l.z.mul(s))), y = h(l.x, l.y.mul(s).sub(l.z.mul(w)), l.y.mul(w).add(l.z.mul(s))), E = x(x(l, p, e), y, b), v = i(Math.PI * 0.5).mul(u), R = l.z.lessThan(0).select(v.negate().div(m.mul(m).add(u.mul(u))), i(0)), D = R.negate(), U = h(1, 0, 0), P = h(0, s, a), T = h(0, a.negate().sub(p.z.mul(R)), s.add(p.y.mul(R))), $ = h(1, 0, 0), B = h(0, s, w), M = h(
|
|
120
|
+
0,
|
|
121
|
+
w.negate().sub(y.z.mul(D)),
|
|
122
|
+
s.add(y.y.mul(D))
|
|
123
|
+
), L = h(1, 0, 0), N = h(0, 1, 0), V = h(0, 0, 1), I = x(x(L, U, e), $, b), z = x(x(N, P, e), B, b), A = x(x(V, T, e), M, b), k = I.div(I.length().max(1e-6)), q = z.sub(k.mul(k.dot(z))), F = q.div(q.length().max(1e-6)), G = A.sub(k.mul(k.dot(A))).sub(F.mul(F.dot(A))), W = G.div(G.length().max(1e-6)), _ = re(k, F, W), S = ae.toMat3(), j = S.transpose(), H = j.mul(_).mul(S);
|
|
124
|
+
return {
|
|
125
|
+
offset: d.offset.add(j.mul(E.sub(l))),
|
|
126
|
+
rotation: H.mul(d.rotation)
|
|
127
|
+
};
|
|
128
|
+
}, intensity: n, radius: r };
|
|
129
|
+
}
|
|
130
|
+
function Ce(o, n = {}) {
|
|
131
|
+
const r = Number.isFinite(n.umbra) ? Math.min(1, Math.max(0, n.umbra)) : 0.45, t = new g.MeshStandardNodeMaterial();
|
|
132
|
+
t.side = g.DoubleSide, t.color = new g.Color(1, 1, 1), t.roughness = 1, t.metalness = 0, t.transparent = !1, t.depthWrite = !0, t.polygonOffset = !0, t.polygonOffsetFactor = 1, t.polygonOffsetUnits = 1;
|
|
133
|
+
const d = te(o), f = x(i(r), i(1), d);
|
|
134
|
+
return t.outputNode = O(h(f), i(1)), t;
|
|
135
|
+
}
|
|
136
|
+
function ge(o) {
|
|
137
|
+
const n = new g.BufferGeometry();
|
|
138
|
+
return n.setAttribute("position", new g.BufferAttribute(o.positions.slice(), 3)), n.setIndex(new g.BufferAttribute(o.indices.slice(), 1)), n;
|
|
139
|
+
}
|
|
140
|
+
function Ee(o = {}) {
|
|
141
|
+
const n = o.albedo ?? 0.5, r = new g.Group(), t = o.matrixWorld ?? null, d = [], f = new g.MeshStandardMaterial({
|
|
142
|
+
color: new g.Color(n, n, n),
|
|
143
|
+
roughness: 1,
|
|
144
|
+
metalness: 0,
|
|
145
|
+
side: g.DoubleSide
|
|
146
|
+
}), u = (e) => {
|
|
147
|
+
t && e.applyMatrix4(t);
|
|
148
|
+
}, l = (e, m) => {
|
|
149
|
+
let c = e, s = m;
|
|
150
|
+
t && !m && (c = e.clone(), s = !0), u(c), c.computeVertexNormals(), s && d.push(c);
|
|
151
|
+
const a = new g.Mesh(c, f);
|
|
152
|
+
a.castShadow = !0, a.receiveShadow = !0, a.raycast = () => {
|
|
153
|
+
}, r.add(a);
|
|
154
|
+
};
|
|
155
|
+
for (const e of o.tiles ?? [])
|
|
156
|
+
l(ge(e.data), !0);
|
|
157
|
+
for (const e of o.geometries ?? [])
|
|
158
|
+
l(e, !1);
|
|
159
|
+
return {
|
|
160
|
+
group: r,
|
|
161
|
+
configureMaterial: (e) => {
|
|
162
|
+
e.side = g.DoubleSide, e.transparent = !1, e.opacity = 1, "color" in e && e.color instanceof g.Color && e.color.setRGB(n, n, n), "roughness" in e && typeof e.roughness == "number" && (e.roughness = 1), "metalness" in e && typeof e.metalness == "number" && (e.metalness = 0);
|
|
163
|
+
},
|
|
164
|
+
dispose: () => {
|
|
165
|
+
f.dispose();
|
|
166
|
+
for (const e of d) e.dispose();
|
|
167
|
+
r.clear();
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
export {
|
|
172
|
+
Ee as createRelightingProxy,
|
|
173
|
+
Ce as createRelightingShadowFactorMaterial,
|
|
174
|
+
ve as depthOfFieldPreset,
|
|
175
|
+
we as lightingPreset,
|
|
176
|
+
Me as revealPreset,
|
|
177
|
+
ye as sdfEffects,
|
|
178
|
+
ke as worldWarpPreset
|
|
179
|
+
};
|
|
180
|
+
//# sourceMappingURL=effects.js.map
|