@unikia/upe-renderer 0.186.110 → 0.187.11

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 (506) hide show
  1. package/README.md +5 -7
  2. package/UPSTREAM.md +4 -4
  3. package/build/three.core.js +853 -277
  4. package/build/three.module.js +1098 -1280
  5. package/build/three.tsl.js +41938 -686
  6. package/build/three.webgpu.js +10604 -49055
  7. package/build/three.webgpu.nodes.js +9249 -5788
  8. package/examples/jsm/Addons.js +2 -0
  9. package/examples/jsm/animation/CCDIKSolver.js +17 -5
  10. package/examples/jsm/controls/FirstPersonControls.js +1 -1
  11. package/examples/jsm/controls/OrbitControls.js +27 -14
  12. package/examples/jsm/csm/CSMShader.js +74 -3
  13. package/examples/jsm/curves/CurveExtras.js +4 -4
  14. package/examples/jsm/curves/NURBSCurve.js +14 -0
  15. package/examples/jsm/exporters/EXRExporter.js +673 -16
  16. package/examples/jsm/exporters/GLTFExporter.js +4 -4
  17. package/examples/jsm/exporters/KTX2Exporter.js +1 -1
  18. package/examples/jsm/exporters/OBJExporter.js +48 -5
  19. package/examples/jsm/exporters/PLYExporter.js +22 -11
  20. package/examples/jsm/generators/CityGenerator.js +3 -2
  21. package/examples/jsm/generators/city/SkyscraperGenerator.js +63 -21
  22. package/examples/jsm/gpgpu/BitonicSort.js +30 -23
  23. package/examples/jsm/helpers/VertexTangentsHelper.js +1 -1
  24. package/examples/jsm/inspector/Extension.js +1 -1
  25. package/examples/jsm/inspector/Inspector.js +6 -46
  26. package/examples/jsm/inspector/RendererInspector.js +43 -8
  27. package/examples/jsm/inspector/Storage.js +49 -0
  28. package/examples/jsm/inspector/extensions/color-grading/ColorGrading.js +32 -27
  29. package/examples/jsm/inspector/tabs/Parameters.js +5 -1
  30. package/examples/jsm/inspector/tabs/Performance.js +22 -7
  31. package/examples/jsm/inspector/tabs/Settings.js +1 -1
  32. package/examples/jsm/inspector/tabs/Timeline.js +83 -7
  33. package/examples/jsm/inspector/tabs/Viewer.js +25 -10
  34. package/examples/jsm/inspector/ui/Profiler.js +1 -1
  35. package/examples/jsm/inspector/ui/Style.js +21 -53
  36. package/examples/jsm/inspector/ui/Values.js +28 -3
  37. package/examples/jsm/libs/fflate.module.js +83 -56
  38. package/examples/jsm/libs/mikktspace.README.md +27 -0
  39. package/examples/jsm/libs/mikktspace.module.js +721 -109
  40. package/examples/jsm/lighting/ClusteredLighting.js +100 -2
  41. package/examples/jsm/lighting/LightMapper.js +295 -0
  42. package/examples/jsm/lighting/LightProbeGrid.js +242 -139
  43. package/examples/jsm/lighting/LightProbeGridWebGL.js +30 -14
  44. package/examples/jsm/lighting/vxgi/VXGIConeTracer.js +54 -5
  45. package/examples/jsm/lighting/vxgi/VXGIIrradiance.js +43 -0
  46. package/examples/jsm/lighting/vxgi/VXGINode.js +13 -56
  47. package/examples/jsm/lighting/vxgi/VXGISceneCollector.js +316 -1
  48. package/examples/jsm/lighting/vxgi/VXGIVolume.js +69 -59
  49. package/examples/jsm/lights/SunLight.js +9 -4
  50. package/examples/jsm/lights/SunLightNode.js +3 -3
  51. package/examples/jsm/lines/LineMaterial.js +26 -10
  52. package/examples/jsm/loaders/3DMLoader.js +10 -1
  53. package/examples/jsm/loaders/3MFLoader.js +5 -1
  54. package/examples/jsm/loaders/AMFLoader.js +2 -0
  55. package/examples/jsm/loaders/BVHLoader.js +2 -0
  56. package/examples/jsm/loaders/ColladaLoader.js +8 -0
  57. package/examples/jsm/loaders/DDSLoader.js +1 -1
  58. package/examples/jsm/loaders/DRACOLoader.js +5 -1
  59. package/examples/jsm/loaders/EXRLoader.js +120 -104
  60. package/examples/jsm/loaders/FBXLoader.js +4 -0
  61. package/examples/jsm/loaders/FontLoader.js +2 -0
  62. package/examples/jsm/loaders/GCodeLoader.js +5 -9
  63. package/examples/jsm/loaders/GLTFLoader.js +112 -31
  64. package/examples/jsm/loaders/GaussianSplatPLYLoader.js +2 -0
  65. package/examples/jsm/loaders/HDRCubeTextureLoader.js +3 -0
  66. package/examples/jsm/loaders/IESLoader.js +46 -12
  67. package/examples/jsm/loaders/JXLLoader.js +159 -0
  68. package/examples/jsm/loaders/KMZLoader.js +6 -0
  69. package/examples/jsm/loaders/KSPLATLoader.js +2 -0
  70. package/examples/jsm/loaders/KTX2Loader.js +49 -4
  71. package/examples/jsm/loaders/KTXLoader.js +5 -3
  72. package/examples/jsm/loaders/LDrawLoader.js +15 -22
  73. package/examples/jsm/loaders/LUT3dlLoader.js +5 -1
  74. package/examples/jsm/loaders/LUTCubeLoader.js +6 -2
  75. package/examples/jsm/loaders/LUTImageLoader.js +2 -0
  76. package/examples/jsm/loaders/LWOLoader.js +6 -0
  77. package/examples/jsm/loaders/MD2Loader.js +2 -0
  78. package/examples/jsm/loaders/MDDLoader.js +4 -0
  79. package/examples/jsm/loaders/MTLLoader.js +8 -0
  80. package/examples/jsm/loaders/MaterialXLoader.js +57 -5
  81. package/examples/jsm/loaders/NRRDLoader.js +4 -2
  82. package/examples/jsm/loaders/OBJLoader.js +10 -4
  83. package/examples/jsm/loaders/PCDLoader.js +6 -2
  84. package/examples/jsm/loaders/PDBLoader.js +5 -3
  85. package/examples/jsm/loaders/PLYLoader.js +39 -15
  86. package/examples/jsm/loaders/PVRLoader.js +2 -2
  87. package/examples/jsm/loaders/SPLATLoader.js +2 -0
  88. package/examples/jsm/loaders/SPZLoader.js +2 -0
  89. package/examples/jsm/loaders/STLLoader.js +3 -1
  90. package/examples/jsm/loaders/SVGLoader.js +27 -49
  91. package/examples/jsm/loaders/TDSLoader.js +4 -0
  92. package/examples/jsm/loaders/TGALoader.js +6 -5
  93. package/examples/jsm/loaders/TTFLoader.js +2 -0
  94. package/examples/jsm/loaders/USDLoader.js +14 -0
  95. package/examples/jsm/loaders/UltraHDRLoader.js +2 -0
  96. package/examples/jsm/loaders/VOXLoader.js +3 -0
  97. package/examples/jsm/loaders/VRMLLoader.js +4 -0
  98. package/examples/jsm/loaders/VTKLoader.js +2 -0
  99. package/examples/jsm/loaders/XYZLoader.js +2 -0
  100. package/examples/jsm/loaders/collada/ColladaComposer.js +15 -8
  101. package/examples/jsm/loaders/materialx/MaterialXDocument.js +610 -138
  102. package/examples/jsm/loaders/materialx/MaterialXInterfaceValidation.js +12 -95
  103. package/examples/jsm/loaders/materialx/MaterialXLog.js +4 -0
  104. package/examples/jsm/loaders/materialx/MaterialXNodeDefs.js +99 -0
  105. package/examples/jsm/loaders/materialx/MaterialXNodeInterfaceRegistry.js +760 -1
  106. package/examples/jsm/loaders/materialx/MaterialXNodeLibrary.js +222 -372
  107. package/examples/jsm/loaders/materialx/MaterialXSurfaceMappings.js +163 -259
  108. package/examples/jsm/loaders/materialx/MaterialXUtils.js +25 -0
  109. package/examples/jsm/loaders/materialx/compile/MaterialXCompileRegistry.js +161 -159
  110. package/examples/jsm/loaders/usd/USDAParser.js +56 -47
  111. package/examples/jsm/loaders/usd/USDComposer.js +25 -30
  112. package/examples/jsm/math/ColorConverter.js +7 -3
  113. package/examples/jsm/math/Lut.js +2 -2
  114. package/examples/jsm/math/OBB.js +3 -3
  115. package/examples/jsm/math/Octree.js +20 -17
  116. package/examples/jsm/objects/GaussianSplat.js +5 -4
  117. package/examples/jsm/objects/Reflector.js +5 -0
  118. package/examples/jsm/objects/SkyMesh.js +14 -7
  119. package/examples/jsm/objects/Water2Mesh.js +2 -2
  120. package/examples/jsm/physics/RapierPhysics.js +2 -2
  121. package/examples/jsm/pipelines/MeshletDrawingPipeline.js +5571 -0
  122. package/examples/jsm/pipelines/VisibilityBufferDrawingPipeline.js +954 -0
  123. package/examples/jsm/postprocessing/BokehPass.js +1 -1
  124. package/examples/jsm/postprocessing/GTAOPass.js +26 -18
  125. package/examples/jsm/renderers/SVGRenderer.js +3 -2
  126. package/examples/jsm/shaders/GTAOShader.js +7 -4
  127. package/examples/jsm/shaders/PoissonDenoiseShader.js +1 -10
  128. package/examples/jsm/tsl/WebGLNodesHandler.js +21 -9
  129. package/examples/jsm/tsl/display/AfterImageNode.js +3 -3
  130. package/examples/jsm/tsl/display/BilateralBlurNode.js +3 -3
  131. package/examples/jsm/tsl/display/BloomNode.js +3 -3
  132. package/examples/jsm/tsl/display/ChromaticAberrationNode.js +8 -19
  133. package/examples/jsm/tsl/display/DenoiseNode.js +5 -5
  134. package/examples/jsm/tsl/display/DepthOfFieldNode.js +3 -3
  135. package/examples/jsm/tsl/display/DotScreenNode.js +3 -3
  136. package/examples/jsm/tsl/display/FSR1Node.js +3 -3
  137. package/examples/jsm/tsl/display/FXAANode.js +3 -10
  138. package/examples/jsm/tsl/display/FilmNode.js +3 -3
  139. package/examples/jsm/tsl/display/GTAONode.js +33 -25
  140. package/examples/jsm/tsl/display/GaussianBlurNode.js +3 -3
  141. package/examples/jsm/tsl/display/GodraysNode.js +5 -19
  142. package/examples/jsm/tsl/display/ImportanceSampledEnvironment.js +8 -4
  143. package/examples/jsm/tsl/display/LensflareNode.js +3 -3
  144. package/examples/jsm/tsl/display/Lut3DNode.js +3 -3
  145. package/examples/jsm/tsl/display/OITPassNode.js +0 -11
  146. package/examples/jsm/tsl/display/OutlineNode.js +107 -5
  147. package/examples/jsm/tsl/display/PixelationPassNode.js +85 -193
  148. package/examples/jsm/tsl/display/RGBShiftNode.js +3 -3
  149. package/examples/jsm/tsl/display/RecurrentDenoiseNode.js +23 -126
  150. package/examples/jsm/tsl/display/SMAANode.js +3 -3
  151. package/examples/jsm/tsl/display/SSAONode.js +6 -20
  152. package/examples/jsm/tsl/display/SSGINode.js +36 -29
  153. package/examples/jsm/tsl/display/SSRNode.js +135 -63
  154. package/examples/jsm/tsl/display/SSSNode.js +6 -20
  155. package/examples/jsm/tsl/display/SharpenNode.js +3 -3
  156. package/examples/jsm/tsl/display/SobelOperatorNode.js +3 -3
  157. package/examples/jsm/tsl/display/TAAUNode.js +10 -42
  158. package/examples/jsm/tsl/display/TRAANode.js +11 -43
  159. package/examples/jsm/tsl/display/TemporalReprojectNode.js +42 -47
  160. package/examples/jsm/tsl/display/TransitionNode.js +3 -3
  161. package/examples/jsm/tsl/display/lensDirt.js +30 -0
  162. package/examples/jsm/tsl/lighting/ClusteredLightsNode.js +482 -121
  163. package/examples/jsm/tsl/lighting/DynamicLightsNode.js +41 -140
  164. package/examples/jsm/tsl/lighting/LightProbeGridNode.js +72 -25
  165. package/examples/jsm/tsl/lighting/data/AmbientLightDataNode.js +6 -0
  166. package/examples/jsm/tsl/lighting/data/DirectionalLightDataNode.js +6 -0
  167. package/examples/jsm/tsl/lighting/data/HemisphereLightDataNode.js +6 -0
  168. package/examples/jsm/tsl/lighting/data/PointLightDataNode.js +6 -0
  169. package/examples/jsm/tsl/lighting/data/SpotLightDataNode.js +6 -0
  170. package/examples/jsm/tsl/shadows/TileShadowNode.js +102 -14
  171. package/examples/jsm/tsl/utils/Raymarching.js +1 -1
  172. package/examples/jsm/tsl/utils/SoftParticles.js +5 -12
  173. package/examples/jsm/tsl/utils/SpecularHelpers.js +47 -70
  174. package/examples/jsm/tsl/utils/TAAUtils.js +6 -17
  175. package/examples/jsm/utils/BufferGeometryUtils.js +110 -88
  176. package/examples/jsm/utils/MeshletGeometryUtils.js +417 -0
  177. package/examples/jsm/utils/SceneOptimizer.js +2 -1
  178. package/examples/jsm/utils/SceneUtils.js +1 -1
  179. package/examples/jsm/utils/unwrapUVs.js +865 -0
  180. package/examples/jsm/webxr/XREstimatedLight.js +11 -4
  181. package/package.json +3 -3
  182. package/src/Three.TSL.js +2 -683
  183. package/src/Three.WebGPU.Nodes.js +2 -1
  184. package/src/Three.WebGPU.js +2 -1
  185. package/src/animation/AnimationAction.js +1 -1
  186. package/src/animation/AnimationUtils.js +1 -3
  187. package/src/animation/KeyframeTrack.js +19 -0
  188. package/src/cameras/ArrayCamera.js +12 -0
  189. package/src/cameras/CubeCamera.js +3 -3
  190. package/src/constants.js +1 -9
  191. package/src/core/BufferAttribute.js +57 -21
  192. package/src/core/BufferGeometry.js +5 -13
  193. package/src/core/Clock.js +1 -1
  194. package/src/core/InterleavedBuffer.js +28 -18
  195. package/src/core/InterleavedBufferAttribute.js +54 -20
  196. package/src/core/Object3D.js +19 -9
  197. package/src/core/Raycaster.js +2 -0
  198. package/src/core/RenderTarget.js +11 -5
  199. package/src/core/UniformsGroup.js +7 -3
  200. package/src/extras/PMREMGenerator.js +392 -685
  201. package/src/extras/core/Curve.js +4 -0
  202. package/src/extras/core/CurvePath.js +13 -0
  203. package/src/extras/core/ShapePath.js +1 -1
  204. package/src/geometries/EdgesGeometry.js +12 -10
  205. package/src/geometries/LatheGeometry.js +2 -0
  206. package/src/helpers/PolarGridHelper.js +1 -1
  207. package/src/lights/Light.js +11 -0
  208. package/src/lights/LightProbe.js +2 -0
  209. package/src/lights/LightShadow.js +2 -0
  210. package/src/loaders/AnimationLoader.js +2 -0
  211. package/src/loaders/AudioLoader.js +2 -0
  212. package/src/loaders/BufferGeometryLoader.js +2 -0
  213. package/src/loaders/CompressedTextureLoader.js +2 -0
  214. package/src/loaders/CubeTextureLoader.js +2 -0
  215. package/src/loaders/DataTextureLoader.js +2 -0
  216. package/src/loaders/FileLoader.js +2 -0
  217. package/src/loaders/ImageBitmapLoader.js +3 -1
  218. package/src/loaders/ImageLoader.js +1 -0
  219. package/src/loaders/Loader.js +47 -0
  220. package/src/loaders/MaterialLoader.js +2 -0
  221. package/src/loaders/ObjectLoader.js +27 -8
  222. package/src/loaders/TextureLoader.js +5 -3
  223. package/src/loaders/nodes/NodeLoader.js +2 -0
  224. package/src/materials/Material.js +21 -2
  225. package/src/materials/MeshDepthMaterial.js +1 -1
  226. package/src/materials/MeshDistanceMaterial.js +1 -1
  227. package/src/materials/MeshLambertMaterial.js +1 -1
  228. package/src/materials/MeshMatcapMaterial.js +1 -1
  229. package/src/materials/MeshNormalMaterial.js +1 -1
  230. package/src/materials/MeshPhongMaterial.js +1 -1
  231. package/src/materials/MeshPhysicalMaterial.js +90 -11
  232. package/src/materials/MeshStandardMaterial.js +1 -1
  233. package/src/materials/MeshToonMaterial.js +1 -1
  234. package/src/materials/nodes/Line2NodeMaterial.js +32 -10
  235. package/src/materials/nodes/MeshLambertNodeMaterial.js +13 -0
  236. package/src/materials/nodes/MeshPhongNodeMaterial.js +13 -0
  237. package/src/materials/nodes/MeshPhysicalNodeMaterial.js +40 -5
  238. package/src/materials/nodes/MeshToonNodeMaterial.js +13 -0
  239. package/src/materials/nodes/NodeMaterial.js +36 -16
  240. package/src/materials/nodes/NodeMaterialInstance.js +193 -0
  241. package/src/materials/nodes/NodeMaterials.js +1 -0
  242. package/src/materials/nodes/PointsNodeMaterial.js +1 -1
  243. package/src/materials/nodes/SpriteNodeMaterial.js +3 -3
  244. package/src/materials/nodes/manager/NodeMaterialObserver.js +191 -22
  245. package/src/math/Box3.js +2 -2
  246. package/src/math/Color.js +8 -8
  247. package/src/math/ColorManagement.js +2 -21
  248. package/src/math/FrustumArray.js +12 -0
  249. package/src/math/Interpolant.js +1 -1
  250. package/src/math/Line3.js +2 -4
  251. package/src/math/MathUtils.js +33 -46
  252. package/src/math/Matrix2.js +1 -1
  253. package/src/math/Matrix3.js +1 -1
  254. package/src/math/Matrix4.js +1 -1
  255. package/src/math/Triangle.js +2 -1
  256. package/src/math/Vector2.js +1 -1
  257. package/src/math/Vector3.js +1 -1
  258. package/src/math/Vector4.js +1 -1
  259. package/src/nodes/Nodes.js +1 -0
  260. package/src/nodes/TSL.js +2 -0
  261. package/src/nodes/accessors/Arrays.js +1 -1
  262. package/src/nodes/accessors/Batch.js +29 -23
  263. package/src/nodes/accessors/BufferAttributeNode.js +21 -1
  264. package/src/nodes/accessors/BuiltinNode.js +6 -0
  265. package/src/nodes/accessors/Camera.js +100 -93
  266. package/src/nodes/accessors/ClippingNode.js +17 -6
  267. package/src/nodes/accessors/CubeTextureNode.js +12 -24
  268. package/src/nodes/accessors/Instance.js +198 -160
  269. package/src/nodes/accessors/Lights.js +19 -6
  270. package/src/nodes/accessors/MaterialNode.js +33 -4
  271. package/src/nodes/accessors/Morph.js +81 -22
  272. package/src/nodes/accessors/Normal.js +0 -42
  273. package/src/nodes/accessors/Object3DNode.js +6 -0
  274. package/src/nodes/accessors/PointUVNode.js +6 -0
  275. package/src/nodes/accessors/ReferenceAttributeNode.js +279 -0
  276. package/src/nodes/accessors/ReferenceBaseNode.js +6 -0
  277. package/src/nodes/accessors/ReferenceNode.js +6 -0
  278. package/src/nodes/accessors/Skinning.js +57 -52
  279. package/src/nodes/accessors/StorageBufferNode.js +3 -12
  280. package/src/nodes/accessors/TextureBicubic.js +30 -27
  281. package/src/nodes/accessors/TextureNode.js +164 -78
  282. package/src/nodes/accessors/TextureSizeNode.js +6 -0
  283. package/src/nodes/accessors/UniformArrayNode.js +8 -15
  284. package/src/nodes/accessors/VelocityNode.js +13 -8
  285. package/src/nodes/code/CodeNode.js +6 -0
  286. package/src/nodes/code/ExpressionNode.js +6 -0
  287. package/src/nodes/code/FunctionCallNode.js +3 -3
  288. package/src/nodes/code/FunctionNode.js +15 -13
  289. package/src/nodes/core/ArrayNode.js +3 -3
  290. package/src/nodes/core/AssignNode.js +10 -9
  291. package/src/nodes/core/AttributeNode.js +39 -0
  292. package/src/nodes/core/BypassNode.js +6 -0
  293. package/src/nodes/core/ConstNode.js +31 -1
  294. package/src/nodes/core/ContextNode.js +11 -6
  295. package/src/nodes/core/IndexNode.js +6 -0
  296. package/src/nodes/core/InputNode.js +6 -0
  297. package/src/nodes/core/InspectorNode.js +17 -31
  298. package/src/nodes/core/IsolateNode.js +8 -0
  299. package/src/nodes/core/Node.js +367 -67
  300. package/src/nodes/core/NodeBuilder.js +664 -222
  301. package/src/nodes/core/NodeUtils.js +25 -0
  302. package/src/nodes/core/NodeVar.js +10 -1
  303. package/src/nodes/core/OutputStructNode.js +6 -0
  304. package/src/nodes/core/OverrideContextNode.js +38 -37
  305. package/src/nodes/core/PropertyNode.js +14 -0
  306. package/src/nodes/core/StackNode.js +33 -21
  307. package/src/nodes/core/StructNode.js +6 -0
  308. package/src/nodes/core/StructTypeNode.js +6 -0
  309. package/src/nodes/core/SubBuildNode.js +8 -0
  310. package/src/nodes/core/TempNode.js +10 -35
  311. package/src/nodes/core/UniformGroupNode.js +6 -0
  312. package/src/nodes/core/VarNode.js +43 -135
  313. package/src/nodes/core/VaryingNode.js +33 -2
  314. package/src/nodes/display/BlendModes.js +5 -40
  315. package/src/nodes/display/BumpMapNode.js +17 -7
  316. package/src/nodes/display/ColorAdjustment.js +22 -10
  317. package/src/nodes/display/ColorSpaceFunctions.js +2 -14
  318. package/src/nodes/display/ColorSpaceNode.js +3 -3
  319. package/src/nodes/display/FrontFacingNode.js +6 -0
  320. package/src/nodes/display/NormalMapNode.js +3 -3
  321. package/src/nodes/display/PassNode.js +75 -16
  322. package/src/nodes/display/RenderOutputNode.js +22 -5
  323. package/src/nodes/display/ScreenNode.js +6 -0
  324. package/src/nodes/display/ToneMappingFunctions.js +9 -51
  325. package/src/nodes/display/ToneMappingNode.js +3 -3
  326. package/src/nodes/display/ToonOutlinePassNode.js +66 -2
  327. package/src/nodes/display/ViewportDepthNode.js +34 -4
  328. package/src/nodes/functions/BSDF/BRDF_EON.js +65 -0
  329. package/src/nodes/functions/BSDF/BRDF_GGX.js +5 -3
  330. package/src/nodes/functions/BSDF/BRDF_Sheen.js +2 -16
  331. package/src/nodes/functions/BSDF/D_GGX.js +1 -8
  332. package/src/nodes/functions/BSDF/D_GGX_Anisotropic.js +7 -10
  333. package/src/nodes/functions/BSDF/LTC.js +20 -47
  334. package/src/nodes/functions/BSDF/Schlick_to_F0.js +1 -9
  335. package/src/nodes/functions/BSDF/V_GGX_SmithCorrelated.js +1 -9
  336. package/src/nodes/functions/BSDF/V_GGX_SmithCorrelated_Anisotropic.js +10 -13
  337. package/src/nodes/functions/PhysicalLightingModel.js +84 -51
  338. package/src/nodes/functions/VolumetricLightingModel.js +1 -1
  339. package/src/nodes/functions/material/getAlphaHashThreshold.js +3 -9
  340. package/src/nodes/functions/material/getParallaxCorrectNormal.js +10 -11
  341. package/src/nodes/functions/material/getRoughness.js +3 -1
  342. package/src/nodes/geometry/RangeNode.js +52 -69
  343. package/src/nodes/gpgpu/AtomicFunctionNode.js +11 -6
  344. package/src/nodes/gpgpu/BarrierNode.js +6 -0
  345. package/src/nodes/gpgpu/ComputeBuiltinNode.js +6 -0
  346. package/src/nodes/gpgpu/ComputeNode.js +6 -0
  347. package/src/nodes/gpgpu/SubgroupFunctionNode.js +38 -29
  348. package/src/nodes/gpgpu/WorkgroupInfoNode.js +6 -0
  349. package/src/nodes/lighting/AnalyticLightNode.js +7 -14
  350. package/src/nodes/lighting/BasicEnvironmentNode.js +5 -1
  351. package/src/nodes/lighting/EnvironmentNode.js +58 -8
  352. package/src/nodes/lighting/IESSpotLightNode.js +19 -4
  353. package/src/nodes/lighting/LightProbeNode.js +2 -1
  354. package/src/nodes/lighting/LightUtils.js +2 -2
  355. package/src/nodes/lighting/LightingNode.js +6 -0
  356. package/src/nodes/lighting/LightsNode.js +26 -32
  357. package/src/nodes/lighting/PointShadowNode.js +92 -48
  358. package/src/nodes/lighting/ProjectorLightNode.js +1 -1
  359. package/src/nodes/lighting/RectAreaLightNode.js +13 -14
  360. package/src/nodes/lighting/ShadowBaseNode.js +9 -17
  361. package/src/nodes/lighting/ShadowFilterNode.js +26 -3
  362. package/src/nodes/lighting/ShadowNode.js +13 -11
  363. package/src/nodes/materialx/MaterialXColor.js +2 -14
  364. package/src/nodes/materialx/MaterialXColorTransform.js +1 -7
  365. package/src/nodes/materialx/MaterialXNodes.js +8 -11
  366. package/src/nodes/materialx/MaterialXNoise.js +195 -454
  367. package/src/nodes/math/BitcastNode.js +5 -5
  368. package/src/nodes/math/BitcountNode.js +18 -46
  369. package/src/nodes/math/ConditionalNode.js +92 -22
  370. package/src/nodes/math/Hash.js +1 -1
  371. package/src/nodes/math/MathNode.js +86 -64
  372. package/src/nodes/math/MathUtils.js +2 -2
  373. package/src/nodes/math/OperatorNode.js +42 -59
  374. package/src/nodes/math/PackFloatNode.js +9 -9
  375. package/src/nodes/math/Packed4x8IntegerNode.js +20 -52
  376. package/src/nodes/math/TriNoise3D.js +3 -23
  377. package/src/nodes/math/UnpackFloatNode.js +9 -9
  378. package/src/nodes/pmrem/PMREMNode.js +38 -75
  379. package/src/nodes/pmrem/PMREMUtils.js +108 -272
  380. package/src/nodes/procedural/Checker.js +1 -1
  381. package/src/nodes/tsl/TSLCore.js +199 -74
  382. package/src/nodes/utils/ArrayElementNode.js +19 -1
  383. package/src/nodes/utils/CubeMapNode.js +3 -3
  384. package/src/nodes/utils/DebugNode.js +2 -2
  385. package/src/nodes/utils/EventNode.js +6 -0
  386. package/src/nodes/utils/FlipNode.js +3 -3
  387. package/src/nodes/utils/FunctionOverloadingNode.js +7 -1
  388. package/src/nodes/utils/JoinNode.js +3 -3
  389. package/src/nodes/utils/LoopNode.js +14 -9
  390. package/src/nodes/utils/MemberNode.js +18 -0
  391. package/src/nodes/utils/PostProcessingUtils.js +20 -25
  392. package/src/nodes/utils/RTTNode.js +2 -0
  393. package/src/nodes/utils/ReflectorNode.js +24 -0
  394. package/src/nodes/utils/RotateNode.js +3 -3
  395. package/src/nodes/utils/SampleNode.js +6 -0
  396. package/src/nodes/utils/SetNode.js +3 -3
  397. package/src/nodes/utils/SplitNode.js +6 -0
  398. package/src/nodes/utils/UVUtils.js +2 -2
  399. package/src/objects/BatchedMesh.js +146 -15
  400. package/src/objects/InstancedMesh.js +12 -4
  401. package/src/objects/Line.js +20 -0
  402. package/src/objects/Mesh.js +8 -0
  403. package/src/objects/Points.js +20 -0
  404. package/src/objects/Skeleton.js +26 -0
  405. package/src/objects/SkinnedMesh.js +3 -3
  406. package/src/objects/Sprite.js +10 -0
  407. package/src/renderers/WebGLRenderer.js +104 -70
  408. package/src/renderers/common/Animation.js +5 -1
  409. package/src/renderers/common/Attributes.js +67 -57
  410. package/src/renderers/common/Backend.js +78 -3
  411. package/src/renderers/common/BindGroup.js +11 -0
  412. package/src/renderers/common/Binding.js +0 -11
  413. package/src/renderers/common/Bindings.js +116 -10
  414. package/src/renderers/common/Buffer.js +1 -1
  415. package/src/renderers/common/ClippingContext.js +12 -1
  416. package/src/renderers/common/ComputePipeline.js +17 -1
  417. package/src/renderers/common/DirectRenderPipeline.js +7 -3
  418. package/src/renderers/common/Geometries.js +170 -132
  419. package/src/renderers/common/Info.js +1 -1
  420. package/src/renderers/common/InspectorBase.js +5 -5
  421. package/src/renderers/common/Lighting.js +59 -0
  422. package/src/renderers/common/Pipelines.js +26 -12
  423. package/src/renderers/common/ProgrammableStage.js +1 -17
  424. package/src/renderers/common/RenderContext.js +21 -0
  425. package/src/renderers/common/RenderContexts.js +13 -4
  426. package/src/renderers/common/RenderObject.js +111 -35
  427. package/src/renderers/common/RenderObjects.js +37 -19
  428. package/src/renderers/common/Renderer.js +537 -295
  429. package/src/renderers/common/RendererUtils.js +15 -0
  430. package/src/renderers/common/StandardDrawingPipeline.js +343 -0
  431. package/src/renderers/common/Storage3DTexture.js +11 -0
  432. package/src/renderers/common/StorageArrayTexture.js +10 -0
  433. package/src/renderers/common/StorageTexture.js +0 -9
  434. package/src/renderers/common/Textures.js +144 -37
  435. package/src/renderers/common/UniformsGroup.js +19 -0
  436. package/src/renderers/common/XRManager.js +10 -91
  437. package/src/renderers/common/extras/PMREMGenerator.js +298 -576
  438. package/src/renderers/common/nodes/NodeBuilderState.js +7 -5
  439. package/src/renderers/common/nodes/NodeLibrary.js +11 -35
  440. package/src/renderers/common/nodes/NodeManager.js +80 -31
  441. package/src/renderers/common/nodes/NodeSampledTexture.js +14 -0
  442. package/src/renderers/common/nodes/NodeSampler.js +14 -0
  443. package/src/renderers/common/nodes/NodeStorageBuffer.js +1 -1
  444. package/src/renderers/common/nodes/NodeUniformBuffer.js +1 -14
  445. package/src/renderers/shaders/ShaderChunk/envmap_common_pars_fragment.glsl.js +13 -4
  446. package/src/renderers/shaders/ShaderChunk/envmap_physical_pars_fragment.glsl.js +8 -8
  447. package/src/renderers/shaders/ShaderChunk/lightprobes_pars_fragment.glsl.js +23 -11
  448. package/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js +21 -2
  449. package/src/renderers/shaders/ShaderChunk/lights_fragment_maps.glsl.js +1 -1
  450. package/src/renderers/shaders/ShaderChunk/lights_physical_fragment.glsl.js +18 -2
  451. package/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +104 -7
  452. package/src/renderers/shaders/ShaderChunk/morphcolor_vertex.glsl.js +11 -2
  453. package/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js +36 -20
  454. package/src/renderers/shaders/ShaderChunk/uv_pars_fragment.glsl.js +5 -0
  455. package/src/renderers/shaders/ShaderChunk/uv_pars_vertex.glsl.js +6 -0
  456. package/src/renderers/shaders/ShaderChunk/uv_vertex.glsl.js +5 -0
  457. package/src/renderers/shaders/ShaderChunk.js +0 -2
  458. package/src/renderers/shaders/ShaderLib/backgroundCube.glsl.js +4 -18
  459. package/src/renderers/shaders/ShaderLib/meshlambert.glsl.js +0 -1
  460. package/src/renderers/shaders/ShaderLib/meshphong.glsl.js +0 -1
  461. package/src/renderers/shaders/ShaderLib/meshphysical.glsl.js +8 -1
  462. package/src/renderers/shaders/ShaderLib.js +3 -0
  463. package/src/renderers/shaders/UniformsLib.js +4 -5
  464. package/src/renderers/webgl/WebGLBackground.js +3 -3
  465. package/src/renderers/webgl/WebGLBindingStates.js +4 -2
  466. package/src/renderers/webgl/WebGLCapabilities.js +7 -1
  467. package/src/renderers/webgl/WebGLEnvironments.js +2 -2
  468. package/src/renderers/webgl/WebGLGeometries.js +42 -14
  469. package/src/renderers/webgl/WebGLMaterials.js +15 -2
  470. package/src/renderers/webgl/WebGLOutput.js +37 -1
  471. package/src/renderers/webgl/WebGLProgram.js +13 -54
  472. package/src/renderers/webgl/WebGLPrograms.js +22 -8
  473. package/src/renderers/webgl/WebGLRenderStates.js +11 -8
  474. package/src/renderers/webgl/WebGLShadowMap.js +6 -4
  475. package/src/renderers/webgl/WebGLState.js +10 -6
  476. package/src/renderers/webgl/WebGLTextures.js +129 -77
  477. package/src/renderers/webgl/WebGLUniforms.js +44 -6
  478. package/src/renderers/webgl/WebGLUniformsGroups.js +75 -69
  479. package/src/renderers/webgl-fallback/WebGLBackend.js +206 -278
  480. package/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +154 -20
  481. package/src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js +45 -42
  482. package/src/renderers/webgl-fallback/utils/WebGLState.js +29 -4
  483. package/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js +129 -49
  484. package/src/renderers/webgl-fallback/utils/WebGLVertexArrayUtils.js +346 -0
  485. package/src/renderers/webgpu/WebGPUBackend.js +140 -82
  486. package/src/renderers/webgpu/WebGPURenderer.js +1 -1
  487. package/src/renderers/webgpu/nodes/BasicNodeLibrary.js +10 -10
  488. package/src/renderers/webgpu/nodes/StandardNodeLibrary.js +10 -10
  489. package/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +135 -60
  490. package/src/renderers/webgpu/utils/WebGPUAttributeUtils.js +128 -55
  491. package/src/renderers/webgpu/utils/WebGPUBindingUtils.js +1 -1
  492. package/src/renderers/webgpu/utils/WebGPUPipelineUtils.js +15 -12
  493. package/src/renderers/webgpu/utils/WebGPUTextureUtils.js +55 -19
  494. package/src/renderers/webgpu/utils/WebGPUUtils.js +20 -3
  495. package/src/renderers/webxr/WebXRController.js +3 -3
  496. package/src/renderers/webxr/WebXRManager.js +5 -90
  497. package/src/scenes/Scene.js +4 -4
  498. package/src/textures/CompressedTexture.js +2 -2
  499. package/src/textures/DepthTexture.js +2 -2
  500. package/src/textures/FramebufferTexture.js +11 -0
  501. package/src/textures/Texture.js +29 -1
  502. package/src/textures/TextureSource.js +1 -1
  503. package/src/upe.js +1 -1
  504. package/src/utils.js +4 -4
  505. package/examples/jsm/libs/surfaceNet.js +0 -201
  506. package/src/renderers/shaders/ShaderChunk/cube_uv_reflection_fragment.glsl.js +0 -186
