@driftengine/core 4.4.2 → 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 (193) hide show
  1. package/CHANGELOG.json +46 -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 +79 -8
  9. package/dist/render/backend/webgl2/renderer.js +360 -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 +2 -12
  34. package/dist/render/backend/webgpu/renderer.d.ts +171 -54
  35. package/dist/render/backend/webgpu/renderer.js +944 -390
  36. package/dist/render/backend/webgpu/scatterPass.js +2 -1
  37. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
  38. package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
  39. package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
  40. package/dist/render/backend/webgpu/snapshotMips.js +52 -0
  41. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
  42. package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
  43. package/dist/render/glass.d.ts +45 -0
  44. package/dist/render/glass.js +39 -0
  45. package/dist/render/glassCasters.d.ts +40 -0
  46. package/dist/render/glassCasters.js +84 -0
  47. package/dist/render/glassShadow.d.ts +78 -0
  48. package/dist/render/glassShadow.js +125 -0
  49. package/dist/render/globalMediumPass.d.ts +2 -3
  50. package/dist/render/globalMediumPass.js +2 -8
  51. package/dist/render/gpudriven/lit.d.ts +12 -0
  52. package/dist/render/gpudriven/lit.js +23 -0
  53. package/dist/render/gpudriven/materialTable.d.ts +15 -5
  54. package/dist/render/gpudriven/materialTable.js +26 -6
  55. package/dist/render/gpudriven/pipeline.d.ts +1 -1
  56. package/dist/render/gpudriven/pipeline.js +4 -3
  57. package/dist/render/gpudriven/sunGlass.d.ts +35 -0
  58. package/dist/render/gpudriven/sunGlass.js +89 -0
  59. package/dist/render/lightBudget.d.ts +17 -5
  60. package/dist/render/lightBudget.js +17 -5
  61. package/dist/render/pointGlassTint.d.ts +59 -0
  62. package/dist/render/pointGlassTint.js +198 -0
  63. package/dist/render/pointShadowArray.d.ts +56 -3
  64. package/dist/render/pointShadowArray.js +127 -16
  65. package/dist/render/pointShadowMap.d.ts +23 -1
  66. package/dist/render/pointShadowMap.js +27 -1
  67. package/dist/render/recon/changeFrames.d.ts +15 -0
  68. package/dist/render/recon/changeFrames.js +21 -0
  69. package/dist/render/recon/lateRouting.d.ts +14 -0
  70. package/dist/render/recon/lateRouting.js +18 -0
  71. package/dist/render/recon/mover.d.ts +57 -0
  72. package/dist/render/recon/mover.js +86 -0
  73. package/dist/render/renderQuality.d.ts +35 -0
  74. package/dist/render/renderQuality.js +7 -0
  75. package/dist/render/rendererHarness.js +12 -0
  76. package/dist/render/sceneTarget.d.ts +7 -1
  77. package/dist/render/sceneTarget.js +58 -12
  78. package/dist/render/shaders/depth.d.ts +16 -1
  79. package/dist/render/shaders/depth.js +85 -9
  80. package/dist/render/shaders/flat/directionalShadow.js +91 -6
  81. package/dist/render/shaders/flat/glassTint.d.ts +6 -0
  82. package/dist/render/shaders/flat/glassTint.js +25 -0
  83. package/dist/render/shaders/flat/index.d.ts +13 -0
  84. package/dist/render/shaders/flat/index.js +4 -0
  85. package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
  86. package/dist/render/shaders/flat/lampGlass.js +183 -0
  87. package/dist/render/shaders/flat/main.d.ts +1 -1
  88. package/dist/render/shaders/flat/main.js +115 -15
  89. package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
  90. package/dist/render/shaders/flat/pointShadow.js +2 -0
  91. package/dist/render/shaders/flat/preamble.js +21 -6
  92. package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
  93. package/dist/render/shaders/generated/depth.wgsl.js +227 -4
  94. package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
  95. package/dist/render/shaders/generated/flat.wgsl.js +376 -328
  96. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
  97. package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
  98. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
  99. package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
  100. package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
  101. package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
  102. package/dist/render/shaders/globalMedium.js +6 -6
  103. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
  104. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
  105. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
  106. package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
  107. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
  108. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
  109. package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
  110. package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
  111. package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
  112. package/dist/render/shaders/lightVolume.js +6 -6
  113. package/dist/render/shaders/octahedralResolve.d.ts +8 -0
  114. package/dist/render/shaders/octahedralResolve.js +48 -17
  115. package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
  116. package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
  117. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  118. package/dist/render/shaders/recon/motion.wgsl.js +84 -0
  119. package/dist/render/shadowCasters.d.ts +20 -1
  120. package/dist/render/shadowCasters.js +15 -1
  121. package/dist/render/shadowMap.d.ts +54 -5
  122. package/dist/render/shadowMap.js +190 -36
  123. package/dist/render/sunGlassTint.d.ts +25 -0
  124. package/dist/render/sunGlassTint.js +79 -0
  125. package/dist/render/translucentQueue.d.ts +6 -0
  126. package/dist/render/translucentQueue.js +15 -1
  127. package/package.json +6 -6
  128. package/src/index.ts +10 -0
  129. package/src/render/backend/api.ts +15 -9
  130. package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
  131. package/src/render/backend/webgl2/renderer.ts +401 -108
  132. package/src/render/backend/webgpu/buffers.ts +50 -2
  133. package/src/render/backend/webgpu/depthPass.ts +2 -1
  134. package/src/render/backend/webgpu/flatPass.ts +22 -1
  135. package/src/render/backend/webgpu/glassTintPass.ts +244 -0
  136. package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
  137. package/src/render/backend/webgpu/instanced.ts +21 -1
  138. package/src/render/backend/webgpu/latePass.ts +152 -0
  139. package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
  140. package/src/render/backend/webgpu/motionPass.ts +535 -0
  141. package/src/render/backend/webgpu/pipelineCache.ts +12 -1
  142. package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
  143. package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
  144. package/src/render/backend/webgpu/renderer.ts +1108 -421
  145. package/src/render/backend/webgpu/scatterPass.ts +2 -1
  146. package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
  147. package/src/render/backend/webgpu/snapshotMips.ts +80 -0
  148. package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
  149. package/src/render/glass.ts +74 -0
  150. package/src/render/glassCasters.ts +164 -0
  151. package/src/render/glassShadow.ts +153 -0
  152. package/src/render/globalMediumPass.ts +4 -11
  153. package/src/render/gpudriven/lit.ts +31 -0
  154. package/src/render/gpudriven/materialTable.ts +28 -6
  155. package/src/render/gpudriven/pipeline.ts +4 -3
  156. package/src/render/gpudriven/sunGlass.ts +117 -0
  157. package/src/render/lightBudget.ts +19 -5
  158. package/src/render/pointGlassTint.ts +250 -0
  159. package/src/render/pointShadowArray.ts +188 -22
  160. package/src/render/pointShadowMap.ts +41 -0
  161. package/src/render/recon/changeFrames.ts +24 -0
  162. package/src/render/recon/lateRouting.ts +29 -0
  163. package/src/render/recon/mover.ts +118 -0
  164. package/src/render/renderQuality.ts +47 -0
  165. package/src/render/rendererHarness.ts +12 -0
  166. package/src/render/sceneTarget.ts +58 -11
  167. package/src/render/shaders/depth.ts +109 -9
  168. package/src/render/shaders/flat/directionalShadow.ts +97 -6
  169. package/src/render/shaders/flat/glassTint.ts +25 -0
  170. package/src/render/shaders/flat/index.ts +17 -0
  171. package/src/render/shaders/flat/lampGlass.ts +185 -0
  172. package/src/render/shaders/flat/main.ts +115 -15
  173. package/src/render/shaders/flat/pointShadow.ts +2 -0
  174. package/src/render/shaders/flat/preamble.ts +21 -6
  175. package/src/render/shaders/generated/depth.wgsl.ts +234 -4
  176. package/src/render/shaders/generated/flat.wgsl.ts +376 -328
  177. package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
  178. package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
  179. package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
  180. package/src/render/shaders/globalMedium.ts +6 -6
  181. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
  182. package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
  183. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
  184. package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
  185. package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
  186. package/src/render/shaders/lightVolume.ts +6 -6
  187. package/src/render/shaders/octahedralResolve.ts +50 -17
  188. package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
  189. package/src/render/shaders/recon/motion.wgsl.ts +84 -0
  190. package/src/render/shadowCasters.ts +28 -1
  191. package/src/render/shadowMap.ts +231 -37
  192. package/src/render/sunGlassTint.ts +86 -0
  193. package/src/render/translucentQueue.ts +20 -1
