@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,143 @@
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
+
23
+ import { octInsetDir } from '../shaders/octahedral.ts';
24
+
25
+ /**
26
+ * Why a scene is not lit by what bounces on WebGL2, in words, so both backends read one sentence.
27
+ *
28
+ * **A probe is a few hundred rays and that is a compute dispatch.** This backend has no compute
29
+ * stage, and the alternatives are both worse than saying so: tracing on the processor is a
30
+ * main-thread cost proportional to the grid, and approximating with more frequent cube bakes means
31
+ * the two backends light one scene differently — which is the disagreement every published scene's
32
+ * pixel gate exists to prevent.
33
+ *
34
+ * Written here rather than in either renderer for the reason `OIT_MULTISAMPLE_REFUSAL` is written
35
+ * outside both: it is a decision the two share, so it belongs where neither owns it.
36
+ */
37
+ export const INDIRECT_LIGHT_WEBGL2_REFUSAL =
38
+ 'driftengine: indirect light traces a few hundred rays a probe, which is a compute dispatch, ' +
39
+ 'and WebGL2 has no compute stage — so `quality.indirectLight` lights nothing on this backend ' +
40
+ 'and the probe grid keeps the bake it has always had. It is WebGPU only, as reconstruction is.';
41
+
42
+ /**
43
+ * Directions a probe traces per refresh.
44
+ *
45
+ * **A few hundred, and the number is a cost rather than a quality.** A refresh is this many rays
46
+ * down a three-level chain, and `probeUpdateSchedule` decides how many probes pay it a frame. The
47
+ * convergence comes from the rotation between refreshes, not from this count, so it is set where a
48
+ * single refresh is already recognisable rather than where one refresh is final.
49
+ */
50
+ export const PROBE_DIRECTIONS = 256;
51
+
52
+ /**
53
+ * The golden angle, which is what spaces a spiral's points as far apart as a count allows.
54
+ *
55
+ * Exported because `shaders/gi/probeBake.wgsl.ts` interpolates it: a shader cannot import a
56
+ * constant, so the choice is one value written here and pasted in, or two literals kept in step by
57
+ * hand. `probe-bake-parity.mjs` checks the result either way, but only one of them can be right by
58
+ * construction.
59
+ */
60
+ export const GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));
61
+
62
+ /**
63
+ * How far the set turns between one refresh and the next, in turns.
64
+ *
65
+ * The golden ratio's fractional part: successive frames land their offsets as far from each other
66
+ * as the sequence allows, for the same reason the spiral's own points do, and it never repeats.
67
+ */
68
+ export const FRAME_TURN = 0.618033988749895;
69
+
70
+ /**
71
+ * Direction `index` of the set this `frame` traces, into `out`.
72
+ *
73
+ * The spiral runs `z` from near `+1` to near `-1` in equal steps and turns by the golden angle at
74
+ * each, which is the construction whose points are evenly spread on a sphere; the frame's own
75
+ * rotation is added to that turn.
76
+ */
77
+ export function probeDirection(index: number, frame: number, out: Float32Array): void {
78
+ const i = ((index % PROBE_DIRECTIONS) + PROBE_DIRECTIONS) % PROBE_DIRECTIONS;
79
+ /* Offset by a half step at each end, so no direction lands exactly on a pole — where the turn
80
+ has no effect and two frames would trace the same ray. */
81
+ const z = 1 - (2 * i + 1) / PROBE_DIRECTIONS;
82
+ const radius = Math.sqrt(Math.max(0, 1 - z * z));
83
+ const angle = i * GOLDEN_ANGLE + frame * FRAME_TURN * Math.PI * 2;
84
+ out[0] = Math.cos(angle) * radius;
85
+ out[1] = Math.sin(angle) * radius;
86
+ out[2] = z;
87
+ }
88
+
89
+ const TEXEL_DIR = new Float32Array(3);
90
+
91
+ /**
92
+ * The cosine convolution of `count` radiance samples into an octahedral map of `edge` texels.
93
+ *
94
+ * Each texel's direction is the normal of a surface facing that way, and its value is the
95
+ * irradiance such a surface receives: every sample weighted by how much of its hemisphere it
96
+ * occupies, which for a cosine lobe is `max(0, dot(normal, direction))`.
97
+ *
98
+ * **Normalised by the weights that were actually used, and that is the whole of the white-furnace
99
+ * property.** Dividing by an analytic constant — pi, or the sample count — is right only in the
100
+ * limit and wrong for any finite set, so a uniformly white sphere would convolve to something
101
+ * slightly other than white and a room would brighten or darken a little at every refresh. Dividing
102
+ * by the sum of the weights is exact for any set, and it is what makes a constant come back
103
+ * constant to the last place a float holds.
104
+ */
105
+ export function convolveProbe(
106
+ samples: Float32Array,
107
+ directions: Float32Array,
108
+ count: number,
109
+ edge: number,
110
+ out: Float32Array,
111
+ ): void {
112
+ const texels = Math.max(1, Math.floor(edge));
113
+ for (let v = 0; v < texels; v += 1) {
114
+ for (let u = 0; u < texels; u += 1) {
115
+ octInsetDir((u + 0.5) / texels, (v + 0.5) / texels, texels, TEXEL_DIR as unknown as never);
116
+ const nx = TEXEL_DIR[0] as number;
117
+ const ny = TEXEL_DIR[1] as number;
118
+ const nz = TEXEL_DIR[2] as number;
119
+ let weights = 0;
120
+ let r = 0;
121
+ let g = 0;
122
+ let b = 0;
123
+ for (let i = 0; i < count; i += 1) {
124
+ const weight =
125
+ nx * (directions[i * 3] as number) +
126
+ ny * (directions[i * 3 + 1] as number) +
127
+ nz * (directions[i * 3 + 2] as number);
128
+ if (weight <= 0) continue;
129
+ weights += weight;
130
+ r += (samples[i * 3] as number) * weight;
131
+ g += (samples[i * 3 + 1] as number) * weight;
132
+ b += (samples[i * 3 + 2] as number) * weight;
133
+ }
134
+ const at = (v * texels + u) * 3;
135
+ /* No weight at all is a texel no sample faced, which a set this dense does not produce —
136
+ and answering zero rather than dividing is what keeps it from being a hole if it ever does. */
137
+ const scale = weights > 0 ? 1 / weights : 0;
138
+ out[at] = Math.max(0, r * scale);
139
+ out[at + 1] = Math.max(0, g * scale);
140
+ out[at + 2] = Math.max(0, b * scale);
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,288 @@
1
+ /** The last level of the chain: a probe volume that knows where the walls are. */
2
+
3
+ import { nearestProbes } from '../probeGrid.ts';
4
+ import { octInsetDir, octInsetUv } from '../shaders/octahedral.ts';
5
+
6
+ import type { vec2, vec3 } from 'gl-matrix';
7
+ import type { Vec3 } from '../../math/color.ts';
8
+ import type { ProbeBlend, ProbeGrid } from '../probeGrid.ts';
9
+
10
+ /**
11
+ * **A probe volume is never wrong and only ever coarse, and that is what it is for.**
12
+ *
13
+ * It is the level the chain falls back to when the screen could not answer and the world field
14
+ * could not either, so it has nothing behind it and must always answer. What makes that safe is
15
+ * that it is an interpolation between measurements rather than a trace: no ray to escape, no frame
16
+ * to leave, no resolution to run out of. The cost is that it is smooth where the world is not.
17
+ *
18
+ * **The one way it *is* wrong is the wall, and every implementation of this has shipped it.**
19
+ * `probeGrid.ts`'s blend is eight corners and three lerps — it reads the lattice and the point and
20
+ * nothing else — so a point in a dark room takes a trilinear share of a probe standing in the lit
21
+ * room next door. Light comes out of solid walls, and no amount of probe density fixes it: halving
22
+ * the spacing halves the width of the glow and leaves it exactly as bright.
23
+ *
24
+ * That is not a defect in `probeGrid.ts`. Its header argues for a bare lattice at length and the
25
+ * argument is about *uniform pressure* — free placement wants a position and an extent per probe
26
+ * against a flat shader already 1.7x over WebGL2's guaranteed `MAX_FRAGMENT_UNIFORM_VECTORS`. This
27
+ * adds the term that was missing without touching any of that: a second array beside the radiance
28
+ * one, holding what each probe could see, and a weight computed from it.
29
+ *
30
+ * **Passing `null` for the visibility is the whole of the off switch**, and it lands on
31
+ * `nearestProbes` itself rather than on a path that reproduces it — so a scene with no visibility
32
+ * volume renders exactly the frame it rendered before this file existed.
33
+ */
34
+
35
+ /**
36
+ * What each probe could see, as one octahedral map of depth moments per probe.
37
+ *
38
+ * **Two moments and not one distance.** A single distance gives a binary answer — in front of the
39
+ * wall or behind it — and a binary answer aliases along every edge in the scene, because the probe
40
+ * that can see a point and the probe that cannot are adjacent. The mean and the mean of squares
41
+ * give a *variance*, and a Chebyshev bound on that variance is a soft weight: confident where the
42
+ * geometry is flat, uncertain where it is not, which is exactly where a hard answer would be wrong.
43
+ * It is the same arithmetic a variance shadow map uses, for the same reason.
44
+ *
45
+ * Octahedral rather than a cube, matching `EnvProbeArray`: one layer a probe, no faces to seam, and
46
+ * no sampler per probe. `octInsetUv` is the mapping, so a direction lands in the same texel here as
47
+ * it does in the radiance map.
48
+ */
49
+ export interface ProbeVisibility {
50
+ /** Two floats a texel: mean distance, then mean of squares. One map a probe, layer-major. */
51
+ readonly moments: Float32Array;
52
+ /** Texels a side, the outer ring being the gutter `octInsetUv` insets past. */
53
+ readonly edge: number;
54
+ readonly layers: number;
55
+ }
56
+
57
+ /**
58
+ * The least variance a texel is treated as having, in square metres.
59
+ *
60
+ * **Without it the falloff is a step**, because a probe looking at a flat wall records the same
61
+ * distance in every direction of the cone and the variance is exactly zero — so the Chebyshev
62
+ * bound is zero everywhere past the wall and one everywhere in front of it, and the seam between
63
+ * them is a hard line that crawls as the probes are rebaked. Two hundredths of a square metre is
64
+ * about fourteen centimetres of slop, which is below a probe spacing anybody would choose and
65
+ * above the depth error of a bake.
66
+ */
67
+ export const PROBE_VISIBILITY_VARIANCE_FLOOR = 0.02;
68
+
69
+ /**
70
+ * How sharply the Chebyshev bound is turned into a weight.
71
+ *
72
+ * The bound is generous by construction — it is an inequality, not a measurement — so a probe that
73
+ * is mostly occluded still scores a third of a fully visible one. Cubing it turns that third into
74
+ * a twenty-seventh, which is the difference between a wall that reads as a wall and one that reads
75
+ * as a curtain. Three is what the technique this follows uses, and it is here as a constant so the
76
+ * next person can see it is a choice.
77
+ */
78
+ export const PROBE_VISIBILITY_SHARPNESS = 3;
79
+
80
+ /**
81
+ * Below this share, a probe is treated as contributing nothing at all.
82
+ *
83
+ * **A weight that is merely very small is not the same as zero, and the difference is the last
84
+ * percent of the leak.** A probe on the far side of a wall scores about four ten-thousandths here;
85
+ * kept, it puts that fraction of the lit room into the dark one, which is faint and is visible
86
+ * because the eye reads a gradient in a dark room extremely well. Crushed, the wall is a wall.
87
+ *
88
+ * It is applied to the *product* of the trilinear weight and the visibility, because that is what a
89
+ * probe actually contributes, and it is applied in `visibleProbes` rather than in
90
+ * `probeVisibilityWeight` — the raw bound stays continuous, which is what lets it be tested as a
91
+ * falloff rather than as a switch.
92
+ */
93
+ export const PROBE_VISIBILITY_CRUSH = 1e-2;
94
+
95
+ export function createProbeVisibility(edge: number, layers: number): ProbeVisibility {
96
+ const side = Math.trunc(edge);
97
+ if (!(side >= 4)) {
98
+ throw new Error(
99
+ `createProbeVisibility: edge is ${String(edge)} and the minimum is 4. The outer ring is a ` +
100
+ 'gutter, so anything smaller is two real texels a side.',
101
+ );
102
+ }
103
+ const count = Math.max(1, Math.trunc(layers));
104
+ return { moments: new Float32Array(side * side * 2 * count), edge: side, layers: count };
105
+ }
106
+
107
+ /** Scratch, so neither baking nor sampling allocates. */
108
+ const UV: vec2 = [0, 0] as unknown as vec2;
109
+ const DIR: vec3 = [0, 0, 0] as unknown as vec3;
110
+ const PROBE_AT: Vec3 = [0, 0, 0];
111
+
112
+ /**
113
+ * Fill one probe's map from what it can see.
114
+ *
115
+ * `distanceInDirection` is the bake: how far the geometry is from this probe along a direction.
116
+ * Given as a callback because how that is measured is the caller's business — a `traceField` march
117
+ * offline, a depth cube read back, or a closed form in a test.
118
+ */
119
+ export function bakeProbeVisibility(
120
+ visibility: ProbeVisibility,
121
+ layer: number,
122
+ distanceInDirection: (x: number, y: number, z: number) => number,
123
+ ): void {
124
+ const { edge, moments } = visibility;
125
+ const base = layer * edge * edge * 2;
126
+ for (let ty = 0; ty < edge; ty++) {
127
+ for (let tx = 0; tx < edge; tx++) {
128
+ octInsetDir((tx + 0.5) / edge, (ty + 0.5) / edge, edge, DIR);
129
+ const distance = distanceInDirection(DIR[0], DIR[1], DIR[2]);
130
+ const at = base + (tx + ty * edge) * 2;
131
+ moments[at] = distance;
132
+ moments[at + 1] = distance * distance;
133
+ }
134
+ }
135
+ }
136
+
137
+ /** The moments one probe recorded in one direction, as a two-element read. */
138
+ function momentsAt(
139
+ visibility: ProbeVisibility,
140
+ layer: number,
141
+ x: number,
142
+ y: number,
143
+ z: number,
144
+ ): { mean: number; meanSquare: number } {
145
+ const { edge, moments } = visibility;
146
+ octInsetUv(x, y, z, edge, UV);
147
+ const tx = clampTexel(Math.floor(UV[0] * edge), edge);
148
+ const ty = clampTexel(Math.floor(UV[1] * edge), edge);
149
+ const at = layer * edge * edge * 2 + (tx + ty * edge) * 2;
150
+ return { mean: moments[at] as number, meanSquare: moments[at + 1] as number };
151
+ }
152
+
153
+ function clampTexel(value: number, edge: number): number {
154
+ return value < 0 ? 0 : value > edge - 1 ? edge - 1 : value;
155
+ }
156
+
157
+ /**
158
+ * How much a probe's answer is trusted at a point this far away in this direction.
159
+ *
160
+ * One in front of what the probe saw; a Chebyshev bound on the depth variance behind it, cubed.
161
+ * Continuous everywhere, which is what `visibleProbes` then crushes rather than this.
162
+ */
163
+ export function probeVisibilityWeight(
164
+ visibility: ProbeVisibility,
165
+ layer: number,
166
+ x: number,
167
+ y: number,
168
+ z: number,
169
+ distanceM: number,
170
+ ): number {
171
+ const { mean, meanSquare } = momentsAt(visibility, layer, x, y, z);
172
+ if (distanceM <= mean) return 1;
173
+ const variance = Math.max(Math.abs(mean * mean - meanSquare), PROBE_VISIBILITY_VARIANCE_FLOOR);
174
+ const beyond = distanceM - mean;
175
+ const chebyshev = variance / (variance + beyond * beyond);
176
+ return chebyshev ** PROBE_VISIBILITY_SHARPNESS;
177
+ }
178
+
179
+ /**
180
+ * The eight probes around a point, weighted by what each of them can actually see.
181
+ *
182
+ * With `visibility` null this is `nearestProbes` and nothing else — see the module header for why
183
+ * that matters. With one, each corner's trilinear weight is multiplied by its visibility, the
184
+ * result is crushed and the eight are renormalised so they still sum to one. **Renormalising is
185
+ * what keeps the blend from losing energy**, which is the property the white furnace in Task 10
186
+ * rests on: a solution that quietly drops a share of its probes darkens every bounce.
187
+ *
188
+ * **A point every probe is blind to still gets an answer**, because this is the last level of the
189
+ * chain and there is nothing behind it. When every weight crushes to zero — a point inside a solid,
190
+ * or a sealed void with a probe on each side of a wall — the nearest probe by distance takes the
191
+ * whole weight. That is the coarse answer this level exists to always have.
192
+ */
193
+ export function visibleProbes(
194
+ grid: ProbeGrid,
195
+ visibility: ProbeVisibility | null,
196
+ x: number,
197
+ y: number,
198
+ z: number,
199
+ out: ProbeBlend,
200
+ ): ProbeBlend {
201
+ nearestProbes(grid, x, y, z, out);
202
+ if (visibility === null) return out;
203
+
204
+ let total = 0;
205
+ let nearest = 0;
206
+ let nearestDistance = Infinity;
207
+ for (let slot = 0; slot < out.layers.length; slot++) {
208
+ const layer = out.layers[slot] as number;
209
+ grid.positionOf(layer, PROBE_AT);
210
+ const dx = x - PROBE_AT[0];
211
+ const dy = y - PROBE_AT[1];
212
+ const dz = z - PROBE_AT[2];
213
+ const distance = Math.hypot(dx, dy, dz);
214
+ if (distance < nearestDistance) {
215
+ nearestDistance = distance;
216
+ nearest = slot;
217
+ }
218
+ /* A point standing exactly on a probe is seen by it, and has no direction to ask about. */
219
+ const seen = distance > 0 ? probeVisibilityWeight(visibility, layer, dx, dy, dz, distance) : 1;
220
+ const weight = (out.weights[slot] as number) * seen;
221
+ const kept = weight < PROBE_VISIBILITY_CRUSH ? 0 : weight;
222
+ out.weights[slot] = kept;
223
+ total += kept;
224
+ }
225
+
226
+ if (total > 0) {
227
+ for (let slot = 0; slot < out.weights.length; slot++) {
228
+ out.weights[slot] = (out.weights[slot] as number) / total;
229
+ }
230
+ return out;
231
+ }
232
+
233
+ out.weights.fill(0);
234
+ out.weights[nearest] = 1;
235
+ return out;
236
+ }
237
+
238
+ /**
239
+ * Blend what the probes carry, `channels` floats each, into a target the caller owns.
240
+ *
241
+ * Channel count rather than a fixed three, because what a probe carries is the caller's decision:
242
+ * three for an ambient colour, nine for a spherical-harmonic band, a whole octahedral map for a
243
+ * directional one. The blend is the same arithmetic whatever it is, and writing it once is what
244
+ * stops two of them disagreeing about what the weights mean.
245
+ */
246
+ export function sampleProbeVolume(
247
+ blend: ProbeBlend,
248
+ values: Float32Array,
249
+ channels: number,
250
+ out: Float32Array,
251
+ ): Float32Array {
252
+ for (let channel = 0; channel < channels; channel++) out[channel] = 0;
253
+ for (let slot = 0; slot < blend.layers.length; slot++) {
254
+ const weight = blend.weights[slot] as number;
255
+ if (weight === 0) continue;
256
+ const base = (blend.layers[slot] as number) * channels;
257
+ for (let channel = 0; channel < channels; channel++) {
258
+ out[channel] = (out[channel] as number) + (values[base + channel] as number) * weight;
259
+ }
260
+ }
261
+ return out;
262
+ }
263
+
264
+ /**
265
+ * Which probes to rebake on this frame.
266
+ *
267
+ * **A grid rebaked in one frame is six draws of the world per probe.** Sixty-four probes is 384
268
+ * submissions in one frame and a hitch a viewer sees; `EnvProbeArray` already bakes a probe at a
269
+ * time for that reason and gates the whole grid until every layer is filled. What is here is the
270
+ * schedule, and the part of it that is easy to get wrong is not the bound — it is that **every
271
+ * probe is reached**. A subset chosen by distance, or by what moved, leaves some probe stale for
272
+ * ever, and a stale probe in a room whose light changed is a room lit by the previous scene.
273
+ *
274
+ * A plain round robin, so it is also reproducible: the same frame index asks for the same probes,
275
+ * which is what the replay fingerprint needs and what makes a bug in it repeatable.
276
+ */
277
+ export function probeUpdateSchedule(
278
+ layers: number,
279
+ perFrame: number,
280
+ frameIndex: number,
281
+ out: Int32Array,
282
+ ): number {
283
+ const count = Math.min(Math.max(0, Math.trunc(perFrame)), layers, out.length);
284
+ if (count < 1 || layers < 1) return 0;
285
+ const start = (((Math.trunc(frameIndex) * count) % layers) + layers) % layers;
286
+ for (let i = 0; i < count; i++) out[i] = (start + i) % layers;
287
+ return count;
288
+ }
@@ -0,0 +1,163 @@
1
+ /** A reflection is an indirect ray with a narrow cone, so it goes down the same chain. */
2
+
3
+ import { GI_SOURCE_SCREEN, traceIndirect } from './chain.ts';
4
+
5
+ import type { ReadonlyVec3 } from 'gl-matrix';
6
+ import type { GiResources, IndirectResult } from './chain.ts';
7
+
8
+ /**
9
+ * **The plan asked for a fallback bolted onto `screenSpaceReflection.ts`, and this is beside it
10
+ * instead.** That module is in `render/` and the chain is in `render/gi/`; importing upward would
11
+ * put the whole global-illumination tree behind every scene that draws a mirror, which is the
12
+ * opposite of the "off by default" this wave is built around. The dependency runs the other way
13
+ * and `screenSpaceReflection.ts` is untouched — a scene that never asks for the fallback imports
14
+ * none of this and renders exactly the frame it rendered before.
15
+ *
16
+ * What is left once the chain exists is small, and that is the point: **a reflection is an
17
+ * indirect ray whose direction is the mirror of the view and whose cone is the roughness.** Writing
18
+ * a second fallback for it would be a second thing to disagree with the first.
19
+ */
20
+
21
+ /** A reflective surface, as a shading path holds one. */
22
+ export interface ReflectiveSample {
23
+ readonly point: ReadonlyVec3;
24
+ readonly normal: ReadonlyVec3;
25
+ /** Which way the eye is looking, toward the surface. Need not be unit length. */
26
+ readonly view: ReadonlyVec3;
27
+ /** 0 is a mirror, 1 is fully diffuse. See `reflectionCone`. */
28
+ readonly roughness: number;
29
+ }
30
+
31
+ export interface ReflectionOptions {
32
+ /**
33
+ * Whether a ray the frame cannot answer continues into the world.
34
+ *
35
+ * **Off is the shipped behaviour and off is the default a caller should assume.** With it off
36
+ * this is `traceScreenSpaceRay` and nothing else: a miss is a miss, the surface keeps whatever
37
+ * the existing fade leaves it, and every scene renders the frame it rendered before this file
38
+ * existed. That is this wave's first global constraint, and it is asserted in the test rather
39
+ * than trusted.
40
+ */
41
+ readonly fallback: boolean;
42
+ }
43
+
44
+ /**
45
+ * Mirror a direction about a normal.
46
+ *
47
+ * `d - 2 (d . n) n`, with `n` normalised first because an interpolated normal is rarely unit — and
48
+ * a normal of length 1.01 mirrors to a direction that is not unit either, which a march then takes
49
+ * as a step length.
50
+ *
51
+ * **A direction already leaving the surface is mirrored back into it**, and that is not
52
+ * special-cased into something else. A caller that hands over a view vector pointing away from the
53
+ * surface has a bug one level up, and answering plausibly would hide it.
54
+ */
55
+ export function reflectDirection(
56
+ direction: ReadonlyVec3,
57
+ normal: ReadonlyVec3,
58
+ out: Float32Array,
59
+ ): Float32Array {
60
+ const dx = direction[0] ?? 0;
61
+ const dy = direction[1] ?? 0;
62
+ const dz = direction[2] ?? 0;
63
+ let nx = normal[0] ?? 0;
64
+ let ny = normal[1] ?? 0;
65
+ let nz = normal[2] ?? 0;
66
+ const length = Math.hypot(nx, ny, nz);
67
+ if (length > 0) {
68
+ nx /= length;
69
+ ny /= length;
70
+ nz /= length;
71
+ }
72
+ const along = 2 * (dx * nx + dy * ny + dz * nz);
73
+ out[0] = dx - along * nx;
74
+ out[1] = dy - along * ny;
75
+ out[2] = dz - along * nz;
76
+ return out;
77
+ }
78
+
79
+ /**
80
+ * The cone half-angle a roughness reflects through.
81
+ *
82
+ * **`atan(roughness^2)`, which is the GGX lobe's own width rather than a curve chosen to look
83
+ * right.** The distribution's half-angle at half its peak is close to `atan(alpha)` and `alpha` is
84
+ * `roughness^2` in the parameterisation this engine uses everywhere else — `roughnessForLevel` in
85
+ * `prefilterEnvMap.ts` maps a mip chain by the same square, so a surface that samples level three
86
+ * of the prefiltered cube and a surface that cone-traces the world field agree about how blurred
87
+ * they are. Two curves here would be two blurs that diverge with distance.
88
+ *
89
+ * Zero is exactly zero, so a mirror traces a pencil ray and no epsilon creeps into the one case a
90
+ * viewer inspects most closely. One is `atan(1)`, a quarter turn's half — wide, and still short of
91
+ * the hemisphere a fully diffuse surface gathers over, which is `traceIndirect`'s job and not this.
92
+ */
93
+ export function reflectionCone(roughness: number): number {
94
+ const clamped = roughness <= 0 ? 0 : roughness >= 1 ? 1 : roughness;
95
+ return Math.atan(clamped * clamped);
96
+ }
97
+
98
+ /** Scratch, because this runs per reflective pixel. */
99
+ const MIRRORED = new Float32Array(3);
100
+
101
+ /**
102
+ * Trace a reflection down the chain, or down its first level alone.
103
+ *
104
+ * With `fallback` off, only the screen answers and a miss stays a miss — which is what
105
+ * `screenSpaceReflection.ts` has always done and what keeps every published scene where it is.
106
+ * With it on, a ray leaving the frame continues into the world's distance field and, behind that,
107
+ * the probes: a mirror at the edge of the picture reflects the world rather than fading to a matte
108
+ * surface as the camera turns.
109
+ */
110
+ export function traceReflection(
111
+ surface: ReflectiveSample,
112
+ resources: GiResources,
113
+ options: ReflectionOptions,
114
+ out: IndirectResult,
115
+ ): IndirectResult {
116
+ reflectDirection(surface.view, surface.normal, MIRRORED);
117
+ const ray = {
118
+ point: surface.point,
119
+ normal: surface.normal,
120
+ direction: MIRRORED,
121
+ coneAngle: reflectionCone(surface.roughness),
122
+ };
123
+
124
+ if (options.fallback) return traceIndirect(ray, resources, out);
125
+
126
+ /*
127
+ * The screen alone. Reached by handing the chain a resource set whose other two levels cannot
128
+ * contribute — rather than by a second copy of the march — so the frame's own answer is
129
+ * byte-identical either way and cannot drift from the fallback path's.
130
+ */
131
+ traceIndirect(ray, { ...resources, ...SCREEN_ONLY }, out);
132
+ if (out.blend[0] === 0) {
133
+ out.hit = false;
134
+ out.radiance.fill(0);
135
+ out.blend.fill(0);
136
+ out.source = GI_SOURCE_SCREEN;
137
+ } else {
138
+ /* What the frame could not answer is left unanswered rather than handed downward. */
139
+ out.radiance[0] = (out.radiance[0] as number) / (out.blend[0] as number);
140
+ out.radiance[1] = (out.radiance[1] as number) / (out.blend[0] as number);
141
+ out.radiance[2] = (out.radiance[2] as number) / (out.blend[0] as number);
142
+ out.blend[0] = 1;
143
+ out.blend[1] = 0;
144
+ out.blend[2] = 0;
145
+ out.source = GI_SOURCE_SCREEN;
146
+ }
147
+ return out;
148
+ }
149
+
150
+ /**
151
+ * The two levels below the screen, silenced.
152
+ *
153
+ * A field whose march can never hit and a probe set that carries nothing, so `traceIndirect` runs
154
+ * its ordinary path and the screen's share is all there is to normalise. Frozen at module scope
155
+ * because this is a per-pixel call and the rule about allocating in one is absolute.
156
+ */
157
+ const SCREEN_ONLY = {
158
+ fieldRadiance: (_x: number, _y: number, _z: number, out: Float32Array): void => {
159
+ out.fill(0);
160
+ },
161
+ probeValues: new Float32Array(0),
162
+ probeChannels: 0,
163
+ } as const;