@@ -1,20 +1,30 @@
1
- import { DataTexture, FloatType, RGBAFormat, Vector2, Vector3, LightsNode, NodeUpdateType } from 'three/webgpu';
1
+ import { DataTexture, FloatType, RGBAFormat, Vector2, Vector3, LightsNode, NodeUpdateType, NodeUtils, PointLightNode, SpotLightNode } from 'three/webgpu';
2
2
 
3
3
  import {
4
- attributeArray, nodeProxy, int, float, vec3, vec4, ivec2, ivec4, uniform, Break, Loop, positionView,
5
- Fn, If, Return, textureLoad, instanceIndex, screenCoordinate, directPointLight,
4
+ instancedArray, nodeProxy, int, float, vec3, vec4, ivec2, ivec4, uniform, Break, Loop, positionView,
5
+ Fn, If, textureLoad, instanceIndex, screenCoordinate, directPointLight,
6
6
  renderGroup,
7
- min, max, pow, log, clamp, dot
7
+ min, max, pow, log, clamp, dot, smoothstep
8
8
  } from 'three/tsl';
9
9
 
10
10
  const _vector3 = /*@__PURE__*/ new Vector3();
11
+ const _direction = /*@__PURE__*/ new Vector3();
12
+ const _targetPosition = /*@__PURE__*/ new Vector3();
11
13
  const _size = /*@__PURE__*/ new Vector2();
