@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.
Files changed (218) hide show
  1. package/CHANGELOG.json +72 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +5 -1
  4. package/dist/index.js +7 -0
  5. package/dist/render/backend/api.d.ts +17 -11
  6. package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
  7. package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
  8. package/dist/render/backend/webgl2/renderer.d.ts +82 -8
  9. package/dist/render/backend/webgl2/renderer.js +373 -105
  10. package/dist/render/backend/webgpu/buffers.d.ts +19 -1
  11. package/dist/render/backend/webgpu/buffers.js +27 -1
  12. package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
  13. package/dist/render/backend/webgpu/depthPass.js +2 -1
  14. package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
  15. package/dist/render/backend/webgpu/flatPass.js +20 -1
  16. package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
  17. package/dist/render/backend/webgpu/glassTintPass.js +205 -0
  18. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
  19. package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
  20. package/dist/render/backend/webgpu/instanced.d.ts +12 -1
  21. package/dist/render/backend/webgpu/instanced.js +20 -1
  22. package/dist/render/backend/webgpu/latePass.d.ts +26 -0
  23. package/dist/render/backend/webgpu/latePass.js +133 -0
  24. package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
  25. package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
  26. package/dist/render/backend/webgpu/motionPass.js +491 -0
  27. package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
  28. package/dist/render/backend/webgpu/pipelineCache.js +11 -1
  29. package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
  30. package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
  31. package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
  32. package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
  33. package/dist/render/backend/webgpu/postPass.d.ts +21 -15
  34. package/dist/render/backend/webgpu/postPass.js +17 -4
  35. package/dist/render/backend/webgpu/renderer.d.ts +181 -54
  36. package/dist/render/backend/webgpu/renderer.js +1011 -394
  37. package/dist/render/backend/webgpu/scatterPass.js +2 -1
  38. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
  39. package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
  40. package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
  41. package/dist/render/backend/webgpu/snapshotMips.js +52 -0
  42. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
  43. package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
  44. package/dist/render/flameLight.js +15 -2
  45. package/dist/render/glass.d.ts +45 -0
  46. package/dist/render/glass.js +39 -0
  47. package/dist/render/glassCasters.d.ts +40 -0
  48. package/dist/render/glassCasters.js +84 -0
  49. package/dist/render/glassShadow.d.ts +78 -0
  50. package/dist/render/glassShadow.js +125 -0
  51. package/dist/render/globalMediumPass.d.ts +2 -3
  52. package/dist/render/globalMediumPass.js +2 -8
  53. package/dist/render/gpudriven/lit.d.ts +12 -0
  54. package/dist/render/gpudriven/lit.js +23 -0
  55. package/dist/render/gpudriven/materialTable.d.ts +15 -5
  56. package/dist/render/gpudriven/materialTable.js +26 -6
  57. package/dist/render/gpudriven/pipeline.d.ts +1 -1
  58. package/dist/render/gpudriven/pipeline.js +4 -3
  59. package/dist/render/gpudriven/sunGlass.d.ts +35 -0
  60. package/dist/render/gpudriven/sunGlass.js +89 -0
  61. package/dist/render/lightBudget.d.ts +17 -5
  62. package/dist/render/lightBudget.js +17 -5
  63. package/dist/render/pointGlassTint.d.ts +59 -0
  64. package/dist/render/pointGlassTint.js +198 -0
  65. package/dist/render/pointShadowArray.d.ts +56 -3
  66. package/dist/render/pointShadowArray.js +127 -16
  67. package/dist/render/pointShadowBudget.js +51 -41
  68. package/dist/render/pointShadowMap.d.ts +23 -1
  69. package/dist/render/pointShadowMap.js +27 -1
  70. package/dist/render/recon/changeFrames.d.ts +15 -0
  71. package/dist/render/recon/changeFrames.js +21 -0
  72. package/dist/render/recon/disocclusion.d.ts +36 -3
  73. package/dist/render/recon/disocclusion.js +56 -4
  74. package/dist/render/recon/lateRouting.d.ts +14 -0
  75. package/dist/render/recon/lateRouting.js +18 -0
  76. package/dist/render/recon/mover.d.ts +57 -0
  77. package/dist/render/recon/mover.js +86 -0
  78. package/dist/render/recon/resolve.js +95 -12
  79. package/dist/render/renderQuality.d.ts +35 -0
  80. package/dist/render/renderQuality.js +7 -0
  81. package/dist/render/rendererHarness.js +12 -0
  82. package/dist/render/sceneTarget.d.ts +9 -1
  83. package/dist/render/sceneTarget.js +59 -13
  84. package/dist/render/shaders/depth.d.ts +16 -1
  85. package/dist/render/shaders/depth.js +85 -9
  86. package/dist/render/shaders/flat/directionalShadow.js +91 -6
  87. package/dist/render/shaders/flat/glassTint.d.ts +6 -0
  88. package/dist/render/shaders/flat/glassTint.js +25 -0
  89. package/dist/render/shaders/flat/index.d.ts +13 -0
  90. package/dist/render/shaders/flat/index.js +4 -0
  91. package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
  92. package/dist/render/shaders/flat/lampGlass.js +183 -0
  93. package/dist/render/shaders/flat/main.d.ts +1 -1
  94. package/dist/render/shaders/flat/main.js +115 -15
  95. package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
  96. package/dist/render/shaders/flat/pointShadow.js +2 -0
  97. package/dist/render/shaders/flat/preamble.js +21 -6
  98. package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
  99. package/dist/render/shaders/generated/depth.wgsl.js +227 -4
  100. package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
  101. package/dist/render/shaders/generated/flat.wgsl.js +376 -328
  102. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
  103. package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
  104. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
  105. package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
  106. package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
  107. package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
  108. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +31 -6
  109. package/dist/render/shaders/generated/temporalResolve.wgsl.js +31 -6
  110. package/dist/render/shaders/globalMedium.js +6 -6
  111. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
  112. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
  113. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
  114. package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
  115. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
  116. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
  117. package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
  118. package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
  119. package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
  120. package/dist/render/shaders/lightVolume.js +6 -6
  121. package/dist/render/shaders/octahedralResolve.d.ts +8 -0
  122. package/dist/render/shaders/octahedralResolve.js +48 -17
  123. package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
  124. package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
  125. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  126. package/dist/render/shaders/recon/motion.wgsl.js +84 -0
  127. package/dist/render/shaders/recon/resolve.wgsl.d.ts +1 -1
  128. package/dist/render/shaders/recon/resolve.wgsl.js +112 -8
  129. package/dist/render/shaders/temporalResolve.js +133 -5
  130. package/dist/render/shadowCasters.d.ts +20 -1
  131. package/dist/render/shadowCasters.js +15 -1
  132. package/dist/render/shadowMap.d.ts +54 -5
  133. package/dist/render/shadowMap.js +190 -36
  134. package/dist/render/sunGlassTint.d.ts +25 -0
  135. package/dist/render/sunGlassTint.js +79 -0
  136. package/dist/render/temporalAa.d.ts +145 -0
  137. package/dist/render/temporalAa.js +190 -0
  138. package/dist/render/temporalPass.d.ts +16 -14
  139. package/dist/render/temporalPass.js +59 -14
  140. package/dist/render/translucentQueue.d.ts +6 -0
  141. package/dist/render/translucentQueue.js +15 -1
  142. package/package.json +6 -6
  143. package/src/index.ts +10 -0
  144. package/src/render/backend/api.ts +15 -9
  145. package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
  146. package/src/render/backend/webgl2/renderer.ts +422 -109
  147. package/src/render/backend/webgpu/buffers.ts +50 -2
  148. package/src/render/backend/webgpu/depthPass.ts +2 -1
  149. package/src/render/backend/webgpu/flatPass.ts +22 -1
  150. package/src/render/backend/webgpu/glassTintPass.ts +244 -0
  151. package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
  152. package/src/render/backend/webgpu/instanced.ts +21 -1
  153. package/src/render/backend/webgpu/latePass.ts +152 -0
  154. package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
  155. package/src/render/backend/webgpu/motionPass.ts +535 -0
  156. package/src/render/backend/webgpu/pipelineCache.ts +12 -1
  157. package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
  158. package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
  159. package/src/render/backend/webgpu/postPass.ts +34 -4
  160. package/src/render/backend/webgpu/renderer.ts +1178 -425
  161. package/src/render/backend/webgpu/scatterPass.ts +2 -1
  162. package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
  163. package/src/render/backend/webgpu/snapshotMips.ts +80 -0
  164. package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
  165. package/src/render/flameLight.ts +15 -2
  166. package/src/render/glass.ts +74 -0
  167. package/src/render/glassCasters.ts +164 -0
  168. package/src/render/glassShadow.ts +153 -0
  169. package/src/render/globalMediumPass.ts +4 -11
  170. package/src/render/gpudriven/lit.ts +31 -0
  171. package/src/render/gpudriven/materialTable.ts +28 -6
  172. package/src/render/gpudriven/pipeline.ts +4 -3
  173. package/src/render/gpudriven/sunGlass.ts +117 -0
  174. package/src/render/lightBudget.ts +19 -5
  175. package/src/render/pointGlassTint.ts +250 -0
  176. package/src/render/pointShadowArray.ts +188 -22
  177. package/src/render/pointShadowBudget.ts +49 -39
  178. package/src/render/pointShadowMap.ts +41 -0
  179. package/src/render/recon/changeFrames.ts +24 -0
  180. package/src/render/recon/disocclusion.ts +54 -3
  181. package/src/render/recon/lateRouting.ts +29 -0
  182. package/src/render/recon/mover.ts +118 -0
  183. package/src/render/recon/resolve.ts +137 -10
  184. package/src/render/renderQuality.ts +47 -0
  185. package/src/render/rendererHarness.ts +12 -0
  186. package/src/render/sceneTarget.ts +66 -12
  187. package/src/render/shaders/depth.ts +109 -9
  188. package/src/render/shaders/flat/directionalShadow.ts +97 -6
  189. package/src/render/shaders/flat/glassTint.ts +25 -0
  190. package/src/render/shaders/flat/index.ts +17 -0
  191. package/src/render/shaders/flat/lampGlass.ts +185 -0
  192. package/src/render/shaders/flat/main.ts +115 -15
  193. package/src/render/shaders/flat/pointShadow.ts +2 -0
  194. package/src/render/shaders/flat/preamble.ts +21 -6
  195. package/src/render/shaders/generated/depth.wgsl.ts +234 -4
  196. package/src/render/shaders/generated/flat.wgsl.ts +376 -328
  197. package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
  198. package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
  199. package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
  200. package/src/render/shaders/generated/temporalResolve.wgsl.ts +31 -6
  201. package/src/render/shaders/globalMedium.ts +6 -6
  202. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
  203. package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
  204. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
  205. package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
  206. package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
  207. package/src/render/shaders/lightVolume.ts +6 -6
  208. package/src/render/shaders/octahedralResolve.ts +50 -17
  209. package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
  210. package/src/render/shaders/recon/motion.wgsl.ts +84 -0
  211. package/src/render/shaders/recon/resolve.wgsl.ts +112 -8
  212. package/src/render/shaders/temporalResolve.ts +141 -5
  213. package/src/render/shadowCasters.ts +28 -1
  214. package/src/render/shadowMap.ts +231 -37
  215. package/src/render/sunGlassTint.ts +86 -0
  216. package/src/render/temporalAa.ts +227 -0
  217. package/src/render/temporalPass.ts +88 -12
  218. 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 layers this array holds: the point pool, the two live transition maps, and two more
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 layers this array holds: the point pool, the two live transition maps, and two more
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.createTexture();
72
- if (texture === null)
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.texture, 0, layer);
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.texture);
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, so a run that reset to zero
181
- * lost the tracking allowance there and the map dropped back to two faces a frame. The shadow
182
- * then travelled smoothly through the fast part of the wander and stepped through the slow
183
- * part, which is not motion and not stillness: it reads as a vibration. Reported that way,
184
- * against a brazier, once the tracking allowance was in.
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
- * So the run is hysteresis. It climbs to twice the threshold, which buys a light that has
187
- * earned the allowance three quiet frames before it loses it, and decays one frame at a time
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
- * **One wandering light finishes its cube in the frame it started it**, the same allowance a
202
- * light with no image at all gets, and for a reason that turns out to be the same one: a map
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
- * A cold light cannot be sampled *at all* until its six faces are in, so it takes the whole
206
- * cube at once. A chronically stale one can be sampled throughout and is wrong in a quieter
207
- * way: `planBake` pins the origin for the whole of a resumed bake, so six faces spread over
208
- * three frames means the published origin steps once every third frame. The shader shoots from
209
- * that origin, so the shadow it draws steps with it — reported on a brazier as motion that was
210
- * "really fast, not smooth, snappy, and unrealistic", which is a 20 Hz sample of a flame that
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
- * Given the cube in one frame the origin moves every frame and the shadow travels instead.
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
- * **Bounded to one light, and it has to be.** Eight braziers each taking six passes is the 48
216
- * passes and 89 ms `pointShadowFacesPerFrame` exists to prevent. One is the light being shaded
217
- * that most recently went stale, which is the one somebody is standing next to; the rest keep
218
- * dribbling under the ordinary budget, and their shadows step rather than travel. That is the
219
- * right way round: a shadow you are looking at moves, and a shadow across the square does not
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
- * It is spent *after* the cold and settling groups, because `planPointShadowBakes` orders the
223
- * chronic ones last — so a light arriving in range still gets its first image ahead of a flame
224
- * refining one it already has.
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
- * fit the frame. Measured against a game whose whole static world is about 8,500 triangles in
228
- * a handful of draws; a world an order of magnitude heavier wants `pointShadowFacesPerFrame`
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 trackingSpent = false;
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
- const cold = sampled && scratch.ready[slot] === 0;
245
- const urgent = cold && !urgentSpent;
246
- /* A light being shaded whose map will be stale again next frame however it is served. */
247
- const tracking = !cold && sampled && scratch.chronic[slot] === 1 && !trackingSpent;
248
- if (urgent)
249
- urgentSpent = true;
250
- if (tracking)
251
- trackingSpent = true;
252
- faces -= bake(map, light, staticCasters, urgent || tracking ? faceCount : faces);
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): 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