@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
|
@@ -2,6 +2,7 @@ import { mat3 } from 'gl-matrix';
|
|
|
2
2
|
|
|
3
3
|
import { createEmptyTexture2D } from './emptyTexture.ts';
|
|
4
4
|
import { LIVE_POINT_SHADOW_MAPS, POINT_SHADOW_POOL } from './lightBudget.ts';
|
|
5
|
+
import { PointGlassTint } from './pointGlassTint.ts';
|
|
5
6
|
import { pointShadowFaceRotation } from './pointShadowImage.ts';
|
|
6
7
|
import { compileProgram, uniformLocations } from './shader.ts';
|
|
7
8
|
import { OCTAHEDRAL_RESOLVE_FRAG, OCTAHEDRAL_RESOLVE_VERT } from './shaders/octahedralResolve.ts';
|
|
@@ -34,6 +35,37 @@ export interface PointShadowTarget {
|
|
|
34
35
|
far: number,
|
|
35
36
|
near: number,
|
|
36
37
|
): void;
|
|
38
|
+
/** Whether the array has grown its glass layers: `growForGlass`. */
|
|
39
|
+
readonly hasGlass: boolean;
|
|
40
|
+
/** Whether `face` of `layer` holds glass, which a face that offers none must clear. */
|
|
41
|
+
heldGlass(layer: number, face: number): boolean;
|
|
42
|
+
/** The depth scratch again, cleared, for the face's glass. */
|
|
43
|
+
beginGlassFace(gl: WebGL2RenderingContext): void;
|
|
44
|
+
resolveGlassFace(
|
|
45
|
+
gl: WebGL2RenderingContext,
|
|
46
|
+
layer: number,
|
|
47
|
+
face: number,
|
|
48
|
+
far: number,
|
|
49
|
+
near: number,
|
|
50
|
+
): void;
|
|
51
|
+
/** The colour scratch, cleared to white, for what the face's glass lets through. */
|
|
52
|
+
beginTintFace(gl: WebGL2RenderingContext): void;
|
|
53
|
+
resolveTintFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Every published layer as the array stores it: `layer × stride`, and −1 left as −1.
|
|
58
|
+
*
|
|
59
|
+
* **Applied to the resolved arrays right after each resolve**, so every consumer of an uploaded
|
|
60
|
+
* index reads the physical layer: once glass arrives, light L's depth is at 2L and its glass at
|
|
61
|
+
* 2L + 1 (`PointShadowArray.growForGlass`). In place, because the resolve rewrites these every time.
|
|
62
|
+
*/
|
|
63
|
+
export function strideLayers(layers: Int32Array, stride: number): void {
|
|
64
|
+
if (stride === 1) return;
|
|
65
|
+
for (let i = 0; i < layers.length; i++) {
|
|
66
|
+
const layer = layers[i] as number;
|
|
67
|
+
if (layer >= 0) layers[i] = layer * stride;
|
|
68
|
+
}
|
|
37
69
|
}
|
|
38
70
|
|
|
39
71
|
/**
|
|
@@ -53,12 +85,16 @@ export interface PointShadowTarget {
|
|
|
53
85
|
* never the same object.
|
|
54
86
|
*/
|
|
55
87
|
export class PointShadowArray implements PointShadowTarget {
|
|
56
|
-
readonly texture: WebGLTexture;
|
|
57
88
|
/**
|
|
58
|
-
* How many
|
|
59
|
-
* for each rectangle a world said would cast. See the constructor's `extraLayers`.
|
|
89
|
+
* How many lights this array holds: the point pool, the two live transition maps, and two more
|
|
90
|
+
* for each rectangle a world said would cast. See the constructor's `extraLayers`. Counted in
|
|
91
|
+
* lights, so it does not move when glass doubles the storage under it.
|
|
60
92
|
*/
|
|
61
93
|
readonly layers: number;
|
|
94
|
+
/** Replaced once, when glass first casts: see `growForGlass`. */
|
|
95
|
+
private current: WebGLTexture;
|
|
96
|
+
/** What the lights' glass lets through, made with the glass layers. */
|
|
97
|
+
private tint: PointGlassTint | null = null;
|
|
62
98
|
|
|
63
99
|
private readonly framebuffer: WebGLFramebuffer;
|
|
64
100
|
private readonly scratchTexture: WebGLTexture;
|
|
@@ -96,23 +132,8 @@ export class PointShadowArray implements PointShadowTarget {
|
|
|
96
132
|
this.layers =
|
|
97
133
|
Math.min(lightCount, POINT_SHADOW_POOL) + LIVE_POINT_SHADOW_MAPS + Math.max(0, extraLayers);
|
|
98
134
|
|
|
99
|
-
const texture = gl.
|
|
100
|
-
|
|
101
|
-
this.texture = texture;
|
|
102
|
-
gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
|
|
103
|
-
gl.texStorage3D(
|
|
104
|
-
gl.TEXTURE_2D_ARRAY,
|
|
105
|
-
1,
|
|
106
|
-
gl.DEPTH_COMPONENT24,
|
|
107
|
-
OCTAHEDRAL_EDGE,
|
|
108
|
-
OCTAHEDRAL_EDGE,
|
|
109
|
-
this.layers,
|
|
110
|
-
);
|
|
111
|
-
/* One storage level and no filtering, so textureLod(..., 0.0) is the only fetch there is. */
|
|
112
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
113
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
114
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
115
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
135
|
+
const texture = depthArray(gl, this.layers);
|
|
136
|
+
this.current = texture;
|
|
116
137
|
|
|
117
138
|
const scratchTexture = gl.createTexture();
|
|
118
139
|
if (scratchTexture === null) throw new Error('PointShadowArray: createTexture failed');
|
|
@@ -183,6 +204,117 @@ export class PointShadowArray implements PointShadowTarget {
|
|
|
183
204
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
184
205
|
}
|
|
185
206
|
|
|
207
|
+
/** The array texture the lit pass samples. Replaced once, when glass first casts. */
|
|
208
|
+
get texture(): WebGLTexture {
|
|
209
|
+
return this.current;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
get hasGlass(): boolean {
|
|
213
|
+
return this.tint !== null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Physical layers a light: 1, or 2 once glass has interleaved the array. */
|
|
217
|
+
get stride(): number {
|
|
218
|
+
return this.tint === null ? 1 : 2;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** The glass tint, one layer a light, once glass has cast; the lit pass samples it. */
|
|
222
|
+
get tintTexture(): WebGLTexture | null {
|
|
223
|
+
return this.tint?.texture ?? null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Interleave the array for glass: light L's depth moves to 2L and its glass takes 2L + 1, and
|
|
228
|
+
* the tint is made. Returns whether it grew — once, on the first face whose casters held glass.
|
|
229
|
+
*
|
|
230
|
+
* **Interleaved rather than stacked above the opaque half** because a shader cannot ask an array
|
|
231
|
+
* its layer count under naga, so it cannot find `L + N`; `layer + 1` it can. **Every map already
|
|
232
|
+
* baked is carried across** by a depth blit, so nothing goes dark, and **no map needs baking
|
|
233
|
+
* again**: this runs on the first face that saw glass, so no map baked before it saw any, and a
|
|
234
|
+
* glass layer born at the far plane with a white tint is exactly what its bake would draw. What
|
|
235
|
+
* it costs is the doubled depth (4.19 MB a light at 1024) and the tint (5.6 MB a light with its
|
|
236
|
+
* chain, 1.4 MB at `'half'`), from then on.
|
|
237
|
+
*/
|
|
238
|
+
growForGlass(gl: WebGL2RenderingContext, tintEdge: number): boolean {
|
|
239
|
+
if (this.tint !== null) return false;
|
|
240
|
+
const old = this.current;
|
|
241
|
+
const texture = depthArray(gl, this.layers * 2);
|
|
242
|
+
/* Off the unit it was made on, as the constructor leaves its own: see `beginFace`. */
|
|
243
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
244
|
+
const read = gl.createFramebuffer();
|
|
245
|
+
if (read === null) throw new Error('PointShadowArray: createFramebuffer failed');
|
|
246
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read);
|
|
247
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.framebuffer);
|
|
248
|
+
for (let layer = 0; layer < this.layers; layer++) {
|
|
249
|
+
gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, old, 0, layer);
|
|
250
|
+
gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texture, 0, 2 * layer);
|
|
251
|
+
gl.blitFramebuffer(
|
|
252
|
+
0,
|
|
253
|
+
0,
|
|
254
|
+
OCTAHEDRAL_EDGE,
|
|
255
|
+
OCTAHEDRAL_EDGE,
|
|
256
|
+
0,
|
|
257
|
+
0,
|
|
258
|
+
OCTAHEDRAL_EDGE,
|
|
259
|
+
OCTAHEDRAL_EDGE,
|
|
260
|
+
gl.DEPTH_BUFFER_BIT,
|
|
261
|
+
gl.NEAREST,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
/* Nothing is nearer a lamp than a pane that has not been drawn. */
|
|
265
|
+
for (let layer = 0; layer < this.layers; layer++) {
|
|
266
|
+
gl.framebufferTextureLayer(
|
|
267
|
+
gl.DRAW_FRAMEBUFFER,
|
|
268
|
+
gl.DEPTH_ATTACHMENT,
|
|
269
|
+
texture,
|
|
270
|
+
0,
|
|
271
|
+
2 * layer + 1,
|
|
272
|
+
);
|
|
273
|
+
gl.clearBufferfv(gl.DEPTH, 0, FAR_PLANE);
|
|
274
|
+
}
|
|
275
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
|
|
276
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
277
|
+
gl.deleteFramebuffer(read);
|
|
278
|
+
gl.deleteTexture(old);
|
|
279
|
+
this.current = texture;
|
|
280
|
+
this.tint = new PointGlassTint(gl, this.faceSize, tintEdge, this.layers);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
heldGlass(layer: number, face: number): boolean {
|
|
285
|
+
return this.tint?.held(layer, face) ?? false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** The depth scratch, cleared, for the face's glass: `beginFace` exactly. */
|
|
289
|
+
beginGlassFace(gl: WebGL2RenderingContext): void {
|
|
290
|
+
this.beginFace(gl);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** The face's glass depth into light `layer`'s glass layer, 2L + 1. */
|
|
294
|
+
resolveGlassFace(
|
|
295
|
+
gl: WebGL2RenderingContext,
|
|
296
|
+
layer: number,
|
|
297
|
+
face: number,
|
|
298
|
+
far: number,
|
|
299
|
+
near: number,
|
|
300
|
+
): void {
|
|
301
|
+
if (this.tint === null) return;
|
|
302
|
+
this.resolveInto(gl, 2 * layer + 1, face, far, near);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
beginTintFace(gl: WebGL2RenderingContext): void {
|
|
306
|
+
this.tint?.beginFace(gl);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
resolveTintFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void {
|
|
310
|
+
this.tint?.resolveFace(gl, layer, face, drawn);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** The chain frost reads, for every light whose tint a round touched: once a round. */
|
|
314
|
+
finishTints(gl: WebGL2RenderingContext): void {
|
|
315
|
+
this.tint?.fillMips(gl);
|
|
316
|
+
}
|
|
317
|
+
|
|
186
318
|
/**
|
|
187
319
|
* Bind the scratch as the render target for one face of one bake, and clear it.
|
|
188
320
|
*
|
|
@@ -224,9 +356,20 @@ export class PointShadowArray implements PointShadowTarget {
|
|
|
224
356
|
face: number,
|
|
225
357
|
far: number,
|
|
226
358
|
near: number,
|
|
359
|
+
): void {
|
|
360
|
+
this.resolveInto(gl, layer * this.stride, face, far, near);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** The scratch into physical layer `physical`'s region for `face`. */
|
|
364
|
+
private resolveInto(
|
|
365
|
+
gl: WebGL2RenderingContext,
|
|
366
|
+
physical: number,
|
|
367
|
+
face: number,
|
|
368
|
+
far: number,
|
|
369
|
+
near: number,
|
|
227
370
|
): void {
|
|
228
371
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
229
|
-
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, this.
|
|
372
|
+
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, this.current, 0, physical);
|
|
230
373
|
gl.viewport(0, 0, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE);
|
|
231
374
|
|
|
232
375
|
gl.useProgram(this.program);
|
|
@@ -267,10 +410,33 @@ export class PointShadowArray implements PointShadowTarget {
|
|
|
267
410
|
dispose(gl: WebGL2RenderingContext): void {
|
|
268
411
|
gl.deleteFramebuffer(this.framebuffer);
|
|
269
412
|
gl.deleteFramebuffer(this.scratchFramebuffer);
|
|
270
|
-
gl.deleteTexture(this.
|
|
413
|
+
gl.deleteTexture(this.current);
|
|
271
414
|
gl.deleteTexture(this.scratchTexture);
|
|
415
|
+
this.tint?.dispose(gl);
|
|
272
416
|
gl.deleteTexture(this.parked);
|
|
273
417
|
gl.deleteProgram(this.program);
|
|
274
418
|
gl.deleteVertexArray(this.vao);
|
|
275
419
|
}
|
|
276
420
|
}
|
|
421
|
+
|
|
422
|
+
const FAR_PLANE = new Float32Array([1]);
|
|
423
|
+
|
|
424
|
+
/** The depth array: one storage level and no filtering, so textureLod(..., 0.0) is the only fetch. */
|
|
425
|
+
function depthArray(gl: WebGL2RenderingContext, layers: number): WebGLTexture {
|
|
426
|
+
const texture = gl.createTexture();
|
|
427
|
+
if (texture === null) throw new Error('PointShadowArray: createTexture failed');
|
|
428
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
|
|
429
|
+
gl.texStorage3D(
|
|
430
|
+
gl.TEXTURE_2D_ARRAY,
|
|
431
|
+
1,
|
|
432
|
+
gl.DEPTH_COMPONENT24,
|
|
433
|
+
OCTAHEDRAL_EDGE,
|
|
434
|
+
OCTAHEDRAL_EDGE,
|
|
435
|
+
layers,
|
|
436
|
+
);
|
|
437
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
438
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
439
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
440
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
441
|
+
return texture;
|
|
442
|
+
}
|
|
@@ -256,15 +256,15 @@ export function runPointShadowBakes<M extends StaleCheck, L extends BakeableLigh
|
|
|
256
256
|
* **The run decays rather than resetting, and it climbs past the threshold before it stops.**
|
|
257
257
|
*
|
|
258
258
|
* A light that wanders on a curve is not stale on *every* frame — near the turning points of
|
|
259
|
-
* its travel it moves less in a frame than the rebake tolerance
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
259
|
+
* its travel it moves less in a frame than the rebake tolerance. When the run decided whether a
|
|
260
|
+
* wanderer got its whole cube, a run that reset to zero dropped it back to two faces a frame
|
|
261
|
+
* there, and the shadow stepped through the slow part of the wander: reported as a vibration
|
|
262
|
+
* against a brazier. Every image-holding map is re-baked whole now, so the run only orders the
|
|
263
|
+
* queue, and the hysteresis keeps a wanderer behind the lights that settle instead of letting
|
|
264
|
+
* it jump ahead at every turning point.
|
|
264
265
|
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
* so a light that genuinely settles stops re-baking rather than holding a budget for ever.
|
|
266
|
+
* It climbs to twice the threshold, which keeps a light chronic for three quiet frames, and
|
|
267
|
+
* decays one frame at a time so a light that genuinely settles rejoins the ordinary order.
|
|
268
268
|
*/
|
|
269
269
|
const previous = scratch.staleRun[slot] ?? 0;
|
|
270
270
|
const run = stale
|
|
@@ -285,37 +285,40 @@ export function runPointShadowBakes<M extends StaleCheck, L extends BakeableLigh
|
|
|
285
285
|
let faces = facesPerFrame;
|
|
286
286
|
let urgentSpent = false;
|
|
287
287
|
/*
|
|
288
|
-
* **
|
|
289
|
-
*
|
|
290
|
-
* dribbled out at two faces a frame is a map nobody can sample correctly until it lands.
|
|
288
|
+
* **A map that holds an image is re-baked whole or not at all**, and one being shaded may take a
|
|
289
|
+
* whole cube a frame outside the budget to make that possible.
|
|
291
290
|
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
* wanders about once a second.
|
|
291
|
+
* Faces land in the layer the shader samples as they are drawn, and `planBake` publishes the
|
|
292
|
+
* origin they were drawn from only with the last one. So a re-bake spread over frames is a cube
|
|
293
|
+
* the shader reads from the old origin with part of it drawn from the new one — and a face is a
|
|
294
|
+
* 90 degree frustum, so the part drawn from somewhere else is a straight-edged quadrilateral of
|
|
295
|
+
* wrong shadow on the ground under the lamp. With the budget spent elsewhere it could hold for a
|
|
296
|
+
* second and more. Reported under a brazier as a dark shape that came and went.
|
|
299
297
|
*
|
|
300
|
-
*
|
|
298
|
+
* A map with no image yet is not sampled until its sixth face lands (see
|
|
299
|
+
* `PointShadowSystem.bind`), so it still fills a few faces a frame, and a cold one being shaded
|
|
300
|
+
* still jumps the queue with all six.
|
|
301
301
|
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
* cost the frame.
|
|
302
|
+
* The allowance used to be for a *chronically* stale light only — a wandering flame — whose
|
|
303
|
+
* dribbled cube published a new origin once every third frame, so its shadow stepped at 20 Hz
|
|
304
|
+
* where the flame wanders about once a second: reported as motion that was "really fast, not
|
|
305
|
+
* smooth, snappy, and unrealistic". Any shaded light is owed the same now, because a cube read
|
|
306
|
+
* part-way is wrong in the same place for the same reason whether or not it will be stale again.
|
|
308
307
|
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
308
|
+
* **Bounded to one light, and it has to be.** Eight braziers each taking six passes is the 48
|
|
309
|
+
* passes and 89 ms `pointShadowFacesPerFrame` exists to prevent. The first in the plan's order
|
|
310
|
+
* takes it, and `planPointShadowBakes` puts the lights that settled ahead of the ones that never
|
|
311
|
+
* do, so a flame refining its image cannot hold a correction hostage. The rest keep sampling a
|
|
312
|
+
* complete image from a few centimetres away until their turn, which is the trade
|
|
313
|
+
* `pointShadowRebakeDistance` already makes. A warm map, held but not shaded, is re-baked when a
|
|
314
|
+
* whole cube fits the budget or once it is shaded, and not before.
|
|
312
315
|
*
|
|
313
|
-
* **What would make it wrong** is a caster set heavy enough that six passes over it does not
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
+
* **What would make it wrong** is a caster set heavy enough that six passes over it does not fit
|
|
317
|
+
* the frame. Measured against a game whose whole static world is about 8,500 triangles in a
|
|
318
|
+
* handful of draws; a world an order of magnitude heavier wants `pointShadowFacesPerFrame`
|
|
316
319
|
* raised or this given up, and `gpuTiming` is how to tell which.
|
|
317
320
|
*/
|
|
318
|
-
let
|
|
321
|
+
let refreshSpent = false;
|
|
319
322
|
for (let i = 0; i < planned && faces > 0; i++) {
|
|
320
323
|
const slot = scratch.order[i] ?? -1;
|
|
321
324
|
/*
|
|
@@ -328,13 +331,20 @@ export function runPointShadowBakes<M extends StaleCheck, L extends BakeableLigh
|
|
|
328
331
|
if (light === undefined || map === undefined) continue;
|
|
329
332
|
|
|
330
333
|
const sampled = slot < pool.sampledCount;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
334
|
+
if (scratch.ready[slot] !== 1) {
|
|
335
|
+
/* No image to spoil: a few faces a frame, or all six at once for one being shaded. */
|
|
336
|
+
const urgent = sampled && !urgentSpent;
|
|
337
|
+
if (urgent) urgentSpent = true;
|
|
338
|
+
faces -= bake(map, light, staticCasters, urgent ? faceCount : faces);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (faces >= faceCount) {
|
|
342
|
+
faces -= bake(map, light, staticCasters, faceCount);
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (!sampled || refreshSpent) continue;
|
|
346
|
+
refreshSpent = true;
|
|
347
|
+
faces -= bake(map, light, staticCasters, faceCount);
|
|
338
348
|
}
|
|
339
349
|
|
|
340
350
|
/*
|
|
@@ -36,6 +36,19 @@ export { FACE_COUNT, POINT_SHADOW_NEAR };
|
|
|
36
36
|
|
|
37
37
|
const scratchViewProj = mat4.create();
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* What a bake asks of the renderer about glass, after each face's opaque depth.
|
|
41
|
+
*
|
|
42
|
+
* **Asked before it is drawn** because the answer decides whether the face binds a glass target at
|
|
43
|
+
* all: a face that offers no glass and holds none from a previous owner does no glass work.
|
|
44
|
+
*/
|
|
45
|
+
export interface PointGlassDraw {
|
|
46
|
+
/** Whether the face just drawn offered any glass. */
|
|
47
|
+
offered(): boolean;
|
|
48
|
+
/** Draw that glass: its depth, or what it lets through lit from the bake origin. */
|
|
49
|
+
draw(pass: 'depth' | 'tint', x: number, y: number, z: number): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
export class PointShadowMap {
|
|
40
53
|
readonly size: number;
|
|
41
54
|
/**
|
|
@@ -163,6 +176,8 @@ export class PointShadowMap {
|
|
|
163
176
|
near = POINT_SHADOW_NEAR,
|
|
164
177
|
sourceRadius = DEFAULT_SOURCE_RADIUS,
|
|
165
178
|
maxFaces = FACE_COUNT,
|
|
179
|
+
/** The face's glass, when glass shadows are on: `PointGlassDraw`. */
|
|
180
|
+
glass: PointGlassDraw | null = null,
|
|
166
181
|
): number {
|
|
167
182
|
/*
|
|
168
183
|
* What to render now, **and what to render it for**. A bake in flight owns the parameters
|
|
@@ -220,6 +235,7 @@ export class PointShadowMap {
|
|
|
220
235
|
* next face's pass does care about.
|
|
221
236
|
*/
|
|
222
237
|
array.resolveFace(gl, this.layer, face, plan.range, plan.near);
|
|
238
|
+
if (glass !== null) this.bakeGlassFace(gl, array, glass, face, plan);
|
|
223
239
|
}
|
|
224
240
|
|
|
225
241
|
gl.enable(gl.CULL_FACE);
|
|
@@ -237,5 +253,30 @@ export class PointShadowMap {
|
|
|
237
253
|
* needing disposal is exactly the shape of a leak nobody notices. The array is disposed by the
|
|
238
254
|
* renderer that built it.
|
|
239
255
|
*/
|
|
256
|
+
/**
|
|
257
|
+
* The face's glass, twice: where the nearest pane is, then what the panes let through.
|
|
258
|
+
*
|
|
259
|
+
* A face that offers no glass still clears a region a previous owner's glass is in — the depth to
|
|
260
|
+
* the far plane and the tint to white, from scratches cleared and not drawn into — because a pool
|
|
261
|
+
* slot changes hands with its layer.
|
|
262
|
+
*/
|
|
263
|
+
private bakeGlassFace(
|
|
264
|
+
gl: WebGL2RenderingContext,
|
|
265
|
+
array: PointShadowTarget,
|
|
266
|
+
glass: PointGlassDraw,
|
|
267
|
+
face: number,
|
|
268
|
+
plan: FaceRange,
|
|
269
|
+
): void {
|
|
270
|
+
if (!array.hasGlass) return;
|
|
271
|
+
const offered = glass.offered();
|
|
272
|
+
if (!offered && !array.heldGlass(this.layer, face)) return;
|
|
273
|
+
array.beginGlassFace(gl);
|
|
274
|
+
if (offered) glass.draw('depth', plan.x, plan.y, plan.z);
|
|
275
|
+
array.resolveGlassFace(gl, this.layer, face, plan.range, plan.near);
|
|
276
|
+
array.beginTintFace(gl);
|
|
277
|
+
if (offered) glass.draw('tint', plan.x, plan.y, plan.z);
|
|
278
|
+
array.resolveTintFace(gl, this.layer, face, offered);
|
|
279
|
+
}
|
|
280
|
+
|
|
240
281
|
dispose(_gl: WebGL2RenderingContext): void {}
|
|
241
282
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
|
|
13
|
+
export function frameOfChange(serial: number, inFrame: boolean): number {
|
|
14
|
+
return inFrame ? serial : serial + 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function keepsPrevious(lastChange: number, frame: number): boolean {
|
|
18
|
+
return lastChange !== frame;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Slots `0 … n-1` pair with last frame's; a slot past last frame's count is new and has no past. */
|
|
22
|
+
export function pairedInstances(previousCount: number, count: number): number {
|
|
23
|
+
return Math.min(previousCount, count);
|
|
24
|
+
}
|
|
@@ -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.
|
|
@@ -51,12 +56,57 @@ function smoothstep(edge0: number, edge1: number, x: number): number {
|
|
|
51
56
|
return t * t * (3 - 2 * t);
|
|
52
57
|
}
|
|
53
58
|
|
|
59
|
+
/**
|
|
60
|
+
* The band over which three texels stop being one plane: how far their two steps in inverse depth
|
|
61
|
+
* fail to cancel, as a share of the two together. None of it is a plane at any angle; all of it is an
|
|
62
|
+
* edge — one side steps, the other does not — or a sliver, both stepping the same way.
|
|
63
|
+
*
|
|
64
|
+
* **A share and not a size**, which is the lesson of the rods in `resolve.test.ts`: a part a
|
|
65
|
+
* hundredth of the depth in front of a wall passes the depth test, and a normal differenced across it
|
|
66
|
+
* still leans by its height over a texel's width — nearly edge-on where texels are small. What would
|
|
67
|
+
* make this wrong is a curved surface judged at its apex, where both steps lean one way and the
|
|
68
|
+
* normal is sound; there it only gives the normals less say, which the depth test does not need.
|
|
69
|
+
*/
|
|
70
|
+
export const PLANAR_FROM = 0.25;
|
|
71
|
+
export const PLANAR_TO = 0.5;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* How sure it is that `centre` and its neighbours either side along one axis lie on one plane, one to
|
|
75
|
+
* zero. Inverse depth is affine across a plane on screen, so a plane's two steps cancel whatever its
|
|
76
|
+
* angle. One where a neighbour is missing — zero, off the picture or undrawn — or where there is no
|
|
77
|
+
* step to compare: nothing to judge by, so a normal keeps the say it always had.
|
|
78
|
+
*/
|
|
79
|
+
export function planarity(before: number, centre: number, after: number): number {
|
|
80
|
+
if (!(before > 0) || !(centre > 0) || !(after > 0)) return 1;
|
|
81
|
+
const a = 1 / after - 1 / centre;
|
|
82
|
+
const b = 1 / before - 1 / centre;
|
|
83
|
+
const spread = Math.abs(a) + Math.abs(b);
|
|
84
|
+
if (!(spread > 0)) return 1;
|
|
85
|
+
return 1 - smoothstep(PLANAR_FROM, PLANAR_TO, Math.abs(a + b) / spread);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* How far a surface's depth moves across one texel along an axis, relative to the depth: the smaller
|
|
90
|
+
* of the steps to its two neighbours, because beside a silhouette one of them is the surface behind.
|
|
91
|
+
* Zero at a sliver — nearer or farther than both, so each step is to another surface — and one
|
|
92
|
+
* neighbour's step where only one is there.
|
|
93
|
+
*/
|
|
94
|
+
export function surfaceSlope(before: number, centre: number, after: number): number {
|
|
95
|
+
const a = after > 0 ? after - centre : Number.NaN;
|
|
96
|
+
const b = before > 0 ? before - centre : Number.NaN;
|
|
97
|
+
if (Number.isNaN(a)) return Number.isNaN(b) ? 0 : Math.abs(b) / centre;
|
|
98
|
+
if (Number.isNaN(b)) return Math.abs(a) / centre;
|
|
99
|
+
if (a * b > 0) return 0;
|
|
100
|
+
return Math.min(Math.abs(a), Math.abs(b)) / centre;
|
|
101
|
+
}
|
|
102
|
+
|
|
54
103
|
/**
|
|
55
104
|
* One where the history is trustworthy, zero where it is not, and continuous between.
|
|
56
105
|
*
|
|
57
106
|
* `currDepth` is the view depth this surface should have had last frame; `histDepth` the view depth
|
|
58
107
|
* last frame held where it lands, or zero where it landed off the screen; `motionLength` the motion's
|
|
59
|
-
* length in uv; `normalDot` the cosine between the two frames' world normals there
|
|
108
|
+
* length in uv; `normalDot` the cosine between the two frames' world normals there; `slope` the
|
|
109
|
+
* surface's `surfaceSlope`, which widens the tolerance one for one.
|
|
60
110
|
*/
|
|
61
111
|
export function disocclusionWeight(
|
|
62
112
|
currDepth: number,
|
|
@@ -64,12 +114,13 @@ export function disocclusionWeight(
|
|
|
64
114
|
motionLength: number,
|
|
65
115
|
normalDot: number,
|
|
66
116
|
params: DisocclusionParams,
|
|
117
|
+
slope = 0,
|
|
67
118
|
): number {
|
|
68
119
|
if (!(currDepth > 0) || !(histDepth > 0) || !(motionLength >= 0) || Number.isNaN(normalDot)) {
|
|
69
120
|
return 0;
|
|
70
121
|
}
|
|
71
122
|
const disagreement = Math.abs(currDepth - histDepth) / currDepth;
|
|
72
|
-
const tolerance = params.depthTolerance + params.motionScale * motionLength;
|
|
123
|
+
const tolerance = params.depthTolerance + params.motionScale * motionLength + slope;
|
|
73
124
|
const depth = 1 - smoothstep(tolerance, 2 * tolerance, disagreement);
|
|
74
125
|
const normal = smoothstep(params.normalFloor, params.normalCeiling, normalDot);
|
|
75
126
|
return depth * normal;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two decisions about one draw in a reconstructing frame, and why they are the same set.
|
|
3
|
+
*
|
|
4
|
+
* A blended draw goes **late** — after the reconstruction, at output resolution, unjittered — and a
|
|
5
|
+
* mover's motion is **tracked**, only for draws that land in the picture being reconstructed. A
|
|
6
|
+
* planar mirror and a probe bake are other pictures drawn through the same verbs, and the overlay
|
|
7
|
+
* after `endFrame` is past the picture altogether; routing any of them late, or counting a mirrored
|
|
8
|
+
* draw as a mover's second draw of the frame, would be wrong in the way that reads as a ghost.
|
|
9
|
+
*
|
|
10
|
+
* Plain booleans rather than a state object, so the per-draw call allocates nothing.
|
|
11
|
+
*/
|
|
12
|
+
export function routesLate(
|
|
13
|
+
reconstructing: boolean,
|
|
14
|
+
mirror: boolean,
|
|
15
|
+
probe: boolean,
|
|
16
|
+
presented: boolean,
|
|
17
|
+
): boolean {
|
|
18
|
+
return reconstructing && !mirror && !probe && !presented;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The same set as `routesLate`, named for the other reader. */
|
|
22
|
+
export function tracksMotion(
|
|
23
|
+
reconstructing: boolean,
|
|
24
|
+
mirror: boolean,
|
|
25
|
+
probe: boolean,
|
|
26
|
+
presented: boolean,
|
|
27
|
+
): boolean {
|
|
28
|
+
return routesLate(reconstructing, mirror, probe, presented);
|
|
29
|
+
}
|