@expofp/renderer 3.34.2 → 3.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Renderer.d.ts +14 -0
- package/dist/Renderer.js +16 -4
- package/dist/core/SquareDataTexture.d.ts +2 -2
- package/dist/core/SquareDataTexture.js +2 -2
- package/dist/core/batch/BatchedMesh.d.ts +3 -3
- package/dist/core/batch/BatchedMesh.js +3 -3
- package/dist/core/instanced/InstancedMesh.d.ts +7 -7
- package/dist/core/instanced/InstancedMesh.js +4 -4
- package/dist/core/instanced/instanced-material.js +1 -1
- package/dist/core/lines/BatchedLines.d.ts +4 -4
- package/dist/core/lines/BatchedLines.js +4 -4
- package/dist/core/shader-source.d.ts +20 -0
- package/dist/core/shader-source.js +26 -0
- package/dist/core/text/BatchedText.d.ts +14 -14
- package/dist/core/text/BatchedText.js +9 -9
- package/dist/core/text/MemberTexture.d.ts +2 -2
- package/dist/core/text/Text.d.ts +5 -5
- package/dist/core/text/Text.js +4 -4
- package/dist/core/text/glyph-packing.d.ts +2 -2
- package/dist/debug/components/Gui.d.ts +2 -0
- package/dist/debug/components/Gui.js +11 -1
- package/dist/events/layout-targets.d.ts +7 -3
- package/dist/events/layout-targets.js +13 -1
- package/dist/external/ExternalCanvas.js +5 -6
- package/dist/external/external-camera.js +26 -11
- package/dist/index.d.ts +1 -0
- package/dist/objects/Background.js +1 -1
- package/dist/objects/containers/SlotContainer.d.ts +27 -2
- package/dist/objects/containers/SlotContainer.js +36 -1
- package/dist/objects/containers/hooks.js +1 -1
- package/dist/objects/containers/mesh-writes.d.ts +1 -1
- package/dist/objects/containers/props.d.ts +10 -0
- package/dist/objects/images/Images.js +10 -3
- package/dist/objects/images/image-container.d.ts +5 -5
- package/dist/objects/images/image-container.js +2 -2
- package/dist/objects/layer/Layer.js +10 -10
- package/dist/objects/layer/mount-scheduler.d.ts +83 -54
- package/dist/objects/layer/mount-scheduler.js +204 -60
- package/dist/objects/lighting/Lighting.d.ts +92 -0
- package/dist/objects/lighting/Lighting.js +385 -0
- package/dist/objects/lighting/OcclusionPasses.d.ts +23 -0
- package/dist/objects/lighting/OcclusionPasses.js +271 -0
- package/dist/objects/lighting/index.d.ts +11 -0
- package/dist/objects/lighting/index.js +10 -0
- package/dist/objects/lighting/lighting-gui.d.ts +24 -0
- package/dist/objects/lighting/lighting-gui.js +114 -0
- package/dist/objects/lighting/occluder.d.ts +34 -0
- package/dist/objects/lighting/occluder.js +62 -0
- package/dist/objects/lighting/occlusion-targets.d.ts +143 -0
- package/dist/objects/lighting/occlusion-targets.js +276 -0
- package/dist/objects/lines/Lines.js +3 -1
- package/dist/objects/lines/line-container.d.ts +4 -4
- package/dist/objects/materials/ColorMaterial.d.ts +58 -51
- package/dist/objects/materials/ColorMaterial.js +74 -52
- package/dist/objects/materials/LineMaterial.d.ts +1 -1
- package/dist/objects/materials/LineMaterial.js +1 -1
- package/dist/objects/materials/ModelMaterial.d.ts +30 -18
- package/dist/objects/materials/ModelMaterial.js +68 -41
- package/dist/objects/materials/TextureMaterial.js +1 -1
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.d.ts +77 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.js +296 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.d.ts +29 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.js +115 -0
- package/dist/objects/materials/effects/ao/blur-pass.d.ts +61 -0
- package/dist/objects/materials/effects/ao/blur-pass.js +101 -0
- package/dist/objects/materials/effects/ao/index.d.ts +129 -0
- package/dist/objects/materials/effects/ao/index.js +72 -0
- package/dist/objects/materials/{dim.d.ts → effects/color-dim.d.ts} +5 -5
- package/dist/objects/materials/{dim.js → effects/color-dim.js} +7 -25
- package/dist/objects/materials/effects/contact-shadow.d.ts +100 -0
- package/dist/objects/materials/effects/contact-shadow.js +167 -0
- package/dist/objects/materials/effects/effects-context.d.ts +52 -0
- package/dist/objects/materials/effects/effects-context.js +24 -0
- package/dist/objects/models/Models.js +20 -4
- package/dist/objects/models/model-container.d.ts +4 -20
- package/dist/objects/models/model-container.js +2 -22
- package/dist/objects/shapes/Shapes.js +20 -2
- package/dist/objects/shapes/shape-container.d.ts +3 -20
- package/dist/objects/shapes/shape-container.js +0 -21
- package/dist/objects/text/Texts.js +3 -1
- package/dist/objects/text/text-container.d.ts +6 -6
- package/dist/objects/text/text-container.js +4 -4
- package/dist/objects/text/text-layout.d.ts +2 -2
- package/dist/space/Scene.d.ts +3 -0
- package/dist/space/Scene.js +17 -5
- package/package.json +2 -2
- package/dist/objects/Lighting.d.ts +0 -20
- package/dist/objects/Lighting.js +0 -85
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { Vector2, type WebGLRenderer, WebGLRenderTarget } from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* The three render targets the occlusion passes draw into, over two color textures.
|
|
4
|
+
*
|
|
5
|
+
* | Target | Color texture | Depth attachment | Written by | Read by |
|
|
6
|
+
* | ------- | ---------------------- | ---------------------- | ---------- | --------------- |
|
|
7
|
+
* | `depth` | its own, never written | `DepthTexture`, 24-bit | prepass | nothing |
|
|
8
|
+
* | `ao` | its own | none | AO pass | the blur pass |
|
|
9
|
+
* | `blur` | the `depth` target's | none | blur pass | the lit shaders |
|
|
10
|
+
*
|
|
11
|
+
* The sharing brings rules, and they live here because this class is the only place that can keep
|
|
12
|
+
* them.
|
|
13
|
+
*
|
|
14
|
+
* A target must not carry a texture that the pass drawing into it samples. WebGL2 calls that a
|
|
15
|
+
* rendering feedback loop, leaves the result undefined, and browsers refuse the draw. Both the AO
|
|
16
|
+
* pass and the blur pass sample the prepass depth, so neither one can bind a target that carries it.
|
|
17
|
+
* That is why two of these three targets carry no depth attachment.
|
|
18
|
+
*
|
|
19
|
+
* Three targets over two color textures. The prepass writes depth with `colorWrite` off, so nothing
|
|
20
|
+
* writes its color attachment and nothing reads it. The blur pass writes what the lit shaders read,
|
|
21
|
+
* so it takes that attachment. The handover costs one framebuffer and no GL texture at all. three's
|
|
22
|
+
* `setupRenderTarget` creates a texture only for a target that has none. `RenderTarget` has a
|
|
23
|
+
* `texture` setter, so this is supported API rather than a reach into internals.
|
|
24
|
+
*
|
|
25
|
+
* Two textures and not one, because the blur pass reads the AO pass's output while it writes its own.
|
|
26
|
+
* One texture for both is the feedback loop above.
|
|
27
|
+
*
|
|
28
|
+
* The set costs `1.5` bytes for each canvas pixel: `1.0` for 24-bit depth and `0.25` for each `R8`
|
|
29
|
+
* color texture, all at a quarter of the canvas area. A `2880 × 1800` canvas therefore holds about
|
|
30
|
+
* `7.8 MB` of them.
|
|
31
|
+
*
|
|
32
|
+
* Never call `setSize`. It calls `dispose()`, and three's deallocation deletes the GL texture of
|
|
33
|
+
* every entry in `renderTarget.textures`, a shared one included. A size change rebuilds all three
|
|
34
|
+
* instead.
|
|
35
|
+
*
|
|
36
|
+
* Dispose all three together, from here. The first disposal of a shared texture deletes it and
|
|
37
|
+
* removes its properties, and the second disposal then finds nothing to delete. No one of the three
|
|
38
|
+
* can outlive the others.
|
|
39
|
+
*
|
|
40
|
+
* The completeness check stays, because a required format is no promise that a particular driver
|
|
41
|
+
* reports the combination complete.
|
|
42
|
+
*/
|
|
43
|
+
export declare class OcclusionTargets {
|
|
44
|
+
/** The prepass's target, or `null` until this builds a complete set. */
|
|
45
|
+
private depthTarget;
|
|
46
|
+
/** The AO pass's target, over a color texture of its own, or `null` beside it. */
|
|
47
|
+
private aoTarget;
|
|
48
|
+
/** The blur pass's target, over the prepass's color texture, or `null` beside them. */
|
|
49
|
+
private blurTarget;
|
|
50
|
+
/** The set's pixel size, which is the canvas size at {@link CANVAS_FRACTION}. */
|
|
51
|
+
private readonly targetSize;
|
|
52
|
+
/** Whether this context cannot give a complete set. Once this turns true it stays true. */
|
|
53
|
+
private unavailable;
|
|
54
|
+
/** The prepass's target, or `null` until {@link OcclusionTargets.sync} returns `true`. */
|
|
55
|
+
get depth(): WebGLRenderTarget | null;
|
|
56
|
+
/** The AO pass's target, or `null` until {@link OcclusionTargets.sync} returns `true`. */
|
|
57
|
+
get ao(): WebGLRenderTarget | null;
|
|
58
|
+
/** The blur pass's target, or `null` until {@link OcclusionTargets.sync} returns `true`. */
|
|
59
|
+
get blur(): WebGLRenderTarget | null;
|
|
60
|
+
/** The set's pixel size. Read-only in spirit: the set owns it and rewrites it on a rebuild. */
|
|
61
|
+
get size(): Vector2;
|
|
62
|
+
/**
|
|
63
|
+
* Whether a set is allocated right now.
|
|
64
|
+
*
|
|
65
|
+
* A caller frees the set once when the debug GUI switches occlusion off, rather than on every frame
|
|
66
|
+
* after. This getter tells the caller which frame that is. A flag kept beside the set drifts the
|
|
67
|
+
* first time a sync fails or a rebuild releases the set on its own.
|
|
68
|
+
*/
|
|
69
|
+
get held(): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Frees all three targets and the depth texture, and forgets their size.
|
|
72
|
+
*
|
|
73
|
+
* This takes no renderer. A `RenderTarget` disposal is an event three's own renderer listens for,
|
|
74
|
+
* so an unmount effect with nothing else in hand can call it.
|
|
75
|
+
*/
|
|
76
|
+
dispose(): void;
|
|
77
|
+
/**
|
|
78
|
+
* Brings the set to the canvas's current size, and rebuilds it when that size moves.
|
|
79
|
+
* @param gl - The renderer, for the canvas size and for the completeness check.
|
|
80
|
+
* @returns Whether a complete set now exists to draw into.
|
|
81
|
+
*/
|
|
82
|
+
sync(gl: WebGLRenderer): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Allocates the set at a size, at one of the two depth widths.
|
|
85
|
+
* @param width - The set's width in pixels.
|
|
86
|
+
* @param height - Its height.
|
|
87
|
+
* @param depthType - The depth texture's type, which decides its bit width.
|
|
88
|
+
*/
|
|
89
|
+
private allocate;
|
|
90
|
+
/**
|
|
91
|
+
* Rebuilds the set, and falls back to 16-bit depth once before it gives up for good.
|
|
92
|
+
* @param gl - The renderer, for the completeness check.
|
|
93
|
+
* @param width - The set's width in pixels.
|
|
94
|
+
* @param height - Its height.
|
|
95
|
+
* @returns Whether a complete set now exists.
|
|
96
|
+
*/
|
|
97
|
+
private build;
|
|
98
|
+
/**
|
|
99
|
+
* Frees whatever is allocated, together and in one place, and forgets its size.
|
|
100
|
+
*/
|
|
101
|
+
private release;
|
|
102
|
+
/**
|
|
103
|
+
* Asks the driver whether the two framebuffer combinations this set uses came back complete, once
|
|
104
|
+
* per allocation.
|
|
105
|
+
*
|
|
106
|
+
* Binding each target through three is what builds its framebuffer, so the check runs against the
|
|
107
|
+
* real thing rather than against an intention. This puts the previously bound target back, which
|
|
108
|
+
* keeps a shared context unaware that the check ran. The MapLibre embed is that shared context.
|
|
109
|
+
*
|
|
110
|
+
* **Two checks cover three targets, because two of the targets are one combination.** The depth
|
|
111
|
+
* target is a color attachment beside a sampleable depth texture, at whichever of the two widths
|
|
112
|
+
* `build` is trying, and it is the only one of the three carrying depth at all. That is its own
|
|
113
|
+
* risk and it takes its own check. The blur target is a single `R8` color attachment over a
|
|
114
|
+
* texture the depth target already owns. The AO target is that same single `R8` attachment over a
|
|
115
|
+
* texture of its own, so the blur target covers the AO target's combination and adds the sharing,
|
|
116
|
+
* which is the part of this design a driver is most likely to refuse. A third bind would ask a
|
|
117
|
+
* question the second one already answers.
|
|
118
|
+
*
|
|
119
|
+
* **three's own renderer never calls `checkFramebufferStatus`**, so this is the only thing that
|
|
120
|
+
* detects a driver refusing either combination. What it buys is the 16-bit retry and, after that,
|
|
121
|
+
* occlusion switching itself off, rather than a plan drawing out of an incomplete framebuffer.
|
|
122
|
+
*
|
|
123
|
+
* A context that throws while three binds it answers the same question the status code answers, so
|
|
124
|
+
* this catches the throw. Occlusion switching itself off is the documented outcome. Taking the
|
|
125
|
+
* frame loop down with it is the worse one.
|
|
126
|
+
* @param gl - The renderer whose context answers.
|
|
127
|
+
* @returns Whether both combinations are complete.
|
|
128
|
+
*/
|
|
129
|
+
private verify;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The share of the canvas size each axis of the set takes.
|
|
133
|
+
*
|
|
134
|
+
* Half, so each pass bills a quarter of the texels a full-size pass bills. Occlusion is a
|
|
135
|
+
* low-frequency signal. The lit read finds its own fragment by normalized coordinates, so the texel
|
|
136
|
+
* count behind that read changes nothing anyone can see.
|
|
137
|
+
*
|
|
138
|
+
* The canvas size and not the drawing buffer's, which is what `gl.getSize()` reports. A device pixel
|
|
139
|
+
* ratio of `2` squares into four times the texels, and both passes bill per texel. A set sized from
|
|
140
|
+
* the buffer therefore costs four times as much on a retina screen, for detail nothing asks of it.
|
|
141
|
+
*/
|
|
142
|
+
export declare const CANVAS_FRACTION = 0.5;
|
|
143
|
+
//# sourceMappingURL=occlusion-targets.d.ts.map
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { DepthTexture, NearestFilter, RedFormat, UnsignedByteType, UnsignedIntType, UnsignedShortType, Vector2, WebGLRenderTarget, } from 'three';
|
|
2
|
+
import { createLogger } from '../../utils/logging';
|
|
3
|
+
const logger = createLogger('occlusion');
|
|
4
|
+
/**
|
|
5
|
+
* The three render targets the occlusion passes draw into, over two color textures.
|
|
6
|
+
*
|
|
7
|
+
* | Target | Color texture | Depth attachment | Written by | Read by |
|
|
8
|
+
* | ------- | ---------------------- | ---------------------- | ---------- | --------------- |
|
|
9
|
+
* | `depth` | its own, never written | `DepthTexture`, 24-bit | prepass | nothing |
|
|
10
|
+
* | `ao` | its own | none | AO pass | the blur pass |
|
|
11
|
+
* | `blur` | the `depth` target's | none | blur pass | the lit shaders |
|
|
12
|
+
*
|
|
13
|
+
* The sharing brings rules, and they live here because this class is the only place that can keep
|
|
14
|
+
* them.
|
|
15
|
+
*
|
|
16
|
+
* A target must not carry a texture that the pass drawing into it samples. WebGL2 calls that a
|
|
17
|
+
* rendering feedback loop, leaves the result undefined, and browsers refuse the draw. Both the AO
|
|
18
|
+
* pass and the blur pass sample the prepass depth, so neither one can bind a target that carries it.
|
|
19
|
+
* That is why two of these three targets carry no depth attachment.
|
|
20
|
+
*
|
|
21
|
+
* Three targets over two color textures. The prepass writes depth with `colorWrite` off, so nothing
|
|
22
|
+
* writes its color attachment and nothing reads it. The blur pass writes what the lit shaders read,
|
|
23
|
+
* so it takes that attachment. The handover costs one framebuffer and no GL texture at all. three's
|
|
24
|
+
* `setupRenderTarget` creates a texture only for a target that has none. `RenderTarget` has a
|
|
25
|
+
* `texture` setter, so this is supported API rather than a reach into internals.
|
|
26
|
+
*
|
|
27
|
+
* Two textures and not one, because the blur pass reads the AO pass's output while it writes its own.
|
|
28
|
+
* One texture for both is the feedback loop above.
|
|
29
|
+
*
|
|
30
|
+
* The set costs `1.5` bytes for each canvas pixel: `1.0` for 24-bit depth and `0.25` for each `R8`
|
|
31
|
+
* color texture, all at a quarter of the canvas area. A `2880 × 1800` canvas therefore holds about
|
|
32
|
+
* `7.8 MB` of them.
|
|
33
|
+
*
|
|
34
|
+
* Never call `setSize`. It calls `dispose()`, and three's deallocation deletes the GL texture of
|
|
35
|
+
* every entry in `renderTarget.textures`, a shared one included. A size change rebuilds all three
|
|
36
|
+
* instead.
|
|
37
|
+
*
|
|
38
|
+
* Dispose all three together, from here. The first disposal of a shared texture deletes it and
|
|
39
|
+
* removes its properties, and the second disposal then finds nothing to delete. No one of the three
|
|
40
|
+
* can outlive the others.
|
|
41
|
+
*
|
|
42
|
+
* The completeness check stays, because a required format is no promise that a particular driver
|
|
43
|
+
* reports the combination complete.
|
|
44
|
+
*/
|
|
45
|
+
export class OcclusionTargets {
|
|
46
|
+
/** The prepass's target, or `null` until this builds a complete set. */
|
|
47
|
+
depthTarget = null;
|
|
48
|
+
/** The AO pass's target, over a color texture of its own, or `null` beside it. */
|
|
49
|
+
aoTarget = null;
|
|
50
|
+
/** The blur pass's target, over the prepass's color texture, or `null` beside them. */
|
|
51
|
+
blurTarget = null;
|
|
52
|
+
/** The set's pixel size, which is the canvas size at {@link CANVAS_FRACTION}. */
|
|
53
|
+
targetSize = new Vector2();
|
|
54
|
+
/** Whether this context cannot give a complete set. Once this turns true it stays true. */
|
|
55
|
+
unavailable = false;
|
|
56
|
+
/** The prepass's target, or `null` until {@link OcclusionTargets.sync} returns `true`. */
|
|
57
|
+
get depth() {
|
|
58
|
+
return this.depthTarget;
|
|
59
|
+
}
|
|
60
|
+
/** The AO pass's target, or `null` until {@link OcclusionTargets.sync} returns `true`. */
|
|
61
|
+
get ao() {
|
|
62
|
+
return this.aoTarget;
|
|
63
|
+
}
|
|
64
|
+
/** The blur pass's target, or `null` until {@link OcclusionTargets.sync} returns `true`. */
|
|
65
|
+
get blur() {
|
|
66
|
+
return this.blurTarget;
|
|
67
|
+
}
|
|
68
|
+
/** The set's pixel size. Read-only in spirit: the set owns it and rewrites it on a rebuild. */
|
|
69
|
+
get size() {
|
|
70
|
+
return this.targetSize;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Whether a set is allocated right now.
|
|
74
|
+
*
|
|
75
|
+
* A caller frees the set once when the debug GUI switches occlusion off, rather than on every frame
|
|
76
|
+
* after. This getter tells the caller which frame that is. A flag kept beside the set drifts the
|
|
77
|
+
* first time a sync fails or a rebuild releases the set on its own.
|
|
78
|
+
*/
|
|
79
|
+
get held() {
|
|
80
|
+
return this.depthTarget !== null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Frees all three targets and the depth texture, and forgets their size.
|
|
84
|
+
*
|
|
85
|
+
* This takes no renderer. A `RenderTarget` disposal is an event three's own renderer listens for,
|
|
86
|
+
* so an unmount effect with nothing else in hand can call it.
|
|
87
|
+
*/
|
|
88
|
+
dispose() {
|
|
89
|
+
this.release();
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Brings the set to the canvas's current size, and rebuilds it when that size moves.
|
|
93
|
+
* @param gl - The renderer, for the canvas size and for the completeness check.
|
|
94
|
+
* @returns Whether a complete set now exists to draw into.
|
|
95
|
+
*/
|
|
96
|
+
sync(gl) {
|
|
97
|
+
if (this.unavailable)
|
|
98
|
+
return false;
|
|
99
|
+
const canvas = gl.getSize(_canvas);
|
|
100
|
+
const width = Math.floor(canvas.x * CANVAS_FRACTION);
|
|
101
|
+
const height = Math.floor(canvas.y * CANVAS_FRACTION);
|
|
102
|
+
// A canvas with no area yet, during a layout or before one. The next resize can undo that, so
|
|
103
|
+
// this is no reason to give up for good.
|
|
104
|
+
if (width < 1 || height < 1)
|
|
105
|
+
return false;
|
|
106
|
+
if (this.depthTarget && this.targetSize.x === width && this.targetSize.y === height) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
return this.build(gl, width, height);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Allocates the set at a size, at one of the two depth widths.
|
|
113
|
+
* @param width - The set's width in pixels.
|
|
114
|
+
* @param height - Its height.
|
|
115
|
+
* @param depthType - The depth texture's type, which decides its bit width.
|
|
116
|
+
*/
|
|
117
|
+
allocate(width, height, depthType) {
|
|
118
|
+
this.depthTarget = new WebGLRenderTarget(width, height, {
|
|
119
|
+
...SINGLE_CHANNEL_TEXTURE,
|
|
120
|
+
depthBuffer: true,
|
|
121
|
+
depthTexture: new DepthTexture(width, height, depthType),
|
|
122
|
+
});
|
|
123
|
+
this.aoTarget = new WebGLRenderTarget(width, height, {
|
|
124
|
+
...SINGLE_CHANNEL_TEXTURE,
|
|
125
|
+
depthBuffer: false,
|
|
126
|
+
});
|
|
127
|
+
// No format of its own. The next line drops the texture this target is built with, so the
|
|
128
|
+
// missing depth attachment is the only option that outlives the constructor.
|
|
129
|
+
this.blurTarget = new WebGLRenderTarget(width, height, { depthBuffer: false });
|
|
130
|
+
// The sharing, in one line. `RenderTarget` has a `texture` setter, so this is supported API
|
|
131
|
+
// rather than a reach into internals. This drops the target's own fresh texture, which never
|
|
132
|
+
// uploaded. Nothing writes the prepass's color attachment and nothing reads it, so the blur pass
|
|
133
|
+
// can take it.
|
|
134
|
+
this.blurTarget.texture = this.depthTarget.texture;
|
|
135
|
+
this.targetSize.set(width, height);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Rebuilds the set, and falls back to 16-bit depth once before it gives up for good.
|
|
139
|
+
* @param gl - The renderer, for the completeness check.
|
|
140
|
+
* @param width - The set's width in pixels.
|
|
141
|
+
* @param height - Its height.
|
|
142
|
+
* @returns Whether a complete set now exists.
|
|
143
|
+
*/
|
|
144
|
+
build(gl, width, height) {
|
|
145
|
+
this.release();
|
|
146
|
+
for (const depthType of DEPTH_TYPES) {
|
|
147
|
+
this.allocate(width, height, depthType);
|
|
148
|
+
if (this.verify(gl))
|
|
149
|
+
return true;
|
|
150
|
+
this.release();
|
|
151
|
+
}
|
|
152
|
+
// Off for good, and silent as far as a caller can tell. The SDK runs in old Android WebViews,
|
|
153
|
+
// and nobody can do anything about a driver that will not report these targets complete. The
|
|
154
|
+
// debug log is for whoever has to find out why a plan shipped without occlusion.
|
|
155
|
+
logger.debug('ambient occlusion off: this context gives no complete render targets');
|
|
156
|
+
this.unavailable = true;
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Frees whatever is allocated, together and in one place, and forgets its size.
|
|
161
|
+
*/
|
|
162
|
+
release() {
|
|
163
|
+
// The order does not matter. The first disposal of the shared texture deletes it and removes its
|
|
164
|
+
// properties, and the second one finds nothing left to delete. All three still have to happen.
|
|
165
|
+
// The AO target's own texture belongs to nobody else, so either order deletes it once.
|
|
166
|
+
this.depthTarget?.dispose();
|
|
167
|
+
this.aoTarget?.dispose();
|
|
168
|
+
this.blurTarget?.dispose();
|
|
169
|
+
this.depthTarget?.depthTexture?.dispose();
|
|
170
|
+
this.depthTarget = null;
|
|
171
|
+
this.aoTarget = null;
|
|
172
|
+
this.blurTarget = null;
|
|
173
|
+
this.targetSize.set(0, 0);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Asks the driver whether the two framebuffer combinations this set uses came back complete, once
|
|
177
|
+
* per allocation.
|
|
178
|
+
*
|
|
179
|
+
* Binding each target through three is what builds its framebuffer, so the check runs against the
|
|
180
|
+
* real thing rather than against an intention. This puts the previously bound target back, which
|
|
181
|
+
* keeps a shared context unaware that the check ran. The MapLibre embed is that shared context.
|
|
182
|
+
*
|
|
183
|
+
* **Two checks cover three targets, because two of the targets are one combination.** The depth
|
|
184
|
+
* target is a color attachment beside a sampleable depth texture, at whichever of the two widths
|
|
185
|
+
* `build` is trying, and it is the only one of the three carrying depth at all. That is its own
|
|
186
|
+
* risk and it takes its own check. The blur target is a single `R8` color attachment over a
|
|
187
|
+
* texture the depth target already owns. The AO target is that same single `R8` attachment over a
|
|
188
|
+
* texture of its own, so the blur target covers the AO target's combination and adds the sharing,
|
|
189
|
+
* which is the part of this design a driver is most likely to refuse. A third bind would ask a
|
|
190
|
+
* question the second one already answers.
|
|
191
|
+
*
|
|
192
|
+
* **three's own renderer never calls `checkFramebufferStatus`**, so this is the only thing that
|
|
193
|
+
* detects a driver refusing either combination. What it buys is the 16-bit retry and, after that,
|
|
194
|
+
* occlusion switching itself off, rather than a plan drawing out of an incomplete framebuffer.
|
|
195
|
+
*
|
|
196
|
+
* A context that throws while three binds it answers the same question the status code answers, so
|
|
197
|
+
* this catches the throw. Occlusion switching itself off is the documented outcome. Taking the
|
|
198
|
+
* frame loop down with it is the worse one.
|
|
199
|
+
* @param gl - The renderer whose context answers.
|
|
200
|
+
* @returns Whether both combinations are complete.
|
|
201
|
+
*/
|
|
202
|
+
verify(gl) {
|
|
203
|
+
const context = webgl2(gl);
|
|
204
|
+
if (!context)
|
|
205
|
+
return false;
|
|
206
|
+
const previous = gl.getRenderTarget();
|
|
207
|
+
try {
|
|
208
|
+
let complete = true;
|
|
209
|
+
for (const target of [this.depthTarget, this.blurTarget]) {
|
|
210
|
+
gl.setRenderTarget(target);
|
|
211
|
+
if (context.checkFramebufferStatus(context.FRAMEBUFFER) !== context.FRAMEBUFFER_COMPLETE) {
|
|
212
|
+
complete = false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return complete;
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
gl.setRenderTarget(previous);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* The renderer's context, when it is one these targets can be built on.
|
|
227
|
+
*
|
|
228
|
+
* A runtime feature detection, and not a type narrowing a reader can drop. The SDK still runs in
|
|
229
|
+
* WebViews whose context can be neither WebGL2 nor able to carry a readable depth texture. The
|
|
230
|
+
* constructor itself can be missing, so this checks for it before it uses it.
|
|
231
|
+
* @param gl - The renderer to read.
|
|
232
|
+
* @returns The context, or `undefined` when the targets cannot be built on it.
|
|
233
|
+
*/
|
|
234
|
+
function webgl2(gl) {
|
|
235
|
+
const context = gl.getContext();
|
|
236
|
+
if (typeof WebGL2RenderingContext !== 'function')
|
|
237
|
+
return undefined;
|
|
238
|
+
return context instanceof WebGL2RenderingContext ? context : undefined;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* The share of the canvas size each axis of the set takes.
|
|
242
|
+
*
|
|
243
|
+
* Half, so each pass bills a quarter of the texels a full-size pass bills. Occlusion is a
|
|
244
|
+
* low-frequency signal. The lit read finds its own fragment by normalized coordinates, so the texel
|
|
245
|
+
* count behind that read changes nothing anyone can see.
|
|
246
|
+
*
|
|
247
|
+
* The canvas size and not the drawing buffer's, which is what `gl.getSize()` reports. A device pixel
|
|
248
|
+
* ratio of `2` squares into four times the texels, and both passes bill per texel. A set sized from
|
|
249
|
+
* the buffer therefore costs four times as much on a retina screen, for detail nothing asks of it.
|
|
250
|
+
*/
|
|
251
|
+
export const CANVAS_FRACTION = 0.5;
|
|
252
|
+
/**
|
|
253
|
+
* The depth widths this tries, in order.
|
|
254
|
+
*
|
|
255
|
+
* 24 bits first, because 16-bit depth at this size over a plan's range bands visibly in the
|
|
256
|
+
* occlusion. 16 bits is the one retry worth making before occlusion latches off for good. It saves
|
|
257
|
+
* memory rather than costing it, which makes it a fallback rather than a default.
|
|
258
|
+
*/
|
|
259
|
+
const DEPTH_TYPES = [UnsignedIntType, UnsignedShortType];
|
|
260
|
+
/**
|
|
261
|
+
* The shared settings for the two color textures the occlusion passes draw into.
|
|
262
|
+
*
|
|
263
|
+
* `R8` holds one byte a texel. It is a color-renderable sized internal format in WebGL 2, so it is
|
|
264
|
+
* required to work rather than merely likely to.
|
|
265
|
+
*
|
|
266
|
+
* Both the blur pass and the lit read weight their samples by depth, so hardware filtering only
|
|
267
|
+
* smears occlusion across the edges those reads respect.
|
|
268
|
+
*/
|
|
269
|
+
const SINGLE_CHANNEL_TEXTURE = {
|
|
270
|
+
format: RedFormat,
|
|
271
|
+
type: UnsignedByteType,
|
|
272
|
+
minFilter: NearestFilter,
|
|
273
|
+
magFilter: NearestFilter,
|
|
274
|
+
};
|
|
275
|
+
/** Scratch for the canvas size, read once per sync. */
|
|
276
|
+
const _canvas = new Vector2();
|
|
@@ -17,6 +17,8 @@ import { LineInstance } from './LineInstance';
|
|
|
17
17
|
*/
|
|
18
18
|
export function Lines(props) {
|
|
19
19
|
const { defs } = props;
|
|
20
|
+
// Only the instances are staged; the container is sized and ordered from the whole membership.
|
|
21
|
+
const mounted = props.mounted ?? defs.length;
|
|
20
22
|
// Sized from the first render's defs; membership changes grow the batch in place, never rebuild
|
|
21
23
|
// it. The container is bookkeeping only, so the copy StrictMode's double-invoked initializer
|
|
22
24
|
// discards is plain garbage.
|
|
@@ -25,5 +27,5 @@ export function Lines(props) {
|
|
|
25
27
|
// claimed slot — it is a frozen snapshot, so that never happens. The `ref` arrow is
|
|
26
28
|
// deliberate: `ref={container.bind}` would make the React Compiler infer `container` itself as a
|
|
27
29
|
// ref and reject its render-time reads.
|
|
28
|
-
return (_jsxs(_Fragment, { children: [_jsx(BatchedLines, { args: container.args, ref: (mesh) => container.bind(mesh), customSort: container.customSort, children: _jsx(LineMaterial, {}) }), defs.map((def) => (_jsx(LineInstance, { def: def, container: container }, defKey(def))))] }));
|
|
30
|
+
return (_jsxs(_Fragment, { children: [_jsx(BatchedLines, { args: container.args, ref: (mesh) => container.bind(mesh), customSort: container.customSort, children: _jsx(LineMaterial, {}) }), defs.slice(0, mounted).map((def) => (_jsx(LineInstance, { def: def, container: container }, defKey(def))))] }));
|
|
29
31
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type
|
|
1
|
+
import { type InstancedMeshImplParams } from '../../core/instanced/InstancedMesh';
|
|
2
|
+
import { type BatchedLinesImpl } from '../../core/lines/BatchedLines';
|
|
3
3
|
import { type LineDef } from '../../types/defs';
|
|
4
4
|
import { SlotContainer } from '../containers';
|
|
5
5
|
/**
|
|
@@ -10,7 +10,7 @@ import { SlotContainer } from '../containers';
|
|
|
10
10
|
* Only the options: the batch builds its own segment template and takes its material from the
|
|
11
11
|
* `<LineMaterial>` child R3F mounts under it.
|
|
12
12
|
*/
|
|
13
|
-
export type LineMeshArgs = [params:
|
|
13
|
+
export type LineMeshArgs = [params: InstancedMeshImplParams];
|
|
14
14
|
/**
|
|
15
15
|
* One line layer's bookkeeping.
|
|
16
16
|
*
|
|
@@ -19,7 +19,7 @@ export type LineMeshArgs = [params: InstancedMeshExtParams];
|
|
|
19
19
|
* endpoints. That is why the resource type is `null` here: nothing about a `LineDef` can force a
|
|
20
20
|
* re-claim, so a def keeps its slot for as long as it is mounted, `points` included.
|
|
21
21
|
*/
|
|
22
|
-
export declare class LineContainer extends SlotContainer<null,
|
|
22
|
+
export declare class LineContainer extends SlotContainer<null, BatchedLinesImpl, LineDef> {
|
|
23
23
|
/** The constructor args R3F builds the batch from; a frozen snapshot, never mutated. */
|
|
24
24
|
readonly args: LineMeshArgs;
|
|
25
25
|
/**
|
|
@@ -4,80 +4,87 @@ export interface ColorMaterialProps {
|
|
|
4
4
|
/** Base color. Defaults to white, which is what a per-instance tint multiplies against. */
|
|
5
5
|
color?: ColorRepresentation;
|
|
6
6
|
/**
|
|
7
|
-
* Whether this batch draws
|
|
8
|
-
* solid and depth testing to occlude
|
|
9
|
-
*
|
|
7
|
+
* Whether this batch draws volumes, which are the meshes carrying real z. A volume needs shading
|
|
8
|
+
* to read as solid, and depth testing to occlude itself and its neighbors. Defaults to `false`,
|
|
9
|
+
* which is the flat content every 2D primitive in a plan draws as.
|
|
10
10
|
*/
|
|
11
11
|
solid?: boolean;
|
|
12
12
|
/**
|
|
13
|
-
* Whether those volumes are translucent, which moves them into three's transparent pass.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Whether those volumes are translucent, which moves them into three's transparent pass.
|
|
14
|
+
*
|
|
15
|
+
* This means something only together with {@link solid}. Flat content never uses it, for the reason
|
|
16
|
+
* the note below gives.
|
|
16
17
|
*/
|
|
17
18
|
translucent?: boolean;
|
|
18
19
|
/**
|
|
19
|
-
* Runs on the shader before the dim patch
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* Runs on the shader before the dim patch runs. This is the slot for a caller's own shader surgery,
|
|
21
|
+
* such as the background quad's clip-space projection and its dim-uniform stand-in.
|
|
22
|
+
*
|
|
23
|
+
* This component captures the handler at the first render and keeps it for the material's lifetime,
|
|
24
|
+
* the way it keeps every other axis. A handler identity that changes risks a recompile per render.
|
|
23
25
|
*/
|
|
24
26
|
onBeforeCompile?: (parameters: WebGLProgramParametersWithUniforms) => void;
|
|
25
27
|
}
|
|
26
28
|
/**
|
|
27
|
-
* The material a shape batch draws with
|
|
28
|
-
* and disposes it.
|
|
29
|
+
* The material a shape batch draws with. It renders as a child of the mesh, so R3F attaches, updates
|
|
30
|
+
* and disposes it. It is also troika's base material for text, which never asks for `solid`. See
|
|
31
|
+
* `Texts`.
|
|
29
32
|
*
|
|
30
|
-
* One component with a branch rather than two files
|
|
31
|
-
*
|
|
32
|
-
* read side by side:
|
|
33
|
+
* One component with a branch rather than two files. The two are the same thing along one axis, which
|
|
34
|
+
* is what a batch of shapes draws with. The three differences read best side by side:
|
|
33
35
|
*
|
|
34
36
|
* | | flat (default) | `solid` |
|
|
35
37
|
* | --- | --- | --- |
|
|
36
|
-
* | base | `MeshBasicMaterial`, unlit | `
|
|
37
|
-
* | `depthWrite` | off, from {@link sharedParams} |
|
|
38
|
-
* | polygon offset | on, from {@link sharedParams} |
|
|
38
|
+
* | base | `MeshBasicMaterial`, unlit | `MeshLambertMaterial`, lit |
|
|
39
|
+
* | `depthWrite` | off, from {@link sharedParams} | on |
|
|
40
|
+
* | polygon offset | on, from {@link sharedParams} | off |
|
|
39
41
|
*
|
|
40
|
-
* Each difference
|
|
41
|
-
* its own faces resolve against each other, and what lets 2D content drawn afterwards be
|
|
42
|
-
* it.
|
|
43
|
-
*
|
|
42
|
+
* Each difference carries its own weight. A volume is the one kind of content that writes depth. That
|
|
43
|
+
* is how its own faces resolve against each other, and it is what lets 2D content drawn afterwards be
|
|
44
|
+
* occluded by it. A volume carries no polygon offset, because solids are what everything else is
|
|
45
|
+
* biased against. An offset on the solids too only moves the problem.
|
|
44
46
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
* The lit branch is `MeshLambertMaterial` alone. It has no specular lobe at all, so this never sets
|
|
48
|
+
* `ior` or `specularIntensity`. three evaluates it per fragment with `BRDF_Lambert`, which is
|
|
49
|
+
* `albedo / π`. Its vertex shader carries `batching_vertex` and `color_vertex`, so
|
|
50
|
+
* `BatchedMesh.setColorAt` keeps working. Its fragment shader carries the output chain, so the dim
|
|
51
|
+
* patch keeps its anchor.
|
|
50
52
|
*
|
|
51
|
-
*
|
|
53
|
+
* The light rig alone holds a roof to its authored color now. `Lighting` derives both intensities from
|
|
54
|
+
* the one `contrast` control every frame. An up-facing surface then lands on the color it was authored
|
|
55
|
+
* with, and no surface the design accounts for clips.
|
|
56
|
+
*
|
|
57
|
+
* Per-instance color arrives through `BatchedMesh.setColorAt` on either branch. three injects the
|
|
52
58
|
* batching-color chunk for whatever material the batch draws with.
|
|
53
59
|
*
|
|
54
|
-
*
|
|
55
|
-
* `transparent: true`, which moves the batch into three's transparent list
|
|
56
|
-
* opaque object in the scene, whatever `renderOrder` says
|
|
57
|
-
*
|
|
58
|
-
* content lives in a later layer
|
|
59
|
-
* surface can only blend with what
|
|
60
|
+
* `translucent` is the one place the array-order contract bends, and it bends on purpose. It sets
|
|
61
|
+
* `transparent: true`, which moves the batch into three's transparent list. three draws that list
|
|
62
|
+
* after every opaque object in the scene, whatever `renderOrder` says. See
|
|
63
|
+
* `WebGLRenderer.renderScene`. That is what lets a translucent volume blend with the opaque content
|
|
64
|
+
* behind it, even when that content lives in a later layer. No position in the ordered stream can do
|
|
65
|
+
* the same, because a translucent surface can only blend with what already sits in the framebuffer.
|
|
60
66
|
*
|
|
61
|
-
*
|
|
62
|
-
* on a glass roof
|
|
63
|
-
* whatever was under the glass rather than onto it, and the volume then
|
|
64
|
-
* translucent solid is right about what is behind it and wrong about what is on it. Flat
|
|
65
|
-
* never
|
|
66
|
-
* order to sort by and order is all it has.
|
|
67
|
+
* The reverse case is what it costs. Take a planar def sitting on a translucent volume, such as a
|
|
68
|
+
* label or a logo on a glass roof. It draws in the opaque pass, so it lands before the volume. The
|
|
69
|
+
* label composites against whatever was under the glass rather than onto it, and the volume then
|
|
70
|
+
* glazes over the label. So a translucent solid is right about what is behind it and wrong about what is on it. Flat
|
|
71
|
+
* content never takes this flag for the same reason, and for one of its own: 2D content is coplanar,
|
|
72
|
+
* so it has no depth order to sort by and order is all it has.
|
|
67
73
|
*
|
|
68
|
-
* `forceSinglePass` is switched off here alone. three
|
|
69
|
-
* twice
|
|
70
|
-
* for free
|
|
71
|
-
*
|
|
74
|
+
* `forceSinglePass` is switched off here alone. three draws a `DoubleSide` transparent material
|
|
75
|
+
* twice, back faces and then front. See `WebGLRenderer.js:1348`. That is a correctly ordered two-pass
|
|
76
|
+
* for free. It is why a closed translucent volume does not blend its own far and near surfaces in
|
|
77
|
+
* whatever order the batch packed them.
|
|
72
78
|
*
|
|
73
|
-
* Depth writes stay
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
79
|
+
* Depth writes stay on even when translucent, which is not three's usual convention. Two rules depend
|
|
80
|
+
* on it. A translucent volume must still be occluded by anything nearer, and a farther volume drawn
|
|
81
|
+
* later must not paint over it. MapLibre makes the same choice for `fill-extrusion`, with
|
|
82
|
+
* `DepthMode.ReadWrite` in both of its passes. The design spec's Decision 2 records the trade and the
|
|
83
|
+
* two rejected alternatives.
|
|
78
84
|
*
|
|
79
|
-
* The branch is stable for a mounted batch
|
|
80
|
-
* a constant `solid
|
|
85
|
+
* The branch is stable for a mounted batch, because `Shapes` renders a separate element per kind and
|
|
86
|
+
* each one carries a constant `solid`. So R3F never swaps one base class for the other under a live
|
|
87
|
+
* mesh.
|
|
81
88
|
* @param props - The material's base color, and whether it draws volumes.
|
|
82
89
|
* @returns The material element.
|
|
83
90
|
*/
|