@navaramap/three 0.0.5 → 0.0.6
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/README.md +1 -1
- package/dist/assets/fontWorker-JfTCpd38.js +2 -0
- package/dist/assets/fontWorker-JfTCpd38.js.map +1 -0
- package/dist/assets/{index-TKhRxurv.js → index-B3U_DvcP.js} +2 -2
- package/dist/assets/index-B3U_DvcP.js.map +1 -0
- package/dist/assets/{navara_wasm_font_worker_bg-Kk06ArRB.wasm → navara_wasm_font_worker_bg-DympKYVX.wasm} +0 -0
- package/dist/assets/{navara_wasm_worker_bg-BZjLKf4i.wasm → navara_wasm_worker_bg-Bx_l45cW.wasm} +0 -0
- package/dist/index.d.ts +510 -374
- package/dist/index.js +6150 -5810
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +1188 -793
- package/dist/index.umd.cjs.map +1 -1
- package/package.json +12 -12
- package/src/core/DescRegistry.ts +3 -2
- package/src/core/EffectDesc.ts +10 -0
- package/src/core/EffectDescRegistry.ts +14 -0
- package/src/core/MeshDesc.ts +39 -2
- package/src/core/MeshDescWithSelectiveEffect.ts +14 -2
- package/src/core/ViewContext.ts +116 -7
- package/src/device.ts +4 -2
- package/src/index.ts +123 -11
- package/src/layer.ts +7 -5
- package/src/layers/effect/MRTPassEffectDesc.ts +34 -3
- package/src/layers/effect/SelectiveEffectDesc.ts +10 -2
- package/src/layers/effect/TransparentPassEffectDesc.ts +5 -4
- package/src/layersManager.ts +32 -1
- package/src/material/enhancer/model/modelBaseEnhancer/shader.ts +3 -2
- package/src/material/enhancer/polygon/polygonBaseEnhancer/index.ts +0 -6
- package/src/material/enhancer/polygon/polygonBaseEnhancer/mutates.ts +0 -6
- package/src/material/enhancer/polygon/polygonBaseEnhancer/shader.ts +4 -10
- package/src/material/enhancer/polygon/polygonBaseEnhancer/state.ts +1 -4
- package/src/material/enhancer/polygon/polygonBaseEnhancer/types.ts +0 -9
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/coverage.ts +31 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.test.ts +75 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.ts +26 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/types.ts +1 -1
- package/src/material/gbufferLayout.test.ts +171 -0
- package/src/material/gbufferLayout.ts +298 -0
- package/src/material/index.ts +1 -0
- package/src/material/macro/tileShader.test.ts +35 -33
- package/src/material/macro/tileShader.ts +33 -10
- package/src/material/overrideMaterialsForMRT.ts +113 -71
- package/src/mesh/DrapedMesh.test.ts +57 -0
- package/src/mesh/DrapedMesh.ts +82 -0
- package/src/mesh/model.ts +3 -0
- package/src/mesh/polygon.ts +3 -2
- package/src/mesh/polyline.ts +3 -0
- package/src/mesh/sdfText/layout.ts +1 -3
- package/src/mesh/tile/index.ts +8 -4
- package/src/passes/CustomRenderPass.test.ts +143 -0
- package/src/passes/CustomRenderPass.ts +273 -30
- package/src/passes/RenderTargetCopyPass.ts +6 -0
- package/src/plugins/AttributionPlugin/index.ts +3 -2
- package/src/shaders/index.ts +16 -0
- package/src/type/index.ts +9 -51
- package/dist/assets/fontWorker-OwvKiQ8o.js +0 -2
- package/dist/assets/fontWorker-OwvKiQ8o.js.map +0 -1
- package/dist/assets/index-TKhRxurv.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navaramap/three",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.6",
|
|
5
5
|
"main": "dist/index.umd.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
"astronomy-engine": "2.1.19",
|
|
31
31
|
"stats.js": "0.17.0",
|
|
32
32
|
"three-stdlib": "2.36.1",
|
|
33
|
-
"@navaramap/
|
|
34
|
-
"@navaramap/engine": "0.0.
|
|
35
|
-
"@navaramap/
|
|
36
|
-
"@navaramap/
|
|
37
|
-
"@navaramap/three-
|
|
38
|
-
"@navaramap/
|
|
39
|
-
"@navaramap/worker": "0.0.
|
|
33
|
+
"@navaramap/engine-api": "0.0.6",
|
|
34
|
+
"@navaramap/engine": "0.0.6",
|
|
35
|
+
"@navaramap/font": "0.0.6",
|
|
36
|
+
"@navaramap/three-api": "0.0.6",
|
|
37
|
+
"@navaramap/three-csm": "0.0.6",
|
|
38
|
+
"@navaramap/core": "0.0.6",
|
|
39
|
+
"@navaramap/worker": "0.0.6"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@playwright/test": "1.61.1",
|
|
@@ -71,10 +71,10 @@
|
|
|
71
71
|
"tweakpane": "4.0.5",
|
|
72
72
|
"vite-plugin-glsl": "1.6.0",
|
|
73
73
|
"vite-plugin-static-copy": "4.1.1",
|
|
74
|
-
"@navaramap/three-default-descs": "0.0.
|
|
75
|
-
"@navaramap/three-
|
|
76
|
-
"@navaramap/three-react": "0.0.
|
|
77
|
-
"@navaramap/three-
|
|
74
|
+
"@navaramap/three-default-descs": "0.0.6",
|
|
75
|
+
"@navaramap/three-default-plugin": "0.0.6",
|
|
76
|
+
"@navaramap/three-react": "0.0.6",
|
|
77
|
+
"@navaramap/three-plugins": "0.0.6"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
80
|
"start": "vite",
|
package/src/core/DescRegistry.ts
CHANGED
|
@@ -80,9 +80,10 @@ export abstract class DescRegistry<
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* Get constructor by name
|
|
83
|
+
* Get constructor by name. Public so `ViewContext.findLight`/`findMesh`
|
|
84
|
+
* can resolve a registered key to its descriptor class.
|
|
84
85
|
*/
|
|
85
|
-
|
|
86
|
+
getConstructor(name: string): TConstructor | undefined {
|
|
86
87
|
return this.registry.get(name);
|
|
87
88
|
}
|
|
88
89
|
}
|
package/src/core/EffectDesc.ts
CHANGED
|
@@ -6,6 +6,7 @@ import invariant from "tiny-invariant";
|
|
|
6
6
|
|
|
7
7
|
import { Pass } from "../effects";
|
|
8
8
|
import type ThreeView from "../index";
|
|
9
|
+
import type { GBufferName } from "../material/gbufferLayout";
|
|
9
10
|
|
|
10
11
|
import {
|
|
11
12
|
BaseDesc,
|
|
@@ -194,6 +195,15 @@ export abstract class EffectDesc<
|
|
|
194
195
|
static insertBefore?: string[];
|
|
195
196
|
/** Set to `true` to allow multiple instances of this effect in the pipeline. */
|
|
196
197
|
static allowDuplication?: boolean;
|
|
198
|
+
/**
|
|
199
|
+
* Optional G-buffer attachments this effect reads. The view derives its
|
|
200
|
+
* G-buffer configuration as the union of these across active effects:
|
|
201
|
+
* adding an effect allocates what it needs, removing the last effect that
|
|
202
|
+
* needs a buffer releases it. Changing the configuration reallocates
|
|
203
|
+
* attachments and recompiles shaders, so add effects once and tune them
|
|
204
|
+
* via `update()` instead of re-adding.
|
|
205
|
+
*/
|
|
206
|
+
static requiredBuffers?: readonly GBufferName[];
|
|
197
207
|
|
|
198
208
|
private instanceId: string;
|
|
199
209
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type ThreeView from "../index";
|
|
2
|
+
import type { GBufferName } from "../material/gbufferLayout";
|
|
2
3
|
|
|
3
4
|
import { DescRegistry } from "./DescRegistry";
|
|
4
5
|
import { EffectDesc, type EffectConfig } from "./EffectDesc";
|
|
@@ -28,4 +29,17 @@ export class EffectDescRegistry extends DescRegistry<
|
|
|
28
29
|
findEffectType(config: Record<string, unknown>): string | null {
|
|
29
30
|
return this.findTypeFromConfig(config);
|
|
30
31
|
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Optional G-buffer attachments the effect type declares via the
|
|
35
|
+
* `requiredBuffers` static (empty when it declares none).
|
|
36
|
+
*/
|
|
37
|
+
getRequiredBuffers(effectType: string): readonly GBufferName[] {
|
|
38
|
+
const EffectClass = this.getConstructor(effectType) as
|
|
39
|
+
| (EffectDescConstructor & {
|
|
40
|
+
requiredBuffers?: readonly GBufferName[];
|
|
41
|
+
})
|
|
42
|
+
| undefined;
|
|
43
|
+
return EffectClass?.requiredBuffers ?? [];
|
|
44
|
+
}
|
|
31
45
|
}
|
package/src/core/MeshDesc.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { BaseEventMap, XYZ } from "@navaramap/core";
|
|
2
|
-
import { Euler, Matrix4, Object3D, Vector3 } from "three";
|
|
2
|
+
import { Euler, Matrix4, Object3D, Vector3, type Mesh } from "three";
|
|
3
3
|
import invariant from "tiny-invariant";
|
|
4
4
|
|
|
5
5
|
import type ThreeView from "../index";
|
|
6
|
+
import { applyLitOption } from "../material";
|
|
6
7
|
import type { Scenes } from "../scene";
|
|
7
8
|
|
|
8
9
|
import {
|
|
@@ -44,11 +45,18 @@ export type MeshConfig = {
|
|
|
44
45
|
* Disables Three.js's auto matrix-world update.
|
|
45
46
|
*/
|
|
46
47
|
matrixWorld?: Matrix4;
|
|
48
|
+
/**
|
|
49
|
+
* Lighting override for every material of the mesh. `false` outputs albedo
|
|
50
|
+
* only, while normals and the shadow G-buffer keep being written.
|
|
51
|
+
* `undefined` follows `view.lit` — passing it to `update()` resets a mesh
|
|
52
|
+
* to inheriting again.
|
|
53
|
+
*/
|
|
54
|
+
lit?: boolean;
|
|
47
55
|
} & BaseDescConfig;
|
|
48
56
|
|
|
49
57
|
export type MeshUpdate = Pick<
|
|
50
58
|
MeshConfig,
|
|
51
|
-
"position" | "scale" | "rotation" | "matrix" | "matrixWorld"
|
|
59
|
+
"position" | "scale" | "rotation" | "matrix" | "matrixWorld" | "lit"
|
|
52
60
|
> &
|
|
53
61
|
BaseDescConfigUpdate;
|
|
54
62
|
|
|
@@ -214,6 +222,8 @@ export abstract class MeshDesc<
|
|
|
214
222
|
public rotation?: XYZ;
|
|
215
223
|
public matrix?: Matrix4;
|
|
216
224
|
public matrixWorld?: Matrix4;
|
|
225
|
+
/** Three-state `lit` override (see {@link MeshConfig.lit}). */
|
|
226
|
+
public lit?: boolean;
|
|
217
227
|
private prevPassKey?: PassKey;
|
|
218
228
|
|
|
219
229
|
constructor(view: ThreeView, ctx: ViewContext, config?: Config) {
|
|
@@ -224,6 +234,25 @@ export abstract class MeshDesc<
|
|
|
224
234
|
this.rotation = resolvedConfig.rotation;
|
|
225
235
|
this.matrix = resolvedConfig.matrix;
|
|
226
236
|
this.matrixWorld = resolvedConfig.matrixWorld;
|
|
237
|
+
this.lit = resolvedConfig.lit;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Applies {@link lit} to every material under the mesh. Subclasses that
|
|
242
|
+
* attach materials asynchronously (glTF) call it again once they exist.
|
|
243
|
+
*/
|
|
244
|
+
protected applyLit(): void {
|
|
245
|
+
const root = this.raw;
|
|
246
|
+
if (!root) return;
|
|
247
|
+
root.traverse((object) => {
|
|
248
|
+
const material = (object as Partial<Mesh>).material;
|
|
249
|
+
if (!material) return;
|
|
250
|
+
if (Array.isArray(material)) {
|
|
251
|
+
for (const m of material) applyLitOption(m, this.lit);
|
|
252
|
+
} else {
|
|
253
|
+
applyLitOption(material, this.lit);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
227
256
|
}
|
|
228
257
|
|
|
229
258
|
/**
|
|
@@ -272,6 +301,7 @@ export abstract class MeshDesc<
|
|
|
272
301
|
invariant(this.raw);
|
|
273
302
|
|
|
274
303
|
this.applyTransform();
|
|
304
|
+
this.applyLit();
|
|
275
305
|
|
|
276
306
|
this._instance.visible = this.visible;
|
|
277
307
|
|
|
@@ -391,6 +421,12 @@ export abstract class MeshDesc<
|
|
|
391
421
|
if (updates.scale !== undefined) this.scale = updates.scale;
|
|
392
422
|
if (updates.rotation !== undefined) this.rotation = updates.rotation;
|
|
393
423
|
|
|
424
|
+
// Key presence, not value: `!== undefined` could not express a reset.
|
|
425
|
+
if ("lit" in updates) {
|
|
426
|
+
this.lit = updates.lit;
|
|
427
|
+
this.applyLit();
|
|
428
|
+
}
|
|
429
|
+
|
|
394
430
|
if (spatialChanged) {
|
|
395
431
|
// With a frame present, the effective transform depends on the
|
|
396
432
|
// combination of all stored fields — recompose the whole thing.
|
|
@@ -428,6 +464,7 @@ export abstract class MeshDesc<
|
|
|
428
464
|
}
|
|
429
465
|
this.prevPassKey = nextPassKey;
|
|
430
466
|
this.addToScene(nextPassKey);
|
|
467
|
+
this.ctx.emit("meshPassKeyChanged");
|
|
431
468
|
}
|
|
432
469
|
|
|
433
470
|
onDestroy(): void {
|
|
@@ -39,6 +39,10 @@ export abstract class MeshDescWithSelectiveEffect<
|
|
|
39
39
|
this.onPassKeyChange();
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
private _onGBufferChanged = () => {
|
|
43
|
+
this.onPassKeyChange();
|
|
44
|
+
};
|
|
45
|
+
|
|
42
46
|
constructor(view: ThreeView, ctx: ViewContext, config?: Config) {
|
|
43
47
|
const resolvedConfig = config ?? ({} as Config);
|
|
44
48
|
super(view, ctx, resolvedConfig);
|
|
@@ -51,11 +55,16 @@ export abstract class MeshDescWithSelectiveEffect<
|
|
|
51
55
|
// ordering between SE participants stays consistent — an empty
|
|
52
56
|
// effectIds list still has to coexist with non-empty siblings, and is
|
|
53
57
|
// expressed by effectIdsMask=0 rather than by falling back to opaque.
|
|
54
|
-
// When no effect is registered, MRT participation is pure overhead, so
|
|
55
|
-
// defer to the parent's default (opaque) pass.
|
|
56
58
|
if (this.ctx.selectiveEffectRegistry.slotCount > 0) {
|
|
57
59
|
return "mrt";
|
|
58
60
|
}
|
|
61
|
+
// Likewise while any optional G-buffer is allocated: outside the MRT pass
|
|
62
|
+
// the mesh leaves the G-buffer holding what lies behind it, and effects
|
|
63
|
+
// reading those buffers shade "through" it.
|
|
64
|
+
if (Object.values(this.view.buffers).some(Boolean)) {
|
|
65
|
+
return "mrt";
|
|
66
|
+
}
|
|
67
|
+
// Otherwise MRT participation is pure overhead.
|
|
59
68
|
return super.getPassKey();
|
|
60
69
|
}
|
|
61
70
|
|
|
@@ -67,6 +76,8 @@ export abstract class MeshDescWithSelectiveEffect<
|
|
|
67
76
|
|
|
68
77
|
// Recompute mask when slot assignments change (effect added/removed)
|
|
69
78
|
this.ctx.on("effectSlotsChanged", this._onSlotsChanged);
|
|
79
|
+
// G-buffer allocation changes also flip getPassKey() (mrt <-> opaque).
|
|
80
|
+
this.ctx.on("gbufferChanged", this._onGBufferChanged);
|
|
70
81
|
}
|
|
71
82
|
|
|
72
83
|
override onUpdateConfig(updates: UpdateConfig): void {
|
|
@@ -122,6 +133,7 @@ export abstract class MeshDescWithSelectiveEffect<
|
|
|
122
133
|
|
|
123
134
|
// Unsubscribe from slot changes
|
|
124
135
|
this.ctx.off("effectSlotsChanged", this._onSlotsChanged);
|
|
136
|
+
this.ctx.off("gbufferChanged", this._onGBufferChanged);
|
|
125
137
|
|
|
126
138
|
super.onDestroy();
|
|
127
139
|
}
|
package/src/core/ViewContext.ts
CHANGED
|
@@ -2,17 +2,22 @@ import { EventHandler } from "@navaramap/core";
|
|
|
2
2
|
import type { Core } from "@navaramap/engine";
|
|
3
3
|
import type { ConcurrencyManager } from "@navaramap/worker";
|
|
4
4
|
import type { Pass as PostProcessingPass } from "postprocessing";
|
|
5
|
-
import type { Material, Object3D, WebGLRenderer } from "three";
|
|
5
|
+
import type { Material, Object3D, Texture, WebGLRenderer } from "three";
|
|
6
6
|
import invariant from "tiny-invariant";
|
|
7
7
|
|
|
8
8
|
import type { LayersManager } from "../layersManager";
|
|
9
|
+
import { type ResolvedGBufferOptions } from "../material/gbufferLayout";
|
|
9
10
|
import type { PickableMesh } from "../mesh/pickableMesh";
|
|
10
11
|
import type { RenderPassOrchestrator } from "../orchestrators";
|
|
11
12
|
import type { CustomRenderPass } from "../passes";
|
|
12
13
|
import type { Scenes } from "../scene";
|
|
13
14
|
import type { MeshCache } from "../type";
|
|
14
15
|
|
|
15
|
-
import type { EffectHandle } from "./BaseHandle";
|
|
16
|
+
import type { EffectHandle, LightHandle, MeshHandle } from "./BaseHandle";
|
|
17
|
+
import type { EffectDesc } from "./EffectDesc";
|
|
18
|
+
import type { LightDesc } from "./LightDesc";
|
|
19
|
+
import type { MeshDesc } from "./MeshDesc";
|
|
20
|
+
import type { Registries } from "./Registries";
|
|
16
21
|
import { SelectiveEffectRegistry } from "./SelectiveEffectRegistry";
|
|
17
22
|
|
|
18
23
|
type ViewContextEvents = {
|
|
@@ -31,6 +36,18 @@ type ViewContextEvents = {
|
|
|
31
36
|
* Listeners should recompute effectIdsMask to stay in sync.
|
|
32
37
|
*/
|
|
33
38
|
effectSlotsChanged: () => void;
|
|
39
|
+
/**
|
|
40
|
+
* Emitted when the derived G-buffer configuration changes. Meshes re-evaluate
|
|
41
|
+
* their pass placement: outside the MRT pass they would leave the G-buffer
|
|
42
|
+
* holding whatever lies behind them.
|
|
43
|
+
*/
|
|
44
|
+
gbufferChanged: () => void;
|
|
45
|
+
/**
|
|
46
|
+
* Emitted when a mesh moves between render scenes. The view re-derives the
|
|
47
|
+
* buffer configuration from it: draped meshes need the globe normal but,
|
|
48
|
+
* not being effects, cannot declare `requiredBuffers` themselves.
|
|
49
|
+
*/
|
|
50
|
+
meshPassKeyChanged: () => void;
|
|
34
51
|
};
|
|
35
52
|
|
|
36
53
|
/**
|
|
@@ -49,6 +66,7 @@ type ViewContextEvents = {
|
|
|
49
66
|
export class ViewContext extends EventHandler<ViewContextEvents> {
|
|
50
67
|
private _selectiveEffectRegistry: SelectiveEffectRegistry;
|
|
51
68
|
private _renderPass?: CustomRenderPass;
|
|
69
|
+
private _registries?: Registries;
|
|
52
70
|
|
|
53
71
|
constructor(
|
|
54
72
|
/** Scene containers for different rendering passes. */
|
|
@@ -133,6 +151,26 @@ export class ViewContext extends EventHandler<ViewContextEvents> {
|
|
|
133
151
|
this._renderPass = renderPass;
|
|
134
152
|
}
|
|
135
153
|
|
|
154
|
+
/** @internal */
|
|
155
|
+
_setRegistries(registries: Registries) {
|
|
156
|
+
this._registries = registries;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @internal
|
|
161
|
+
* No-op until the pass exists — it reads the view's configuration at
|
|
162
|
+
* creation time.
|
|
163
|
+
*/
|
|
164
|
+
_setGBufferOptions(buffers: ResolvedGBufferOptions): void {
|
|
165
|
+
this._renderPass?.setBuffers(buffers);
|
|
166
|
+
this.emit("gbufferChanged");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** @internal No-op until the pass exists (see {@link _setGBufferOptions}). */
|
|
170
|
+
_setLit(lit: boolean): void {
|
|
171
|
+
this._renderPass?.setLit(lit);
|
|
172
|
+
}
|
|
173
|
+
|
|
136
174
|
/**
|
|
137
175
|
* Gets the globe depth texture for post-processing effects.
|
|
138
176
|
*/
|
|
@@ -162,23 +200,47 @@ export class ViewContext extends EventHandler<ViewContextEvents> {
|
|
|
162
200
|
*/
|
|
163
201
|
getNormalTexture() {
|
|
164
202
|
invariant(this._renderPass, "CustomRenderPass isn't initialized yet.");
|
|
165
|
-
|
|
203
|
+
const index = this._renderPass.textureIndex.normal;
|
|
204
|
+
return index === undefined
|
|
205
|
+
? undefined
|
|
206
|
+
: this._renderPass.gbufferRenderTarget.textures[index];
|
|
166
207
|
}
|
|
167
208
|
|
|
168
209
|
/**
|
|
169
210
|
* Gets the effect IDs texture from the G-buffer.
|
|
211
|
+
* `undefined` when the view disabled `buffers.selectiveEffect`.
|
|
170
212
|
*/
|
|
171
|
-
getEffectIdsTexture() {
|
|
213
|
+
getEffectIdsTexture(): Texture | undefined {
|
|
172
214
|
invariant(this._renderPass, "CustomRenderPass isn't initialized yet.");
|
|
173
|
-
|
|
215
|
+
const index = this._renderPass.textureIndex.effectIds;
|
|
216
|
+
return index === undefined
|
|
217
|
+
? undefined
|
|
218
|
+
: this._renderPass.gbufferRenderTarget.textures[index];
|
|
174
219
|
}
|
|
175
220
|
|
|
176
221
|
/**
|
|
177
222
|
* Gets the emissive texture from the G-buffer.
|
|
223
|
+
* `undefined` when the view disabled `buffers.emissive`.
|
|
224
|
+
*/
|
|
225
|
+
getEmissiveTexture(): Texture | undefined {
|
|
226
|
+
invariant(this._renderPass, "CustomRenderPass isn't initialized yet.");
|
|
227
|
+
const index = this._renderPass.textureIndex.emissive;
|
|
228
|
+
return index === undefined
|
|
229
|
+
? undefined
|
|
230
|
+
: this._renderPass.gbufferRenderTarget.textures[index];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Gets the shadow texture (R=shadow amount, 0=lit..1=fully shadowed) from
|
|
235
|
+
* the G-buffer. `undefined` unless an active effect requires the `shadow`
|
|
236
|
+
* buffer.
|
|
178
237
|
*/
|
|
179
|
-
|
|
238
|
+
getShadowTexture(): Texture | undefined {
|
|
180
239
|
invariant(this._renderPass, "CustomRenderPass isn't initialized yet.");
|
|
181
|
-
|
|
240
|
+
const index = this._renderPass.textureIndex.shadow;
|
|
241
|
+
return index === undefined
|
|
242
|
+
? undefined
|
|
243
|
+
: this._renderPass.gbufferRenderTarget.textures[index];
|
|
182
244
|
}
|
|
183
245
|
|
|
184
246
|
// --- Descriptor query ---
|
|
@@ -188,6 +250,53 @@ export class ViewContext extends EventHandler<ViewContextEvents> {
|
|
|
188
250
|
return this.layersManager.getEffectDescs();
|
|
189
251
|
}
|
|
190
252
|
|
|
253
|
+
/** @internal Iterate over all registered light descriptors. */
|
|
254
|
+
_getLights(): Generator<LightHandle> {
|
|
255
|
+
return this.layersManager.getLightDescs();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** @internal Iterate over all registered mesh descriptors. */
|
|
259
|
+
_getMeshes(): Generator<MeshHandle> {
|
|
260
|
+
return this.layersManager.getMeshDescs();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Finds an active effect descriptor by its `key` (e.g. `"mrt"`,
|
|
265
|
+
* `"selectiveBloom"`). Returns the first match, or `undefined`.
|
|
266
|
+
*/
|
|
267
|
+
findEffect<T extends EffectDesc = EffectDesc>(key: string): T | undefined {
|
|
268
|
+
for (const handle of this._getEffects()) {
|
|
269
|
+
if (handle.ref.getKey() === key) return handle.ref as T;
|
|
270
|
+
}
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Finds the first active light descriptor registered under `key` (e.g.
|
|
276
|
+
* `"sun"`), for inheriting the scene's lighting in a custom effect.
|
|
277
|
+
*/
|
|
278
|
+
findLight<T extends LightDesc = LightDesc>(key: string): T | undefined {
|
|
279
|
+
const LightClass = this._registries?.light.getConstructor(key);
|
|
280
|
+
if (!LightClass) return undefined;
|
|
281
|
+
for (const handle of this._getLights()) {
|
|
282
|
+
if (handle.ref instanceof LightClass) return handle.ref as T;
|
|
283
|
+
}
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Finds an active mesh descriptor by its registered key (e.g. `"box"`,
|
|
289
|
+
* `"gltfModel"`). Returns the first match, or `undefined`.
|
|
290
|
+
*/
|
|
291
|
+
findMesh<T extends MeshDesc = MeshDesc>(key: string): T | undefined {
|
|
292
|
+
const MeshClass = this._registries?.mesh.getConstructor(key);
|
|
293
|
+
if (!MeshClass) return undefined;
|
|
294
|
+
for (const handle of this._getMeshes()) {
|
|
295
|
+
if (handle.ref instanceof MeshClass) return handle.ref as T;
|
|
296
|
+
}
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
|
|
191
300
|
/**
|
|
192
301
|
* Register a material for CSM shadow rendering.
|
|
193
302
|
* @experimental This API may change or be removed in future versions.
|
package/src/device.ts
CHANGED
|
@@ -53,8 +53,10 @@ const MOBILE_MAX_PIXEL_RATIO = 1.0;
|
|
|
53
53
|
/**
|
|
54
54
|
* Default pixel-ratio cap applied on every mobile device (without opting into
|
|
55
55
|
* `mobileOptimization`). The fixed screen-sized render-target stack (composer
|
|
56
|
-
* buffers, gbuffer MRT
|
|
57
|
-
* `fixed_gpu_bytes`) grows quadratically with the drawing-buffer size
|
|
56
|
+
* buffers, gbuffer MRT and depth textures, counted by the ledger as
|
|
57
|
+
* `fixed_gpu_bytes`) grows quadratically with the drawing-buffer size — and
|
|
58
|
+
* with the derived `buffers` configuration, which starts at two attachments
|
|
59
|
+
* (color + normal) and grows as effects declare `requiredBuffers`: at
|
|
58
60
|
* DPR 3 it alone can exceed a mobile tile-cache budget and permanently close
|
|
59
61
|
* the load gate. Above DPR 2 the sharpness gain is marginal on phone screens.
|
|
60
62
|
*/
|
package/src/index.ts
CHANGED
|
@@ -108,7 +108,14 @@ import {
|
|
|
108
108
|
import { FinalCopyEffectDesc } from "./layers/effect/FinalCopyEffectDesc";
|
|
109
109
|
import { LayersManager } from "./layersManager";
|
|
110
110
|
import { disposeTexture } from "./loaders";
|
|
111
|
-
import {
|
|
111
|
+
import {
|
|
112
|
+
overrideMaterialsForMRT,
|
|
113
|
+
GBUFFER_ATTACHMENT_NAMES,
|
|
114
|
+
resolveGBufferOptions,
|
|
115
|
+
unionGBufferRequirements,
|
|
116
|
+
type GBufferName,
|
|
117
|
+
type ResolvedGBufferOptions,
|
|
118
|
+
} from "./material";
|
|
112
119
|
import type { TileMesh } from "./mesh/tile";
|
|
113
120
|
import { RenderPassOrchestrator } from "./orchestrators/RenderPassOrchestrator";
|
|
114
121
|
import { PickHelper } from "./pick/pickHelper";
|
|
@@ -245,11 +252,6 @@ export type WorkerMemoryStats = {
|
|
|
245
252
|
fontWorker: FontWorkerMemoryStats | undefined;
|
|
246
253
|
};
|
|
247
254
|
|
|
248
|
-
// NOTE:
|
|
249
|
-
// This overrides all materials to output a normal buffer, meaning Navara operates using MRT (Multiple Render Targets).
|
|
250
|
-
// Currently, Navara requires two buffers, so your shader must output them.
|
|
251
|
-
overrideMaterialsForMRT();
|
|
252
|
-
|
|
253
255
|
/**
|
|
254
256
|
* Configuration options for initializing ThreeView.
|
|
255
257
|
*/
|
|
@@ -792,6 +794,15 @@ export default class ThreeView<
|
|
|
792
794
|
/** Built-in attribution UI; unset when disabled or in a worker (no DOM). */
|
|
793
795
|
private _attribution?: AttributionPlugin;
|
|
794
796
|
|
|
797
|
+
/**
|
|
798
|
+
* Derived G-buffer attachment configuration — the union of the
|
|
799
|
+
* `requiredBuffers` declared by the active effects.
|
|
800
|
+
*/
|
|
801
|
+
private _buffers: ResolvedGBufferOptions;
|
|
802
|
+
|
|
803
|
+
/** Scene-level default of the `lit` material option (see {@link lit}). */
|
|
804
|
+
private _lit = true;
|
|
805
|
+
|
|
795
806
|
private pixelRatioMatchedMedia?: MediaQueryList;
|
|
796
807
|
|
|
797
808
|
private fixedGpuBytesReportScheduled = false;
|
|
@@ -808,9 +819,43 @@ export default class ThreeView<
|
|
|
808
819
|
return this._attribution;
|
|
809
820
|
}
|
|
810
821
|
|
|
822
|
+
/**
|
|
823
|
+
* Buffers currently allocated. Not configurable — derived as the union of
|
|
824
|
+
* the active effects' `requiredBuffers`. All but `globeNormal` are G-buffer
|
|
825
|
+
* attachments.
|
|
826
|
+
*/
|
|
827
|
+
get buffers(): ResolvedGBufferOptions {
|
|
828
|
+
return this._buffers;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Scene-level default of the `lit` material option. `false` makes every
|
|
833
|
+
* material without an explicit `lit` output plain albedo, while normals and
|
|
834
|
+
* the shadow G-buffer keep being written – the input for deferred lighting.
|
|
835
|
+
* Toggling recompiles the affected shaders once.
|
|
836
|
+
* @defaultValue true
|
|
837
|
+
*/
|
|
838
|
+
get lit(): boolean {
|
|
839
|
+
return this._lit;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
set lit(value: boolean) {
|
|
843
|
+
if (this._lit === value) return;
|
|
844
|
+
this._lit = value;
|
|
845
|
+
this.viewContext._setLit(value);
|
|
846
|
+
this.forceUpdate();
|
|
847
|
+
}
|
|
848
|
+
|
|
811
849
|
constructor(options: Options = {}) {
|
|
812
850
|
super();
|
|
813
851
|
|
|
852
|
+
// Patches the global ShaderLib, but the injected source is
|
|
853
|
+
// view-independent (defines carry the layout). Here rather than at module
|
|
854
|
+
// import so importing the library has no side effects.
|
|
855
|
+
overrideMaterialsForMRT();
|
|
856
|
+
|
|
857
|
+
this._buffers = resolveGBufferOptions();
|
|
858
|
+
|
|
814
859
|
if (!options.canvas) {
|
|
815
860
|
const div = document.createElement("div");
|
|
816
861
|
div.id = "navara-root";
|
|
@@ -1412,6 +1457,8 @@ export default class ThreeView<
|
|
|
1412
1457
|
this._meshes,
|
|
1413
1458
|
);
|
|
1414
1459
|
this.registries = new Registries(this, this.viewContext);
|
|
1460
|
+
this.viewContext.on("meshPassKeyChanged", this._syncGBuffers);
|
|
1461
|
+
this.viewContext._setRegistries(this.registries);
|
|
1415
1462
|
this.eventContext = new EventContext({
|
|
1416
1463
|
eventManager: this._eventManager,
|
|
1417
1464
|
scenes: this._scenes,
|
|
@@ -2039,9 +2086,26 @@ export default class ThreeView<
|
|
|
2039
2086
|
throw new UnknownTypeError("effect", config);
|
|
2040
2087
|
}
|
|
2041
2088
|
|
|
2089
|
+
// Reject the effect while nothing is created yet if its required
|
|
2090
|
+
// G-buffers would exceed the device's MRT attachment limit
|
|
2091
|
+
// (gl.MAX_DRAW_BUFFERS – spec minimum 4, nearly all devices expose 8).
|
|
2092
|
+
this._assertGBufferCapacity(effectType);
|
|
2093
|
+
|
|
2042
2094
|
// Create effect descriptor instance
|
|
2043
2095
|
const effectDesc = this.registries.effect.create(effectType, config);
|
|
2044
2096
|
|
|
2097
|
+
const l = new EffectHandle(effectDesc);
|
|
2098
|
+
|
|
2099
|
+
// Store the effect descriptor
|
|
2100
|
+
this.layersManager.add(l);
|
|
2101
|
+
|
|
2102
|
+
// Allocate before `onCreate()`: `createPass()` reads the buffers this
|
|
2103
|
+
// effect declared, so they have to exist by then. Reallocates attachments
|
|
2104
|
+
// and recompiles shaders when the derived configuration actually changes,
|
|
2105
|
+
// so tune effects via `update()` rather than re-adding them.
|
|
2106
|
+
this._syncGBuffers();
|
|
2107
|
+
l.on("deleted", this._syncGBuffers);
|
|
2108
|
+
|
|
2045
2109
|
// Initialize the effect
|
|
2046
2110
|
effectDesc.onCreate();
|
|
2047
2111
|
|
|
@@ -2053,15 +2117,63 @@ export default class ThreeView<
|
|
|
2053
2117
|
// Trigger re-render
|
|
2054
2118
|
effectDesc.on("needsUpdate", this.forceUpdate);
|
|
2055
2119
|
|
|
2056
|
-
const l = new EffectHandle(effectDesc);
|
|
2057
|
-
|
|
2058
|
-
// Store the effect descriptor
|
|
2059
|
-
this.layersManager.add(l);
|
|
2060
|
-
|
|
2061
2120
|
// Return handle for imperative access
|
|
2062
2121
|
return l as EffectHandle<L>;
|
|
2063
2122
|
}
|
|
2064
2123
|
|
|
2124
|
+
/**
|
|
2125
|
+
* Re-derives the G-buffer configuration from the union of the active
|
|
2126
|
+
* effects' `requiredBuffers` and applies it to the MRT pass when changed.
|
|
2127
|
+
*/
|
|
2128
|
+
private _syncGBuffers = (): void => {
|
|
2129
|
+
const requirements: (readonly GBufferName[])[] = [];
|
|
2130
|
+
for (const handle of this.layersManager.getEffectDescs()) {
|
|
2131
|
+
const EffectClass = handle.ref.constructor as {
|
|
2132
|
+
requiredBuffers?: readonly GBufferName[];
|
|
2133
|
+
};
|
|
2134
|
+
if (EffectClass.requiredBuffers) {
|
|
2135
|
+
requirements.push(EffectClass.requiredBuffers);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
// Draped meshes are not effects, so they cannot declare this themselves:
|
|
2139
|
+
// they read the terrain normal through the globe-normal copy.
|
|
2140
|
+
if (this._scenes.draped.children.length > 0) {
|
|
2141
|
+
requirements.push(["globeNormal"]);
|
|
2142
|
+
}
|
|
2143
|
+
this._buffers = unionGBufferRequirements(requirements);
|
|
2144
|
+
this.viewContext._setGBufferOptions(this._buffers);
|
|
2145
|
+
};
|
|
2146
|
+
|
|
2147
|
+
/**
|
|
2148
|
+
* Throws when enabling `effectType`'s required G-buffers would push the
|
|
2149
|
+
* attachment count (color + normal + optional buffers) past the device's
|
|
2150
|
+
* `MAX_DRAW_BUFFERS` limit.
|
|
2151
|
+
*/
|
|
2152
|
+
private _assertGBufferCapacity(effectType: string): void {
|
|
2153
|
+
const required = this.registries.effect.getRequiredBuffers(effectType);
|
|
2154
|
+
if (required.length === 0) return;
|
|
2155
|
+
const prospective = unionGBufferRequirements([
|
|
2156
|
+
(Object.keys(this._buffers) as GBufferName[]).filter(
|
|
2157
|
+
(name) => this._buffers[name],
|
|
2158
|
+
),
|
|
2159
|
+
required,
|
|
2160
|
+
]);
|
|
2161
|
+
const attachmentCount =
|
|
2162
|
+
2 + GBUFFER_ATTACHMENT_NAMES.filter((name) => prospective[name]).length;
|
|
2163
|
+
// Navara requires WebGL2 (MRT), so the context is always WebGL2.
|
|
2164
|
+
const gl = this.viewContext
|
|
2165
|
+
.getRenderer()
|
|
2166
|
+
.getContext() as WebGL2RenderingContext;
|
|
2167
|
+
const maxDrawBuffers = gl.getParameter(gl.MAX_DRAW_BUFFERS) as number;
|
|
2168
|
+
if (attachmentCount > maxDrawBuffers) {
|
|
2169
|
+
throw new Error(
|
|
2170
|
+
`The "${effectType}" effect requires G-buffers [${required.join(", ")}], ` +
|
|
2171
|
+
`but the resulting ${attachmentCount} MRT attachments exceed this ` +
|
|
2172
|
+
`device's MAX_DRAW_BUFFERS (${maxDrawBuffers}).`,
|
|
2173
|
+
);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2065
2177
|
/**
|
|
2066
2178
|
* Finds a resource layer by its ID.
|
|
2067
2179
|
* Only returns resource layers added via `addLayer()` (not mesh, light, or
|
package/src/layer.ts
CHANGED
|
@@ -51,12 +51,14 @@ export type LayerEvent = {
|
|
|
51
51
|
*
|
|
52
52
|
* @example
|
|
53
53
|
* ```typescript
|
|
54
|
-
* //
|
|
55
|
-
* const
|
|
54
|
+
* // Register a GeoJSON source, then render it with a vector layer
|
|
55
|
+
* const geoJsonSource = view.addSource({
|
|
56
56
|
* type: "geojson",
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
57
|
+
* url: "https://example.com/data.geojson",
|
|
58
|
+
* });
|
|
59
|
+
* const geoJsonLayer = view.addLayer({
|
|
60
|
+
* type: "vector",
|
|
61
|
+
* source: geoJsonSource,
|
|
60
62
|
* point: { color: 0xff0000 }
|
|
61
63
|
* });
|
|
62
64
|
*
|