@volter/editor-threejs 0.5.211 → 0.5.212
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/NOTICE +7 -2
- package/README.md +8 -13
- package/contributions/animation-mixers.service.ts +2 -4
- package/contributions/animation-timeline.utility.tsx +1 -2
- package/contributions/three.page.ts +50 -0
- package/dist-node/serving.mjs +84 -3
- package/dist-node/shared-three.mjs +145 -0
- package/package.json +296 -16
- package/serving/animation-stamp.ts +1 -1
- package/serving/index.ts +22 -5
- package/serving/model-import-conversion.ts +6 -3
- package/serving/shared-three.ts +176 -0
- package/src/adapter/constraint.ts +1 -18
- package/src/adapter/object3d-authoring-subject.ts +1 -12
- package/src/adapter/reflection-probe.ts +1 -9
- package/src/adapter/three-contract.ts +6 -60
- package/src/animation/live-mixers.ts +0 -4
- package/src/api.ts +7 -0
- package/src/asset-loaders.ts +2 -178
- package/src/capture/photograph-sampling.ts +333 -0
- package/src/capture/scene.ts +1 -1
- package/src/ecs/object-marks.ts +6 -14
- package/src/ecs/user-data.ts +3 -138
- package/src/kit/animation/three-clips-subject.ts +19 -42
- package/src/kit/asset-compare-core.ts +161 -0
- package/src/kit/asset-compare.ts +8 -27
- package/src/kit/asset-preview-command.ts +14 -19
- package/src/kit/asset-preview-framing.ts +8 -67
- package/src/kit/asset-preview.ts +109 -342
- package/src/kit/asset-workflow/model-inspection.ts +11 -154
- package/src/kit/asset-workflow/preview-resource-lifetime.ts +25 -0
- package/src/kit/authoring/component-instance-root.ts +3 -39
- package/src/kit/authoring/live-object-transform.ts +1 -30
- package/src/kit/authoring/object3d-document-session-registry.ts +7 -15
- package/src/kit/authoring/object3d-document-session.ts +471 -208
- package/src/kit/authoring/shell-viewport-policy.ts +18 -24
- package/src/kit/authoring/source-object3d-authoring-adapter.ts +6 -48
- package/src/kit/authoring/three-projection-core.ts +11 -40
- package/src/kit/authoring/viewport-pick-context.ts +5 -12
- package/src/kit/authoring/viewport-raycast.ts +15 -93
- package/src/kit/authoring/world-hidden-viewport.ts +8 -61
- package/src/kit/capture-camera-pose.ts +68 -0
- package/src/kit/collaboration-presence.ts +41 -0
- package/src/kit/components/CameraInfo.tsx +4 -6
- package/src/kit/components/InspectorObjectPreview.tsx +3 -30
- package/src/kit/components/Object3DDocumentToolbar.css +75 -0
- package/src/kit/components/Object3DDocumentToolbar.tsx +18 -84
- package/src/kit/components/Object3DDocumentViewport.tsx +5 -8
- package/src/kit/components/StageHost.css +40 -0
- package/src/kit/components/StageHost.tsx +181 -404
- package/src/kit/components/StageOverlays.tsx +1 -2
- package/src/kit/components/StatsOverlay.tsx +5 -17
- package/src/kit/components/ViewportFurniture.tsx +27 -154
- package/src/kit/components/ViewportOverlay.tsx +13 -17
- package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
- package/src/kit/components/ViewportShadingMenu.tsx +2 -42
- package/src/kit/components/ViewportViewMenu.tsx +7 -18
- package/src/kit/components/asset-viewers/EntityModelDocument.tsx +4 -13
- package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +5 -5
- package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +24 -102
- package/src/kit/components/asset-viewers/LutAssetDocument.tsx +6 -6
- package/src/kit/components/asset-viewers/Object3DPreview.tsx +10 -45
- package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +2 -2
- package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +11 -10
- package/src/kit/components/asset-viewers/shader-source.ts +144 -0
- package/src/kit/components/asset-viewers/three-asset-viewers.tsx +2 -14
- package/src/kit/components/object3d-contribution-surfaces.tsx +1 -1
- package/src/kit/components/stage-keyboard.tsx +6 -12
- package/src/kit/components/stage-overlay-set.tsx +11 -20
- package/src/kit/components/stage-presence-markers.ts +9 -32
- package/src/kit/components/stage-transform-chrome.ts +4 -4
- package/src/kit/components/stage-transform-tools.tsx +7 -14
- package/src/kit/components/standard-viewport-dressing.ts +47 -167
- package/src/kit/components/world-root-binding.ts +2 -7
- package/src/kit/constraint-helper.ts +1 -1
- package/src/kit/document-renderer-session.ts +114 -0
- package/src/kit/editor-presentation-activity.ts +58 -0
- package/src/kit/editor-shell-store.ts +31 -403
- package/src/kit/editor-viewport.ts +476 -1762
- package/src/kit/entity-lod.ts +3 -16
- package/src/kit/entity-object.ts +1 -39
- package/src/kit/hierarchy-mark-reader.ts +13 -30
- package/src/kit/instanced-presentation.ts +1 -47
- package/src/kit/live-gesture-lock.ts +35 -0
- package/src/kit/live-module-source.ts +12 -52
- package/src/kit/model-thumbnail.ts +7 -35
- package/src/kit/play-camera-flight.ts +7 -16
- package/src/kit/projection/three.ts +13 -366
- package/src/kit/retained-document-states.ts +91 -0
- package/src/kit/scene-document-viewport.ts +3 -3
- package/src/kit/scene-framing.ts +11 -152
- package/src/kit/scene-view-fog.ts +2 -47
- package/src/kit/spatial-handle-visuals.ts +19 -28
- package/src/kit/stories/three-story-model.ts +5 -33
- package/src/kit/three-canvas-render.ts +3 -4
- package/src/kit/three-hierarchy-row-media.ts +3 -11
- package/src/kit/three-inspection-media.ts +6 -6
- package/src/kit/three-integration.ts +18 -18
- package/src/kit/three-state.ts +2 -19
- package/src/kit/three-viewport/camera-fit.ts +2 -6
- package/src/kit/three-viewport/selection-brackets.ts +5 -20
- package/src/kit/three-viewport/selection-outline.ts +4 -56
- package/src/kit/three-viewport/skeleton-helper.ts +1 -32
- package/src/kit/three-viewport/source-color.ts +1 -33
- package/src/kit/three-viewport/studio-environment.ts +4 -55
- package/src/kit/three-viewport-presentation.ts +23 -0
- package/src/kit/transport-strip.tsx +147 -0
- package/src/kit/viewport-actions.ts +8 -50
- package/src/kit/viewport-authoring-policy.ts +5 -52
- package/src/kit/viewport-commands.ts +28 -57
- package/src/kit/viewport-hotkeys.ts +29 -31
- package/src/kit/viewport-status-facet.ts +4 -5
- package/src/kit/viewport-surface-status.tsx +44 -0
- package/src/loader.ts +8 -71
- package/src/object3d-contributions.ts +18 -34
- package/src/viewport/clip-planes.ts +4 -32
- package/src/viewport/content-bounds.ts +13 -142
- package/src/viewport/editor-layers.ts +4 -26
- package/src/viewport/environment.ts +1 -1
- package/src/viewport/preview-renderer.ts +7 -45
- package/src/viewport/renderer-ownership.ts +14 -62
- package/src/viewport-api.ts +14 -33
- package/src/viewport-door.ts +16 -86
- package/contributions/three-integration.service.ts +0 -13
- package/src/adapter/hierarchy-marks.ts +0 -156
- package/src/adapter/ingest/scene-capture.ts +0 -843
- package/src/adapter/ingest/structural-ids.ts +0 -139
- package/src/adapter/ingest/visible-capture-window.ts +0 -302
- package/src/adapter/renderer-config.ts +0 -115
- package/src/asset-parse-error.ts +0 -29
- package/src/host-hierarchy-objects.ts +0 -31
- package/src/kit/authoring/design-time-settle.ts +0 -343
- package/src/kit/camera-authoring.ts +0 -175
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* this is a live adapter seam, not another document format.
|
|
9
9
|
*/
|
|
10
10
|
import type * as THREE from 'three';
|
|
11
|
-
import {
|
|
11
|
+
import { getUserData } from '../ecs/user-data';
|
|
12
12
|
|
|
13
13
|
export type ReflectionProbeShape = 'box' | 'sphere';
|
|
14
14
|
export type ReflectionProbeCaptureMode = 'on-change' | 'manual' | 'realtime';
|
|
@@ -65,11 +65,3 @@ export function reflectionProbeOf(
|
|
|
65
65
|
? (candidate as ReflectionProbeMark)
|
|
66
66
|
: null;
|
|
67
67
|
}
|
|
68
|
-
|
|
69
|
-
export function setReflectionProbeMark(object: THREE.Object3D, mark: ReflectionProbeMark): void {
|
|
70
|
-
setUserData(object, 'reflectionProbe', mark);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function clearReflectionProbeMark(object: THREE.Object3D): void {
|
|
74
|
-
deleteUserData(object, 'reflectionProbe');
|
|
75
|
-
}
|
|
@@ -1,72 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The Three-typed half of the adapter contract. `@volter/project`
|
|
3
|
-
* states the `
|
|
3
|
+
* states the `stage` surface with its medium objects opaque (a scene, a
|
|
4
4
|
* camera, a renderer, a hierarchy's scene objects, navigation's debug mesh);
|
|
5
5
|
* this module names them as three.js objects for the editor side that renders
|
|
6
|
-
* and authors them. A `
|
|
6
|
+
* and authors them. A `stage`-surface adapter hands exactly these objects, so
|
|
7
7
|
* each view below is a narrowing of what the contract already carries, not a
|
|
8
8
|
* conversion.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type {
|
|
12
|
-
AuthoringAdapter,
|
|
13
|
-
HierarchyProvider,
|
|
14
|
-
MountedThreeRoot,
|
|
15
|
-
NavigationAdapter,
|
|
16
|
-
PhysicsAdapter,
|
|
17
|
-
ThreeHostContext,
|
|
18
|
-
} from '@volter/project/adapter';
|
|
19
|
-
import type { NavBakeParams } from '@volter/project/adapter/system-adapter';
|
|
11
|
+
import type { HierarchyProvider } from '@volter/project/adapter';
|
|
20
12
|
import type * as THREE from 'three';
|
|
21
13
|
|
|
22
|
-
/**
|
|
23
|
-
export interface ThreeAssetCache {
|
|
24
|
-
/** Load an asset by URL. Returns a cached promise if already loading/loaded. */
|
|
25
|
-
load<T = unknown>(url: string): Promise<T>;
|
|
26
|
-
/** Get a previously-loaded asset synchronously. Throws if not yet loaded. */
|
|
27
|
-
get<T = unknown>(url: string): T;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** What a `three`-surface root is handed, Three-typed. */
|
|
31
|
-
export type ThreeRootHostContext = ThreeHostContext<typeof THREE, THREE.WebGLRenderer, ThreeAssetCache>;
|
|
32
|
-
|
|
33
|
-
/** A live, mounted Three world, Three-typed. */
|
|
34
|
-
export type ThreeMountedRoot = MountedThreeRoot<THREE.Scene, THREE.Camera>;
|
|
35
|
-
|
|
36
|
-
/** A mounted `three` root, as the Three objects it hands. */
|
|
37
|
-
export function threeRoot(mounted: MountedThreeRoot): ThreeMountedRoot {
|
|
38
|
-
return mounted as ThreeMountedRoot;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** The hierarchy's scene-object doors, Three-typed. */
|
|
42
|
-
export interface ThreeHierarchy {
|
|
43
|
-
object3D?(id: string): THREE.Object3D | null;
|
|
44
|
-
idForObject3D?(o: THREE.Object3D): string | null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** A `three`-surface authoring adapter's hierarchy, as the Three objects it hands. */
|
|
48
|
-
export function threeHierarchy(adapter: Pick<AuthoringAdapter, 'hierarchy'>): HierarchyProvider & ThreeHierarchy {
|
|
49
|
-
return adapter.hierarchy as HierarchyProvider & ThreeHierarchy;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** The live Three object a `three`-surface hierarchy hands for `id` (null
|
|
14
|
+
/** The live Three object a `stage`-surface hierarchy hands for `id` (null
|
|
53
15
|
* when it has none, or omits the door). */
|
|
54
|
-
export function threeObject(hierarchy: Pick<HierarchyProvider, '
|
|
55
|
-
return (hierarchy.
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** A `three`-surface navigation adapter, as the Three objects it trades in. */
|
|
59
|
-
export interface ThreeNavigationAdapter extends NavigationAdapter {
|
|
60
|
-
debugMesh(scene: THREE.Scene): THREE.Object3D | null;
|
|
61
|
-
bake?(meshes: THREE.Mesh[], params?: NavBakeParams): boolean;
|
|
62
|
-
clear?(scene: THREE.Scene): void;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function threeNavigation(adapter: NavigationAdapter): ThreeNavigationAdapter {
|
|
66
|
-
return adapter as ThreeNavigationAdapter;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** A `three`-surface physics adapter's debug-draw object. */
|
|
70
|
-
export function threePhysicsDebugDraw(adapter: PhysicsAdapter): THREE.Object3D | null {
|
|
71
|
-
return (adapter.debugDraw?.() ?? null) as THREE.Object3D | null;
|
|
16
|
+
export function threeObject(hierarchy: Pick<HierarchyProvider, 'nativeObject'>, id: string): THREE.Object3D | null {
|
|
17
|
+
return (hierarchy.nativeObject?.(id) ?? null) as THREE.Object3D | null;
|
|
72
18
|
}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EDITOR-THREEJS'S API, AS TYPES: what its page half's `activate` returns (`../contributions/three.page.ts`). A package
|
|
3
|
+
* that reads the API imports these type-only; the values come from `getExtension(...).exports`.
|
|
4
|
+
*/
|
|
5
|
+
export type ThreePageApi = ReturnType<typeof import('../contributions/three.page').activate>;
|
|
6
|
+
export type { ViewportStage } from './viewport-api';
|
|
7
|
+
export type { DocumentDisplayTransform } from './render/document-display-transform';
|
package/src/asset-loaders.ts
CHANGED
|
@@ -1,90 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canonical GLTF / texture load + cache layer (A2). (Formerly also hosted the
|
|
3
|
-
* `.animgraph.json` loader — removed by E5; see
|
|
4
|
-
* packages/threejs-runtime/src/animation/xstate-animation-binding.ts.)
|
|
5
|
-
*
|
|
6
|
-
* ONE place that owns the URL-keyed caches and the clone-safety contract, shared
|
|
7
|
-
* by BOTH the engine runtime (scene-loader, create-runtime) AND the editor
|
|
8
|
-
* (entity-factory, scene-sync). Previously this logic was duplicated across six
|
|
9
|
-
* sites with three separate GLTF caches and four GLTFLoader instances, so fixes
|
|
10
|
-
* (DRACO wiring, the `__sharedGeometry` clone-safety tag, skybox handling) did
|
|
11
|
-
* not propagate. Everything funnels through the shared loaders in `../loader`
|
|
12
|
-
* (`gltfLoader` is DRACO-wired — CB2 — and `textureLoader` shares the engine
|
|
13
|
-
* LoadingManager / asset-prefix rewrite).
|
|
14
|
-
*
|
|
15
|
-
* Cache lifetime / P1.8: these caches are intentionally module-level and persist
|
|
16
|
-
* ACROSS hot-reloads / scene switches (they are NOT cleared on every scene load).
|
|
17
|
-
* This is deliberate: (1) it makes repeated Play→Stop→Play cheap, and (2) it
|
|
18
|
-
* underpins the `__sharedGeometry` contract from P0.2 — GLTF clones share the
|
|
19
|
-
* cached source geometry, so clearing mid-session would orphan live clones. The
|
|
20
|
-
* caches are keyed by URL, so loading the SAME asset N times is bounded (one
|
|
21
|
-
* entry per distinct URL, not per load). They are released only on full runtime
|
|
22
|
-
* teardown via {@link clearAssetCaches}.
|
|
23
|
-
*/
|
|
1
|
+
/** Canonical GLTF and texture loading, URL-keyed caches and clone-safe shared geometry. Caches persist across scene changes and are released on runtime teardown. */
|
|
24
2
|
|
|
25
3
|
import type { SplatMesh } from '@sparkjsdev/spark';
|
|
26
4
|
import * as THREE from 'three';
|
|
27
|
-
import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
|
|
28
|
-
import { AssetParseError } from './asset-parse-error';
|
|
29
5
|
import { setUserData } from './ecs/user-data';
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
const textureCache = new Map<string, THREE.Texture>();
|
|
33
|
-
const gltfCache = new Map<
|
|
34
|
-
string,
|
|
35
|
-
Promise<{ scene: THREE.Group; animations: THREE.AnimationClip[] }>
|
|
36
|
-
>();
|
|
6
|
+
import { resolveUrl } from './loader';
|
|
37
7
|
const splatBytesCache = new Map<string, Promise<Uint8Array>>();
|
|
38
8
|
|
|
39
|
-
/** Load (and cache) a texture via the shared, prefix-aware TextureLoader. */
|
|
40
|
-
export function loadTexture(url: string): THREE.Texture {
|
|
41
|
-
const cached = textureCache.get(url);
|
|
42
|
-
if (cached) return cached;
|
|
43
|
-
const tex = textureLoader.load(url);
|
|
44
|
-
textureCache.set(url, tex);
|
|
45
|
-
return tex;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Load a GLTF/GLB and return a fresh, disposal-safe clone (+ the source's
|
|
50
|
-
* animation clips). DRACO decoding is handled by the shared `gltfLoader`.
|
|
51
|
-
*
|
|
52
|
-
* Clone-safety contract (P0.2 — load-bearing, tested):
|
|
53
|
-
* `SkeletonUtils.clone` shares geometry AND materials with the cached source.
|
|
54
|
-
* We give each clone its OWN materials (so scene cleanup can dispose them
|
|
55
|
-
* without corrupting the cache) and tag every mesh `userData.__sharedGeometry`
|
|
56
|
-
* so cleanup (engine create-runtime fullCleanup + editor scene-sync
|
|
57
|
-
* disposeObject3D) skips disposing the shared geometry buffers.
|
|
58
|
-
*/
|
|
59
|
-
export function loadGLTF(
|
|
60
|
-
url: string,
|
|
61
|
-
): Promise<{ scene: THREE.Group; animations: THREE.AnimationClip[] }> {
|
|
62
|
-
let cached = gltfCache.get(url);
|
|
63
|
-
if (!cached) {
|
|
64
|
-
cached = new Promise((resolve, reject) => {
|
|
65
|
-
gltfLoader.load(
|
|
66
|
-
url,
|
|
67
|
-
(gltf) => resolve({ scene: gltf.scene, animations: gltf.animations }),
|
|
68
|
-
undefined,
|
|
69
|
-
(err) => reject(err),
|
|
70
|
-
);
|
|
71
|
-
});
|
|
72
|
-
gltfCache.set(url, cached);
|
|
73
|
-
}
|
|
74
|
-
return cached.then((entry) => {
|
|
75
|
-
const scene = SkeletonUtils.clone(entry.scene) as THREE.Group;
|
|
76
|
-
scene.traverse((child) => {
|
|
77
|
-
if (child instanceof THREE.Mesh) {
|
|
78
|
-
child.material = Array.isArray(child.material)
|
|
79
|
-
? child.material.map((m) => m.clone())
|
|
80
|
-
: child.material.clone();
|
|
81
|
-
setUserData(child, '__sharedGeometry', true);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
return { scene, animations: entry.animations };
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
9
|
/**
|
|
89
10
|
* Load one native Gaussian-splat asset as Spark's own Object3D.
|
|
90
11
|
*
|
|
@@ -143,100 +64,3 @@ export async function loadSplat(url: string, providedBytes?: Uint8Array): Promis
|
|
|
143
64
|
throw error;
|
|
144
65
|
}
|
|
145
66
|
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Resolve a single named node inside an already-loaded glTF scene graph (F4,
|
|
149
|
-
* `mesh.node`). Pure and headlessly testable — no fetch, no cache lookups;
|
|
150
|
-
* callers pass the `scene` they already got from {@link loadGLTF}.
|
|
151
|
-
*
|
|
152
|
-
* The returned node is the SAME object (and shares the SAME geometry) as
|
|
153
|
-
* found in `scene` — nothing is cloned or copied out. `scene` is already a
|
|
154
|
-
* fresh `SkeletonUtils.clone()` per {@link loadGLTF}'s clone-safety contract,
|
|
155
|
-
* so mutating/detaching the returned node is safe.
|
|
156
|
-
*
|
|
157
|
-
* Atomic-subtree guard: a `SkinnedMesh` cannot be lifted out of its armature
|
|
158
|
-
* (its skeleton/bind matrices reference sibling bone nodes elsewhere in the
|
|
159
|
-
* hierarchy), so resolving a node that IS a SkinnedMesh, or that CONTAINS one,
|
|
160
|
-
* throws a loud `AssetParseError` instead of silently producing broken skinning.
|
|
161
|
-
*/
|
|
162
|
-
export function resolveGltfNode(
|
|
163
|
-
scene: THREE.Object3D,
|
|
164
|
-
nodeName: string,
|
|
165
|
-
src: string,
|
|
166
|
-
): THREE.Object3D {
|
|
167
|
-
const node = scene.getObjectByName(nodeName);
|
|
168
|
-
if (!node) {
|
|
169
|
-
throw new AssetParseError(
|
|
170
|
-
[
|
|
171
|
-
{
|
|
172
|
-
code: 'custom',
|
|
173
|
-
path: ['mesh', 'node'],
|
|
174
|
-
message: `glTF "${src}" has no node named "${nodeName}" (getObjectByName found nothing)`,
|
|
175
|
-
},
|
|
176
|
-
],
|
|
177
|
-
src,
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const isSkinned = (o: THREE.Object3D) => (o as THREE.SkinnedMesh).isSkinnedMesh;
|
|
182
|
-
let containsSkinned = isSkinned(node);
|
|
183
|
-
if (!containsSkinned) {
|
|
184
|
-
node.traverse((child) => {
|
|
185
|
-
if (isSkinned(child)) containsSkinned = true;
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
if (containsSkinned) {
|
|
189
|
-
throw new AssetParseError(
|
|
190
|
-
[
|
|
191
|
-
{
|
|
192
|
-
code: 'custom',
|
|
193
|
-
path: ['mesh', 'node'],
|
|
194
|
-
message:
|
|
195
|
-
`glTF "${src}" node "${nodeName}" is (or contains) a SkinnedMesh — a skinned mesh ` +
|
|
196
|
-
'cannot be lifted out of its armature (atomic-subtree rule). Reference the whole ' +
|
|
197
|
-
'file instead of a `node`, or bake/export a non-skinned sub-asset.',
|
|
198
|
-
},
|
|
199
|
-
],
|
|
200
|
-
src,
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return node;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Clear the module-level GLTF / texture caches. Call on full runtime teardown
|
|
209
|
-
* (create-runtime fullCleanup / hot-reload) to release cached GPU resources
|
|
210
|
-
* and avoid leaking across editor Play sessions. See the lifetime note at the
|
|
211
|
-
* top of this file for why these are NOT cleared per scene load.
|
|
212
|
-
*
|
|
213
|
-
* NOTE: the IBL/skybox env-map cache lives in scene-loader (it needs a
|
|
214
|
-
* WebGLRenderer for PMREM); scene-loader's `clearAssetCaches` wraps this and
|
|
215
|
-
* also clears that env cache.
|
|
216
|
-
*/
|
|
217
|
-
export function clearAssetCaches(): void {
|
|
218
|
-
textureCache.clear();
|
|
219
|
-
gltfCache.clear();
|
|
220
|
-
splatBytesCache.clear();
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Drop the cached entry for ONE asset URL after its bytes changed on disk
|
|
225
|
-
* (editor asset-optimization write-back — W4a). Existing clones keep their
|
|
226
|
-
* shared geometry alive (the cache entry only owns the lookup, not the
|
|
227
|
-
* buffers), and the next `loadGLTF`/`loadTexture` for this URL re-fetches the
|
|
228
|
-
* rewritten file instead of serving stale bytes.
|
|
229
|
-
*/
|
|
230
|
-
export function invalidateCachedAsset(url: string): void {
|
|
231
|
-
textureCache.delete(url);
|
|
232
|
-
gltfCache.delete(url);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Test-only: current sizes of the module-level caches. Used by the P1.8 headless
|
|
237
|
-
* test to assert caches stay bounded (one entry per distinct asset URL) across
|
|
238
|
-
* repeated loads of the same scene, rather than growing per load.
|
|
239
|
-
*/
|
|
240
|
-
export function assetCacheSizes(): { textures: number; gltf: number; splat: number } {
|
|
241
|
-
return { textures: textureCache.size, gltf: gltfCache.size, splat: splatBytesCache.size };
|
|
242
|
-
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/** Shared Three capture mechanics; this entry must not import host or contribution code. */
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* HOW A PHOTOGRAPH IS ANTI-ALIASED, when its document states it: the samples a renderer's own
|
|
7
|
+
* temporal accumulation would draw, each with the projection moved by `offset * projectionScale`
|
|
8
|
+
* pixels, each spread over the neighbours within `reach` by the filter weighed at `offset`, and
|
|
9
|
+
* summed in the renderer's own space. The document supplies the model (Blender's Workbench and
|
|
10
|
+
* EEVEE film, `blender-viewport-sampling.ts`); this module only carries it out.
|
|
11
|
+
*/
|
|
12
|
+
export interface PhotographSampling {
|
|
13
|
+
/** Each sample's offset in pixels, as the filter weighs it. */
|
|
14
|
+
readonly offsets: readonly (readonly [number, number])[];
|
|
15
|
+
/** The projection moves `offset * projectionScale` pixels (Workbench 0.5, EEVEE 1). */
|
|
16
|
+
readonly projectionScale: number;
|
|
17
|
+
/** Neighbours gathered on each side of a pixel. */
|
|
18
|
+
readonly reach: number;
|
|
19
|
+
/** Blackman-Harris over a width of `2 * radius`, or a Gaussian of sigma `0.284 * radius` cut at
|
|
20
|
+
* `radius`. A radius of 0 keeps each pixel's own sample. */
|
|
21
|
+
readonly filter: 'blackman-harris' | 'gaussian';
|
|
22
|
+
readonly radius: number;
|
|
23
|
+
/** `log2`: colour summed as log2(1 + c), resolved with exp2(x) - 1, alpha linear (Workbench).
|
|
24
|
+
* `luma`: each texel weighed again by 1 / (4 + (R + 2G + B) * exposureScale) and blended with
|
|
25
|
+
* the history by that weight over the filter weight so far (EEVEE's film). */
|
|
26
|
+
readonly accumulation: 'log2' | 'luma';
|
|
27
|
+
readonly exposureScale: number;
|
|
28
|
+
/** Drawn without its backdrop, then laid over it after the display encoding in display-linear,
|
|
29
|
+
* `pow(c, 2.2)`, as Blender's viewport merges its overlay (the OCIO display shader). The merge
|
|
30
|
+
* writes the display encoding itself (sRGB, no tone mapping): a document asks for it only under
|
|
31
|
+
* a renderer that tone-maps nothing, and the stage merges only then. */
|
|
32
|
+
readonly backdropMerge: boolean;
|
|
33
|
+
/** The live view shows its sum from this many samples on, the ordinary (MSAA) frame before:
|
|
34
|
+
* where the sum's edges first are no worse than the ordinary frame's. All of them when unset. */
|
|
35
|
+
readonly liveFrom?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The most samples a photograph draws, whatever a document asks for. */
|
|
39
|
+
export const PHOTOGRAPH_SAMPLE_CAP = 64;
|
|
40
|
+
|
|
41
|
+
/** The filter's weight at `distance` pixels from a sample. */
|
|
42
|
+
function filterWeight(sampling: PhotographSampling, distance: number): number {
|
|
43
|
+
if (sampling.radius <= 0) return distance === 0 ? 1 : 0;
|
|
44
|
+
if (sampling.filter === 'gaussian') {
|
|
45
|
+
if (distance * distance >= sampling.radius * sampling.radius) return 0;
|
|
46
|
+
const r = (distance * distance) / (sampling.radius * sampling.radius);
|
|
47
|
+
const sigma = 0.284;
|
|
48
|
+
return Math.exp((-0.5 / (sigma * sigma)) * r);
|
|
49
|
+
}
|
|
50
|
+
const width = 2 * sampling.radius;
|
|
51
|
+
if (distance > width * 0.5) return 0;
|
|
52
|
+
const x = 2 * Math.PI * Math.min(1, Math.max(0, distance / width + 0.5));
|
|
53
|
+
return 0.35875 - 0.48829 * Math.cos(x) + 0.14128 * Math.cos(2 * x) - 0.01168 * Math.cos(3 * x);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const QUAD_VERTEX = `precision highp float; in vec3 position; in vec2 uv; out vec2 vUv;
|
|
57
|
+
void main(){vUv=uv;gl_Position=vec4(position,1.0);}`;
|
|
58
|
+
|
|
59
|
+
/** One sample spread over its neighbours, added to the sum (Workbench's `workbench_taa`). */
|
|
60
|
+
const LOG2_ACCUMULATE = (reach: number) => /* glsl */ `
|
|
61
|
+
precision highp float;
|
|
62
|
+
uniform sampler2D frame;
|
|
63
|
+
uniform float weights[${(2 * reach + 1) ** 2}];
|
|
64
|
+
in vec2 vUv;
|
|
65
|
+
out vec4 outputColor;
|
|
66
|
+
void main() {
|
|
67
|
+
ivec2 size = textureSize(frame, 0);
|
|
68
|
+
ivec2 p = ivec2(vUv * vec2(size));
|
|
69
|
+
vec4 sum = vec4(0.0);
|
|
70
|
+
int i = 0;
|
|
71
|
+
for (int y = -${reach}; y <= ${reach}; y++) {
|
|
72
|
+
for (int x = -${reach}; x <= ${reach}; x++) {
|
|
73
|
+
vec4 color = clamp(texelFetch(frame, clamp(p + ivec2(x, y), ivec2(0), size - 1), 0), vec4(0.0), vec4(1e10));
|
|
74
|
+
color.rgb = log2(color.rgb + 1.0);
|
|
75
|
+
sum += color * weights[i];
|
|
76
|
+
i++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
outputColor = sum;
|
|
80
|
+
}`;
|
|
81
|
+
|
|
82
|
+
/** The sum, out of log2 space (the SMAA resolve's `exp2(x) - 1` over the accumulated weight). */
|
|
83
|
+
const LOG2_RESOLVE = /* glsl */ `
|
|
84
|
+
precision highp float;
|
|
85
|
+
uniform sampler2D sum;
|
|
86
|
+
uniform float total;
|
|
87
|
+
in vec2 vUv;
|
|
88
|
+
out vec4 outputColor;
|
|
89
|
+
void main() {
|
|
90
|
+
vec4 color = texelFetch(sum, ivec2(vUv * vec2(textureSize(sum, 0))), 0) / total;
|
|
91
|
+
color.rgb = exp2(color.rgb) - 1.0;
|
|
92
|
+
if (color.a > 0.999) color.a = 1.0;
|
|
93
|
+
outputColor = color;
|
|
94
|
+
}`;
|
|
95
|
+
|
|
96
|
+
/** One sample gathered by luma-weighted filter weights and blended with the history
|
|
97
|
+
* (EEVEE's `sample_accum_combined` and `store_combined`, static accumulation). */
|
|
98
|
+
const LUMA_ACCUMULATE = (reach: number) => /* glsl */ `
|
|
99
|
+
precision highp float;
|
|
100
|
+
uniform sampler2D frame;
|
|
101
|
+
uniform sampler2D history;
|
|
102
|
+
uniform float weights[${(2 * reach + 1) ** 2}];
|
|
103
|
+
uniform float filmWeight;
|
|
104
|
+
uniform float exposureScale;
|
|
105
|
+
in vec2 vUv;
|
|
106
|
+
out vec4 outputColor;
|
|
107
|
+
float lumaWeight(vec3 rgb) { return 1.0 / (4.0 + dot(rgb, vec3(1.0, 2.0, 1.0)) * exposureScale); }
|
|
108
|
+
void main() {
|
|
109
|
+
ivec2 size = textureSize(frame, 0);
|
|
110
|
+
ivec2 p = ivec2(vUv * vec2(size));
|
|
111
|
+
vec4 sum = vec4(0.0);
|
|
112
|
+
float weight = 0.0;
|
|
113
|
+
int i = 0;
|
|
114
|
+
for (int y = -${reach}; y <= ${reach}; y++) {
|
|
115
|
+
for (int x = -${reach}; x <= ${reach}; x++) {
|
|
116
|
+
vec4 color = texelFetch(frame, clamp(p + ivec2(x, y), ivec2(0), size - 1), 0);
|
|
117
|
+
float w = lumaWeight(color.rgb) * weights[i];
|
|
118
|
+
sum += color * w;
|
|
119
|
+
weight += w;
|
|
120
|
+
i++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
vec4 source = weight > 0.0 ? sum / weight : vec4(0.0);
|
|
124
|
+
if (filmWeight <= 0.0) { outputColor = source; return; }
|
|
125
|
+
vec4 past = texelFetch(history, p, 0);
|
|
126
|
+
float pastWeight = lumaWeight(past.rgb) * filmWeight;
|
|
127
|
+
vec4 color = (past * pastWeight + source * weight) / (pastWeight + weight);
|
|
128
|
+
if (color.a > 0.995) color.a = 1.0;
|
|
129
|
+
outputColor = max(color, vec4(0.0));
|
|
130
|
+
}`;
|
|
131
|
+
|
|
132
|
+
/** The resolved drawing laid over its backdrop as Blender's viewport merges the overlay
|
|
133
|
+
* (`gpu_viewport.cc`, `ocio_frag.glsl`): the drawing's colour AS ACCUMULATED, premultiplied (the
|
|
134
|
+
* viewport sets up the display shader without predivide), through the view (sRGB, Standard), to
|
|
135
|
+
* display-linear by `pow(c, 2.2)`; the background's "alpha under" added, `bg * (1 - alpha)`
|
|
136
|
+
* (the overlay buffer's alpha is 0 where nothing else is drawn, so nothing scales the drawing);
|
|
137
|
+
* and back by `pow(c, 1/2.2)`. */
|
|
138
|
+
const BACKDROP_MERGE = /* glsl */ `
|
|
139
|
+
precision highp float;
|
|
140
|
+
uniform sampler2D drawing;
|
|
141
|
+
uniform vec3 backdropLinear;
|
|
142
|
+
in vec2 vUv;
|
|
143
|
+
out vec4 outputColor;
|
|
144
|
+
vec3 srgbEncode(vec3 c) {
|
|
145
|
+
c = clamp(c, 0.0, 1.0);
|
|
146
|
+
return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(vec3(0.0031308), c));
|
|
147
|
+
}
|
|
148
|
+
void main() {
|
|
149
|
+
vec4 color = texelFetch(drawing, ivec2(vUv * vec2(textureSize(drawing, 0))), 0);
|
|
150
|
+
float alpha = clamp(color.a, 0.0, 1.0);
|
|
151
|
+
vec3 display = pow(srgbEncode(color.rgb), vec3(2.2));
|
|
152
|
+
vec3 merged = display + backdropLinear * (1.0 - alpha);
|
|
153
|
+
outputColor = vec4(pow(merged, vec3(1.0 / 2.2)), 1.0);
|
|
154
|
+
}`;
|
|
155
|
+
|
|
156
|
+
const blend = {
|
|
157
|
+
depthTest: false,
|
|
158
|
+
depthWrite: false,
|
|
159
|
+
} as const;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* One photograph's accumulation: `add` each jittered render with its offset, then `resolve`
|
|
163
|
+
* into `result` (scene-linear, the drawing's alpha kept), or `merge` it over a backdrop.
|
|
164
|
+
*/
|
|
165
|
+
export class PhotographAccumulation {
|
|
166
|
+
readonly offsets: readonly (readonly [number, number])[];
|
|
167
|
+
private readonly targets: THREE.WebGLRenderTarget[];
|
|
168
|
+
readonly result: THREE.WebGLRenderTarget;
|
|
169
|
+
private readonly accumulate: THREE.RawShaderMaterial;
|
|
170
|
+
private readonly resolver: THREE.RawShaderMaterial | null;
|
|
171
|
+
private readonly merger = new THREE.RawShaderMaterial({
|
|
172
|
+
name: 'Photograph backdrop merge', glslVersion: THREE.GLSL3, ...blend, blending: THREE.NoBlending,
|
|
173
|
+
uniforms: { drawing: { value: null }, backdropLinear: { value: new THREE.Vector3() } },
|
|
174
|
+
vertexShader: QUAD_VERTEX, fragmentShader: BACKDROP_MERGE,
|
|
175
|
+
});
|
|
176
|
+
private readonly quad = new FullScreenQuad();
|
|
177
|
+
private total = 0;
|
|
178
|
+
private filmWeight = 0;
|
|
179
|
+
private current = 0;
|
|
180
|
+
|
|
181
|
+
constructor(
|
|
182
|
+
private readonly sampling: PhotographSampling,
|
|
183
|
+
width: number,
|
|
184
|
+
height: number,
|
|
185
|
+
renderer: THREE.WebGLRenderer,
|
|
186
|
+
/** `half` for the live view, whose sums need not repeat to the bit (half the memory). */
|
|
187
|
+
precision: 'best' | 'half' = 'best',
|
|
188
|
+
) {
|
|
189
|
+
this.offsets = sampling.offsets.slice(0, PHOTOGRAPH_SAMPLE_CAP);
|
|
190
|
+
// Accumulate photographs in single precision when EXT_float_blend is available; use half
|
|
191
|
+
// precision otherwise. Single precision reduces rounding differences between samples.
|
|
192
|
+
const float = precision === 'best' &&
|
|
193
|
+
renderer.extensions.has('EXT_color_buffer_float') && renderer.extensions.has('EXT_float_blend');
|
|
194
|
+
const options = {
|
|
195
|
+
type: float ? THREE.FloatType : THREE.HalfFloatType,
|
|
196
|
+
depthBuffer: false, minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter,
|
|
197
|
+
} as const;
|
|
198
|
+
const reach = Math.max(0, Math.round(sampling.reach));
|
|
199
|
+
if (sampling.accumulation === 'log2') {
|
|
200
|
+
this.targets = [new THREE.WebGLRenderTarget(width, height, options)];
|
|
201
|
+
this.result = new THREE.WebGLRenderTarget(width, height, options);
|
|
202
|
+
this.accumulate = new THREE.RawShaderMaterial({
|
|
203
|
+
name: 'Photograph accumulation (log2)', glslVersion: THREE.GLSL3, ...blend, transparent: true,
|
|
204
|
+
blending: THREE.CustomBlending, blendEquation: THREE.AddEquation,
|
|
205
|
+
blendSrc: THREE.OneFactor, blendDst: THREE.OneFactor, blendSrcAlpha: THREE.OneFactor, blendDstAlpha: THREE.OneFactor,
|
|
206
|
+
uniforms: { frame: { value: null }, weights: { value: new Array<number>((2 * reach + 1) ** 2).fill(0) } },
|
|
207
|
+
vertexShader: QUAD_VERTEX, fragmentShader: LOG2_ACCUMULATE(reach),
|
|
208
|
+
});
|
|
209
|
+
this.resolver = new THREE.RawShaderMaterial({
|
|
210
|
+
name: 'Photograph resolve (log2)', glslVersion: THREE.GLSL3, ...blend, blending: THREE.NoBlending,
|
|
211
|
+
uniforms: { sum: { value: null }, total: { value: 1 } },
|
|
212
|
+
vertexShader: QUAD_VERTEX, fragmentShader: LOG2_RESOLVE,
|
|
213
|
+
});
|
|
214
|
+
} else {
|
|
215
|
+
this.targets = [new THREE.WebGLRenderTarget(width, height, options), new THREE.WebGLRenderTarget(width, height, options)];
|
|
216
|
+
this.result = this.targets[0]!;
|
|
217
|
+
this.accumulate = new THREE.RawShaderMaterial({
|
|
218
|
+
name: 'Photograph accumulation (luma)', glslVersion: THREE.GLSL3, ...blend, blending: THREE.NoBlending,
|
|
219
|
+
uniforms: {
|
|
220
|
+
frame: { value: null }, history: { value: null }, filmWeight: { value: 0 },
|
|
221
|
+
exposureScale: { value: sampling.exposureScale },
|
|
222
|
+
weights: { value: new Array<number>((2 * reach + 1) ** 2).fill(0) },
|
|
223
|
+
},
|
|
224
|
+
vertexShader: QUAD_VERTEX, fragmentShader: LUMA_ACCUMULATE(reach),
|
|
225
|
+
});
|
|
226
|
+
this.resolver = null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** The neighbourhood's filter weights for a sample at `offset`, in the shaders' loop order. */
|
|
231
|
+
private weights(offset: readonly [number, number]): number[] {
|
|
232
|
+
const reach = Math.max(0, Math.round(this.sampling.reach));
|
|
233
|
+
const weights: number[] = [];
|
|
234
|
+
for (let y = -reach; y <= reach; y++)
|
|
235
|
+
for (let x = -reach; x <= reach; x++) weights.push(filterWeight(this.sampling, Math.hypot(x - offset[0], y - offset[1])));
|
|
236
|
+
return weights;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private draw(renderer: THREE.WebGLRenderer, material: THREE.Material, target: THREE.WebGLRenderTarget | null): void {
|
|
240
|
+
const autoClear = renderer.autoClear;
|
|
241
|
+
renderer.setRenderTarget(target);
|
|
242
|
+
// Added to (or written over) explicitly: never cleared first, whatever the renderer's setting.
|
|
243
|
+
renderer.autoClear = false;
|
|
244
|
+
try {
|
|
245
|
+
this.quad.material = material;
|
|
246
|
+
this.quad.render(renderer);
|
|
247
|
+
} finally {
|
|
248
|
+
renderer.autoClear = autoClear;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
clear(renderer: THREE.WebGLRenderer): void {
|
|
253
|
+
const previous = renderer.getRenderTarget();
|
|
254
|
+
const color = renderer.getClearColor(new THREE.Color());
|
|
255
|
+
const alpha = renderer.getClearAlpha();
|
|
256
|
+
renderer.setClearColor(0x000000, 0);
|
|
257
|
+
for (const target of this.targets) {
|
|
258
|
+
renderer.setRenderTarget(target);
|
|
259
|
+
renderer.clear(true, false, false);
|
|
260
|
+
}
|
|
261
|
+
renderer.setClearColor(color, alpha);
|
|
262
|
+
renderer.setRenderTarget(previous);
|
|
263
|
+
this.total = 0;
|
|
264
|
+
this.filmWeight = 0;
|
|
265
|
+
this.current = 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
add(renderer: THREE.WebGLRenderer, frame: THREE.Texture, offset: readonly [number, number]): void {
|
|
269
|
+
const previous = renderer.getRenderTarget();
|
|
270
|
+
const weights = this.weights(offset);
|
|
271
|
+
const sum = weights.reduce((total, weight) => total + weight, 0);
|
|
272
|
+
this.accumulate.uniforms['frame']!.value = frame;
|
|
273
|
+
this.accumulate.uniforms['weights']!.value = weights;
|
|
274
|
+
if (this.sampling.accumulation === 'log2') {
|
|
275
|
+
this.draw(renderer, this.accumulate, this.targets[0]!);
|
|
276
|
+
this.total += sum;
|
|
277
|
+
} else {
|
|
278
|
+
const read = this.targets[this.current]!;
|
|
279
|
+
const write = this.targets[1 - this.current]!;
|
|
280
|
+
this.accumulate.uniforms['history']!.value = read.texture;
|
|
281
|
+
this.accumulate.uniforms['filmWeight']!.value = this.filmWeight;
|
|
282
|
+
this.draw(renderer, this.accumulate, write);
|
|
283
|
+
this.filmWeight += sum;
|
|
284
|
+
this.current = 1 - this.current;
|
|
285
|
+
}
|
|
286
|
+
renderer.setRenderTarget(previous);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** The filtered drawing, scene-linear with its alpha. */
|
|
290
|
+
resolve(renderer: THREE.WebGLRenderer): THREE.WebGLRenderTarget {
|
|
291
|
+
if (!this.resolver) return this.targets[this.current]!;
|
|
292
|
+
const previous = renderer.getRenderTarget();
|
|
293
|
+
this.resolver.uniforms['sum']!.value = this.targets[0]!.texture;
|
|
294
|
+
this.resolver.uniforms['total']!.value = this.total > 0 ? this.total : 1;
|
|
295
|
+
this.draw(renderer, this.resolver, this.result);
|
|
296
|
+
renderer.setRenderTarget(previous);
|
|
297
|
+
return this.result;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** The resolved drawing over `backdrop` (a scene background colour), display-encoded, into
|
|
301
|
+
* `target` (null: the canvas). */
|
|
302
|
+
merge(renderer: THREE.WebGLRenderer, drawing: THREE.WebGLRenderTarget, backdrop: THREE.Color, target: THREE.WebGLRenderTarget | null): void {
|
|
303
|
+
const previous = renderer.getRenderTarget();
|
|
304
|
+
// The backdrop as the display shows it, back to display-linear by Blender's own 2.2.
|
|
305
|
+
const shown = backdrop.clone().convertLinearToSRGB();
|
|
306
|
+
(this.merger.uniforms['backdropLinear']!.value as THREE.Vector3).set(shown.r ** 2.2, shown.g ** 2.2, shown.b ** 2.2);
|
|
307
|
+
this.merger.uniforms['drawing']!.value = drawing.texture;
|
|
308
|
+
this.draw(renderer, this.merger, target);
|
|
309
|
+
renderer.setRenderTarget(previous);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
dispose(): void {
|
|
313
|
+
for (const target of this.targets) target.dispose();
|
|
314
|
+
if (this.result !== this.targets[0]) this.result.dispose();
|
|
315
|
+
this.accumulate.dispose();
|
|
316
|
+
this.resolver?.dispose();
|
|
317
|
+
this.merger.dispose();
|
|
318
|
+
this.quad.dispose();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Move `camera`'s projection so the scene lands `offset` output pixels over, on a `width` by
|
|
323
|
+
* `height` drawing; answers the undo. */
|
|
324
|
+
export function jitterProjection(camera: THREE.Camera, offset: readonly [number, number], width: number, height: number): () => void {
|
|
325
|
+
const projection = camera.projectionMatrix.clone();
|
|
326
|
+
const inverse = camera.projectionMatrixInverse.clone();
|
|
327
|
+
camera.projectionMatrix.premultiply(new THREE.Matrix4().makeTranslation((2 * offset[0]) / width, (2 * offset[1]) / height, 0));
|
|
328
|
+
camera.projectionMatrixInverse.copy(camera.projectionMatrix).invert();
|
|
329
|
+
return () => {
|
|
330
|
+
camera.projectionMatrix.copy(projection);
|
|
331
|
+
camera.projectionMatrixInverse.copy(inverse);
|
|
332
|
+
};
|
|
333
|
+
}
|
package/src/capture/scene.ts
CHANGED
|
@@ -86,7 +86,7 @@ function capture(
|
|
|
86
86
|
hdr = new THREE.WebGLRenderTarget(renderWidth, renderHeight, { type: THREE.HalfFloatType });
|
|
87
87
|
if (options.effect) hdr.depthTexture = new THREE.DepthTexture(renderWidth, renderHeight);
|
|
88
88
|
renderer.shadowMap.enabled = true;
|
|
89
|
-
renderer.shadowMap.type = THREE.
|
|
89
|
+
renderer.shadowMap.type = THREE.PCFShadowMap;
|
|
90
90
|
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
91
91
|
renderer.toneMapping = options.toneMapping ?? THREE.NoToneMapping;
|
|
92
92
|
renderer.toneMappingExposure = options.exposure ?? 1;
|