@driftengine/core 4.4.2 → 4.6.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 (339) hide show
  1. package/CHANGELOG.json +97 -0
  2. package/README.md +1 -1
  3. package/dist/geometry/boxCsg.d.ts +28 -0
  4. package/dist/geometry/boxCsg.js +139 -0
  5. package/dist/geometry/csg.d.ts +36 -0
  6. package/dist/geometry/csg.js +329 -0
  7. package/dist/geometry/meshBuilder.js +13 -0
  8. package/dist/geometry/meshConcat.js +1 -0
  9. package/dist/geometry/pixelFont.js +2 -0
  10. package/dist/geometry/solid.d.ts +61 -0
  11. package/dist/geometry/solid.js +152 -0
  12. package/dist/geometry/solidFlat.d.ts +35 -0
  13. package/dist/geometry/solidFlat.js +428 -0
  14. package/dist/geometry/solidHarness.d.ts +19 -0
  15. package/dist/geometry/solidHarness.js +66 -0
  16. package/dist/geometry/solidNormals.d.ts +17 -0
  17. package/dist/geometry/solidNormals.js +86 -0
  18. package/dist/geometry/solidRound.d.ts +41 -0
  19. package/dist/geometry/solidRound.js +323 -0
  20. package/dist/geometry/solidWriter.d.ts +16 -0
  21. package/dist/geometry/solidWriter.js +42 -0
  22. package/dist/index.d.ts +28 -3
  23. package/dist/index.js +20 -1
  24. package/dist/render/backend/api.d.ts +34 -14
  25. package/dist/render/backend/drawFog.d.ts +26 -0
  26. package/dist/render/backend/drawFog.js +30 -0
  27. package/dist/render/backend/materialChanges.d.ts +2 -1
  28. package/dist/render/backend/materialChanges.js +6 -1
  29. package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
  30. package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
  31. package/dist/render/backend/webgl2/instanced.d.ts +24 -5
  32. package/dist/render/backend/webgl2/instanced.js +72 -15
  33. package/dist/render/backend/webgl2/renderer.d.ts +201 -16
  34. package/dist/render/backend/webgl2/renderer.js +596 -224
  35. package/dist/render/backend/webgpu/buffers.d.ts +29 -1
  36. package/dist/render/backend/webgpu/buffers.js +61 -4
  37. package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
  38. package/dist/render/backend/webgpu/depthPass.js +7 -2
  39. package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
  40. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  41. package/dist/render/backend/webgpu/flatPass.d.ts +6 -1
  42. package/dist/render/backend/webgpu/flatPass.js +57 -9
  43. package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
  44. package/dist/render/backend/webgpu/glassTintPass.js +206 -0
  45. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
  46. package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
  47. package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
  48. package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
  49. package/dist/render/backend/webgpu/instanced.d.ts +23 -2
  50. package/dist/render/backend/webgpu/instanced.js +41 -4
  51. package/dist/render/backend/webgpu/latePass.d.ts +30 -0
  52. package/dist/render/backend/webgpu/latePass.js +140 -0
  53. package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
  54. package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
  55. package/dist/render/backend/webgpu/motionPass.js +491 -0
  56. package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
  57. package/dist/render/backend/webgpu/pipelineCache.js +11 -1
  58. package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
  59. package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
  60. package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
  61. package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
  62. package/dist/render/backend/webgpu/postPass.d.ts +3 -13
  63. package/dist/render/backend/webgpu/renderer.d.ts +271 -64
  64. package/dist/render/backend/webgpu/renderer.js +1395 -580
  65. package/dist/render/backend/webgpu/scatterPass.js +2 -1
  66. package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
  67. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
  68. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
  69. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
  70. package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
  71. package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
  72. package/dist/render/backend/webgpu/snapshotMips.js +52 -0
  73. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +22 -3
  74. package/dist/render/backend/webgpu/surfaceTexturePass.js +78 -39
  75. package/dist/render/driftLight/bake.d.ts +7 -0
  76. package/dist/render/driftLight/bake.js +61 -56
  77. package/dist/render/driftLight/denseField.d.ts +46 -0
  78. package/dist/render/driftLight/denseField.js +109 -0
  79. package/dist/render/driftLight/lightField.d.ts +10 -5
  80. package/dist/render/driftLight/lightField.js +25 -19
  81. package/dist/render/driftLight/presence.d.ts +50 -0
  82. package/dist/render/driftLight/presence.js +43 -0
  83. package/dist/render/driftLight/uniforms.d.ts +3 -3
  84. package/dist/render/driftLight/uniforms.js +5 -6
  85. package/dist/render/driftLight/worldLightField.d.ts +41 -0
  86. package/dist/render/driftLight/worldLightField.js +66 -0
  87. package/dist/render/fixtureAtlas.d.ts +20 -0
  88. package/dist/render/fixtureAtlas.js +49 -0
  89. package/dist/render/glass.d.ts +45 -0
  90. package/dist/render/glass.js +39 -0
  91. package/dist/render/glassCasters.d.ts +40 -0
  92. package/dist/render/glassCasters.js +84 -0
  93. package/dist/render/glassShadow.d.ts +78 -0
  94. package/dist/render/glassShadow.js +125 -0
  95. package/dist/render/globalMediumPass.d.ts +2 -3
  96. package/dist/render/globalMediumPass.js +2 -8
  97. package/dist/render/gpudriven/lit.d.ts +12 -0
  98. package/dist/render/gpudriven/lit.js +23 -0
  99. package/dist/render/gpudriven/materialTable.d.ts +15 -5
  100. package/dist/render/gpudriven/materialTable.js +26 -6
  101. package/dist/render/gpudriven/pipeline.d.ts +1 -1
  102. package/dist/render/gpudriven/pipeline.js +4 -3
  103. package/dist/render/gpudriven/sunGlass.d.ts +35 -0
  104. package/dist/render/gpudriven/sunGlass.js +89 -0
  105. package/dist/render/instanceCull.d.ts +63 -0
  106. package/dist/render/instanceCull.js +160 -0
  107. package/dist/render/instances.d.ts +15 -0
  108. package/dist/render/lightBudget.d.ts +36 -35
  109. package/dist/render/lightBudget.js +38 -37
  110. package/dist/render/lightGrid.d.ts +42 -0
  111. package/dist/render/lightGrid.js +87 -0
  112. package/dist/render/mesh.d.ts +18 -24
  113. package/dist/render/mesh.js +51 -68
  114. package/dist/render/occlusion.d.ts +10 -0
  115. package/dist/render/occlusion.js +36 -7
  116. package/dist/render/pointGlassTint.d.ts +59 -0
  117. package/dist/render/pointGlassTint.js +198 -0
  118. package/dist/render/pointLightSelection.d.ts +7 -0
  119. package/dist/render/pointLightSelection.js +29 -4
  120. package/dist/render/pointShadowArray.d.ts +56 -3
  121. package/dist/render/pointShadowArray.js +127 -16
  122. package/dist/render/pointShadowMap.d.ts +23 -1
  123. package/dist/render/pointShadowMap.js +27 -1
  124. package/dist/render/rainField.d.ts +13 -0
  125. package/dist/render/rainField.js +27 -5
  126. package/dist/render/recon/changeFrames.d.ts +15 -0
  127. package/dist/render/recon/changeFrames.js +21 -0
  128. package/dist/render/recon/lateDepth.d.ts +11 -0
  129. package/dist/render/recon/lateDepth.js +70 -0
  130. package/dist/render/recon/lateRouting.d.ts +14 -0
  131. package/dist/render/recon/lateRouting.js +18 -0
  132. package/dist/render/recon/mover.d.ts +57 -0
  133. package/dist/render/recon/mover.js +86 -0
  134. package/dist/render/recon/resolve.d.ts +34 -4
  135. package/dist/render/recon/resolve.js +161 -20
  136. package/dist/render/renderQuality.d.ts +35 -0
  137. package/dist/render/renderQuality.js +7 -0
  138. package/dist/render/rendererHarness.js +16 -0
  139. package/dist/render/sceneTarget.d.ts +9 -1
  140. package/dist/render/sceneTarget.js +60 -12
  141. package/dist/render/shaders/depth.d.ts +16 -1
  142. package/dist/render/shaders/depth.js +94 -15
  143. package/dist/render/shaders/flat/directionalShadow.js +91 -6
  144. package/dist/render/shaders/flat/driftLight.d.ts +1 -1
  145. package/dist/render/shaders/flat/driftLight.js +32 -7
  146. package/dist/render/shaders/flat/glassTint.d.ts +6 -0
  147. package/dist/render/shaders/flat/glassTint.js +25 -0
  148. package/dist/render/shaders/flat/index.d.ts +13 -0
  149. package/dist/render/shaders/flat/index.js +9 -3
  150. package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
  151. package/dist/render/shaders/flat/lampGlass.js +183 -0
  152. package/dist/render/shaders/flat/main.d.ts +1 -1
  153. package/dist/render/shaders/flat/main.js +190 -38
  154. package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
  155. package/dist/render/shaders/flat/pointShadow.js +2 -0
  156. package/dist/render/shaders/flat/preamble.js +96 -26
  157. package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
  158. package/dist/render/shaders/flat/surfaceEffects.js +152 -0
  159. package/dist/render/shaders/generated/depth.wgsl.d.ts +230 -7
  160. package/dist/render/shaders/generated/depth.wgsl.js +230 -7
  161. package/dist/render/shaders/generated/flat.wgsl.d.ts +1644 -1596
  162. package/dist/render/shaders/generated/flat.wgsl.js +1683 -1621
  163. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
  164. package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
  165. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
  166. package/dist/render/shaders/generated/lightVolume.wgsl.js +21 -17
  167. package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
  168. package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
  169. package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
  170. package/dist/render/shaders/generated/rush.wgsl.js +21 -16
  171. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
  172. package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
  173. package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
  174. package/dist/render/shaders/generated/text.wgsl.js +1 -1
  175. package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
  176. package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
  177. package/dist/render/shaders/globalMedium.js +6 -6
  178. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
  179. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
  180. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
  181. package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
  182. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
  183. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
  184. package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
  185. package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
  186. package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
  187. package/dist/render/shaders/lightVolume.js +6 -6
  188. package/dist/render/shaders/octahedralResolve.d.ts +8 -0
  189. package/dist/render/shaders/octahedralResolve.js +48 -17
  190. package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
  191. package/dist/render/shaders/recon/lateDepth.wgsl.js +69 -0
  192. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  193. package/dist/render/shaders/recon/motion.wgsl.js +84 -0
  194. package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
  195. package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
  196. package/dist/render/shaders/rush.js +11 -1
  197. package/dist/render/shaders/sdfText.d.ts +1 -1
  198. package/dist/render/shaders/sdfText.js +3 -2
  199. package/dist/render/shaders/text.d.ts +1 -1
  200. package/dist/render/shaders/text.js +18 -9
  201. package/dist/render/shadowCasters.d.ts +20 -1
  202. package/dist/render/shadowCasters.js +15 -1
  203. package/dist/render/shadowMap.d.ts +54 -5
  204. package/dist/render/shadowMap.js +190 -36
  205. package/dist/render/sunGlassTint.d.ts +25 -0
  206. package/dist/render/sunGlassTint.js +79 -0
  207. package/dist/render/surfaceEffects.d.ts +83 -0
  208. package/dist/render/surfaceEffects.js +75 -0
  209. package/dist/render/surfaceScene.d.ts +8 -0
  210. package/dist/render/surfaceScene.js +8 -0
  211. package/dist/render/surfaceTexture.d.ts +18 -2
  212. package/dist/render/surfaceTexture.js +68 -15
  213. package/dist/render/temporalAa.d.ts +25 -6
  214. package/dist/render/temporalAa.js +45 -15
  215. package/dist/render/textureSource.d.ts +30 -0
  216. package/dist/render/textureSource.js +53 -0
  217. package/dist/render/translucentQueue.d.ts +6 -0
  218. package/dist/render/translucentQueue.js +15 -1
  219. package/dist/render/vertexDefaults.js +2 -0
  220. package/dist/world/hlod.d.ts +104 -0
  221. package/dist/world/hlod.js +214 -0
  222. package/package.json +6 -6
  223. package/src/geometry/boxCsg.ts +186 -0
  224. package/src/geometry/csg.ts +384 -0
  225. package/src/geometry/meshBuilder.ts +15 -0
  226. package/src/geometry/meshConcat.ts +1 -0
  227. package/src/geometry/pixelFont.ts +2 -0
  228. package/src/geometry/solid.ts +189 -0
  229. package/src/geometry/solidFlat.ts +522 -0
  230. package/src/geometry/solidHarness.ts +83 -0
  231. package/src/geometry/solidNormals.ts +103 -0
  232. package/src/geometry/solidRound.ts +443 -0
  233. package/src/geometry/solidWriter.ts +58 -0
  234. package/src/index.ts +61 -1
  235. package/src/render/backend/api.ts +36 -11
  236. package/src/render/backend/drawFog.ts +31 -0
  237. package/src/render/backend/materialChanges.ts +9 -2
  238. package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
  239. package/src/render/backend/webgl2/instanced.ts +80 -14
  240. package/src/render/backend/webgl2/renderer.ts +700 -250
  241. package/src/render/backend/webgpu/buffers.ts +92 -5
  242. package/src/render/backend/webgpu/depthPass.ts +7 -2
  243. package/src/render/backend/webgpu/drawCommand.ts +6 -0
  244. package/src/render/backend/webgpu/flatPass.ts +62 -11
  245. package/src/render/backend/webgpu/glassTintPass.ts +245 -0
  246. package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
  247. package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
  248. package/src/render/backend/webgpu/instanced.ts +42 -4
  249. package/src/render/backend/webgpu/latePass.ts +161 -0
  250. package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
  251. package/src/render/backend/webgpu/motionPass.ts +535 -0
  252. package/src/render/backend/webgpu/pipelineCache.ts +12 -1
  253. package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
  254. package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
  255. package/src/render/backend/webgpu/renderer.ts +1671 -670
  256. package/src/render/backend/webgpu/scatterPass.ts +2 -1
  257. package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
  258. package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
  259. package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
  260. package/src/render/backend/webgpu/snapshotMips.ts +80 -0
  261. package/src/render/backend/webgpu/surfaceTexturePass.ts +91 -51
  262. package/src/render/driftLight/bake.ts +85 -62
  263. package/src/render/driftLight/denseField.ts +149 -0
  264. package/src/render/driftLight/lightField.ts +33 -18
  265. package/src/render/driftLight/presence.ts +69 -0
  266. package/src/render/driftLight/uniforms.ts +8 -9
  267. package/src/render/driftLight/worldLightField.ts +83 -0
  268. package/src/render/fixtureAtlas.ts +63 -0
  269. package/src/render/glass.ts +74 -0
  270. package/src/render/glassCasters.ts +164 -0
  271. package/src/render/glassShadow.ts +153 -0
  272. package/src/render/globalMediumPass.ts +4 -11
  273. package/src/render/gpudriven/lit.ts +31 -0
  274. package/src/render/gpudriven/materialTable.ts +28 -6
  275. package/src/render/gpudriven/pipeline.ts +4 -3
  276. package/src/render/gpudriven/sunGlass.ts +117 -0
  277. package/src/render/instanceCull.ts +205 -0
  278. package/src/render/instances.ts +16 -0
  279. package/src/render/lightBudget.ts +41 -38
  280. package/src/render/lightGrid.ts +130 -0
  281. package/src/render/mesh.ts +59 -72
  282. package/src/render/occlusion.ts +51 -7
  283. package/src/render/pointGlassTint.ts +250 -0
  284. package/src/render/pointLightSelection.ts +72 -4
  285. package/src/render/pointShadowArray.ts +188 -22
  286. package/src/render/pointShadowMap.ts +41 -0
  287. package/src/render/rainField.ts +39 -5
  288. package/src/render/recon/changeFrames.ts +24 -0
  289. package/src/render/recon/lateDepth.ts +83 -0
  290. package/src/render/recon/lateRouting.ts +29 -0
  291. package/src/render/recon/mover.ts +118 -0
  292. package/src/render/recon/resolve.ts +206 -20
  293. package/src/render/renderQuality.ts +47 -0
  294. package/src/render/rendererHarness.ts +16 -0
  295. package/src/render/sceneTarget.ts +62 -11
  296. package/src/render/shaders/depth.ts +118 -15
  297. package/src/render/shaders/flat/directionalShadow.ts +97 -6
  298. package/src/render/shaders/flat/driftLight.ts +32 -7
  299. package/src/render/shaders/flat/glassTint.ts +25 -0
  300. package/src/render/shaders/flat/index.ts +22 -3
  301. package/src/render/shaders/flat/lampGlass.ts +185 -0
  302. package/src/render/shaders/flat/main.ts +190 -38
  303. package/src/render/shaders/flat/pointShadow.ts +2 -0
  304. package/src/render/shaders/flat/preamble.ts +96 -26
  305. package/src/render/shaders/flat/surfaceEffects.ts +152 -0
  306. package/src/render/shaders/generated/depth.wgsl.ts +237 -7
  307. package/src/render/shaders/generated/flat.wgsl.ts +1685 -1621
  308. package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
  309. package/src/render/shaders/generated/lightVolume.wgsl.ts +23 -17
  310. package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
  311. package/src/render/shaders/generated/rush.wgsl.ts +21 -16
  312. package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
  313. package/src/render/shaders/generated/text.wgsl.ts +1 -1
  314. package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
  315. package/src/render/shaders/globalMedium.ts +6 -6
  316. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
  317. package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
  318. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
  319. package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
  320. package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
  321. package/src/render/shaders/lightVolume.ts +6 -6
  322. package/src/render/shaders/octahedralResolve.ts +50 -17
  323. package/src/render/shaders/recon/lateDepth.wgsl.ts +72 -0
  324. package/src/render/shaders/recon/motion.wgsl.ts +84 -0
  325. package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
  326. package/src/render/shaders/rush.ts +11 -1
  327. package/src/render/shaders/sdfText.ts +3 -2
  328. package/src/render/shaders/text.ts +18 -9
  329. package/src/render/shadowCasters.ts +28 -1
  330. package/src/render/shadowMap.ts +231 -37
  331. package/src/render/sunGlassTint.ts +86 -0
  332. package/src/render/surfaceEffects.ts +132 -0
  333. package/src/render/surfaceScene.ts +16 -0
  334. package/src/render/surfaceTexture.ts +110 -19
  335. package/src/render/temporalAa.ts +78 -14
  336. package/src/render/textureSource.ts +72 -0
  337. package/src/render/translucentQueue.ts +20 -1
  338. package/src/render/vertexDefaults.ts +2 -0
  339. package/src/world/hlod.ts +290 -0
