@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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { Globe } from "@navaramap/core";
|
|
2
|
+
import {
|
|
3
|
+
Group,
|
|
4
|
+
Mesh,
|
|
5
|
+
MeshStandardMaterial,
|
|
6
|
+
PerspectiveCamera,
|
|
7
|
+
Scene,
|
|
8
|
+
WebGLRenderTarget,
|
|
9
|
+
} from "three";
|
|
10
|
+
import { describe, expect, it } from "vitest";
|
|
11
|
+
|
|
12
|
+
import { NVR_UNLIT_SCENE_DEFINE } from "../material";
|
|
13
|
+
import {
|
|
14
|
+
USE_GBUFFER_SHADOW_DEFINE,
|
|
15
|
+
resolveGBufferOptions,
|
|
16
|
+
} from "../material/gbufferLayout";
|
|
17
|
+
import type { Scenes } from "../scene";
|
|
18
|
+
|
|
19
|
+
import { CustomRenderPass } from "./CustomRenderPass";
|
|
20
|
+
|
|
21
|
+
const createScenes = (): Scenes => ({
|
|
22
|
+
light: new Group(),
|
|
23
|
+
mrt: new Scene(),
|
|
24
|
+
globe: new Scene(),
|
|
25
|
+
draped: new Scene(),
|
|
26
|
+
opaque: new Scene(),
|
|
27
|
+
transparent: new Scene(),
|
|
28
|
+
skyEnvMap: new Scene(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const addMesh = (scene: Scene): MeshStandardMaterial => {
|
|
32
|
+
const material = new MeshStandardMaterial();
|
|
33
|
+
scene.add(new Mesh(undefined, material));
|
|
34
|
+
return material;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const createPass = (scenes: Scenes, lit: boolean) =>
|
|
38
|
+
new CustomRenderPass(
|
|
39
|
+
scenes,
|
|
40
|
+
new PerspectiveCamera(),
|
|
41
|
+
new WebGLRenderTarget(1, 1),
|
|
42
|
+
{} as Globe,
|
|
43
|
+
{ buffers: resolveGBufferOptions({ shadow: true }), lit },
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Regression: `view.lit` was stamped on the G-buffer scenes only, so meshes
|
|
47
|
+
// in the forward-only scenes – the default routing - stayed lit.
|
|
48
|
+
describe("stampGBufferDefines", () => {
|
|
49
|
+
it("stamps the scene lit default on both G-buffer and forward-only scenes", () => {
|
|
50
|
+
const scenes = createScenes();
|
|
51
|
+
const mrtMaterial = addMesh(scenes.mrt);
|
|
52
|
+
const opaqueMaterial = addMesh(scenes.opaque);
|
|
53
|
+
const transparentMaterial = addMesh(scenes.transparent);
|
|
54
|
+
|
|
55
|
+
const pass = createPass(scenes, false);
|
|
56
|
+
pass["stampGBufferDefines"]();
|
|
57
|
+
|
|
58
|
+
for (const material of [mrtMaterial, opaqueMaterial, transparentMaterial]) {
|
|
59
|
+
expect(material.defines?.[NVR_UNLIT_SCENE_DEFINE]).toBe(1);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("keeps the G-buffer defines out of the forward-only scenes", () => {
|
|
64
|
+
const scenes = createScenes();
|
|
65
|
+
const mrtMaterial = addMesh(scenes.mrt);
|
|
66
|
+
const opaqueMaterial = addMesh(scenes.opaque);
|
|
67
|
+
|
|
68
|
+
const pass = createPass(scenes, true);
|
|
69
|
+
pass["stampGBufferDefines"]();
|
|
70
|
+
|
|
71
|
+
expect(mrtMaterial.defines?.[USE_GBUFFER_SHADOW_DEFINE]).toBe(1);
|
|
72
|
+
// Single-attachment target – the extra outputs would be undeclared.
|
|
73
|
+
expect(opaqueMaterial.defines?.[USE_GBUFFER_SHADOW_DEFINE] ?? false).toBe(
|
|
74
|
+
false,
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("stamps the G-buffer defines when a material moves from opaque to mrt", () => {
|
|
79
|
+
const scenes = createScenes();
|
|
80
|
+
const material = new MeshStandardMaterial();
|
|
81
|
+
const mesh = new Mesh(undefined, material);
|
|
82
|
+
scenes.opaque.add(mesh);
|
|
83
|
+
|
|
84
|
+
const pass = createPass(scenes, true);
|
|
85
|
+
pass["stampGBufferDefines"]();
|
|
86
|
+
|
|
87
|
+
// A shared "already stamped" set would leave it without the outputs.
|
|
88
|
+
scenes.opaque.remove(mesh);
|
|
89
|
+
scenes.mrt.add(mesh);
|
|
90
|
+
pass["stampGBufferDefines"]();
|
|
91
|
+
|
|
92
|
+
expect(material.defines?.[USE_GBUFFER_SHADOW_DEFINE]).toBe(1);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("restamps every material after setLit", () => {
|
|
96
|
+
const scenes = createScenes();
|
|
97
|
+
const opaqueMaterial = addMesh(scenes.opaque);
|
|
98
|
+
|
|
99
|
+
const pass = createPass(scenes, true);
|
|
100
|
+
pass["stampGBufferDefines"]();
|
|
101
|
+
expect(opaqueMaterial.defines?.[NVR_UNLIT_SCENE_DEFINE] ?? false).toBe(
|
|
102
|
+
false,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
pass.setLit(false);
|
|
106
|
+
pass["stampGBufferDefines"]();
|
|
107
|
+
expect(opaqueMaterial.defines?.[NVR_UNLIT_SCENE_DEFINE]).toBe(1);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("globe-normal copy target", () => {
|
|
112
|
+
const size = (pass: CustomRenderPass) =>
|
|
113
|
+
pass.globeNormalCopyPass.texture.image as { width: number; height: number };
|
|
114
|
+
|
|
115
|
+
it("stays 1x1 while no draped mesh exists, even across a resize", () => {
|
|
116
|
+
const pass = createPass(createScenes(), true);
|
|
117
|
+
pass.setSize(800, 600);
|
|
118
|
+
|
|
119
|
+
expect(size(pass).width).toBe(1);
|
|
120
|
+
expect(size(pass).height).toBe(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("applies the size stored while inactive once a draped mesh appears", () => {
|
|
124
|
+
const pass = createPass(createScenes(), true);
|
|
125
|
+
pass.setSize(800, 600);
|
|
126
|
+
|
|
127
|
+
pass["setGlobeNormalActive"](true);
|
|
128
|
+
|
|
129
|
+
expect(size(pass).width).toBe(800);
|
|
130
|
+
expect(size(pass).height).toBe(600);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("shrinks back when the last draped mesh goes away", () => {
|
|
134
|
+
const pass = createPass(createScenes(), true);
|
|
135
|
+
pass.setSize(800, 600);
|
|
136
|
+
pass["setGlobeNormalActive"](true);
|
|
137
|
+
|
|
138
|
+
pass["setGlobeNormalActive"](false);
|
|
139
|
+
|
|
140
|
+
expect(size(pass).width).toBe(1);
|
|
141
|
+
expect(size(pass).height).toBe(1);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -4,18 +4,31 @@ import {
|
|
|
4
4
|
Color,
|
|
5
5
|
DepthStencilFormat,
|
|
6
6
|
DepthTexture,
|
|
7
|
-
HalfFloatType,
|
|
8
|
-
NearestFilter,
|
|
9
7
|
RGBADepthPacking,
|
|
10
8
|
Scene,
|
|
11
9
|
UnsignedInt248Type,
|
|
12
10
|
WebGLRenderTarget,
|
|
11
|
+
type Material,
|
|
12
|
+
type Mesh,
|
|
13
13
|
type PerspectiveCamera,
|
|
14
|
+
type Texture,
|
|
14
15
|
type WebGLRenderer,
|
|
15
16
|
} from "three";
|
|
16
17
|
|
|
17
18
|
import { RenderPass } from "../effects";
|
|
18
|
-
import {
|
|
19
|
+
import { NVR_BLENDED_DEFINE, NVR_UNLIT_SCENE_DEFINE } from "../material";
|
|
20
|
+
import {
|
|
21
|
+
GBUFFER_ATTACHMENT_NAMES,
|
|
22
|
+
GBUFFER_DEFINE_NAMES,
|
|
23
|
+
appendOptionalGBufferAttachments,
|
|
24
|
+
computeGBufferDefines,
|
|
25
|
+
computeGBufferTextureIndex,
|
|
26
|
+
createGBufferAttachments,
|
|
27
|
+
resolveGBufferOptions,
|
|
28
|
+
type GBufferTextureIndex,
|
|
29
|
+
type ResolvedGBufferOptions,
|
|
30
|
+
} from "../material/gbufferLayout";
|
|
31
|
+
import { DrapedMesh, setupMaterialForDrape } from "../mesh/DrapedMesh";
|
|
19
32
|
import type { Scenes } from "../scene";
|
|
20
33
|
|
|
21
34
|
import { AllDepthCopyPass, NormalCopyPass, RenderTargetCopyPass } from ".";
|
|
@@ -30,6 +43,14 @@ export type CustomRenderPassOptions = {
|
|
|
30
43
|
debugNormal?: boolean;
|
|
31
44
|
disableShadow?: boolean;
|
|
32
45
|
allowTransparent?: boolean;
|
|
46
|
+
/** G-buffer attachment configuration. Defaults to all optional buffers disabled. */
|
|
47
|
+
buffers?: ResolvedGBufferOptions;
|
|
48
|
+
/**
|
|
49
|
+
* Scene-level default of the `lit` material option (`view.lit`). When
|
|
50
|
+
* `false`, materials without an explicit `lit` output plain albedo.
|
|
51
|
+
* @defaultValue true
|
|
52
|
+
*/
|
|
53
|
+
lit?: boolean;
|
|
33
54
|
};
|
|
34
55
|
|
|
35
56
|
export class CustomRenderPass extends RenderPass {
|
|
@@ -53,7 +74,39 @@ export class CustomRenderPass extends RenderPass {
|
|
|
53
74
|
});
|
|
54
75
|
|
|
55
76
|
private debugNormalCopyPass?: NormalCopyPass;
|
|
77
|
+
private readonly gbufferScenes: readonly Scene[];
|
|
78
|
+
// Rendered outside the G-buffer pass, so these must never declare G-buffer
|
|
79
|
+
// outputs the render target has no attachments for.
|
|
80
|
+
private readonly forwardScenes: readonly Scene[];
|
|
81
|
+
private readonly stampScenes: readonly Scene[];
|
|
56
82
|
private allowTransparent: boolean;
|
|
83
|
+
private buffers: ResolvedGBufferOptions;
|
|
84
|
+
// Scene-level default of the `lit` material option (`view.lit`).
|
|
85
|
+
private sceneLit: boolean;
|
|
86
|
+
// Template for rebuilding the G-buffer render target in setBuffers.
|
|
87
|
+
private inputBufferTemplate: WebGLRenderTarget;
|
|
88
|
+
/**
|
|
89
|
+
* Attachment indices of `gbufferRenderTarget.textures` for this view.
|
|
90
|
+
* Recomputed by {@link setBuffers}.
|
|
91
|
+
*/
|
|
92
|
+
textureIndex: GBufferTextureIndex;
|
|
93
|
+
// Defines carrying the enabled optional outputs and their locations,
|
|
94
|
+
// stamped onto every material rendered into the G-buffer.
|
|
95
|
+
private gbufferDefines: Readonly<Record<string, number>>;
|
|
96
|
+
private gbufferDefinesStamped = new WeakSet<Material>();
|
|
97
|
+
// Separate from the G-buffer set: the two cover different scene sets, and a
|
|
98
|
+
// shared one would skip the G-buffer defines for a material first seen in
|
|
99
|
+
// the opaque scene that later moves to the MRT pass.
|
|
100
|
+
private litStamped = new WeakSet<Material>();
|
|
101
|
+
private stampDirty = true;
|
|
102
|
+
// Drives the globe-normal copy target, which stays 1x1 until a draped mesh
|
|
103
|
+
// exists. Tracked here because setSize can arrive while it is inactive.
|
|
104
|
+
private globeNormalActive = false;
|
|
105
|
+
private readonly globeNormalUniform: { value: Texture | null };
|
|
106
|
+
private width = 1;
|
|
107
|
+
private height = 1;
|
|
108
|
+
private lastProgramCount = -1;
|
|
109
|
+
private readonly lastSceneChildCounts = [-1, -1, -1, -1, -1];
|
|
57
110
|
|
|
58
111
|
constructor(
|
|
59
112
|
scenes: Scenes,
|
|
@@ -72,25 +125,20 @@ export class CustomRenderPass extends RenderPass {
|
|
|
72
125
|
this._camera = camera;
|
|
73
126
|
this.globe = globe;
|
|
74
127
|
|
|
128
|
+
this.gbufferScenes = [scenes.globe, scenes.mrt, scenes.draped];
|
|
129
|
+
this.forwardScenes = [scenes.opaque, scenes.transparent];
|
|
130
|
+
this.stampScenes = [...this.gbufferScenes, ...this.forwardScenes];
|
|
131
|
+
|
|
132
|
+
this.buffers = options?.buffers ?? resolveGBufferOptions();
|
|
133
|
+
this.sceneLit = options?.lit ?? true;
|
|
134
|
+
this.textureIndex = computeGBufferTextureIndex(this.buffers);
|
|
135
|
+
this.gbufferDefines = computeGBufferDefines(this.buffers);
|
|
136
|
+
this.inputBufferTemplate = inputBuffer;
|
|
137
|
+
|
|
75
138
|
this.gbufferRenderTarget = inputBuffer.clone();
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
80
|
-
// attachment 2: effectIds buffer
|
|
81
|
-
// HalfFloatType preserves integer masks up to 11 bits (0..2047) exactly.
|
|
82
|
-
// NearestFilter is required because this stores discrete bitmask data.
|
|
83
|
-
const effectIdsTexture = this.gbufferRenderTarget.texture.clone();
|
|
84
|
-
effectIdsTexture.type = HalfFloatType;
|
|
85
|
-
effectIdsTexture.minFilter = NearestFilter;
|
|
86
|
-
effectIdsTexture.magFilter = NearestFilter;
|
|
87
|
-
effectIdsTexture.generateMipmaps = false;
|
|
88
|
-
this.gbufferRenderTarget.textures.push(effectIdsTexture);
|
|
89
|
-
// attachment 3: emissive buffer (RGB=diffuseColor×intensity+emissive additive, A=unused)
|
|
90
|
-
// HalfFloatType preserves HDR emissive values for bloom extraction.
|
|
91
|
-
const emissiveTexture = this.gbufferRenderTarget.texture.clone();
|
|
92
|
-
emissiveTexture.type = HalfFloatType;
|
|
93
|
-
this.gbufferRenderTarget.textures.push(emissiveTexture);
|
|
139
|
+
// Extra G-buffer attachments (normal/effectIds/emissive) – see
|
|
140
|
+
// material/gbufferLayout.ts for the layout definition.
|
|
141
|
+
createGBufferAttachments(this.gbufferRenderTarget, this.buffers);
|
|
94
142
|
|
|
95
143
|
// The gbuffer owns its depth texture instead of sharing the composer's one
|
|
96
144
|
// (postprocessing >= 6.39 no longer exposes a writable shared depth
|
|
@@ -117,16 +165,27 @@ export class CustomRenderPass extends RenderPass {
|
|
|
117
165
|
this.allowTransparent = options?.allowTransparent ?? true;
|
|
118
166
|
|
|
119
167
|
this.globeNormalCopyPass = new NormalCopyPass();
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
168
|
+
// The copy target keeps its Texture identity across setSize, so draped
|
|
169
|
+
// materials can bind this ref once.
|
|
170
|
+
this.globeNormalUniform = { value: this.globeNormalCopyPass.texture };
|
|
123
171
|
if (options?.debugNormal) {
|
|
124
172
|
this.debugNormalCopyPass = new NormalCopyPass();
|
|
125
173
|
this.debugNormalCopyPass.unpackNormal = true;
|
|
126
|
-
this.debugNormalCopyPass.setNormalTexture(
|
|
127
|
-
this.gbufferRenderTarget.textures[1],
|
|
128
|
-
);
|
|
129
174
|
}
|
|
175
|
+
this.bindNormalTexture();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Re-points the copy passes at the normal attachment. The attachment is
|
|
180
|
+
* freshly allocated on every {@link setBuffers}, so the reference cannot be
|
|
181
|
+
* captured once.
|
|
182
|
+
*/
|
|
183
|
+
private bindNormalTexture(): void {
|
|
184
|
+
const index = this.textureIndex.normal;
|
|
185
|
+
if (index === undefined) return;
|
|
186
|
+
const texture = this.gbufferRenderTarget.textures[index];
|
|
187
|
+
this.globeNormalCopyPass.setNormalTexture(texture);
|
|
188
|
+
this.debugNormalCopyPass?.setNormalTexture(texture);
|
|
130
189
|
}
|
|
131
190
|
|
|
132
191
|
// Render the scene with world scene that includes user setting object like a light.
|
|
@@ -140,11 +199,170 @@ export class CustomRenderPass extends RenderPass {
|
|
|
140
199
|
scene.remove(this._scenes.light);
|
|
141
200
|
}
|
|
142
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Applies a new derived buffer configuration. Attachment locations may have
|
|
204
|
+
* shifted, so every material is re-stamped and its shader recompiled.
|
|
205
|
+
*/
|
|
206
|
+
setBuffers(buffers: ResolvedGBufferOptions): void {
|
|
207
|
+
// Only the attachment-backed names matter here — `globeNormal` is picked
|
|
208
|
+
// up by render() and must not trigger a rebuild on its own.
|
|
209
|
+
const unchanged = GBUFFER_ATTACHMENT_NAMES.every(
|
|
210
|
+
(name) => buffers[name] === this.buffers[name],
|
|
211
|
+
);
|
|
212
|
+
this.buffers = buffers;
|
|
213
|
+
if (unchanged) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.textureIndex = computeGBufferTextureIndex(buffers);
|
|
217
|
+
this.gbufferDefines = computeGBufferDefines(buffers);
|
|
218
|
+
this.gbufferDefinesStamped = new WeakSet();
|
|
219
|
+
this.stampDirty = true;
|
|
220
|
+
|
|
221
|
+
// Must be a fresh target: splicing textures in place leaves the renderer's
|
|
222
|
+
// cached GL state on a stale attachment, so writes land in the framebuffer
|
|
223
|
+
// while samplers read empty storage. Only color and depth keep their
|
|
224
|
+
// Texture identity; every optional attachment is reallocated, which is why
|
|
225
|
+
// consumers must re-fetch each frame rather than capture at creation.
|
|
226
|
+
const old = this.gbufferRenderTarget;
|
|
227
|
+
const rt = this.inputBufferTemplate.clone();
|
|
228
|
+
rt.setSize(old.width, old.height);
|
|
229
|
+
rt.texture = old.texture;
|
|
230
|
+
appendOptionalGBufferAttachments(rt, buffers);
|
|
231
|
+
rt.depthTexture = old.depthTexture;
|
|
232
|
+
// three disposes rt.depthTexture with the target; detach the reused one.
|
|
233
|
+
old.depthTexture = null;
|
|
234
|
+
this.gbufferRenderTarget = rt;
|
|
235
|
+
old.dispose();
|
|
236
|
+
|
|
237
|
+
this.copyPass.setRenderTarget(rt);
|
|
238
|
+
this.bindNormalTexture();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Applies a new scene-level `lit` default (`view.lit` changed). */
|
|
242
|
+
setLit(lit: boolean): void {
|
|
243
|
+
if (this.sceneLit === lit) return;
|
|
244
|
+
this.sceneLit = lit;
|
|
245
|
+
this.litStamped = new WeakSet();
|
|
246
|
+
this.stampDirty = true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* O(1) gate keeping the stamping traversal off the per-frame path. The
|
|
251
|
+
* program count catches deeply-nested async additions (a glTF populating a
|
|
252
|
+
* scene-resident group): a material must compile before it can render, so
|
|
253
|
+
* the traversal converges one frame later.
|
|
254
|
+
*/
|
|
255
|
+
private shouldStampGBufferDefines(renderer: WebGLRenderer): boolean {
|
|
256
|
+
let dirty = this.stampDirty;
|
|
257
|
+
this.stampDirty = false;
|
|
258
|
+
|
|
259
|
+
const programCount = renderer.info.programs?.length ?? 0;
|
|
260
|
+
if (programCount !== this.lastProgramCount) {
|
|
261
|
+
this.lastProgramCount = programCount;
|
|
262
|
+
dirty = true;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
for (let i = 0; i < this.stampScenes.length; i++) {
|
|
266
|
+
const count = this.stampScenes[i].children.length;
|
|
267
|
+
if (count !== this.lastSceneChildCounts[i]) {
|
|
268
|
+
this.lastSceneChildCounts[i] = count;
|
|
269
|
+
dirty = true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return dirty;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Materials reach the G-buffer from anywhere (built-ins, enhancers, user
|
|
277
|
+
// ShaderMaterials), so they are stamped here rather than per-desc. `lit` is
|
|
278
|
+
// a lighting define, not a G-buffer one, hence the forward scenes too.
|
|
279
|
+
private stampGBufferDefines(): void {
|
|
280
|
+
for (const scene of this.gbufferScenes) {
|
|
281
|
+
scene.traverse((object) => {
|
|
282
|
+
this.forEachMaterial(object, (m) => this.stampGBufferDefine(m));
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
for (const scene of this.forwardScenes) {
|
|
286
|
+
scene.traverse((object) => {
|
|
287
|
+
this.forEachMaterial(object, (m) => this.stampLitDefine(m));
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
// Drape shading is opted into here rather than per-desc, so a mesh moved
|
|
291
|
+
// into the draped scene picks it up wherever it came from.
|
|
292
|
+
this._scenes.draped.traverse((object) => {
|
|
293
|
+
this.forEachMaterial(object, (m) =>
|
|
294
|
+
setupMaterialForDrape(m, this.globeNormalUniform),
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private forEachMaterial(
|
|
300
|
+
object: unknown,
|
|
301
|
+
visit: (material: Material) => void,
|
|
302
|
+
): void {
|
|
303
|
+
const material = (object as Partial<Mesh>).material;
|
|
304
|
+
if (!material) return;
|
|
305
|
+
if (Array.isArray(material)) {
|
|
306
|
+
for (const m of material) visit(m);
|
|
307
|
+
} else {
|
|
308
|
+
visit(material);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private stampGBufferDefine(material: Material): void {
|
|
313
|
+
material.defines ??= {};
|
|
314
|
+
let changed = false;
|
|
315
|
+
|
|
316
|
+
// `material.transparent` toggles at runtime (e.g. `globe.transparent`),
|
|
317
|
+
// so this one is synced on every visit instead of being gated by the set.
|
|
318
|
+
const blended = material.transparent ? 1 : false;
|
|
319
|
+
if ((material.defines[NVR_BLENDED_DEFINE] ?? false) !== blended) {
|
|
320
|
+
material.defines[NVR_BLENDED_DEFINE] = blended;
|
|
321
|
+
changed = true;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!this.gbufferDefinesStamped.has(material)) {
|
|
325
|
+
this.gbufferDefinesStamped.add(material);
|
|
326
|
+
for (const name of GBUFFER_DEFINE_NAMES) {
|
|
327
|
+
// `false` clears a stale define – three's generateDefines skips it.
|
|
328
|
+
const value = this.gbufferDefines[name] ?? false;
|
|
329
|
+
const current = material.defines[name] ?? false;
|
|
330
|
+
if (current !== value) {
|
|
331
|
+
material.defines[name] = value;
|
|
332
|
+
changed = true;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (changed) {
|
|
338
|
+
material.needsUpdate = true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
this.stampLitDefine(material);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** Per-material `lit` (NVR_LIT / NVR_UNLIT) wins over this in the shader. */
|
|
345
|
+
private stampLitDefine(material: Material): void {
|
|
346
|
+
if (this.litStamped.has(material)) return;
|
|
347
|
+
this.litStamped.add(material);
|
|
348
|
+
|
|
349
|
+
material.defines ??= {};
|
|
350
|
+
const sceneUnlit = this.sceneLit ? false : 1;
|
|
351
|
+
if ((material.defines[NVR_UNLIT_SCENE_DEFINE] ?? false) !== sceneUnlit) {
|
|
352
|
+
material.defines[NVR_UNLIT_SCENE_DEFINE] = sceneUnlit;
|
|
353
|
+
material.needsUpdate = true;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
143
357
|
render(
|
|
144
358
|
renderer: WebGLRenderer,
|
|
145
359
|
inputBuffer: WebGLRenderTarget | null,
|
|
146
360
|
_outputBuffer: WebGLRenderTarget | null,
|
|
147
361
|
) {
|
|
362
|
+
if (this.shouldStampGBufferDefines(renderer)) {
|
|
363
|
+
this.stampGBufferDefines();
|
|
364
|
+
}
|
|
365
|
+
|
|
148
366
|
const shouldDrapeByStencilTest = this._scenes.draped.children.length !== 0;
|
|
149
367
|
|
|
150
368
|
const renderTarget = this.gbufferRenderTarget;
|
|
@@ -179,8 +397,15 @@ export class CustomRenderPass extends RenderPass {
|
|
|
179
397
|
|
|
180
398
|
this._renderWithLight(renderer, this._scenes.globe);
|
|
181
399
|
|
|
182
|
-
//
|
|
183
|
-
|
|
400
|
+
// Draped meshes read it and draw further down, so the copy still happens
|
|
401
|
+
// before its consumer; effects opt in through `requiredBuffers`.
|
|
402
|
+
const needsGlobeNormal =
|
|
403
|
+
(shouldDrapeByStencilTest || this.buffers.globeNormal) &&
|
|
404
|
+
this.textureIndex.normal !== undefined;
|
|
405
|
+
this.setGlobeNormalActive(needsGlobeNormal);
|
|
406
|
+
if (needsGlobeNormal) {
|
|
407
|
+
this.globeNormalCopyPass.render(renderer, null, null);
|
|
408
|
+
}
|
|
184
409
|
|
|
185
410
|
this.globeDepthCopyPass.setDepthTexture(
|
|
186
411
|
renderTarget.depthTexture as DepthTexture,
|
|
@@ -263,13 +488,31 @@ export class CustomRenderPass extends RenderPass {
|
|
|
263
488
|
setDepthTexture(): void {}
|
|
264
489
|
|
|
265
490
|
setSize(width: number, height: number) {
|
|
491
|
+
this.width = width;
|
|
492
|
+
this.height = height;
|
|
266
493
|
this.gbufferRenderTarget.setSize(width, height);
|
|
267
494
|
this.globeDepthCopyPass.setSize(width, height);
|
|
268
495
|
this.allDepthCopyPass.setSize(width, height);
|
|
269
|
-
this.
|
|
496
|
+
if (this.globeNormalActive) {
|
|
497
|
+
this.globeNormalCopyPass.setSize(width, height);
|
|
498
|
+
}
|
|
270
499
|
this.debugNormalCopyPass?.setSize(width, height);
|
|
271
500
|
}
|
|
272
501
|
|
|
502
|
+
/**
|
|
503
|
+
* Grows the globe-normal copy target only while something consumes it, and
|
|
504
|
+
* shrinks it back to 1x1 otherwise — at full resolution it is a screen-sized
|
|
505
|
+
* RGBA16F target, the same cost again as the G-buffer's normal attachment.
|
|
506
|
+
*/
|
|
507
|
+
private setGlobeNormalActive(active: boolean): void {
|
|
508
|
+
if (this.globeNormalActive === active) return;
|
|
509
|
+
this.globeNormalActive = active;
|
|
510
|
+
this.globeNormalCopyPass.setSize(
|
|
511
|
+
active ? this.width : 1,
|
|
512
|
+
active ? this.height : 1,
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
273
516
|
// Drape a feature on the terrain by stencil test.
|
|
274
517
|
// Refs
|
|
275
518
|
// - https://www.isprs.org/proceedings/XXXVII/congress/2_pdf/5_WG-II-5/06.pdf
|
|
@@ -77,6 +77,12 @@ export class RenderTargetCopyPass extends Pass {
|
|
|
77
77
|
this._material.uniforms.color.value = texture;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/** Points the pass at a rebuilt render target (same-purpose replacement). */
|
|
81
|
+
setRenderTarget(renderTarget: WebGLRenderTarget): void {
|
|
82
|
+
this._renderTarget = renderTarget;
|
|
83
|
+
this.setTexture(renderTarget.texture);
|
|
84
|
+
}
|
|
85
|
+
|
|
80
86
|
/**
|
|
81
87
|
* When a depth texture is set, the pass copies its depth into the target's
|
|
82
88
|
* depth buffer via gl_FragDepth. Depth test must be enabled (with AlwaysDepth)
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* await view.init();
|
|
19
19
|
*
|
|
20
20
|
* // A 3D-tiles layer whose tiles embed their own copyright (tracked dynamically).
|
|
21
|
-
* const
|
|
21
|
+
* const photorealSource = view.addSource({ type: "3d-tiles", url });
|
|
22
|
+
* const photoreal = view.addLayer({ type: "3d-tiles", source: photorealSource });
|
|
22
23
|
*
|
|
23
24
|
* // `add` / `remove` manage the set of displayed attributions.
|
|
24
25
|
* view.attribution?.add([
|
|
@@ -124,7 +125,7 @@ let styleRefCount = 0;
|
|
|
124
125
|
*/
|
|
125
126
|
const NAVARA_CREDIT: AttributionItem = {
|
|
126
127
|
attribution: "Navara",
|
|
127
|
-
attributionUrl: "https://navara-docs.
|
|
128
|
+
attributionUrl: "https://navara-docs.reearth.workers.dev//",
|
|
128
129
|
};
|
|
129
130
|
|
|
130
131
|
/**
|
package/src/shaders/index.ts
CHANGED
|
@@ -1 +1,17 @@
|
|
|
1
|
+
import { packing } from "@takram/three-geospatial/shaders";
|
|
2
|
+
import { ShaderChunk } from "three";
|
|
3
|
+
|
|
1
4
|
export * from "@takram/three-geospatial/shaders";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Codec for the normal buffer's RG channels, which are **octahedral**, not a
|
|
8
|
+
* plain `xy * 2 - 1`: decode with `unpackVec2ToNormal`. Prepend to shaders
|
|
9
|
+
* that cannot use `#include` resolution (postprocessing effect fragments).
|
|
10
|
+
*/
|
|
11
|
+
export const NORMAL_PACKING_SHADER: string = packing;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Three's `packing` chunk, for the MRT pass's packed depth buffers – check
|
|
15
|
+
* `depthBufferPacking` / `globeDepthBufferPacking` for the packing in use.
|
|
16
|
+
*/
|
|
17
|
+
export const DEPTH_PACKING_SHADER: string = ShaderChunk.packing;
|
package/src/type/index.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import type { NormalizeWASMClass, TileHandle } from "@navaramap/core";
|
|
2
2
|
import type {
|
|
3
|
-
B3dmLayerDescription,
|
|
4
|
-
PntsLayerDescription,
|
|
5
|
-
Cesium3dTilesLayerDescription,
|
|
6
|
-
GeoJsonLayerDescription,
|
|
7
|
-
TerrainLayerDescription,
|
|
8
|
-
TileLayerDescription,
|
|
9
|
-
MvtLayerDescription,
|
|
10
3
|
VectorLayerDescription,
|
|
11
4
|
RasterLayerDescription,
|
|
12
5
|
TerrainSourceLayerDescription,
|
|
@@ -56,16 +49,6 @@ export type BuiltInEffectDescription =
|
|
|
56
49
|
| SkyEnvMapPassConfig
|
|
57
50
|
| TransparentPassConfig;
|
|
58
51
|
|
|
59
|
-
// export type MVTLayer = {
|
|
60
|
-
// type: "mvt";
|
|
61
|
-
// zoom: number;
|
|
62
|
-
// layers?: string[];
|
|
63
|
-
// height?: number;
|
|
64
|
-
// extent?: Extent;
|
|
65
|
-
// url: string;
|
|
66
|
-
// color?: number;
|
|
67
|
-
// };
|
|
68
|
-
|
|
69
52
|
type Layer<LD> = NormalizeWASMClass<LD>;
|
|
70
53
|
|
|
71
54
|
/**
|
|
@@ -87,31 +70,11 @@ type ConvertColorFields<T> = {
|
|
|
87
70
|
|
|
88
71
|
/**
|
|
89
72
|
* Helper type to enable Navara Color objects in all color-related fields.
|
|
90
|
-
* This applies to model, point, billboard, text, polyline, polygon,
|
|
73
|
+
* This applies to model, point, billboard, text, polyline, polygon, raster, etc.
|
|
91
74
|
* Both number and Navara Color objects are accepted for backward compatibility.
|
|
92
75
|
*/
|
|
93
76
|
type WithColorSupport<T> = ConvertColorFields<T>;
|
|
94
77
|
|
|
95
|
-
export type TilesLayer = WithColorSupport<
|
|
96
|
-
Layer<TileLayerDescription & { type: "tiles" }>
|
|
97
|
-
>;
|
|
98
|
-
export type TerrainLayer = Layer<TerrainLayerDescription & { type: "terrain" }>;
|
|
99
|
-
export type GeoJsonLayer = WithColorSupport<
|
|
100
|
-
Layer<GeoJsonLayerDescription & { type: "geojson" }>
|
|
101
|
-
>;
|
|
102
|
-
export type B3dmLayer = WithColorSupport<
|
|
103
|
-
Layer<B3dmLayerDescription & { type: "b3dm" }>
|
|
104
|
-
>;
|
|
105
|
-
export type PntsLayer = WithColorSupport<
|
|
106
|
-
Layer<PntsLayerDescription & { type: "pnts" }>
|
|
107
|
-
>;
|
|
108
|
-
export type Cesium3dTilesLayer = WithColorSupport<
|
|
109
|
-
Layer<Cesium3dTilesLayerDescription & { type: "cesium3dtiles" }>
|
|
110
|
-
>;
|
|
111
|
-
export type MvtLayer = WithColorSupport<
|
|
112
|
-
Layer<MvtLayerDescription & { type: "mvt" }>
|
|
113
|
-
>;
|
|
114
|
-
|
|
115
78
|
/**
|
|
116
79
|
* A reference to a {@link Source}: either the source handle returned by
|
|
117
80
|
* `addSource`, or its `id` string.
|
|
@@ -145,9 +108,14 @@ type TerrainSourceLayerBase = WithColorSupport<
|
|
|
145
108
|
>;
|
|
146
109
|
|
|
147
110
|
/**
|
|
148
|
-
* A `terrain` layer renders a `raster-dem` or `quantized-mesh` source as the
|
|
111
|
+
* A `terrain` layer renders a `raster-dem` or `quantized-mesh` source as the
|
|
112
|
+
* globe surface. Alternatively, omit `source` and pass `ellipsoid` to render a
|
|
113
|
+
* flat ellipsoid surface (e.g. to drape clamp-to-ground data on).
|
|
149
114
|
*/
|
|
150
|
-
export type TerrainSourceLayer = SourceLayerBase<TerrainSourceLayerBase
|
|
115
|
+
export type TerrainSourceLayer = SourceLayerBase<TerrainSourceLayerBase> & {
|
|
116
|
+
/** Render a source-less flat ellipsoid surface. Mutually exclusive with `source`. */
|
|
117
|
+
ellipsoid?: TerrainSourceLayerBase["terrain"];
|
|
118
|
+
};
|
|
151
119
|
|
|
152
120
|
type Tiles3dLayerBase = WithColorSupport<
|
|
153
121
|
Layer<Tiles3dLayerDescription & { type: "3d-tiles" }>
|
|
@@ -157,17 +125,7 @@ type Tiles3dLayerBase = WithColorSupport<
|
|
|
157
125
|
export type Tiles3dLayer = SourceLayerBase<Tiles3dLayerBase>;
|
|
158
126
|
|
|
159
127
|
export type LayerDescription =
|
|
160
|
-
|
|
|
161
|
-
| TerrainLayer
|
|
162
|
-
| GeoJsonLayer
|
|
163
|
-
| B3dmLayer
|
|
164
|
-
| PntsLayer
|
|
165
|
-
| Cesium3dTilesLayer
|
|
166
|
-
| MvtLayer
|
|
167
|
-
| VectorLayer
|
|
168
|
-
| RasterLayer
|
|
169
|
-
| TerrainSourceLayer
|
|
170
|
-
| Tiles3dLayer;
|
|
128
|
+
VectorLayer | RasterLayer | TerrainSourceLayer | Tiles3dLayer;
|
|
171
129
|
|
|
172
130
|
/**
|
|
173
131
|
* Fields common to every source, independent of its `type`.
|