@woosh/meep-engine 3.13.0 → 3.14.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 (663) hide show
  1. package/README.md +257 -119
  2. package/editor/prototypeEditorShell.js +26 -2
  3. package/package.json +4 -3
  4. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  5. package/src/core/geom/3d/shape/ConvexHullShape3D.js +41 -25
  6. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  7. package/src/core/geom/3d/shape/PosedShape3D.js +7 -2
  8. package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.d.ts +47 -0
  9. package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.d.ts.map +1 -0
  10. package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.js +109 -0
  11. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshHighlightSystem.d.ts +28 -16
  12. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshHighlightSystem.d.ts.map +1 -1
  13. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshHighlightSystem.js +111 -92
  14. package/src/engine/graphics3/FogOfWarSystem.d.ts +1 -1
  15. package/src/engine/graphics3/FogOfWarSystem.d.ts.map +1 -1
  16. package/src/engine/graphics3/FogOfWarSystem.js +1 -1
  17. package/src/engine/graphics3/HighlightOutlineSystem.d.ts +99 -0
  18. package/src/engine/graphics3/HighlightOutlineSystem.d.ts.map +1 -0
  19. package/src/engine/graphics3/HighlightOutlineSystem.js +240 -0
  20. package/src/engine/graphics3/HighlightSystem.d.ts +14 -77
  21. package/src/engine/graphics3/HighlightSystem.d.ts.map +1 -1
  22. package/src/engine/graphics3/HighlightSystem.js +19 -233
  23. package/src/engine/graphics3/HighlightSystem3.d.ts +7 -4
  24. package/src/engine/graphics3/HighlightSystem3.d.ts.map +1 -1
  25. package/src/engine/graphics3/HighlightSystem3.js +15 -10
  26. package/src/engine/graphics3/highlight/pack_highlight_table.d.ts +13 -8
  27. package/src/engine/graphics3/highlight/pack_highlight_table.d.ts.map +1 -1
  28. package/src/engine/graphics3/highlight/pack_highlight_table.js +14 -8
  29. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  30. package/src/shade/RENDERER_CONTRACT.md +579 -568
  31. package/src/shade/device/PASS_ENCODER_PROPOSAL_2026_08_31.md +19 -14
  32. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.js +1 -1
  33. package/src/shade/renderer/scene/Mesh.d.ts.map +1 -1
  34. package/src/shade/renderer/scene/Mesh.js +4 -6
  35. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts +16 -7
  36. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  37. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +1342 -1322
  38. package/src/shade/renderer/shadow/map/Shadowmap.d.ts +13 -0
  39. package/src/shade/renderer/shadow/map/Shadowmap.d.ts.map +1 -1
  40. package/src/shade/renderer/shadow/map/Shadowmap.js +14 -0
  41. package/src/shade/renderer/shadow/map/ShadowmapDrawSelector.d.ts +79 -0
  42. package/src/shade/renderer/shadow/map/ShadowmapDrawSelector.d.ts.map +1 -0
  43. package/src/shade/renderer/shadow/map/ShadowmapDrawSelector.js +196 -0
  44. package/src/shade/renderer/shadow/map/ShadowmapRegistry.d.ts +91 -0
  45. package/src/shade/renderer/shadow/map/ShadowmapRegistry.d.ts.map +1 -0
  46. package/src/shade/renderer/shadow/map/ShadowmapRegistry.js +156 -0
  47. package/src/engine/graphics/ecs/highlight/plugin/HighlightRenderElementSource.d.ts +0 -9
  48. package/src/engine/graphics/ecs/highlight/plugin/HighlightRenderElementSource.d.ts.map +0 -1
  49. package/src/engine/graphics/ecs/highlight/plugin/HighlightRenderElementSource.js +0 -12
  50. package/src/engine/graphics/ecs/highlight/renderer/HighlightRenderElement.d.ts +0 -16
  51. package/src/engine/graphics/ecs/highlight/renderer/HighlightRenderElement.d.ts.map +0 -1
  52. package/src/engine/graphics/ecs/highlight/renderer/HighlightRenderElement.js +0 -44
  53. package/src/engine/graphics/ecs/highlight/renderer/HighlightRenderGroup.d.ts +0 -20
  54. package/src/engine/graphics/ecs/highlight/renderer/HighlightRenderGroup.d.ts.map +0 -1
  55. package/src/engine/graphics/ecs/highlight/renderer/HighlightRenderGroup.js +0 -29
  56. package/src/engine/graphics/ecs/highlight/renderer/makeGaussianBlurShader.d.ts +0 -8
  57. package/src/engine/graphics/ecs/highlight/renderer/makeGaussianBlurShader.d.ts.map +0 -1
  58. package/src/engine/graphics/ecs/highlight/renderer/makeGaussianBlurShader.js +0 -58
  59. package/src/engine/graphics/ecs/highlight/renderer/traverseThreeObject.d.ts +0 -8
  60. package/src/engine/graphics/ecs/highlight/renderer/traverseThreeObject.d.ts.map +0 -1
  61. package/src/engine/graphics/ecs/highlight/renderer/traverseThreeObject.js +0 -18
  62. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1.mag.xy.png +0 -0
  63. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1.mag.xz.png +0 -0
  64. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_0.png +0 -0
  65. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_1.png +0 -0
  66. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_10.png +0 -0
  67. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_11.png +0 -0
  68. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_12.png +0 -0
  69. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_13.png +0 -0
  70. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_14.png +0 -0
  71. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_15.png +0 -0
  72. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_16.png +0 -0
  73. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_17.png +0 -0
  74. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_18.png +0 -0
  75. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_19.png +0 -0
  76. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_2.png +0 -0
  77. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_20.png +0 -0
  78. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_21.png +0 -0
  79. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_22.png +0 -0
  80. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_23.png +0 -0
  81. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_24.png +0 -0
  82. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_25.png +0 -0
  83. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_26.png +0 -0
  84. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_27.png +0 -0
  85. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_28.png +0 -0
  86. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_29.png +0 -0
  87. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_3.png +0 -0
  88. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_30.png +0 -0
  89. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_31.png +0 -0
  90. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_32.png +0 -0
  91. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_33.png +0 -0
  92. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_34.png +0 -0
  93. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_35.png +0 -0
  94. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_36.png +0 -0
  95. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_37.png +0 -0
  96. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_38.png +0 -0
  97. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_39.png +0 -0
  98. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_4.png +0 -0
  99. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_40.png +0 -0
  100. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_41.png +0 -0
  101. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_42.png +0 -0
  102. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_43.png +0 -0
  103. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_44.png +0 -0
  104. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_45.png +0 -0
  105. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_46.png +0 -0
  106. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_47.png +0 -0
  107. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_48.png +0 -0
  108. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_49.png +0 -0
  109. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_5.png +0 -0
  110. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_50.png +0 -0
  111. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_51.png +0 -0
  112. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_52.png +0 -0
  113. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_53.png +0 -0
  114. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_54.png +0 -0
  115. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_55.png +0 -0
  116. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_56.png +0 -0
  117. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_57.png +0 -0
  118. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_58.png +0 -0
  119. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_59.png +0 -0
  120. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_6.png +0 -0
  121. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_60.png +0 -0
  122. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_61.png +0 -0
  123. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_62.png +0 -0
  124. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_63.png +0 -0
  125. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_7.png +0 -0
  126. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_8.png +0 -0
  127. package/src/shade/renderer/texture/noise/stbn/images/stbn_scalar_2Dx1Dx1D_128x128x64x1_9.png +0 -0
  128. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64.dat +0 -0
  129. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64.mag.xy.png +0 -0
  130. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64.mag.xz.png +0 -0
  131. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_0.png +0 -0
  132. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_1.png +0 -0
  133. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_10.png +0 -0
  134. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_11.png +0 -0
  135. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_12.png +0 -0
  136. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_13.png +0 -0
  137. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_14.png +0 -0
  138. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_15.png +0 -0
  139. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_16.png +0 -0
  140. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_17.png +0 -0
  141. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_18.png +0 -0
  142. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_19.png +0 -0
  143. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_2.png +0 -0
  144. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_20.png +0 -0
  145. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_21.png +0 -0
  146. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_22.png +0 -0
  147. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_23.png +0 -0
  148. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_24.png +0 -0
  149. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_25.png +0 -0
  150. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_26.png +0 -0
  151. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_27.png +0 -0
  152. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_28.png +0 -0
  153. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_29.png +0 -0
  154. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_3.png +0 -0
  155. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_30.png +0 -0
  156. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_31.png +0 -0
  157. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_32.png +0 -0
  158. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_33.png +0 -0
  159. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_34.png +0 -0
  160. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_35.png +0 -0
  161. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_36.png +0 -0
  162. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_37.png +0 -0
  163. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_38.png +0 -0
  164. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_39.png +0 -0
  165. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_4.png +0 -0
  166. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_40.png +0 -0
  167. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_41.png +0 -0
  168. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_42.png +0 -0
  169. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_43.png +0 -0
  170. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_44.png +0 -0
  171. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_45.png +0 -0
  172. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_46.png +0 -0
  173. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_47.png +0 -0
  174. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_48.png +0 -0
  175. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_49.png +0 -0
  176. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_5.png +0 -0
  177. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_50.png +0 -0
  178. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_51.png +0 -0
  179. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_52.png +0 -0
  180. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_53.png +0 -0
  181. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_54.png +0 -0
  182. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_55.png +0 -0
  183. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_56.png +0 -0
  184. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_57.png +0 -0
  185. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_58.png +0 -0
  186. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_59.png +0 -0
  187. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_6.png +0 -0
  188. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_60.png +0 -0
  189. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_61.png +0 -0
  190. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_62.png +0 -0
  191. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_63.png +0 -0
  192. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_7.png +0 -0
  193. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_8.png +0 -0
  194. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec1_2Dx1D_128x128x64_9.png +0 -0
  195. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64.dat +0 -0
  196. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64.mag.xy.png +0 -0
  197. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64.mag.xz.png +0 -0
  198. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_0.png +0 -0
  199. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_1.png +0 -0
  200. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_10.png +0 -0
  201. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_11.png +0 -0
  202. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_12.png +0 -0
  203. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_13.png +0 -0
  204. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_14.png +0 -0
  205. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_15.png +0 -0
  206. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_16.png +0 -0
  207. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_17.png +0 -0
  208. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_18.png +0 -0
  209. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_19.png +0 -0
  210. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_2.png +0 -0
  211. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_20.png +0 -0
  212. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_21.png +0 -0
  213. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_22.png +0 -0
  214. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_23.png +0 -0
  215. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_24.png +0 -0
  216. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_25.png +0 -0
  217. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_26.png +0 -0
  218. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_27.png +0 -0
  219. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_28.png +0 -0
  220. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_29.png +0 -0
  221. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_3.png +0 -0
  222. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_30.png +0 -0
  223. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_31.png +0 -0
  224. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_32.png +0 -0
  225. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_33.png +0 -0
  226. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_34.png +0 -0
  227. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_35.png +0 -0
  228. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_36.png +0 -0
  229. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_37.png +0 -0
  230. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_38.png +0 -0
  231. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_39.png +0 -0
  232. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_4.png +0 -0
  233. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_40.png +0 -0
  234. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_41.png +0 -0
  235. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_42.png +0 -0
  236. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_43.png +0 -0
  237. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_44.png +0 -0
  238. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_45.png +0 -0
  239. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_46.png +0 -0
  240. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_47.png +0 -0
  241. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_48.png +0 -0
  242. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_49.png +0 -0
  243. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_5.png +0 -0
  244. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_50.png +0 -0
  245. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_51.png +0 -0
  246. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_52.png +0 -0
  247. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_53.png +0 -0
  248. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_54.png +0 -0
  249. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_55.png +0 -0
  250. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_56.png +0 -0
  251. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_57.png +0 -0
  252. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_58.png +0 -0
  253. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_59.png +0 -0
  254. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_6.png +0 -0
  255. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_60.png +0 -0
  256. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_61.png +0 -0
  257. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_62.png +0 -0
  258. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_63.png +0 -0
  259. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_7.png +0 -0
  260. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_8.png +0 -0
  261. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec2_2Dx1D_128x128x64_9.png +0 -0
  262. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64.dat +0 -0
  263. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64.mag.xy.png +0 -0
  264. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64.mag.xz.png +0 -0
  265. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_0.png +0 -0
  266. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_1.png +0 -0
  267. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_10.png +0 -0
  268. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_11.png +0 -0
  269. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_12.png +0 -0
  270. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_13.png +0 -0
  271. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_14.png +0 -0
  272. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_15.png +0 -0
  273. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_16.png +0 -0
  274. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_17.png +0 -0
  275. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_18.png +0 -0
  276. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_19.png +0 -0
  277. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_2.png +0 -0
  278. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_20.png +0 -0
  279. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_21.png +0 -0
  280. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_22.png +0 -0
  281. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_23.png +0 -0
  282. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_24.png +0 -0
  283. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_25.png +0 -0
  284. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_26.png +0 -0
  285. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_27.png +0 -0
  286. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_28.png +0 -0
  287. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_29.png +0 -0
  288. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_3.png +0 -0
  289. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_30.png +0 -0
  290. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_31.png +0 -0
  291. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_32.png +0 -0
  292. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_33.png +0 -0
  293. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_34.png +0 -0
  294. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_35.png +0 -0
  295. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_36.png +0 -0
  296. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_37.png +0 -0
  297. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_38.png +0 -0
  298. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_39.png +0 -0
  299. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_4.png +0 -0
  300. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_40.png +0 -0
  301. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_41.png +0 -0
  302. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_42.png +0 -0
  303. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_43.png +0 -0
  304. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_44.png +0 -0
  305. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_45.png +0 -0
  306. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_46.png +0 -0
  307. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_47.png +0 -0
  308. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_48.png +0 -0
  309. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_49.png +0 -0
  310. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_5.png +0 -0
  311. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_50.png +0 -0
  312. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_51.png +0 -0
  313. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_52.png +0 -0
  314. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_53.png +0 -0
  315. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_54.png +0 -0
  316. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_55.png +0 -0
  317. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_56.png +0 -0
  318. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_57.png +0 -0
  319. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_58.png +0 -0
  320. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_59.png +0 -0
  321. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_6.png +0 -0
  322. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_60.png +0 -0
  323. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_61.png +0 -0
  324. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_62.png +0 -0
  325. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_63.png +0 -0
  326. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_7.png +0 -0
  327. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_8.png +0 -0
  328. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_2Dx1D_128x128x64_9.png +0 -0
  329. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64.dat +0 -0
  330. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64.mag.xy.png +0 -0
  331. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64.mag.xz.png +0 -0
  332. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_0.png +0 -0
  333. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_1.png +0 -0
  334. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_10.png +0 -0
  335. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_11.png +0 -0
  336. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_12.png +0 -0
  337. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_13.png +0 -0
  338. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_14.png +0 -0
  339. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_15.png +0 -0
  340. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_16.png +0 -0
  341. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_17.png +0 -0
  342. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_18.png +0 -0
  343. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_19.png +0 -0
  344. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_2.png +0 -0
  345. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_20.png +0 -0
  346. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_21.png +0 -0
  347. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_22.png +0 -0
  348. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_23.png +0 -0
  349. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_24.png +0 -0
  350. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_25.png +0 -0
  351. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_26.png +0 -0
  352. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_27.png +0 -0
  353. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_28.png +0 -0
  354. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_29.png +0 -0
  355. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_3.png +0 -0
  356. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_30.png +0 -0
  357. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_31.png +0 -0
  358. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_32.png +0 -0
  359. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_33.png +0 -0
  360. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_34.png +0 -0
  361. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_35.png +0 -0
  362. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_36.png +0 -0
  363. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_37.png +0 -0
  364. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_38.png +0 -0
  365. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_39.png +0 -0
  366. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_4.png +0 -0
  367. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_40.png +0 -0
  368. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_41.png +0 -0
  369. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_42.png +0 -0
  370. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_43.png +0 -0
  371. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_44.png +0 -0
  372. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_45.png +0 -0
  373. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_46.png +0 -0
  374. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_47.png +0 -0
  375. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_48.png +0 -0
  376. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_49.png +0 -0
  377. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_5.png +0 -0
  378. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_50.png +0 -0
  379. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_51.png +0 -0
  380. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_52.png +0 -0
  381. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_53.png +0 -0
  382. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_54.png +0 -0
  383. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_55.png +0 -0
  384. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_56.png +0 -0
  385. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_57.png +0 -0
  386. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_58.png +0 -0
  387. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_59.png +0 -0
  388. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_6.png +0 -0
  389. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_60.png +0 -0
  390. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_61.png +0 -0
  391. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_62.png +0 -0
  392. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_63.png +0 -0
  393. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_7.png +0 -0
  394. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_8.png +0 -0
  395. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_cosine_2Dx1D_128x128x64_9.png +0 -0
  396. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64.dat +0 -1
  397. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64.mag.xy.png +0 -0
  398. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64.mag.xz.png +0 -0
  399. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_0.png +0 -0
  400. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_1.png +0 -0
  401. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_10.png +0 -0
  402. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_11.png +0 -0
  403. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_12.png +0 -0
  404. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_13.png +0 -0
  405. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_14.png +0 -0
  406. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_15.png +0 -0
  407. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_16.png +0 -0
  408. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_17.png +0 -0
  409. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_18.png +0 -0
  410. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_19.png +0 -0
  411. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_2.png +0 -0
  412. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_20.png +0 -0
  413. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_21.png +0 -0
  414. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_22.png +0 -0
  415. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_23.png +0 -0
  416. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_24.png +0 -0
  417. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_25.png +0 -0
  418. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_26.png +0 -0
  419. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_27.png +0 -0
  420. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_28.png +0 -0
  421. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_29.png +0 -0
  422. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_3.png +0 -0
  423. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_30.png +0 -0
  424. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_31.png +0 -0
  425. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_32.png +0 -0
  426. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_33.png +0 -0
  427. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_34.png +0 -0
  428. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_35.png +0 -0
  429. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_36.png +0 -0
  430. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_37.png +0 -0
  431. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_38.png +0 -0
  432. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_39.png +0 -0
  433. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_4.png +0 -0
  434. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_40.png +0 -0
  435. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_41.png +0 -0
  436. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_42.png +0 -0
  437. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_43.png +0 -0
  438. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_44.png +0 -0
  439. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_45.png +0 -0
  440. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_46.png +0 -0
  441. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_47.png +0 -0
  442. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_48.png +0 -0
  443. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_49.png +0 -0
  444. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_5.png +0 -0
  445. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_50.png +0 -0
  446. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_51.png +0 -0
  447. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_52.png +0 -0
  448. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_53.png +0 -0
  449. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_54.png +0 -0
  450. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_55.png +0 -0
  451. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_56.png +0 -0
  452. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_57.png +0 -0
  453. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_58.png +0 -0
  454. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_59.png +0 -0
  455. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_6.png +0 -0
  456. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_60.png +0 -0
  457. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_61.png +0 -0
  458. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_62.png +0 -0
  459. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_63.png +0 -0
  460. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_7.png +0 -0
  461. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_8.png +0 -0
  462. package/src/shade/renderer/texture/noise/stbn/images/stbn_unitvec3_forest_2Dx1D_128x128x64_9.png +0 -0
  463. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64.dat +0 -0
  464. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64.mag.xy.png +0 -0
  465. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64.mag.xz.png +0 -0
  466. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_0.png +0 -0
  467. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_1.png +0 -0
  468. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_10.png +0 -0
  469. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_11.png +0 -0
  470. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_12.png +0 -0
  471. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_13.png +0 -0
  472. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_14.png +0 -0
  473. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_15.png +0 -0
  474. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_16.png +0 -0
  475. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_17.png +0 -0
  476. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_18.png +0 -0
  477. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_19.png +0 -0
  478. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_2.png +0 -0
  479. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_20.png +0 -0
  480. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_21.png +0 -0
  481. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_22.png +0 -0
  482. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_23.png +0 -0
  483. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_24.png +0 -0
  484. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_25.png +0 -0
  485. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_26.png +0 -0
  486. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_27.png +0 -0
  487. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_28.png +0 -0
  488. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_29.png +0 -0
  489. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_3.png +0 -0
  490. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_30.png +0 -0
  491. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_31.png +0 -0
  492. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_32.png +0 -0
  493. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_33.png +0 -0
  494. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_34.png +0 -0
  495. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_35.png +0 -0
  496. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_36.png +0 -0
  497. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_37.png +0 -0
  498. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_38.png +0 -0
  499. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_39.png +0 -0
  500. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_4.png +0 -0
  501. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_40.png +0 -0
  502. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_41.png +0 -0
  503. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_42.png +0 -0
  504. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_43.png +0 -0
  505. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_44.png +0 -0
  506. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_45.png +0 -0
  507. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_46.png +0 -0
  508. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_47.png +0 -0
  509. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_48.png +0 -0
  510. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_49.png +0 -0
  511. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_5.png +0 -0
  512. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_50.png +0 -0
  513. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_51.png +0 -0
  514. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_52.png +0 -0
  515. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_53.png +0 -0
  516. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_54.png +0 -0
  517. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_55.png +0 -0
  518. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_56.png +0 -0
  519. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_57.png +0 -0
  520. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_58.png +0 -0
  521. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_59.png +0 -0
  522. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_6.png +0 -0
  523. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_60.png +0 -0
  524. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_61.png +0 -0
  525. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_62.png +0 -0
  526. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_63.png +0 -0
  527. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_7.png +0 -0
  528. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_8.png +0 -0
  529. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec1_2Dx1D_128x128x64_9.png +0 -0
  530. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64.dat +0 -0
  531. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64.mag.xy.png +0 -0
  532. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64.mag.xz.png +0 -0
  533. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_0.png +0 -0
  534. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_1.png +0 -0
  535. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_10.png +0 -0
  536. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_11.png +0 -0
  537. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_12.png +0 -0
  538. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_13.png +0 -0
  539. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_14.png +0 -0
  540. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_15.png +0 -0
  541. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_16.png +0 -0
  542. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_17.png +0 -0
  543. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_18.png +0 -0
  544. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_19.png +0 -0
  545. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_2.png +0 -0
  546. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_20.png +0 -0
  547. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_21.png +0 -0
  548. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_22.png +0 -0
  549. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_23.png +0 -0
  550. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_24.png +0 -0
  551. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_25.png +0 -0
  552. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_26.png +0 -0
  553. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_27.png +0 -0
  554. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_28.png +0 -0
  555. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_29.png +0 -0
  556. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_3.png +0 -0
  557. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_30.png +0 -0
  558. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_31.png +0 -0
  559. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_32.png +0 -0
  560. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_33.png +0 -0
  561. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_34.png +0 -0
  562. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_35.png +0 -0
  563. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_36.png +0 -0
  564. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_37.png +0 -0
  565. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_38.png +0 -0
  566. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_39.png +0 -0
  567. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_4.png +0 -0
  568. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_40.png +0 -0
  569. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_41.png +0 -0
  570. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_42.png +0 -0
  571. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_43.png +0 -0
  572. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_44.png +0 -0
  573. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_45.png +0 -0
  574. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_46.png +0 -0
  575. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_47.png +0 -0
  576. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_48.png +0 -0
  577. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_49.png +0 -0
  578. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_5.png +0 -0
  579. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_50.png +0 -0
  580. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_51.png +0 -0
  581. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_52.png +0 -0
  582. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_53.png +0 -0
  583. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_54.png +0 -0
  584. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_55.png +0 -0
  585. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_56.png +0 -0
  586. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_57.png +0 -0
  587. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_58.png +0 -0
  588. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_59.png +0 -0
  589. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_6.png +0 -0
  590. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_60.png +0 -0
  591. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_61.png +0 -0
  592. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_62.png +0 -0
  593. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_63.png +0 -0
  594. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_7.png +0 -0
  595. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_8.png +0 -0
  596. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec2_2Dx1D_128x128x64_9.png +0 -0
  597. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64.dat +0 -0
  598. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64.mag.xy.png +0 -0
  599. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64.mag.xz.png +0 -0
  600. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_0.png +0 -0
  601. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_1.png +0 -0
  602. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_10.png +0 -0
  603. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_11.png +0 -0
  604. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_12.png +0 -0
  605. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_13.png +0 -0
  606. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_14.png +0 -0
  607. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_15.png +0 -0
  608. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_16.png +0 -0
  609. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_17.png +0 -0
  610. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_18.png +0 -0
  611. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_19.png +0 -0
  612. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_2.png +0 -0
  613. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_20.png +0 -0
  614. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_21.png +0 -0
  615. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_22.png +0 -0
  616. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_23.png +0 -0
  617. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_24.png +0 -0
  618. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_25.png +0 -0
  619. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_26.png +0 -0
  620. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_27.png +0 -0
  621. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_28.png +0 -0
  622. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_29.png +0 -0
  623. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_3.png +0 -0
  624. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_30.png +0 -0
  625. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_31.png +0 -0
  626. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_32.png +0 -0
  627. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_33.png +0 -0
  628. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_34.png +0 -0
  629. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_35.png +0 -0
  630. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_36.png +0 -0
  631. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_37.png +0 -0
  632. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_38.png +0 -0
  633. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_39.png +0 -0
  634. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_4.png +0 -0
  635. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_40.png +0 -0
  636. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_41.png +0 -0
  637. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_42.png +0 -0
  638. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_43.png +0 -0
  639. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_44.png +0 -0
  640. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_45.png +0 -0
  641. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_46.png +0 -0
  642. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_47.png +0 -0
  643. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_48.png +0 -0
  644. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_49.png +0 -0
  645. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_5.png +0 -0
  646. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_50.png +0 -0
  647. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_51.png +0 -0
  648. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_52.png +0 -0
  649. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_53.png +0 -0
  650. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_54.png +0 -0
  651. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_55.png +0 -0
  652. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_56.png +0 -0
  653. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_57.png +0 -0
  654. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_58.png +0 -0
  655. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_59.png +0 -0
  656. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_6.png +0 -0
  657. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_60.png +0 -0
  658. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_61.png +0 -0
  659. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_62.png +0 -0
  660. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_63.png +0 -0
  661. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_7.png +0 -0
  662. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_8.png +0 -0
  663. package/src/shade/renderer/texture/noise/stbn/images/stbn_vec3_2Dx1D_128x128x64_9.png +0 -0
