@driftengine/core 4.4.2 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/CHANGELOG.json +97 -0
  2. package/README.md +1 -1
  3. package/dist/geometry/boxCsg.d.ts +28 -0
  4. package/dist/geometry/boxCsg.js +139 -0
  5. package/dist/geometry/csg.d.ts +36 -0
  6. package/dist/geometry/csg.js +329 -0
  7. package/dist/geometry/meshBuilder.js +13 -0
  8. package/dist/geometry/meshConcat.js +1 -0
  9. package/dist/geometry/pixelFont.js +2 -0
  10. package/dist/geometry/solid.d.ts +61 -0
  11. package/dist/geometry/solid.js +152 -0
  12. package/dist/geometry/solidFlat.d.ts +35 -0
  13. package/dist/geometry/solidFlat.js +428 -0
  14. package/dist/geometry/solidHarness.d.ts +19 -0
  15. package/dist/geometry/solidHarness.js +66 -0
  16. package/dist/geometry/solidNormals.d.ts +17 -0
  17. package/dist/geometry/solidNormals.js +86 -0
  18. package/dist/geometry/solidRound.d.ts +41 -0
  19. package/dist/geometry/solidRound.js +323 -0
  20. package/dist/geometry/solidWriter.d.ts +16 -0
  21. package/dist/geometry/solidWriter.js +42 -0
  22. package/dist/index.d.ts +28 -3
  23. package/dist/index.js +20 -1
  24. package/dist/render/backend/api.d.ts +34 -14
  25. package/dist/render/backend/drawFog.d.ts +26 -0
  26. package/dist/render/backend/drawFog.js +30 -0
  27. package/dist/render/backend/materialChanges.d.ts +2 -1
  28. package/dist/render/backend/materialChanges.js +6 -1
  29. package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
  30. package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
  31. package/dist/render/backend/webgl2/instanced.d.ts +24 -5
  32. package/dist/render/backend/webgl2/instanced.js +72 -15
  33. package/dist/render/backend/webgl2/renderer.d.ts +201 -16
  34. package/dist/render/backend/webgl2/renderer.js +596 -224
  35. package/dist/render/backend/webgpu/buffers.d.ts +29 -1
  36. package/dist/render/backend/webgpu/buffers.js +61 -4
  37. package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
  38. package/dist/render/backend/webgpu/depthPass.js +7 -2
  39. package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
  40. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  41. package/dist/render/backend/webgpu/flatPass.d.ts +6 -1
  42. package/dist/render/backend/webgpu/flatPass.js +57 -9
  43. package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
  44. package/dist/render/backend/webgpu/glassTintPass.js +206 -0
  45. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
  46. package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
  47. package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
  48. package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
  49. package/dist/render/backend/webgpu/instanced.d.ts +23 -2
  50. package/dist/render/backend/webgpu/instanced.js +41 -4
  51. package/dist/render/backend/webgpu/latePass.d.ts +30 -0
  52. package/dist/render/backend/webgpu/latePass.js +140 -0
  53. package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
  54. package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
  55. package/dist/render/backend/webgpu/motionPass.js +491 -0
  56. package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
  57. package/dist/render/backend/webgpu/pipelineCache.js +11 -1
  58. package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
  59. package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
  60. package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
  61. package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
  62. package/dist/render/backend/webgpu/postPass.d.ts +3 -13
  63. package/dist/render/backend/webgpu/renderer.d.ts +271 -64
  64. package/dist/render/backend/webgpu/renderer.js +1395 -580
  65. package/dist/render/backend/webgpu/scatterPass.js +2 -1
  66. package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
  67. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
  68. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
  69. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
  70. package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
  71. package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
  72. package/dist/render/backend/webgpu/snapshotMips.js +52 -0
  73. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +22 -3
  74. package/dist/render/backend/webgpu/surfaceTexturePass.js +78 -39
  75. package/dist/render/driftLight/bake.d.ts +7 -0
  76. package/dist/render/driftLight/bake.js +61 -56
  77. package/dist/render/driftLight/denseField.d.ts +46 -0
  78. package/dist/render/driftLight/denseField.js +109 -0
  79. package/dist/render/driftLight/lightField.d.ts +10 -5
  80. package/dist/render/driftLight/lightField.js +25 -19
  81. package/dist/render/driftLight/presence.d.ts +50 -0
  82. package/dist/render/driftLight/presence.js +43 -0
  83. package/dist/render/driftLight/uniforms.d.ts +3 -3
  84. package/dist/render/driftLight/uniforms.js +5 -6
  85. package/dist/render/driftLight/worldLightField.d.ts +41 -0
  86. package/dist/render/driftLight/worldLightField.js +66 -0
  87. package/dist/render/fixtureAtlas.d.ts +20 -0
  88. package/dist/render/fixtureAtlas.js +49 -0
  89. package/dist/render/glass.d.ts +45 -0
  90. package/dist/render/glass.js +39 -0
  91. package/dist/render/glassCasters.d.ts +40 -0
  92. package/dist/render/glassCasters.js +84 -0
  93. package/dist/render/glassShadow.d.ts +78 -0
  94. package/dist/render/glassShadow.js +125 -0
  95. package/dist/render/globalMediumPass.d.ts +2 -3
  96. package/dist/render/globalMediumPass.js +2 -8
  97. package/dist/render/gpudriven/lit.d.ts +12 -0
  98. package/dist/render/gpudriven/lit.js +23 -0
  99. package/dist/render/gpudriven/materialTable.d.ts +15 -5
  100. package/dist/render/gpudriven/materialTable.js +26 -6
  101. package/dist/render/gpudriven/pipeline.d.ts +1 -1
  102. package/dist/render/gpudriven/pipeline.js +4 -3
  103. package/dist/render/gpudriven/sunGlass.d.ts +35 -0
  104. package/dist/render/gpudriven/sunGlass.js +89 -0
  105. package/dist/render/instanceCull.d.ts +63 -0
  106. package/dist/render/instanceCull.js +160 -0
  107. package/dist/render/instances.d.ts +15 -0
  108. package/dist/render/lightBudget.d.ts +36 -35
  109. package/dist/render/lightBudget.js +38 -37
  110. package/dist/render/lightGrid.d.ts +42 -0
  111. package/dist/render/lightGrid.js +87 -0
  112. package/dist/render/mesh.d.ts +18 -24
  113. package/dist/render/mesh.js +51 -68
  114. package/dist/render/occlusion.d.ts +10 -0
  115. package/dist/render/occlusion.js +36 -7
  116. package/dist/render/pointGlassTint.d.ts +59 -0
  117. package/dist/render/pointGlassTint.js +198 -0
  118. package/dist/render/pointLightSelection.d.ts +7 -0
  119. package/dist/render/pointLightSelection.js +29 -4
  120. package/dist/render/pointShadowArray.d.ts +56 -3
  121. package/dist/render/pointShadowArray.js +127 -16
  122. package/dist/render/pointShadowMap.d.ts +23 -1
  123. package/dist/render/pointShadowMap.js +27 -1
  124. package/dist/render/rainField.d.ts +13 -0
  125. package/dist/render/rainField.js +27 -5
  126. package/dist/render/recon/changeFrames.d.ts +15 -0
  127. package/dist/render/recon/changeFrames.js +21 -0
  128. package/dist/render/recon/lateDepth.d.ts +11 -0
  129. package/dist/render/recon/lateDepth.js +70 -0
  130. package/dist/render/recon/lateRouting.d.ts +14 -0
  131. package/dist/render/recon/lateRouting.js +18 -0
  132. package/dist/render/recon/mover.d.ts +57 -0
  133. package/dist/render/recon/mover.js +86 -0
  134. package/dist/render/recon/resolve.d.ts +34 -4
  135. package/dist/render/recon/resolve.js +161 -20
  136. package/dist/render/renderQuality.d.ts +35 -0
  137. package/dist/render/renderQuality.js +7 -0
  138. package/dist/render/rendererHarness.js +16 -0
  139. package/dist/render/sceneTarget.d.ts +9 -1
  140. package/dist/render/sceneTarget.js +60 -12
  141. package/dist/render/shaders/depth.d.ts +16 -1
  142. package/dist/render/shaders/depth.js +94 -15
  143. package/dist/render/shaders/flat/directionalShadow.js +91 -6
  144. package/dist/render/shaders/flat/driftLight.d.ts +1 -1
  145. package/dist/render/shaders/flat/driftLight.js +32 -7
  146. package/dist/render/shaders/flat/glassTint.d.ts +6 -0
  147. package/dist/render/shaders/flat/glassTint.js +25 -0
  148. package/dist/render/shaders/flat/index.d.ts +13 -0
  149. package/dist/render/shaders/flat/index.js +9 -3
  150. package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
  151. package/dist/render/shaders/flat/lampGlass.js +183 -0
  152. package/dist/render/shaders/flat/main.d.ts +1 -1
  153. package/dist/render/shaders/flat/main.js +190 -38
  154. package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
  155. package/dist/render/shaders/flat/pointShadow.js +2 -0
  156. package/dist/render/shaders/flat/preamble.js +96 -26
  157. package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
  158. package/dist/render/shaders/flat/surfaceEffects.js +152 -0
  159. package/dist/render/shaders/generated/depth.wgsl.d.ts +230 -7
  160. package/dist/render/shaders/generated/depth.wgsl.js +230 -7
  161. package/dist/render/shaders/generated/flat.wgsl.d.ts +1644 -1596
  162. package/dist/render/shaders/generated/flat.wgsl.js +1683 -1621
  163. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
  164. package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
  165. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
  166. package/dist/render/shaders/generated/lightVolume.wgsl.js +21 -17
  167. package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
  168. package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
  169. package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
  170. package/dist/render/shaders/generated/rush.wgsl.js +21 -16
  171. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
  172. package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
  173. package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
  174. package/dist/render/shaders/generated/text.wgsl.js +1 -1
  175. package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
  176. package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
  177. package/dist/render/shaders/globalMedium.js +6 -6
  178. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
  179. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
  180. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
  181. package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
  182. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
  183. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
  184. package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
  185. package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
  186. package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
  187. package/dist/render/shaders/lightVolume.js +6 -6
  188. package/dist/render/shaders/octahedralResolve.d.ts +8 -0
  189. package/dist/render/shaders/octahedralResolve.js +48 -17
  190. package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
  191. package/dist/render/shaders/recon/lateDepth.wgsl.js +69 -0
  192. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  193. package/dist/render/shaders/recon/motion.wgsl.js +84 -0
  194. package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
  195. package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
  196. package/dist/render/shaders/rush.js +11 -1
  197. package/dist/render/shaders/sdfText.d.ts +1 -1
  198. package/dist/render/shaders/sdfText.js +3 -2
  199. package/dist/render/shaders/text.d.ts +1 -1
  200. package/dist/render/shaders/text.js +18 -9
  201. package/dist/render/shadowCasters.d.ts +20 -1
  202. package/dist/render/shadowCasters.js +15 -1
  203. package/dist/render/shadowMap.d.ts +54 -5
  204. package/dist/render/shadowMap.js +190 -36
  205. package/dist/render/sunGlassTint.d.ts +25 -0
  206. package/dist/render/sunGlassTint.js +79 -0
  207. package/dist/render/surfaceEffects.d.ts +83 -0
  208. package/dist/render/surfaceEffects.js +75 -0
  209. package/dist/render/surfaceScene.d.ts +8 -0
  210. package/dist/render/surfaceScene.js +8 -0
  211. package/dist/render/surfaceTexture.d.ts +18 -2
  212. package/dist/render/surfaceTexture.js +68 -15
  213. package/dist/render/temporalAa.d.ts +25 -6
  214. package/dist/render/temporalAa.js +45 -15
  215. package/dist/render/textureSource.d.ts +30 -0
  216. package/dist/render/textureSource.js +53 -0
  217. package/dist/render/translucentQueue.d.ts +6 -0
  218. package/dist/render/translucentQueue.js +15 -1
  219. package/dist/render/vertexDefaults.js +2 -0
  220. package/dist/world/hlod.d.ts +104 -0
  221. package/dist/world/hlod.js +214 -0
  222. package/package.json +6 -6
  223. package/src/geometry/boxCsg.ts +186 -0
  224. package/src/geometry/csg.ts +384 -0
  225. package/src/geometry/meshBuilder.ts +15 -0
  226. package/src/geometry/meshConcat.ts +1 -0
  227. package/src/geometry/pixelFont.ts +2 -0
  228. package/src/geometry/solid.ts +189 -0
  229. package/src/geometry/solidFlat.ts +522 -0
  230. package/src/geometry/solidHarness.ts +83 -0
  231. package/src/geometry/solidNormals.ts +103 -0
  232. package/src/geometry/solidRound.ts +443 -0
  233. package/src/geometry/solidWriter.ts +58 -0
  234. package/src/index.ts +61 -1
  235. package/src/render/backend/api.ts +36 -11
  236. package/src/render/backend/drawFog.ts +31 -0
  237. package/src/render/backend/materialChanges.ts +9 -2
  238. package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
  239. package/src/render/backend/webgl2/instanced.ts +80 -14
  240. package/src/render/backend/webgl2/renderer.ts +700 -250
  241. package/src/render/backend/webgpu/buffers.ts +92 -5
  242. package/src/render/backend/webgpu/depthPass.ts +7 -2
  243. package/src/render/backend/webgpu/drawCommand.ts +6 -0
  244. package/src/render/backend/webgpu/flatPass.ts +62 -11
  245. package/src/render/backend/webgpu/glassTintPass.ts +245 -0
  246. package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
  247. package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
  248. package/src/render/backend/webgpu/instanced.ts +42 -4
  249. package/src/render/backend/webgpu/latePass.ts +161 -0
  250. package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
  251. package/src/render/backend/webgpu/motionPass.ts +535 -0
  252. package/src/render/backend/webgpu/pipelineCache.ts +12 -1
  253. package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
  254. package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
  255. package/src/render/backend/webgpu/renderer.ts +1671 -670
  256. package/src/render/backend/webgpu/scatterPass.ts +2 -1
  257. package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
  258. package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
  259. package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
  260. package/src/render/backend/webgpu/snapshotMips.ts +80 -0
  261. package/src/render/backend/webgpu/surfaceTexturePass.ts +91 -51
  262. package/src/render/driftLight/bake.ts +85 -62
  263. package/src/render/driftLight/denseField.ts +149 -0
  264. package/src/render/driftLight/lightField.ts +33 -18
  265. package/src/render/driftLight/presence.ts +69 -0
  266. package/src/render/driftLight/uniforms.ts +8 -9
  267. package/src/render/driftLight/worldLightField.ts +83 -0
  268. package/src/render/fixtureAtlas.ts +63 -0
  269. package/src/render/glass.ts +74 -0
  270. package/src/render/glassCasters.ts +164 -0
  271. package/src/render/glassShadow.ts +153 -0
  272. package/src/render/globalMediumPass.ts +4 -11
  273. package/src/render/gpudriven/lit.ts +31 -0
  274. package/src/render/gpudriven/materialTable.ts +28 -6
  275. package/src/render/gpudriven/pipeline.ts +4 -3
  276. package/src/render/gpudriven/sunGlass.ts +117 -0
  277. package/src/render/instanceCull.ts +205 -0
  278. package/src/render/instances.ts +16 -0
  279. package/src/render/lightBudget.ts +41 -38
  280. package/src/render/lightGrid.ts +130 -0
  281. package/src/render/mesh.ts +59 -72
  282. package/src/render/occlusion.ts +51 -7
  283. package/src/render/pointGlassTint.ts +250 -0
  284. package/src/render/pointLightSelection.ts +72 -4
  285. package/src/render/pointShadowArray.ts +188 -22
  286. package/src/render/pointShadowMap.ts +41 -0
  287. package/src/render/rainField.ts +39 -5
  288. package/src/render/recon/changeFrames.ts +24 -0
  289. package/src/render/recon/lateDepth.ts +83 -0
  290. package/src/render/recon/lateRouting.ts +29 -0
  291. package/src/render/recon/mover.ts +118 -0
  292. package/src/render/recon/resolve.ts +206 -20
  293. package/src/render/renderQuality.ts +47 -0
  294. package/src/render/rendererHarness.ts +16 -0
  295. package/src/render/sceneTarget.ts +62 -11
  296. package/src/render/shaders/depth.ts +118 -15
  297. package/src/render/shaders/flat/directionalShadow.ts +97 -6
  298. package/src/render/shaders/flat/driftLight.ts +32 -7
  299. package/src/render/shaders/flat/glassTint.ts +25 -0
  300. package/src/render/shaders/flat/index.ts +22 -3
  301. package/src/render/shaders/flat/lampGlass.ts +185 -0
  302. package/src/render/shaders/flat/main.ts +190 -38
  303. package/src/render/shaders/flat/pointShadow.ts +2 -0
  304. package/src/render/shaders/flat/preamble.ts +96 -26
  305. package/src/render/shaders/flat/surfaceEffects.ts +152 -0
  306. package/src/render/shaders/generated/depth.wgsl.ts +237 -7
  307. package/src/render/shaders/generated/flat.wgsl.ts +1685 -1621
  308. package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
  309. package/src/render/shaders/generated/lightVolume.wgsl.ts +23 -17
  310. package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
  311. package/src/render/shaders/generated/rush.wgsl.ts +21 -16
  312. package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
  313. package/src/render/shaders/generated/text.wgsl.ts +1 -1
  314. package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
  315. package/src/render/shaders/globalMedium.ts +6 -6
  316. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
  317. package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
  318. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
  319. package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
  320. package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
  321. package/src/render/shaders/lightVolume.ts +6 -6
  322. package/src/render/shaders/octahedralResolve.ts +50 -17
  323. package/src/render/shaders/recon/lateDepth.wgsl.ts +72 -0
  324. package/src/render/shaders/recon/motion.wgsl.ts +84 -0
  325. package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
  326. package/src/render/shaders/rush.ts +11 -1
  327. package/src/render/shaders/sdfText.ts +3 -2
  328. package/src/render/shaders/text.ts +18 -9
  329. package/src/render/shadowCasters.ts +28 -1
  330. package/src/render/shadowMap.ts +231 -37
  331. package/src/render/sunGlassTint.ts +86 -0
  332. package/src/render/surfaceEffects.ts +132 -0
  333. package/src/render/surfaceScene.ts +16 -0
  334. package/src/render/surfaceTexture.ts +110 -19
  335. package/src/render/temporalAa.ts +78 -14
  336. package/src/render/textureSource.ts +72 -0
  337. package/src/render/translucentQueue.ts +20 -1
  338. package/src/render/vertexDefaults.ts +2 -0
  339. package/src/world/hlod.ts +290 -0