12
14
 
15
+ // light data texture rows
16
+
17
+ const POSITION_ROW = 0; // world position, cutoff distance
18
+ const COLOR_ROW = 1; // color * intensity, decay
19
+ const SPOT_ROW = 2; // scaled world spot direction, scaled cone cosine
20
+ const BOUNDS_ROW = 3; // world bounding sphere center, radius ( negative if unbounded )
21
+ const ROWS = 4;
22
+
13
23
  /**
14
24
  * A custom version of `LightsNode` implementing Forward+ clustered shading:
15
25
  * the view frustum is subdivided into a 3D grid of clusters (X × Y screen tiles
16
26
  * times an exponentially-spaced set of Z depth slices), and each cluster holds
17
- * only the point lights whose spheres intersect it. At shading time each fragment
27
+ * only the point and spot lights whose bounding spheres intersect it. At shading time each fragment
18
28
  * looks up its cluster and loops over just that cluster's lights. Unlike 2D tiled
19
29
  * lighting, clustered shading culls lights that share screen pixels but lie at
20
30
  * different depths — suitable for 3D scenes with real depth complexity.
@@ -33,7 +43,7 @@ class ClusteredLightsNode extends LightsNode {
33
43
  /**
34
44
  * Constructs a new clustered lights node.
35
45
  *
36
- * @param {number} [maxLights=1024] - Maximum number of point lights.
46
+ * @param {number} [maxLights=1024] - Maximum number of point and spot lights.
37
47
  * @param {number} [tileSize=32] - Screen tile size in pixels (cluster XY size).
38
48
  * @param {number} [zSlices=24] - Number of exponential depth slices.
39
49
  * @param {number} [maxLightsPerCluster=64] - Per-cluster light-list capacity.
@@ -42,6 +52,15 @@ class ClusteredLightsNode extends LightsNode {
42
52
 
43
53
  super();
44
54
 
55
+ /**
56
+ * This flag can be used for type testing.
57
+ *
58
+ * @type {boolean}
59
+ * @readonly
60
+ * @default true
61
+ */
62
+ this.isClusteredLightsNode = true;
63
+
45
64
  this.materialLights = [];