@@ -1,1322 +1,1342 @@
1
- import { assert } from "../../../../core/assert.js";
2
- import AABB2 from "../../../../core/geom/2d/aabb/AABB2.js";
3
- import { MaxRectanglesPacker } from "../../../../core/geom/packing/max-rect/MaxRectanglesPacker.js";
4
- import { FrameGraph } from "../../../../engine/graphics/render/frame_graph/FrameGraph.js";
5
- import { TextureResourceDescriptor } from "../../../device/graph/TextureResourceDescriptor.js";
6
- import { CameraManager } from "../../camera/CameraManager.js";
7
- import { OrthographicCamera } from "../../camera/OrthographicCamera.js";
8
- import { PerspectiveCamera } from "../../camera/PerspectiveCamera.js";
9
- import { CullPolicy } from "../../rasterize/bucket/construct_primitive_state.js";
10
- import { graph_mesh_sphere_cull } from "../../rasterize/compute/graph_mesh_sphere_cull.js";
11
- import { execute_rasterization_jobs } from "../../rasterize/execute_rasterization_jobs.js";
12
- import {
13
- shadow_rasterization_alpha_tested_pass_descriptor
14
- } from "../../rasterize/native/depth/shadow_rasterization_alpha_tested_pass_descriptor.js";
15
- import {
16
- shadow_rasterization_pass_descriptor
17
- } from "../../rasterize/native/depth/shadow_rasterization_pass_descriptor.js";
18
- import { RasterizationJob, RasterizationPipelineType } from "../../rasterize/RasterizationJob.js";
19
- import { graph_rasterize_scene_fast_raw } from "../../rasterize/fast/graph_rasterize_scene_fast.js";
20
- import { graph_create_texture, graph_import_texture } from "../../shader/graph/graph_import_texture.js";
21
- import { GPUTextureContext } from "../../texture/GPUTextureContext.js";
22
- import { RenderTarget } from "../../texture/RenderTarget.js";
23
- import { View } from "../../view/View.js";
24
- import { ViewManager } from "../../view/ViewManager.js";
25
- import { SHADOWMAP_CSM_CASCADE_COUNT } from "./csm/SHADOWMAP_CSM_CASCADE_COUNT.js";
26
- import { DirectionalLightShadowmap } from "./DirectionalLightShadowmap.js";
27
- import { POINT_SHADOW_CUBE_COLS, POINT_SHADOW_CUBE_ROWS } from "./POINT_SHADOW_CUBE_LAYOUT.js";
28
- import {
29
- POINT_SHADOW_TETRA_COLS,
30
- POINT_SHADOW_TETRA_FACE_COUNT,
31
- POINT_SHADOW_TETRA_MAPPING_RESOLUTION_SCALE,
32
- POINT_SHADOW_TETRA_ROWS,
33
- } from "./POINT_SHADOW_TETRAHEDRON_LAYOUT.js";
34
- import { PointLightShadowmap } from "./PointLightShadowmap.js";
35
- import {
36
- shader_shadowmap_remap_cube_to_octahedral
37
- } from "./shader/shader_shadowmap_remap_cube_to_octahedral.js";
38
- import {
39
- POINT_SHADOW_TETRA_REMAP_SETTINGS,
40
- shader_shadowmap_remap_tetrahedron_to_octahedral
41
- } from "./shader/shader_shadowmap_remap_tetrahedron_to_octahedral.js";
42
- import { SHADOWMAP_ATLAS_BORDER } from "./SHADOWMAP_ATLAS_BORDER.js";
43
- import { TetrahedronPointLightShadowmap } from "./TetrahedronPointLightShadowmap.js";
44
- import { compute_light_projected_area_px, compute_shadowmap_update_score } from "./shadowmap_update_score.js";
45
- import { ShadowmapResolutionPolicy } from "./ShadowmapResolutionPolicy.js";
46
- import { SpotLightShadowmap } from "./SpotLightShadowmap.js";
47
-
48
- const DEFAULT_SHADOWMAP_ATLAS_RESOLUTION = 4096 * 2;
49
-
50
- /**
51
- * Resolution by cascade.
52
- * If the number of cascades is greater than the array length, the last resolution is used.
53
- * @type {number[]}
54
- */
55
- const DEFAULT_SHADOWMAP_DIRECTIONAL_RESOLUTION = [
56
- 1740, // first cascade
57
- 1440,
58
- ];
59
-
60
- /**
61
- * Placeholder resolution for local-light shadowmaps (spot/point).
62
- * A proper implementation will project the light's bounding volume into screen space to derive this at runtime.
63
- * @type {number}
64
- */
65
- const DEFAULT_SHADOWMAP_LOCAL_RESOLUTION = 128;
66
-
67
- /**
68
- * Per-frame shadow refresh budget, measured in **views** (one cube face counts as one view,
69
- * one CSM cascade counts as one view, etc.). A point light costs 6 views, a spot light 1, a
70
- * directional SHADOWMAP_CSM_CASCADE_COUNT. A bigger light consumes more of the budget, which
71
- * keeps the cost of the refresh step roughly proportional to how much work we're asking the
72
- * GPU to do a single point light running the meshlet rasterization pipeline 6 times is
73
- * equivalent work to 6 spot lights.
74
- *
75
- * Directional lights are always refreshed unconditionally (they drive the whole scene) and
76
- * their view cost is charged against the budget, so they effectively reduce the pool available
77
- * for local lights that frame.
78
- *
79
- * Unused budget is carried over between frames when demand exceeds supply — see
80
- * {@link GPUSceneShadowmapContext.#budget_carry_over}. This lets a light that's too expensive
81
- * to fit in a single frame (e.g. a point needing 6 views when only 2 are free) eventually get
82
- * its turn without permanently starving other lights.
83
- * @type {number}
84
- */
85
- const SHADOWMAP_UPDATE_BUDGET = 8;
86
-
87
- /**
88
- * Shadows for a single scene
89
- */
90
- export class GPUSceneShadowmapContext {
91
- atlas = new MaxRectanglesPacker(DEFAULT_SHADOWMAP_ATLAS_RESOLUTION, DEFAULT_SHADOWMAP_ATLAS_RESOLUTION);
92
-
93
- /**
94
- * Depth texture atlas used for shadowmaps.
95
- * @type {GPUTextureContext}
96
- */
97
- texture;
98
-
99
-
100
- /**
101
- * @type {GraphicsContext}
102
- */
103
- #graphics;
104
-
105
- /**
106
- * Dedicated ViewManager for the cube/cascade/perspective shadow views this context
107
- * creates and rasterizes into the atlas. Decoupled from the renderer-level ViewManager
108
- * so the ownership chain stays self-contained the shadow context creates the views,
109
- * caches their GPU contexts, and disposes them with itself. The shared SceneManager
110
- * lookup is short-circuited with a tiny object that always returns our owning
111
- * GPUSceneContext (every shadow view here is for the same scene by construction).
112
- * @type {ViewManager}
113
- */
114
- shadow_view_manager;
115
-
116
- /**
117
- * Internal CameraManager backing {@link shadow_view_manager}. Shadow cameras are
118
- * created locally in {@link process_lights} and only referenced by this context, so
119
- * they don't need to share the renderer-level CameraManager.
120
- * @type {CameraManager}
121
- */
122
- #cameras;
123
-
124
- /**
125
- * @type {RenderTarget}
126
- */
127
- #render_target;
128
-
129
-
130
- /**
131
- *
132
- * @type {Shadowmap[]}
133
- */
134
- #maps = [];
135
-
136
- /**
137
- * Master kill switch for shadows on this scene. Defaults true; the renderer sets it from
138
- * its own `feature_shadows_enabled` flag before each frame's light update. When false,
139
- * {@link process_lights} evicts all maps so the next light-record build clears
140
- * CASTS_SHADOW_BIT on every light. {@link select_for_draw} and {@link draw} are still
141
- * gated externally by the renderer; this flag exists to keep the GPU light records in
142
- * sync with that gating without needing to thread the renderer's flag down further.
143
- * @type {boolean}
144
- */
145
- enabled = true;
146
-
147
- /**
148
- * Selects the tetrahedral point-light shadow generator (4 face frusta) over the default
149
- * cube generator (6 faces). The renderer pushes this from its
150
- * `feature_tetrahedron_point_shadows` flag before each light update, alongside {@link enabled}.
151
- *
152
- * Only affects how a point light's shadow is GENERATED — both paths remap into the same
153
- * octahedral atlas encoding, so the sampler is identical and this can be toggled at runtime.
154
- * A flip is picked up in {@link process_lights}: existing point maps whose generator no longer
155
- * matches this flag are evicted and rebuilt with the correct face count on the same pass.
156
- * @type {boolean}
157
- */
158
- use_tetrahedron_point_shadows = false;
159
-
160
- /**
161
- * Frame index of the most recent {@link select_for_draw} call. Passed to {@link draw} via
162
- * this field so we don't have to thread it through the call again — draw() stamps
163
- * `map.last_updated_frame_index` with this value after each successful rasterization.
164
- * @type {number}
165
- */
166
- #current_frame_index = -1;
167
-
168
- /**
169
- * View-budget surplus carried forward from prior frames. Accumulates whenever we had
170
- * non-directional candidates that couldn't fit in the per-frame budget (i.e. a point light
171
- * wanted 6 views but only 2 were free after directionals + earlier locals consumed their
172
- * share). Resets to 0 on any frame where the full demand was satisfied, so the carryover
173
- * doesn't grow unboundedly when the scene is quiet.
174
- * @type {number}
175
- */
176
- #budget_carry_over = 0;
177
-
178
- /**
179
- * Adjusts per-map atlas footprints each frame based on projected on-screen area. Lazily
180
- * tied to {@link atlas} in the constructor.
181
- * @type {ShadowmapResolutionPolicy}
182
- */
183
- #resolution_policy;
184
-
185
- /**
186
- * Number of shadowmaps actually rasterized by the most recent {@link draw} call. Reset at
187
- * the start of draw(), incremented once per map that went through the raster path. Exposed
188
- * for debugging scheduling behaviour a persistently oscillating value (e.g. 36/20/15
189
- * repeating) indicates some maps are being invalidated every frame and forcing a redraw,
190
- * which usually means the resolution policy or selector is churning.
191
- * @type {number}
192
- */
193
- #debug_render_count = 0;
194
-
195
- /**
196
- * @returns {number} number of shadowmaps rasterized in the most recent draw pass
197
- */
198
- get debug_render_count() {
199
- return this.#debug_render_count;
200
- }
201
-
202
- /**
203
- * Most recently measured atlas occupancy (placed area / total area, including pending
204
- * reservations), in [0, 1]. Forwards from the resolution policy. Useful for the debug
205
- * overlay alongside {@link debug_drop_size_scale} together they show whether the global
206
- * shrink/grow feedback has converged or is still chasing the target band.
207
- * @returns {number}
208
- */
209
- get debug_atlas_occupancy() {
210
- return this.#resolution_policy.last_occupancy;
211
- }
212
-
213
- /**
214
- * Current value of the global drop-size scale applied by the resolution policy. 1 means no
215
- * shrink; values below 1 mean atlas pressure has trimmed every light's target size
216
- * proportionally. Forwarded from the resolution policy for debug overlays.
217
- * @returns {number}
218
- */
219
- get debug_drop_size_scale() {
220
- return this.#resolution_policy.drop_size_scale;
221
- }
222
-
223
-
224
- /**
225
- * Per-type counts of shadow records written to the GPU database in the previous
226
- * {@link draw} pass. Used to identify trailing slots that must be zeroed when the
227
- * map count for a given type shrinks (e.g. a shadow-casting light is removed),
228
- * mirroring the bookkeeping in {@link GPULightCollection.#build}.
229
- */
230
- #prev_point_count = 0;
231
- #prev_spot_count = 0;
232
- #prev_directional_count = 0;
233
-
234
- /**
235
- * The Scene this context manages shadows for. Captured at construction so
236
- * {@link process_lights} doesn't need it threaded back through every frame.
237
- * @type {Scene}
238
- */
239
- #scene;
240
-
241
- /**
242
- * Owning light collection — gives us access to the GPU database where shadow
243
- * records live and to the source LightCollection. Set in the constructor.
244
- * @type {GPULightCollection}
245
- */
246
- #owner;
247
-
248
- /**
249
- *
250
- * @param {GraphicsContext} graphics
251
- * @param {GPUSceneContext} scene_ctx the GPU-side context for the Scene we manage
252
- * shadows for. Used as the (single) destination of {@link shadow_view_manager}'s
253
- * scene lookup every shadow view is for this scene, so the lookup short-circuits.
254
- * @param {GPULightCollection} owner the parent light collection gives us back-access
255
- * to the GPU database we publish records into during {@link draw}.
256
- */
257
- constructor(
258
- graphics,
259
- scene_ctx,
260
- owner,
261
- ) {
262
- assert.defined(graphics, 'graphics');
263
- assert.defined(scene_ctx, 'scene_ctx');
264
- assert.equal(scene_ctx.isGPUSceneContext, true, 'scene_ctx.isGPUSceneContext !== true');
265
- assert.defined(owner, 'owner');
266
-
267
-
268
- const initial_atlas_resolution = Math.min(
269
- graphics.device.limits.maxTextureDimension2D,
270
- DEFAULT_SHADOWMAP_ATLAS_RESOLUTION,
271
- );
272
-
273
- {
274
- const t = this.texture = new GPUTextureContext(graphics.device);
275
-
276
-
277
- const descriptor = t.descriptor;
278
-
279
- descriptor.format = "depth32float";
280
- descriptor.dimension = "2d";
281
- descriptor.label = "Shadowmap atlas";
282
- descriptor.usage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC;
283
-
284
- t.resize(
285
- initial_atlas_resolution,
286
- initial_atlas_resolution
287
- );
288
-
289
- // ensure atlas size matches
290
- this.atlas.resize(
291
- initial_atlas_resolution,
292
- initial_atlas_resolution
293
- );
294
- }
295
-
296
- this.#graphics = graphics;
297
- this.#scene = scene_ctx.scene;
298
- this.#owner = owner;
299
-
300
- // Local CameraManager + ViewManager dedicated to shadow rasterization. We don't
301
- // share with the renderer-level managers because the cameras and views here are
302
- // owned by this context and only referenced from inside it. The "scenes" lookup
303
- // is a stub that always returns the owning scene context — every shadow view is
304
- // for `this.#scene` by construction.
305
- this.#cameras = new CameraManager(graphics.device);
306
- this.shadow_view_manager = new ViewManager(graphics, this.#cameras, {
307
- obtain: () => scene_ctx,
308
- });
309
-
310
- this.#render_target = RenderTarget.from({
311
- depth: this.texture,
312
- });
313
-
314
- this.#resolution_policy = new ShadowmapResolutionPolicy(this.atlas);
315
- }
316
-
317
- /**
318
- *
319
- * @param {Light} light
320
- * @returns {Shadowmap|undefined}
321
- */
322
- get_map(light) {
323
- return this.#maps.find(map => map.light === light);
324
- }
325
-
326
- /**
327
- * Decide which shadowmaps should be rasterized this frame.
328
- *
329
- * Runs at the top of every frame, before {@link draw}. Responsibilities:
330
- *
331
- * 1. **Reset per-frame signals.** `should_draw` is cleared on every map so a stale "yes"
332
- * from a prior frame can never cause a spurious extra raster.
333
- * 2. **Measure on-screen presence.** `projected_area_px` is computed once per map and stored
334
- * on the map itself — consumed by the resolution policy, the scorer, and the selector,
335
- * and exposed for debug tooling.
336
- * 3. **Reserve pending atlas footprints.** {@link ShadowmapResolutionPolicy.adjust} may
337
- * attach a `pending_layout` to maps whose projected area has shifted enough to warrant a
338
- * resize. The committed `layout` is left untouched so sampling remains valid; the
339
- * pending is consumed later by {@link draw} when the scheduler naturally picks this map
340
- * up (no forced redraw).
341
- * 4. **Score + select.** For each map:
342
- * - **Directional** → always selected (infinite score); view cost still charged.
343
- * - **Local, off-screen** (`projected_area_px <= 0`) → skipped entirely.
344
- * - **Local, in-view, invalid** → MUST draw. `is_invalid` means the committed layout
345
- * has never held valid depth (freshly created map); sampling it would display
346
- * garbage, so we bypass the budget. Resizes no longer invalidate under the dual-
347
- * layout model this flag fires only on first-draw.
348
- * - **Local, in-view, valid** → compete for the remaining budget by score. Ordering
349
- * is descending score so the most important (largest-on-screen, stalest) maps win.
350
- * 5. **Commit selection.** On each winner we set `should_draw = true` and call
351
- * `map.update()` so its projection matrices reflect the current frame. The
352
- * `last_updated_frame_index` stamp is deferred to {@link draw} it records when the
353
- * atlas contents become current, not when the projection is fitted.
354
- *
355
- * Shadow records are NOT written here; they're written at the end of {@link draw} so the
356
- * shader reads slots that reflect any just-committed pending swaps within the same frame.
357
- *
358
- * See {@link compute_shadowmap_update_score} for the scoring formula and rationale.
359
- *
360
- * @param {Camera} main_camera used both for rendering the final frame and for projected-area
361
- * computation and directional CSM fit; must be callable for `.update()`
362
- * @param {GPUSceneContext} scene
363
- * @param {number} frame_index monotonically increasing frame counter, used for staleness
364
- * @param {ArrayLike<number>} view_resolution [width, height] of the main view in pixels; used
365
- * to convert projected sphere extents into a pixel-area score
366
- */
367
- select_for_draw(main_camera, scene, frame_index, view_resolution) {
368
- assert.defined(main_camera, 'main_camera');
369
- assert.isObject(main_camera, 'main_camera');
370
- assert.equal(scene.isGPUSceneContext, true, 'scene.isGPUSceneContext !== true');
371
- assert.isNumber(frame_index, 'frame_index');
372
- assert.defined(view_resolution, 'view_resolution');
373
-
374
- // make sure the camera is updated — both our scoring (projected area) and any downstream
375
- // map.update() reads depend on view/projection matrices being current
376
- main_camera.update();
377
-
378
- this.#current_frame_index = frame_index;
379
-
380
- const shadowmaps = this.#maps;
381
- const n = shadowmaps.length;
382
-
383
- // Step 1: clear per-frame flags so a stale signal from a prior frame can't fire now.
384
- for (let i = 0; i < n; i++) {
385
- shadowmaps[i].should_draw = false;
386
- }
387
-
388
- // Step 2: recompute projected areas. Writing onto the map so the policy + scorer can
389
- // just read `map.projected_area_px` without threading a parallel array through.
390
- for (let i = 0; i < n; i++) {
391
- const m = shadowmaps[i];
392
- m.projected_area_px = compute_light_projected_area_px(m.light, main_camera, view_resolution);
393
- }
394
-
395
- // Step 3: policy reserves pending resize footprints alongside committed layouts. It
396
- // never mutates layout and never invalidates sampling continues against the old
397
- // region until draw() commits the pending during a normal raster pass. Directionals and
398
- // off-screen locals are skipped (off-screen locals also get any stale pending released
399
- // so an idle reservation can't tie up atlas space).
400
- this.#resolution_policy.adjust(shadowmaps, frame_index);
401
-
402
- // Step 4: score every map. Directionals return Infinity; off-screen locals return 0.
403
- const scored = new Array(n);
404
- for (let i = 0; i < n; i++) {
405
- scored[i] = {
406
- map: shadowmaps[i],
407
- score: compute_shadowmap_update_score(shadowmaps[i], frame_index),
408
- };
409
- }
410
- scored.sort((a, b) => b.score - a.score);
411
-
412
- // Step 5: greedy-fill the view-budget in score order.
413
- const budget_total = SHADOWMAP_UPDATE_BUDGET + this.#budget_carry_over;
414
- let views_spent = 0;
415
- let had_unfit_candidates = false;
416
-
417
- for (let i = 0; i < n; i++) {
418
- const map = scored[i].map;
419
- const cost = map.views.length;
420
- const light = map.light;
421
-
422
- if (light.isDirectionalLight) {
423
- // Phase A: directionals always refresh. Infinite score puts them at the front of
424
- // the sorted list; their view cost still counts against the pool, so local lights
425
- // see a reduced budget this frame.
426
- map.should_draw = true;
427
- map.update(main_camera, scene);
428
- views_spent += cost;
429
- continue;
430
- }
431
-
432
- // Phase B: local lights (spot/point).
433
- if (map.projected_area_px <= 0) {
434
- // Off-screen. Not demand, not carryover fuel. Leave is_invalid / should_draw
435
- // alone so the map springs back to life the moment it returns to view.
436
- continue;
437
- }
438
-
439
- if (map.is_invalid) {
440
- // Phase B-override: invalid + in-view is a MUST-draw. Sampling garbage depth on
441
- // a visible light would be user-visible corruption, so we bypass the budget.
442
- // The view cost still counts so normal-priority locals see the deficit.
443
- map.should_draw = true;
444
- map.update(main_camera, scene);
445
- views_spent += cost;
446
- continue;
447
- }
448
-
449
- // Phase B normal: score-ordered greedy fill.
450
- if (views_spent + cost <= budget_total) {
451
- map.should_draw = true;
452
- map.update(main_camera, scene);
453
- views_spent += cost;
454
- } else {
455
- // wanted to refresh but the pool couldn't cover its view cost; flag so we carry
456
- // unused budget forward for next frame's attempt
457
- had_unfit_candidates = true;
458
- }
459
- // Skipped maps keep their prior metadata + atlas contents a never-refreshed map
460
- // samples the initial depth-cleared rect, which reads as "fully lit" under the
461
- // reverse-Z greater compare. Acceptable behavior until the map eventually wins a slot.
462
- }
463
-
464
- // Carry over the unused pool only when there was demand we couldn't meet. When the whole
465
- // scene's demand was satisfied, reset to 0 so idle frames don't inflate the pool. If
466
- // must-draw bypasses over-spent the pool (views_spent > budget_total), clamp at zero
467
- // we can't owe future frames for past over-spend, that way lies runaway starvation.
468
- if (had_unfit_candidates) {
469
- this.#budget_carry_over = Math.max(0, budget_total - views_spent);
470
- } else {
471
- this.#budget_carry_over = 0;
472
- }
473
- }
474
-
475
- /**
476
- * Publish per-shadowmap records into the scene's GPU database. Each map writes its record
477
- * into the type-specific shadow_* table at the slot identified by `light._gpu_shadowmap_id`
478
- * (which is itself the per-type index, set by {@link process_lights}). Trailing slots from
479
- * previous frames are zeroed when the map count for a type shrinks, so stale entries don't
480
- * linger.
481
- *
482
- * Only maps with `metadata_changed === true` are re-uploaded; the flag is cleared after
483
- * the slot is queued so unchanged maps don't pay for an upload they don't need. Trim writes
484
- * always run regardless of the flag — they zero slots that no longer correspond to any map.
485
- *
486
- * The type-bucket order here MUST match the per-type id assignment in {@link process_lights}
487
- * (Point → Spot → Directional) so the shadow_id stamped onto each light record points at the
488
- * slot we write here.
489
- *
490
- * Caller must invoke `database.update(cmd)` afterwards to upload the writes — that is done
491
- * by {@link draw} once for both the new records and any zeroed trailing slots.
492
- *
493
- * @param {GPUDatabase} database
494
- */
495
- #write_shadow_records(database) {
496
- const t_point = database.get('shadow_point');
497
- const t_spot = database.get('shadow_spot');
498
- const t_directional = database.get('shadow_directional');
499
-
500
- const maps = this.#maps;
501
- let pc = 0;
502
- let sc = 0;
503
- let dc = 0;
504
-
505
- for (let i = 0; i < maps.length; i++) {
506
- const map = maps[i];
507
- const light = map.light;
508
-
509
- let table;
510
- let slot;
511
- if (light.isPointLight) {
512
- table = t_point;
513
- slot = pc++;
514
- } else if (light.isSpotLight) {
515
- table = t_spot;
516
- slot = sc++;
517
- } else if (light.isDirectionalLight) {
518
- table = t_directional;
519
- slot = dc++;
520
- } else {
521
- continue;
522
- }
523
-
524
- if (map.metadata_changed) {
525
- table.set(slot, map.make_record());
526
- map.metadata_changed = false;
527
- }
528
- }
529
-
530
- // trim trailing slots from previous frames so disused entries don't leak
531
- for (let i = pc; i < this.#prev_point_count; i++) {
532
- t_point.remove(i);
533
- }
534
- for (let i = sc; i < this.#prev_spot_count; i++) {
535
- t_spot.remove(i);
536
- }
537
- for (let i = dc; i < this.#prev_directional_count; i++) {
538
- t_directional.remove(i);
539
- }
540
-
541
- this.#prev_point_count = pc;
542
- this.#prev_spot_count = sc;
543
- this.#prev_directional_count = dc;
544
- }
545
-
546
- /**
547
- * Rasterize every shadowmap flagged by {@link select_for_draw} for a refresh this frame,
548
- * committing any pending resize reservations in the process, then publish per-shadow
549
- * metadata into the scene's GPU database.
550
- *
551
- * For each map with `should_draw` set:
552
- *
553
- * 1. If the map has a {@link Shadowmap.pending_layout} reservation, swap it into place
554
- * BEFORE rasterization so the draw targets the new region. The old committed rects
555
- * are remembered for step 3.
556
- * 2. Dispatch to the type-specific raster path:
557
- * - **Directional / spot**: direct raster into the atlas's sub-rects (one job per
558
- * cascade for directionals, one job per spot).
559
- * - **Point**: 6 cube-face depth passes into a shared intermediate texture, then
560
- * cube→octahedral remap into the atlas. Point lights are serialised so they can
561
- * reuse the intermediate texture.
562
- * 3. If a commit happened in step 1, release the old committed rects from the atlas now
563
- * that the new region holds valid depth.
564
- * 4. Clear per-frame flags (`should_draw`, `is_invalid`) and stamp
565
- * `last_updated_frame_index` with the current frame.
566
- *
567
- * After the loop, each map's metadata record is written into the corresponding
568
- * `shadow_point` / `shadow_spot` / `shadow_directional` table on the owning light
569
- * collection's database, then the database is uploaded this is where the committed
570
- * swaps become visible to the lighting shader. `#debug_render_count` is reset at entry
571
- * and incremented per map to expose scheduling behaviour to the debug overlay.
572
- *
573
- * No external job list: every path batches its own work internally via
574
- * {@link execute_rasterization_jobs}. Callers just pass a command context.
575
- *
576
- * @param {ShadeGPUCommandContext} command_ctx
577
- */
578
- draw(command_ctx) {
579
- assert.defined(command_ctx, 'command_ctx');
580
-
581
-
582
- this.#debug_render_count = 0;
583
-
584
- const frame_index = this.#current_frame_index;
585
- const maps = this.#maps;
586
-
587
- for (let i = 0; i < maps.length; i++) {
588
- const map = maps[i];
589
- if (!map.should_draw) {
590
- continue;
591
- }
592
-
593
- // Step 1: commit any pending-layout reservation before rasterizing. The draw helpers
594
- // read `map.layout` directly, so swapping here makes the raster target the new
595
- // region. We stash the old rect(s) locally for step 3's atlas release — once the
596
- // raster commands are enqueued, the old region's contents will no longer be sampled
597
- // (the next shadow-record write publishes the new layout), so returning that atlas
598
- // space to the packer is safe.
599
- let retired_rects = null;
600
- if (map.pending_layout !== null) {
601
- retired_rects = map.layout;
602
- map.layout = map.pending_layout;
603
- map.pending_layout = null;
604
- map.metadata_changed = true;
605
- }
606
-
607
- const light = map.light;
608
-
609
- if (light.isDirectionalLight || light.isSpotLight) {
610
- this.#draw_direct(map, command_ctx);
611
- } else if (light.isPointLight) {
612
- if (map.isTetrahedron) {
613
- this.#draw_point_tetrahedron(map, command_ctx);
614
- } else {
615
- this.#draw_point(map, command_ctx);
616
- }
617
- } else {
618
- throw new Error(`Unsupported light type ${light.constructor.name}`);
619
- }
620
-
621
- // Step 3: release the retired rects from the atlas. Must happen AFTER the raster
622
- // helpers finish reading map.layout (they may close over retired_rects as scratch
623
- // memory or view references in some paths; safest order is raster-first).
624
- if (retired_rects !== null) {
625
- for (let j = 0; j < retired_rects.length; j++) {
626
- this.atlas.remove(retired_rects[j]);
627
- }
628
- }
629
-
630
- // Step 4: atlas contents are now current. Clear per-frame flags and stamp the frame
631
- // index so the next frame's scorer computes staleness correctly.
632
- map.should_draw = false;
633
- map.is_invalid = false;
634
- map.last_updated_frame_index = frame_index;
635
- this.#debug_render_count++;
636
- }
637
-
638
-
639
- // Publish metadata AFTER all commits so the lighting shader sees atlas coordinates that
640
- // match the regions we just rasterised into. Never-drawn maps carry stale-but-valid
641
- // projection + layout; off-screen maps and skipped candidates publish their prior state.
642
- const database = this.#owner.database;
643
- this.#write_shadow_records(database);
644
- database.update(command_ctx);
645
-
646
-
647
- // finalize rendered views
648
- for (const map of this.#maps) {
649
- if (map.last_updated_frame_index !== frame_index) {
650
- // skip
651
- continue;
652
- }
653
-
654
- for (const view of map.views) {
655
- const view_ctx = this.shadow_view_manager.obtain(view);
656
- view_ctx.finish_frame(command_ctx);
657
- }
658
- }
659
- }
660
-
661
- /**
662
- * Rasterize a directional or spot shadowmap directly into its atlas rects. One job per
663
- * `map.views[i]` / `map.layout[i]` pair — directionals have N cascades, spots have one.
664
- *
665
- * @param {Shadowmap} map
666
- * @param {ShadeGPUCommandContext} command_ctx
667
- */
668
- #draw_direct(map, command_ctx) {
669
- // TODO pre-cull mesh set by sphere-casting the light volume against the main View Frustum
670
- // see ref: https://www.reddit.com/r/Unity3D/comments/1foe8qi/added_a_way_to_perform_frustum_culling_and_take
671
-
672
- const graph = new FrameGraph("shadows");
673
-
674
- const shadow_views = map.views;
675
- const jobs = new Array(shadow_views.length);
676
-
677
- for (let i = 0; i < shadow_views.length; i++) {
678
- const view = shadow_views[i];
679
- const view_ctx = this.shadow_view_manager.obtain(view);
680
-
681
- view_ctx.update(this.#graphics);
682
-
683
- const atlas_aabb = map.layout[i];
684
- view_ctx.setViewportSize(atlas_aabb.width, atlas_aabb.height);
685
-
686
- const job = new RasterizationJob();
687
- job.opaque_pass = shadow_rasterization_pass_descriptor;
688
- job.alpha_tested_pass = shadow_rasterization_alpha_tested_pass_descriptor;
689
- job.cull_policy = CullPolicy.Reversed;
690
- job.render_target = this.#render_target;
691
- job.view_ctx = view_ctx;
692
- job.viewport = [atlas_aabb.x0, atlas_aabb.y0, atlas_aabb.width, atlas_aabb.height];
693
-
694
-
695
- jobs[i] = job;
696
-
697
- // TODO reproject current scene view into shadow map for better occlusion culling,
698
- // see Assassin's Creed Unity talk on GPU-driven rendering,
699
- // see "RE ENGINE Meshlet Rendering Pipeline" https://youtu.be/nWgPtCDXlPc?t=2055
700
-
701
- // TODO need to linearize depth(?)
702
- // can linearize together with reprojection (TAA)
703
- }
704
-
705
- execute_rasterization_jobs({
706
- jobs,
707
- limits: this.#graphics.collection_limits,
708
- command_ctx,
709
- graph,
710
- });
711
-
712
- command_ctx.encodeGraph(graph);
713
- }
714
-
715
- /**
716
- * Rasterize a point light's 6 cube faces into the shared intermediate texture, then run the
717
- * cube→octahedral remap shader to write the encoded depth into this light's atlas rect.
718
- *
719
- * Sequencing matters: each point light fully completes (6 cube rasters + remap) before the
720
- * next one starts, so the intermediate texture can be reused across point lights without
721
- * cross-contamination.
722
- *
723
- * @param {PointLightShadowmap} map
724
- * @param {ShadeGPUCommandContext} command_ctx
725
- */
726
- #draw_point(
727
- map,
728
- command_ctx,
729
- ) {
730
-
731
- const graph = new FrameGraph("shadows");
732
-
733
- const gr_atlas_texture = graph_import_texture(graph, this.texture);
734
-
735
- const atlas_rect = map.layout[0];
736
-
737
- const octahedral_resolution = atlas_rect.width;
738
-
739
- /**
740
- * Controls the relationship between octahedral resolution and the cube map used during rendering
741
- * If this is larger - we get better quality at the cost of rendering more pixels.
742
- *
743
- * Solving for solid angle coverage, using the formula `N=POW(1 - SQRT(E/3), 3/2)` we get:
744
- *
745
- * 0% error -> 1
746
- * 1% error -> 0.915
747
- * 5% error -> 0.81299
748
- * 10% error -> 0.739
749
- * 20% error -> 0.639
750
- * 30% error -> 0.565
751
- * 40% error -> 0.506
752
- * 50% error -> 0.455
753
- *
754
- * @type {number}
755
- */
756
- const CUBE_MAPPING_RESOLUTION_SCALE = 1;
757
-
758
- const cube_face_resolution = Math.ceil(octahedral_resolution * CUBE_MAPPING_RESOLUTION_SCALE);
759
-
760
- // reserve texture
761
- let cube_packed_texture = graph_create_texture(graph, TextureResourceDescriptor.from({
762
- resolution: [
763
- cube_face_resolution * POINT_SHADOW_CUBE_COLS,
764
- cube_face_resolution * POINT_SHADOW_CUBE_ROWS,
765
- ],
766
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
767
- format: 'depth32float',
768
- enableMips: false,
769
- }));
770
-
771
- /**
772
- *
773
- * @type {PointLight}
774
- */
775
- const light = map.light;
776
-
777
- const scene_ctx = this.shadow_view_manager.obtain(map.views[0]).scene;
778
-
779
- /**
780
- * Sphere-culled meshes from the scene.
781
- * We could do per-face culling, but most point lights are local, and there's already meshlet culling happening later in the pipeline.
782
- * Good enough.
783
- * @type {number}
784
- */
785
- const input_meshes = graph_mesh_sphere_cull({
786
- graph,
787
- sphere: [...light.position, light.distance],
788
- scene: scene_ctx,
789
- limits: this.#graphics.collection_limits,
790
- });
791
-
792
- for (let face = 0; face < 6; face++) {
793
- const view = map.views[face];
794
- const view_ctx = this.shadow_view_manager.obtain(view);
795
-
796
- view_ctx.update(this.#graphics);
797
- view_ctx.setViewportSize(cube_face_resolution, cube_face_resolution);
798
-
799
- const col = face % POINT_SHADOW_CUBE_COLS;
800
- const row = Math.floor(face / POINT_SHADOW_CUBE_COLS);
801
-
802
- const job = new RasterizationJob();
803
-
804
- job.pipeline_type = RasterizationPipelineType.Fast;
805
- job.opaque_pass = shadow_rasterization_pass_descriptor;
806
- job.alpha_tested_pass = shadow_rasterization_alpha_tested_pass_descriptor;
807
- job.cull_policy = CullPolicy.Reversed;
808
- job.render_target = null;
809
- job.view_ctx = view_ctx;
810
- job.viewport = [
811
- col * cube_face_resolution,
812
- row * cube_face_resolution,
813
- cube_face_resolution,
814
- cube_face_resolution,
815
- ];
816
-
817
- cube_packed_texture = graph_rasterize_scene_fast_raw({
818
- graph,
819
- meshes: input_meshes,
820
- limits: this.#graphics.collection_limits,
821
- target_depth: cube_packed_texture,
822
- job,
823
- }).target_depth;
824
- }
825
-
826
- /**
827
- *
828
- * @param data
829
- * @param {FramePassResources} resources
830
- * @param {GraphicsExecutionContext} context
831
- */
832
- function remap_to_atlas(data, resources, context) {
833
- const atlas = resources.get(data.atlas);
834
- const cube_texture = resources.get(data.cube_texture);
835
-
836
- const settings = {
837
- atlas_offset: [
838
- atlas_rect.x0 + SHADOWMAP_ATLAS_BORDER,
839
- atlas_rect.y0 + SHADOWMAP_ATLAS_BORDER,
840
- atlas_rect.width - 2 * SHADOWMAP_ATLAS_BORDER,
841
- atlas_rect.height - 2 * SHADOWMAP_ATLAS_BORDER,
842
- ],
843
- light_params: [
844
- map.light.distance,
845
- map.cube_near,
846
- 0,
847
- 0,
848
- ]
849
- };
850
-
851
- shader_shadowmap_remap_cube_to_octahedral.draw({
852
- encoder: context.encoder,
853
- bindings: {
854
- settings: settings,
855
- cube_faces: cube_texture.obtainView(),
856
- },
857
- depthStencilAttachment: {
858
- view: atlas.obtainView(),
859
- depthLoadOp: "load",
860
- depthStoreOp: "store",
861
- },
862
- viewport: [
863
- atlas_rect.x0,
864
- atlas_rect.y0,
865
- atlas_rect.width,
866
- atlas_rect.height,
867
- ]
868
- })
869
- }
870
-
871
- const remap_data = {};
872
-
873
- const remap_pass = graph.add("remap to atals", remap_data, remap_to_atlas);
874
-
875
- remap_data.atlas = remap_pass.write(gr_atlas_texture);
876
- remap_data.cube_texture = remap_pass.read(cube_packed_texture);
877
-
878
-
879
- command_ctx.encodeGraph(graph);
880
- }
881
-
882
- /**
883
- * Rasterize a point light's 4 tetrahedron faces into a shared intermediate texture, then run
884
- * the tetrahedron→octahedral remap to write the encoded depth into this light's atlas rect.
885
- * The tetrahedral parallel to {@link #draw_point} — same shape, 4 faces in a 2x2 grid instead
886
- * of 6 in a 3x2 grid, and the remap is handed each face's view-projection matrix so it can
887
- * select faces and project directions in exact agreement with how the depth was rasterized.
888
- *
889
- * @param {TetrahedronPointLightShadowmap} map
890
- * @param {ShadeGPUCommandContext} command_ctx
891
- */
892
- #draw_point_tetrahedron(
893
- map,
894
- command_ctx,
895
- ) {
896
-
897
- const graph = new FrameGraph("shadows");
898
-
899
- const gr_atlas_texture = graph_import_texture(graph, this.texture);
900
-
901
- const atlas_rect = map.layout[0];
902
-
903
- const octahedral_resolution = atlas_rect.width;
904
-
905
- // Wider face fov than the cube path (see POINT_SHADOW_TETRA_FOV_DEGREES) needs a larger
906
- // per-face resolution to hold comparable on-axis density the analogue of the cube
907
- // path's CUBE_MAPPING_RESOLUTION_SCALE.
908
- const face_resolution = Math.ceil(octahedral_resolution * POINT_SHADOW_TETRA_MAPPING_RESOLUTION_SCALE);
909
-
910
- // reserve intermediate depth texture — 2x2 grid of faces
911
- let tetra_packed_texture = graph_create_texture(graph, TextureResourceDescriptor.from({
912
- resolution: [
913
- face_resolution * POINT_SHADOW_TETRA_COLS,
914
- face_resolution * POINT_SHADOW_TETRA_ROWS,
915
- ],
916
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
917
- format: 'depth32float',
918
- enableMips: false,
919
- }));
920
-
921
- /**
922
- *
923
- * @type {PointLight}
924
- */
925
- const light = map.light;
926
-
927
- const scene_ctx = this.shadow_view_manager.obtain(map.views[0]).scene;
928
-
929
- // One sphere cull for all 4 faces — most point lights are local and meshlet culling
930
- // refines later; per-face culling isn't worth it. Same trade as the cube path.
931
- const input_meshes = graph_mesh_sphere_cull({
932
- graph,
933
- sphere: [...light.position, light.distance],
934
- scene: scene_ctx,
935
- limits: this.#graphics.collection_limits,
936
- });
937
-
938
- for (let face = 0; face < POINT_SHADOW_TETRA_FACE_COUNT; face++) {
939
- const view = map.views[face];
940
- const view_ctx = this.shadow_view_manager.obtain(view);
941
-
942
- view_ctx.update(this.#graphics);
943
- view_ctx.setViewportSize(face_resolution, face_resolution);
944
-
945
- const col = face % POINT_SHADOW_TETRA_COLS;
946
- const row = Math.floor(face / POINT_SHADOW_TETRA_COLS);
947
-
948
- const job = new RasterizationJob();
949
-
950
- job.pipeline_type = RasterizationPipelineType.Fast;
951
- job.opaque_pass = shadow_rasterization_pass_descriptor;
952
- job.alpha_tested_pass = shadow_rasterization_alpha_tested_pass_descriptor;
953
- job.cull_policy = CullPolicy.Reversed;
954
- job.render_target = null;
955
- job.view_ctx = view_ctx;
956
- job.viewport = [
957
- col * face_resolution,
958
- row * face_resolution,
959
- face_resolution,
960
- face_resolution,
961
- ];
962
-
963
- tetra_packed_texture = graph_rasterize_scene_fast_raw({
964
- graph,
965
- meshes: input_meshes,
966
- limits: this.#graphics.collection_limits,
967
- target_depth: tetra_packed_texture,
968
- job,
969
- }).target_depth;
970
- }
971
-
972
- const views = map.views;
973
- const tetra_near = map.tetra_near;
974
- const light_max_distance = map.light.distance;
975
-
976
- /**
977
- *
978
- * @param data
979
- * @param {FramePassResources} resources
980
- * @param {GraphicsExecutionContext} context
981
- */
982
- function remap_to_atlas(data, resources, context) {
983
- const atlas = resources.get(data.atlas);
984
- const tetra_texture = resources.get(data.tetra_texture);
985
-
986
- // Read each face camera's view-projection at encode time — set in map.update() and
987
- // untouched since, and it is exactly the matrix the rasterizer used above.
988
- const face_view_proj = new Array(POINT_SHADOW_TETRA_FACE_COUNT);
989
- for (let f = 0; f < POINT_SHADOW_TETRA_FACE_COUNT; f++) {
990
- face_view_proj[f] = views[f].camera.view_projection_matrix;
991
- }
992
-
993
- const settings = context.encoder.allocateTransientValueBuffer(
994
- POINT_SHADOW_TETRA_REMAP_SETTINGS,
995
- {
996
- atlas_offset: [
997
- atlas_rect.x0 + SHADOWMAP_ATLAS_BORDER,
998
- atlas_rect.y0 + SHADOWMAP_ATLAS_BORDER,
999
- atlas_rect.width - 2 * SHADOWMAP_ATLAS_BORDER,
1000
- atlas_rect.height - 2 * SHADOWMAP_ATLAS_BORDER,
1001
- ],
1002
- light_params: [
1003
- light_max_distance,
1004
- tetra_near,
1005
- 0,
1006
- 0,
1007
- ],
1008
- face_view_proj,
1009
- }
1010
- );
1011
-
1012
- shader_shadowmap_remap_tetrahedron_to_octahedral.draw({
1013
- encoder: context.encoder,
1014
- bindings: {
1015
- settings: settings,
1016
- tetra_faces: tetra_texture.obtainView(),
1017
- },
1018
- depthStencilAttachment: {
1019
- view: atlas.obtainView(),
1020
- depthLoadOp: "load",
1021
- depthStoreOp: "store",
1022
- },
1023
- viewport: [
1024
- atlas_rect.x0,
1025
- atlas_rect.y0,
1026
- atlas_rect.width,
1027
- atlas_rect.height,
1028
- ]
1029
- })
1030
- }
1031
-
1032
- const remap_data = {};
1033
-
1034
- const remap_pass = graph.add("remap tetra to atlas", remap_data, remap_to_atlas);
1035
-
1036
- remap_data.atlas = remap_pass.write(gr_atlas_texture);
1037
- remap_data.tetra_texture = remap_pass.read(tetra_packed_texture);
1038
-
1039
-
1040
- command_ctx.encodeGraph(graph);
1041
- }
1042
-
1043
- /**
1044
- * Release the per-face GPU contexts (each view's uniform/HZB/volumetrics buffers + its camera
1045
- * uniform) backing a map. The dedicated {@link shadow_view_manager} / {@link #cameras} caches
1046
- * key on the View/Camera objects and have no GC path, so a map's contexts must be released
1047
- * explicitly here or their GPU buffers leak for the context's lifetime. Most visible when the
1048
- * point-shadow generator toggle rebuilds every point map (fresh Views each time), but it also
1049
- * closes the ordinary eviction leak (light removed / stops casting / shadows disabled).
1050
- *
1051
- * In this context Views and Cameras are 1:1 per map (each map creates its own), so releasing a
1052
- * view's camera alongside it can't strand a still-referenced camera.
1053
- *
1054
- * @param {Shadowmap} map
1055
- */
1056
- #release_map_gpu_contexts(map) {
1057
- const views = map.views;
1058
- for (let j = 0; j < views.length; j++) {
1059
- const view = views[j];
1060
- this.shadow_view_manager.release(view);
1061
- this.#cameras.release(view.camera);
1062
- }
1063
- }
1064
-
1065
- /**
1066
- * Release a map's atlas rects + GPU side-resources. Caller is responsible for splicing it
1067
- * out of {@link #maps}.
1068
- *
1069
- * @param {Shadowmap} map
1070
- */
1071
- #evict_map(map) {
1072
- for (let j = 0; j < map.layout.length; j++) {
1073
- this.atlas.remove(map.layout[j]);
1074
- }
1075
- if (map.pending_layout !== null) {
1076
- for (let j = 0; j < map.pending_layout.length; j++) {
1077
- this.atlas.remove(map.pending_layout[j]);
1078
- }
1079
- map.pending_layout = null;
1080
- }
1081
-
1082
- // free the per-view/-camera GPU contexts without this an evicted map's buffers leak
1083
- this.#release_map_gpu_contexts(map);
1084
-
1085
- map.light._gpu_shadowmap_id = -1;
1086
- }
1087
-
1088
- /**
1089
- * Reconcile shadow allocations against the current scene state. Evicts maps for lights
1090
- * that no longer cast shadows or have left the scene, allocates maps for new shadow-
1091
- * casting lights, and reassigns per-type `_gpu_shadowmap_id` when the map ordering
1092
- * shifts.
1093
- *
1094
- * Called from {@link GPULightCollection.update} every frame, including frames where
1095
- * {@link LightCollection.version} hasn't moved `light.casts_shadow` flips are not
1096
- * tracked by version and are detected here.
1097
- *
1098
- * @returns {boolean} true when at least one map was added/evicted or at least one light's
1099
- * per-type shadow_id moved. The caller uses this to decide whether the light
1100
- * records need to be re-published (their `shadow_id` and CASTS_SHADOW_BIT may
1101
- * have shifted).
1102
- */
1103
- process_lights() {
1104
- // When the renderer has shadows disabled, treat every map as evictable so the next
1105
- // build clears CASTS_SHADOW_BIT on every light. Re-enabling reconstructs everything
1106
- // from scratch on the following frame — that's a one-frame catch-up cost, considered
1107
- // an acceptable trade for the simpler invariant ("disabled" = no maps held).
1108
- if (!this.enabled) {
1109
- const had_maps = this.#maps.length > 0;
1110
- for (let i = this.#maps.length - 1; i >= 0; i--) {
1111
- this.#evict_map(this.#maps[i]);
1112
- }
1113
- this.#maps.length = 0;
1114
- return had_maps;
1115
- }
1116
-
1117
- const scene = this.#scene;
1118
- const lights = scene.lights.elements;
1119
- let changed = false;
1120
-
1121
- // Reclamation: drop maps whose lights left the scene or stopped casting shadows so a
1122
- // removed light's atlas rect doesn't stay pinned forever. Walk in reverse so splice
1123
- // indices stay stable.
1124
- const live_lights = new Set(lights);
1125
- for (let i = this.#maps.length - 1; i >= 0; i--) {
1126
- const map = this.#maps[i];
1127
- const light = map.light;
1128
-
1129
- // A point map built by the other generator (cube vs tetra) must be rebuilt when the
1130
- // flag flips — its face count and draw path no longer match. Dropping it here lets
1131
- // add_light re-create it with the current generator later in this same pass.
1132
- const generator_mismatch = light.isPointLight
1133
- && Boolean(map.isTetrahedron) !== this.use_tetrahedron_point_shadows;
1134
-
1135
- if (live_lights.has(light) && light.casts_shadow && !generator_mismatch) {
1136
- continue;
1137
- }
1138
- this.#evict_map(map);
1139
- this.#maps.splice(i, 1);
1140
- changed = true;
1141
- }
1142
-
1143
- const add_directional = (light) => {
1144
- const map = new DirectionalLightShadowmap();
1145
- map.light = light;
1146
-
1147
- const bound = [];
1148
- try {
1149
- for (let j = 0; j < SHADOWMAP_CSM_CASCADE_COUNT; j++) {
1150
- const resolution_index = Math.min(j, DEFAULT_SHADOWMAP_DIRECTIONAL_RESOLUTION.length - 1);
1151
- const resolution = DEFAULT_SHADOWMAP_DIRECTIONAL_RESOLUTION[resolution_index];
1152
-
1153
- const rect = this.bind(resolution);
1154
- bound.push(rect);
1155
- map.layout[j] = rect;
1156
-
1157
- const camera = new OrthographicCamera();
1158
- camera.update();
1159
-
1160
- const view = new View(camera, scene);
1161
- view.label = `Shadow Cascade ${j}`;
1162
-
1163
- map.views[j] = view;
1164
- }
1165
- } catch (e) {
1166
- // Partial bind: roll back any cascade rects already inserted so the atlas isn't
1167
- // left holding orphans for a map that never got pushed to #maps.
1168
- for (let j = 0; j < bound.length; j++) {
1169
- this.atlas.remove(bound[j]);
1170
- }
1171
- throw e;
1172
- }
1173
-
1174
- this.#maps.push(map);
1175
- };
1176
-
1177
- const add_spot = (light) => {
1178
- const map = new SpotLightShadowmap();
1179
- map.light = light;
1180
-
1181
- const rect = this.bind(DEFAULT_SHADOWMAP_LOCAL_RESOLUTION);
1182
- map.layout[0] = rect;
1183
-
1184
- try {
1185
- const camera = new PerspectiveCamera();
1186
- camera.update();
1187
-
1188
- const view = new View(camera, scene);
1189
- view.label = `Spot Shadow`;
1190
-
1191
- map.views[0] = view;
1192
- } catch (e) {
1193
- this.atlas.remove(rect);
1194
- throw e;
1195
- }
1196
-
1197
- this.#maps.push(map);
1198
- };
1199
-
1200
- const add_point = (light) => {
1201
-
1202
- // Generator chosen by the current flag; both remap into one octahedral slot, they
1203
- // only differ in how many face frusta feed that remap (tetra 4, cube 6).
1204
- const use_tetra = this.use_tetrahedron_point_shadows;
1205
-
1206
- const map = use_tetra ? new TetrahedronPointLightShadowmap() : new PointLightShadowmap();
1207
- map.light = light;
1208
-
1209
- // single atlas rect holds the octahedral-encoded depth for all faces
1210
- const rect = this.bind(DEFAULT_SHADOWMAP_LOCAL_RESOLUTION);
1211
- map.layout[0] = rect;
1212
-
1213
- const face_count = use_tetra ? POINT_SHADOW_TETRA_FACE_COUNT : 6;
1214
-
1215
- for (let j = 0; j < face_count; j++) {
1216
- const camera = new PerspectiveCamera();
1217
- camera.update();
1218
-
1219
- const view = new View(camera, scene);
1220
- view.label = use_tetra ? `Point Shadow (tetra) face ${j}` : `Point Shadow face ${j}`;
1221
-
1222
- map.views[j] = view;
1223
- }
1224
-
1225
- this.#maps.push(map);
1226
- };
1227
-
1228
- const add_light = (light) => {
1229
- if (!light.casts_shadow) {
1230
- return;
1231
- }
1232
-
1233
- if (this.get_map(light) !== undefined) {
1234
- return;
1235
- }
1236
-
1237
- if (light.isDirectionalLight) {
1238
- add_directional(light);
1239
- changed = true;
1240
- } else if (light.isSpotLight) {
1241
- add_spot(light);
1242
- changed = true;
1243
- } else if (light.isPointLight) {
1244
- add_point(light);
1245
- changed = true;
1246
- }
1247
- };
1248
-
1249
- for (let i = 0; i < lights.length; i++) {
1250
- const light = lights[i];
1251
-
1252
- try {
1253
- add_light(light);
1254
- } catch (e) {
1255
- console.error(e);
1256
- }
1257
- }
1258
-
1259
- // Publish per-type shadowmap ids back onto the lights. Each shadow_* table is indexed
1260
- // independently, so a point light's shadow_id points into shadow_point, a spot's into
1261
- // shadow_spot, etc. Type-bucket order MUST match {@link #write_shadow_records} so each
1262
- // light record's stored shadow_id lines up with the slot we'll write into the table.
1263
- // When a map's slot index actually changes (eviction shifted neighbours up the array),
1264
- // mark its record dirty so the next #write_shadow_records re-publishes it into the new
1265
- // slot, and report `changed` so the caller re-publishes the light records too.
1266
- let pc = 0;
1267
- let sc = 0;
1268
- let dc = 0;
1269
- for (let i = 0; i < this.#maps.length; i++) {
1270
- const map = this.#maps[i];
1271
- const light = map.light;
1272
-
1273
- let new_id;
1274
- if (light.isPointLight) {
1275
- new_id = pc++;
1276
- } else if (light.isSpotLight) {
1277
- new_id = sc++;
1278
- } else if (light.isDirectionalLight) {
1279
- new_id = dc++;
1280
- } else {
1281
- continue;
1282
- }
1283
-
1284
- if (light._gpu_shadowmap_id !== new_id) {
1285
- light._gpu_shadowmap_id = new_id;
1286
- map.metadata_changed = true;
1287
- changed = true;
1288
- }
1289
- }
1290
-
1291
- return changed;
1292
- }
1293
-
1294
- /**
1295
- *
1296
- * @param {number} resolution
1297
- * @returns {AABB2}
1298
- */
1299
- bind(resolution) {
1300
-
1301
- const rect = new AABB2(0, 0, resolution, resolution);
1302
-
1303
- if (!this.atlas.add(rect)) {
1304
- throw new Error(`Failed to add shadowmap to atlas`);
1305
- }
1306
-
1307
- return rect;
1308
- }
1309
-
1310
- destroy() {
1311
- this.texture.destroy();
1312
-
1313
- for (let i = 0; i < this.#maps.length; i++) {
1314
- const map = this.#maps[i];
1315
- // release the per-view/-camera GPU contexts too — map.destroy() alone doesn't (the
1316
- // buffers live in the view/camera manager caches, not on the map)
1317
- this.#release_map_gpu_contexts(map);
1318
- map.destroy();
1319
- }
1320
- this.#maps.length = 0;
1321
- }
1322
- }
1
+ import { assert } from "../../../../core/assert.js";
2
+ import AABB2 from "../../../../core/geom/2d/aabb/AABB2.js";
3
+ import { MaxRectanglesPacker } from "../../../../core/geom/packing/max-rect/MaxRectanglesPacker.js";
4
+ import { FrameGraph } from "../../../../engine/graphics/render/frame_graph/FrameGraph.js";
5
+ import { TextureResourceDescriptor } from "../../../device/graph/TextureResourceDescriptor.js";
6
+ import { CameraManager } from "../../camera/CameraManager.js";
7
+ import { OrthographicCamera } from "../../camera/OrthographicCamera.js";
8
+ import { PerspectiveCamera } from "../../camera/PerspectiveCamera.js";
9
+ import { CullPolicy } from "../../rasterize/bucket/construct_primitive_state.js";
10
+ import { graph_mesh_sphere_cull } from "../../rasterize/compute/graph_mesh_sphere_cull.js";
11
+ import { execute_rasterization_jobs } from "../../rasterize/execute_rasterization_jobs.js";
12
+ import {
13
+ shadow_rasterization_alpha_tested_pass_descriptor
14
+ } from "../../rasterize/native/depth/shadow_rasterization_alpha_tested_pass_descriptor.js";
15
+ import {
16
+ shadow_rasterization_pass_descriptor
17
+ } from "../../rasterize/native/depth/shadow_rasterization_pass_descriptor.js";
18
+ import { RasterizationJob, RasterizationPipelineType } from "../../rasterize/RasterizationJob.js";
19
+ import { graph_rasterize_scene_fast_raw } from "../../rasterize/fast/graph_rasterize_scene_fast.js";
20
+ import { graph_create_texture, graph_import_texture } from "../../shader/graph/graph_import_texture.js";
21
+ import { GPUTextureContext } from "../../texture/GPUTextureContext.js";
22
+ import { RenderTarget } from "../../texture/RenderTarget.js";
23
+ import { View } from "../../view/View.js";
24
+ import { ViewManager } from "../../view/ViewManager.js";
25
+ import { SHADOWMAP_CSM_CASCADE_COUNT } from "./csm/SHADOWMAP_CSM_CASCADE_COUNT.js";
26
+ import { DirectionalLightShadowmap } from "./DirectionalLightShadowmap.js";
27
+ import { POINT_SHADOW_CUBE_COLS, POINT_SHADOW_CUBE_ROWS } from "./POINT_SHADOW_CUBE_LAYOUT.js";
28
+ import {
29
+ POINT_SHADOW_TETRA_COLS,
30
+ POINT_SHADOW_TETRA_FACE_COUNT,
31
+ POINT_SHADOW_TETRA_MAPPING_RESOLUTION_SCALE,
32
+ POINT_SHADOW_TETRA_ROWS,
33
+ } from "./POINT_SHADOW_TETRAHEDRON_LAYOUT.js";
34
+ import { PointLightShadowmap } from "./PointLightShadowmap.js";
35
+ import {
36
+ shader_shadowmap_remap_cube_to_octahedral
37
+ } from "./shader/shader_shadowmap_remap_cube_to_octahedral.js";
38
+ import {
39
+ POINT_SHADOW_TETRA_REMAP_SETTINGS,
40
+ shader_shadowmap_remap_tetrahedron_to_octahedral
41
+ } from "./shader/shader_shadowmap_remap_tetrahedron_to_octahedral.js";
42
+ import { SHADOWMAP_ATLAS_BORDER } from "./SHADOWMAP_ATLAS_BORDER.js";
43
+ import { TetrahedronPointLightShadowmap } from "./TetrahedronPointLightShadowmap.js";
44
+ import { compute_light_projected_area_px } from "./shadowmap_update_score.js";
45
+ import { ShadowmapDrawSelector } from "./ShadowmapDrawSelector.js";
46
+ import { ShadowmapRegistry } from "./ShadowmapRegistry.js";
47
+ import { ShadowmapResolutionPolicy } from "./ShadowmapResolutionPolicy.js";
48
+ import { SpotLightShadowmap } from "./SpotLightShadowmap.js";
49
+
50
+ const DEFAULT_SHADOWMAP_ATLAS_RESOLUTION = 4096 * 2;
51
+
52
+ /**
53
+ * Resolution by cascade.
54
+ * If the number of cascades is greater than the array length, the last resolution is used.
55
+ * @type {number[]}
56
+ */
57
+ const DEFAULT_SHADOWMAP_DIRECTIONAL_RESOLUTION = [
58
+ 1740, // first cascade
59
+ 1440,
60
+ ];
61
+
62
+ /**
63
+ * Placeholder resolution for local-light shadowmaps (spot/point).
64
+ * A proper implementation will project the light's bounding volume into screen space to derive this at runtime.
65
+ * @type {number}
66
+ */
67
+ const DEFAULT_SHADOWMAP_LOCAL_RESOLUTION = 128;
68
+
69
+ /**
70
+ * Per-frame shadow refresh budget, measured in **views** (one cube face counts as one view,
71
+ * one CSM cascade counts as one view, etc.). A point light costs 6 views, a spot light 1, a
72
+ * directional SHADOWMAP_CSM_CASCADE_COUNT. A bigger light consumes more of the budget, which
73
+ * keeps the cost of the refresh step roughly proportional to how much work we're asking the
74
+ * GPU to do — a single point light running the meshlet rasterization pipeline 6 times is
75
+ * equivalent work to 6 spot lights.
76
+ *
77
+ * Directional lights are always refreshed unconditionally (they drive the whole scene) and
78
+ * their view cost is charged against the budget, so they effectively reduce the pool available
79
+ * for local lights that frame.
80
+ *
81
+ * Unused budget is carried over between frames when demand exceeds supply see
82
+ * {@link GPUSceneShadowmapContext.#budget_carry_over}. This lets a light that's too expensive
83
+ * to fit in a single frame (e.g. a point needing 6 views when only 2 are free) eventually get
84
+ * its turn without permanently starving other lights.
85
+ * @type {number}
86
+ */
87
+ const SHADOWMAP_UPDATE_BUDGET = 8;
88
+
89
+ /**
90
+ * Shadows for a single scene
91
+ */
92
+ export class GPUSceneShadowmapContext {
93
+ atlas = new MaxRectanglesPacker(DEFAULT_SHADOWMAP_ATLAS_RESOLUTION, DEFAULT_SHADOWMAP_ATLAS_RESOLUTION);
94
+
95
+ /**
96
+ * Depth texture atlas used for shadowmaps.
97
+ * @type {GPUTextureContext}
98
+ */
99
+ texture;
100
+
101
+
102
+ /**
103
+ * @type {GraphicsContext}
104
+ */
105
+ #graphics;
106
+
107
+ /**
108
+ * Dedicated ViewManager for the cube/cascade/perspective shadow views this context
109
+ * creates and rasterizes into the atlas. Decoupled from the renderer-level ViewManager
110
+ * so the ownership chain stays self-contained the shadow context creates the views,
111
+ * caches their GPU contexts, and disposes them with itself. The shared SceneManager
112
+ * lookup is short-circuited with a tiny object that always returns our owning
113
+ * GPUSceneContext (every shadow view here is for the same scene by construction).
114
+ * @type {ViewManager}
115
+ */
116
+ shadow_view_manager;
117
+
118
+ /**
119
+ * Internal CameraManager backing {@link shadow_view_manager}. Shadow cameras are
120
+ * created locally in {@link process_lights} and only referenced by this context, so
121
+ * they don't need to share the renderer-level CameraManager.
122
+ * @type {CameraManager}
123
+ */
124
+ #cameras;
125
+
126
+ /**
127
+ * @type {RenderTarget}
128
+ */
129
+ #render_target;
130
+
131
+
132
+ /**
133
+ * Every shadowmap this scene holds, plus the light -> map index and the reconciliation marks
134
+ * that make {@link process_lights} O(lights + maps) instead of O(lights x maps).
135
+ * @type {ShadowmapRegistry}
136
+ */
137
+ #registry = new ShadowmapRegistry();
138
+
139
+ /**
140
+ * Scores and picks the maps to refresh each frame. Holds the heap it selects with across
141
+ * frames, so the per-frame scoring allocates nothing.
142
+ * @type {ShadowmapDrawSelector}
143
+ */
144
+ #selector = new ShadowmapDrawSelector();
145
+
146
+ /**
147
+ * Scratch list of lights that need a shadowmap allocated, handed from pass 1 of
148
+ * {@link process_lights} to pass 3. Held across frames rather than allocated per call — it is
149
+ * empty on every frame where the light set didn't move, which is nearly all of them.
150
+ * @type {Light[]}
151
+ */
152
+ #reconcile_newcomers = [];
153
+
154
+ /**
155
+ * Master kill switch for shadows on this scene. Defaults true; the renderer sets it from
156
+ * its own `feature_shadows_enabled` flag before each frame's light update. When false,
157
+ * {@link process_lights} evicts all maps so the next light-record build clears
158
+ * CASTS_SHADOW_BIT on every light. {@link select_for_draw} and {@link draw} are still
159
+ * gated externally by the renderer; this flag exists to keep the GPU light records in
160
+ * sync with that gating without needing to thread the renderer's flag down further.
161
+ * @type {boolean}
162
+ */
163
+ enabled = true;
164
+
165
+ /**
166
+ * Selects the tetrahedral point-light shadow generator (4 face frusta) over the default
167
+ * cube generator (6 faces). The renderer pushes this from its
168
+ * `feature_tetrahedron_point_shadows` flag before each light update, alongside {@link enabled}.
169
+ *
170
+ * Only affects how a point light's shadow is GENERATED both paths remap into the same
171
+ * octahedral atlas encoding, so the sampler is identical and this can be toggled at runtime.
172
+ * A flip is picked up in {@link process_lights}: existing point maps whose generator no longer
173
+ * matches this flag are evicted and rebuilt with the correct face count on the same pass.
174
+ * @type {boolean}
175
+ */
176
+ use_tetrahedron_point_shadows = false;
177
+
178
+ /**
179
+ * Frame index of the most recent {@link select_for_draw} call. Passed to {@link draw} via
180
+ * this field so we don't have to thread it through the call again — draw() stamps
181
+ * `map.last_updated_frame_index` with this value after each successful rasterization.
182
+ * @type {number}
183
+ */
184
+ #current_frame_index = -1;
185
+
186
+ /**
187
+ * View-budget surplus carried forward from prior frames. Accumulates whenever we had
188
+ * non-directional candidates that couldn't fit in the per-frame budget (i.e. a point light
189
+ * wanted 6 views but only 2 were free after directionals + earlier locals consumed their
190
+ * share). Resets to 0 on any frame where the full demand was satisfied, so the carryover
191
+ * doesn't grow unboundedly when the scene is quiet.
192
+ * @type {number}
193
+ */
194
+ #budget_carry_over = 0;
195
+
196
+ /**
197
+ * Adjusts per-map atlas footprints each frame based on projected on-screen area. Lazily
198
+ * tied to {@link atlas} in the constructor.
199
+ * @type {ShadowmapResolutionPolicy}
200
+ */
201
+ #resolution_policy;
202
+
203
+ /**
204
+ * Number of shadowmaps actually rasterized by the most recent {@link draw} call. Reset at
205
+ * the start of draw(), incremented once per map that went through the raster path. Exposed
206
+ * for debugging scheduling behaviour a persistently oscillating value (e.g. 36/20/15
207
+ * repeating) indicates some maps are being invalidated every frame and forcing a redraw,
208
+ * which usually means the resolution policy or selector is churning.
209
+ * @type {number}
210
+ */
211
+ #debug_render_count = 0;
212
+
213
+ /**
214
+ * @returns {number} number of shadowmaps rasterized in the most recent draw pass
215
+ */
216
+ get debug_render_count() {
217
+ return this.#debug_render_count;
218
+ }
219
+
220
+ /**
221
+ * Most recently measured atlas occupancy (placed area / total area, including pending
222
+ * reservations), in [0, 1]. Forwards from the resolution policy. Useful for the debug
223
+ * overlay alongside {@link debug_drop_size_scale} — together they show whether the global
224
+ * shrink/grow feedback has converged or is still chasing the target band.
225
+ * @returns {number}
226
+ */
227
+ get debug_atlas_occupancy() {
228
+ return this.#resolution_policy.last_occupancy;
229
+ }
230
+
231
+ /**
232
+ * Current value of the global drop-size scale applied by the resolution policy. 1 means no
233
+ * shrink; values below 1 mean atlas pressure has trimmed every light's target size
234
+ * proportionally. Forwarded from the resolution policy for debug overlays.
235
+ * @returns {number}
236
+ */
237
+ get debug_drop_size_scale() {
238
+ return this.#resolution_policy.drop_size_scale;
239
+ }
240
+
241
+
242
+ /**
243
+ * Per-type counts of shadow records written to the GPU database in the previous
244
+ * {@link draw} pass. Used to identify trailing slots that must be zeroed when the
245
+ * map count for a given type shrinks (e.g. a shadow-casting light is removed),
246
+ * mirroring the bookkeeping in {@link GPULightCollection.#build}.
247
+ */
248
+ #prev_point_count = 0;
249
+ #prev_spot_count = 0;
250
+ #prev_directional_count = 0;
251
+
252
+ /**
253
+ * The Scene this context manages shadows for. Captured at construction so
254
+ * {@link process_lights} doesn't need it threaded back through every frame.
255
+ * @type {Scene}
256
+ */
257
+ #scene;
258
+
259
+ /**
260
+ * Owning light collection — gives us access to the GPU database where shadow
261
+ * records live and to the source LightCollection. Set in the constructor.
262
+ * @type {GPULightCollection}
263
+ */
264
+ #owner;
265
+
266
+ /**
267
+ *
268
+ * @param {GraphicsContext} graphics
269
+ * @param {GPUSceneContext} scene_ctx the GPU-side context for the Scene we manage
270
+ * shadows for. Used as the (single) destination of {@link shadow_view_manager}'s
271
+ * scene lookup — every shadow view is for this scene, so the lookup short-circuits.
272
+ * @param {GPULightCollection} owner the parent light collection — gives us back-access
273
+ * to the GPU database we publish records into during {@link draw}.
274
+ */
275
+ constructor(
276
+ graphics,
277
+ scene_ctx,
278
+ owner,
279
+ ) {
280
+ assert.defined(graphics, 'graphics');
281
+ assert.defined(scene_ctx, 'scene_ctx');
282
+ assert.equal(scene_ctx.isGPUSceneContext, true, 'scene_ctx.isGPUSceneContext !== true');
283
+ assert.defined(owner, 'owner');
284
+
285
+
286
+ const initial_atlas_resolution = Math.min(
287
+ graphics.device.limits.maxTextureDimension2D,
288
+ DEFAULT_SHADOWMAP_ATLAS_RESOLUTION,
289
+ );
290
+
291
+ {
292
+ const t = this.texture = new GPUTextureContext(graphics.device);
293
+
294
+
295
+ const descriptor = t.descriptor;
296
+
297
+ descriptor.format = "depth32float";
298
+ descriptor.dimension = "2d";
299
+ descriptor.label = "Shadowmap atlas";
300
+ descriptor.usage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC;
301
+
302
+ t.resize(
303
+ initial_atlas_resolution,
304
+ initial_atlas_resolution
305
+ );
306
+
307
+ // ensure atlas size matches
308
+ this.atlas.resize(
309
+ initial_atlas_resolution,
310
+ initial_atlas_resolution
311
+ );
312
+ }
313
+
314
+ this.#graphics = graphics;
315
+ this.#scene = scene_ctx.scene;
316
+ this.#owner = owner;
317
+
318
+ // Local CameraManager + ViewManager dedicated to shadow rasterization. We don't
319
+ // share with the renderer-level managers because the cameras and views here are
320
+ // owned by this context and only referenced from inside it. The "scenes" lookup
321
+ // is a stub that always returns the owning scene context — every shadow view is
322
+ // for `this.#scene` by construction.
323
+ this.#cameras = new CameraManager(graphics.device);
324
+ this.shadow_view_manager = new ViewManager(graphics, this.#cameras, {
325
+ obtain: () => scene_ctx,
326
+ });
327
+
328
+ this.#render_target = RenderTarget.from({
329
+ depth: this.texture,
330
+ });
331
+
332
+ this.#resolution_policy = new ShadowmapResolutionPolicy(this.atlas);
333
+ }
334
+
335
+ /**
336
+ *
337
+ * @param {Light} light
338
+ * @returns {Shadowmap|undefined}
339
+ */
340
+ get_map(light) {
341
+ return this.#registry.get(light);
342
+ }
343
+
344
+ /**
345
+ * Decide which shadowmaps should be rasterized this frame.
346
+ *
347
+ * Runs at the top of every frame, before {@link draw}. Responsibilities:
348
+ *
349
+ * 1. **Reset per-frame signals.** `should_draw` is cleared on every map so a stale "yes"
350
+ * from a prior frame can never cause a spurious extra raster. Folded into the selector's
351
+ * scoring pass, which walks every map anyway.
352
+ * 2. **Measure on-screen presence.** `projected_area_px` is computed once per map and stored
353
+ * on the map itself consumed by the resolution policy, the scorer, and the selector,
354
+ * and exposed for debug tooling.
355
+ * 3. **Reserve pending atlas footprints.** {@link ShadowmapResolutionPolicy.adjust} may
356
+ * attach a `pending_layout` to maps whose projected area has shifted enough to warrant a
357
+ * resize. The committed `layout` is left untouched so sampling remains valid; the
358
+ * pending is consumed later by {@link draw} when the scheduler naturally picks this map
359
+ * up (no forced redraw).
360
+ * 4. **Score + select.** Delegated to {@link ShadowmapDrawSelector}, which sets `should_draw`
361
+ * on the winners and reports the view spend and whether any candidate went unmet:
362
+ * - **Directional** always selected (infinite score); view cost still charged.
363
+ * - **Local, off-screen** (`projected_area_px <= 0`) skipped entirely.
364
+ * - **Local, in-view, invalid** MUST draw. `is_invalid` means the committed layout
365
+ * has never held valid depth (freshly created map); sampling it would display
366
+ * garbage, so we bypass the budget. Resizes no longer invalidate under the dual-
367
+ * layout model this flag fires only on first-draw.
368
+ * - **Local, in-view, valid** → compete for the remaining budget by score. Ordering
369
+ * is descending score so the most important (largest-on-screen, stalest) maps win.
370
+ * 5. **Fit the winners.** `map.update()` on each selected map, so its projection matrices
371
+ * reflect the current frame. The `last_updated_frame_index` stamp is deferred to
372
+ * {@link draw} — it records when the atlas contents become current, not when the
373
+ * projection is fitted.
374
+ *
375
+ * Shadow records are NOT written here; they're written at the end of {@link draw} so the
376
+ * shader reads slots that reflect any just-committed pending swaps within the same frame.
377
+ *
378
+ * See {@link compute_shadowmap_update_score} for the scoring formula and rationale, and
379
+ * {@link ShadowmapDrawSelector} for why the ordering is a heap drain rather than a sort.
380
+ *
381
+ * @param {Camera} main_camera used both for rendering the final frame and for projected-area
382
+ * computation and directional CSM fit; must be callable for `.update()`
383
+ * @param {GPUSceneContext} scene
384
+ * @param {number} frame_index monotonically increasing frame counter, used for staleness
385
+ * @param {ArrayLike<number>} view_resolution [width, height] of the main view in pixels; used
386
+ * to convert projected sphere extents into a pixel-area score
387
+ */
388
+ select_for_draw(main_camera, scene, frame_index, view_resolution) {
389
+ assert.defined(main_camera, 'main_camera');
390
+ assert.isObject(main_camera, 'main_camera');
391
+ assert.equal(scene.isGPUSceneContext, true, 'scene.isGPUSceneContext !== true');
392
+ assert.isNumber(frame_index, 'frame_index');
393
+ assert.defined(view_resolution, 'view_resolution');
394
+
395
+ // make sure the camera is updated both our scoring (projected area) and any downstream
396
+ // map.update() reads depend on view/projection matrices being current
397
+ main_camera.update();
398
+
399
+ this.#current_frame_index = frame_index;
400
+
401
+ const shadowmaps = this.#registry.maps;
402
+ const n = shadowmaps.length;
403
+
404
+ // Step 1 (clearing the stale per-frame `should_draw`) happens inside the selector's own
405
+ // scoring pass, so there is no separate walk for it here.
406
+
407
+ // Step 2: recompute projected areas. Writing onto the map so the policy + scorer can
408
+ // just read `map.projected_area_px` without threading a parallel array through.
409
+ for (let i = 0; i < n; i++) {
410
+ const m = shadowmaps[i];
411
+ m.projected_area_px = compute_light_projected_area_px(m.light, main_camera, view_resolution);
412
+ }
413
+
414
+ // Step 3: policy reserves pending resize footprints alongside committed layouts. It
415
+ // never mutates layout and never invalidates — sampling continues against the old
416
+ // region until draw() commits the pending during a normal raster pass. Directionals and
417
+ // off-screen locals are skipped (off-screen locals also get any stale pending released
418
+ // so an idle reservation can't tie up atlas space).
419
+ this.#resolution_policy.adjust(shadowmaps, frame_index);
420
+
421
+ // Steps 4 and 5: score every map and greedy-fill the view budget in score order. See
422
+ // ShadowmapDrawSelector for why this is a heap drain rather than a sort — the budget
423
+ // consumes a handful of winners out of however many candidates the scene has.
424
+ const budget_total = SHADOWMAP_UPDATE_BUDGET + this.#budget_carry_over;
425
+ const selector = this.#selector;
426
+
427
+ selector.select(shadowmaps, frame_index, budget_total);
428
+
429
+ // Fit the winners' projections to the current frame. Kept out of the selector because it
430
+ // needs the camera and the scene, and the selector is deliberately free of both.
431
+ //
432
+ // Maps that lost keep their prior metadata + atlas contents — a never-refreshed map
433
+ // samples the initial depth-cleared rect, which reads as "fully lit" under the reverse-Z
434
+ // greater compare. Acceptable behavior until the map eventually wins a slot.
435
+ const selected = selector.selected;
436
+ for (let i = 0; i < selected.length; i++) {
437
+ selected[i].update(main_camera, scene);
438
+ }
439
+
440
+ // Carry over the unused pool only when there was demand we couldn't meet. When the whole
441
+ // scene's demand was satisfied, reset to 0 so idle frames don't inflate the pool. If
442
+ // must-draw bypasses over-spent the pool (views_spent > budget_total), clamp at zero —
443
+ // we can't owe future frames for past over-spend, that way lies runaway starvation.
444
+ if (selector.had_unfit_candidates) {
445
+ this.#budget_carry_over = Math.max(0, budget_total - selector.views_spent);
446
+ } else {
447
+ this.#budget_carry_over = 0;
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Publish per-shadowmap records into the scene's GPU database. Each map writes its record
453
+ * into the type-specific shadow_* table at the slot identified by `light._gpu_shadowmap_id`
454
+ * (which is itself the per-type index, set by {@link process_lights}). Trailing slots from
455
+ * previous frames are zeroed when the map count for a type shrinks, so stale entries don't
456
+ * linger.
457
+ *
458
+ * Only maps with `metadata_changed === true` are re-uploaded; the flag is cleared after
459
+ * the slot is queued so unchanged maps don't pay for an upload they don't need. Trim writes
460
+ * always run regardless of the flag they zero slots that no longer correspond to any map.
461
+ *
462
+ * The type-bucket order here MUST match the per-type id assignment in {@link process_lights}
463
+ * (Point → Spot → Directional) so the shadow_id stamped onto each light record points at the
464
+ * slot we write here.
465
+ *
466
+ * Caller must invoke `database.update(cmd)` afterwards to upload the writes that is done
467
+ * by {@link draw} once for both the new records and any zeroed trailing slots.
468
+ *
469
+ * @param {GPUDatabase} database
470
+ */
471
+ #write_shadow_records(database) {
472
+ const t_point = database.get('shadow_point');
473
+ const t_spot = database.get('shadow_spot');
474
+ const t_directional = database.get('shadow_directional');
475
+
476
+ const maps = this.#registry.maps;
477
+ let pc = 0;
478
+ let sc = 0;
479
+ let dc = 0;
480
+
481
+ for (let i = 0; i < maps.length; i++) {
482
+ const map = maps[i];
483
+ const light = map.light;
484
+
485
+ let table;
486
+ let slot;
487
+ if (light.isPointLight) {
488
+ table = t_point;
489
+ slot = pc++;
490
+ } else if (light.isSpotLight) {
491
+ table = t_spot;
492
+ slot = sc++;
493
+ } else if (light.isDirectionalLight) {
494
+ table = t_directional;
495
+ slot = dc++;
496
+ } else {
497
+ continue;
498
+ }
499
+
500
+ if (map.metadata_changed) {
501
+ table.set(slot, map.make_record());
502
+ map.metadata_changed = false;
503
+ }
504
+ }
505
+
506
+ // trim trailing slots from previous frames so disused entries don't leak
507
+ for (let i = pc; i < this.#prev_point_count; i++) {
508
+ t_point.remove(i);
509
+ }
510
+ for (let i = sc; i < this.#prev_spot_count; i++) {
511
+ t_spot.remove(i);
512
+ }
513
+ for (let i = dc; i < this.#prev_directional_count; i++) {
514
+ t_directional.remove(i);
515
+ }
516
+
517
+ this.#prev_point_count = pc;
518
+ this.#prev_spot_count = sc;
519
+ this.#prev_directional_count = dc;
520
+ }
521
+
522
+ /**
523
+ * Rasterize every shadowmap flagged by {@link select_for_draw} for a refresh this frame,
524
+ * committing any pending resize reservations in the process, then publish per-shadow
525
+ * metadata into the scene's GPU database.
526
+ *
527
+ * For each map with `should_draw` set:
528
+ *
529
+ * 1. If the map has a {@link Shadowmap.pending_layout} reservation, swap it into place
530
+ * BEFORE rasterization so the draw targets the new region. The old committed rects
531
+ * are remembered for step 3.
532
+ * 2. Dispatch to the type-specific raster path:
533
+ * - **Directional / spot**: direct raster into the atlas's sub-rects (one job per
534
+ * cascade for directionals, one job per spot).
535
+ * - **Point**: 6 cube-face depth passes into a shared intermediate texture, then
536
+ * cube→octahedral remap into the atlas. Point lights are serialised so they can
537
+ * reuse the intermediate texture.
538
+ * 3. If a commit happened in step 1, release the old committed rects from the atlas now
539
+ * that the new region holds valid depth.
540
+ * 4. Clear per-frame flags (`should_draw`, `is_invalid`) and stamp
541
+ * `last_updated_frame_index` with the current frame.
542
+ *
543
+ * After the loop, each map's metadata record is written into the corresponding
544
+ * `shadow_point` / `shadow_spot` / `shadow_directional` table on the owning light
545
+ * collection's database, then the database is uploaded — this is where the committed
546
+ * swaps become visible to the lighting shader. `#debug_render_count` is reset at entry
547
+ * and incremented per map to expose scheduling behaviour to the debug overlay.
548
+ *
549
+ * No external job list: every path batches its own work internally via
550
+ * {@link execute_rasterization_jobs}. Callers just pass a command context.
551
+ *
552
+ * @param {ShadeGPUCommandContext} command_ctx
553
+ */
554
+ draw(command_ctx) {
555
+ assert.defined(command_ctx, 'command_ctx');
556
+
557
+
558
+ this.#debug_render_count = 0;
559
+
560
+ const frame_index = this.#current_frame_index;
561
+ const maps = this.#registry.maps;
562
+
563
+ for (let i = 0; i < maps.length; i++) {
564
+ const map = maps[i];
565
+ if (!map.should_draw) {
566
+ continue;
567
+ }
568
+
569
+ // Step 1: commit any pending-layout reservation before rasterizing. The draw helpers
570
+ // read `map.layout` directly, so swapping here makes the raster target the new
571
+ // region. We stash the old rect(s) locally for step 3's atlas release — once the
572
+ // raster commands are enqueued, the old region's contents will no longer be sampled
573
+ // (the next shadow-record write publishes the new layout), so returning that atlas
574
+ // space to the packer is safe.
575
+ let retired_rects = null;
576
+ if (map.pending_layout !== null) {
577
+ retired_rects = map.layout;
578
+ map.layout = map.pending_layout;
579
+ map.pending_layout = null;
580
+ map.metadata_changed = true;
581
+ }
582
+
583
+ const light = map.light;
584
+
585
+ if (light.isDirectionalLight || light.isSpotLight) {
586
+ this.#draw_direct(map, command_ctx);
587
+ } else if (light.isPointLight) {
588
+ if (map.isTetrahedron) {
589
+ this.#draw_point_tetrahedron(map, command_ctx);
590
+ } else {
591
+ this.#draw_point(map, command_ctx);
592
+ }
593
+ } else {
594
+ throw new Error(`Unsupported light type ${light.constructor.name}`);
595
+ }
596
+
597
+ // Step 3: release the retired rects from the atlas. Must happen AFTER the raster
598
+ // helpers finish reading map.layout (they may close over retired_rects as scratch
599
+ // memory or view references in some paths; safest order is raster-first).
600
+ if (retired_rects !== null) {
601
+ for (let j = 0; j < retired_rects.length; j++) {
602
+ this.atlas.remove(retired_rects[j]);
603
+ }
604
+ }
605
+
606
+ // Step 4: atlas contents are now current. Clear per-frame flags and stamp the frame
607
+ // index so the next frame's scorer computes staleness correctly.
608
+ map.should_draw = false;
609
+ map.is_invalid = false;
610
+ map.last_updated_frame_index = frame_index;
611
+ this.#debug_render_count++;
612
+ }
613
+
614
+
615
+ // Publish metadata AFTER all commits so the lighting shader sees atlas coordinates that
616
+ // match the regions we just rasterised into. Never-drawn maps carry stale-but-valid
617
+ // projection + layout; off-screen maps and skipped candidates publish their prior state.
618
+ const database = this.#owner.database;
619
+ this.#write_shadow_records(database);
620
+ database.update(command_ctx);
621
+
622
+
623
+ // finalize rendered views
624
+ for (const map of this.#registry.maps) {
625
+ if (map.last_updated_frame_index !== frame_index) {
626
+ // skip
627
+ continue;
628
+ }
629
+
630
+ for (const view of map.views) {
631
+ const view_ctx = this.shadow_view_manager.obtain(view);
632
+ view_ctx.finish_frame(command_ctx);
633
+ }
634
+ }
635
+ }
636
+
637
+ /**
638
+ * Rasterize a directional or spot shadowmap directly into its atlas rects. One job per
639
+ * `map.views[i]` / `map.layout[i]` pair directionals have N cascades, spots have one.
640
+ *
641
+ * @param {Shadowmap} map
642
+ * @param {ShadeGPUCommandContext} command_ctx
643
+ */
644
+ #draw_direct(map, command_ctx) {
645
+ // TODO pre-cull mesh set by sphere-casting the light volume against the main View Frustum
646
+ // see ref: https://www.reddit.com/r/Unity3D/comments/1foe8qi/added_a_way_to_perform_frustum_culling_and_take
647
+
648
+ const graph = new FrameGraph("shadows");
649
+
650
+ const shadow_views = map.views;
651
+ const jobs = new Array(shadow_views.length);
652
+
653
+ for (let i = 0; i < shadow_views.length; i++) {
654
+ const view = shadow_views[i];
655
+ const view_ctx = this.shadow_view_manager.obtain(view);
656
+
657
+ view_ctx.update(this.#graphics);
658
+
659
+ const atlas_aabb = map.layout[i];
660
+ view_ctx.setViewportSize(atlas_aabb.width, atlas_aabb.height);
661
+
662
+ const job = new RasterizationJob();
663
+ job.opaque_pass = shadow_rasterization_pass_descriptor;
664
+ job.alpha_tested_pass = shadow_rasterization_alpha_tested_pass_descriptor;
665
+ job.cull_policy = CullPolicy.Reversed;
666
+ job.render_target = this.#render_target;
667
+ job.view_ctx = view_ctx;
668
+ job.viewport = [atlas_aabb.x0, atlas_aabb.y0, atlas_aabb.width, atlas_aabb.height];
669
+
670
+
671
+ jobs[i] = job;
672
+
673
+ // TODO reproject current scene view into shadow map for better occlusion culling,
674
+ // see Assassin's Creed Unity talk on GPU-driven rendering,
675
+ // see "RE ENGINE Meshlet Rendering Pipeline" https://youtu.be/nWgPtCDXlPc?t=2055
676
+
677
+ // TODO need to linearize depth(?)
678
+ // can linearize together with reprojection (TAA)
679
+ }
680
+
681
+ execute_rasterization_jobs({
682
+ jobs,
683
+ limits: this.#graphics.collection_limits,
684
+ command_ctx,
685
+ graph,
686
+ });
687
+
688
+ command_ctx.encodeGraph(graph);
689
+ }
690
+
691
+ /**
692
+ * Rasterize a point light's 6 cube faces into the shared intermediate texture, then run the
693
+ * cube→octahedral remap shader to write the encoded depth into this light's atlas rect.
694
+ *
695
+ * Sequencing matters: each point light fully completes (6 cube rasters + remap) before the
696
+ * next one starts, so the intermediate texture can be reused across point lights without
697
+ * cross-contamination.
698
+ *
699
+ * @param {PointLightShadowmap} map
700
+ * @param {ShadeGPUCommandContext} command_ctx
701
+ */
702
+ #draw_point(
703
+ map,
704
+ command_ctx,
705
+ ) {
706
+
707
+ const graph = new FrameGraph("shadows");
708
+
709
+ const gr_atlas_texture = graph_import_texture(graph, this.texture);
710
+
711
+ const atlas_rect = map.layout[0];
712
+
713
+ const octahedral_resolution = atlas_rect.width;
714
+
715
+ /**
716
+ * Controls the relationship between octahedral resolution and the cube map used during rendering
717
+ * If this is larger - we get better quality at the cost of rendering more pixels.
718
+ *
719
+ * Solving for solid angle coverage, using the formula `N=POW(1 - SQRT(E/3), 3/2)` we get:
720
+ *
721
+ * 0% error -> 1
722
+ * 1% error -> 0.915
723
+ * 5% error -> 0.81299
724
+ * 10% error -> 0.739
725
+ * 20% error -> 0.639
726
+ * 30% error -> 0.565
727
+ * 40% error -> 0.506
728
+ * 50% error -> 0.455
729
+ *
730
+ * @type {number}
731
+ */
732
+ const CUBE_MAPPING_RESOLUTION_SCALE = 1;
733
+
734
+ const cube_face_resolution = Math.ceil(octahedral_resolution * CUBE_MAPPING_RESOLUTION_SCALE);
735
+
736
+ // reserve texture
737
+ let cube_packed_texture = graph_create_texture(graph, TextureResourceDescriptor.from({
738
+ resolution: [
739
+ cube_face_resolution * POINT_SHADOW_CUBE_COLS,
740
+ cube_face_resolution * POINT_SHADOW_CUBE_ROWS,
741
+ ],
742
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
743
+ format: 'depth32float',
744
+ enableMips: false,
745
+ }));
746
+
747
+ /**
748
+ *
749
+ * @type {PointLight}
750
+ */
751
+ const light = map.light;
752
+
753
+ const scene_ctx = this.shadow_view_manager.obtain(map.views[0]).scene;
754
+
755
+ /**
756
+ * Sphere-culled meshes from the scene.
757
+ * We could do per-face culling, but most point lights are local, and there's already meshlet culling happening later in the pipeline.
758
+ * Good enough.
759
+ * @type {number}
760
+ */
761
+ const input_meshes = graph_mesh_sphere_cull({
762
+ graph,
763
+ sphere: [...light.position, light.distance],
764
+ scene: scene_ctx,
765
+ limits: this.#graphics.collection_limits,
766
+ });
767
+
768
+ for (let face = 0; face < 6; face++) {
769
+ const view = map.views[face];
770
+ const view_ctx = this.shadow_view_manager.obtain(view);
771
+
772
+ view_ctx.update(this.#graphics);
773
+ view_ctx.setViewportSize(cube_face_resolution, cube_face_resolution);
774
+
775
+ const col = face % POINT_SHADOW_CUBE_COLS;
776
+ const row = Math.floor(face / POINT_SHADOW_CUBE_COLS);
777
+
778
+ const job = new RasterizationJob();
779
+
780
+ job.pipeline_type = RasterizationPipelineType.Fast;
781
+ job.opaque_pass = shadow_rasterization_pass_descriptor;
782
+ job.alpha_tested_pass = shadow_rasterization_alpha_tested_pass_descriptor;
783
+ job.cull_policy = CullPolicy.Reversed;
784
+ job.render_target = null;
785
+ job.view_ctx = view_ctx;
786
+ job.viewport = [
787
+ col * cube_face_resolution,
788
+ row * cube_face_resolution,
789
+ cube_face_resolution,
790
+ cube_face_resolution,
791
+ ];
792
+
793
+ cube_packed_texture = graph_rasterize_scene_fast_raw({
794
+ graph,
795
+ meshes: input_meshes,
796
+ limits: this.#graphics.collection_limits,
797
+ target_depth: cube_packed_texture,
798
+ job,
799
+ }).target_depth;
800
+ }
801
+
802
+ /**
803
+ *
804
+ * @param data
805
+ * @param {FramePassResources} resources
806
+ * @param {GraphicsExecutionContext} context
807
+ */
808
+ function remap_to_atlas(data, resources, context) {
809
+ const atlas = resources.get(data.atlas);
810
+ const cube_texture = resources.get(data.cube_texture);
811
+
812
+ const settings = {
813
+ atlas_offset: [
814
+ atlas_rect.x0 + SHADOWMAP_ATLAS_BORDER,
815
+ atlas_rect.y0 + SHADOWMAP_ATLAS_BORDER,
816
+ atlas_rect.width - 2 * SHADOWMAP_ATLAS_BORDER,
817
+ atlas_rect.height - 2 * SHADOWMAP_ATLAS_BORDER,
818
+ ],
819
+ light_params: [
820
+ map.light.distance,
821
+ map.cube_near,
822
+ 0,
823
+ 0,
824
+ ]
825
+ };
826
+
827
+ shader_shadowmap_remap_cube_to_octahedral.draw({
828
+ encoder: context.encoder,
829
+ bindings: {
830
+ settings: settings,
831
+ cube_faces: cube_texture.obtainView(),
832
+ },
833
+ depthStencilAttachment: {
834
+ view: atlas.obtainView(),
835
+ depthLoadOp: "load",
836
+ depthStoreOp: "store",
837
+ },
838
+ viewport: [
839
+ atlas_rect.x0,
840
+ atlas_rect.y0,
841
+ atlas_rect.width,
842
+ atlas_rect.height,
843
+ ]
844
+ })
845
+ }
846
+
847
+ const remap_data = {};
848
+
849
+ const remap_pass = graph.add("remap to atals", remap_data, remap_to_atlas);
850
+
851
+ remap_data.atlas = remap_pass.write(gr_atlas_texture);
852
+ remap_data.cube_texture = remap_pass.read(cube_packed_texture);
853
+
854
+
855
+ command_ctx.encodeGraph(graph);
856
+ }
857
+
858
+ /**
859
+ * Rasterize a point light's 4 tetrahedron faces into a shared intermediate texture, then run
860
+ * the tetrahedron→octahedral remap to write the encoded depth into this light's atlas rect.
861
+ * The tetrahedral parallel to {@link #draw_point} — same shape, 4 faces in a 2x2 grid instead
862
+ * of 6 in a 3x2 grid, and the remap is handed each face's view-projection matrix so it can
863
+ * select faces and project directions in exact agreement with how the depth was rasterized.
864
+ *
865
+ * @param {TetrahedronPointLightShadowmap} map
866
+ * @param {ShadeGPUCommandContext} command_ctx
867
+ */
868
+ #draw_point_tetrahedron(
869
+ map,
870
+ command_ctx,
871
+ ) {
872
+
873
+ const graph = new FrameGraph("shadows");
874
+
875
+ const gr_atlas_texture = graph_import_texture(graph, this.texture);
876
+
877
+ const atlas_rect = map.layout[0];
878
+
879
+ const octahedral_resolution = atlas_rect.width;
880
+
881
+ // Wider face fov than the cube path (see POINT_SHADOW_TETRA_FOV_DEGREES) needs a larger
882
+ // per-face resolution to hold comparable on-axis density — the analogue of the cube
883
+ // path's CUBE_MAPPING_RESOLUTION_SCALE.
884
+ const face_resolution = Math.ceil(octahedral_resolution * POINT_SHADOW_TETRA_MAPPING_RESOLUTION_SCALE);
885
+
886
+ // reserve intermediate depth texture 2x2 grid of faces
887
+ let tetra_packed_texture = graph_create_texture(graph, TextureResourceDescriptor.from({
888
+ resolution: [
889
+ face_resolution * POINT_SHADOW_TETRA_COLS,
890
+ face_resolution * POINT_SHADOW_TETRA_ROWS,
891
+ ],
892
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
893
+ format: 'depth32float',
894
+ enableMips: false,
895
+ }));
896
+
897
+ /**
898
+ *
899
+ * @type {PointLight}
900
+ */
901
+ const light = map.light;
902
+
903
+ const scene_ctx = this.shadow_view_manager.obtain(map.views[0]).scene;
904
+
905
+ // One sphere cull for all 4 faces most point lights are local and meshlet culling
906
+ // refines later; per-face culling isn't worth it. Same trade as the cube path.
907
+ const input_meshes = graph_mesh_sphere_cull({
908
+ graph,
909
+ sphere: [...light.position, light.distance],
910
+ scene: scene_ctx,
911
+ limits: this.#graphics.collection_limits,
912
+ });
913
+
914
+ for (let face = 0; face < POINT_SHADOW_TETRA_FACE_COUNT; face++) {
915
+ const view = map.views[face];
916
+ const view_ctx = this.shadow_view_manager.obtain(view);
917
+
918
+ view_ctx.update(this.#graphics);
919
+ view_ctx.setViewportSize(face_resolution, face_resolution);
920
+
921
+ const col = face % POINT_SHADOW_TETRA_COLS;
922
+ const row = Math.floor(face / POINT_SHADOW_TETRA_COLS);
923
+
924
+ const job = new RasterizationJob();
925
+
926
+ job.pipeline_type = RasterizationPipelineType.Fast;
927
+ job.opaque_pass = shadow_rasterization_pass_descriptor;
928
+ job.alpha_tested_pass = shadow_rasterization_alpha_tested_pass_descriptor;
929
+ job.cull_policy = CullPolicy.Reversed;
930
+ job.render_target = null;
931
+ job.view_ctx = view_ctx;
932
+ job.viewport = [
933
+ col * face_resolution,
934
+ row * face_resolution,
935
+ face_resolution,
936
+ face_resolution,
937
+ ];
938
+
939
+ tetra_packed_texture = graph_rasterize_scene_fast_raw({
940
+ graph,
941
+ meshes: input_meshes,
942
+ limits: this.#graphics.collection_limits,
943
+ target_depth: tetra_packed_texture,
944
+ job,
945
+ }).target_depth;
946
+ }
947
+
948
+ const views = map.views;
949
+ const tetra_near = map.tetra_near;
950
+ const light_max_distance = map.light.distance;
951
+
952
+ /**
953
+ *
954
+ * @param data
955
+ * @param {FramePassResources} resources
956
+ * @param {GraphicsExecutionContext} context
957
+ */
958
+ function remap_to_atlas(data, resources, context) {
959
+ const atlas = resources.get(data.atlas);
960
+ const tetra_texture = resources.get(data.tetra_texture);
961
+
962
+ // Read each face camera's view-projection at encode time — set in map.update() and
963
+ // untouched since, and it is exactly the matrix the rasterizer used above.
964
+ const face_view_proj = new Array(POINT_SHADOW_TETRA_FACE_COUNT);
965
+ for (let f = 0; f < POINT_SHADOW_TETRA_FACE_COUNT; f++) {
966
+ face_view_proj[f] = views[f].camera.view_projection_matrix;
967
+ }
968
+
969
+ const settings = context.encoder.allocateTransientValueBuffer(
970
+ POINT_SHADOW_TETRA_REMAP_SETTINGS,
971
+ {
972
+ atlas_offset: [
973
+ atlas_rect.x0 + SHADOWMAP_ATLAS_BORDER,
974
+ atlas_rect.y0 + SHADOWMAP_ATLAS_BORDER,
975
+ atlas_rect.width - 2 * SHADOWMAP_ATLAS_BORDER,
976
+ atlas_rect.height - 2 * SHADOWMAP_ATLAS_BORDER,
977
+ ],
978
+ light_params: [
979
+ light_max_distance,
980
+ tetra_near,
981
+ 0,
982
+ 0,
983
+ ],
984
+ face_view_proj,
985
+ }
986
+ );
987
+
988
+ shader_shadowmap_remap_tetrahedron_to_octahedral.draw({
989
+ encoder: context.encoder,
990
+ bindings: {
991
+ settings: settings,
992
+ tetra_faces: tetra_texture.obtainView(),
993
+ },
994
+ depthStencilAttachment: {
995
+ view: atlas.obtainView(),
996
+ depthLoadOp: "load",
997
+ depthStoreOp: "store",
998
+ },
999
+ viewport: [
1000
+ atlas_rect.x0,
1001
+ atlas_rect.y0,
1002
+ atlas_rect.width,
1003
+ atlas_rect.height,
1004
+ ]
1005
+ })
1006
+ }
1007
+
1008
+ const remap_data = {};
1009
+
1010
+ const remap_pass = graph.add("remap tetra to atlas", remap_data, remap_to_atlas);
1011
+
1012
+ remap_data.atlas = remap_pass.write(gr_atlas_texture);
1013
+ remap_data.tetra_texture = remap_pass.read(tetra_packed_texture);
1014
+
1015
+
1016
+ command_ctx.encodeGraph(graph);
1017
+ }
1018
+
1019
+ /**
1020
+ * Release the per-face GPU contexts (each view's uniform/HZB/volumetrics buffers + its camera
1021
+ * uniform) backing a map. The dedicated {@link shadow_view_manager} / {@link #cameras} caches
1022
+ * key on the View/Camera objects and have no GC path, so a map's contexts must be released
1023
+ * explicitly here or their GPU buffers leak for the context's lifetime. Most visible when the
1024
+ * point-shadow generator toggle rebuilds every point map (fresh Views each time), but it also
1025
+ * closes the ordinary eviction leak (light removed / stops casting / shadows disabled).
1026
+ *
1027
+ * In this context Views and Cameras are 1:1 per map (each map creates its own), so releasing a
1028
+ * view's camera alongside it can't strand a still-referenced camera.
1029
+ *
1030
+ * @param {Shadowmap} map
1031
+ */
1032
+ #release_map_gpu_contexts(map) {
1033
+ const views = map.views;
1034
+ for (let j = 0; j < views.length; j++) {
1035
+ const view = views[j];
1036
+ this.shadow_view_manager.release(view);
1037
+ this.#cameras.release(view.camera);
1038
+ }
1039
+ }
1040
+
1041
+ /**
1042
+ * Release a map's atlas rects + GPU side-resources. Caller is responsible for splicing it
1043
+ * out of {@link #registry}.
1044
+ *
1045
+ * @param {Shadowmap} map
1046
+ */
1047
+ #evict_map(map) {
1048
+ for (let j = 0; j < map.layout.length; j++) {
1049
+ this.atlas.remove(map.layout[j]);
1050
+ }
1051
+ if (map.pending_layout !== null) {
1052
+ for (let j = 0; j < map.pending_layout.length; j++) {
1053
+ this.atlas.remove(map.pending_layout[j]);
1054
+ }
1055
+ map.pending_layout = null;
1056
+ }
1057
+
1058
+ // free the per-view/-camera GPU contexts without this an evicted map's buffers leak
1059
+ this.#release_map_gpu_contexts(map);
1060
+
1061
+ map.light._gpu_shadowmap_id = -1;
1062
+ }
1063
+
1064
+ /**
1065
+ * Reconcile shadow allocations against the current scene state. Evicts maps for lights
1066
+ * that no longer cast shadows or have left the scene, allocates maps for new shadow-
1067
+ * casting lights, and reassigns per-type `_gpu_shadowmap_id` when the map ordering
1068
+ * shifts.
1069
+ *
1070
+ * Called from {@link GPULightCollection.update} every frame, including frames where
1071
+ * {@link LightCollection.version} hasn't moved — `light.casts_shadow` flips are not
1072
+ * tracked by version and are detected here.
1073
+ *
1074
+ * Runs as three linear passes — mark, reclaim, allocate — over {@link #registry}, which owns
1075
+ * the light -> map index and the marks. It used to scan the map array once per light to find
1076
+ * out whether that light already had one, which made the pass quadratic: 148us per frame at
1077
+ * 512 lights against 19us now, and 554us against 45us at 1024. See
1078
+ * benchmark/reconcile.spec.js.
1079
+ *
1080
+ * @returns {boolean} true when at least one map was added/evicted or at least one light's
1081
+ * per-type shadow_id moved. The caller uses this to decide whether the light
1082
+ * records need to be re-published (their `shadow_id` and CASTS_SHADOW_BIT may
1083
+ * have shifted).
1084
+ */
1085
+ process_lights() {
1086
+ const registry = this.#registry;
1087
+ const shadowmaps = registry.maps;
1088
+
1089
+ // When the renderer has shadows disabled, treat every map as evictable so the next
1090
+ // build clears CASTS_SHADOW_BIT on every light. Re-enabling reconstructs everything
1091
+ // from scratch on the following frame that's a one-frame catch-up cost, considered
1092
+ // an acceptable trade for the simpler invariant ("disabled" = no maps held).
1093
+ if (!this.enabled) {
1094
+ const had_maps = shadowmaps.length > 0;
1095
+ for (let i = shadowmaps.length - 1; i >= 0; i--) {
1096
+ this.#evict_map(shadowmaps[i]);
1097
+ }
1098
+ registry.clear();
1099
+ return had_maps;
1100
+ }
1101
+
1102
+ const scene = this.#scene;
1103
+ const lights = scene.lights.elements;
1104
+ let changed = false;
1105
+
1106
+ const use_tetra = this.use_tetrahedron_point_shadows;
1107
+ const newcomers = this.#reconcile_newcomers;
1108
+
1109
+ registry.begin_reconcile();
1110
+ newcomers.length = 0;
1111
+
1112
+ // Pass 1 — mark. One index lookup per light both answers "does this light already have a
1113
+ // map" and stamps that map as belonging to a light still in the scene, which is what lets
1114
+ // the reclamation below run without building a set of live lights first.
1115
+ //
1116
+ // A light that stopped casting is simply not marked; its map falls out in pass 2 on the
1117
+ // strength of the stale mark alone.
1118
+ for (let i = 0; i < lights.length; i++) {
1119
+ const light = lights[i];
1120
+
1121
+ if (!light.casts_shadow) {
1122
+ continue;
1123
+ }
1124
+
1125
+ const map = registry.mark_live(light);
1126
+
1127
+ if (map === undefined) {
1128
+ newcomers.push(light);
1129
+ continue;
1130
+ }
1131
+
1132
+ // A point map built by the other generator (cube vs tetra) must be rebuilt when the
1133
+ // flag flips — its face count and draw path no longer match. Pass 2 drops it on the
1134
+ // same test; queueing the rebuild here lands it in this pass rather than the next.
1135
+ if (light.isPointLight && Boolean(map.isTetrahedron) !== use_tetra) {
1136
+ newcomers.push(light);
1137
+ }
1138
+ }
1139
+
1140
+ // Pass 2 — reclaim. Anything not marked belongs to a light that left the scene or stopped
1141
+ // casting, and its atlas rect would otherwise stay pinned forever. Walk in reverse so
1142
+ // splice indices stay stable.
1143
+ for (let i = shadowmaps.length - 1; i >= 0; i--) {
1144
+ const map = shadowmaps[i];
1145
+ const light = map.light;
1146
+
1147
+ const generator_mismatch = light.isPointLight
1148
+ && Boolean(map.isTetrahedron) !== use_tetra;
1149
+
1150
+ if (registry.is_marked(map) && !generator_mismatch) {
1151
+ continue;
1152
+ }
1153
+ this.#evict_map(map);
1154
+ registry.remove_at(i);
1155
+ changed = true;
1156
+ }
1157
+
1158
+ const add_directional = (light) => {
1159
+ const map = new DirectionalLightShadowmap();
1160
+ map.light = light;
1161
+
1162
+ const bound = [];
1163
+ try {
1164
+ for (let j = 0; j < SHADOWMAP_CSM_CASCADE_COUNT; j++) {
1165
+ const resolution_index = Math.min(j, DEFAULT_SHADOWMAP_DIRECTIONAL_RESOLUTION.length - 1);
1166
+ const resolution = DEFAULT_SHADOWMAP_DIRECTIONAL_RESOLUTION[resolution_index];
1167
+
1168
+ const rect = this.bind(resolution);
1169
+ bound.push(rect);
1170
+ map.layout[j] = rect;
1171
+
1172
+ const camera = new OrthographicCamera();
1173
+ camera.update();
1174
+
1175
+ const view = new View(camera, scene);
1176
+ view.label = `Shadow Cascade ${j}`;
1177
+
1178
+ map.views[j] = view;
1179
+ }
1180
+ } catch (e) {
1181
+ // Partial bind: roll back any cascade rects already inserted so the atlas isn't
1182
+ // left holding orphans for a map that never got registered.
1183
+ for (let j = 0; j < bound.length; j++) {
1184
+ this.atlas.remove(bound[j]);
1185
+ }
1186
+ throw e;
1187
+ }
1188
+
1189
+ registry.add(map);
1190
+ };
1191
+
1192
+ const add_spot = (light) => {
1193
+ const map = new SpotLightShadowmap();
1194
+ map.light = light;
1195
+
1196
+ const rect = this.bind(DEFAULT_SHADOWMAP_LOCAL_RESOLUTION);
1197
+ map.layout[0] = rect;
1198
+
1199
+ try {
1200
+ const camera = new PerspectiveCamera();
1201
+ camera.update();
1202
+
1203
+ const view = new View(camera, scene);
1204
+ view.label = `Spot Shadow`;
1205
+
1206
+ map.views[0] = view;
1207
+ } catch (e) {
1208
+ this.atlas.remove(rect);
1209
+ throw e;
1210
+ }
1211
+
1212
+ registry.add(map);
1213
+ };
1214
+
1215
+ const add_point = (light) => {
1216
+
1217
+ // Generator picked by `use_tetra` above; both remap into one octahedral slot, they
1218
+ // only differ in how many face frusta feed that remap (tetra 4, cube 6).
1219
+ const map = use_tetra ? new TetrahedronPointLightShadowmap() : new PointLightShadowmap();
1220
+ map.light = light;
1221
+
1222
+ // single atlas rect holds the octahedral-encoded depth for all faces
1223
+ const rect = this.bind(DEFAULT_SHADOWMAP_LOCAL_RESOLUTION);
1224
+ map.layout[0] = rect;
1225
+
1226
+ const face_count = use_tetra ? POINT_SHADOW_TETRA_FACE_COUNT : 6;
1227
+
1228
+ for (let j = 0; j < face_count; j++) {
1229
+ const camera = new PerspectiveCamera();
1230
+ camera.update();
1231
+
1232
+ const view = new View(camera, scene);
1233
+ view.label = use_tetra ? `Point Shadow (tetra) face ${j}` : `Point Shadow face ${j}`;
1234
+
1235
+ map.views[j] = view;
1236
+ }
1237
+
1238
+ registry.add(map);
1239
+ };
1240
+
1241
+ const add_light = (light) => {
1242
+ // A light present twice in the scene's light array gets queued twice by pass 1, since
1243
+ // neither occurrence finds a map. LightCollection.add doesn't dedupe, so re-check here
1244
+ // rather than letting the registry's one-map-per-light assert fire every frame. Free
1245
+ // in the normal case — this loop only runs over lights that actually need a map.
1246
+ if (registry.get(light) !== undefined) {
1247
+ return;
1248
+ }
1249
+
1250
+ if (light.isDirectionalLight) {
1251
+ add_directional(light);
1252
+ changed = true;
1253
+ } else if (light.isSpotLight) {
1254
+ add_spot(light);
1255
+ changed = true;
1256
+ } else if (light.isPointLight) {
1257
+ add_point(light);
1258
+ changed = true;
1259
+ }
1260
+ };
1261
+
1262
+ // Pass 3 allocate. Only the lights pass 1 flagged, so on the overwhelming majority of
1263
+ // frames (nothing arrived, nothing left) this loop does not run at all. The old version
1264
+ // walked every light here and re-tested casts_shadow on each; pass 1 already answered that.
1265
+ for (let i = 0; i < newcomers.length; i++) {
1266
+ try {
1267
+ add_light(newcomers[i]);
1268
+ } catch (e) {
1269
+ console.error(e);
1270
+ }
1271
+ }
1272
+
1273
+ // Drop the references rather than leaving them until the next call — a light that gets
1274
+ // removed from the scene right after being added here would otherwise stay reachable from
1275
+ // this scratch array for a frame.
1276
+ newcomers.length = 0;
1277
+
1278
+ // Publish per-type shadowmap ids back onto the lights. Each shadow_* table is indexed
1279
+ // independently, so a point light's shadow_id points into shadow_point, a spot's into
1280
+ // shadow_spot, etc. Type-bucket order MUST match {@link #write_shadow_records} so each
1281
+ // light record's stored shadow_id lines up with the slot we'll write into the table.
1282
+ // When a map's slot index actually changes (eviction shifted neighbours up the array),
1283
+ // mark its record dirty so the next #write_shadow_records re-publishes it into the new
1284
+ // slot, and report `changed` so the caller re-publishes the light records too.
1285
+ let pc = 0;
1286
+ let sc = 0;
1287
+ let dc = 0;
1288
+ for (let i = 0; i < shadowmaps.length; i++) {
1289
+ const map = shadowmaps[i];
1290
+ const light = map.light;
1291
+
1292
+ let new_id;
1293
+ if (light.isPointLight) {
1294
+ new_id = pc++;
1295
+ } else if (light.isSpotLight) {
1296
+ new_id = sc++;
1297
+ } else if (light.isDirectionalLight) {
1298
+ new_id = dc++;
1299
+ } else {
1300
+ continue;
1301
+ }
1302
+
1303
+ if (light._gpu_shadowmap_id !== new_id) {
1304
+ light._gpu_shadowmap_id = new_id;
1305
+ map.metadata_changed = true;
1306
+ changed = true;
1307
+ }
1308
+ }
1309
+
1310
+ return changed;
1311
+ }
1312
+
1313
+ /**
1314
+ *
1315
+ * @param {number} resolution
1316
+ * @returns {AABB2}
1317
+ */
1318
+ bind(resolution) {
1319
+
1320
+ const rect = new AABB2(0, 0, resolution, resolution);
1321
+
1322
+ if (!this.atlas.add(rect)) {
1323
+ throw new Error(`Failed to add shadowmap to atlas`);
1324
+ }
1325
+
1326
+ return rect;
1327
+ }
1328
+
1329
+ destroy() {
1330
+ this.texture.destroy();
1331
+
1332
+ const maps = this.#registry.maps;
1333
+ for (let i = 0; i < maps.length; i++) {
1334
+ const map = maps[i];
1335
+ // release the per-view/-camera GPU contexts too — map.destroy() alone doesn't (the
1336
+ // buffers live in the view/camera manager caches, not on the map)
1337
+ this.#release_map_gpu_contexts(map);
1338
+ map.destroy();
1339
+ }
1340
+ this.#registry.clear();
1341
+ }
1342
+ }