@@ -30,6 +30,29 @@
30
30
  * the render read bicubically fills the gap. The history keeps colour and weight; the picture shown
31
31
  * is it sharpened.
32
32
  *
33
+ * **And the temporal resolve's anti-flicker, where the caller carries its records** (`temporalAa.ts`
34
+ * says why it works and what it gives up). A thread finer than a render texel is caught in some
35
+ * phases of the jitter and missed in others, and in a phase that misses it the nine texels can all
36
+ * miss it: the box collapses onto what is behind, the history is clipped out, and a still picture
37
+ * flashes at every fine edge each period. A still camera over a still scene gathers every pixel the
38
+ * same way each period, so each pixel keeps a record of this frame's gathered luma over the period,
39
+ * and once two periods running have repeated its box widens by its own spread and it keeps more of
40
+ * its history. **The record stays at the pixel rather than following the reprojection**, because the
41
+ * proof is only ever for a pixel that has not moved; it starts over wherever the history did not
42
+ * survive, and is forgotten in proportion to motion.
43
+ *
44
+ * **And a still pixel whose depth has been seen to swing is not a disocclusion.** A rail finer than
45
+ * a texel in front of a wall is caught in some phases and missed in others, so last frame can hold
46
+ * the wall where this frame holds the rail with nothing moved — and the depth test threw the history
47
+ * away, leaving the record nothing to keep. So each pixel also keeps the largest relative miss of
48
+ * last frame's depth it has seen lately, kept whatever the test says, and while still its tolerance
49
+ * widens to `DEPTH_SWING_WIDTH` of it — the temporal resolve's answer to a crack of light round a
50
+ * door, as a held peak rather than its running mean: a rail caught in one phase of eighteen misses
51
+ * once a period, and a mean has worn that miss down to a thirtieth by the next. **What it gives up**
52
+ * is the depth test's word at such a pixel while still: a surface arriving there inside the swing
53
+ * keeps the history, and the neighbourhood clip, which the unproven sample of an arrival leaves
54
+ * unwidened, is what takes it out.
55
+ *
33
56
  * **Conventions.** Images are rows from `v = 0` upward. Depths are clip depths in the matrices'