@@ -13,4 +13,4 @@
13
13
  * The including module declares `driftIndex: texture_3d<u32>`, `driftAtlas: texture_3d<f32>` and a
14
14
  * filtering `driftSampler`; `origin` is the field's first sample and, in `w`, the spacing.
15
15
  */
16
- export declare const DRIFT_LIGHT_WGSL = "\nfn driftLightIrradiance(world: vec3<f32>, n: vec3<f32>, origin: vec4<f32>) -> vec3<f32> {\n let spacing = origin.w;\n let at = (world + n * (0.5 * spacing) - origin.xyz) / (3.0 * spacing);\n let cell = vec3<i32>(floor(at));\n if (any(cell < vec3<i32>(0)) || any(cell >= vec3<i32>(textureDimensions(driftIndex, 0)))) {\n return vec3<f32>(0.0);\n }\n let id = textureLoad(driftIndex, cell, 0).r;\n if (id == 0u) {\n return vec3<f32>(0.0);\n }\n let size = vec3<i32>(textureDimensions(driftAtlas, 0));\n let brick = i32(id) - 1;\n let across = size.x / 8;\n let down = size.y / 4;\n let corner = vec3<f32>(\n f32((brick % across) * 8),\n f32(((brick / across) % down) * 4),\n f32((brick / (across * down)) * 4)\n );\n let texel = 1.0 / vec3<f32>(size);\n let inside = (at - vec3<f32>(cell)) * 3.0 + 0.5;\n let light = textureSampleLevel(driftAtlas, driftSampler, (corner + inside) * texel, 0.0);\n if (light.a < 1e-3) {\n return vec3<f32>(0.0);\n }\n let toward = textureSampleLevel(driftAtlas, driftSampler, (corner + vec3<f32>(4.0, 0.0, 0.0) + inside) * texel, 0.0);\n let arriving = light.rgb / light.a;\n let towardLight = toward.xyz / light.a;\n let facing = max(dot(n, towardLight), 0.0) + (1.0 - min(length(towardLight), 1.0)) * 0.25;\n return arriving * facing;\n}\n";
16
+ export declare const DRIFT_LIGHT_WGSL = "\nfn driftLightFacing(light: vec4<f32>, toward: vec4<f32>, n: vec3<f32>) -> vec3<f32> {\n let arriving = light.rgb / light.a;\n let towardLight = toward.xyz / light.a;\n let facing = max(dot(n, towardLight), 0.0) + (1.0 - min(length(towardLight), 1.0)) * 0.25;\n return arriving * facing;\n}\n\nfn driftLightDense(world: vec3<f32>, n: vec3<f32>, origin: vec4<f32>, spacing: f32) -> vec3<f32> {\n let size = vec3<i32>(textureDimensions(driftAtlas, 0));\n let dims = vec3<f32>(f32(size.x), f32(size.y / 2), f32(size.z));\n let at = (world + n * (0.5 * spacing) - origin.xyz) / spacing;\n if (any(at < vec3<f32>(0.0)) || any(at > dims - 1.0)) {\n return vec3<f32>(0.0);\n }\n let uvw = (at + 0.5) / vec3<f32>(dims.x, dims.y * 2.0, dims.z);\n let light = textureSampleLevel(driftAtlas, driftSampler, uvw, 0.0);\n if (light.a < 1e-3) {\n return vec3<f32>(0.0);\n }\n let toward = textureSampleLevel(driftAtlas, driftSampler, uvw + vec3<f32>(0.0, 0.5, 0.0), 0.0);\n return driftLightFacing(light, toward, n);\n}\n\nfn driftLightIrradiance(world: vec3<f32>, n: vec3<f32>, origin: vec4<f32>) -> vec3<f32> {\n let spacing = origin.w;\n if (spacing < 0.0) {\n return driftLightDense(world, n, origin, -spacing);\n }\n let at = (world + n * (0.5 * spacing) - origin.xyz) / (3.0 * spacing);\n let cell = vec3<i32>(floor(at));\n if (any(cell < vec3<i32>(0)) || any(cell >= vec3<i32>(textureDimensions(driftIndex, 0)))) {\n return vec3<f32>(0.0);\n }\n let id = textureLoad(driftIndex, cell, 0).r;\n if (id == 0u) {\n return vec3<f32>(0.0);\n }\n let size = vec3<i32>(textureDimensions(driftAtlas, 0));\n let brick = i32(id) - 1;\n let across = size.x / 8;\n let down = size.y / 4;\n let corner = vec3<f32>(\n f32((brick % across) * 8),\n f32(((brick / across) % down) * 4),\n f32((brick / (across * down)) * 4)\n );\n let texel = 1.0 / vec3<f32>(size);\n let inside = (at - vec3<f32>(cell)) * 3.0 + 0.5;\n let light = textureSampleLevel(driftAtlas, driftSampler, (corner + inside) * texel, 0.0);\n if (light.a < 1e-3) {\n return vec3<f32>(0.0);\n }\n let toward = textureSampleLevel(driftAtlas, driftSampler, (corner + vec3<f32>(4.0, 0.0, 0.0) + inside) * texel, 0.0);\n return driftLightFacing(light, toward, n);\n}\n";
@@ -14,8 +14,34 @@
14
14
  * filtering `driftSampler`; `origin` is the field's first sample and, in `w`, the spacing.