46
65
  this.clusteredLights = [];
47
66
 
@@ -53,12 +72,16 @@ class ClusteredLightsNode extends LightsNode {
53
72
  this._chunksPerCluster = Math.ceil( maxLightsPerCluster / 4 );
54
73
 
55
74
  this._bufferSize = null;
75
+ this._isWebGLBackend = false;
76
+ this._needsRebuild = false;
77
+ this._invocationsPerCluster = 1;
56
78
  this._lightIndexes = null;
79
+ this._renderer = null;
57
80
  this._screenClusterIndex = null;
58
81
  this._compute = null;
59
82
 
60
- const lightsData = new Float32Array( maxLights * 4 * 2 );
61
- this._lightsTexture = new DataTexture( lightsData, lightsData.length / 8, 2, RGBAFormat, FloatType );
83
+ const lightsData = new Float32Array( maxLights * 4 * ROWS );
84
+ this._lightsTexture = new DataTexture( lightsData, maxLights, ROWS, RGBAFormat, FloatType );
62
85
 
63
86
  // Per Z-slice light range for Z-culling (CPU-sorted, uploaded each frame)
64
87
 
@@ -83,30 +106,44 @@ class ClusteredLightsNode extends LightsNode {
83
106
 
84
107
  }
85
108
 
109
+ /**
110
+ * Overwrites the default {@link LightsNode#customCacheKey} implementation by
111
+ * including the node identity and resource version into the cache key.
112
+ *
113
+ * @return {number} The custom cache key.
114
+ */
86
115
  customCacheKey() {
87
116
 
88
- return ( this._compute ? this._compute.getCacheKey() : 0 ) + super.customCacheKey();
117
+ // First use allocates the grid after material cache keys have been computed.
118
+ // Keep that allocation stable, but never share bindings between lights nodes.
119
+ return NodeUtils.hash( this.id, this.version, super.customCacheKey() );
89
120
 
90
121
  }
91
122
 
92
- updateLightsTexture( camera ) {
123
+ /**
124
+ * Updates the light data and the light ranges of the depth slices.
125
+ *
126
+ * @private
127
+ * @param {Camera} camera - The camera.
128
+ */
129
+ _updateLightsTexture( camera ) {
93
130
 
94
131
  const { _lightsTexture: lightsTexture, clusteredLights } = this;
95
132
 
96
133
  const data = lightsTexture.image.data;
97
134
  const lineSize = lightsTexture.image.width * 4;
98
- const count = clusteredLights.length;
135
+ const count = Math.min( clusteredLights.length, this.maxLights );
99
136
 
100
137
  this._lightsCount.value = count;
101
138
 
102
- // Sort lights by view-space depth for Z-culling
139
+ // Sort lights by the view-space depth of their bounding spheres for Z-culling
103
140
 
104
141
  const viewZ = this._lightViewZ;
105
142
  const order = this._lightSortOrder;
106
143
 
107
144
  for ( let i = 0; i < count; i ++ ) {
108
145
 
109
- _vector3.setFromMatrixPosition( clusteredLights[ i ].matrixWorld );
146
+ this._getBoundingSphereCenter( clusteredLights[ i ], _vector3 );
110
147
  _vector3.applyMatrix4( camera.matrixWorldInverse );
111
148
  viewZ[ i ] = _vector3.z;
112
149
  order[ i ] = i;
@@ -122,19 +159,60 @@ class ClusteredLightsNode extends LightsNode {
122
159
 
123
160
  const light = clusteredLights[ order[ i ] ];
124
161
 
162
+ const offset = i * 4;
163
+
125
164
  _vector3.setFromMatrixPosition( light.matrixWorld );
126
165
 
127
- const offset = i * 4;
166
+ let rowOffset = lineSize * POSITION_ROW + offset;
167
+
168
+ data[ rowOffset + 0 ] = _vector3.x;
169
+ data[ rowOffset + 1 ] = _vector3.y;
170
+ data[ rowOffset + 2 ] = _vector3.z;
171
+ data[ rowOffset + 3 ] = light.distance;
172
+
173
+ rowOffset = lineSize * COLOR_ROW + offset;
174
+
175
+ data[ rowOffset + 0 ] = light.color.r * light.intensity;
176
+ data[ rowOffset + 1 ] = light.color.g * light.intensity;
177
+ data[ rowOffset + 2 ] = light.color.b * light.intensity;
178
+ data[ rowOffset + 3 ] = light.decay;
179
+
180
+ // The spot attenuation smoothstep( coneCos, penumbraCos, dot( L, direction ) ) is stored
181
+ // as smoothstep( 0, 1, dot( L, direction * scale ) - coneCos * scale ) with
182
+ // scale = 1 / ( penumbraCos - coneCos ). Point lights store a constant attenuation of 1.
183
+
184
+ rowOffset = lineSize * SPOT_ROW + offset;
185
+
186
+ if ( light.isSpotLight === true ) {
187
+
188
+ const coneCos = Math.cos( light.angle );
189
+ const penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) );
190
+ const scale = 1 / Math.max( penumbraCos - coneCos, 1e-4 );
191
+
192
+ this._getSpotDirection( light, _direction ).multiplyScalar( scale );
193
+
194
+ data[ rowOffset + 0 ] = _direction.x;
195
+ data[ rowOffset + 1 ] = _direction.y;
196
+ data[ rowOffset + 2 ] = _direction.z;
197
+ data[ rowOffset + 3 ] = coneCos * scale;
198
+
199
+ } else {
128
200
 
129
- data[ offset + 0 ] = _vector3.x;
130
- data[ offset + 1 ] = _vector3.y;
131
- data[ offset + 2 ] = _vector3.z;
132
- data[ offset + 3 ] = light.distance;
201
+ data[ rowOffset + 0 ] = 0;
202
+ data[ rowOffset + 1 ] = 0;
203
+ data[ rowOffset + 2 ] = 0;
204
+ data[ rowOffset + 3 ] = - 1;
133
205
 
134
- data[ lineSize + offset + 0 ] = light.color.r * light.intensity;
135
- data[ lineSize + offset + 1 ] = light.color.g * light.intensity;
136
- data[ lineSize + offset + 2 ] = light.color.b * light.intensity;
137
- data[ lineSize + offset + 3 ] = light.decay;
206
+ }
207
+
208
+ this._getBoundingSphereCenter( light, _vector3 );
209
+
210
+ rowOffset = lineSize * BOUNDS_ROW + offset;
211
+
212
+ data[ rowOffset + 0 ] = _vector3.x;
213
+ data[ rowOffset + 1 ] = _vector3.y;
214
+ data[ rowOffset + 2 ] = _vector3.z;
215
+ data[ rowOffset + 3 ] = this._getBoundingSphereRadius( light );
138
216
 
139
217
  }
140
218
 
@@ -160,10 +238,10 @@ class ClusteredLightsNode extends LightsNode {
160
238
  for ( let i = 0; i < count; i ++ ) {
161
239
 
162
240
  const vz = viewZ[ order[ i ] ];
163
- const r = clusteredLights[ order[ i ] ].distance;
164
- const radius = r > 0 ? r : far;
241
+ const r = data[ lineSize * BOUNDS_ROW + i * 4 + 3 ];
242
+ const radius = r >= 0 ? r : far;
165
243
 
166
- // Light sphere Z: [vz - radius, vz + radius]
244
+ // Bounding sphere Z: [vz - radius, vz + radius]
167
245
  // Slice Z: [sliceFar, sliceNear] (both negative, sliceFar < sliceNear)
168
246
  if ( vz + radius >= sliceFar && vz - radius <= sliceNear ) {
169
247
 
@@ -190,13 +268,86 @@ class ClusteredLightsNode extends LightsNode {
190
268
 
191
269
  }
192
270
 
271
+ /**
272
+ * Returns the normalized world-space direction pointing from the spot light's
273
+ * target to the spot light.
274
+ *
275
+ * @private
276
+ * @param {SpotLight} light - The spot light.
277
+ * @param {Vector3} target - The target vector.
278
+ * @return {Vector3} The direction.
279
+ */
280
+ _getSpotDirection( light, target ) {
281
+
282
+ _targetPosition.setFromMatrixPosition( light.target.matrixWorld );
283
+
284
+ return target.setFromMatrixPosition( light.matrixWorld ).sub( _targetPosition ).normalize();
285
+
286
+ }
287
+
288
+ /**
289
+ * Returns the world-space center of the light's bounding sphere. For spot lights,
290
+ * this is the center of the smallest sphere enclosing the cone.
291
+ *
292
+ * @private
293
+ * @param {PointLight|SpotLight} light - The light.
294
+ * @param {Vector3} target - The target vector.
295
+ * @return {Vector3} The bounding sphere center.
296
+ */
297
+ _getBoundingSphereCenter( light, target ) {
298
+
299
+ target.setFromMatrixPosition( light.matrixWorld );
300
+
301
+ if ( light.isSpotLight === true && light.distance > 0 ) {
302
+
303
+ const angle = Math.min( light.angle, Math.PI / 2 );
304
+ const offset = angle > Math.PI / 4 ? light.distance * Math.cos( angle ) : light.distance / ( 2 * Math.cos( angle ) );
305
+
306
+ // _getSpotDirection() points away from the cone, so step backwards
307
+
308
+ target.addScaledVector( this._getSpotDirection( light, _direction ), - offset );
309
+
310
+ }
311
+
312
+ return target;
313
+
314
+ }
315
+
316
+ /**
317
+ * Returns the radius of the light's bounding sphere.
318
+ *
319
+ * @private
320
+ * @param {PointLight|SpotLight} light - The light.
321
+ * @return {number} The bounding sphere radius. `-1` if the light has no cutoff distance.
322
+ */
323
+ _getBoundingSphereRadius( light ) {
324
+
325
+ if ( light.distance <= 0 ) return - 1;
326
+
327
+ if ( light.isSpotLight === true ) {
328
+
329
+ const angle = Math.min( light.angle, Math.PI / 2 );
330
+
331
+ return angle > Math.PI / 4 ? light.distance * Math.sin( angle ) : light.distance / ( 2 * Math.cos( angle ) );
332
+
333
+ }
334
+
335
+ return light.distance;
336
+
337
+ }
338
+
339
+ /**
340
+ * Updates the light data and assigns the lights to the clusters.
341
+ *
342
+ * @param {NodeFrame} frame - A reference to the current node frame.
343
+ */
193
344
  updateBefore( frame ) {
194
345
 
195
346
  const { renderer, camera } = frame;
196
347
 
197
- this.updateProgram( renderer );
348
+ this._initGrid( renderer );
198
349
 
199
- this.updateLightsTexture( camera );
350
+ this._updateLightsTexture( camera );
200
351
 
201
352
  this._cameraNear.value = camera.near;
202
353
  this._cameraFar.value = camera.far;
@@ -207,6 +358,15 @@ class ClusteredLightsNode extends LightsNode {
207
358
 
208
359
  }
209
360
 
361
+ /**
362
+ * Configures this node with an array of lights. Point and spot lights without
363
+ * shadows are clustered (except lights with a `colorNode`, spot lights with a
364
+ * projected `map` and light types with their own light node, like `IESSpotLight`
365
+ * or `ProjectorLight`), all other lights use the default lighting.
366
+ *
367
+ * @param {Array<Light>} lights - An array of lights.
368
+ * @return {ClusteredLightsNode} A reference to this node.
369
+ */
210
370
  setLights( lights ) {
211
371
 
212
372
  const { clusteredLights, materialLights } = this;
@@ -216,7 +376,7 @@ class ClusteredLightsNode extends LightsNode {
216
376
 
217
377
  for ( const light of lights ) {
218
378
 
219
- if ( light.isPointLight === true && light.castShadow !== true ) {
379
+ if ( this._isClusteredLight( light ) ) {
220
380
 
221
381
  clusteredLights[ clusteredIndex ++ ] = light;
222
382
 
@@ -235,19 +395,48 @@ class ClusteredLightsNode extends LightsNode {
235
395
 
236
396
  }
237
397
 
238
- getBuiltinLights() {
398
+ /**
399
+ * Returns `true` if the given light can be clustered.
400
+ *
401
+ * @private
402
+ * @param {Light} light - The light.
403
+ * @return {boolean} Whether the light is clustered or not.
404
+ */
405
+ _isClusteredLight( light ) {
239
406
 
240
- return this.materialLights;
407
+ // Shadows, color nodes (any light) and projected maps (spot lights) are not supported.
408
+
409
+ if ( light.castShadow === true || light.colorNode ) return false;
410
+
411
+ // Only lights shaded by the plain point and spot light nodes are clustered. Subclasses
412
+ // registered with their own light node (e.g. IESSpotLight, ProjectorLight) keep the
413
+ // default lighting, since clustering would shade them as plain spot lights.
414
+
415
+ if ( light._lightNode === PointLightNode ) return true;
416
+
417
+ return light._lightNode === SpotLightNode && ! light.map;
241
418
 
242
419
  }
243
420
 
244
- getBlock() {
421
+ /**
422
+ * Returns the lights that are not clustered.
423
+ *
424
+ * @return {Array<Light>} The lights using the default lighting.
425
+ */
426
+ getBuiltinLights() {
245
427
 
246
- return this._lightIndexes.element( this._screenClusterIndex.mul( int( this._chunksPerCluster ) ) );
428
+ return this.materialLights;
247
429
 
248
430
  }
249
431
 
250
- getTile( element ) {
432
+ /**
433
+ * Returns the light index stored at the given slot of the fragment's cluster.
434
+ *
435
+ * @private
436
+ * @param {number|Node<int>} element - The slot.
437
+ * @return {Node<int>} The 1-based light index. `0` marks the end of the list.
438
+ */
439
+ _getTile( element ) {
251
440
 
252
441
  element = int( element );
253
442
 
@@ -259,6 +448,12 @@ class ClusteredLightsNode extends LightsNode {
259
448
 
260
449
  }
261
450
 
451
+ /**
452
+ * Returns the number of lights in the fragment's cluster. Useful for debugging.
453
+ *
454
+ * @param {Node<int>} zSliceNode - The depth slice to inspect. A negative value selects the fragment's own cluster.
455
+ * @return {Node<int>} The light count.
456
+ */
262
457
  getClusterLightCount( zSliceNode ) {
263
458
 
264
459
  const getCount = Fn( ( [ zSliceNode ] ) => {
@@ -308,32 +503,66 @@ class ClusteredLightsNode extends LightsNode {
308
503
 
309
504
  }
310
505
 
311
- getLightData( index ) {
506
+ /**
507
+ * Returns the shading data of the light at the given index.
508
+ *
509
+ * @private
510
+ * @param {number|Node<int>} index - The light index.
511
+ * @return {Object} The light's view position, distance, color, decay, view-space spot direction and spot offset.
512
+ */
513
+ _getLightData( index ) {
312
514
 
313
515
  index = int( index );
314
516
 
315
- const dataA = textureLoad( this._lightsTexture, ivec2( index, 0 ) );
316
- const dataB = textureLoad( this._lightsTexture, ivec2( index, 1 ) );
517
+ const dataA = textureLoad( this._lightsTexture, ivec2( index, POSITION_ROW ) );
518
+ const dataB = textureLoad( this._lightsTexture, ivec2( index, COLOR_ROW ) );
519
+ const dataC = textureLoad( this._lightsTexture, ivec2( index, SPOT_ROW ) );
317
520
 
318
- const position = dataA.xyz;
319
- const viewPosition = this._cameraViewMatrix.mul( vec4( position, 1.0 ) ).xyz;
521
+ const viewPosition = this._cameraViewMatrix.mul( vec4( dataA.xyz, 1.0 ) ).xyz;
320
522
  const distance = dataA.w;
321
523
  const color = dataB.rgb;
322
524
  const decay = dataB.w;
525
+ const spotDirection = this._cameraViewMatrix.mul( vec4( dataC.xyz, 0.0 ) ).xyz;
526
+ const spotOffset = dataC.w;
323
527
 
324
528
  return {
325
- position,
326
529
  viewPosition,
327
530
  distance,
328
531
  color,
329
- decay
532
+ decay,
533
+ spotDirection,
534
+ spotOffset
330
535
  };
331
536
 
332
537
  }
333
538
 
539
+ /**
540
+ * Returns the culling data of the light at the given index.
541
+ *
542
+ * @private
543
+ * @param {number|Node<int>} index - The light index.
544
+ * @return {Object} The view-space center and the radius of the light's bounding sphere. A negative radius marks an unbounded light.
545
+ */
546
+ _getLightBounds( index ) {
547
+
548
+ const data = textureLoad( this._lightsTexture, ivec2( int( index ), BOUNDS_ROW ) );
549
+
550
+ const center = this._cameraViewMatrix.mul( vec4( data.xyz, 1.0 ) ).xyz;
551
+ const radius = data.w;
552
+
553
+ return { center, radius };
554
+
555
+ }
556
+
557
+ /**
558
+ * Sets up the default lights and the clustered point and spot lights.
559
+ *
560
+ * @param {NodeBuilder} builder - A reference to the current node builder.
561
+ * @param {Array<LightingNode>} lightNodes - An array of lighting nodes.
562
+ */
334
563
  setupLights( builder, lightNodes ) {
335
564
 
336
- this.updateProgram( builder.renderer );
565
+ this._initGrid( builder.renderer );
337
566
 
338
567
  //
339
568
 
@@ -348,7 +577,7 @@ class ClusteredLightsNode extends LightsNode {
348
577
 
349
578
  Loop( this.maxLightsPerCluster, ( { i } ) => {
350
579
 
351
- const lightIndex = this.getTile( i );
580
+ const lightIndex = this._getTile( i );
352
581
 
353
582
  If( lightIndex.equal( int( 0 ) ), () => {
354
583
 
@@ -356,15 +585,20 @@ class ClusteredLightsNode extends LightsNode {
356
585
 
357
586
  } );
358
587
 
359
- const { color, decay, viewPosition, distance } = this.getLightData( lightIndex.sub( 1 ) );
588
+ const { color, decay, viewPosition, distance, spotDirection, spotOffset } = this._getLightData( lightIndex.sub( 1 ) );
360
589
 
361
590
  const lightVector = viewPosition.sub( positionView );
362
591
 
363
- // Early-out: skip full BRDF if fragment is beyond the light's cutoff
364
- If( distance.equal( 0 ).or( dot( lightVector, lightVector ).lessThanEqual( distance.mul( distance ) ) ), () => {
592
+ // spot cone attenuation, always 1 for point lights
593
+ const spotAttenuation = smoothstep( 0, 1, dot( lightVector.normalize(), spotDirection ).sub( spotOffset ) ).toVar();
594
+
595
+ // Early-out: skip full BRDF if fragment is beyond the light's cutoff or outside the spot cone
596
+ const inRange = distance.equal( 0 ).or( dot( lightVector, lightVector ).lessThanEqual( distance.mul( distance ) ) );
597
+
598
+ If( inRange.and( spotAttenuation.greaterThan( 0 ) ), () => {
365
599
 
366
600
  builder.lightsNode.setupDirectLight( builder, this, directPointLight( {
367
- color,
601
+ color: color.mul( spotAttenuation ),
368
602
  lightVector,
369
603
  cutoffDistance: distance,
370
604
  decayExponent: decay
@@ -378,7 +612,14 @@ class ClusteredLightsNode extends LightsNode {
378
612
 
379
613
  }
380
614
 
381
- getBufferFitSize( value ) {
615
+ /**
616
+ * Rounds the given size up to a multiple of the tile size.
617
+ *
618
+ * @private
619
+ * @param {number} value - The size.
620
+ * @return {number} The fitted size.
621
+ */
622
+ _getBufferFitSize( value ) {
382
623
 
383
624
  const multiple = this.tileSize;
384
625
 
@@ -386,98 +627,161 @@ class ClusteredLightsNode extends LightsNode {
386
627
 
387
628
  }
388
629
 
630
+ /**
631
+ * Sets the size of the cluster grid. Must be called outside of rendering. Not required
632
+ * with {@link ClusteredLighting}, which fits the grid at the beginning of each render.
633
+ *
634
+ * @param {number} width - The drawing buffer width.
635
+ * @param {number} height - The drawing buffer height.
636
+ * @return {ClusteredLightsNode} A reference to this node.
637
+ */
389
638
  setSize( width, height ) {
390
639
 
391
- width = this.getBufferFitSize( width );
392
- height = this.getBufferFitSize( height );
640
+ this._resize( this._getBufferFitSize( width ), this._getBufferFitSize( height ) );
393
641
 
394
- if ( ! this._bufferSize || this._bufferSize.width !== width || this._bufferSize.height !== height ) {
642
+ return this;
395
643
 
396
- this.create( width, height );
644
+ }
397
645
 
398
- }
646
+ /**
647
+ * Fits the cluster grid to the framebuffer of the render that is about to start. Called by
648
+ * {@link ClusteredLighting} before the render objects of the render are prepared. Since each
649
+ * render depth has its own node, no pending command buffer uses the resources of this node at
650
+ * this point, so a rebuild can free the replaced resources immediately.
651
+ */
652
+ updateSize() {
399
653
 
400
- return this;
654
+ if ( this._renderer !== null ) this._fitToRenderer( this._renderer );
401
655
 
402
656
  }
403
657
 
404
- updateProgram( renderer ) {
658
+ /**
659
+ * Creates the cluster grid on first use. While rendering, the grid is never rebuilt: the
660
+ * replaced resources could still be used by a command buffer that was not submitted yet.
661
+ *
662
+ * @private
663
+ * @param {Renderer} renderer - The renderer.
664
+ */
665
+ _initGrid( renderer ) {
666
+
667
+ this._renderer = renderer;
405
668
 
406
- renderer.getDrawingBufferSize( _size );
669
+ if ( this._compute === null ) this._fitToRenderer( renderer );
670
+
671
+ }
672
+
673
+ /**
674
+ * Fits the cluster grid to the framebuffer the renderer is rendering to.
675
+ *
676
+ * @private
677
+ * @param {Renderer} renderer - The renderer.
678
+ */
679
+ _fitToRenderer( renderer ) {
407
680
 
408
- const width = this.getBufferFitSize( _size.width );
409
- const height = this.getBufferFitSize( _size.height );
681
+ // the grid must match the framebuffer being rendered: a render target (e.g. a reflector
682
+ // with a resolution scale) can differ from the drawing buffer
410
683
 
411
- if ( this._bufferSize === null ) {
684
+ const renderTarget = renderer.getRenderTarget();
412
685
 
413
- this.create( width, height );
686
+ if ( renderTarget !== null ) {
414
687
 
415
- } else if ( this._bufferSize.width !== width || this._bufferSize.height !== height ) {
688
+ _size.set( renderTarget.width, renderTarget.height );
416
689
 
417
- this.create( width, height );
690
+ } else {
691
+
692
+ renderer.getDrawingBufferSize( _size );
418
693
 
419
694
  }
420
695
 
696
+ // the compute differs per backend, so a backend change requires a rebuild
697
+
698
+ const isWebGLBackend = renderer.backend.isWebGLBackend === true;
699
+
700
+ if ( this._compute !== null && this._isWebGLBackend !== isWebGLBackend ) this._needsRebuild = true;
701
+
702
+ this._isWebGLBackend = isWebGLBackend;
703
+
704
+ this._resize( this._getBufferFitSize( _size.width ), this._getBufferFitSize( _size.height ) );
705
+
421
706
  }
422
707
 
423
- create( width, height ) {
708
+ /**
709
+ * Resizes the cluster grid. The grid dimensions are uniforms, so resizing only updates
710
+ * them and the compute count; the compute and the storage are rebuilt only when the
711
+ * storage has to grow (or the backend changed).
712
+ *
713
+ * @private
714
+ * @param {number} width - The fitted width.
715
+ * @param {number} height - The fitted height.
716
+ */
717
+ _resize( width, height ) {
424
718
 
425
- const { tileSize, zSlices, maxLightsPerCluster, _chunksPerCluster: chunksPerCluster } = this;
719
+ if ( this._needsRebuild === false && this._bufferSize !== null && this._bufferSize.width === width && this._bufferSize.height === height ) return;
426
720
 
427
- const bufferSize = new Vector2( width, height );
721
+ const NX = Math.floor( width / this.tileSize );
722
+ const NY = Math.floor( height / this.tileSize );
723
+ const clusterCount = NX * NY * this.zSlices;
428
724
 
429
- const NX = Math.floor( bufferSize.width / tileSize );
430
- const NY = Math.floor( bufferSize.height / tileSize );
431
- const NZ = zSlices;
432
- const clusterCount = NX * NY * NZ;
725
+ const requiredElements = clusterCount * this._chunksPerCluster;
433
726
 
434
- this._gridDimensions.value.set( NX, NY );
727
+ if ( this._needsRebuild === true || this._compute === null || this._lightIndexes.value.array.length / 4 < requiredElements ) {
435
728
 
436
- // release the compute pipeline and bindings built for the previous grid
729
+ this._create( clusterCount );
437
730
 
438
- if ( this._compute !== null ) this._compute.dispose();
731
+ }
439
732
 
440
- // Per-cluster light-index storage (ivec4 chunks).
733
+ this._gridDimensions.value.set( NX, NY );
734
+ this._compute.count = clusterCount * this._invocationsPerCluster;
441
735
 
442
- const lightIndexesLength = clusterCount * chunksPerCluster * 4;
736
+ if ( this._bufferSize === null ) this._bufferSize = new Vector2();
443
737
 
444
- let lightIndexes = this._lightIndexes;
738
+ this._bufferSize.set( width, height );
445
739
 
446
- if ( lightIndexes === null || lightIndexes.value.array.length < lightIndexesLength ) {
740
+ }
447
741
 
448
- // TODO: Free the outgoing buffer once the renderer destroys the GPU resources of
449
- // standalone storage attributes (`Bindings._destroyBindings()` skips storage buffers).
742
+ /**
743
+ * Creates the light index storage, the compute node and the fragment cluster lookup.
744
+ * The storage is allocated with headroom so that most resizes do not need a rebuild.
745
+ *
746
+ * @private
747
+ * @param {number} clusterCount - The number of clusters of the current grid.
748
+ */
749
+ _create( clusterCount ) {
450
750
 
451
- const lightIndexesArray = new Int32Array( lightIndexesLength );
452
- lightIndexes = attributeArray( lightIndexesArray, 'ivec4' ).setName( 'lightIndexes' );
751
+ const { tileSize, zSlices, _chunksPerCluster: chunksPerCluster } = this;
453
752
 
454
- }
753
+ const NZ = zSlices;
455
754
 
456
- // compute-side accessors (use instanceIndex)
755
+ // grid dimensions as uniforms: shaders do not depend on the drawing buffer size
457
756
 
458
- const getClusterChunk = ( chunkIdx ) => {
757
+ const grid = this._gridDimensions;
758
+ const NX = int( grid.x );
759
+ const NY = int( grid.y );
459
760
 
460
- const idx = instanceIndex.mul( int( chunksPerCluster ) ).add( int( chunkIdx ) );
761
+ // Per-cluster light-index storage (ivec4 chunks), grown geometrically.
461
762
 
462
- return lightIndexes.element( idx );
763
+ const requiredElements = clusterCount * chunksPerCluster;
764
+ const previousElements = this._lightIndexes !== null ? this._lightIndexes.value.array.length / 4 : 0;
765
+ const elements = previousElements === 0 ? Math.ceil( requiredElements * 1.5 ) : Math.max( requiredElements, previousElements * 2 );
463
766
 
464
- };
767
+ // rebuilds only happen outside of rendering (see _initGrid()), so the replaced resources can be freed
465
768
 
466
- const getClusterSlot = ( slotIdx ) => {
769
+ if ( this._compute !== null ) {
467
770
 
468
- slotIdx = int( slotIdx );
771
+ // Replacing storage requires new material bindings. The initial allocation does not.
772
+ this.needsUpdate = true;
469
773
 
470
- const stride = int( 4 );
471
- const chunkOffset = slotIdx.div( stride );
472
- const idx = instanceIndex.mul( int( chunksPerCluster ) ).add( chunkOffset );
774
+ this._compute.dispose();
775
+ this._lightIndexes.value.dispose();
473
776
 
474
- return lightIndexes.element( idx ).element( slotIdx.mod( stride ) );
777
+ }
475
778
 
476
- };
779
+ // instanced storage: WebGL computes use gl_InstanceID as instanceIndex; PBO: random access in the fragment stage
780
+ const lightIndexes = instancedArray( new Int32Array( elements * 4 ), 'ivec4' ).setPBO( true ).setName( 'lightIndexes' );
477
781
 
478
- // compute: one thread per cluster
782
+ // view-space AABB of the given cluster
479
783
 
480
- const compute = Fn( () => {
784
+ const getClusterBounds = ( clusterIndex ) => {
481
785
 
482
786
  // view-space scale factors derived from the projection matrix:
483
787
  // view_x = ndc_x * (-view_z) / focal_x = ndc_x * (-view_z) * invFocalX
@@ -486,17 +790,17 @@ class ClusteredLightsNode extends LightsNode {
486
790
  const invFocalX = float( 1 ).div( this._cameraProjectionMatrix.element( 0 ).element( 0 ) );
487
791
  const invFocalY = float( 1 ).div( this._cameraProjectionMatrix.element( 1 ).element( 1 ) );
488
792
 
489
- // 3D cluster coordinates from instanceIndex
490
- const cx = instanceIndex.mod( NX );
491
- const cy = instanceIndex.div( NX ).mod( NY );
492
- const cz = instanceIndex.div( NX * NY );
793
+ // 3D cluster coordinates
794
+ const cx = clusterIndex.mod( NX );
795
+ const cy = clusterIndex.div( NX ).mod( NY );
796
+ const cz = clusterIndex.div( NX.mul( NY ) );
493
797
 
494
798
  // NDC X/Y bounds of the cluster.
495
799
  // Y is flipped: cy=0 is the top screen row (fragment y=0), which is NDC y=+1.
496
- const ndcXmin = float( cx ).mul( 2.0 / NX ).sub( 1.0 );
497
- const ndcXmax = float( cx.add( int( 1 ) ) ).mul( 2.0 / NX ).sub( 1.0 );
498
- const ndcYmax = float( 1 ).sub( float( cy ).mul( 2.0 / NY ) );
499
- const ndcYmin = float( 1 ).sub( float( cy.add( int( 1 ) ) ).mul( 2.0 / NY ) );
800
+ const ndcXmin = float( cx ).mul( 2.0 ).div( grid.x ).sub( 1.0 );
801
+ const ndcXmax = float( cx.add( int( 1 ) ) ).mul( 2.0 ).div( grid.x ).sub( 1.0 );
802
+ const ndcYmax = float( 1 ).sub( float( cy ).mul( 2.0 ).div( grid.y ) );
803
+ const ndcYmin = float( 1 ).sub( float( cy.add( int( 1 ) ) ).mul( 2.0 ).div( grid.y ) );
500
804
 
501
805
  // View-space Z bounds (negative, exponential slicing)
502
806
  const farOverNear = this._cameraFar.div( this._cameraNear );
@@ -527,13 +831,43 @@ class ClusteredLightsNode extends LightsNode {
527
831
  const aabbMin = vec3( aabbMinX, aabbMinY, zFarCluster );
528
832
  const aabbMax = vec3( aabbMaxX, aabbMaxY, zNearCluster );
529
833
 
530
- // clear stale data from previous frame
531
- Loop( chunksPerCluster, ( { i } ) => {
834
+ return { cz, aabbMin, aabbMax };
532
835
 
533
- getClusterChunk( i ).assign( ivec4( 0 ) );
836
+ };
534
837
 
535
- } );
838
+ // sphere-AABB intersection in view space
839
+
840
+ const intersectsCluster = ( lightIdx, aabbMin, aabbMax ) => {
841
+
842
+ const { center, radius } = this._getLightBounds( lightIdx );
843
+
844
+ const closest = max( aabbMin, min( center, aabbMax ) );
845
+ const diff = center.sub( closest );
846
+ const distSq = dot( diff, diff );
847
+
848
+ return radius.lessThan( 0 ).or( distSq.lessThanEqual( radius.mul( radius ) ) );
849
+
850
+ };
851
+
852
+ // Each invocation fills `chunksPerInvocation` ivec4 chunks of one cluster. WebGL computes
853
+ // run via transform feedback and can only write their own element, so there each
854
+ // invocation fills a single chunk; WebGPU uses one invocation per cluster.
855
+
856
+ const chunksPerInvocation = this._isWebGLBackend ? 1 : chunksPerCluster;
857
+ const invocationsPerCluster = chunksPerCluster / chunksPerInvocation;
536
858
 
859
+ this._invocationsPerCluster = invocationsPerCluster;
860
+
861
+ const compute = Fn( () => {
862
+
863
+ const clusterIndex = int( instanceIndex.div( invocationsPerCluster ) );
864
+ const firstChunk = int( instanceIndex.mod( invocationsPerCluster ) ).mul( chunksPerInvocation );
865
+ const firstSlot = firstChunk.mul( 4 );
866
+ const lastSlot = firstSlot.add( chunksPerInvocation * 4 );
867
+
868
+ const { cz, aabbMin, aabbMax } = getClusterBounds( clusterIndex );
869
+
870
+ const chunks = ivec4( 0 ).toArray( chunksPerInvocation ).toVar();
537
871
  const index = int( 0 ).toVar();
538
872
 
539
873
  // Z-culling: only test lights that can reach this cluster's Z-slice
@@ -545,30 +879,56 @@ class ClusteredLightsNode extends LightsNode {
545
879
 
546
880
  const lightIdx = rangeStart.add( i );
547
881
 
548
- If( index.greaterThanEqual( int( maxLightsPerCluster ) ).or( lightIdx.greaterThanEqual( rangeEnd ) ), () => {
882
+ If( index.greaterThanEqual( lastSlot ).or( lightIdx.greaterThanEqual( rangeEnd ) ), () => {
549
883
 
550
- Return();
884
+ Break();
551
885
 
552
886
  } );
553
887
 
554
- const { viewPosition, distance } = this.getLightData( lightIdx );
888
+ If( intersectsCluster( lightIdx, aabbMin, aabbMax ), () => {
555
889
 
556
- // sphere-AABB intersection in view space
557
- const pos = viewPosition.xyz;
558
- const closest = max( aabbMin, min( pos, aabbMax ) );
559
- const diff = pos.sub( closest );
560
- const distSq = dot( diff, diff );
890
+ // slots before firstSlot belong to other invocations of this cluster
891
+ If( index.greaterThanEqual( firstSlot ), () => {
561
892
 
562
- If( distSq.lessThanEqual( distance.mul( distance ) ), () => {
893
+ const slot = index.sub( firstSlot );
894
+
895
+ chunks.element( slot.div( 4 ) ).element( slot.mod( 4 ) ).assign( lightIdx.add( int( 1 ) ) );
896
+
897
+ } );
563
898
 
564
- getClusterSlot( index ).assign( lightIdx.add( int( 1 ) ) );
565
899
  index.addAssign( int( 1 ) );
566
900
 
567
901
  } );
568
902
 
569
903
  } );
570
904
 
571
- } )().compute( clusterCount ).setName( 'Update Clustered Lights' );
905
+ const firstElement = clusterIndex.mul( chunksPerCluster ).add( firstChunk );
906
+
907
+ const writeChunks = () => {
908
+
909
+ Loop( chunksPerInvocation, ( { i } ) => {
910
+
911
+ lightIndexes.element( firstElement.add( i ) ).assign( chunks.element( i ) );
912
+
913
+ } );
914
+
915
+ };
916
+
917
+ if ( this._isWebGLBackend === true ) {
918
+
919
+ // WebGL binds the transform feedback buffers per compute program, and programs are shared by
920
+ // identical code. The (always true) capacity and node id constants give each node and storage
921
+ // allocation its own program, so a compute never writes to the storage of another one.
922
+
923
+ If( firstElement.lessThan( int( elements ) ).and( int( this.id ).notEqual( int( - 1 ) ) ), writeChunks );
924
+
925
+ } else {
926
+
927
+ writeChunks();
928
+
929
+ }
930
+
931
+ } )().compute( clusterCount * invocationsPerCluster ).setName( 'Update Clustered Lights' );
572
932
 
573
933
  // shading-side: fragment → cluster index
574
934
 
@@ -585,8 +945,8 @@ class ClusteredLightsNode extends LightsNode {
585
945
  const zSlice = clamp( sliceFloat.floor(), float( 0 ), float( NZ - 1 ) );
586
946
 
587
947
  return int( screenTile.x )
588
- .add( int( screenTile.y ).mul( int( NX ) ) )
589
- .add( int( zSlice ).mul( int( NX * NY ) ) );
948
+ .add( int( screenTile.y ).mul( NX ) )
949
+ .add( int( zSlice ).mul( NX.mul( NY ) ) );
590
950
 
591
951
  } );
592
952
 
@@ -594,10 +954,10 @@ class ClusteredLightsNode extends LightsNode {
594
954
 
595
955
  // assigns
596
956
 
597
- this._bufferSize = bufferSize;
598
957
  this._lightIndexes = lightIndexes;
599
958
  this._screenClusterIndex = screenClusterIndex;
600
959
  this._compute = compute;
960
+ this._needsRebuild = false;
601
961
 
602
962
  }
603
963
 
@@ -607,6 +967,7 @@ class ClusteredLightsNode extends LightsNode {
607
967
  dispose() {
608
968
 
609
969
  if ( this._compute !== null ) this._compute.dispose();
970
+ if ( this._lightIndexes !== null ) this._lightIndexes.value.dispose();
610
971
 
611
972
  this._lightsTexture.dispose();
612
973
  this._zSliceRangesTexture.dispose();
@@ -624,7 +985,7 @@ export default ClusteredLightsNode;
624
985
  *
625
986
  * @tsl
626
987
  * @function
627
- * @param {number} [maxLights=1024] - Maximum number of point lights.
988
+ * @param {number} [maxLights=1024] - Maximum number of point and spot lights.
628
989
  * @param {number} [tileSize=32] - Screen tile size in pixels.
629
990
  * @param {number} [zSlices=24] - Depth slice count.
630
991
  * @param {number} [maxLightsPerCluster=64] - Per-cluster light-list capacity.