34
57
  * convention, OpenGL's here — the nearest is the smallest — and the renderer converts its reversed
35
58
  * buffer before a sample reaches this arithmetic, as the temporal resolve already does.
@@ -37,6 +60,7 @@
37
60
  import { clipToBox, rgbToYCoCg, varianceBox, yCoCgToRgb } from './clamp.js';
38
61
  import { disocclusionWeight, normalFromPositions, planarity, surfaceSlope, worldPositionFromDepth, } from './disocclusion.js';
39
62
  import { sampleBilinear, sampleHistoryBicubic5 } from './reproject.js';
63
+ import { TEMPORAL_FLICKER, flickerBlend, flickerRule, flickerWidening, freshFlicker, nextFlicker, stillness, } from '../temporalAa.js';
40
64
  /**
41
65
  * A tenth of each frame kept, as the temporal resolve keeps; a box of a standard deviation and a
42
66
  * quarter; a light sharpen. At two thirds of the output a side.
@@ -98,6 +122,12 @@ export function reconstructionWeight(dx, dy) {
98
122
  * pixel away.
99
123
  */
100
124
  export const CONFIDENT_WEIGHT = 1;
125
+ /**
126
+ * How many of its held depth swings a still pixel's depth test tolerates. The peak halves over a
127
+ * period, so the frame before the next once-a-period miss holds just over half of it: tolerating
128
+ * twice the peak passes that miss outright, and three leaves the same room again.
129
+ */
130
+ export const DEPTH_SWING_WIDTH = 3;
101
131
  const SAMPLE = new Float32Array(3);