15
15
  */
16
16
  export const DRIFT_LIGHT_WGSL = /* wgsl */ `
17
+ fn driftLightFacing(light: vec4<f32>, toward: vec4<f32>, n: vec3<f32>) -> vec3<f32> {
18
+ let arriving = light.rgb / light.a;
19
+ let towardLight = toward.xyz / light.a;
20
+ let facing = max(dot(n, towardLight), 0.0) + (1.0 - min(length(towardLight), 1.0)) * 0.25;
21
+ return arriving * facing;
22
+ }
23
+
24
+ fn driftLightDense(world: vec3<f32>, n: vec3<f32>, origin: vec4<f32>, spacing: f32) -> vec3<f32> {
25
+ let size = vec3<i32>(textureDimensions(driftAtlas, 0));
26
+ let dims = vec3<f32>(f32(size.x), f32(size.y / 2), f32(size.z));
27
+ let at = (world + n * (0.5 * spacing) - origin.xyz) / spacing;
28
+ if (any(at < vec3<f32>(0.0)) || any(at > dims - 1.0)) {
29
+ return vec3<f32>(0.0);
30
+ }
31
+ let uvw = (at + 0.5) / vec3<f32>(dims.x, dims.y * 2.0, dims.z);
32
+ let light = textureSampleLevel(driftAtlas, driftSampler, uvw, 0.0);
33
+ if (light.a < 1e-3) {
34
+ return vec3<f32>(0.0);
35
+ }
36
+ let toward = textureSampleLevel(driftAtlas, driftSampler, uvw + vec3<f32>(0.0, 0.5, 0.0), 0.0);
37
+ return driftLightFacing(light, toward, n);
38
+ }
39
+
17
40
  fn driftLightIrradiance(world: vec3<f32>, n: vec3<f32>, origin: vec4<f32>) -> vec3<f32> {
18
41
  let spacing = origin.w;
42
+ if (spacing < 0.0) {
43
+ return driftLightDense(world, n, origin, -spacing);
44
+ }
19
45
  let at = (world + n * (0.5 * spacing) - origin.xyz) / (3.0 * spacing);
20
46
  let cell = vec3<i32>(floor(at));
21
47
  if (any(cell < vec3<i32>(0)) || any(cell >= vec3<i32>(textureDimensions(driftIndex, 0)))) {
@@ -41,9 +67,6 @@ fn driftLightIrradiance(world: vec3<f32>, n: vec3<f32>, origin: vec4<f32>) -> ve
41
67
  return vec3<f32>(0.0);
42
68
  }
43
69
  let toward = textureSampleLevel(driftAtlas, driftSampler, (corner + vec3<f32>(4.0, 0.0, 0.0) + inside) * texel, 0.0);
44
- let arriving = light.rgb / light.a;
45
- let towardLight = toward.xyz / light.a;
46
- let facing = max(dot(n, towardLight), 0.0) + (1.0 - min(length(towardLight), 1.0)) * 0.25;
47
- return arriving * facing;
70
+ return driftLightFacing(light, toward, n);
48
71
  }
49
72
  `;
@@ -1,3 +1,4 @@
1
+ import { SUN_DYNAMIC_LAYER, SUN_PEELED_LAYER, SUN_STATIC_LAYER } from '../shadowMap.js';
1
2
  import { glslIsFarDepth, glslSceneDepthToNdc } from '../depthConvention.js';
2
3
  import { MAX_GLOBAL_MEDIUM_STEPS } from '../globalMedium.js';
3
4
  /**
@@ -77,9 +78,8 @@ uniform int uSteps;
77
78
  /** How much of the medium the sun's own shadow map removes, 0 to 1. At 0 nothing is sampled. */
78
79
  uniform float uSunShadow;
79
80
  uniform mat4 uLightViewProj;
80
- uniform sampler2D uStaticShadowMap;
81
- uniform sampler2D uPeeledShadowMap;
82
- uniform sampler2D uDynamicShadowMap;
81
+ /* The sun's static, moving and peeled maps, as layers of one array: see shadowMap.ts. */
82
+ uniform highp sampler2DArray uSunShadows;
83
83
  uniform int uPeeledShadowEnabled;
84
84
 
85
85
  out vec4 fragColor;
