@driftengine/core 4.4.1 → 4.5.0
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/CHANGELOG.json +72 -0
- package/README.md +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -0
- package/dist/render/backend/api.d.ts +17 -11
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/renderer.d.ts +82 -8
- package/dist/render/backend/webgl2/renderer.js +373 -105
- package/dist/render/backend/webgpu/buffers.d.ts +19 -1
- package/dist/render/backend/webgpu/buffers.js +27 -1
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
- package/dist/render/backend/webgpu/flatPass.js +20 -1
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +205 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
- package/dist/render/backend/webgpu/instanced.d.ts +12 -1
- package/dist/render/backend/webgpu/instanced.js +20 -1
- package/dist/render/backend/webgpu/latePass.d.ts +26 -0
- package/dist/render/backend/webgpu/latePass.js +133 -0
- package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
- package/dist/render/backend/webgpu/motionPass.js +491 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
- package/dist/render/backend/webgpu/pipelineCache.js +11 -1
- package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
- package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
- package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
- package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
- package/dist/render/backend/webgpu/postPass.d.ts +21 -15
- package/dist/render/backend/webgpu/postPass.js +17 -4
- package/dist/render/backend/webgpu/renderer.d.ts +181 -54
- package/dist/render/backend/webgpu/renderer.js +1011 -394
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
- package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
- package/dist/render/backend/webgpu/snapshotMips.js +52 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
- package/dist/render/flameLight.js +15 -2
- package/dist/render/glass.d.ts +45 -0
- package/dist/render/glass.js +39 -0
- package/dist/render/glassCasters.d.ts +40 -0
- package/dist/render/glassCasters.js +84 -0
- package/dist/render/glassShadow.d.ts +78 -0
- package/dist/render/glassShadow.js +125 -0
- package/dist/render/globalMediumPass.d.ts +2 -3
- package/dist/render/globalMediumPass.js +2 -8
- package/dist/render/gpudriven/lit.d.ts +12 -0
- package/dist/render/gpudriven/lit.js +23 -0
- package/dist/render/gpudriven/materialTable.d.ts +15 -5
- package/dist/render/gpudriven/materialTable.js +26 -6
- package/dist/render/gpudriven/pipeline.d.ts +1 -1
- package/dist/render/gpudriven/pipeline.js +4 -3
- package/dist/render/gpudriven/sunGlass.d.ts +35 -0
- package/dist/render/gpudriven/sunGlass.js +89 -0
- package/dist/render/lightBudget.d.ts +17 -5
- package/dist/render/lightBudget.js +17 -5
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointShadowArray.d.ts +56 -3
- package/dist/render/pointShadowArray.js +127 -16
- package/dist/render/pointShadowBudget.js +51 -41
- package/dist/render/pointShadowMap.d.ts +23 -1
- package/dist/render/pointShadowMap.js +27 -1
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/disocclusion.d.ts +36 -3
- package/dist/render/recon/disocclusion.js +56 -4
- package/dist/render/recon/lateRouting.d.ts +14 -0
- package/dist/render/recon/lateRouting.js +18 -0
- package/dist/render/recon/mover.d.ts +57 -0
- package/dist/render/recon/mover.js +86 -0
- package/dist/render/recon/resolve.js +95 -12
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +9 -1
- package/dist/render/sceneTarget.js +59 -13
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +85 -9
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/glassTint.d.ts +6 -0
- package/dist/render/shaders/flat/glassTint.js +25 -0
- package/dist/render/shaders/flat/index.d.ts +13 -0
- package/dist/render/shaders/flat/index.js +4 -0
- package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
- package/dist/render/shaders/flat/lampGlass.js +183 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +115 -15
- package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -0
- package/dist/render/shaders/flat/preamble.js +21 -6
- package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
- package/dist/render/shaders/generated/depth.wgsl.js +227 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
- package/dist/render/shaders/generated/flat.wgsl.js +376 -328
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
- package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +31 -6
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +31 -6
- package/dist/render/shaders/globalMedium.js +6 -6
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
- package/dist/render/shaders/lightVolume.js +6 -6
- package/dist/render/shaders/octahedralResolve.d.ts +8 -0
- package/dist/render/shaders/octahedralResolve.js +48 -17
- package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
- package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +84 -0
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/resolve.wgsl.js +112 -8
- package/dist/render/shaders/temporalResolve.js +133 -5
- package/dist/render/shadowCasters.d.ts +20 -1
- package/dist/render/shadowCasters.js +15 -1
- package/dist/render/shadowMap.d.ts +54 -5
- package/dist/render/shadowMap.js +190 -36
- package/dist/render/sunGlassTint.d.ts +25 -0
- package/dist/render/sunGlassTint.js +79 -0
- package/dist/render/temporalAa.d.ts +145 -0
- package/dist/render/temporalAa.js +190 -0
- package/dist/render/temporalPass.d.ts +16 -14
- package/dist/render/temporalPass.js +59 -14
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/package.json +6 -6
- package/src/index.ts +10 -0
- package/src/render/backend/api.ts +15 -9
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/renderer.ts +422 -109
- package/src/render/backend/webgpu/buffers.ts +50 -2
- package/src/render/backend/webgpu/depthPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +22 -1
- package/src/render/backend/webgpu/glassTintPass.ts +244 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanced.ts +21 -1
- package/src/render/backend/webgpu/latePass.ts +152 -0
- package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
- package/src/render/backend/webgpu/motionPass.ts +535 -0
- package/src/render/backend/webgpu/pipelineCache.ts +12 -1
- package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
- package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
- package/src/render/backend/webgpu/postPass.ts +34 -4
- package/src/render/backend/webgpu/renderer.ts +1178 -425
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
- package/src/render/backend/webgpu/snapshotMips.ts +80 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
- package/src/render/flameLight.ts +15 -2
- package/src/render/glass.ts +74 -0
- package/src/render/glassCasters.ts +164 -0
- package/src/render/glassShadow.ts +153 -0
- package/src/render/globalMediumPass.ts +4 -11
- package/src/render/gpudriven/lit.ts +31 -0
- package/src/render/gpudriven/materialTable.ts +28 -6
- package/src/render/gpudriven/pipeline.ts +4 -3
- package/src/render/gpudriven/sunGlass.ts +117 -0
- package/src/render/lightBudget.ts +19 -5
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowBudget.ts +49 -39
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/disocclusion.ts +54 -3
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/recon/resolve.ts +137 -10
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +12 -0
- package/src/render/sceneTarget.ts +66 -12
- package/src/render/shaders/depth.ts +109 -9
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +17 -0
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +115 -15
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +21 -6
- package/src/render/shaders/generated/depth.wgsl.ts +234 -4
- package/src/render/shaders/generated/flat.wgsl.ts +376 -328
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +31 -6
- package/src/render/shaders/globalMedium.ts +6 -6
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
- package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
- package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
- package/src/render/shaders/lightVolume.ts +6 -6
- package/src/render/shaders/octahedralResolve.ts +50 -17
- package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +112 -8
- package/src/render/shaders/temporalResolve.ts +141 -5
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/temporalAa.ts +227 -0
- package/src/render/temporalPass.ts +88 -12
- package/src/render/translucentQueue.ts +20 -1
|
@@ -16,7 +16,25 @@ export declare const OCTAHEDRAL_EDGE = 1024;
|
|
|
16
16
|
export interface PointShadowTarget {
|
|
17
17
|
beginFace(gl: WebGL2RenderingContext): void;
|
|
18
18
|
resolveFace(gl: WebGL2RenderingContext, layer: number, face: number, far: number, near: number): void;
|
|
19
|
+
/** Whether the array has grown its glass layers: `growForGlass`. */
|
|
20
|
+
readonly hasGlass: boolean;
|
|
21
|
+
/** Whether `face` of `layer` holds glass, which a face that offers none must clear. */
|
|
22
|
+
heldGlass(layer: number, face: number): boolean;
|
|
23
|
+
/** The depth scratch again, cleared, for the face's glass. */
|
|
24
|
+
beginGlassFace(gl: WebGL2RenderingContext): void;
|
|
25
|
+
resolveGlassFace(gl: WebGL2RenderingContext, layer: number, face: number, far: number, near: number): void;
|
|
26
|
+
/** The colour scratch, cleared to white, for what the face's glass lets through. */
|
|
27
|
+
beginTintFace(gl: WebGL2RenderingContext): void;
|
|
28
|
+
resolveTintFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void;
|
|
19
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Every published layer as the array stores it: `layer × stride`, and −1 left as −1.
|
|
32
|
+
*
|
|
33
|
+
* **Applied to the resolved arrays right after each resolve**, so every consumer of an uploaded
|
|
34
|
+
* index reads the physical layer: once glass arrives, light L's depth is at 2L and its glass at
|
|
35
|
+
* 2L + 1 (`PointShadowArray.growForGlass`). In place, because the resolve rewrites these every time.
|
|
36
|
+
*/
|
|
37
|
+
export declare function strideLayers(layers: Int32Array, stride: number): void;
|
|
20
38
|
/**
|
|
21
39
|
* Every point light's shadow as one array texture, and the scratch that fills it.
|
|
22
40
|
*
|
|
@@ -34,12 +52,16 @@ export interface PointShadowTarget {
|
|
|
34
52
|
* never the same object.
|
|
35
53
|
*/
|
|
36
54
|
export declare class PointShadowArray implements PointShadowTarget {
|
|
37
|
-
readonly texture: WebGLTexture;
|
|
38
55
|
/**
|
|
39
|
-
* How many
|
|
40
|
-
* for each rectangle a world said would cast. See the constructor's `extraLayers`.
|
|
56
|
+
* How many lights this array holds: the point pool, the two live transition maps, and two more
|
|
57
|
+
* for each rectangle a world said would cast. See the constructor's `extraLayers`. Counted in
|
|
58
|
+
* lights, so it does not move when glass doubles the storage under it.
|
|
41
59
|
*/
|
|
42
60
|
readonly layers: number;
|
|
61
|
+
/** Replaced once, when glass first casts: see `growForGlass`. */
|
|
62
|
+
private current;
|
|
63
|
+
/** What the lights' glass lets through, made with the glass layers. */
|
|
64
|
+
private tint;
|
|
43
65
|
private readonly framebuffer;
|
|
44
66
|
private readonly scratchTexture;
|
|
45
67
|
private readonly scratchFramebuffer;
|
|
@@ -62,6 +84,35 @@ export declare class PointShadowArray implements PointShadowTarget {
|
|
|
62
84
|
* map.
|
|
63
85
|
*/
|
|
64
86
|
extraLayers?: number);
|
|
87
|
+
/** The array texture the lit pass samples. Replaced once, when glass first casts. */
|
|
88
|
+
get texture(): WebGLTexture;
|
|
89
|
+
get hasGlass(): boolean;
|
|
90
|
+
/** Physical layers a light: 1, or 2 once glass has interleaved the array. */
|
|
91
|
+
get stride(): number;
|
|
92
|
+
/** The glass tint, one layer a light, once glass has cast; the lit pass samples it. */
|
|
93
|
+
get tintTexture(): WebGLTexture | null;
|
|
94
|
+
/**
|
|
95
|
+
* Interleave the array for glass: light L's depth moves to 2L and its glass takes 2L + 1, and
|
|
96
|
+
* the tint is made. Returns whether it grew — once, on the first face whose casters held glass.
|
|
97
|
+
*
|
|
98
|
+
* **Interleaved rather than stacked above the opaque half** because a shader cannot ask an array
|
|
99
|
+
* its layer count under naga, so it cannot find `L + N`; `layer + 1` it can. **Every map already
|
|
100
|
+
* baked is carried across** by a depth blit, so nothing goes dark, and **no map needs baking
|
|
101
|
+
* again**: this runs on the first face that saw glass, so no map baked before it saw any, and a
|
|
102
|
+
* glass layer born at the far plane with a white tint is exactly what its bake would draw. What
|
|
103
|
+
* it costs is the doubled depth (4.19 MB a light at 1024) and the tint (5.6 MB a light with its
|
|
104
|
+
* chain, 1.4 MB at `'half'`), from then on.
|
|
105
|
+
*/
|
|
106
|
+
growForGlass(gl: WebGL2RenderingContext, tintEdge: number): boolean;
|
|
107
|
+
heldGlass(layer: number, face: number): boolean;
|
|
108
|
+
/** The depth scratch, cleared, for the face's glass: `beginFace` exactly. */
|
|
109
|
+
beginGlassFace(gl: WebGL2RenderingContext): void;
|
|
110
|
+
/** The face's glass depth into light `layer`'s glass layer, 2L + 1. */
|
|
111
|
+
resolveGlassFace(gl: WebGL2RenderingContext, layer: number, face: number, far: number, near: number): void;
|
|
112
|
+
beginTintFace(gl: WebGL2RenderingContext): void;
|
|
113
|
+
resolveTintFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void;
|
|
114
|
+
/** The chain frost reads, for every light whose tint a round touched: once a round. */
|
|
115
|
+
finishTints(gl: WebGL2RenderingContext): void;
|
|
65
116
|
/**
|
|
66
117
|
* Bind the scratch as the render target for one face of one bake, and clear it.
|
|
67
118
|
*
|
|
@@ -91,5 +142,7 @@ export declare class PointShadowArray implements PointShadowTarget {
|
|
|
91
142
|
* from an empty vertex array.
|
|
92
143
|
*/
|
|
93
144
|
resolveFace(gl: WebGL2RenderingContext, layer: number, face: number, far: number, near: number): void;
|
|
145
|
+
/** The scratch into physical layer `physical`'s region for `face`. */
|
|
146
|
+
private resolveInto;
|
|
94
147
|
dispose(gl: WebGL2RenderingContext): void;
|
|
95
148
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mat3 } from 'gl-matrix';
|
|
2
2
|
import { createEmptyTexture2D } from './emptyTexture.js';
|
|
3
3
|
import { LIVE_POINT_SHADOW_MAPS, POINT_SHADOW_POOL } from './lightBudget.js';
|
|
4
|
+
import { PointGlassTint } from './pointGlassTint.js';
|
|
4
5
|
import { pointShadowFaceRotation } from './pointShadowImage.js';
|
|
5
6
|
import { compileProgram, uniformLocations } from './shader.js';
|
|
6
7
|
import { OCTAHEDRAL_RESOLVE_FRAG, OCTAHEDRAL_RESOLVE_VERT } from './shaders/octahedralResolve.js';
|
|
@@ -15,6 +16,22 @@ import { OCTAHEDRAL_RESOLVE_FRAG, OCTAHEDRAL_RESOLVE_VERT } from './shaders/octa
|
|
|
15
16
|
export const OCTAHEDRAL_EDGE = 1024;
|
|
16
17
|
/** Filled per resolved face, at module scope, because a bake runs inside the frame's budget. */
|
|
17
18
|
const faceRotation = mat3.create();
|
|
19
|
+
/**
|
|
20
|
+
* Every published layer as the array stores it: `layer × stride`, and −1 left as −1.
|
|
21
|
+
*
|
|
22
|
+
* **Applied to the resolved arrays right after each resolve**, so every consumer of an uploaded
|
|
23
|
+
* index reads the physical layer: once glass arrives, light L's depth is at 2L and its glass at
|
|
24
|
+
* 2L + 1 (`PointShadowArray.growForGlass`). In place, because the resolve rewrites these every time.
|
|
25
|
+
*/
|
|
26
|
+
export function strideLayers(layers, stride) {
|
|
27
|
+
if (stride === 1)
|
|
28
|
+
return;
|
|
29
|
+
for (let i = 0; i < layers.length; i++) {
|
|
30
|
+
const layer = layers[i];
|
|
31
|
+
if (layer >= 0)
|
|
32
|
+
layers[i] = layer * stride;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
18
35
|
/**
|
|
19
36
|
* Every point light's shadow as one array texture, and the scratch that fills it.
|
|
20
37
|
*
|
|
@@ -32,12 +49,16 @@ const faceRotation = mat3.create();
|
|
|
32
49
|
* never the same object.
|
|
33
50
|
*/
|
|
34
51
|
export class PointShadowArray {
|
|
35
|
-
texture;
|
|
36
52
|
/**
|
|
37
|
-
* How many
|
|
38
|
-
* for each rectangle a world said would cast. See the constructor's `extraLayers`.
|
|
53
|
+
* How many lights this array holds: the point pool, the two live transition maps, and two more
|
|
54
|
+
* for each rectangle a world said would cast. See the constructor's `extraLayers`. Counted in
|
|
55
|
+
* lights, so it does not move when glass doubles the storage under it.
|
|
39
56
|
*/
|
|
40
57
|
layers;
|
|
58
|
+
/** Replaced once, when glass first casts: see `growForGlass`. */
|
|
59
|
+
current;
|
|
60
|
+
/** What the lights' glass lets through, made with the glass layers. */
|
|
61
|
+
tint = null;
|
|
41
62
|
framebuffer;
|
|
42
63
|
scratchTexture;
|
|
43
64
|
scratchFramebuffer;
|
|
@@ -68,17 +89,8 @@ export class PointShadowArray {
|
|
|
68
89
|
*/
|
|
69
90
|
this.layers =
|
|
70
91
|
Math.min(lightCount, POINT_SHADOW_POOL) + LIVE_POINT_SHADOW_MAPS + Math.max(0, extraLayers);
|
|
71
|
-
const texture = gl.
|
|
72
|
-
|
|
73
|
-
throw new Error('PointShadowArray: createTexture failed');
|
|
74
|
-
this.texture = texture;
|
|
75
|
-
gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
|
|
76
|
-
gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.DEPTH_COMPONENT24, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, this.layers);
|
|
77
|
-
/* One storage level and no filtering, so textureLod(..., 0.0) is the only fetch there is. */
|
|
78
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
79
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
80
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
81
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
92
|
+
const texture = depthArray(gl, this.layers);
|
|
93
|
+
this.current = texture;
|
|
82
94
|
const scratchTexture = gl.createTexture();
|
|
83
95
|
if (scratchTexture === null)
|
|
84
96
|
throw new Error('PointShadowArray: createTexture failed');
|
|
@@ -141,6 +153,86 @@ export class PointShadowArray {
|
|
|
141
153
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
142
154
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
143
155
|
}
|
|
156
|
+
/** The array texture the lit pass samples. Replaced once, when glass first casts. */
|
|
157
|
+
get texture() {
|
|
158
|
+
return this.current;
|
|
159
|
+
}
|
|
160
|
+
get hasGlass() {
|
|
161
|
+
return this.tint !== null;
|
|
162
|
+
}
|
|
163
|
+
/** Physical layers a light: 1, or 2 once glass has interleaved the array. */
|
|
164
|
+
get stride() {
|
|
165
|
+
return this.tint === null ? 1 : 2;
|
|
166
|
+
}
|
|
167
|
+
/** The glass tint, one layer a light, once glass has cast; the lit pass samples it. */
|
|
168
|
+
get tintTexture() {
|
|
169
|
+
return this.tint?.texture ?? null;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Interleave the array for glass: light L's depth moves to 2L and its glass takes 2L + 1, and
|
|
173
|
+
* the tint is made. Returns whether it grew — once, on the first face whose casters held glass.
|
|
174
|
+
*
|
|
175
|
+
* **Interleaved rather than stacked above the opaque half** because a shader cannot ask an array
|
|
176
|
+
* its layer count under naga, so it cannot find `L + N`; `layer + 1` it can. **Every map already
|
|
177
|
+
* baked is carried across** by a depth blit, so nothing goes dark, and **no map needs baking
|
|
178
|
+
* again**: this runs on the first face that saw glass, so no map baked before it saw any, and a
|
|
179
|
+
* glass layer born at the far plane with a white tint is exactly what its bake would draw. What
|
|
180
|
+
* it costs is the doubled depth (4.19 MB a light at 1024) and the tint (5.6 MB a light with its
|
|
181
|
+
* chain, 1.4 MB at `'half'`), from then on.
|
|
182
|
+
*/
|
|
183
|
+
growForGlass(gl, tintEdge) {
|
|
184
|
+
if (this.tint !== null)
|
|
185
|
+
return false;
|
|
186
|
+
const old = this.current;
|
|
187
|
+
const texture = depthArray(gl, this.layers * 2);
|
|
188
|
+
/* Off the unit it was made on, as the constructor leaves its own: see `beginFace`. */
|
|
189
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
190
|
+
const read = gl.createFramebuffer();
|
|
191
|
+
if (read === null)
|
|
192
|
+
throw new Error('PointShadowArray: createFramebuffer failed');
|
|
193
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read);
|
|
194
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.framebuffer);
|
|
195
|
+
for (let layer = 0; layer < this.layers; layer++) {
|
|
196
|
+
gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, old, 0, layer);
|
|
197
|
+
gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texture, 0, 2 * layer);
|
|
198
|
+
gl.blitFramebuffer(0, 0, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, 0, 0, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, gl.DEPTH_BUFFER_BIT, gl.NEAREST);
|
|
199
|
+
}
|
|
200
|
+
/* Nothing is nearer a lamp than a pane that has not been drawn. */
|
|
201
|
+
for (let layer = 0; layer < this.layers; layer++) {
|
|
202
|
+
gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texture, 0, 2 * layer + 1);
|
|
203
|
+
gl.clearBufferfv(gl.DEPTH, 0, FAR_PLANE);
|
|
204
|
+
}
|
|
205
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
|
|
206
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
207
|
+
gl.deleteFramebuffer(read);
|
|
208
|
+
gl.deleteTexture(old);
|
|
209
|
+
this.current = texture;
|
|
210
|
+
this.tint = new PointGlassTint(gl, this.faceSize, tintEdge, this.layers);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
heldGlass(layer, face) {
|
|
214
|
+
return this.tint?.held(layer, face) ?? false;
|
|
215
|
+
}
|
|
216
|
+
/** The depth scratch, cleared, for the face's glass: `beginFace` exactly. */
|
|
217
|
+
beginGlassFace(gl) {
|
|
218
|
+
this.beginFace(gl);
|
|
219
|
+
}
|
|
220
|
+
/** The face's glass depth into light `layer`'s glass layer, 2L + 1. */
|
|
221
|
+
resolveGlassFace(gl, layer, face, far, near) {
|
|
222
|
+
if (this.tint === null)
|
|
223
|
+
return;
|
|
224
|
+
this.resolveInto(gl, 2 * layer + 1, face, far, near);
|
|
225
|
+
}
|
|
226
|
+
beginTintFace(gl) {
|
|
227
|
+
this.tint?.beginFace(gl);
|
|
228
|
+
}
|
|
229
|
+
resolveTintFace(gl, layer, face, drawn) {
|
|
230
|
+
this.tint?.resolveFace(gl, layer, face, drawn);
|
|
231
|
+
}
|
|
232
|
+
/** The chain frost reads, for every light whose tint a round touched: once a round. */
|
|
233
|
+
finishTints(gl) {
|
|
234
|
+
this.tint?.fillMips(gl);
|
|
235
|
+
}
|
|
144
236
|
/**
|
|
145
237
|
* Bind the scratch as the render target for one face of one bake, and clear it.
|
|
146
238
|
*
|
|
@@ -176,8 +268,12 @@ export class PointShadowArray {
|
|
|
176
268
|
* from an empty vertex array.
|
|
177
269
|
*/
|
|
178
270
|
resolveFace(gl, layer, face, far, near) {
|
|
271
|
+
this.resolveInto(gl, layer * this.stride, face, far, near);
|
|
272
|
+
}
|
|
273
|
+
/** The scratch into physical layer `physical`'s region for `face`. */
|
|
274
|
+
resolveInto(gl, physical, face, far, near) {
|
|
179
275
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
180
|
-
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, this.
|
|
276
|
+
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, this.current, 0, physical);
|
|
181
277
|
gl.viewport(0, 0, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE);
|
|
182
278
|
gl.useProgram(this.program);
|
|
183
279
|
gl.bindVertexArray(this.vao);
|
|
@@ -210,10 +306,25 @@ export class PointShadowArray {
|
|
|
210
306
|
dispose(gl) {
|
|
211
307
|
gl.deleteFramebuffer(this.framebuffer);
|
|
212
308
|
gl.deleteFramebuffer(this.scratchFramebuffer);
|
|
213
|
-
gl.deleteTexture(this.
|
|
309
|
+
gl.deleteTexture(this.current);
|
|
214
310
|
gl.deleteTexture(this.scratchTexture);
|
|
311
|
+
this.tint?.dispose(gl);
|
|
215
312
|
gl.deleteTexture(this.parked);
|
|
216
313
|
gl.deleteProgram(this.program);
|
|
217
314
|
gl.deleteVertexArray(this.vao);
|
|
218
315
|
}
|
|
219
316
|
}
|
|
317
|
+
const FAR_PLANE = new Float32Array([1]);
|
|
318
|
+
/** The depth array: one storage level and no filtering, so textureLod(..., 0.0) is the only fetch. */
|
|
319
|
+
function depthArray(gl, layers) {
|
|
320
|
+
const texture = gl.createTexture();
|
|
321
|
+
if (texture === null)
|
|
322
|
+
throw new Error('PointShadowArray: createTexture failed');
|
|
323
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
|
|
324
|
+
gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.DEPTH_COMPONENT24, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, layers);
|
|
325
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
326
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
327
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
328
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
329
|
+
return texture;
|
|
330
|
+
}
|
|
@@ -177,15 +177,15 @@ export function runPointShadowBakes(pool, lights, facesPerFrame, liveFacesPerFra
|
|
|
177
177
|
* **The run decays rather than resetting, and it climbs past the threshold before it stops.**
|
|
178
178
|
*
|
|
179
179
|
* A light that wanders on a curve is not stale on *every* frame — near the turning points of
|
|
180
|
-
* its travel it moves less in a frame than the rebake tolerance
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
180
|
+
* its travel it moves less in a frame than the rebake tolerance. When the run decided whether a
|
|
181
|
+
* wanderer got its whole cube, a run that reset to zero dropped it back to two faces a frame
|
|
182
|
+
* there, and the shadow stepped through the slow part of the wander: reported as a vibration
|
|
183
|
+
* against a brazier. Every image-holding map is re-baked whole now, so the run only orders the
|
|
184
|
+
* queue, and the hysteresis keeps a wanderer behind the lights that settle instead of letting
|
|
185
|
+
* it jump ahead at every turning point.
|
|
185
186
|
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* so a light that genuinely settles stops re-baking rather than holding a budget for ever.
|
|
187
|
+
* It climbs to twice the threshold, which keeps a light chronic for three quiet frames, and
|
|
188
|
+
* decays one frame at a time so a light that genuinely settles rejoins the ordinary order.
|
|
189
189
|
*/
|
|
190
190
|
const previous = scratch.staleRun[slot] ?? 0;
|
|
191
191
|
const run = stale
|
|
@@ -198,37 +198,40 @@ export function runPointShadowBakes(pool, lights, facesPerFrame, liveFacesPerFra
|
|
|
198
198
|
let faces = facesPerFrame;
|
|
199
199
|
let urgentSpent = false;
|
|
200
200
|
/*
|
|
201
|
-
* **
|
|
202
|
-
*
|
|
203
|
-
* dribbled out at two faces a frame is a map nobody can sample correctly until it lands.
|
|
201
|
+
* **A map that holds an image is re-baked whole or not at all**, and one being shaded may take a
|
|
202
|
+
* whole cube a frame outside the budget to make that possible.
|
|
204
203
|
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* wanders about once a second.
|
|
204
|
+
* Faces land in the layer the shader samples as they are drawn, and `planBake` publishes the
|
|
205
|
+
* origin they were drawn from only with the last one. So a re-bake spread over frames is a cube
|
|
206
|
+
* the shader reads from the old origin with part of it drawn from the new one — and a face is a
|
|
207
|
+
* 90 degree frustum, so the part drawn from somewhere else is a straight-edged quadrilateral of
|
|
208
|
+
* wrong shadow on the ground under the lamp. With the budget spent elsewhere it could hold for a
|
|
209
|
+
* second and more. Reported under a brazier as a dark shape that came and went.
|
|
212
210
|
*
|
|
213
|
-
*
|
|
211
|
+
* A map with no image yet is not sampled until its sixth face lands (see
|
|
212
|
+
* `PointShadowSystem.bind`), so it still fills a few faces a frame, and a cold one being shaded
|
|
213
|
+
* still jumps the queue with all six.
|
|
214
214
|
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* cost the frame.
|
|
215
|
+
* The allowance used to be for a *chronically* stale light only — a wandering flame — whose
|
|
216
|
+
* dribbled cube published a new origin once every third frame, so its shadow stepped at 20 Hz
|
|
217
|
+
* where the flame wanders about once a second: reported as motion that was "really fast, not
|
|
218
|
+
* smooth, snappy, and unrealistic". Any shaded light is owed the same now, because a cube read
|
|
219
|
+
* part-way is wrong in the same place for the same reason whether or not it will be stale again.
|
|
221
220
|
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
221
|
+
* **Bounded to one light, and it has to be.** Eight braziers each taking six passes is the 48
|
|
222
|
+
* passes and 89 ms `pointShadowFacesPerFrame` exists to prevent. The first in the plan's order
|
|
223
|
+
* takes it, and `planPointShadowBakes` puts the lights that settled ahead of the ones that never
|
|
224
|
+
* do, so a flame refining its image cannot hold a correction hostage. The rest keep sampling a
|
|
225
|
+
* complete image from a few centimetres away until their turn, which is the trade
|
|
226
|
+
* `pointShadowRebakeDistance` already makes. A warm map, held but not shaded, is re-baked when a
|
|
227
|
+
* whole cube fits the budget or once it is shaded, and not before.
|
|
225
228
|
*
|
|
226
|
-
* **What would make it wrong** is a caster set heavy enough that six passes over it does not
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
+
* **What would make it wrong** is a caster set heavy enough that six passes over it does not fit
|
|
230
|
+
* the frame. Measured against a game whose whole static world is about 8,500 triangles in a
|
|
231
|
+
* handful of draws; a world an order of magnitude heavier wants `pointShadowFacesPerFrame`
|
|
229
232
|
* raised or this given up, and `gpuTiming` is how to tell which.
|
|
230
233
|
*/
|
|
231
|
-
let
|
|
234
|
+
let refreshSpent = false;
|
|
232
235
|
for (let i = 0; i < planned && faces > 0; i++) {
|
|
233
236
|
const slot = scratch.order[i] ?? -1;
|
|
234
237
|
/*
|
|
@@ -241,15 +244,22 @@ export function runPointShadowBakes(pool, lights, facesPerFrame, liveFacesPerFra
|
|
|
241
244
|
if (light === undefined || map === undefined)
|
|
242
245
|
continue;
|
|
243
246
|
const sampled = slot < pool.sampledCount;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
247
|
+
if (scratch.ready[slot] !== 1) {
|
|
248
|
+
/* No image to spoil: a few faces a frame, or all six at once for one being shaded. */
|
|
249
|
+
const urgent = sampled && !urgentSpent;
|
|
250
|
+
if (urgent)
|
|
251
|
+
urgentSpent = true;
|
|
252
|
+
faces -= bake(map, light, staticCasters, urgent ? faceCount : faces);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (faces >= faceCount) {
|
|
256
|
+
faces -= bake(map, light, staticCasters, faceCount);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (!sampled || refreshSpent)
|
|
260
|
+
continue;
|
|
261
|
+
refreshSpent = true;
|
|
262
|
+
faces -= bake(map, light, staticCasters, faceCount);
|
|
253
263
|
}
|
|
254
264
|
/*
|
|
255
265
|
* Live maps contain movers only and are composed with the static maps in the shader. A second
|
|
@@ -21,6 +21,18 @@ import { FACE_COUNT, POINT_SHADOW_NEAR } from './pointShadowImage.ts';
|
|
|
21
21
|
* projection the shader reads. Nothing about the bake's cost, order or resumption moved.
|
|
22
22
|
*/
|
|
23
23
|
export { FACE_COUNT, POINT_SHADOW_NEAR };
|
|
24
|
+
/**
|
|
25
|
+
* What a bake asks of the renderer about glass, after each face's opaque depth.
|
|
26
|
+
*
|
|
27
|
+
* **Asked before it is drawn** because the answer decides whether the face binds a glass target at
|
|
28
|
+
* all: a face that offers no glass and holds none from a previous owner does no glass work.
|
|
29
|
+
*/
|
|
30
|
+
export interface PointGlassDraw {
|
|
31
|
+
/** Whether the face just drawn offered any glass. */
|
|
32
|
+
offered(): boolean;
|
|
33
|
+
/** Draw that glass: its depth, or what it lets through lit from the bake origin. */
|
|
34
|
+
draw(pass: 'depth' | 'tint', x: number, y: number, z: number): void;
|
|
35
|
+
}
|
|
24
36
|
export declare class PointShadowMap {
|
|
25
37
|
readonly size: number;
|
|
26
38
|
/**
|
|
@@ -85,7 +97,9 @@ export declare class PointShadowMap {
|
|
|
85
97
|
* bake is not sampled at all until it completes, so this only matters for a re-bake,
|
|
86
98
|
* where holding the old values keeps them matched to the old faces.
|
|
87
99
|
*/
|
|
88
|
-
bake(gl: WebGL2RenderingContext, x: number, y: number, z: number, range: number, drawCasters: (viewProj: mat4) => void, near?: number, sourceRadius?: number, maxFaces?: number
|
|
100
|
+
bake(gl: WebGL2RenderingContext, x: number, y: number, z: number, range: number, drawCasters: (viewProj: mat4) => void, near?: number, sourceRadius?: number, maxFaces?: number,
|
|
101
|
+
/** The face's glass, when glass shadows are on: `PointGlassDraw`. */
|
|
102
|
+
glass?: PointGlassDraw | null): number;
|
|
89
103
|
/**
|
|
90
104
|
* Nothing to free: every GPU object this map used to own belongs to `PointShadowArray` now.
|
|
91
105
|
*
|
|
@@ -93,5 +107,13 @@ export declare class PointShadowMap {
|
|
|
93
107
|
* needing disposal is exactly the shape of a leak nobody notices. The array is disposed by the
|
|
94
108
|
* renderer that built it.
|
|
95
109
|
*/
|
|
110
|
+
/**
|
|
111
|
+
* The face's glass, twice: where the nearest pane is, then what the panes let through.
|
|
112
|
+
*
|
|
113
|
+
* A face that offers no glass still clears a region a previous owner's glass is in — the depth to
|
|
114
|
+
* the far plane and the tint to white, from scratches cleared and not drawn into — because a pool
|
|
115
|
+
* slot changes hands with its layer.
|
|
116
|
+
*/
|
|
117
|
+
private bakeGlassFace;
|
|
96
118
|
dispose(_gl: WebGL2RenderingContext): void;
|
|
97
119
|
}
|
|
@@ -113,7 +113,9 @@ export class PointShadowMap {
|
|
|
113
113
|
* bake is not sampled at all until it completes, so this only matters for a re-bake,
|
|
114
114
|
* where holding the old values keeps them matched to the old faces.
|
|
115
115
|
*/
|
|
116
|
-
bake(gl, x, y, z, range, drawCasters, near = POINT_SHADOW_NEAR, sourceRadius = DEFAULT_SOURCE_RADIUS, maxFaces = FACE_COUNT
|
|
116
|
+
bake(gl, x, y, z, range, drawCasters, near = POINT_SHADOW_NEAR, sourceRadius = DEFAULT_SOURCE_RADIUS, maxFaces = FACE_COUNT,
|
|
117
|
+
/** The face's glass, when glass shadows are on: `PointGlassDraw`. */
|
|
118
|
+
glass = null) {
|
|
117
119
|
/*
|
|
118
120
|
* What to render now, **and what to render it for**. A bake in flight owns the parameters
|
|
119
121
|
* it started with, so the arguments here are only a proposal for a bake that is not already
|
|
@@ -159,6 +161,8 @@ export class PointShadowMap {
|
|
|
159
161
|
* next face's pass does care about.
|
|
160
162
|
*/
|
|
161
163
|
array.resolveFace(gl, this.layer, face, plan.range, plan.near);
|
|
164
|
+
if (glass !== null)
|
|
165
|
+
this.bakeGlassFace(gl, array, glass, face, plan);
|
|
162
166
|
}
|
|
163
167
|
gl.enable(gl.CULL_FACE);
|
|
164
168
|
gl.cullFace(gl.BACK);
|
|
@@ -173,5 +177,27 @@ export class PointShadowMap {
|
|
|
173
177
|
* needing disposal is exactly the shape of a leak nobody notices. The array is disposed by the
|
|
174
178
|
* renderer that built it.
|
|
175
179
|
*/
|
|
180
|
+
/**
|
|
181
|
+
* The face's glass, twice: where the nearest pane is, then what the panes let through.
|
|
182
|
+
*
|
|
183
|
+
* A face that offers no glass still clears a region a previous owner's glass is in — the depth to
|
|
184
|
+
* the far plane and the tint to white, from scratches cleared and not drawn into — because a pool
|
|
185
|
+
* slot changes hands with its layer.
|
|
186
|
+
*/
|
|
187
|
+
bakeGlassFace(gl, array, glass, face, plan) {
|
|
188
|
+
if (!array.hasGlass)
|
|
189
|
+
return;
|
|
190
|
+
const offered = glass.offered();
|
|
191
|
+
if (!offered && !array.heldGlass(this.layer, face))
|
|
192
|
+
return;
|
|
193
|
+
array.beginGlassFace(gl);
|
|
194
|
+
if (offered)
|
|
195
|
+
glass.draw('depth', plan.x, plan.y, plan.z);
|
|
196
|
+
array.resolveGlassFace(gl, this.layer, face, plan.range, plan.near);
|
|
197
|
+
array.beginTintFace(gl);
|
|
198
|
+
if (offered)
|
|
199
|
+
glass.draw('tint', plan.x, plan.y, plan.z);
|
|
200
|
+
array.resolveTintFace(gl, this.layer, face, offered);
|
|
201
|
+
}
|
|
176
202
|
dispose(_gl) { }
|
|
177
203
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which frame a change to a dynamic mesh or an instanced batch belongs to.
|
|
3
|
+
*
|
|
4
|
+
* **A consumer may change geometry in its simulation step, before `beginFrame`.** That change is
|
|
5
|
+
* what the *next* frame draws, so it is that frame's — counting it against the frame last begun
|
|
6
|
+
* would say the geometry changed a frame early and then did not change at all, which is a motion
|
|
7
|
+
* vector on the wrong frame and none on the right one.
|
|
8
|
+
*
|
|
9
|
+
* Only the **first** change in a frame keeps the state before it: a second rewrite in the same
|
|
10
|
+
* frame must not replace "where it was last frame" with "where it was a moment ago".
|
|
11
|
+
*/
|
|
12
|
+
export declare function frameOfChange(serial: number, inFrame: boolean): number;
|
|
13
|
+
export declare function keepsPrevious(lastChange: number, frame: number): boolean;
|
|
14
|
+
/** Slots `0 … n-1` pair with last frame's; a slot past last frame's count is new and has no past. */
|
|
15
|
+
export declare function pairedInstances(previousCount: number, count: number): number;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which frame a change to a dynamic mesh or an instanced batch belongs to.
|
|
3
|
+
*
|
|
4
|
+
* **A consumer may change geometry in its simulation step, before `beginFrame`.** That change is
|
|
5
|
+
* what the *next* frame draws, so it is that frame's — counting it against the frame last begun
|
|
6
|
+
* would say the geometry changed a frame early and then did not change at all, which is a motion
|
|
7
|
+
* vector on the wrong frame and none on the right one.
|
|
8
|
+
*
|
|
9
|
+
* Only the **first** change in a frame keeps the state before it: a second rewrite in the same
|
|
10
|
+
* frame must not replace "where it was last frame" with "where it was a moment ago".
|
|
11
|
+
*/
|
|
12
|
+
export function frameOfChange(serial, inFrame) {
|
|
13
|
+
return inFrame ? serial : serial + 1;
|
|
14
|
+
}
|
|
15
|
+
export function keepsPrevious(lastChange, frame) {
|
|
16
|
+
return lastChange !== frame;
|
|
17
|
+
}
|
|
18
|
+
/** Slots `0 … n-1` pair with last frame's; a slot past last frame's count is new and has no past. */
|
|
19
|
+
export function pairedInstances(previousCount, count) {
|
|
20
|
+
return Math.min(previousCount, count);
|
|
21
|
+
}
|
|
@@ -12,10 +12,15 @@
|
|
|
12
12
|
* a different part of a texel than the one it stands for, which at a grazing angle is a different
|
|
13
13
|
* depth; the tolerance grows with the motion's length.
|
|
14
14
|
*
|
|
15
|
+
* **And in proportion to the surface's own slope.** A new jitter samples a surface somewhere else, so
|
|
16
|
+
* on one turned from the eye two frames' samples of it differ by up to a texel's worth of its depth
|
|
17
|
+
* with nothing moved. `surfaceSlope` measures that step on the surface's own side of any silhouette.
|
|
18
|
+
*
|
|
15
19
|
* **Normals, for the case depth cannot see.** A leaf's two sides are at one depth, and the history of
|
|
16
20
|
* the front is not the back. The forward frame has no normal buffer, so both normals come from their
|
|
17
21
|
* frame's depth — `worldPositionFromDepth` and `normalFromPositions` — in world space, where the two
|
|
18
|
-
* frames agree.
|
|
22
|
+
* frames agree. **A normal differenced across an edge is not a normal**, so the caller weighs each by
|
|
23
|
+
* `planarity`: at an edge, a sliver, or anything finer than a texel the depth test decides alone.
|
|
19
24
|
*
|
|
20
25
|
* **Continuous in every input**, because a step is a line on screen along which the history is kept on
|
|
21
26
|
* one frame and dropped on the next: every threshold is a smoothstep, never a comparison.
|
|
@@ -38,14 +43,42 @@ export interface DisocclusionParams {
|
|
|
38
43
|
* normals trusted fully within sixty degrees and not at all past ninety.
|
|
39
44
|
*/
|
|
40
45
|
export declare const DEFAULT_DISOCCLUSION: DisocclusionParams;
|
|
46
|
+
/**
|
|
47
|
+
* The band over which three texels stop being one plane: how far their two steps in inverse depth
|
|
48
|
+
* fail to cancel, as a share of the two together. None of it is a plane at any angle; all of it is an
|
|
49
|
+
* edge — one side steps, the other does not — or a sliver, both stepping the same way.
|
|
50
|
+
*
|
|
51
|
+
* **A share and not a size**, which is the lesson of the rods in `resolve.test.ts`: a part a
|
|
52
|
+
* hundredth of the depth in front of a wall passes the depth test, and a normal differenced across it
|
|
53
|
+
* still leans by its height over a texel's width — nearly edge-on where texels are small. What would
|
|
54
|
+
* make this wrong is a curved surface judged at its apex, where both steps lean one way and the
|
|
55
|
+
* normal is sound; there it only gives the normals less say, which the depth test does not need.
|
|
56
|
+
*/
|
|
57
|
+
export declare const PLANAR_FROM = 0.25;
|
|
58
|
+
export declare const PLANAR_TO = 0.5;
|
|
59
|
+
/**
|
|
60
|
+
* How sure it is that `centre` and its neighbours either side along one axis lie on one plane, one to
|
|
61
|
+
* zero. Inverse depth is affine across a plane on screen, so a plane's two steps cancel whatever its
|
|
62
|
+
* angle. One where a neighbour is missing — zero, off the picture or undrawn — or where there is no
|
|
63
|
+
* step to compare: nothing to judge by, so a normal keeps the say it always had.
|
|
64
|
+
*/
|
|
65
|
+
export declare function planarity(before: number, centre: number, after: number): number;
|
|
66
|
+
/**
|
|
67
|
+
* How far a surface's depth moves across one texel along an axis, relative to the depth: the smaller
|
|
68
|
+
* of the steps to its two neighbours, because beside a silhouette one of them is the surface behind.
|
|
69
|
+
* Zero at a sliver — nearer or farther than both, so each step is to another surface — and one
|
|
70
|
+
* neighbour's step where only one is there.
|
|
71
|
+
*/
|
|
72
|
+
export declare function surfaceSlope(before: number, centre: number, after: number): number;
|
|
41
73
|
/**
|
|
42
74
|
* One where the history is trustworthy, zero where it is not, and continuous between.
|
|
43
75
|
*
|
|
44
76
|
* `currDepth` is the view depth this surface should have had last frame; `histDepth` the view depth
|
|
45
77
|
* last frame held where it lands, or zero where it landed off the screen; `motionLength` the motion's
|
|
46
|
-
* length in uv; `normalDot` the cosine between the two frames' world normals there
|
|
78
|
+
* length in uv; `normalDot` the cosine between the two frames' world normals there; `slope` the
|
|
79
|
+
* surface's `surfaceSlope`, which widens the tolerance one for one.
|
|
47
80
|
*/
|
|
48
|
-
export declare function disocclusionWeight(currDepth: number, histDepth: number, motionLength: number, normalDot: number, params: DisocclusionParams): number;
|
|
81
|
+
export declare function disocclusionWeight(currDepth: number, histDepth: number, motionLength: number, normalDot: number, params: DisocclusionParams, slope?: number): number;
|
|
49
82
|
/**
|
|
50
83
|
* The world position a depth stands for: `(u, v, clipDepth)` through the inverse view-projection,
|
|
51
84
|
* in whatever depth convention that matrix was built for. Returns false where the matrix has no
|