102
132
  const GATHERED = new Float64Array(3);
103
133
  const HISTORY = new Float32Array(4);
@@ -113,6 +143,37 @@ const UP = new Float64Array(3);
113
143
  const NORMAL = new Float64Array(3);
114
144
  const PREVIOUS_NORMAL = new Float64Array(3);
115
145
  const CLIP = new Float64Array(4);
146
+ /** The last frame's reprojection, derived once a frame rather than once a pixel. */
147
+ const REPROJECTION = new Float64Array(16);
148
+ let reprojected = null;
149
+ /** `frame.reprojection`, or the product of its two matrices, multiplied once per frame. */
150
+ function reprojectionOf(frame) {
151
+ if (frame.reprojection !== undefined)
152
+ return frame.reprojection;
153
+ if (reprojected !== frame) {
154
+ reprojected = frame;
155
+ const a = frame.previousViewProj;
156
+ const b = frame.inverseViewProj;
157
+ for (let c = 0; c < 4; c += 1) {
158
+ for (let r = 0; r < 4; r += 1) {
159
+ let sum = 0;
160
+ for (let k = 0; k < 4; k += 1)
161
+ sum += a[k * 4 + r] * b[c * 4 + k];
162
+ REPROJECTION[c * 4 + r] = sum;
163
+ }
164
+ }
165
+ }
166
+ return REPROJECTION;
167
+ }
168
+ const HELD = { spread: 0, sum: 0, mean: 0, repeated: 0 };
169
+ const CARRIED = { spread: 0, sum: 0, mean: 0, repeated: 0 };
170
+ /** How far, in output pixels, the last `historyWeight` found its surface had moved. */
171
+ let moved = 0;
172
+ /** How far, relative, last frame's depth missed the one expected there; -1 where nothing was held. */
173
+ let missed = -1;
174
+ /** The rule for the last period asked for, kept: one is made per period, not per pixel. */
175
+ let rulePhases = 0;
176
+ let rule = TEMPORAL_FLICKER;
116
177
  function clampTexel(value, size) {
117
178
  return value < 0 ? 0 : value >= size ? size - 1 : value;
118
179
  }
