@driftengine/core 3.63.0 → 4.1.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 (428) hide show
  1. package/CHANGELOG.json +122 -0
  2. package/NOTICE +20 -0
  3. package/README.md +1 -1
  4. package/dist/core/loop.js +18 -2
  5. package/dist/core/storage.js +5 -0
  6. package/dist/geometry/meshBuilder.d.ts +1 -1
  7. package/dist/geometry/meshBuilder.js +6 -2
  8. package/dist/index.d.ts +69 -4
  9. package/dist/index.js +112 -2
  10. package/dist/input/input.js +6 -1
  11. package/dist/math/exact.d.ts +13 -3
  12. package/dist/math/exact.js +68 -3
  13. package/dist/math/frustum.d.ts +17 -2
  14. package/dist/math/frustum.js +24 -0
  15. package/dist/render/backend/api.d.ts +34 -1
  16. package/dist/render/backend/api.js +22 -1
  17. package/dist/render/backend/budget.d.ts +7 -6
  18. package/dist/render/backend/budget.js +7 -6
  19. package/dist/render/backend/createRenderer.d.ts +57 -0
  20. package/dist/render/backend/createRenderer.js +51 -2
  21. package/dist/render/backend/materialChanges.d.ts +41 -0
  22. package/dist/render/backend/materialChanges.js +38 -0
  23. package/dist/render/backend/select.js +8 -0
  24. package/dist/render/backend/webgl2/renderer.d.ts +80 -1
  25. package/dist/render/backend/webgl2/renderer.js +208 -10
  26. package/dist/render/backend/webgpu/buffers.d.ts +20 -1
  27. package/dist/render/backend/webgpu/buffers.js +116 -58
  28. package/dist/render/backend/webgpu/fieldCompose.d.ts +156 -0
  29. package/dist/render/backend/webgpu/fieldCompose.js +396 -0
  30. package/dist/render/backend/webgpu/giFieldPass.d.ts +66 -0
  31. package/dist/render/backend/webgpu/giFieldPass.js +137 -0
  32. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +728 -0
  33. package/dist/render/backend/webgpu/gpuDrivenPass.js +2473 -0
  34. package/dist/render/backend/webgpu/linePass.d.ts +4 -14
  35. package/dist/render/backend/webgpu/linePass.js +4 -33
  36. package/dist/render/backend/webgpu/probeBake.d.ts +148 -0
  37. package/dist/render/backend/webgpu/probeBake.js +493 -0
  38. package/dist/render/backend/webgpu/renderer.d.ts +379 -24
  39. package/dist/render/backend/webgpu/renderer.js +1369 -76
  40. package/dist/render/backend/webgpu/scatterPass.js +15 -1
  41. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +19 -9
  42. package/dist/render/backend/webgpu/surfaceTexturePass.js +58 -32
  43. package/dist/render/causticsRenderer.d.ts +3 -1
  44. package/dist/render/causticsRenderer.js +4 -1
  45. package/dist/render/depthConvention.d.ts +12 -0
  46. package/dist/render/depthConvention.js +14 -0
  47. package/dist/render/deviceFeatures.d.ts +32 -0
  48. package/dist/render/deviceFeatures.js +41 -0
  49. package/dist/render/fog.d.ts +79 -0
  50. package/dist/render/fog.js +138 -0
  51. package/dist/render/frame/alias.d.ts +30 -0
  52. package/dist/render/frame/alias.js +61 -0
  53. package/dist/render/frame/deps.d.ts +37 -0
  54. package/dist/render/frame/deps.js +122 -0
  55. package/dist/render/frame/flushGraph.d.ts +43 -0
  56. package/dist/render/frame/flushGraph.js +84 -0
  57. package/dist/render/frame/graphSchedule.d.ts +49 -0
  58. package/dist/render/frame/graphSchedule.js +120 -0
  59. package/dist/render/frame/index.d.ts +15 -0
  60. package/dist/render/frame/index.js +8 -0
  61. package/dist/render/frame/lifetime.d.ts +24 -0
  62. package/dist/render/frame/lifetime.js +36 -0
  63. package/dist/render/frame/passTimings.d.ts +42 -0
  64. package/dist/render/frame/passTimings.js +38 -0
  65. package/dist/render/frame/validate.d.ts +15 -0
  66. package/dist/render/frame/validate.js +22 -0
  67. package/dist/render/frame/virtual.d.ts +44 -0
  68. package/dist/render/frame/virtual.js +61 -0
  69. package/dist/render/gi/chain.d.ts +82 -0
  70. package/dist/render/gi/chain.js +135 -0
  71. package/dist/render/gi/denoise.d.ts +114 -0
  72. package/dist/render/gi/denoise.js +169 -0
  73. package/dist/render/gi/fieldScene.d.ts +125 -0
  74. package/dist/render/gi/fieldScene.js +219 -0
  75. package/dist/render/gi/globalField.d.ts +157 -0
  76. package/dist/render/gi/globalField.js +373 -0
  77. package/dist/render/gi/probeGridFit.d.ts +24 -0
  78. package/dist/render/gi/probeGridFit.js +71 -0
  79. package/dist/render/gi/probeTrace.d.ts +82 -0
  80. package/dist/render/gi/probeTrace.js +128 -0
  81. package/dist/render/gi/probeVolume.d.ts +136 -0
  82. package/dist/render/gi/probeVolume.js +191 -0
  83. package/dist/render/gi/reflection.d.ts +73 -0
  84. package/dist/render/gi/reflection.js +109 -0
  85. package/dist/render/gi/traceField.d.ts +91 -0
  86. package/dist/render/gi/traceField.js +129 -0
  87. package/dist/render/gi/traceScreen.d.ts +68 -0
  88. package/dist/render/gi/traceScreen.js +82 -0
  89. package/dist/render/gpuScene.d.ts +38 -0
  90. package/dist/render/gpuScene.js +41 -0
  91. package/dist/render/gpudriven/alphaTest.d.ts +28 -0
  92. package/dist/render/gpudriven/alphaTest.js +32 -0
  93. package/dist/render/gpudriven/clusterUpload.d.ts +59 -0
  94. package/dist/render/gpudriven/clusterUpload.js +75 -0
  95. package/dist/render/gpudriven/clusterWorld.d.ts +57 -0
  96. package/dist/render/gpudriven/clusterWorld.js +155 -0
  97. package/dist/render/gpudriven/compact.d.ts +51 -0
  98. package/dist/render/gpudriven/compact.js +68 -0
  99. package/dist/render/gpudriven/cullClusters.d.ts +129 -0
  100. package/dist/render/gpudriven/cullClusters.js +237 -0
  101. package/dist/render/gpudriven/decodeTables.d.ts +64 -0
  102. package/dist/render/gpudriven/decodeTables.js +219 -0
  103. package/dist/render/gpudriven/frustum.d.ts +25 -0
  104. package/dist/render/gpudriven/frustum.js +59 -0
  105. package/dist/render/gpudriven/geometryHarness.d.ts +15 -0
  106. package/dist/render/gpudriven/geometryHarness.js +31 -0
  107. package/dist/render/gpudriven/hzb.d.ts +60 -0
  108. package/dist/render/gpudriven/hzb.js +97 -0
  109. package/dist/render/gpudriven/ibl.d.ts +85 -0
  110. package/dist/render/gpudriven/ibl.js +129 -0
  111. package/dist/render/gpudriven/indirect.d.ts +67 -0
  112. package/dist/render/gpudriven/indirect.js +77 -0
  113. package/dist/render/gpudriven/instances.d.ts +43 -0
  114. package/dist/render/gpudriven/instances.js +172 -0
  115. package/dist/render/gpudriven/lit.d.ts +165 -0
  116. package/dist/render/gpudriven/lit.js +165 -0
  117. package/dist/render/gpudriven/lodCut.d.ts +28 -0
  118. package/dist/render/gpudriven/lodCut.js +38 -0
  119. package/dist/render/gpudriven/materialBin.d.ts +60 -0
  120. package/dist/render/gpudriven/materialBin.js +108 -0
  121. package/dist/render/gpudriven/materialTable.d.ts +88 -0
  122. package/dist/render/gpudriven/materialTable.js +115 -0
  123. package/dist/render/gpudriven/pipeline.d.ts +135 -0
  124. package/dist/render/gpudriven/pipeline.js +237 -0
  125. package/dist/render/gpudriven/rangeAlloc.d.ts +50 -0
  126. package/dist/render/gpudriven/rangeAlloc.js +112 -0
  127. package/dist/render/gpudriven/sceneUpload.d.ts +96 -0
  128. package/dist/render/gpudriven/sceneUpload.js +115 -0
  129. package/dist/render/gpudriven/shadeBins.d.ts +92 -0
  130. package/dist/render/gpudriven/shadeBins.js +182 -0
  131. package/dist/render/gpudriven/shadow.d.ts +136 -0
  132. package/dist/render/gpudriven/shadow.js +224 -0
  133. package/dist/render/gpudriven/shadowCamera.d.ts +87 -0
  134. package/dist/render/gpudriven/shadowCamera.js +345 -0
  135. package/dist/render/gpudriven/streamScene.d.ts +194 -0
  136. package/dist/render/gpudriven/streamScene.js +459 -0
  137. package/dist/render/gpudriven/surfaceFrame.d.ts +25 -0
  138. package/dist/render/gpudriven/surfaceFrame.js +51 -0
  139. package/dist/render/gpudriven/twoPhase.d.ts +122 -0
  140. package/dist/render/gpudriven/twoPhase.js +160 -0
  141. package/dist/render/gpudriven/visbuffer.d.ts +25 -0
  142. package/dist/render/gpudriven/visbuffer.js +37 -0
  143. package/dist/render/halfFloat.d.ts +10 -0
  144. package/dist/render/halfFloat.js +23 -4
  145. package/dist/render/inference/attentionTiled.d.ts +27 -0
  146. package/dist/render/inference/attentionTiled.js +117 -0
  147. package/dist/render/inference/denseKernels.d.ts +29 -0
  148. package/dist/render/inference/denseKernels.js +312 -0
  149. package/dist/render/inference/device.d.ts +21 -0
  150. package/dist/render/inference/device.js +32 -0
  151. package/dist/render/inference/deviceGraph.d.ts +34 -0
  152. package/dist/render/inference/deviceGraph.js +17 -0
  153. package/dist/render/inference/fuse.d.ts +15 -0
  154. package/dist/render/inference/fuse.js +37 -0
  155. package/dist/render/inference/kernelKit.d.ts +52 -0
  156. package/dist/render/inference/kernelKit.js +70 -0
  157. package/dist/render/inference/kernels.d.ts +3 -0
  158. package/dist/render/inference/kernels.js +11 -0
  159. package/dist/render/inference/plan.d.ts +13 -0
  160. package/dist/render/inference/plan.js +18 -0
  161. package/dist/render/inference/reuse.d.ts +39 -0
  162. package/dist/render/inference/reuse.js +65 -0
  163. package/dist/render/inference/runner.d.ts +31 -0
  164. package/dist/render/inference/runner.js +175 -0
  165. package/dist/render/inference/schedule.d.ts +39 -0
  166. package/dist/render/inference/schedule.js +59 -0
  167. package/dist/render/inference/shapeKernels.d.ts +10 -0
  168. package/dist/render/inference/shapeKernels.js +80 -0
  169. package/dist/render/inference/spatialKernels.d.ts +21 -0
  170. package/dist/render/inference/spatialKernels.js +179 -0
  171. package/dist/render/inference/splatRaster.wgsl.d.ts +75 -0
  172. package/dist/render/inference/splatRaster.wgsl.js +337 -0
  173. package/dist/render/inference/splatTiles.d.ts +79 -0
  174. package/dist/render/inference/splatTiles.js +161 -0
  175. package/dist/render/lineBatch.d.ts +2 -5
  176. package/dist/render/lineBatch.js +4 -24
  177. package/dist/render/mesh.d.ts +4 -0
  178. package/dist/render/mesh.js +45 -1
  179. package/dist/render/pass.d.ts +145 -8
  180. package/dist/render/prefilterEnvMap.d.ts +22 -0
  181. package/dist/render/prefilterEnvMap.js +31 -0
  182. package/dist/render/readback.d.ts +30 -0
  183. package/dist/render/readback.js +47 -0
  184. package/dist/render/recon/clamp.d.ts +36 -0
  185. package/dist/render/recon/clamp.js +92 -0
  186. package/dist/render/recon/disocclusion.d.ts +60 -0
  187. package/dist/render/recon/disocclusion.js +109 -0
  188. package/dist/render/recon/frameGen.d.ts +81 -0
  189. package/dist/render/recon/frameGen.js +150 -0
  190. package/dist/render/recon/frameSizes.d.ts +34 -0
  191. package/dist/render/recon/frameSizes.js +58 -0
  192. package/dist/render/recon/jitter.d.ts +60 -0
  193. package/dist/render/recon/jitter.js +118 -0
  194. package/dist/render/recon/motionVectors.d.ts +55 -0
  195. package/dist/render/recon/motionVectors.js +107 -0
  196. package/dist/render/recon/reproject.d.ts +41 -0
  197. package/dist/render/recon/reproject.js +131 -0
  198. package/dist/render/recon/resolve.d.ts +90 -0
  199. package/dist/render/recon/resolve.js +313 -0
  200. package/dist/render/recon/tier.d.ts +24 -0
  201. package/dist/render/recon/tier.js +8 -0
  202. package/dist/render/reflectionProbe.d.ts +26 -0
  203. package/dist/render/reflectionProbe.js +33 -0
  204. package/dist/render/renderQuality.d.ts +69 -0
  205. package/dist/render/renderQuality.js +22 -0
  206. package/dist/render/screenSpaceReflection.d.ts +23 -0
  207. package/dist/render/screenSpaceReflection.js +22 -3
  208. package/dist/render/sdfFont.js +2 -0
  209. package/dist/render/segmentQuads.d.ts +13 -0
  210. package/dist/render/segmentQuads.js +32 -0
  211. package/dist/render/shaders/flat/lobes.d.ts +27 -0
  212. package/dist/render/shaders/flat/lobes.js +33 -3
  213. package/dist/render/shaders/flat/preamble.d.ts +7 -0
  214. package/dist/render/shaders/flat/preamble.js +24 -1
  215. package/dist/render/shaders/flat/probeGrid.d.ts +1 -1
  216. package/dist/render/shaders/flat/probeGrid.js +71 -2
  217. package/dist/render/shaders/generated/flat.wgsl.d.ts +48 -8
  218. package/dist/render/shaders/generated/flat.wgsl.js +64 -24
  219. package/dist/render/shaders/gi/composeField.wgsl.d.ts +56 -0
  220. package/dist/render/shaders/gi/composeField.wgsl.js +231 -0
  221. package/dist/render/shaders/gi/marchField.wgsl.d.ts +30 -0
  222. package/dist/render/shaders/gi/marchField.wgsl.js +141 -0
  223. package/dist/render/shaders/gi/probeBake.wgsl.d.ts +113 -0
  224. package/dist/render/shaders/gi/probeBake.wgsl.js +635 -0
  225. package/dist/render/shaders/gi/probeTrace.wgsl.d.ts +72 -0
  226. package/dist/render/shaders/gi/probeTrace.wgsl.js +261 -0
  227. package/dist/render/shaders/gi/probeVolume.wgsl.d.ts +49 -0
  228. package/dist/render/shaders/gi/probeVolume.wgsl.js +242 -0
  229. package/dist/render/shaders/gi/sampleField.wgsl.d.ts +32 -0
  230. package/dist/render/shaders/gi/sampleField.wgsl.js +134 -0
  231. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +26 -0
  232. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +339 -0
  233. package/dist/render/shaders/gpudriven/compact.wgsl.d.ts +29 -0
  234. package/dist/render/shaders/gpudriven/compact.wgsl.js +58 -0
  235. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +90 -0
  236. package/dist/render/shaders/gpudriven/cull.wgsl.js +423 -0
  237. package/dist/render/shaders/gpudriven/decode.wgsl.d.ts +72 -0
  238. package/dist/render/shaders/gpudriven/decode.wgsl.js +349 -0
  239. package/dist/render/shaders/gpudriven/environment.wgsl.d.ts +44 -0
  240. package/dist/render/shaders/gpudriven/environment.wgsl.js +137 -0
  241. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +28 -0
  242. package/dist/render/shaders/gpudriven/fog.wgsl.js +107 -0
  243. package/dist/render/shaders/gpudriven/frameBlock.wgsl.d.ts +13 -0
  244. package/dist/render/shaders/gpudriven/frameBlock.wgsl.js +62 -0
  245. package/dist/render/shaders/gpudriven/materialBin.wgsl.d.ts +51 -0
  246. package/dist/render/shaders/gpudriven/materialBin.wgsl.js +112 -0
  247. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +25 -0
  248. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +43 -0
  249. package/dist/render/shaders/gpudriven/meshTransform.wgsl.d.ts +18 -0
  250. package/dist/render/shaders/gpudriven/meshTransform.wgsl.js +37 -0
  251. package/dist/render/shaders/gpudriven/present.wgsl.d.ts +43 -0
  252. package/dist/render/shaders/gpudriven/present.wgsl.js +127 -0
  253. package/dist/render/shaders/gpudriven/shade.wgsl.d.ts +127 -0
  254. package/dist/render/shaders/gpudriven/shade.wgsl.js +874 -0
  255. package/dist/render/shaders/gpudriven/shadow.wgsl.d.ts +44 -0
  256. package/dist/render/shaders/gpudriven/shadow.wgsl.js +308 -0
  257. package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.d.ts +28 -0
  258. package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.js +245 -0
  259. package/dist/render/shaders/network.wgsl.d.ts +101 -0
  260. package/dist/render/shaders/network.wgsl.js +206 -0
  261. package/dist/render/shaders/recon/motion.wgsl.d.ts +44 -0
  262. package/dist/render/shaders/recon/motion.wgsl.js +96 -0
  263. package/dist/render/shaders/recon/resolve.wgsl.d.ts +51 -0
  264. package/dist/render/shaders/recon/resolve.wgsl.js +733 -0
  265. package/dist/render/temporalAa.js +8 -33
  266. package/dist/render/textLayout.js +12 -6
  267. package/dist/render/textRenderer.d.ts +3 -1
  268. package/dist/render/textRenderer.js +4 -1
  269. package/dist/render/translucentQueue.d.ts +6 -0
  270. package/dist/render/translucentQueue.js +11 -0
  271. package/dist/render/windStreakRenderer.d.ts +2 -1
  272. package/dist/render/windStreakRenderer.js +3 -1
  273. package/dist/world/cell.d.ts +63 -0
  274. package/dist/world/cell.js +181 -0
  275. package/dist/world/cellStream.d.ts +103 -0
  276. package/dist/world/cellStream.js +198 -0
  277. package/dist/world/freeze.d.ts +81 -0
  278. package/dist/world/freeze.js +114 -0
  279. package/dist/world/rebase.d.ts +29 -0
  280. package/dist/world/rebase.js +41 -0
  281. package/package.json +8 -3
  282. package/scripts/frames.mjs +92 -0
  283. package/scripts/gpuCompute.mjs +323 -0
  284. package/scripts/png.d.mts +16 -0
  285. package/scripts/png.mjs +25 -0
  286. package/src/core/loop.ts +19 -2
  287. package/src/core/storage.ts +5 -0
  288. package/src/geometry/meshBuilder.ts +5 -1
  289. package/src/index.ts +246 -4
  290. package/src/input/input.ts +6 -1
  291. package/src/math/exact.ts +69 -3
  292. package/src/math/frustum.ts +52 -3
  293. package/src/render/backend/api.ts +42 -1
  294. package/src/render/backend/budget.ts +7 -6
  295. package/src/render/backend/createRenderer.ts +92 -3
  296. package/src/render/backend/materialChanges.ts +54 -0
  297. package/src/render/backend/select.ts +8 -0
  298. package/src/render/backend/webgl2/renderer.ts +211 -9
  299. package/src/render/backend/webgpu/buffers.ts +182 -69
  300. package/src/render/backend/webgpu/fieldCompose.ts +482 -0
  301. package/src/render/backend/webgpu/giFieldPass.ts +200 -0
  302. package/src/render/backend/webgpu/gpuDrivenPass.ts +2993 -0
  303. package/src/render/backend/webgpu/linePass.ts +4 -39
  304. package/src/render/backend/webgpu/probeBake.ts +588 -0
  305. package/src/render/backend/webgpu/renderer.ts +1481 -76
  306. package/src/render/backend/webgpu/scatterPass.ts +15 -1
  307. package/src/render/backend/webgpu/surfaceTexturePass.ts +63 -36
  308. package/src/render/causticsRenderer.ts +5 -2
  309. package/src/render/depthConvention.ts +15 -0
  310. package/src/render/deviceFeatures.ts +77 -0
  311. package/src/render/fog.ts +185 -0
  312. package/src/render/frame/alias.ts +97 -0
  313. package/src/render/frame/deps.ts +155 -0
  314. package/src/render/frame/flushGraph.ts +135 -0
  315. package/src/render/frame/graphSchedule.ts +170 -0
  316. package/src/render/frame/index.ts +52 -0
  317. package/src/render/frame/lifetime.ts +61 -0
  318. package/src/render/frame/passTimings.ts +69 -0
  319. package/src/render/frame/validate.ts +40 -0
  320. package/src/render/frame/virtual.ts +91 -0
  321. package/src/render/gi/chain.ts +225 -0
  322. package/src/render/gi/denoise.ts +276 -0
  323. package/src/render/gi/fieldScene.ts +264 -0
  324. package/src/render/gi/globalField.ts +543 -0
  325. package/src/render/gi/probeGridFit.ts +89 -0
  326. package/src/render/gi/probeTrace.ts +143 -0
  327. package/src/render/gi/probeVolume.ts +288 -0
  328. package/src/render/gi/reflection.ts +163 -0
  329. package/src/render/gi/traceField.ts +223 -0
  330. package/src/render/gi/traceScreen.ts +106 -0
  331. package/src/render/gpuScene.ts +80 -0
  332. package/src/render/gpudriven/alphaTest.ts +33 -0
  333. package/src/render/gpudriven/clusterUpload.ts +123 -0
  334. package/src/render/gpudriven/clusterWorld.ts +239 -0
  335. package/src/render/gpudriven/compact.ts +80 -0
  336. package/src/render/gpudriven/cullClusters.ts +333 -0
  337. package/src/render/gpudriven/decodeTables.ts +332 -0
  338. package/src/render/gpudriven/frustum.ts +70 -0
  339. package/src/render/gpudriven/geometryHarness.ts +51 -0
  340. package/src/render/gpudriven/hzb.ts +113 -0
  341. package/src/render/gpudriven/ibl.ts +147 -0
  342. package/src/render/gpudriven/indirect.ts +89 -0
  343. package/src/render/gpudriven/instances.ts +234 -0
  344. package/src/render/gpudriven/lit.ts +329 -0
  345. package/src/render/gpudriven/lodCut.ts +62 -0
  346. package/src/render/gpudriven/materialBin.ts +131 -0
  347. package/src/render/gpudriven/materialTable.ts +164 -0
  348. package/src/render/gpudriven/pipeline.ts +336 -0
  349. package/src/render/gpudriven/rangeAlloc.ts +122 -0
  350. package/src/render/gpudriven/sceneUpload.ts +173 -0
  351. package/src/render/gpudriven/shadeBins.ts +222 -0
  352. package/src/render/gpudriven/shadow.ts +285 -0
  353. package/src/render/gpudriven/shadowCamera.ts +406 -0
  354. package/src/render/gpudriven/streamScene.ts +575 -0
  355. package/src/render/gpudriven/surfaceFrame.ts +68 -0
  356. package/src/render/gpudriven/twoPhase.ts +214 -0
  357. package/src/render/gpudriven/visbuffer.ts +41 -0
  358. package/src/render/halfFloat.ts +22 -4
  359. package/src/render/inference/attentionTiled.ts +132 -0
  360. package/src/render/inference/denseKernels.ts +369 -0
  361. package/src/render/inference/device.ts +44 -0
  362. package/src/render/inference/deviceGraph.ts +39 -0
  363. package/src/render/inference/fuse.ts +47 -0
  364. package/src/render/inference/kernelKit.ts +120 -0
  365. package/src/render/inference/kernels.ts +18 -0
  366. package/src/render/inference/plan.ts +26 -0
  367. package/src/render/inference/reuse.ts +79 -0
  368. package/src/render/inference/runner.ts +204 -0
  369. package/src/render/inference/schedule.ts +80 -0
  370. package/src/render/inference/shapeKernels.ts +112 -0
  371. package/src/render/inference/spatialKernels.ts +220 -0
  372. package/src/render/inference/splatRaster.wgsl.ts +348 -0
  373. package/src/render/inference/splatTiles.ts +186 -0
  374. package/src/render/lineBatch.ts +4 -24
  375. package/src/render/mesh.ts +46 -1
  376. package/src/render/pass.ts +155 -10
  377. package/src/render/prefilterEnvMap.ts +33 -0
  378. package/src/render/readback.ts +73 -0
  379. package/src/render/recon/clamp.ts +113 -0
  380. package/src/render/recon/disocclusion.ts +143 -0
  381. package/src/render/recon/frameGen.ts +191 -0
  382. package/src/render/recon/frameSizes.ts +64 -0
  383. package/src/render/recon/jitter.ts +126 -0
  384. package/src/render/recon/motionVectors.ts +138 -0
  385. package/src/render/recon/reproject.ts +179 -0
  386. package/src/render/recon/resolve.ts +444 -0
  387. package/src/render/recon/tier.ts +31 -0
  388. package/src/render/reflectionProbe.ts +34 -0
  389. package/src/render/renderQuality.ts +87 -0
  390. package/src/render/screenSpaceReflection.ts +37 -3
  391. package/src/render/sdfFont.ts +2 -0
  392. package/src/render/segmentQuads.ts +39 -0
  393. package/src/render/shaders/flat/lobes.ts +35 -3
  394. package/src/render/shaders/flat/preamble.ts +25 -1
  395. package/src/render/shaders/flat/probeGrid.ts +77 -2
  396. package/src/render/shaders/generated/flat.wgsl.ts +64 -24
  397. package/src/render/shaders/gi/composeField.wgsl.ts +239 -0
  398. package/src/render/shaders/gi/marchField.wgsl.ts +146 -0
  399. package/src/render/shaders/gi/probeBake.wgsl.ts +649 -0
  400. package/src/render/shaders/gi/probeTrace.wgsl.ts +269 -0
  401. package/src/render/shaders/gi/probeVolume.wgsl.ts +246 -0
  402. package/src/render/shaders/gi/sampleField.wgsl.ts +140 -0
  403. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +343 -0
  404. package/src/render/shaders/gpudriven/compact.wgsl.ts +59 -0
  405. package/src/render/shaders/gpudriven/cull.wgsl.ts +433 -0
  406. package/src/render/shaders/gpudriven/decode.wgsl.ts +373 -0
  407. package/src/render/shaders/gpudriven/environment.wgsl.ts +141 -0
  408. package/src/render/shaders/gpudriven/fog.wgsl.ts +110 -0
  409. package/src/render/shaders/gpudriven/frameBlock.wgsl.ts +62 -0
  410. package/src/render/shaders/gpudriven/materialBin.wgsl.ts +116 -0
  411. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +46 -0
  412. package/src/render/shaders/gpudriven/meshTransform.wgsl.ts +37 -0
  413. package/src/render/shaders/gpudriven/present.wgsl.ts +129 -0
  414. package/src/render/shaders/gpudriven/shade.wgsl.ts +884 -0
  415. package/src/render/shaders/gpudriven/shadow.wgsl.ts +322 -0
  416. package/src/render/shaders/gpudriven/visbufferRaster.wgsl.ts +251 -0
  417. package/src/render/shaders/network.wgsl.ts +238 -0
  418. package/src/render/shaders/recon/motion.wgsl.ts +101 -0
  419. package/src/render/shaders/recon/resolve.wgsl.ts +741 -0
  420. package/src/render/temporalAa.ts +9 -33
  421. package/src/render/textLayout.ts +12 -6
  422. package/src/render/textRenderer.ts +5 -2
  423. package/src/render/translucentQueue.ts +11 -0
  424. package/src/render/windStreakRenderer.ts +4 -2
  425. package/src/world/cell.ts +231 -0
  426. package/src/world/cellStream.ts +278 -0
  427. package/src/world/freeze.ts +157 -0
  428. package/src/world/rebase.ts +56 -0