@@ -148,11 +148,11 @@ float sunReach(vec3 worldPos) {
148
148
  if (edgeFade <= 0.0) return 1.0;
149
149
 
150
150
  float compare = p.z - SHADOW_BIAS;
151
- float blocked = occlusion(textureLod(uStaticShadowMap, p.xy, 0.0).r, compare);
151
+ float blocked = occlusion(textureLod(uSunShadows, vec3(p.xy, ${SUN_STATIC_LAYER}.0), 0.0).r, compare);
152
152
  if (uPeeledShadowEnabled != 0) {
153
- blocked = max(blocked, occlusion(textureLod(uPeeledShadowMap, p.xy, 0.0).r, compare));
153
+ blocked = max(blocked, occlusion(textureLod(uSunShadows, vec3(p.xy, ${SUN_PEELED_LAYER}.0), 0.0).r, compare));
154
154
  }
155
- blocked = max(blocked, occlusion(textureLod(uDynamicShadowMap, p.xy, 0.0).r, compare));
155
+ blocked = max(blocked, occlusion(textureLod(uSunShadows, vec3(p.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r, compare));
156
156
  return mix(1.0, 1.0 - blocked, uSunShadow * edgeFade);
157
157
  }
158
158
 
@@ -24,3 +24,10 @@ export declare const BLEND_RESOLVE_WGSL = "\n@group(0) @binding(0) var accumMap:
24
24
  * `oitWeight` and `resolveOit` over generated cases.
25
25
  */
26
26
  export declare const BLEND_OIT_PARITY_WGSL = "\n/** Six floats a case: depth, alpha, colour, and the background behind it. */\n@group(0) @binding(0) var<storage, read> cases: array<f32>;\n/** Four out: the weight, and the three resolved channels. */\n@group(0) @binding(1) var<storage, read_write> results: array<f32>;\n\nfn oitWeight(viewDepth: f32, alpha: f32) -> f32 {\n let z = max(viewDepth, 0.0) / 200.0;\n let falloff = 0.03 / (1e-5 + z * z * z * z);\n return alpha * clamp(falloff, 1e-2, 3e3);\n}\n\n/* Premultiplied and weighted, against a blend of \"one, one\": the target holds the two sums. */\nfn oitAccumulate(colour: vec3<f32>, alpha: f32, weight: f32) -> vec4<f32> {\n return vec4<f32>(colour * alpha * weight, alpha * weight);\n}\n\n/*\n * The two targets, as a premultiplied fragment to lay over what is behind them.\n *\n * The weighted sum divided by the weighted alpha is the average colour the layers make, and the\n * revealage says how much of the background still shows. The division is guarded because a pixel no\n * fragment touched has a weighted alpha of zero \u2014 and its revealage of one discards whatever this\n * produces anyway.\n */\nfn oitResolved(accum: vec4<f32>, reveal: f32) -> vec4<f32> {\n let average = accum.xyz / max(accum.w, 1e-5);\n let covered = 1.0 - reveal;\n return vec4<f32>(average * covered, covered);\n}\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x * 8u + 7u >= arrayLength(&cases)) { return; }\n let at = id.x * 8u;\n let depth = cases[at];\n let alpha = cases[at + 1u];\n let colour = vec3<f32>(cases[at + 2u], cases[at + 3u], cases[at + 4u]);\n let background = vec3<f32>(cases[at + 5u], cases[at + 6u], cases[at + 7u]);\n\n let weight = oitWeight(depth, alpha);\n /* One fragment folded in, which is the anchor: for a single layer this reduces to \"over\". */\n let accum = oitAccumulate(colour, alpha, weight);\n let reveal = 1.0 - alpha;\n /*\n * The resolve's own fragment, then the blend state applied by hand: the pipeline lays it over\n * the target with \"one, one-minus-src-alpha\", so the background keeps the revealage.\n */\n let over = oitResolved(accum, reveal);\n let resolved = over.xyz + background * (1.0 - over.w);\n\n let out = id.x * 4u;\n results[out] = weight;\n results[out + 1u] = resolved.x;\n results[out + 2u] = resolved.y;\n results[out + 3u] = resolved.z;\n}\n";
27
+ /**
28
+ * `glassShade` alone, as a dispatch, so it can be checked against `gpudriven/lit.ts`.
29
+ *
30
+ * Sixteen floats a case — own, seen, transmission, frost, tint, the view cosine, the glow, one
31
+ * spare — and four out, the colour and one spare.
32
+ */
33
+ export declare const GLASS_SHADE_PARITY_WGSL = "\n@group(0) @binding(0) var<storage, read> cases: array<f32>;\n@group(0) @binding(1) var<storage, read_write> results: array<f32>;\n\nfn glassShade(\n own: vec3<f32>,\n seen: vec3<f32>,\n transmission: f32,\n frost: f32,\n tint: vec3<f32>,\n cosView: f32,\n sunGlow: vec3<f32>,\n) -> vec3<f32> {\n let c = clamp(abs(cosView), 0.05, 1.0);\n let fresnel = 0.04 + 0.96 * pow(1.0 - c, 5.0);\n let shown = transmission * (1.0 - fresnel);\n return own + (seen * tint - own) * shown + sunGlow * tint * (frost * transmission);\n}\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x * 16u + 15u >= arrayLength(&cases)) { return; }\n let at = id.x * 16u;\n let own = vec3<f32>(cases[at], cases[at + 1u], cases[at + 2u]);\n let seen = vec3<f32>(cases[at + 3u], cases[at + 4u], cases[at + 5u]);\n let tint = vec3<f32>(cases[at + 8u], cases[at + 9u], cases[at + 10u]);\n let glow = vec3<f32>(cases[at + 12u], cases[at + 13u], cases[at + 14u]);\n let shaded = glassShade(own, seen, cases[at + 6u], cases[at + 7u], tint, cases[at + 11u], glow);\n let out = id.x * 4u;\n results[out] = shaded.x;\n results[out + 1u] = shaded.y;\n results[out + 2u] = shaded.z;\n}\n";
@@ -80,6 +80,27 @@ fn oitResolved(accum: vec4<f32>, reveal: f32) -> vec4<f32> {
80
80
  return vec4<f32>(average * covered, covered);
81
81
  }
82
82
  `;
83
+ /**
84
+ * Glass over its own shading, as the forward path draws it: `gpudriven/lit.ts`'s `glassShade`,
85
+ * which `scripts/gpu-parity.mjs` holds this to. Schlick at 0.04 with the angle unsigned — a pane
86
+ * passes the same light seen from either side — and floored at the forward path's grazing 0.05.
87
+ */
88
+ const GLASS_SHADE_WGSL = /* wgsl */ `
89
+ fn glassShade(
90
+ own: vec3<f32>,
91
+ seen: vec3<f32>,
92
+ transmission: f32,
93
+ frost: f32,
94
+ tint: vec3<f32>,
95
+ cosView: f32,
96
+ sunGlow: vec3<f32>,
97
+ ) -> vec3<f32> {
98
+ let c = clamp(abs(cosView), 0.05, 1.0);
99
+ let fresnel = 0.04 + 0.96 * pow(1.0 - c, 5.0);
100
+ let shown = transmission * (1.0 - fresnel);
101
+ return own + (seen * tint - own) * shown + sunGlow * tint * (frost * transmission);
102
+ }
103
+ `;
83
104
  export const BLEND_RASTER_WGSL = /* wgsl */ `
84
105
  struct BlendVarying {
85
106
  @builtin(position) position: vec4<f32>,
@@ -106,6 +127,12 @@ ${FRAME_BLOCK_WGSL}
106
127
  @group(0) @binding(9) var environmentMap: texture_2d_array<f32>;
107
128
  @group(0) @binding(10) var environmentSampler: sampler;
108
129
  ${decodeResourcesWgsl({ group: 0, latents: 11, clampSampler: 12, repeatSampler: 13, nodes: 14, weights: 15 })}
130
+ /**
131
+ * What the opaque half drew, with a mip chain on a frame whose scene holds glass: a pane shows it,
132
+ * a frosted one a coarser level of it. Read only by a material whose transmission is not zero.
133
+ */
134
+ @group(0) @binding(16) var opaqueColour: texture_2d<f32>;
135
+ @group(0) @binding(17) var opaqueSampler: sampler;
109
136
  fn decodeLatentSize() -> f32 {
110
137
  return frame.size.w;
111
138
  }
@@ -124,6 +151,7 @@ ${MESH_TRANSFORM_WGSL}
124
151
  ${SHADE_LIGHTING_WGSL}
125
152
  ${FOG_WGSL}
126
153
  ${OIT_WGSL}
154
+ ${GLASS_SHADE_WGSL}
127
155
 
128
156
  const NO_PROGRAM_HERE: u32 = ${0xffffffff}u;
129
157
 
@@ -239,7 +267,25 @@ fn blendFrag(in: BlendVarying) -> BlendTargets {
239
267
  * distance — which is the whole reason this pipeline gained a fog.
240
268
  */
241
269
  let toward = distance(in.world, frame.eye.xyz);
242
- let colour = mix(litColour(surface, room), mediumColour(), mediumFog(toward, in.world.y));
270
+ let fog = mediumFog(toward, in.world.y);
271
+ var colour = mix(litColour(surface, room), mediumColour(), fog);
272
+ /*
273
+ * **Glass shows what is behind it**, as the forward path's glass branch does: the opaque half's
274
+ * picture at this pixel, at the level frost picks, two short of the chain's end so a fully frosted
275
+ * pane keeps the large light and dark behind it. The sun glows through from the pane's far side,
276
+ * after the haze as the forward path adds it. A pane is seen from both sides, so its facing is
277
+ * the side toward the eye.
278
+ */
279
+ let transmission = entry.extra.y;
280
+ if (transmission > 0.0) {
281
+ let size = vec2<f32>(textureDimensions(opaqueColour, 0));
282
+ let levels = max(floor(log2(max(size.x, size.y))) - 2.0, 0.0);
283
+ let seen = textureSampleLevel(opaqueColour, opaqueSampler, in.position.xy / size, entry.extra.z * levels).xyz;
284
+ let cosView = dot(surface.normal, toEye);
285
+ let facing = select(surface.normal, -surface.normal, cosView < 0.0);
286
+ let sunGlow = frame.lightColour.xyz * max(-dot(facing, frame.lightDir.xyz), 0.0) * (1.0 - fog);
287
+ colour = glassShade(colour, seen, transmission, entry.extra.z, entry.glassTint.xyz, cosView, sunGlow);
288
+ }
243
289
 
244
290
  /*
245
291
  * **The distance to the eye, not the clip-space depth**, because that is what oitWeight's falloff
@@ -253,6 +299,20 @@ fn blendFrag(in: BlendVarying) -> BlendTargets {
253
299
  out.reveal = vec4<f32>(alpha, 0.0, 0.0, alpha);
254
300
  return out;
255
301
  }
302
+
303
+ /*
304
+ * **What a pane lets through, drawn from the sun into the tint map**: \`blendVert\` again, handed
305
+ * the light's raster matrix and the glass list, and this for the colour — \`glassShadow.ts\`'s
306
+ * paneTexel, the forward path's GLASS_TINT_FRAG in this language. Written against a white target
307
+ * with a multiplying blend and no depth, so every pane on a ray counts in any order.
308
+ */
309
+ @fragment
310
+ fn glassTintFrag(in: BlendVarying) -> @location(0) vec4<f32> {
311
+ let entry = materials[min(in.material, ${MATERIAL_SLOTS - 1}u)];
312
+ let c = clamp(abs(dot(normalize(in.normal), frame.lightDir.xyz)), 0.05, 1.0);
313
+ let passes = entry.extra.y * (1.0 - (0.04 + 0.96 * pow(1.0 - c, 5.0)));
314
+ return vec4<f32>(passes * entry.glassTint.xyz, 1.0 - entry.extra.z);
315
+ }
256
316
  `;
257
317
  /**
258
318
  * The two targets composited over what the opaque half drew.
@@ -337,3 +397,28 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
337
397
  results[out + 3u] = resolved.z;
338
398
  }
339
399
  `;
400
+ /**
401
+ * `glassShade` alone, as a dispatch, so it can be checked against `gpudriven/lit.ts`.
402
+ *
403
+ * Sixteen floats a case — own, seen, transmission, frost, tint, the view cosine, the glow, one
404
+ * spare — and four out, the colour and one spare.
405
+ */
406
+ export const GLASS_SHADE_PARITY_WGSL = /* wgsl */ `
407
+ @group(0) @binding(0) var<storage, read> cases: array<f32>;
408
+ @group(0) @binding(1) var<storage, read_write> results: array<f32>;
409
+ ${GLASS_SHADE_WGSL}
410
+ @compute @workgroup_size(64)
411
+ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
412
+ if (id.x * 16u + 15u >= arrayLength(&cases)) { return; }
413
+ let at = id.x * 16u;
414
+ let own = vec3<f32>(cases[at], cases[at + 1u], cases[at + 2u]);
415
+ let seen = vec3<f32>(cases[at + 3u], cases[at + 4u], cases[at + 5u]);
416
+ let tint = vec3<f32>(cases[at + 8u], cases[at + 9u], cases[at + 10u]);
417
+ let glow = vec3<f32>(cases[at + 12u], cases[at + 13u], cases[at + 14u]);
418
+ let shaded = glassShade(own, seen, cases[at + 6u], cases[at + 7u], tint, cases[at + 11u], glow);
419
+ let out = id.x * 4u;
420
+ results[out] = shaded.x;
421
+ results[out + 1u] = shaded.y;
422
+ results[out + 2u] = shaded.z;
423
+ }
424
+ `;
@@ -43,7 +43,7 @@ export declare const CULL_INSTANCES_WGSL = "\n@group(0) @binding(0) var<storage,
43
43
  * wall and is the whole reason the front half of the pipeline is reusable. A scene with nothing
44
44
  * blended runs this once and the filter costs it one comparison against a uniform.
45
45
  */
46
- export declare const LOD_CUT_WGSL = "\nstruct Params {\n screenHeight: f32,\n fovY: f32,\n thresholdPixels: f32,\n eyeX: f32,\n eyeY: f32,\n eyeZ: f32,\n /** Non-zero to select the blended clusters, zero to select everything else. */\n wantBlend: f32,\n};\n\nstruct Material {\n /** rgb is the tint, w is the emissive gain. */\n tint: vec4<f32>,\n /** x is the roughness, y the specular reflectance, z the environment's and w the metalness. */\n surface: vec4<f32>,\n /** Base colour, normal, ORM and emissive programs; NO_PROGRAM where there is none. */\n programs: vec4<u32>,\n /** u scale, v scale, normal strength, occlusion strength. */\n maps: vec4<f32>,\n /** Roughness scale, metallic scale, the alpha cutoff, and non-zero where this one blends. */\n orm: vec4<f32>,\n /** The blended opacity, and three spare. */\n extra: vec4<f32>,\n}\n\nconst NO_PROGRAM: u32 = 4294967295u;\n\n@group(0) @binding(0) var<storage, read> params: array<f32, 7>;\n/** Six floats a cluster: centre, radius, own error, parent error. */\n@group(0) @binding(1) var<storage, read> clusters: array<f32>;\n@group(0) @binding(2) var<storage, read_write> selected: array<u32>;\n/** Four words a cluster: index offset, index count, identifier, mesh. */\n@group(0) @binding(3) var<storage, read> clusterMeta: array<u32>;\n/** One word a mesh, nonzero where CULL_INSTANCES_WGSL found it wholly outside the view. */\n@group(0) @binding(4) var<storage, read> instanceHidden: array<u32>;\n/** One material a cluster, and the table it indexes. Both only to read one flag. */\n@group(0) @binding(5) var<storage, read> materialOf: array<u32>;\n@group(0) @binding(6) var<uniform> materials: array<Material, 64>;\n\nfn projectedError(error: f32, distance: f32, radius: f32, screenHeight: f32, fovY: f32) -> f32 {\n let safe = max(max(distance, radius), 1e-4);\n let scale = screenHeight / (2.0 * tan(fovY * 0.5));\n return (error * scale) / safe;\n}\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i * 6u + 5u >= arrayLength(&clusters)) { return; }\n /* A hidden mesh contributes nothing, so none of its clusters is in the cut at any level. */\n if (instanceHidden[clusterMeta[i * 4u + 3u]] != 0u) {\n selected[i] = 0u;\n return;\n }\n /*\n * **Which half of the frame this cluster belongs to, asked once.** The two halves are exclusive:\n * a blended cluster is not in the opaque cut and an opaque one is not in the blended cut, so\n * neither is drawn twice and neither is dropped. The flag is read through a uniform by an index\n * that is the same for every cluster of a mesh, so the branch is as coherent as this shader gets.\n */\n let blended = materials[min(materialOf[i], 63u)].orm.w != 0.0;\n if (blended != (params[6] != 0.0)) {\n selected[i] = 0u;\n return;\n }\n let screenHeight = params[0];\n let fovY = params[1];\n let threshold = params[2];\n let eye = vec3<f32>(params[3], params[4], params[5]);\n\n let centre = vec3<f32>(clusters[i * 6u], clusters[i * 6u + 1u], clusters[i * 6u + 2u]);\n let radius = clusters[i * 6u + 3u];\n let ownError = clusters[i * 6u + 4u];\n let parentError = clusters[i * 6u + 5u];\n let distance = length(centre - eye);\n\n let own = projectedError(ownError, distance, radius, screenHeight, fovY);\n let parent = projectedError(parentError, distance, radius, screenHeight, fovY);\n /* Both halves: the first alone draws every ancestor too, the second alone draws nothing. */\n selected[i] = select(0u, 1u, own <= threshold && parent > threshold);\n}\n";
46
+ export declare const LOD_CUT_WGSL = "\nstruct Params {\n screenHeight: f32,\n fovY: f32,\n thresholdPixels: f32,\n eyeX: f32,\n eyeY: f32,\n eyeZ: f32,\n /** Non-zero to select the blended clusters, zero to select everything else. */\n wantBlend: f32,\n /** Non-zero to keep, of those, only glass: what the light's glass pass draws. */\n wantGlass: f32,\n};\n\nstruct Material {\n /** rgb is the tint, w is the emissive gain. */\n tint: vec4<f32>,\n /** x is the roughness, y the specular reflectance, z the environment's and w the metalness. */\n surface: vec4<f32>,\n /** Base colour, normal, ORM and emissive programs; NO_PROGRAM where there is none. */\n programs: vec4<u32>,\n /** u scale, v scale, normal strength, occlusion strength. */\n maps: vec4<f32>,\n /** Roughness scale, metallic scale, the alpha cutoff, and non-zero where this one blends. */\n orm: vec4<f32>,\n /** The blended opacity, the glass's transmission and frost, and one spare. */\n extra: vec4<f32>,\n /** The glass's tint, white where the material is not glass, and one spare. */\n glassTint: vec4<f32>,\n}\n\nconst NO_PROGRAM: u32 = 4294967295u;\n\n@group(0) @binding(0) var<storage, read> params: array<f32, 8>;\n/** Six floats a cluster: centre, radius, own error, parent error. */\n@group(0) @binding(1) var<storage, read> clusters: array<f32>;\n@group(0) @binding(2) var<storage, read_write> selected: array<u32>;\n/** Four words a cluster: index offset, index count, identifier, mesh. */\n@group(0) @binding(3) var<storage, read> clusterMeta: array<u32>;\n/** One word a mesh, nonzero where CULL_INSTANCES_WGSL found it wholly outside the view. */\n@group(0) @binding(4) var<storage, read> instanceHidden: array<u32>;\n/** One material a cluster, and the table it indexes. Both only to read one flag. */\n@group(0) @binding(5) var<storage, read> materialOf: array<u32>;\n@group(0) @binding(6) var<uniform> materials: array<Material, 64>;\n\nfn projectedError(error: f32, distance: f32, radius: f32, screenHeight: f32, fovY: f32) -> f32 {\n let safe = max(max(distance, radius), 1e-4);\n let scale = screenHeight / (2.0 * tan(fovY * 0.5));\n return (error * scale) / safe;\n}\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i * 6u + 5u >= arrayLength(&clusters)) { return; }\n /* A hidden mesh contributes nothing, so none of its clusters is in the cut at any level. */\n if (instanceHidden[clusterMeta[i * 4u + 3u]] != 0u) {\n selected[i] = 0u;\n return;\n }\n /*\n * **Which half of the frame this cluster belongs to, asked once.** The two halves are exclusive:\n * a blended cluster is not in the opaque cut and an opaque one is not in the blended cut, so\n * neither is drawn twice and neither is dropped. The flag is read through a uniform by an index\n * that is the same for every cluster of a mesh, so the branch is as coherent as this shader gets.\n */\n let material = materials[min(materialOf[i], 63u)];\n let blended = material.orm.w != 0.0;\n if (blended != (params[6] != 0.0)) {\n selected[i] = 0u;\n return;\n }\n /* The light's glass pass: a blended surface that is not glass casts nothing, as on the forward\n path, so only a material letting light through is kept. */\n if (params[7] != 0.0 && material.extra.y <= 0.0) {\n selected[i] = 0u;\n return;\n }\n let screenHeight = params[0];\n let fovY = params[1];\n let threshold = params[2];\n let eye = vec3<f32>(params[3], params[4], params[5]);\n\n let centre = vec3<f32>(clusters[i * 6u], clusters[i * 6u + 1u], clusters[i * 6u + 2u]);\n let radius = clusters[i * 6u + 3u];\n let ownError = clusters[i * 6u + 4u];\n let parentError = clusters[i * 6u + 5u];\n let distance = length(centre - eye);\n\n let own = projectedError(ownError, distance, radius, screenHeight, fovY);\n let parent = projectedError(parentError, distance, radius, screenHeight, fovY);\n /* Both halves: the first alone draws every ancestor too, the second alone draws nothing. */\n selected[i] = select(0u, 1u, own <= threshold && parent > threshold);\n}\n";
47
47
  /**
48
48
  * The pyramid's base level, copied out of the depth attachment a phase drew into.
49
49
  *
@@ -100,9 +100,11 @@ struct Params {
100
100
  eyeZ: f32,
101
101
  /** Non-zero to select the blended clusters, zero to select everything else. */
102
102
  wantBlend: f32,
103
+ /** Non-zero to keep, of those, only glass: what the light's glass pass draws. */
104
+ wantGlass: f32,
103
105
  };
104
106
  ${MATERIAL_TABLE_WGSL}
105
- @group(0) @binding(0) var<storage, read> params: array<f32, 7>;
107
+ @group(0) @binding(0) var<storage, read> params: array<f32, 8>;
106
108
  /** Six floats a cluster: centre, radius, own error, parent error. */
107
109
  @group(0) @binding(1) var<storage, read> clusters: array<f32>;
108
110
  @group(0) @binding(2) var<storage, read_write> selected: array<u32>;
@@ -135,11 +137,18 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
135
137
  * neither is drawn twice and neither is dropped. The flag is read through a uniform by an index
136
138
  * that is the same for every cluster of a mesh, so the branch is as coherent as this shader gets.
137
139
  */
138
- let blended = materials[min(materialOf[i], ${MATERIAL_SLOTS - 1}u)].orm.w != 0.0;
140
+ let material = materials[min(materialOf[i], ${MATERIAL_SLOTS - 1}u)];
141
+ let blended = material.orm.w != 0.0;
139
142
  if (blended != (params[6] != 0.0)) {
140
143
  selected[i] = 0u;
141
144
  return;
142
145
  }
146
+ /* The light's glass pass: a blended surface that is not glass casts nothing, as on the forward
147
+ path, so only a material letting light through is kept. */
148
+ if (params[7] != 0.0 && material.extra.y <= 0.0) {
149
+ selected[i] = 0u;
150
+ return;
151
+ }
143
152
  let screenHeight = params[0];
144
153
  let fovY = params[1];
145
154
  let threshold = params[2];
@@ -22,4 +22,4 @@
22
22
  */
23
23
  export declare const MATERIAL_SLOTS = 64;
24
24
  /** The struct and the sentinel, for any module that binds the table. */
25
- export declare const MATERIAL_TABLE_WGSL = "\nstruct Material {\n /** rgb is the tint, w is the emissive gain. */\n tint: vec4<f32>,\n /** x is the roughness, y the specular reflectance, z the environment's and w the metalness. */\n surface: vec4<f32>,\n /** Base colour, normal, ORM and emissive programs; NO_PROGRAM where there is none. */\n programs: vec4<u32>,\n /** u scale, v scale, normal strength, occlusion strength. */\n maps: vec4<f32>,\n /** Roughness scale, metallic scale, the alpha cutoff, and non-zero where this one blends. */\n orm: vec4<f32>,\n /** The blended opacity, and three spare. */\n extra: vec4<f32>,\n}\n\nconst NO_PROGRAM: u32 = 4294967295u;\n";
25
+ export declare const MATERIAL_TABLE_WGSL = "\nstruct Material {\n /** rgb is the tint, w is the emissive gain. */\n tint: vec4<f32>,\n /** x is the roughness, y the specular reflectance, z the environment's and w the metalness. */\n surface: vec4<f32>,\n /** Base colour, normal, ORM and emissive programs; NO_PROGRAM where there is none. */\n programs: vec4<u32>,\n /** u scale, v scale, normal strength, occlusion strength. */\n maps: vec4<f32>,\n /** Roughness scale, metallic scale, the alpha cutoff, and non-zero where this one blends. */\n orm: vec4<f32>,\n /** The blended opacity, the glass's transmission and frost, and one spare. */\n extra: vec4<f32>,\n /** The glass's tint, white where the material is not glass, and one spare. */\n glassTint: vec4<f32>,\n}\n\nconst NO_PROGRAM: u32 = 4294967295u;\n";
@@ -35,8 +35,10 @@ struct Material {
35
35
  maps: vec4<f32>,
36
36
  /** Roughness scale, metallic scale, the alpha cutoff, and non-zero where this one blends. */
37
37
  orm: vec4<f32>,
38
- /** The blended opacity, and three spare. */
38
+ /** The blended opacity, the glass's transmission and frost, and one spare. */
39
39
  extra: vec4<f32>,
40
+ /** The glass's tint, white where the material is not glass, and one spare. */
41
+ glassTint: vec4<f32>,
40
42
  }
41
43
 
42
44
  const NO_PROGRAM: u32 = ${DECODE_NO_PROGRAM}u;
@@ -8,6 +8,7 @@ import { MATERIAL_SLOTS, MATERIAL_TABLE_WGSL } from './materialTable.wgsl.js';
8
8
  import { MESH_TRANSFORM_WGSL } from './meshTransform.wgsl.js';
9
9
  import { ENVIRONMENT_WGSL } from './environment.wgsl.js';
10
10
  import { SHADE_SHADOW_WGSL } from './shadow.wgsl.js';
11
+ import { SUN_GLASS_WGSL } from './sunGlass.wgsl.js';
11
12
  /**
12
13
  * Rebuilding a pixel's surface from the triangle it recorded, on the device.
13
14
  *
@@ -509,6 +510,14 @@ struct Job {
509
510
  @group(0) @binding(12) var environmentMap: texture_2d_array<f32>;
510
511
  @group(0) @binding(13) var environmentSampler: sampler;
511
512
  ${decodeResourcesWgsl({ group: 0, latents: 14, clampSampler: 15, repeatSampler: 16, nodes: 17, weights: 18 })}
513
+ /*
514
+ * **What the sun keeps through glass: the nearest pane's depth and what the panes let through**,
515
+ * which the pass draws from the sun after its opaque map. Where no material is glass the depth is
516
+ * the opaque map again and the tint a single white texel, which sunGlassAt reads as none.
517
+ */
518
+ @group(0) @binding(19) var glassDepthMap: texture_depth_2d;
519
+ @group(0) @binding(20) var glassTints: texture_2d<f32>;
520
+ @group(0) @binding(21) var glassSampler: sampler;
512
521
 
513
522
  fn decodeLatentSize() -> f32 {
514
523
  return frame.size.w;
@@ -540,6 +549,45 @@ fn shadowDepthAt(u: f32, v: f32) -> f32 {
540
549
  return textureLoad(shadowMap, vec2<i32>(x, y), 0);
541
550
  }
542
551
  ${SHADE_SHADOW_WGSL}
552
+
553
+ /* sunGlass.wgsl.ts's three reads, as the opaque map is read: nearest by integer coordinate. */
554
+ fn glassDepthAt(u: f32, v: f32) -> f32 {
555
+ let size = frame.shadow.y;
556
+ let x = i32(clamp(floor(u * size), 0.0, size - 1.0));
557
+ let y = i32(clamp(floor(v * size), 0.0, size - 1.0));
558
+ return textureLoad(glassDepthMap, vec2<i32>(x, y), 0);
559
+ }
560
+
561
+ fn glassTintTexel(u: f32, v: f32) -> vec4<f32> {
562
+ let size = f32(textureDimensions(glassTints, 0).x);
563
+ let x = i32(clamp(floor(u * size), 0.0, size - 1.0));
564
+ let y = i32(clamp(floor(v * size), 0.0, size - 1.0));
565
+ return textureLoad(glassTints, vec2<i32>(x, y), 0);
566
+ }
567
+
568
+ /* The tint over a spread, from the chain: the level whose texel is the spread's width. */
569
+ fn glassTintAround(u: f32, v: f32, radius: f32) -> vec4<f32> {
570
+ let size = f32(textureDimensions(glassTints, 0).x);
571
+ return textureSampleLevel(glassTints, glassSampler, vec2<f32>(u, v), log2(max(radius * size, 1.0)));
572
+ }
573
+ ${SUN_GLASS_WGSL}
574
+
575
+ /*
576
+ * The sun's colour at this receiver after the panes between it and the sun: white where no glass
577
+ * cast, and faded with the opaque shadow's own strength. The map's scale in metres comes off the
578
+ * light's matrix, which is orthographic: a compute invocation has no derivatives to take it from.
579
+ */
580
+ fn sunGlassAt(lightPos: vec3<f32>) -> vec3<f32> {
581
+ if (textureDimensions(glassTints, 0).x <= 1u || frame.shadow.x <= 0.0) { return vec3<f32>(1.0); }
582
+ let m = frame.lightViewProj;
583
+ var settings: SunGlassSettings;
584
+ settings.mapSize = frame.shadow.y;
585
+ settings.depthSpan = frame.shadow.z;
586
+ settings.uvPerMetre = 0.5 * length(vec3<f32>(m[0][0], m[1][0], m[2][0]));
587
+ settings.taps = frame.shadowLimits.y;
588
+ let tint = sunGlass(lightPos.x * 0.5 + 0.5, lightPos.y * 0.5 + 0.5, lightPos.z, settings);
589
+ return mix(vec3<f32>(1.0), tint, frame.shadow.x);
590
+ }
543
591
  ${ENVIRONMENT_WGSL}
544
592
  ${FOG_WGSL}
545
593
 
@@ -732,7 +780,8 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
732
780
 
733
781
  var room: LitEnvironment;
734
782
  room.lightDir = frame.lightDir.xyz;
735
- room.lightColour = frame.lightColour.xyz;
783
+ /* The sun's colour through whatever glass stands between it and here, as flat/main.ts's is. */
784
+ room.lightColour = frame.lightColour.xyz * sunGlassAt(lightPos);
736
785
  room.sky = frame.sky.xyz;
737
786
  room.ground = frame.ground.xyz;
738
787
  /*
@@ -0,0 +1,9 @@
1
+ export declare const SUN_GLASS_WGSL: string;
2
+ /**
3
+ * `sunGlass` over maps held in storage buffers, read as the reference reads them, so the whole
4
+ * lookup can be compared with `glassTintAt` rather than only the arithmetic between the reads.
5
+ *
6
+ * Four floats a case in — u, v, the receiver's depth, one spare — and four out, the tint and one
7
+ * spare. The config is the map's size, the depth span, map units a metre, and the tap count.
8
+ */
9
+ export declare const SUN_GLASS_PARITY_WGSL: string;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * The sun through glass for the device: `gpudriven/sunGlass.ts`'s `glassTintAt`, term for term, as
3
+ * the text the shading pass and the parity entry point both include.
4
+ *
5
+ * **The reads are the including module's**, as `SHADE_SHADOW_WGSL`'s are: `glassDepthAt(u, v)` the
6
+ * nearest pane's depth, `glassTintTexel(u, v)` the tint at the finest level, and
7
+ * `glassTintAround(u, v, radius)` the tint averaged over a spread of that radius. The shading pass
8
+ * answers the last from the tint's mip chain; the parity entry answers it with the reference's own
9
+ * box average, so everything around the reads is checked as it stands. Every constant is the
10
+ * reference's, interpolated rather than restated.
11
+ */
12
+ import { FROST_RADIUS_CAP, FROST_SPREAD } from '../../glassShadow.js';
13
+ import { SHADOW_DEPTH_BIAS, SHADOW_PCF_OFFSETS, SHADOW_TEXEL_SCALE, } from '../../gpudriven/shadow.js';
14
+ import { SHADE_SHADOW_WGSL } from './shadow.wgsl.js';
15
+ export const SUN_GLASS_WGSL = /* wgsl */ `
16
+ struct SunGlassSettings {
17
+ mapSize: f32,
18
+ depthSpan: f32,
19
+ uvPerMetre: f32,
20
+ taps: f32,
21
+ }
22
+
23
+ /*
24
+ * A coarse texel with the open ground's white taken back out, by how milky the pane at the
25
+ * receiver is: the pane's colour and its share of the texel. glassShadow.ts's unmixTint.
26
+ */
27
+ fn sunGlassUnmix(mip: vec4<f32>, centreClarity: f32) -> vec4<f32> {
28
+ if (centreClarity >= 1.0) { return vec4<f32>(mip.xyz, 1.0); }
29
+ let share = clamp((1.0 - mip.w) / (1.0 - centreClarity), 0.0, 1.0);
30
+ if (share <= 0.0) { return vec4<f32>(1.0, 1.0, 1.0, 0.0); }
31
+ let colour = clamp((mip.xyz - vec3<f32>(1.0 - share)) / share, vec3<f32>(0.0), vec3<f32>(1.0));
32
+ return vec4<f32>(colour, share);
33
+ }
34
+
35
+ /* What the sun keeps through glass at map position (u, v) and light depth "depth". */
36
+ fn sunGlass(u: f32, v: f32, depth: f32, settings: SunGlassSettings) -> vec3<f32> {
37
+ if (u < 0.0 || u > 1.0 || v < 0.0 || v > 1.0) { return vec3<f32>(1.0); }
38
+ let texel = ${SHADOW_TEXEL_SCALE.toFixed(4)} / settings.mapSize;
39
+ let bias = ${SHADOW_DEPTH_BIAS.toFixed(4)} / settings.depthSpan;
40
+ let taps = min(u32(settings.taps), ${SHADOW_PCF_OFFSETS.length}u);
41
+ let clarity = glassTintTexel(u, v).w;
42
+ let behind = max(depth - glassDepthAt(u, v), 0.0) * settings.depthSpan;
43
+ let spread = (1.0 - clarity) * ${FROST_SPREAD.toFixed(6)} * behind * settings.uvPerMetre;
44
+ let radius = min(max(texel, spread), texel * ${FROST_RADIUS_CAP.toFixed(1)});
45
+
46
+ var cover = 0.0;
47
+ var colour = vec3<f32>(0.0);
48
+ var found = 0.0;
49
+ for (var i = 0u; i < taps; i = i + 1u) {
50
+ let offset = SHADOW_TAPS[i];
51
+ /* The outline: a tap counts the pane only where the receiver is behind it, by the bias. */
52
+ if (depth > glassDepthAt(u + offset.x * texel, v + offset.y * texel) + bias) {
53
+ cover = cover + 1.0;
54
+ }
55
+ /* The colour: the spread's taps, each an average, unmixed and weighed by the pane's share. */
56
+ let pane = sunGlassUnmix(glassTintAround(u + offset.x * radius, v + offset.y * radius, radius), clarity);
57
+ colour = colour + pane.xyz * pane.w;
58
+ found = found + pane.w;
59
+ }
60
+ let mixed = select(vec3<f32>(1.0), colour / max(found, 1e-6), found > 0.0);
61
+ return mix(vec3<f32>(1.0), mixed, cover / f32(max(taps, 1u)));
62
+ }
63
+ `;
64
+ /**
65
+ * `sunGlass` over maps held in storage buffers, read as the reference reads them, so the whole
66
+ * lookup can be compared with `glassTintAt` rather than only the arithmetic between the reads.
67
+ *
68
+ * Four floats a case in — u, v, the receiver's depth, one spare — and four out, the tint and one
69
+ * spare. The config is the map's size, the depth span, map units a metre, and the tap count.
70
+ */
71
+ export const SUN_GLASS_PARITY_WGSL = /* wgsl */ `
72
+ @group(0) @binding(0) var<storage, read> depths: array<f32>;
73
+ @group(0) @binding(1) var<storage, read> tints: array<f32>;
74
+ @group(0) @binding(2) var<storage, read> cases: array<f32>;
75
+ @group(0) @binding(3) var<storage, read> config: array<f32>;
76
+ @group(0) @binding(4) var<storage, read_write> results: array<f32>;
77
+
78
+ /* The opaque lookup's own read, which this entry never calls: included only for its tap table. */
79
+ fn shadowDepthAt(u: f32, v: f32) -> f32 { return 1.0; }
80
+ ${SHADE_SHADOW_WGSL}
81
+
82
+ fn glassIndex(u: f32, v: f32) -> u32 {
83
+ let size = config[0];
84
+ let x = u32(clamp(floor(u * size), 0.0, size - 1.0));
85
+ let y = u32(clamp(floor(v * size), 0.0, size - 1.0));
86
+ return y * u32(size) + x;
87
+ }
88
+
89
+ fn glassDepthAt(u: f32, v: f32) -> f32 {
90
+ return depths[glassIndex(u, v)];
91
+ }
92
+
93
+ fn glassTintTexel(u: f32, v: f32) -> vec4<f32> {
94
+ let at = glassIndex(u, v) * 4u;
95
+ return vec4<f32>(tints[at], tints[at + 1u], tints[at + 2u], tints[at + 3u]);
96
+ }
97
+
98
+ /* The reference's coarse read: the average of every texel within the radius. */
99
+ fn glassTintAround(u: f32, v: f32, radius: f32) -> vec4<f32> {
100
+ let size = config[0];
101
+ let reach = radius * size;
102
+ let x0 = u32(max(0.0, floor(u * size - reach)));
103
+ let x1 = u32(min(size - 1.0, floor(u * size + reach)));
104
+ let y0 = u32(max(0.0, floor(v * size - reach)));
105
+ let y1 = u32(min(size - 1.0, floor(v * size + reach)));
106
+ var sum = vec4<f32>(0.0);
107
+ var n = 0.0;
108
+ for (var y = y0; y <= y1; y = y + 1u) {
109
+ for (var x = x0; x <= x1; x = x + 1u) {
110
+ let at = (y * u32(size) + x) * 4u;
111
+ sum = sum + vec4<f32>(tints[at], tints[at + 1u], tints[at + 2u], tints[at + 3u]);
112
+ n = n + 1.0;
113
+ }
114
+ }
115
+ return sum / max(n, 1.0);
116
+ }
117
+ ${SUN_GLASS_WGSL}
118
+
119
+ @compute @workgroup_size(64)
120
+ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
121
+ if (id.x * 4u + 3u >= arrayLength(&cases)) { return; }
122
+ var settings: SunGlassSettings;
123
+ settings.mapSize = config[0];
124
+ settings.depthSpan = config[1];
125
+ settings.uvPerMetre = config[2];
126
+ settings.taps = config[3];
127
+ let at = id.x * 4u;
128
+ let tint = sunGlass(cases[at], cases[at + 1u], cases[at + 2u], settings);
129
+ results[at] = tint.x;
130
+ results[at + 1u] = tint.y;
131
+ results[at + 2u] = tint.z;
132
+ }
133
+ `;
@@ -46,6 +46,7 @@
46
46
  * the window it came through, and without it a barred floor under a smooth shaft is two
47
47
  * halves of one frame disagreeing about the same light.
48
48
  */
49
+ import { SUN_DYNAMIC_LAYER, SUN_PEELED_LAYER, SUN_STATIC_LAYER } from '../shadowMap.js';
49
50
  import { resolveConditionals } from './conditionals.js';
50
51
  export const LIGHT_VOLUME_VERT = `#version 300 es
51
52
  precision highp float;
@@ -143,9 +144,8 @@ uniform vec3 uDustOffset;
143
144
  uniform float uSunShadow;
144
145
  uniform mat4 uLightViewProj;
145
146
  uniform float uShadowMapSize;
146
- uniform sampler2D uStaticShadowMap;
147
- uniform sampler2D uPeeledShadowMap;
148
- uniform sampler2D uDynamicShadowMap;
147
+ /* The sun's static, moving and peeled maps, as layers of one array: see shadowMap.ts. */
148
+ uniform highp sampler2DArray uSunShadows;
149
149
  uniform int uPeeledShadowEnabled;
150
150
  #endif
151
151
 
@@ -308,11 +308,11 @@ float sunReach(vec3 worldPos) {
308
308
  if (edgeFade <= 0.0) return 1.0;
309
309
 
310
310
  float compare = p.z - BIAS;
311
- float blocked = occlusion(textureLod(uStaticShadowMap, p.xy, 0.0).r, compare);
311
+ float blocked = occlusion(textureLod(uSunShadows, vec3(p.xy, ${SUN_STATIC_LAYER}.0), 0.0).r, compare);
312
312
  if (uPeeledShadowEnabled != 0) {
313
- blocked = max(blocked, occlusion(textureLod(uPeeledShadowMap, p.xy, 0.0).r, compare));
313
+ blocked = max(blocked, occlusion(textureLod(uSunShadows, vec3(p.xy, ${SUN_PEELED_LAYER}.0), 0.0).r, compare));
314
314
  }
315
- blocked = max(blocked, occlusion(textureLod(uDynamicShadowMap, p.xy, 0.0).r, compare));
315
+ blocked = max(blocked, occlusion(textureLod(uSunShadows, vec3(p.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r, compare));
316
316
  return mix(1.0, 1.0 - blocked, uSunShadow * edgeFade);
317
317
  }
318
318
  #endif
@@ -23,3 +23,11 @@ export declare const OCTAHEDRAL_RESOLVE_VERT = "#version 300 es\nvoid main() {\n
23
23
  * redeclaration and GLSL rejects the whole shader.
24
24
  */
25
25
  export declare const OCTAHEDRAL_RESOLVE_FRAG = "#version 300 es\nprecision highp float;\n\n/** The face the bake just rendered, as a depth texture. */\nuniform highp sampler2D uFace;\n/** That face's world-to-face rotation. See pointShadowFaceRotation. */\nuniform mat3 uFaceRotation;\n/** Which of the six this pass is resolving. Every other texel discards. */\nuniform int uFaceIndex;\nuniform float uFar;\nuniform float uNear;\n/** The octahedral map's edge, so this needs no viewport query. */\nuniform float uEdge;\n\nvec2 octEncode(vec3 d) {\n vec3 n = d / (abs(d.x) + abs(d.y) + abs(d.z));\n vec2 p = n.z >= 0.0\n ? n.xy\n : (1.0 - abs(vec2(n.y, n.x))) * vec2(n.x >= 0.0 ? 1.0 : -1.0, n.y >= 0.0 ? 1.0 : -1.0);\n return p * 0.5 + 0.5;\n}\n\nvec3 octDecode(vec2 uv) {\n vec2 f = uv * 2.0 - 1.0;\n vec3 n = vec3(f.x, f.y, 1.0 - abs(f.x) - abs(f.y));\n float t = max(-n.z, 0.0);\n n.x += n.x >= 0.0 ? -t : t;\n n.y += n.y >= 0.0 ? -t : t;\n return normalize(n);\n}\n\nvec2 octWrapUv(vec2 uv) {\n vec2 f = uv * 2.0 - 1.0;\n if (f.x < -1.0 || f.x > 1.0) {\n f.x = (f.x > 0.0 ? 2.0 : -2.0) - f.x;\n f.y = -f.y;\n }\n if (f.y < -1.0 || f.y > 1.0) {\n f.y = (f.y > 0.0 ? 2.0 : -2.0) - f.y;\n f.x = -f.x;\n }\n return f * 0.5 + 0.5;\n}\n\nvec2 octInsetUv(vec3 d, float edge) {\n return (octEncode(d) * (edge - 2.0) + 1.0) / edge;\n}\n\nvec3 octInsetDir(vec2 uv, float edge) {\n return octDecode(octWrapUv((uv * edge - 1.0) / (edge - 2.0)));\n}\n\nvoid main() {\n vec3 d = octDecode(gl_FragCoord.xy / uEdge);\n\n /*\n * Which face owns this texel. The tie-breaking is x, then y, then z, and it has to match the\n * CPU's exactly or a sliver of texels along a boundary belongs to two faces or to none.\n */\n vec3 a = abs(d);\n int face;\n if (a.x >= a.y && a.x >= a.z) face = d.x >= 0.0 ? 0 : 1;\n else if (a.y >= a.z) face = d.y >= 0.0 ? 2 : 3;\n else face = d.z >= 0.0 ? 4 : 5;\n if (face != uFaceIndex) discard;\n\n /* lookAt looks down -z, so a direction inside this face has a negative z here. */\n vec3 v = uFaceRotation * d;\n float forward = -v.z;\n vec2 faceUv = (v.xy / forward) * 0.5 + 0.5;\n\n /*\n * textureLod at level zero rather than plain texture. The scratch carries one storage level and\n * NEAREST filtering so the fetch is identical, and this call sits after a discard \u2014 which is\n * non-uniform control flow, where an implicit derivative is undefined in GLSL and rejected\n * outright in WGSL. AGENTS.md, 2026-08-07.\n */\n float stored = textureLod(uFace, faceUv, 0.0).r;\n if (stored >= 0.9999) {\n /* Nothing was rendered along this direction, so the stored radius is the far plane. */\n gl_FragDepth = 1.0;\n return;\n }\n float ndc = stored * 2.0 - 1.0;\n float faceLocalZ = (2.0 * uFar * uNear) / (uFar + uNear - ndc * (uFar - uNear));\n /* d is a unit vector, so forward is its cosine to the axis and this is the true radius. */\n gl_FragDepth = clamp((faceLocalZ / forward) / uFar, 0.0, 1.0);\n}";
26
+ /**
27
+ * One face of a lamp's glass tint into its region of the tint layer: the depth resolve's region,
28
+ * and a colour copied rather than a depth decoded.
29
+ *
30
+ * **Nearest, at level zero**, like the depth beside it: the tint's outline has to land on the
31
+ * glass depth's texels, and a filtered copy would smear a pane's colour a texel past its edge.
32
+ */
33
+ export declare const OCTAHEDRAL_RESOLVE_TINT_FRAG = "#version 300 es\nprecision highp float;\n\n/** The face the bake just tinted, as colour. */\nuniform highp sampler2D uFace;\nuniform mat3 uFaceRotation;\nuniform int uFaceIndex;\nuniform float uEdge;\n\nout vec4 outTint;\n\nvec2 octEncode(vec3 d) {\n vec3 n = d / (abs(d.x) + abs(d.y) + abs(d.z));\n vec2 p = n.z >= 0.0\n ? n.xy\n : (1.0 - abs(vec2(n.y, n.x))) * vec2(n.x >= 0.0 ? 1.0 : -1.0, n.y >= 0.0 ? 1.0 : -1.0);\n return p * 0.5 + 0.5;\n}\n\nvec3 octDecode(vec2 uv) {\n vec2 f = uv * 2.0 - 1.0;\n vec3 n = vec3(f.x, f.y, 1.0 - abs(f.x) - abs(f.y));\n float t = max(-n.z, 0.0);\n n.x += n.x >= 0.0 ? -t : t;\n n.y += n.y >= 0.0 ? -t : t;\n return normalize(n);\n}\n\nvec2 octWrapUv(vec2 uv) {\n vec2 f = uv * 2.0 - 1.0;\n if (f.x < -1.0 || f.x > 1.0) {\n f.x = (f.x > 0.0 ? 2.0 : -2.0) - f.x;\n f.y = -f.y;\n }\n if (f.y < -1.0 || f.y > 1.0) {\n f.y = (f.y > 0.0 ? 2.0 : -2.0) - f.y;\n f.x = -f.x;\n }\n return f * 0.5 + 0.5;\n}\n\nvec2 octInsetUv(vec3 d, float edge) {\n return (octEncode(d) * (edge - 2.0) + 1.0) / edge;\n}\n\nvec3 octInsetDir(vec2 uv, float edge) {\n return octDecode(octWrapUv((uv * edge - 1.0) / (edge - 2.0)));\n}\n\nvoid main() {\n vec3 d = octDecode(gl_FragCoord.xy / uEdge);\n\n /*\n * Which face owns this texel. The tie-breaking is x, then y, then z, and it has to match the\n * CPU's exactly or a sliver of texels along a boundary belongs to two faces or to none.\n */\n vec3 a = abs(d);\n int face;\n if (a.x >= a.y && a.x >= a.z) face = d.x >= 0.0 ? 0 : 1;\n else if (a.y >= a.z) face = d.y >= 0.0 ? 2 : 3;\n else face = d.z >= 0.0 ? 4 : 5;\n if (face != uFaceIndex) discard;\n\n /* lookAt looks down -z, so a direction inside this face has a negative z here. */\n vec3 v = uFaceRotation * d;\n float forward = -v.z;\n vec2 faceUv = (v.xy / forward) * 0.5 + 0.5;\n outTint = textureLod(uFace, faceUv, 0.0);\n}";