@@ -184,7 +245,8 @@ function normalAt(depths, inverse, jitter, eye, width, height, x, y, out) {
184
245
  * was behind last frame's eye or off its picture, and otherwise what it gathered, scaled by whether it
185
246
  * is the same surface. Reads `HISTORY`; writes the history's colour there.
186
247
  */
187
- function historyWeight(frame, u, v, dx, dy) {
248
+ function historyWeight(frame, u, v, dx, dy, swing) {
249
+ missed = -1;
188
250
  if (!frame.hasHistory)
189
251
  return 0;
190
252
  const rw = frame.renderWidth;
@@ -199,27 +261,45 @@ function historyWeight(frame, u, v, dx, dy) {
199
261
  expected = frame.motion[flagAt + 2];
200
262
  }
201
263
  else {
202
- if (worldAt(frame.depth, frame.inverseViewProj, frame.jitter, rw, rh, dx, dy, WORLD) <= 0) {
264
+ /*
265
+ * **Through one matrix, last frame's view-projection times this frame's inverse**, rather than
266
+ * out to a world position and back. The two are the same arithmetic in exact numbers; in single
267
+ * precision the round trip is not the identity — a near plane a quarter of a metre from a far one
268
+ * five kilometres off — and a still camera measured a few hundredths of a pixel of motion growing
269
+ * out from the middle of the frame, which crept every still history and kept every pixel from
270
+ * the anti-flicker's stillness. The product is taken where the precision is (`reprojection`),
271
+ * and for a still camera it is the identity.
272
+ */
273
+ const du = (dx + 0.5 - frame.jitter[0]) / rw;
274
+ const dv = (dy + 0.5 - frame.jitter[1]) / rh;
275
+ const x = du * 2 - 1;
276
+ const y = dv * 2 - 1;
277
+ const z = frame.depth[dy * rw + dx];
278
+ const inverse = frame.inverseViewProj;
279
+ /* The homogeneous w of the world position: none, or behind the eye, is no surface. */
280
+ const hw = inverse[3] * x +
281
+ inverse[7] * y +
282
+ inverse[11] * z +
283
+ inverse[15];
284
+ if (!(hw > 1e-12))
203
285
  return 0;
204
- }
205
- const m = frame.previousViewProj;
286
+ const m = reprojectionOf(frame);
206
287
  for (let r = 0; r < 4; r += 1) {
207
288
  CLIP[r] =
208
- m[r] * WORLD[0] +
209
- m[4 + r] * WORLD[1] +
210
- m[8 + r] * WORLD[2] +
289
+ m[r] * x +
290
+ m[4 + r] * y +
291
+ m[8 + r] * z +
211
292
  m[12 + r];
212
293
  }
213
294
  /*
214
295
  * A surface behind last frame's eye has a w that is not positive: the disocclusion trusts no
215
296
  * such depth, so it needs no refusal of its own here.
216
297
  */
217
- expected = CLIP[3];
218
- const du = (dx + 0.5 - frame.jitter[0]) / rw;
219
- const dv = (dy + 0.5 - frame.jitter[1]) / rh;
220
- mu = (CLIP[0] / expected) * 0.5 + 0.5 - du;
221
- mv = (CLIP[1] / expected) * 0.5 + 0.5 - dv;
298
+ expected = CLIP[3] / hw;
299
+ mu = (CLIP[0] / CLIP[3]) * 0.5 + 0.5 - du;
300
+ mv = (CLIP[1] / CLIP[3]) * 0.5 + 0.5 - dv;
222
301
  }
302
+ moved = Math.hypot(mu * frame.outputWidth, mv * frame.outputHeight);
223
303
  const pu = u + mu;
224
304
  const pv = v + mv;
225
305
  if (!(pu >= 0 && pu <= 1 && pv >= 0 && pv <= 1))
@@ -284,14 +364,18 @@ function historyWeight(frame, u, v, dx, dy) {
284
364
  NORMAL[2] * PREVIOUS_NORMAL[2];
285
365
  normalDot = 1 - planar * (1 - cosine);
286
366
  }
287
- const trust = disocclusionWeight(expected, held, Math.hypot(mu, mv), normalDot, frame.disocclusion, slope);
367
+ if (held > 0 && expected > 0)
368
+ missed = Math.abs(held - expected) / expected;
369
+ /* A still pixel's own depth swing, as wide as the anti-flicker's box: see the header. */
370
+ const swung = swing > 0 ? DEPTH_SWING_WIDTH * swing * stillness(moved) : 0;
371
+ const trust = disocclusionWeight(expected, held, Math.hypot(mu, mv), normalDot, frame.disocclusion, slope + swung);
288
372
  return HISTORY[3] * trust;
289
373
  }
290
374
  /**
291
375
  * One output pixel: the history's next colour, before sharpening, and the weight it has gathered —
292
- * four values into `out`.
376
+ * four values into `out` — and, where the frame carries records, the pixel's next one into `record`.
293
377
  */
294
- export function resolvePixel(frame, ox, oy, out) {
378
+ export function resolvePixel(frame, ox, oy, out, record, swingOut) {
295
379
  const rw = frame.renderWidth;
296
380
  const rh = frame.renderHeight;
297
381
  const ow = frame.outputWidth;
@@ -339,8 +423,57 @@ export function resolvePixel(frame, ox, oy, out) {
339
423
  for (let c = 0; c < 3; c += 1)
340
424
  GATHERED[c] = GATHERED[c] / gathered;
341
425
  /* 3 and 4. The history, and the weight it still deserves. */
342
- const cap = (1 - frame.quality.alpha) / frame.quality.alpha;
343
- const kept = Math.min(cap, historyWeight(frame, u, v, dx, dy));
426
+ const alpha = frame.quality.alpha;
427
+ const records = frame.flicker;
428
+ const anti = records !== undefined && records !== null;
429
+ if (anti) {
430
+ const phases = frame.flickerPhases ?? 0;
431
+ if (phases !== rulePhases) {
432
+ rulePhases = phases;
433
+ rule = phases > 0 ? flickerRule(phases) : TEMPORAL_FLICKER;
434
+ }
435
+ }
436
+ const swings = frame.depthSwing;
437
+ const swing = anti && swings !== undefined && swings !== null ? swings[oy * ow + ox] : 0;
438
+ const weight = historyWeight(frame, u, v, dx, dy, swing);
439
+ let cap = (1 - alpha) / alpha;
440
+ /* The anti-flicker: a proven pixel's box widened by its spread, and more of its history kept. */
441
+ if (anti) {
442
+ const sample = GATHERED[0] * 0.2126 +
443
+ GATHERED[1] * 0.7152 +
444
+ GATHERED[2] * 0.0722;
445
+ const at = (oy * ow + ox) * 4;
446
+ if (weight > 0) {
447
+ HELD.spread = records[at];
448
+ HELD.sum = records[at + 1];
449
+ HELD.mean = records[at + 2];
450
+ HELD.repeated = records[at + 3];
451
+ }
452
+ else {
453
+ freshFlicker(HELD, sample, rule);
454
+ }
455
+ const still = weight > 0 ? stillness(moved) : 0;
456
+ const widen = flickerWidening(HELD, sample, still, rule);
457
+ for (let c = 0; c < 3; c += 1) {
458
+ LOW[c] = LOW[c] - widen;
459
+ HIGH[c] = HIGH[c] + widen;
460
+ }
461
+ const keep = flickerBlend(1 - alpha, HELD, sample, still, rule);
462
+ cap = keep / (1 - keep);
463
+ nextFlicker(CARRIED, HELD, sample, still, frame.periodStart === true, rule);
464
+ if (record !== undefined) {
465
+ record[0] = CARRIED.spread;
466
+ record[1] = CARRIED.sum;
467
+ record[2] = CARRIED.mean;
468
+ record[3] = CARRIED.repeated;
469
+ }
470
+ /* The peak carried on, halving over a period, whatever the test said; none where nothing was
471
+ held to miss. */
472
+ if (swingOut !== undefined) {
473
+ swingOut[0] = missed >= 0 ? Math.max(missed, swing * 0.5 ** rule.memory) : 0;
474
+ }
475
+ }
476
+ const kept = Math.min(cap, weight);
344
477
  /* 5. Clipped, mixed by weight, and the gaps filled from the render. */
345
478
  const total = kept + gathered;
346
479
  if (kept > 0) {
@@ -359,6 +492,8 @@ export function resolvePixel(frame, ox, oy, out) {
359
492
  out[3] = total;
360
493
  }
361
494
  const TAP = new Float32Array(4);
495
+ const RECORD = new Float32Array(4);
496
+ const SWING = new Float32Array(1);
362
497
  const CENTRE = new Float32Array(4);
363
498
  const AROUND = new Float32Array(12);
364
499
  const SHOWN = new Float32Array(3);
@@ -370,15 +505,21 @@ const OFFSETS = [
370
505
  ];
371
506
  /**
372
507
  * The whole frame: the next history into `history`, four floats a pixel, and the picture shown into
373
- * `shown`, three — the history sharpened against the four direct neighbours it has just been given.
508
+ * `shown`, three — the history sharpened against the four direct neighbours it has just been given —
509
+ * and, where the frame carries records, the next ones into `records`, four floats a pixel, and the
510
+ * next depth swings into `swings`, one.
374
511
  */
375
- export function resolveFrame(frame, history, shown) {
512
+ export function resolveFrame(frame, history, shown, records, swings) {
376
513
  const ow = frame.outputWidth;
377
514
  const oh = frame.outputHeight;
378
515
  for (let y = 0; y < oh; y += 1) {
379
516
  for (let x = 0; x < ow; x += 1) {
380
- resolvePixel(frame, x, y, TAP);
517
+ resolvePixel(frame, x, y, TAP, RECORD, SWING);
381
518
  history.set(TAP, (y * ow + x) * 4);
519
+ if (records !== undefined)
520
+ records.set(RECORD, (y * ow + x) * 4);
521
+ if (swings !== undefined)
522
+ swings[y * ow + x] = SWING[0];
382
523
  }
383
524
  }
384
525
  for (let y = 0; y < oh; y += 1) {
@@ -29,6 +29,8 @@ export type OutputTransform = 'none' | 'srgb' | 'aces';
29
29
  export type ShadowFilterTaps = 4 | 8 | 12;
30
30
  export type DirectionalShadowDepthLayers = 1 | 2;
31
31
  export type WaterReflectionFilterTaps = 1 | 5 | 9;
32
+ /** How light through glass is coloured: see `RenderQuality.glassShadows`. */
33
+ export type GlassShadows = 'full' | 'half' | 'off';
32
34
  export interface RenderQuality {
33
35
  /**
34
36
  * The transfer curve applied at the end of every shaded pass.
@@ -141,6 +143,25 @@ export interface RenderQuality {
141
143
  readonly lightVolumeSamples: number;
142
144
  /** Independently composited static sun depths; two preserves common overlaps. */
143
145
  readonly directionalShadowDepthLayers: DirectionalShadowDepthLayers;
146
+ /**
147
+ * Whether light through glass takes the glass's colour, and at what size.
148
+ *
149
+ * **`'full'`, the default**: every shadow a glass caster reaches gains a layer holding the depth of
150
+ * the nearest pane and a tint layer holding what the panes let through, at the size of the depth
151
+ * map beside it — 8 bytes a texel, so 32 MB for a 2048 sun map and 8 MB for a 1024 lamp layer.
152
+ * **`'half'`** halves the tint layers only (the glass depth must match its array): 20 MB and 5 MB,
153
+ * and a coloured patch softer than an opaque shadow's edge. **`'off'`** is glass before this
154
+ * existed: a pane casts nothing at all.
155
+ *
156
+ * Nothing is allocated until a glass caster first reaches a shadow, so a scene with no glass pays
157
+ * no memory and no pass whatever this says. **The lit shader is the exception**: `'full'` and
158
+ * `'half'` compile the glass lookup into it, and a lookup that finds no glass and returns at its
159
+ * first line still costs the lit pass its registers — measured on the courtyard at night, 0.27 ms
160
+ * at 720p on WebGPU, 0.42 ms on WebGL2 and 1.2 ms at 4K. `'off'` compiles none of it, so a world
161
+ * that never casts through glass is that much faster for saying so. What would make `'full'` wrong
162
+ * is a phone budget with a lot of glass; that is what `'half'` is for. See `glassShadow.ts`.
163
+ */
164
+ readonly glassShadows: GlassShadows;
144
165
  /** Horizontal world-space reach over which a directional shadow dissolves. */
145
166
  readonly directionalShadowMaxDistance: number;
146
167
  /** Maximum horizontal projection per vertical metre before low-angle shadows vanish. */
@@ -548,6 +569,20 @@ export interface RenderQuality {
548
569
  * the render is a quarter of the output and no reconstruction holds an edge through that. A
549
570
  * negative number is off, not a clamp to the bottom: it cannot mean "a little".
550
571
  *
572
+ * **What it needs from you.** A `createMover()` per moving object, passed with that object's
573
+ * draws where `drawMesh` takes last frame's matrix — the renderer keeps last frame's model and,
574
+ * for a skinned draw, last frame's pose. Instanced batches and meshes rewritten with
575
+ * `updateMesh` need nothing: the batch and the mesh are their own identity. And `cameraCut()` at
576
+ * every cut, because nothing detects one: without it a cut blends the previous shot into the
577
+ * next. Blended draws — translucent meshes, particles, plumes, film, bolts, lines, world text —
578
+ * are drawn after the upscale at the output size and need nothing either; light volumes stay in
579
+ * the reconstructed picture, being a march that keeps its history.
580
+ *
581
+ * **What it cannot give back** is the resolution it draws at: an edge that never moves is softer
582
+ * than a native frame's at any ratio, because two thirds of the pixels at 1.5 are a guess made
583
+ * sharp. Measured on `demo/dev/ghost.html` against a native frame at four samples, still edges
584
+ * err by about 8 levels where a native frame at one sample errs by 3.
585
+ *
551
586
  * `?recon=` is how it gets A/B'd in one reload.
552
587
  */
553
588
  readonly reconstruction: number;
@@ -56,6 +56,7 @@ export const DEFAULT_RENDER_QUALITY = Object.freeze({
56
56
  shadowFilterTaps: MAX_SHADOW_FILTER_TAPS,
57
57
  lightVolumeSamples: 32,
58
58
  directionalShadowDepthLayers: 2,
59
+ glassShadows: 'full',
59
60
  directionalShadowMaxDistance: 6,
60
61
  directionalShadowMaxSlope: 3,
61
62
  pointShadowRebakeDistance: 0.001,
@@ -205,6 +206,7 @@ export function resolveRenderQuality(options = {}) {
205
206
  shadowFilterTaps: options.shadowFilterTaps ?? DEFAULT_RENDER_QUALITY.shadowFilterTaps,
206
207
  lightVolumeSamples: options.lightVolumeSamples ?? DEFAULT_RENDER_QUALITY.lightVolumeSamples,
207
208
  directionalShadowDepthLayers: options.directionalShadowDepthLayers ?? DEFAULT_RENDER_QUALITY.directionalShadowDepthLayers,
209
+ glassShadows: options.glassShadows ?? DEFAULT_RENDER_QUALITY.glassShadows,
208
210
  directionalShadowMaxDistance: options.directionalShadowMaxDistance ?? DEFAULT_RENDER_QUALITY.directionalShadowMaxDistance,
209
211
  directionalShadowMaxSlope: options.directionalShadowMaxSlope ?? DEFAULT_RENDER_QUALITY.directionalShadowMaxSlope,
210
212
  pointShadowRebakeDistance: options.pointShadowRebakeDistance ?? DEFAULT_RENDER_QUALITY.pointShadowRebakeDistance,
@@ -286,6 +288,11 @@ export function resolveRenderQuality(options = {}) {
286
288
  if (quality.directionalShadowDepthLayers !== 1 && quality.directionalShadowDepthLayers !== 2) {
287
289
  throw new Error(`RenderQuality.directionalShadowDepthLayers must be 1 or 2, got ${quality.directionalShadowDepthLayers}`);
288
290
  }
291
+ if (quality.glassShadows !== 'full' &&
292
+ quality.glassShadows !== 'half' &&
293
+ quality.glassShadows !== 'off') {
294
+ throw new Error(`RenderQuality.glassShadows must be 'full', 'half' or 'off', got ${String(quality.glassShadows)}`);
295
+ }
289
296
  if (quality.plumeNoiseOctaves > 3) {
290
297
  throw new Error(`RenderQuality.plumeNoiseOctaves must be <= 3, got ${quality.plumeNoiseOctaves}`);
291
298
  }
@@ -29,6 +29,10 @@ export function recordingGl(options = {}) {
29
29
  const programFragments = new WeakMap();
30
30
  const calls = [];
31
31
  const constants = {
32
+ /* The canvas's size, as a real context reports its drawing buffer; a function here made every
33
+ jitter and every per-pixel offset divided by it a NaN. */
34
+ drawingBufferWidth: 640,
35
+ drawingBufferHeight: 480,
32
36
  ACTIVE_UNIFORMS: 0x8b86,
33
37
  ACTIVE_ATTRIBUTES: 0x8b89,
34
38
  COMPILE_STATUS: 0x8b81,
@@ -86,6 +90,18 @@ export function recordingGl(options = {}) {
86
90
  COLOR_BUFFER_BIT: 0x00004000,
87
91
  /* Named so a test can tell a mesh that declared itself deforming from one that did not. */
88
92
  ARRAY_BUFFER: 0x8892,
93
+ /* Named so a blend a pass sets can be read back as numbers: glass multiplies what it lets
94
+ through, and a test that cannot tell DST_COLOR from a recorder cannot tell it multiplies. */
95
+ ZERO: 0,
96
+ DST_ALPHA: 0x0304,
97
+ DST_COLOR: 0x0306,
98
+ FUNC_ADD: 0x8006,
99
+ COLOR: 0x1800,
100
+ DEPTH: 0x1801,
101
+ /* Named so a copy can be read as from-and-to: a blit's two framebuffers are the whole claim. */
102
+ READ_FRAMEBUFFER: 0x8ca8,
103
+ DRAW_FRAMEBUFFER: 0x8ca9,
104
+ BLEND: 0x0be2,
89
105
  STATIC_DRAW: 0x88e4,
90
106
  DYNAMIC_DRAW: 0x88e8,
91
107
  };
@@ -72,6 +72,8 @@ export declare class SceneTarget {
72
72
  private colorCopyFramebuffer;
73
73
  /** Whether this frame has filled it. One snapshot serves every refracting draw in a frame. */
74
74
  private colorCopyFilled;
75
+ /** Whether this frame's copy has its mip chain, which frosted glass reads. See `snapshotColor`. */
76
+ private colorCopyMips;
75
77
  /**
76
78
  * The colour grade, as a 3D texture on unit 4, and the table it was built from.
77
79
  *
@@ -221,7 +223,11 @@ export declare class SceneTarget {
221
223
  * Null where the copy could not be made, and a caller must read that as "do not refract" rather
222
224
  * than as a black texture — refracting against black paints every pane the colour of a hole.
223
225
  */
224
- snapshotColor(afresh?: boolean): WebGLTexture | null;
226
+ snapshotColor(afresh?: boolean,
227
+ /** Fill the copy's mip chain as well, once a frame: what frosted glass reads. */
228
+ mips?: boolean): WebGLTexture | null;
229
+ /** The copy's mip chain from its level 0, once per copy. */
230
+ private fillColorCopyMips;
225
231
  snapshotDepth(
226
232
  /**
227
233
  * Take the copy again even though one has already been taken this frame.
@@ -287,6 +293,8 @@ export declare class SceneTarget {
287
293
  /** The projection's x and y scales, which turn a world radius into a screen one. */
288
294
  readonly projScale: Float32Array;
289
295
  readonly invProjection: Float32Array;
296
+ /** Where the frame's jitter put the occlusion, in uv: see the shader's `uAoOffset`. */
297
+ readonly offset?: Float32Array;
290
298
  }, bloom?: {
291
299
  readonly strength: number;
292
300
  /** In scene units, which is why this wants `keepsRange`. See `BloomPass`. */
@@ -76,6 +76,8 @@ export class SceneTarget {
76
76
  colorCopyFramebuffer = null;
77
77
  /** Whether this frame has filled it. One snapshot serves every refracting draw in a frame. */
78
78
  colorCopyFilled = false;
79
+ /** Whether this frame's copy has its mip chain, which frosted glass reads. See `snapshotColor`. */
80
+ colorCopyMips = false;
79
81
  /**
80
82
  * The colour grade, as a 3D texture on unit 4, and the table it was built from.
81
83
  *
@@ -241,13 +243,25 @@ export class SceneTarget {
241
243
  if (this.colorCopy === null)
242
244
  this.colorCopy = gl.createTexture();
243
245
  gl.bindTexture(gl.TEXTURE_2D, this.colorCopy);
244
- if (this.floatColor) {
245
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, width, height, 0, gl.RGBA, gl.HALF_FLOAT, null);
246
- }
247
- else {
248
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
246
+ /*
247
+ * **Every level, so glass can read a blurrier one.** Frosted glass reads the copy at a level its
248
+ * frost picks, and a texture sampled through a mipmapping filter with a level missing is
249
+ * incomplete and reads black — so the chain is allocated here, once per size, and filled only on
250
+ * a frame that draws glass (`snapshotColor`). Plain refraction reads level 0, as it always has.
251
+ */
252
+ const levels = Math.floor(Math.log2(Math.max(width, height))) + 1;
253
+ for (let level = 0; level < levels; level += 1) {
254
+ const w = Math.max(1, width >> level);
255
+ const h = Math.max(1, height >> level);
256
+ if (this.floatColor) {
257
+ gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA16F, w, h, 0, gl.RGBA, gl.HALF_FLOAT, null);
258
+ }
259
+ else {
260
+ gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
261
+ }
249
262
  }
250
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
263
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, levels - 1);
264
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
251
265
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
252
266
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
253
267
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
@@ -359,6 +373,7 @@ export class SceneTarget {
359
373
  /* Last frame's snapshot is last frame's scene. See `snapshotDepth`. */
360
374
  this.depthCopyFilled = false;
361
375
  this.colorCopyFilled = false;
376
+ this.colorCopyMips = false;
362
377
  this.bind();
363
378
  }
364
379
  /**
@@ -449,15 +464,22 @@ export class SceneTarget {
449
464
  * Null where the copy could not be made, and a caller must read that as "do not refract" rather
450
465
  * than as a black texture — refracting against black paints every pane the colour of a hole.
451
466
  */
452
- snapshotColor(afresh = false) {
467
+ snapshotColor(afresh = false,
468
+ /** Fill the copy's mip chain as well, once a frame: what frosted glass reads. */
469
+ mips = false) {
453
470
  if (this.colorCopy === null || this.colorCopyFramebuffer === null)
454
471
  return null;
455
- if (this.colorCopyFilled && !afresh)
472
+ if (this.colorCopyFilled && !afresh) {
473
+ if (mips)
474
+ this.fillColorCopyMips();
456
475
  return this.colorCopy;
476
+ }
457
477
  const { gl } = this;
458
478
  const source = this.msaaFramebuffer ?? this.framebuffer;
459
479
  if (source === null)
460
480
  return null;
481
+ const drawing = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING);
482
+ const reading = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING);
461
483
  gl.bindFramebuffer(gl.READ_FRAMEBUFFER, source);
462
484
  gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.colorCopyFramebuffer);
463
485
  /* Drained first, for the reason `resolve` gives at length: `getError` reports whatever is
@@ -470,13 +492,34 @@ export class SceneTarget {
470
492
  choice only decides which refusals are possible. */
471
493
  gl.blitFramebuffer(0, 0, this.width, this.height, 0, 0, this.width, this.height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
472
494
  const refused = gl.getError() !== gl.NO_ERROR;
473
- /* Back to what the caller was drawing into, whichever that was. */
474
- gl.bindFramebuffer(gl.FRAMEBUFFER, source);
495
+ /*
496
+ * **Back to what the caller was drawing into, whichever that was** — read before the copy and
497
+ * handed back, never assumed. It was the scene's framebuffer, assumed, and a pane drawn into a
498
+ * reflection probe's face then left the scene's bound: the probe attached its next face to it,
499
+ * a face-sized colour beside a screen-sized depth, and every draw after refused as incomplete.
500
+ */
501
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, drawing);
502
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, reading);
475
503
  if (refused)
476
504
  return null;
477
505
  this.colorCopyFilled = true;
506
+ this.colorCopyMips = false;
507
+ if (mips)
508
+ this.fillColorCopyMips();
478
509
  return this.colorCopy;
479
510
  }
511
+ /** The copy's mip chain from its level 0, once per copy. */
512
+ fillColorCopyMips() {
513
+ if (this.colorCopyMips || this.colorCopy === null)
514
+ return;
515
+ const { gl } = this;
516
+ /* Bound on whichever unit is active and handed back, so the caller's texture there survives. */
517
+ const previous = gl.getParameter(gl.TEXTURE_BINDING_2D);
518
+ gl.bindTexture(gl.TEXTURE_2D, this.colorCopy);
519
+ gl.generateMipmap(gl.TEXTURE_2D);
520
+ gl.bindTexture(gl.TEXTURE_2D, previous);
521
+ this.colorCopyMips = true;
522
+ }
480
523
  snapshotDepth(
481
524
  /**
482
525
  * Take the copy again even though one has already been taken this frame.
@@ -499,6 +542,8 @@ export class SceneTarget {
499
542
  const source = this.msaaFramebuffer ?? this.framebuffer;
500
543
  if (source === null)
501
544
  return null;
545
+ const drawing = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING);
546
+ const reading = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING);
502
547
  gl.bindFramebuffer(gl.READ_FRAMEBUFFER, source);
503
548
  gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.depthCopyFramebuffer);
504
549
  /* Drained first, for the reason `resolve` gives at length: `getError` reports whatever is
@@ -509,8 +554,9 @@ export class SceneTarget {
509
554
  }
510
555
  gl.blitFramebuffer(0, 0, this.width, this.height, 0, 0, this.width, this.height, gl.DEPTH_BUFFER_BIT, gl.NEAREST);
511
556
  const refused = gl.getError() !== gl.NO_ERROR;
512
- /* Back to what the caller was drawing into, whichever that was. */
513
- gl.bindFramebuffer(gl.FRAMEBUFFER, source);
557
+ /* Back to what the caller was drawing into, read rather than assumed: see `snapshotColor`. */
558
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, drawing);
559
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, reading);
514
560
  if (refused)
515
561
  return null;
516
562
  this.depthCopyFilled = true;
@@ -788,6 +834,8 @@ export class SceneTarget {
788
834
  gl.bindTexture(gl.TEXTURE_2D, aoTexture ?? scene);
789
835
  gl.uniform1i(this.uniforms['uAo'] ?? null, 2);
790
836
  gl.uniform1f(this.uniforms['uAoStrength'] ?? null, aoTexture === null ? 0 : (ao?.strength ?? 0));
837
+ const offset = ao?.offset;
838
+ gl.uniform2f(this.uniforms['uAoOffset'] ?? null, offset?.[0] ?? 0, offset?.[1] ?? 0);
791
839
  /* Bloom on its own unit, and the placeholder for the same reason as the one above: a
792
840
  driver may fetch a texture's descriptor before it evaluates the branch that skips the
793
841
  read, and an incomplete texture is not a thing to hand it. */
@@ -23,7 +23,7 @@ export declare const DEPTH_INSTANCED_VERT: string;
23
23
  */
24
24
  export declare const DEPTH_CUTOUT_VERT: string;
25
25
  export declare const DEPTH_INSTANCED_CUTOUT_VERT: string;
26
- export declare const DEPTH_FRAG = "#version 300 es\nprecision highp float;\nin vec4 vLightPosition;\n\nuniform highp sampler2D uPreviousShadowMap;\nuniform int uPeelShadowLayer;\n\nvoid main() {\n if (uPeelShadowLayer != 0) {\n vec3 p = vLightPosition.xyz / vLightPosition.w;\n vec2 uv = p.xy * 0.5 + 0.5;\n float previousDepth = texture(uPreviousShadowMap, uv).r;\n // Discard the already-recorded surface and everything in front of it. The\n // ordinary depth test then stores the next independently fading occluder.\n if (gl_FragCoord.z <= previousDepth + 0.00001) discard;\n }\n}\n";
26
+ export declare const DEPTH_FRAG = "#version 300 es\nprecision highp float;\nin vec4 vLightPosition;\n\nuniform highp sampler2DArray uPreviousShadowMap;\nuniform int uPeelShadowLayer;\n\nvoid main() {\n if (uPeelShadowLayer != 0) {\n vec3 p = vLightPosition.xyz / vLightPosition.w;\n vec2 uv = p.xy * 0.5 + 0.5;\n float previousDepth = texture(uPreviousShadowMap, vec3(uv, 0.0)).r;\n // Discard the already-recorded surface and everything in front of it. The\n // ordinary depth test then stores the next independently fading occluder.\n if (gl_FragCoord.z <= previousDepth + 0.00001) discard;\n }\n}\n";
27
27
  /**
28
28
  * The cutout caster's depth: the plain pass, and a texel of alpha under the cutoff writes nothing.
29
29
  *
@@ -37,3 +37,18 @@ export declare const DEPTH_FRAG = "#version 300 es\nprecision highp float;\nin v
37
37
  * map takes the next.
38
38
  */
39
39
  export declare const DEPTH_CUTOUT_FRAG: string;
40
+ /**
41
+ * The glass tint's vertex stages: every way a caster is drawn, with the world position handed on.
42
+ *
43
+ * **The same five as the depth pass, and for the same reason**: a pane must cast its colour from
44
+ * exactly where it casts its depth, deformed and placed the same way, or the coloured patch parts
45
+ * from the pane's own shadow edge. The only addition is `vGlassWorld`.
46
+ */
47
+ export declare const GLASS_TINT_VERT: string;
48
+ export declare const GLASS_TINT_SKINNED_VERT: string;
49
+ export declare const GLASS_TINT_INSTANCED_VERT: string;
50
+ export declare const GLASS_TINT_CUTOUT_VERT: string;
51
+ export declare const GLASS_TINT_INSTANCED_CUTOUT_VERT: string;
52
+ export declare const GLASS_TINT_FRAG = "#version 300 es\nprecision highp float;\nin vec4 vLightPosition;\nin vec3 vGlassWorld;\n\n/* rgb: transmission times tint; a: clarity, one less the frost. */\nuniform vec4 uGlassPane;\n/* xyz: the light's position (w 1) or the direction toward it (w 0). */\nuniform vec4 uGlassLight;\n\nout vec4 outTint;\n\nvoid main() {\n\n vec3 n = normalize(cross(dFdx(vGlassWorld), dFdy(vGlassWorld)));\n vec3 toLight = uGlassLight.w > 0.5 ? normalize(uGlassLight.xyz - vGlassWorld) : uGlassLight.xyz;\n float cosLight = abs(dot(n, toLight));\n float reflected = 0.04 + 0.96 * pow(1.0 - cosLight, 5.0);\n outTint = vec4(uGlassPane.rgb * (1.0 - reflected), uGlassPane.a);\n\n}\n";
53
+ /** A cut-out pane: its colour lands only where its depth does, by the depth cutout's own test. */
54
+ export declare const GLASS_TINT_CUTOUT_FRAG: string;