package/CHANGELOG.json CHANGED
@@ -1,4 +1,50 @@
1
1
  [
2
+ {
3
+ "version": "4.5.0",
4
+ "date": "2026-09-29",
5
+ "entries": [
6
+ {
7
+ "kind": "added",
8
+ "short": "Moving objects stay sharp under reconstruction: name each with createMover() and it no longer leaves a ghost or a doubled edge.",
9
+ "text": "**A reconstruction can follow what moves, when the draw says what it is.** DriftTR reprojects each pixel by where its surface was last frame, and it knew that only for a draw handed last frame's model matrix. A consumer that passed none, which was every consumer measured, got the camera's motion for everything: right for the world, wrong for anything that moved, so a mover trailed a ghost and a doubled edge.\n\n`createMover()` makes an identity for one moving object, and `drawMesh` takes it where it took last frame's matrix. The renderer keeps that object's last model and, for a skinned draw, its last joint palette, so a bending limb reprojects as well as a sliding body. A matrix works exactly as it did. A mover drawn twice in one frame is two objects wearing one identity: the second draw is warned about once and records no motion.\n\nMeasured on `demo/dev/ghost.html` against a native four-sample frame, trail pixels without and with a mover: a slide 6,602 → 1,150, a spin 8,599 → 3,012, a skinned limb 11,648 → 1,518. **A cut is still the consumer's to say**: call `renderer.cameraCut()` where the view jumps, or the first frames after it blend the old view in. Only a reconstruction reads any of this, and reconstruction is WebGPU's."
10
+ },
11
+ {
12
+ "kind": "added",
13
+ "short": "Glass: a pane shows what is behind it, blurred as far as it is frosted, keeps its own highlight, and glows with the lights behind it.",
14
+ "text": "**`TranslucentMeshOptions.glass`: `{ transmission, frost, tint }`.** A pane shows what is behind it by how much it lets through less what Schlick's Fresnel reflects at the view angle, keeps its own lighting and highlight, blurs what it shows by reading the level of the frame copy's mip chain its frost picks, and glows with every light behind it (the sun, lamps, rectangles and DriftLight) in proportion to its frost. It is two-sided and symmetric: seen from behind it passes and glows with the light beyond it as it does from in front. A glass caster writes no depth, so a lantern's own light leaves the lantern.\n\nA model says so three ways. `.drft` 1.19 carries the three fields; the glTF reader maps `KHR_materials_transmission`, `KHR_materials_diffuse_transmission` and `KHR_materials_volume`, rough glass arriving as frosted glass; and `DrftLoaderOptions.surface` can declare a file's opaque pane glass, which lands on `DrftPart.glass`. Both backends agree to a level on a bought courtyard's lantern at night, its panes from 95 to 246."
15
+ },
16
+ {
17
+ "kind": "added",
18
+ "short": "Light through glass takes the glass's colour: sunlight and lamplight through a stained or frosted pane land tinted, and frost spreads the colour.",
19
+ "text": "**Coloured shadows, for every light that casts one, on both backends.** Light through a pane takes `transmission × (1 − F) × tint`: the sun's static and moving maps, pooled and live lamps, and rectangles. Each map gains a glass depth layer, the nearest pane, and an RGBA8 tint, every pane on the ray multiplied together with its clarity. A receiver is tinted only behind a pane; its outline comes from depth-tested taps and its colour from taps spread by frost and read at the tint's matching mip level, unmixed from the ground around the pane, so a frosted patch is softer and passes the light a clear one does. Measured on `demo/dev/glassShadow.html`, both backends within 0.4 levels: clear sun patches 149/56 against 149.1/55.6 derived by hand, a lamp's 47.2/30.0 against 47.1/29.8; frost moves the light it passes by 0.1 to 1.4%.\n\n**`RenderQualityOptions.glassShadows`** is `'full'`, `'half'` (the tints at half size) or `'off'`. Nothing is allocated until a glass caster reaches a map; then, at `'full'`, 78 MB for the sun's two maps and 9.8 MB a lamp layer, roughly halved at `'half'`. `'off'` casts nothing through glass and compiles the lookup out of the lit shader, which a world that never casts through glass should say: a lookup that finds no glass still costs the lit pass its registers, 0.27 ms on WebGPU and 0.42 ms on WebGL2 at 720p on a bought courtyard at night. Where glass does cast, that courtyard's lit pass costs 2.3 ms more on either backend, its lamps being enclosed in frosted glass.\n\n**What it gives up**: a pane is thin, so a solid of glass under-darkens and bends light once; DriftLight's summed far field and the probes' bounce are not tinted; frost spreads colour only inside the opaque frame's shadow; and a second copy of a pane's triangles is a second pane, so two-sided glass is declared with `doubleSided`. The core bundle grows 91.6 KB gzipped, nearly all of it the lit shaders' generated WGSL."
20
+ },
21
+ {
22
+ "kind": "added",
23
+ "short": "The GPU-driven renderer draws glass, and its sunlight through glass takes the glass's colour.",
24
+ "text": "**`GpuDrivenMaterial.glass` takes the same `{ transmission, frost, tint }`.** A glass material is drawn in the renderer's blended half with the forward renderer's see-through, frost, Fresnel and sun glow, reading a mip chain of the opaque colour, and `GpuDrivenShadowOptions.glass` gives its sun a third light-space cut for glass, drawn into a glass layer and a tint as the forward sun's is. Its arithmetic is checked on the device against the TypeScript reference by `scripts/gpu-parity.mjs`, and its clear patches measure the forward renderer's 149/56. Its glass shadow ignores cutouts, as its opaque shadow does."
25
+ },
26
+ {
27
+ "kind": "changed",
28
+ "short": "Under reconstruction, glass, captions, particles and other see-through things are drawn at full resolution, sharp and without trails.",
29
+ "text": "**While reconstructing, every blended draw lands after the upscale, at output resolution.** A translucent pane, a caption, a spark or a plume has no one depth a reprojection can trust, so drawn into the reconstructed picture it trailed and softened with whatever was behind it. Blended meshes, particles, plumes, bolts, lines, film, caustics and world text now draw in a late pass on the output-size frame, unjittered and depth-tested against the upscaled depth; refraction and order-independent panes read the reconstructed picture and land in it. On the ghost page a moving translucent pane's trail went from 5,751 pixels to 20, and particles' from 2,464 to 0.\n\n**Light volumes stay reconstructed**: their march reads render-size depth and is among the most expensive passes in a frame, so they keep the render size. Without a reconstruction nothing changes, and every published scene draws identically, to the pixel on WebGPU."
30
+ },
31
+ {
32
+ "kind": "fixed",
33
+ "short": "Crowds of instanced objects and meshes rewritten every frame no longer leave a ghost behind them under reconstruction.",
34
+ "text": "**Two kinds of motion had no way to say where they had been.** An instanced batch carried one placement per instance and no previous one, and a mesh rewritten with `updateMesh` moved its vertices under a draw that looked still, so both were reprojected by the camera's motion alone and ghosted under reconstruction whatever the consumer passed. Each now keeps last frame's state beside this frame's, slot for slot and vertex for vertex, and needs nothing from the caller: the batch and the mesh are their own identity. An instanced crowd's trail on the ghost page went from 8,961 pixels to 1,340."
35
+ },
36
+ {
37
+ "kind": "fixed",
38
+ "short": "WebGL2: a pane in front of a reflection probe no longer turns the whole frame grey, and a double-sided instanced batch draws both of its sides.",
39
+ "text": "**Three WebGL2 defects, each reached first by glass.** The copy of the frame that refraction reads rebound the scene's framebuffer on its way out rather than the caller's, so a pane drawn into a reflection probe's face attached the probe's next face to the scene and the whole frame went flat grey. WebGL2 also took that copy inside a probe bake at all, which WebGPU has always refused: the copy is of the frame on screen, not of the face. And an instanced batch never turned culling off for a double-sided material, so its far faces were missing on WebGL2 alone; 28 of one bought courtyard's 53 double-sided materials are instanced."
40
+ },
41
+ {
42
+ "kind": "note",
43
+ "short": "DrftPart.glass is a new required field: code that builds its own DrftPart adds glass: null.",
44
+ "text": "**`DrftPart.glass` is required**, `null` for a part that is not glass, so a consumer that constructs a `DrftPart` itself, a test stub for instance, adds `glass: null` or no longer typechecks. Parts the loader makes carry it already.\n\n**And a reconstruction is still softer than native on geometry that never moves.** A mover fixes motion, not that: on the ghost page a still edge at a reconstruction of 1.5 measures about 8 in edge error against a native frame's 3, which is the reconstruction's own resolution rather than anything a consumer passes."
45
+ }
46
+ ]
47
+ },
2
48
  {
3
49
  "version": "4.4.2",
4
50
  "date": "2026-09-28",
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The runtime everything needs: renderer, loop, camera, input, geometry. Collision and dynamics live in [`@driftengine/physics`](../physics/README.md), which this package depends on and re-exports whole, so reaching for them through this barrel works as it always did.
4
4
 
5
- **Cost: 795.8 KB gzipped, importing `createRenderer`.** Measured by `scripts/size-gate.test.mjs`, which fails if it drifts more than
5
+ **Cost: 887.4 KB gzipped, importing `createRenderer`.** Measured by `scripts/size-gate.test.mjs`, which fails if it drifts more than
6
6
  3% — the number is a fact about the build rather than a claim in a document.
7
7
 
8
8
  Part of [DriftEngine](../../README.md). See [`docs/ARCHITECTURE.md`](../../docs/ARCHITECTURE.md)
package/dist/index.d.ts CHANGED
@@ -71,6 +71,10 @@ export type { PassAttachmentOptions, PassTarget } from './render/passTarget.ts';
71
71
  export { generateMipChain, mipLevelCount, type MipPipelines, } from './render/backend/webgpu/surfaceTexturePass.ts';
72
72
  export { createPassAttachment } from './render/passTarget.ts';
73
73
  export { jitterClip } from './render/recon/jitter.ts';
74
+ export { resolveGlass } from './render/glass.ts';
75
+ export type { GlassOptions, ResolvedGlass } from './render/glass.ts';
76
+ export { createMover } from './render/recon/mover.ts';
77
+ export type { Mover } from './render/recon/mover.ts';
74
78
  export type { FrameResource } from './render/frame/index.ts';
75
79
  export type { OcclusionOptions } from './render/occlusion.ts';
76
80
  export { OcclusionBuffer } from './render/occlusion.ts';
@@ -193,7 +197,7 @@ export type { PrefilterQuality } from './render/prefilterEnvMap.ts';
193
197
  * `ProbeBakeOptions`, which is the whole of the reasoning.
194
198
  */
195
199
  export type { ProbeBakeOptions } from './render/reflectionProbe.ts';
196
- export type { DirectionalShadowDepthLayers, OutputTransform, RenderQuality, RenderQualityOptions, ShadowFilterTaps, WaterReflectionFilterTaps, } from './render/renderQuality.ts';
200
+ export type { DirectionalShadowDepthLayers, GlassShadows, OutputTransform, RenderQuality, RenderQualityOptions, ShadowFilterTaps, WaterReflectionFilterTaps, } from './render/renderQuality.ts';
197
201
  export { MAX_POINT_LIGHTS, SURFACE_TEXTURE_UNIT } from './render/lightBudget.ts';
198
202
  export { MAX_CLUSTERED_LIGHTS } from './render/clusteredLights.ts';
199
203
  export { SurfaceTexture } from './render/surfaceTexture.ts';
package/dist/index.js CHANGED
@@ -86,6 +86,13 @@ export { createPassAttachment } from './render/passTarget.js';
86
86
  * resolve finds its geometry where the renderer's own verbs put theirs.
87
87
  */
88
88
  export { jitterClip } from './render/recon/jitter.js';
89
+ /*
90
+ * Which object a draw is, for a reconstruction that has to find where it was last frame. See
91
+ * `RendererApi.drawMesh`.
92
+ */
93
+ /* Glass: a pane that shows what is behind it, clear or frosted. See `TranslucentMeshOptions.glass`. */
94
+ export { resolveGlass } from './render/glass.js';
95
+ export { createMover } from './render/recon/mover.js';
89
96
  export { OcclusionBuffer } from './render/occlusion.js';
90
97
  /*
91
98
  * `Renderer` above stays exported and is not deprecated. `createRenderer` is the way to get
@@ -10,6 +10,7 @@ import type { TextStyle } from '../textLayout.ts';
10
10
  import type { SdfFont } from '../sdfFont.ts';
11
11
  import type { SdfTextStyle } from '../sdfTextLayout.ts';
12
12
  import type { FrameTimer } from './timer.ts';
13
+ import type { Mover } from '../recon/mover.ts';
13
14
  /**
14
15
  * Geometry on the device, as something a consumer holds and hands back.
15
16
  *
@@ -466,18 +467,23 @@ export type RendererApi = Omit<{
466
467
  /** Draw a handle this renderer made. Passing one from the other backend is a caller error. */
467
468
  drawMesh(mesh: MeshHandle, model: ReadonlyMat4, depthLayer?: number, tint?: Vec3 | null,
468
469
  /**
469
- * Where this mesh's surface was last frame, for a reconstruction's motion target.
470
- *
471
- * **Only a reconstruction reads it**, and only on WebGPU: it is what turns a moving object's
472
- * stale history from a smear into a reprojection. A draw that says nothing gets the camera's
473
- * motion, derived from the depth, which is exactly right for anything that did not move and
474
- * wrong in proportion to how far a mover moved. Null and omitted mean the same thing.
475
- *
476
- * It is the *model* matrix this mesh was drawn with last frame, not a velocity: the pass
477
- * transforms each vertex twice and lets the perspective divide do the rest, so a rotation and a
478
- * scale are as correct as a translation.
470
+ * Where this mesh's surface was last frame, for a reconstruction's motion target: a `Mover`,
471
+ * or last frame's model matrix.
472
+ *
473
+ * **A `Mover` is the ordinary answer.** Make one per moving object with `createMover()` and pass
474
+ * it with every draw of that object; the renderer keeps last frame's model and, for a skinned
475
+ * draw, last frame's palette. A matrix still works exactly as it always has: the *model* matrix
476
+ * this mesh was drawn with last frame, not a velocity, so a rotation and a scale are as correct
477
+ * as a translation. Null and omitted mean the same thing: the camera's motion, derived from the
478
+ * depth, which is exactly right for anything that did not move and wrong in proportion to how
479
+ * far a mover moved.
480
+ *
481
+ * **Only a reconstruction reads it**, and only on WebGPU. A mover drawn twice in one frame is
482
+ * two objects wearing one identity: the second draw is warned about once and records no motion.
483
+ * Instanced batches and meshes rewritten with `updateMesh` need none — the batch and the mesh
484
+ * are their own identity.
479
485
  */
480
- previousModel?: ReadonlyMat4 | null): void;
486
+ previousModel?: ReadonlyMat4 | Mover | null): void;
481
487
  /** Release the geometry behind a handle. */
482
488
  disposeMesh(mesh: MeshHandle): void;
483
489
  /**
@@ -0,0 +1,13 @@
1
+ export interface GlassTintProgram {
2
+ readonly program: WebGLProgram;
3
+ readonly uniforms: Record<string, WebGLUniformLocation>;
4
+ }
5
+ export declare class GlassTintPrograms {
6
+ readonly rigid: GlassTintProgram;
7
+ readonly instanced: GlassTintProgram;
8
+ readonly skinned: GlassTintProgram;
9
+ readonly cutout: GlassTintProgram;
10
+ readonly instancedCutout: GlassTintProgram;
11
+ constructor(gl: WebGL2RenderingContext);
12
+ dispose(gl: WebGL2RenderingContext): void;
13
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The five programs a pane casts its colour through, one per way a caster is drawn, compiled the
3
+ * first time glass casts — so a scene with no glass compiles none of them.
4
+ *
5
+ * The vertex stages are the depth pass's with the world position handed on (`depth.ts`), so a
6
+ * pane's colour lands exactly where its depth does; the fragment is `paneTexel` for the device.
7
+ */
8
+ import { compileProgram, uniformLocations } from '../../shader.js';
9
+ import { GLASS_TINT_CUTOUT_FRAG, GLASS_TINT_CUTOUT_VERT, GLASS_TINT_FRAG, GLASS_TINT_INSTANCED_CUTOUT_VERT, GLASS_TINT_INSTANCED_VERT, GLASS_TINT_SKINNED_VERT, GLASS_TINT_VERT, } from '../../shaders/depth.js';
10
+ export class GlassTintPrograms {
11
+ rigid;
12
+ instanced;
13
+ skinned;
14
+ cutout;
15
+ instancedCutout;
16
+ constructor(gl) {
17
+ const build = (vert, frag, label) => {
18
+ const program = compileProgram(gl, vert, frag, label);
19
+ return { program, uniforms: uniformLocations(gl, program, label) };
20
+ };
21
+ this.rigid = build(GLASS_TINT_VERT, GLASS_TINT_FRAG, 'glassTint');
22
+ this.instanced = build(GLASS_TINT_INSTANCED_VERT, GLASS_TINT_FRAG, 'glassTint.instanced');
23
+ this.skinned = build(GLASS_TINT_SKINNED_VERT, GLASS_TINT_FRAG, 'glassTint.skinned');
24
+ this.cutout = build(GLASS_TINT_CUTOUT_VERT, GLASS_TINT_CUTOUT_FRAG, 'glassTint.cutout');
25
+ this.instancedCutout = build(GLASS_TINT_INSTANCED_CUTOUT_VERT, GLASS_TINT_CUTOUT_FRAG, 'glassTint.instancedCutout');
26
+ }
27
+ dispose(gl) {
28
+ for (const { program } of [
29
+ this.rigid,
30
+ this.instanced,
31
+ this.skinned,
32
+ this.cutout,
33
+ this.instancedCutout,
34
+ ]) {
35
+ gl.deleteProgram(program);
36
+ }
37
+ }
38
+ }
@@ -1,4 +1,6 @@
1
1
  import { FrameBudget } from '../budget.ts';
2
+ import type { Mover } from '../../recon/mover.ts';
3
+ import type { GlassOptions } from '../../glass.ts';
2
4
  import type { ReadonlyMat4 } from 'gl-matrix';
3
5
  import type { Camera } from '../../camera.ts';
4
6
  import type { PickHit, PickableSource } from '../../pickable.ts';
@@ -521,6 +523,13 @@ export interface TranslucentMeshOptions {
521
523
  * clear, and it is the whole difference between this and a flat tint.
522
524
  */
523
525
  readonly thicknessM?: number;
526
+ /**
527
+ * Glass: the pane shows what is behind it, by how much it lets through and how little it reflects
528
+ * at the view angle, blurred by how frosted it is, tinted, and glowing with the lights behind it.
529
+ * It keeps its own lighting and highlight, which `refraction` alone replaces. Absent, or letting
530
+ * nothing through, is not glass. See `glass.ts`; `refraction` still bends what a pane shows.
531
+ */
532
+ readonly glass?: GlassOptions;
524
533
  /**
525
534
  * Whether this draw writes depth. `true`, which is what it has always done, unless a caller
526
535
  * says otherwise.
@@ -819,6 +828,8 @@ export declare class WebGL2Renderer implements RendererApi {
819
828
  /** Whether a palette is currently chosen. Distinct from whether one was ever uploaded. */
820
829
  private skinPaletteSet;
821
830
  private readonly emptyTexture2D;
831
+ /** One glass resolution reused by every draw, so a pane allocates nothing. See `glass.ts`. */
832
+ private readonly glassScratch;
822
833
  /** DriftLight's stand-ins while no field is whole: no brick anywhere, and no light. */
823
834
  private readonly emptyDriftIndex;
824
835
  private readonly emptyDriftAtlas;
@@ -1008,9 +1019,8 @@ export declare class WebGL2Renderer implements RendererApi {
1008
1019
  private readonly depthInstancedCutoutUniforms;
1009
1020
  private readonly scatterDepthProgram;
1010
1021
  private readonly scatterDepthUniforms;
1011
- private readonly shadowMap;
1012
- private readonly peeledShadowMap;
1013
- private readonly dynamicShadowMap;
1022
+ /** The sun's static, moving and peeled maps, as layers of one array. See `shadowMap.ts`. */
1023
+ private readonly sunShadows;
1014
1024
  private readonly pointShadows;
1015
1025
  /**
1016
1026
  * The layers a world's casting rectangles hold, above the point pool in the same array.
@@ -1181,7 +1191,7 @@ export declare class WebGL2Renderer implements RendererApi {
1181
1191
  /** Non-zero while the drawing buffer is pinned; see `lockDrawingBuffer`. */
1182
1192
  private lockedWidth;
1183
1193
  private lockedHeight;
1184
- private activeShadowMap;
1194
+ private activeSunLayer;
1185
1195
  private reflectionPassActive;
1186
1196
  private reflectionReadyThisFrame;
1187
1197
  private reflectionAtmosphereY;
@@ -1203,7 +1213,7 @@ export declare class WebGL2Renderer implements RendererApi {
1203
1213
  *
1204
1214
  * **Not whether the map exists.** That was the test here, on the belief that a GL texture is
1205
1215
  * born reading as the far plane; it is born zeroed, and a scene that built the layer and never
1206
- * peeled it sampled an occluder at the light. `ShadowMap` now clears itself at birth, so an
1216
+ * peeled it sampled an occluder at the light. `SunShadowArray` clears itself at birth, so an
1207
1217
  * unfilled layer would read as lit either way — this is what keeps the two backends one
1208
1218
  * decision, and a frame that does not peel from paying a fetch per tap for nothing.
1209
1219
  */
@@ -1229,6 +1239,21 @@ export declare class WebGL2Renderer implements RendererApi {
1229
1239
  * Built once at construction, arrow properties closing over `this`: a sink created
1230
1240
  * per pass would allocate six objects per light per frame.
1231
1241
  */
1242
+ /** What the sun's light keeps through glass, made the first time glass casts: `sunGlassTint.ts`. */
1243
+ private sunGlassTint;
1244
+ /** The five programs a pane casts its colour through, compiled the first time glass casts. */
1245
+ private glassTintPrograms;
1246
+ /** True while the glass list is replayed into the glass depth layer through `casterSink`. */
1247
+ private drawingGlassDepth;
1248
+ /** How many times a shadow array grew its glass layers: once per family, and never again. */
1249
+ private sunGlassRebuilds;
1250
+ /** The light a tint pass is drawn for, and one pane's colour and clarity: uploaded per entry. */
1251
+ private readonly glassLight;
1252
+ private readonly glassPane;
1253
+ /** The glass the open shadow pass was offered, kept for its own passes: `glassCasters.ts`. */
1254
+ private readonly glassCasters;
1255
+ /** One resolution every glass caster is read through, so recording one allocates nothing. */
1256
+ private readonly glassCasterScratch;
1232
1257
  private readonly casterSink;
1233
1258
  /**
1234
1259
  * What a cutout depth draw needs beyond the plain one: the pass's matrix and peel, the material's
@@ -1236,6 +1261,13 @@ export declare class WebGL2Renderer implements RendererApi {
1236
1261
  */
1237
1262
  private bindCutoutDepth;
1238
1263
  private readonly drawPointShadowFace;
1264
+ /**
1265
+ * A lamp's glass, after each face's opaque depth: `PointShadowMap.bake` asks and binds, this
1266
+ * draws, through the same sinks the sun's glass takes and in the face's own matrix.
1267
+ */
1268
+ private readonly pointGlassDraw;
1269
+ /** The glass hook a bake is handed: none at all when glass shadows are off. */
1270
+ private get pointGlass();
1239
1271
  /**
1240
1272
  * Viewport in CSS pixels rather than device pixels.
1241
1273
  *
@@ -1843,6 +1875,21 @@ export declare class WebGL2Renderer implements RendererApi {
1843
1875
  * consumer's, which is also where the placements are.
1844
1876
  */
1845
1877
  private submitInstanced;
1878
+ /**
1879
+ * Bind what a translucent draw shows through it: the frame's copy, refraction's bend and absorption,
1880
+ * and glass. Returns whether the copy is bound, and so whether `unbindSeeThrough` owes it back.
1881
+ *
1882
+ * **A glass draw takes the copy even when it does not bend**, since a pane shows what is behind it
1883
+ * either way, and asks for the copy's mip chain, which a frosted pane reads blurred. A null copy
1884
+ * leaves both off, so the pane shades as ordinary translucency rather than sampling black.
1885
+ *
1886
+ * **Refused inside a probe's face or a mirror**, as WebGPU's `takeRefractSnapshot` refuses: the
1887
+ * copy is of the frame drawn to the screen, so a pane there would show the wrong picture, and
1888
+ * taking it mid-bake is how a probe's next face came to be attached to the scene's framebuffer.
1889
+ */
1890
+ private bindSeeThrough;
1891
+ /** Put back what `bindSeeThrough` bound: the binding outlives its frame (the 2026-08-27 rule). */
1892
+ private unbindSeeThrough;
1846
1893
  disposeMesh(mesh: Mesh): void;
1847
1894
  /**
1848
1895
  * Release a resource this renderer created, for every kind that needs the context back.
@@ -2329,6 +2376,25 @@ export declare class WebGL2Renderer implements RendererApi {
2329
2376
  */
2330
2377
  drawSceneCasters(casters: ShadowCasters): void;
2331
2378
  drawShadowCasters(casters: ShadowCasters): void;
2379
+ /**
2380
+ * Draw the pass's glass twice: where the nearest pane is, and what the panes let through.
2381
+ *
2382
+ * After the opaque casters, in the pass's own light matrix. A layer that has held glass is
2383
+ * redrawn even when this pass offered none, so glass that has gone takes its colour with it. The
2384
+ * first glass grows the array (`SunShadowArray.growForGlass`) and makes the tint target.
2385
+ */
2386
+ private flushSunGlass;
2387
+ /** The glass list into the glass depth layer: `casterSink` itself, with glass let through. */
2388
+ private readonly glassDepthSink;
2389
+ /** One pane's tint uniforms, and its cutout where it has one. */
2390
+ private bindGlassTint;
2391
+ /**
2392
+ * The glass list into the tint layer, through the tint programs, **culling nothing**: a light
2393
+ * crosses a pane whichever way the pane faces, and a closed glass object has two surfaces on a
2394
+ * ray, so every one counts. What that asks of a model is that a pane be one surface — two-sided
2395
+ * glass says so with `doubleSided`, and a second copy of its triangles is a second pane.
2396
+ */
2397
+ private readonly glassTintSink;
2332
2398
  endShadowPass(): void;
2333
2399
  /**
2334
2400
  * The lit program, built at the largest light budget this device will actually link.
@@ -2586,6 +2652,10 @@ export declare class WebGL2Renderer implements RendererApi {
2586
2652
  *
2587
2653
  * What it gives up is one frame of history: the frame after a cut has no blur and no temporal
2588
2654
  * blend, as the first frame of a session has none. Nothing else is reset.
2655
+ *
2656
+ * **A reconstructing renderer depends on it**: its history is the last shot's, and without this
2657
+ * call a cut blends that shot into the first frames of the next. Nothing detects a cut on the
2658
+ * caller's behalf, deliberately — a fast pan and a cut look alike from here.
2589
2659
  */
2590
2660
  cameraCut(): void;
2591
2661
  /**
@@ -3050,13 +3120,14 @@ export declare class WebGL2Renderer implements RendererApi {
3050
3120
  */
3051
3121
  tint?: Vec3 | null,
3052
3122
  /**
3053
- * Where the mesh was last frame, which this backend takes and ignores.
3123
+ * Where the mesh was last frame — a `Mover` or a matrix — which this backend takes and ignores.
3054
3124
  *
3055
3125
  * Accepted rather than absent so that one scene draws through both renderers unchanged — the
3056
3126
  * parity rule this repository is built on. Reconstruction is WebGPU's, having no compute stage
3057
- * here, so there is no motion target for this to be written into.
3127
+ * here, so there is no motion target for this to be written into, and a mover passed here is
3128
+ * neither read nor advanced.
3058
3129
  */
3059
- _previousModel?: ReadonlyMat4 | null): void;
3130
+ _previousModel?: ReadonlyMat4 | Mover | null): void;
3060
3131
  /**
3061
3132
  * Draw a mesh you can see through, in the same material as everything else by default.
3062
3133
  *