@@ -0,0 +1,128 @@
1
+ /**
2
+ * What a probe asks the indirect chain, and what it does with the answers.
3
+ *
4
+ * **A probe's measurement is an octahedral irradiance map**, because that is what the shading
5
+ * already samples: `gridIrradiance` in `shaders/flat/probeGrid.ts` blends eight probes and reads
6
+ * each through `probeIrradiance(dir, layer)`. So nothing downstream of a probe changes — only where
7
+ * the numbers in that map came from. Today they come from six rasterised faces of the world;
8
+ * `chain.ts` is what they come from once this is wired up, and the difference is a second bounce
9
+ * and radiance from surfaces that were never in frame.
10
+ *
11
+ * **Why a spherical Fibonacci spiral and not random directions.** At a few hundred samples a random
12
+ * set leaves holes and pairs, and a probe with a hole in its sphere has looked at part of its world
13
+ * and reports all of it — as a dark patch in a direction nothing happened to be traced along. The
14
+ * spiral's points are as far from each other as the count allows, and its cost is one sine and one
15
+ * cosine a direction.
16
+ *
17
+ * **And the set turns every frame, which is what makes a probe converge.** A fixed set measures the
18
+ * same few hundred directions for ever, so whatever it misses it misses permanently. Rotating it by
19
+ * an irrational fraction of a turn each refresh means successive refreshes sample different
20
+ * directions, and the accumulation over a few refreshes is a denser set than any one of them.
21
+ */
22
+ import { octInsetDir } from '../shaders/octahedral.js';
23
+ /**
24
+ * Why a scene is not lit by what bounces on WebGL2, in words, so both backends read one sentence.
25
+ *
26
+ * **A probe is a few hundred rays and that is a compute dispatch.** This backend has no compute
27
+ * stage, and the alternatives are both worse than saying so: tracing on the processor is a
28
+ * main-thread cost proportional to the grid, and approximating with more frequent cube bakes means
29
+ * the two backends light one scene differently — which is the disagreement every published scene's
30
+ * pixel gate exists to prevent.
31
+ *
32
+ * Written here rather than in either renderer for the reason `OIT_MULTISAMPLE_REFUSAL` is written
33
+ * outside both: it is a decision the two share, so it belongs where neither owns it.
34
+ */
35
+ export const INDIRECT_LIGHT_WEBGL2_REFUSAL = 'driftengine: indirect light traces a few hundred rays a probe, which is a compute dispatch, ' +
36
+ 'and WebGL2 has no compute stage — so `quality.indirectLight` lights nothing on this backend ' +
37
+ 'and the probe grid keeps the bake it has always had. It is WebGPU only, as reconstruction is.';
38
+ /**
39
+ * Directions a probe traces per refresh.
40
+ *
41
+ * **A few hundred, and the number is a cost rather than a quality.** A refresh is this many rays
42
+ * down a three-level chain, and `probeUpdateSchedule` decides how many probes pay it a frame. The
43
+ * convergence comes from the rotation between refreshes, not from this count, so it is set where a
44
+ * single refresh is already recognisable rather than where one refresh is final.
45
+ */
46
+ export const PROBE_DIRECTIONS = 256;
47
+ /**
48
+ * The golden angle, which is what spaces a spiral's points as far apart as a count allows.
49
+ *
50
+ * Exported because `shaders/gi/probeBake.wgsl.ts` interpolates it: a shader cannot import a
51
+ * constant, so the choice is one value written here and pasted in, or two literals kept in step by
52
+ * hand. `probe-bake-parity.mjs` checks the result either way, but only one of them can be right by
53
+ * construction.
54
+ */
55
+ export const GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));
56
+ /**
57
+ * How far the set turns between one refresh and the next, in turns.
58
+ *
59
+ * The golden ratio's fractional part: successive frames land their offsets as far from each other
60
+ * as the sequence allows, for the same reason the spiral's own points do, and it never repeats.
61
+ */
62
+ export const FRAME_TURN = 0.618033988749895;
63
+ /**
64
+ * Direction `index` of the set this `frame` traces, into `out`.
65
+ *
66
+ * The spiral runs `z` from near `+1` to near `-1` in equal steps and turns by the golden angle at
67
+ * each, which is the construction whose points are evenly spread on a sphere; the frame's own
68
+ * rotation is added to that turn.
69
+ */
70
+ export function probeDirection(index, frame, out) {
71
+ const i = ((index % PROBE_DIRECTIONS) + PROBE_DIRECTIONS) % PROBE_DIRECTIONS;
72
+ /* Offset by a half step at each end, so no direction lands exactly on a pole — where the turn
73
+ has no effect and two frames would trace the same ray. */
74
+ const z = 1 - (2 * i + 1) / PROBE_DIRECTIONS;
75
+ const radius = Math.sqrt(Math.max(0, 1 - z * z));
76
+ const angle = i * GOLDEN_ANGLE + frame * FRAME_TURN * Math.PI * 2;
77
+ out[0] = Math.cos(angle) * radius;
78
+ out[1] = Math.sin(angle) * radius;
79
+ out[2] = z;
80
+ }
81
+ const TEXEL_DIR = new Float32Array(3);
82
+ /**
83
+ * The cosine convolution of `count` radiance samples into an octahedral map of `edge` texels.
84
+ *
85
+ * Each texel's direction is the normal of a surface facing that way, and its value is the
86
+ * irradiance such a surface receives: every sample weighted by how much of its hemisphere it
87
+ * occupies, which for a cosine lobe is `max(0, dot(normal, direction))`.
88
+ *
89
+ * **Normalised by the weights that were actually used, and that is the whole of the white-furnace
90
+ * property.** Dividing by an analytic constant — pi, or the sample count — is right only in the
91
+ * limit and wrong for any finite set, so a uniformly white sphere would convolve to something
92
+ * slightly other than white and a room would brighten or darken a little at every refresh. Dividing
93
+ * by the sum of the weights is exact for any set, and it is what makes a constant come back
94
+ * constant to the last place a float holds.
95
+ */
96
+ export function convolveProbe(samples, directions, count, edge, out) {
97
+ const texels = Math.max(1, Math.floor(edge));
98
+ for (let v = 0; v < texels; v += 1) {
99
+ for (let u = 0; u < texels; u += 1) {
100
+ octInsetDir((u + 0.5) / texels, (v + 0.5) / texels, texels, TEXEL_DIR);
101
+ const nx = TEXEL_DIR[0];
102
+ const ny = TEXEL_DIR[1];
103
+ const nz = TEXEL_DIR[2];
104
+ let weights = 0;
105
+ let r = 0;
106
+ let g = 0;
107
+ let b = 0;
108
+ for (let i = 0; i < count; i += 1) {
109
+ const weight = nx * directions[i * 3] +
110
+ ny * directions[i * 3 + 1] +
111
+ nz * directions[i * 3 + 2];
112
+ if (weight <= 0)
113
+ continue;
114
+ weights += weight;
115
+ r += samples[i * 3] * weight;
116
+ g += samples[i * 3 + 1] * weight;
117
+ b += samples[i * 3 + 2] * weight;
118
+ }
119
+ const at = (v * texels + u) * 3;
120
+ /* No weight at all is a texel no sample faced, which a set this dense does not produce —
121
+ and answering zero rather than dividing is what keeps it from being a hole if it ever does. */
122
+ const scale = weights > 0 ? 1 / weights : 0;
123
+ out[at] = Math.max(0, r * scale);
124
+ out[at + 1] = Math.max(0, g * scale);
125
+ out[at + 2] = Math.max(0, b * scale);
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,136 @@
1
+ /** The last level of the chain: a probe volume that knows where the walls are. */
2
+ import type { ProbeBlend, ProbeGrid } from '../probeGrid.ts';
3
+ /**
4
+ * **A probe volume is never wrong and only ever coarse, and that is what it is for.**
5
+ *
6
+ * It is the level the chain falls back to when the screen could not answer and the world field
7
+ * could not either, so it has nothing behind it and must always answer. What makes that safe is
8
+ * that it is an interpolation between measurements rather than a trace: no ray to escape, no frame
9
+ * to leave, no resolution to run out of. The cost is that it is smooth where the world is not.
10
+ *
11
+ * **The one way it *is* wrong is the wall, and every implementation of this has shipped it.**
12
+ * `probeGrid.ts`'s blend is eight corners and three lerps — it reads the lattice and the point and
13
+ * nothing else — so a point in a dark room takes a trilinear share of a probe standing in the lit
14
+ * room next door. Light comes out of solid walls, and no amount of probe density fixes it: halving
15
+ * the spacing halves the width of the glow and leaves it exactly as bright.
16
+ *
17
+ * That is not a defect in `probeGrid.ts`. Its header argues for a bare lattice at length and the
18
+ * argument is about *uniform pressure* — free placement wants a position and an extent per probe
19
+ * against a flat shader already 1.7x over WebGL2's guaranteed `MAX_FRAGMENT_UNIFORM_VECTORS`. This
20
+ * adds the term that was missing without touching any of that: a second array beside the radiance
21
+ * one, holding what each probe could see, and a weight computed from it.
22
+ *
23
+ * **Passing `null` for the visibility is the whole of the off switch**, and it lands on
24
+ * `nearestProbes` itself rather than on a path that reproduces it — so a scene with no visibility
25
+ * volume renders exactly the frame it rendered before this file existed.
26
+ */
27
+ /**
28
+ * What each probe could see, as one octahedral map of depth moments per probe.
29
+ *
30
+ * **Two moments and not one distance.** A single distance gives a binary answer — in front of the
31
+ * wall or behind it — and a binary answer aliases along every edge in the scene, because the probe
32
+ * that can see a point and the probe that cannot are adjacent. The mean and the mean of squares
33
+ * give a *variance*, and a Chebyshev bound on that variance is a soft weight: confident where the
34
+ * geometry is flat, uncertain where it is not, which is exactly where a hard answer would be wrong.
35
+ * It is the same arithmetic a variance shadow map uses, for the same reason.
36
+ *
37
+ * Octahedral rather than a cube, matching `EnvProbeArray`: one layer a probe, no faces to seam, and
38
+ * no sampler per probe. `octInsetUv` is the mapping, so a direction lands in the same texel here as
39
+ * it does in the radiance map.
40
+ */
41
+ export interface ProbeVisibility {
42
+ /** Two floats a texel: mean distance, then mean of squares. One map a probe, layer-major. */
43
+ readonly moments: Float32Array;
44
+ /** Texels a side, the outer ring being the gutter `octInsetUv` insets past. */
45
+ readonly edge: number;
46
+ readonly layers: number;
47
+ }
48
+ /**
49
+ * The least variance a texel is treated as having, in square metres.
50
+ *
51
+ * **Without it the falloff is a step**, because a probe looking at a flat wall records the same
52
+ * distance in every direction of the cone and the variance is exactly zero — so the Chebyshev
53
+ * bound is zero everywhere past the wall and one everywhere in front of it, and the seam between
54
+ * them is a hard line that crawls as the probes are rebaked. Two hundredths of a square metre is
55
+ * about fourteen centimetres of slop, which is below a probe spacing anybody would choose and
56
+ * above the depth error of a bake.
57
+ */
58
+ export declare const PROBE_VISIBILITY_VARIANCE_FLOOR = 0.02;
59
+ /**
60
+ * How sharply the Chebyshev bound is turned into a weight.
61
+ *
62
+ * The bound is generous by construction — it is an inequality, not a measurement — so a probe that
63
+ * is mostly occluded still scores a third of a fully visible one. Cubing it turns that third into
64
+ * a twenty-seventh, which is the difference between a wall that reads as a wall and one that reads
65
+ * as a curtain. Three is what the technique this follows uses, and it is here as a constant so the
66
+ * next person can see it is a choice.
67
+ */
68
+ export declare const PROBE_VISIBILITY_SHARPNESS = 3;
69
+ /**
70
+ * Below this share, a probe is treated as contributing nothing at all.
71
+ *
72
+ * **A weight that is merely very small is not the same as zero, and the difference is the last
73
+ * percent of the leak.** A probe on the far side of a wall scores about four ten-thousandths here;
74
+ * kept, it puts that fraction of the lit room into the dark one, which is faint and is visible
75
+ * because the eye reads a gradient in a dark room extremely well. Crushed, the wall is a wall.
76
+ *
77
+ * It is applied to the *product* of the trilinear weight and the visibility, because that is what a
78
+ * probe actually contributes, and it is applied in `visibleProbes` rather than in
79
+ * `probeVisibilityWeight` — the raw bound stays continuous, which is what lets it be tested as a
80
+ * falloff rather than as a switch.
81
+ */
82
+ export declare const PROBE_VISIBILITY_CRUSH = 0.01;
83
+ export declare function createProbeVisibility(edge: number, layers: number): ProbeVisibility;
84
+ /**
85
+ * Fill one probe's map from what it can see.
86
+ *
87
+ * `distanceInDirection` is the bake: how far the geometry is from this probe along a direction.
88
+ * Given as a callback because how that is measured is the caller's business — a `traceField` march
89
+ * offline, a depth cube read back, or a closed form in a test.
90
+ */
91
+ export declare function bakeProbeVisibility(visibility: ProbeVisibility, layer: number, distanceInDirection: (x: number, y: number, z: number) => number): void;
92
+ /**
93
+ * How much a probe's answer is trusted at a point this far away in this direction.
94
+ *
95
+ * One in front of what the probe saw; a Chebyshev bound on the depth variance behind it, cubed.
96
+ * Continuous everywhere, which is what `visibleProbes` then crushes rather than this.
97
+ */
98
+ export declare function probeVisibilityWeight(visibility: ProbeVisibility, layer: number, x: number, y: number, z: number, distanceM: number): number;
99
+ /**
100
+ * The eight probes around a point, weighted by what each of them can actually see.
101
+ *
102
+ * With `visibility` null this is `nearestProbes` and nothing else — see the module header for why
103
+ * that matters. With one, each corner's trilinear weight is multiplied by its visibility, the
104
+ * result is crushed and the eight are renormalised so they still sum to one. **Renormalising is
105
+ * what keeps the blend from losing energy**, which is the property the white furnace in Task 10
106
+ * rests on: a solution that quietly drops a share of its probes darkens every bounce.
107
+ *
108
+ * **A point every probe is blind to still gets an answer**, because this is the last level of the
109
+ * chain and there is nothing behind it. When every weight crushes to zero — a point inside a solid,
110
+ * or a sealed void with a probe on each side of a wall — the nearest probe by distance takes the
111
+ * whole weight. That is the coarse answer this level exists to always have.
112
+ */
113
+ export declare function visibleProbes(grid: ProbeGrid, visibility: ProbeVisibility | null, x: number, y: number, z: number, out: ProbeBlend): ProbeBlend;
114
+ /**
115
+ * Blend what the probes carry, `channels` floats each, into a target the caller owns.
116
+ *
117
+ * Channel count rather than a fixed three, because what a probe carries is the caller's decision:
118
+ * three for an ambient colour, nine for a spherical-harmonic band, a whole octahedral map for a
119
+ * directional one. The blend is the same arithmetic whatever it is, and writing it once is what
120
+ * stops two of them disagreeing about what the weights mean.
121
+ */
122
+ export declare function sampleProbeVolume(blend: ProbeBlend, values: Float32Array, channels: number, out: Float32Array): Float32Array;
123
+ /**
124
+ * Which probes to rebake on this frame.
125
+ *
126
+ * **A grid rebaked in one frame is six draws of the world per probe.** Sixty-four probes is 384
127
+ * submissions in one frame and a hitch a viewer sees; `EnvProbeArray` already bakes a probe at a
128
+ * time for that reason and gates the whole grid until every layer is filled. What is here is the
129
+ * schedule, and the part of it that is easy to get wrong is not the bound — it is that **every
130
+ * probe is reached**. A subset chosen by distance, or by what moved, leaves some probe stale for
131
+ * ever, and a stale probe in a room whose light changed is a room lit by the previous scene.
132
+ *
133
+ * A plain round robin, so it is also reproducible: the same frame index asks for the same probes,
134
+ * which is what the replay fingerprint needs and what makes a bug in it repeatable.
135
+ */
136
+ export declare function probeUpdateSchedule(layers: number, perFrame: number, frameIndex: number, out: Int32Array): number;
@@ -0,0 +1,191 @@
1
+ /** The last level of the chain: a probe volume that knows where the walls are. */
2
+ import { nearestProbes } from '../probeGrid.js';
3
+ import { octInsetDir, octInsetUv } from '../shaders/octahedral.js';
4
+ /**
5
+ * The least variance a texel is treated as having, in square metres.
6
+ *
7
+ * **Without it the falloff is a step**, because a probe looking at a flat wall records the same
8
+ * distance in every direction of the cone and the variance is exactly zero — so the Chebyshev
9
+ * bound is zero everywhere past the wall and one everywhere in front of it, and the seam between
10
+ * them is a hard line that crawls as the probes are rebaked. Two hundredths of a square metre is
11
+ * about fourteen centimetres of slop, which is below a probe spacing anybody would choose and
12
+ * above the depth error of a bake.
13
+ */
14
+ export const PROBE_VISIBILITY_VARIANCE_FLOOR = 0.02;
15
+ /**
16
+ * How sharply the Chebyshev bound is turned into a weight.
17
+ *
18
+ * The bound is generous by construction — it is an inequality, not a measurement — so a probe that
19
+ * is mostly occluded still scores a third of a fully visible one. Cubing it turns that third into
20
+ * a twenty-seventh, which is the difference between a wall that reads as a wall and one that reads
21
+ * as a curtain. Three is what the technique this follows uses, and it is here as a constant so the
22
+ * next person can see it is a choice.
23
+ */
24
+ export const PROBE_VISIBILITY_SHARPNESS = 3;
25
+ /**
26
+ * Below this share, a probe is treated as contributing nothing at all.
27
+ *
28
+ * **A weight that is merely very small is not the same as zero, and the difference is the last
29
+ * percent of the leak.** A probe on the far side of a wall scores about four ten-thousandths here;
30
+ * kept, it puts that fraction of the lit room into the dark one, which is faint and is visible
31
+ * because the eye reads a gradient in a dark room extremely well. Crushed, the wall is a wall.
32
+ *
33
+ * It is applied to the *product* of the trilinear weight and the visibility, because that is what a
34
+ * probe actually contributes, and it is applied in `visibleProbes` rather than in
35
+ * `probeVisibilityWeight` — the raw bound stays continuous, which is what lets it be tested as a
36
+ * falloff rather than as a switch.
37
+ */
38
+ export const PROBE_VISIBILITY_CRUSH = 1e-2;
39
+ export function createProbeVisibility(edge, layers) {
40
+ const side = Math.trunc(edge);
41
+ if (!(side >= 4)) {
42
+ throw new Error(`createProbeVisibility: edge is ${String(edge)} and the minimum is 4. The outer ring is a ` +
43
+ 'gutter, so anything smaller is two real texels a side.');
44
+ }
45
+ const count = Math.max(1, Math.trunc(layers));
46
+ return { moments: new Float32Array(side * side * 2 * count), edge: side, layers: count };
47
+ }
48
+ /** Scratch, so neither baking nor sampling allocates. */
49
+ const UV = [0, 0];
50
+ const DIR = [0, 0, 0];
51
+ const PROBE_AT = [0, 0, 0];
52
+ /**
53
+ * Fill one probe's map from what it can see.
54
+ *
55
+ * `distanceInDirection` is the bake: how far the geometry is from this probe along a direction.
56
+ * Given as a callback because how that is measured is the caller's business — a `traceField` march
57
+ * offline, a depth cube read back, or a closed form in a test.
58
+ */
59
+ export function bakeProbeVisibility(visibility, layer, distanceInDirection) {
60
+ const { edge, moments } = visibility;
61
+ const base = layer * edge * edge * 2;
62
+ for (let ty = 0; ty < edge; ty++) {
63
+ for (let tx = 0; tx < edge; tx++) {
64
+ octInsetDir((tx + 0.5) / edge, (ty + 0.5) / edge, edge, DIR);
65
+ const distance = distanceInDirection(DIR[0], DIR[1], DIR[2]);
66
+ const at = base + (tx + ty * edge) * 2;
67
+ moments[at] = distance;
68
+ moments[at + 1] = distance * distance;
69
+ }
70
+ }
71
+ }
72
+ /** The moments one probe recorded in one direction, as a two-element read. */
73
+ function momentsAt(visibility, layer, x, y, z) {
74
+ const { edge, moments } = visibility;
75
+ octInsetUv(x, y, z, edge, UV);
76
+ const tx = clampTexel(Math.floor(UV[0] * edge), edge);
77
+ const ty = clampTexel(Math.floor(UV[1] * edge), edge);
78
+ const at = layer * edge * edge * 2 + (tx + ty * edge) * 2;
79
+ return { mean: moments[at], meanSquare: moments[at + 1] };
80
+ }
81
+ function clampTexel(value, edge) {
82
+ return value < 0 ? 0 : value > edge - 1 ? edge - 1 : value;
83
+ }
84
+ /**
85
+ * How much a probe's answer is trusted at a point this far away in this direction.
86
+ *
87
+ * One in front of what the probe saw; a Chebyshev bound on the depth variance behind it, cubed.
88
+ * Continuous everywhere, which is what `visibleProbes` then crushes rather than this.
89
+ */
90
+ export function probeVisibilityWeight(visibility, layer, x, y, z, distanceM) {
91
+ const { mean, meanSquare } = momentsAt(visibility, layer, x, y, z);
92
+ if (distanceM <= mean)
93
+ return 1;
94
+ const variance = Math.max(Math.abs(mean * mean - meanSquare), PROBE_VISIBILITY_VARIANCE_FLOOR);
95
+ const beyond = distanceM - mean;
96
+ const chebyshev = variance / (variance + beyond * beyond);
97
+ return chebyshev ** PROBE_VISIBILITY_SHARPNESS;
98
+ }
99
+ /**
100
+ * The eight probes around a point, weighted by what each of them can actually see.
101
+ *
102
+ * With `visibility` null this is `nearestProbes` and nothing else — see the module header for why
103
+ * that matters. With one, each corner's trilinear weight is multiplied by its visibility, the
104
+ * result is crushed and the eight are renormalised so they still sum to one. **Renormalising is
105
+ * what keeps the blend from losing energy**, which is the property the white furnace in Task 10
106
+ * rests on: a solution that quietly drops a share of its probes darkens every bounce.
107
+ *
108
+ * **A point every probe is blind to still gets an answer**, because this is the last level of the
109
+ * chain and there is nothing behind it. When every weight crushes to zero — a point inside a solid,
110
+ * or a sealed void with a probe on each side of a wall — the nearest probe by distance takes the
111
+ * whole weight. That is the coarse answer this level exists to always have.
112
+ */
113
+ export function visibleProbes(grid, visibility, x, y, z, out) {
114
+ nearestProbes(grid, x, y, z, out);
115
+ if (visibility === null)
116
+ return out;
117
+ let total = 0;
118
+ let nearest = 0;
119
+ let nearestDistance = Infinity;
120
+ for (let slot = 0; slot < out.layers.length; slot++) {
121
+ const layer = out.layers[slot];
122
+ grid.positionOf(layer, PROBE_AT);
123
+ const dx = x - PROBE_AT[0];
124
+ const dy = y - PROBE_AT[1];
125
+ const dz = z - PROBE_AT[2];
126
+ const distance = Math.hypot(dx, dy, dz);
127
+ if (distance < nearestDistance) {
128
+ nearestDistance = distance;
129
+ nearest = slot;
130
+ }
131
+ /* A point standing exactly on a probe is seen by it, and has no direction to ask about. */
132
+ const seen = distance > 0 ? probeVisibilityWeight(visibility, layer, dx, dy, dz, distance) : 1;
133
+ const weight = out.weights[slot] * seen;
134
+ const kept = weight < PROBE_VISIBILITY_CRUSH ? 0 : weight;
135
+ out.weights[slot] = kept;
136
+ total += kept;
137
+ }
138
+ if (total > 0) {
139
+ for (let slot = 0; slot < out.weights.length; slot++) {
140
+ out.weights[slot] = out.weights[slot] / total;
141
+ }
142
+ return out;
143
+ }
144
+ out.weights.fill(0);
145
+ out.weights[nearest] = 1;
146
+ return out;
147
+ }
148
+ /**
149
+ * Blend what the probes carry, `channels` floats each, into a target the caller owns.
150
+ *
151
+ * Channel count rather than a fixed three, because what a probe carries is the caller's decision:
152
+ * three for an ambient colour, nine for a spherical-harmonic band, a whole octahedral map for a
153
+ * directional one. The blend is the same arithmetic whatever it is, and writing it once is what
154
+ * stops two of them disagreeing about what the weights mean.
155
+ */
156
+ export function sampleProbeVolume(blend, values, channels, out) {
157
+ for (let channel = 0; channel < channels; channel++)
158
+ out[channel] = 0;
159
+ for (let slot = 0; slot < blend.layers.length; slot++) {
160
+ const weight = blend.weights[slot];
161
+ if (weight === 0)
162
+ continue;
163
+ const base = blend.layers[slot] * channels;
164
+ for (let channel = 0; channel < channels; channel++) {
165
+ out[channel] = out[channel] + values[base + channel] * weight;
166
+ }
167
+ }
168
+ return out;
169
+ }
170
+ /**
171
+ * Which probes to rebake on this frame.
172
+ *
173
+ * **A grid rebaked in one frame is six draws of the world per probe.** Sixty-four probes is 384
174
+ * submissions in one frame and a hitch a viewer sees; `EnvProbeArray` already bakes a probe at a
175
+ * time for that reason and gates the whole grid until every layer is filled. What is here is the
176
+ * schedule, and the part of it that is easy to get wrong is not the bound — it is that **every
177
+ * probe is reached**. A subset chosen by distance, or by what moved, leaves some probe stale for
178
+ * ever, and a stale probe in a room whose light changed is a room lit by the previous scene.
179
+ *
180
+ * A plain round robin, so it is also reproducible: the same frame index asks for the same probes,
181
+ * which is what the replay fingerprint needs and what makes a bug in it repeatable.
182
+ */
183
+ export function probeUpdateSchedule(layers, perFrame, frameIndex, out) {
184
+ const count = Math.min(Math.max(0, Math.trunc(perFrame)), layers, out.length);
185
+ if (count < 1 || layers < 1)
186
+ return 0;
187
+ const start = (((Math.trunc(frameIndex) * count) % layers) + layers) % layers;
188
+ for (let i = 0; i < count; i++)
189
+ out[i] = (start + i) % layers;
190
+ return count;
191
+ }
@@ -0,0 +1,73 @@
1
+ /** A reflection is an indirect ray with a narrow cone, so it goes down the same chain. */
2
+ import type { ReadonlyVec3 } from 'gl-matrix';
3
+ import type { GiResources, IndirectResult } from './chain.ts';
4
+ /**
5
+ * **The plan asked for a fallback bolted onto `screenSpaceReflection.ts`, and this is beside it
6
+ * instead.** That module is in `render/` and the chain is in `render/gi/`; importing upward would
7
+ * put the whole global-illumination tree behind every scene that draws a mirror, which is the
8
+ * opposite of the "off by default" this wave is built around. The dependency runs the other way
9
+ * and `screenSpaceReflection.ts` is untouched — a scene that never asks for the fallback imports
10
+ * none of this and renders exactly the frame it rendered before.
11
+ *
12
+ * What is left once the chain exists is small, and that is the point: **a reflection is an
13
+ * indirect ray whose direction is the mirror of the view and whose cone is the roughness.** Writing
14
+ * a second fallback for it would be a second thing to disagree with the first.
15
+ */
16
+ /** A reflective surface, as a shading path holds one. */
17
+ export interface ReflectiveSample {
18
+ readonly point: ReadonlyVec3;
19
+ readonly normal: ReadonlyVec3;
20
+ /** Which way the eye is looking, toward the surface. Need not be unit length. */
21
+ readonly view: ReadonlyVec3;
22
+ /** 0 is a mirror, 1 is fully diffuse. See `reflectionCone`. */
23
+ readonly roughness: number;
24
+ }
25
+ export interface ReflectionOptions {
26
+ /**
27
+ * Whether a ray the frame cannot answer continues into the world.
28
+ *
29
+ * **Off is the shipped behaviour and off is the default a caller should assume.** With it off
30
+ * this is `traceScreenSpaceRay` and nothing else: a miss is a miss, the surface keeps whatever
31
+ * the existing fade leaves it, and every scene renders the frame it rendered before this file
32
+ * existed. That is this wave's first global constraint, and it is asserted in the test rather
33
+ * than trusted.
34
+ */
35
+ readonly fallback: boolean;
36
+ }
37
+ /**
38
+ * Mirror a direction about a normal.
39
+ *
40
+ * `d - 2 (d . n) n`, with `n` normalised first because an interpolated normal is rarely unit — and
41
+ * a normal of length 1.01 mirrors to a direction that is not unit either, which a march then takes
42
+ * as a step length.
43
+ *
44
+ * **A direction already leaving the surface is mirrored back into it**, and that is not
45
+ * special-cased into something else. A caller that hands over a view vector pointing away from the
46
+ * surface has a bug one level up, and answering plausibly would hide it.
47
+ */
48
+ export declare function reflectDirection(direction: ReadonlyVec3, normal: ReadonlyVec3, out: Float32Array): Float32Array;
49
+ /**
50
+ * The cone half-angle a roughness reflects through.
51
+ *
52
+ * **`atan(roughness^2)`, which is the GGX lobe's own width rather than a curve chosen to look
53
+ * right.** The distribution's half-angle at half its peak is close to `atan(alpha)` and `alpha` is
54
+ * `roughness^2` in the parameterisation this engine uses everywhere else — `roughnessForLevel` in
55
+ * `prefilterEnvMap.ts` maps a mip chain by the same square, so a surface that samples level three
56
+ * of the prefiltered cube and a surface that cone-traces the world field agree about how blurred
57
+ * they are. Two curves here would be two blurs that diverge with distance.
58
+ *
59
+ * Zero is exactly zero, so a mirror traces a pencil ray and no epsilon creeps into the one case a
60
+ * viewer inspects most closely. One is `atan(1)`, a quarter turn's half — wide, and still short of
61
+ * the hemisphere a fully diffuse surface gathers over, which is `traceIndirect`'s job and not this.
62
+ */
63
+ export declare function reflectionCone(roughness: number): number;
64
+ /**
65
+ * Trace a reflection down the chain, or down its first level alone.
66
+ *
67
+ * With `fallback` off, only the screen answers and a miss stays a miss — which is what
68
+ * `screenSpaceReflection.ts` has always done and what keeps every published scene where it is.
69
+ * With it on, a ray leaving the frame continues into the world's distance field and, behind that,
70
+ * the probes: a mirror at the edge of the picture reflects the world rather than fading to a matte
71
+ * surface as the camera turns.
72
+ */
73
+ export declare function traceReflection(surface: ReflectiveSample, resources: GiResources, options: ReflectionOptions, out: IndirectResult): IndirectResult;
@@ -0,0 +1,109 @@
1
+ /** A reflection is an indirect ray with a narrow cone, so it goes down the same chain. */
2
+ import { GI_SOURCE_SCREEN, traceIndirect } from './chain.js';
3
+ /**
4
+ * Mirror a direction about a normal.
5
+ *
6
+ * `d - 2 (d . n) n`, with `n` normalised first because an interpolated normal is rarely unit — and
7
+ * a normal of length 1.01 mirrors to a direction that is not unit either, which a march then takes
8
+ * as a step length.
9
+ *
10
+ * **A direction already leaving the surface is mirrored back into it**, and that is not
11
+ * special-cased into something else. A caller that hands over a view vector pointing away from the
12
+ * surface has a bug one level up, and answering plausibly would hide it.
13
+ */
14
+ export function reflectDirection(direction, normal, out) {
15
+ const dx = direction[0] ?? 0;
16
+ const dy = direction[1] ?? 0;
17
+ const dz = direction[2] ?? 0;
18
+ let nx = normal[0] ?? 0;
19
+ let ny = normal[1] ?? 0;
20
+ let nz = normal[2] ?? 0;
21
+ const length = Math.hypot(nx, ny, nz);
22
+ if (length > 0) {
23
+ nx /= length;
24
+ ny /= length;
25
+ nz /= length;
26
+ }
27
+ const along = 2 * (dx * nx + dy * ny + dz * nz);
28
+ out[0] = dx - along * nx;
29
+ out[1] = dy - along * ny;
30
+ out[2] = dz - along * nz;
31
+ return out;
32
+ }
33
+ /**
34
+ * The cone half-angle a roughness reflects through.
35
+ *
36
+ * **`atan(roughness^2)`, which is the GGX lobe's own width rather than a curve chosen to look
37
+ * right.** The distribution's half-angle at half its peak is close to `atan(alpha)` and `alpha` is
38
+ * `roughness^2` in the parameterisation this engine uses everywhere else — `roughnessForLevel` in
39
+ * `prefilterEnvMap.ts` maps a mip chain by the same square, so a surface that samples level three
40
+ * of the prefiltered cube and a surface that cone-traces the world field agree about how blurred
41
+ * they are. Two curves here would be two blurs that diverge with distance.
42
+ *
43
+ * Zero is exactly zero, so a mirror traces a pencil ray and no epsilon creeps into the one case a
44
+ * viewer inspects most closely. One is `atan(1)`, a quarter turn's half — wide, and still short of
45
+ * the hemisphere a fully diffuse surface gathers over, which is `traceIndirect`'s job and not this.
46
+ */
47
+ export function reflectionCone(roughness) {
48
+ const clamped = roughness <= 0 ? 0 : roughness >= 1 ? 1 : roughness;
49
+ return Math.atan(clamped * clamped);
50
+ }
51
+ /** Scratch, because this runs per reflective pixel. */
52
+ const MIRRORED = new Float32Array(3);
53
+ /**
54
+ * Trace a reflection down the chain, or down its first level alone.
55
+ *
56
+ * With `fallback` off, only the screen answers and a miss stays a miss — which is what
57
+ * `screenSpaceReflection.ts` has always done and what keeps every published scene where it is.
58
+ * With it on, a ray leaving the frame continues into the world's distance field and, behind that,
59
+ * the probes: a mirror at the edge of the picture reflects the world rather than fading to a matte
60
+ * surface as the camera turns.
61
+ */
62
+ export function traceReflection(surface, resources, options, out) {
63
+ reflectDirection(surface.view, surface.normal, MIRRORED);
64
+ const ray = {
65
+ point: surface.point,
66
+ normal: surface.normal,
67
+ direction: MIRRORED,
68
+ coneAngle: reflectionCone(surface.roughness),
69
+ };
70
+ if (options.fallback)
71
+ return traceIndirect(ray, resources, out);
72
+ /*
73
+ * The screen alone. Reached by handing the chain a resource set whose other two levels cannot
74
+ * contribute — rather than by a second copy of the march — so the frame's own answer is
75
+ * byte-identical either way and cannot drift from the fallback path's.
76
+ */
77
+ traceIndirect(ray, { ...resources, ...SCREEN_ONLY }, out);
78
+ if (out.blend[0] === 0) {
79
+ out.hit = false;
80
+ out.radiance.fill(0);
81
+ out.blend.fill(0);
82
+ out.source = GI_SOURCE_SCREEN;
83
+ }
84
+ else {
85
+ /* What the frame could not answer is left unanswered rather than handed downward. */
86
+ out.radiance[0] = out.radiance[0] / out.blend[0];
87
+ out.radiance[1] = out.radiance[1] / out.blend[0];
88
+ out.radiance[2] = out.radiance[2] / out.blend[0];
89
+ out.blend[0] = 1;
90
+ out.blend[1] = 0;
91
+ out.blend[2] = 0;
92
+ out.source = GI_SOURCE_SCREEN;
93
+ }
94
+ return out;
95
+ }
96
+ /**
97
+ * The two levels below the screen, silenced.
98
+ *
99
+ * A field whose march can never hit and a probe set that carries nothing, so `traceIndirect` runs
100
+ * its ordinary path and the screen's share is all there is to normalise. Frozen at module scope
101
+ * because this is a per-pixel call and the rule about allocating in one is absolute.
102
+ */
103
+ const SCREEN_ONLY = {
104
+ fieldRadiance: (_x, _y, _z, out) => {
105
+ out.fill(0);
106
+ },
107
+ probeValues: new Float32Array(0),
108
+ probeChannels: 0,
109
+ };