@zephyr3d/scene 0.9.25 → 0.9.27

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 (299) hide show
  1. package/dist/animation/animationset.js +92 -30
  2. package/dist/animation/animationset.js.map +1 -1
  3. package/dist/animation/hair/gpu_hair_simulation.js +1090 -0
  4. package/dist/animation/hair/gpu_hair_simulation.js.map +1 -0
  5. package/dist/animation/joint_dynamics/controller.js +7 -5
  6. package/dist/animation/joint_dynamics/controller.js.map +1 -1
  7. package/dist/animation/joint_dynamics/convex_collider.js +320 -0
  8. package/dist/animation/joint_dynamics/convex_collider.js.map +1 -0
  9. package/dist/animation/joint_dynamics/joint_dynamics_system.js +81 -5
  10. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
  11. package/dist/animation/morphtrack.js +3 -4
  12. package/dist/animation/morphtrack.js.map +1 -1
  13. package/dist/animation/skeleton.js +79 -25
  14. package/dist/animation/skeleton.js.map +1 -1
  15. package/dist/animation/spring/multi_chain_spring_system.js +577 -179
  16. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
  17. package/dist/animation/spring/spring_collider.js +4 -3
  18. package/dist/animation/spring/spring_collider.js.map +1 -1
  19. package/dist/animation/spring/spring_node_pose_tracker.js +40 -0
  20. package/dist/animation/spring/spring_node_pose_tracker.js.map +1 -0
  21. package/dist/animation/spring/spring_particle.js +2 -0
  22. package/dist/animation/spring/spring_particle.js.map +1 -1
  23. package/dist/animation/spring/spring_system.js +11 -3
  24. package/dist/animation/spring/spring_system.js.map +1 -1
  25. package/dist/animation/spring_modifier.js +10 -0
  26. package/dist/animation/spring_modifier.js.map +1 -1
  27. package/dist/app/engine.js +53 -43
  28. package/dist/app/engine.js.map +1 -1
  29. package/dist/app/scriptingsystem.js +119 -6
  30. package/dist/app/scriptingsystem.js.map +1 -1
  31. package/dist/app/scriptregistry.js +3 -2
  32. package/dist/app/scriptregistry.js.map +1 -1
  33. package/dist/asset/assetmanager.js +284 -67
  34. package/dist/asset/assetmanager.js.map +1 -1
  35. package/dist/asset/loaders/zhair/zhair_format.js +288 -0
  36. package/dist/asset/loaders/zhair/zhair_format.js.map +1 -0
  37. package/dist/asset/loaders/zhair/zhair_loader.js +221 -0
  38. package/dist/asset/loaders/zhair/zhair_loader.js.map +1 -0
  39. package/dist/asset/model.js +184 -69
  40. package/dist/asset/model.js.map +1 -1
  41. package/dist/camera/camera.js +268 -44
  42. package/dist/camera/camera.js.map +1 -1
  43. package/dist/camera/perspectivecamera.js +3 -1
  44. package/dist/camera/perspectivecamera.js.map +1 -1
  45. package/dist/index.d.ts +9044 -5139
  46. package/dist/index.js +17 -6
  47. package/dist/index.js.map +1 -1
  48. package/dist/material/eye.js +932 -0
  49. package/dist/material/eye.js.map +1 -0
  50. package/dist/material/hair.js +917 -0
  51. package/dist/material/hair.js.map +1 -0
  52. package/dist/material/hairstrand.js +842 -0
  53. package/dist/material/hairstrand.js.map +1 -0
  54. package/dist/material/hairstrand_data.js +162 -0
  55. package/dist/material/hairstrand_data.js.map +1 -0
  56. package/dist/material/lambert.js +5 -2
  57. package/dist/material/lambert.js.map +1 -1
  58. package/dist/material/material.js +48 -10
  59. package/dist/material/material.js.map +1 -1
  60. package/dist/material/meshmaterial.js +120 -19
  61. package/dist/material/meshmaterial.js.map +1 -1
  62. package/dist/material/mixins/albedocolor.js +1 -1
  63. package/dist/material/mixins/albedocolor.js.map +1 -1
  64. package/dist/material/mixins/lightmodel/blinnphong.js +5 -3
  65. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  66. package/dist/material/mixins/lightmodel/lambert.js +5 -4
  67. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  68. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +3 -3
  69. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  70. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +5 -5
  71. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  72. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +3 -3
  73. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  74. package/dist/material/mixins/lit.js +106 -19
  75. package/dist/material/mixins/lit.js.map +1 -1
  76. package/dist/material/mixins/pbr/common.js +4 -4
  77. package/dist/material/mixins/pbr/common.js.map +1 -1
  78. package/dist/material/mixins/texture.js +48 -11
  79. package/dist/material/mixins/texture.js.map +1 -1
  80. package/dist/material/mtoon.js +6 -5
  81. package/dist/material/mtoon.js.map +1 -1
  82. package/dist/material/pbrblueprint.js +146 -28
  83. package/dist/material/pbrblueprint.js.map +1 -1
  84. package/dist/material/pbrblueprint_instance.js +501 -0
  85. package/dist/material/pbrblueprint_instance.js.map +1 -0
  86. package/dist/material/pbrmr.js +15 -16
  87. package/dist/material/pbrmr.js.map +1 -1
  88. package/dist/material/shader/helper.js +734 -23
  89. package/dist/material/shader/helper.js.map +1 -1
  90. package/dist/material/shader/terrain_blend.js +55 -0
  91. package/dist/material/shader/terrain_blend.js.map +1 -0
  92. package/dist/material/skin.js +192 -32
  93. package/dist/material/skin.js.map +1 -1
  94. package/dist/material/spriteblueprint.js +16 -1
  95. package/dist/material/spriteblueprint.js.map +1 -1
  96. package/dist/material/subsurfaceprofile.js +86 -23
  97. package/dist/material/subsurfaceprofile.js.map +1 -1
  98. package/dist/material/water.js +2059 -72
  99. package/dist/material/water.js.map +1 -1
  100. package/dist/posteffect/bloom.js +167 -7
  101. package/dist/posteffect/bloom.js.map +1 -1
  102. package/dist/posteffect/coloradjust.js +2 -1
  103. package/dist/posteffect/coloradjust.js.map +1 -1
  104. package/dist/posteffect/compositor.js.map +1 -1
  105. package/dist/posteffect/fxaa.js +2 -1
  106. package/dist/posteffect/fxaa.js.map +1 -1
  107. package/dist/posteffect/grayscale.js +3 -1
  108. package/dist/posteffect/grayscale.js.map +1 -1
  109. package/dist/posteffect/motionblur.js +664 -56
  110. package/dist/posteffect/motionblur.js.map +1 -1
  111. package/dist/posteffect/posteffect.js +12 -1
  112. package/dist/posteffect/posteffect.js.map +1 -1
  113. package/dist/posteffect/sao.js +46 -44
  114. package/dist/posteffect/sao.js.map +1 -1
  115. package/dist/posteffect/skinsss.js +368 -69
  116. package/dist/posteffect/skinsss.js.map +1 -1
  117. package/dist/posteffect/ssgi.js +16 -9
  118. package/dist/posteffect/ssgi.js.map +1 -1
  119. package/dist/posteffect/ssr.js +3 -0
  120. package/dist/posteffect/ssr.js.map +1 -1
  121. package/dist/posteffect/sss.js +121 -36
  122. package/dist/posteffect/sss.js.map +1 -1
  123. package/dist/posteffect/taa.js.map +1 -1
  124. package/dist/posteffect/tonemap.js +44 -1
  125. package/dist/posteffect/tonemap.js.map +1 -1
  126. package/dist/render/abuffer_oit.js +8 -2
  127. package/dist/render/abuffer_oit.js.map +1 -1
  128. package/dist/render/clipmap.js +156 -5
  129. package/dist/render/clipmap.js.map +1 -1
  130. package/dist/render/cull_visitor.js +20 -0
  131. package/dist/render/cull_visitor.js.map +1 -1
  132. package/dist/render/depthpass.js +36 -2
  133. package/dist/render/depthpass.js.map +1 -1
  134. package/dist/render/drawable_mixin.js +26 -1
  135. package/dist/render/drawable_mixin.js.map +1 -1
  136. package/dist/render/dualdepthpeeling_oit.js +3 -2
  137. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  138. package/dist/render/envlight.js +1 -1
  139. package/dist/render/envlight.js.map +1 -1
  140. package/dist/render/fbm_wavegenerator.js +6 -3
  141. package/dist/render/fbm_wavegenerator.js.map +1 -1
  142. package/dist/render/fft_wavegenerator.js +57 -15
  143. package/dist/render/fft_wavegenerator.js.map +1 -1
  144. package/dist/render/gerstner_wavegenerator.js +130 -20
  145. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  146. package/dist/render/globalbindgroup_allocator.js +3 -0
  147. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  148. package/dist/render/hzb.js +80 -22
  149. package/dist/render/hzb.js.map +1 -1
  150. package/dist/render/lightpass.js +6 -3
  151. package/dist/render/lightpass.js.map +1 -1
  152. package/dist/render/oit.js +29 -0
  153. package/dist/render/oit.js.map +1 -0
  154. package/dist/render/render_queue.js +39 -0
  155. package/dist/render/render_queue.js.map +1 -1
  156. package/dist/render/renderer.js +4 -0
  157. package/dist/render/renderer.js.map +1 -1
  158. package/dist/render/rendergraph/blackboard.js +1 -0
  159. package/dist/render/rendergraph/blackboard.js.map +1 -1
  160. package/dist/render/rendergraph/device_pool_allocator.js.map +1 -1
  161. package/dist/render/rendergraph/executor.js +2 -0
  162. package/dist/render/rendergraph/executor.js.map +1 -1
  163. package/dist/render/rendergraph/forward_plus_builder.js +379 -10
  164. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  165. package/dist/render/rendergraph/frame_resource_requirements.js +1 -0
  166. package/dist/render/rendergraph/frame_resource_requirements.js.map +1 -1
  167. package/dist/render/rendergraph/history_resources.js +8 -1
  168. package/dist/render/rendergraph/history_resources.js.map +1 -1
  169. package/dist/render/rendergraph/rendergraph.js.map +1 -1
  170. package/dist/render/rendergraph/texture_affinity_cache.js +1 -1
  171. package/dist/render/rendergraph/texture_affinity_cache.js.map +1 -1
  172. package/dist/render/rendergraph/types.js.map +1 -1
  173. package/dist/render/shadow_mask_pass.js +73 -8
  174. package/dist/render/shadow_mask_pass.js.map +1 -1
  175. package/dist/render/sky.js +166 -126
  176. package/dist/render/sky.js.map +1 -1
  177. package/dist/render/underwater.js +624 -0
  178. package/dist/render/underwater.js.map +1 -0
  179. package/dist/render/water_caustics.js +1380 -0
  180. package/dist/render/water_caustics.js.map +1 -0
  181. package/dist/render/weightedblended_oit.js +3 -2
  182. package/dist/render/weightedblended_oit.js.map +1 -1
  183. package/dist/scene/basesprite.js +8 -0
  184. package/dist/scene/basesprite.js.map +1 -1
  185. package/dist/scene/batchgroup.js +3 -0
  186. package/dist/scene/batchgroup.js.map +1 -1
  187. package/dist/scene/environment.js +4 -1
  188. package/dist/scene/environment.js.map +1 -1
  189. package/dist/scene/hair.js +963 -0
  190. package/dist/scene/hair.js.map +1 -0
  191. package/dist/scene/light.js +5 -1
  192. package/dist/scene/light.js.map +1 -1
  193. package/dist/scene/mesh.js +481 -47
  194. package/dist/scene/mesh.js.map +1 -1
  195. package/dist/scene/meshdrawable.js +6 -0
  196. package/dist/scene/meshdrawable.js.map +1 -1
  197. package/dist/scene/msdftext.js +3 -0
  198. package/dist/scene/msdftext.js.map +1 -1
  199. package/dist/scene/msdftextsprite.js +3 -0
  200. package/dist/scene/msdftextsprite.js.map +1 -1
  201. package/dist/scene/particlesys.js +6 -0
  202. package/dist/scene/particlesys.js.map +1 -1
  203. package/dist/scene/scene.js +3 -0
  204. package/dist/scene/scene.js.map +1 -1
  205. package/dist/scene/scene_node.js +6 -1
  206. package/dist/scene/scene_node.js.map +1 -1
  207. package/dist/scene/terrain-cm/grass.js +3 -0
  208. package/dist/scene/terrain-cm/grass.js.map +1 -1
  209. package/dist/scene/terrain-cm/grassmaterial.js +3 -0
  210. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  211. package/dist/scene/terrain-cm/terrain-cm.js +6 -0
  212. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  213. package/dist/scene/terrain-cm/texture_cache.js +309 -0
  214. package/dist/scene/terrain-cm/texture_cache.js.map +1 -0
  215. package/dist/scene/water.js +546 -4
  216. package/dist/scene/water.js.map +1 -1
  217. package/dist/shaders/atmosphere.js.map +1 -1
  218. package/dist/shaders/diffusion.js +54 -0
  219. package/dist/shaders/diffusion.js.map +1 -0
  220. package/dist/shaders/shadow.js +33 -21
  221. package/dist/shaders/shadow.js.map +1 -1
  222. package/dist/shaders/temporal.js +71 -33
  223. package/dist/shaders/temporal.js.map +1 -1
  224. package/dist/shaders/water.js +22 -19
  225. package/dist/shaders/water.js.map +1 -1
  226. package/dist/shaders/water_medium.js +56 -0
  227. package/dist/shaders/water_medium.js.map +1 -0
  228. package/dist/shadow/dom.js +546 -0
  229. package/dist/shadow/dom.js.map +1 -0
  230. package/dist/shadow/pcf_opt.js +3 -5
  231. package/dist/shadow/pcf_opt.js.map +1 -1
  232. package/dist/shadow/pcf_pd.js +1 -1
  233. package/dist/shadow/pcf_pd.js.map +1 -1
  234. package/dist/shadow/receiver_bias.js +41 -0
  235. package/dist/shadow/receiver_bias.js.map +1 -0
  236. package/dist/shadow/shader.js +20 -60
  237. package/dist/shadow/shader.js.map +1 -1
  238. package/dist/shadow/shadow_impl.js +89 -0
  239. package/dist/shadow/shadow_impl.js.map +1 -1
  240. package/dist/shadow/shadow_region.js +1 -1
  241. package/dist/shadow/shadow_region.js.map +1 -1
  242. package/dist/shadow/shadowmapper.js +150 -37
  243. package/dist/shadow/shadowmapper.js.map +1 -1
  244. package/dist/shapes/shape.js +11 -2
  245. package/dist/shapes/shape.js.map +1 -1
  246. package/dist/shapes/sphere.js +50 -9
  247. package/dist/shapes/sphere.js.map +1 -1
  248. package/dist/utility/blueprint/common/constants.js +112 -2
  249. package/dist/utility/blueprint/common/constants.js.map +1 -1
  250. package/dist/utility/blueprint/material/func.js +142 -49
  251. package/dist/utility/blueprint/material/func.js.map +1 -1
  252. package/dist/utility/blueprint/material/ir.js +490 -25
  253. package/dist/utility/blueprint/material/ir.js.map +1 -1
  254. package/dist/utility/blueprint/material/texture.js +898 -4
  255. package/dist/utility/blueprint/material/texture.js.map +1 -1
  256. package/dist/utility/serialization/manager.js +434 -101
  257. package/dist/utility/serialization/manager.js.map +1 -1
  258. package/dist/utility/serialization/scene/animation.js +401 -5
  259. package/dist/utility/serialization/scene/animation.js.map +1 -1
  260. package/dist/utility/serialization/scene/batch.js +4 -0
  261. package/dist/utility/serialization/scene/batch.js.map +1 -1
  262. package/dist/utility/serialization/scene/camera.js +186 -26
  263. package/dist/utility/serialization/scene/camera.js.map +1 -1
  264. package/dist/utility/serialization/scene/common.js +26 -5
  265. package/dist/utility/serialization/scene/common.js.map +1 -1
  266. package/dist/utility/serialization/scene/hair.js +1141 -0
  267. package/dist/utility/serialization/scene/hair.js.map +1 -0
  268. package/dist/utility/serialization/scene/light.js +84 -4
  269. package/dist/utility/serialization/scene/light.js.map +1 -1
  270. package/dist/utility/serialization/scene/material.js +1867 -166
  271. package/dist/utility/serialization/scene/material.js.map +1 -1
  272. package/dist/utility/serialization/scene/mesh.js +231 -14
  273. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  274. package/dist/utility/serialization/scene/node.js +141 -7
  275. package/dist/utility/serialization/scene/node.js.map +1 -1
  276. package/dist/utility/serialization/scene/particle.js +4 -0
  277. package/dist/utility/serialization/scene/particle.js.map +1 -1
  278. package/dist/utility/serialization/scene/primitive.js +18 -0
  279. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  280. package/dist/utility/serialization/scene/sprite.js +4 -0
  281. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  282. package/dist/utility/serialization/scene/terrain.js +4 -0
  283. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  284. package/dist/utility/serialization/scene/text.js +4 -0
  285. package/dist/utility/serialization/scene/text.js.map +1 -1
  286. package/dist/utility/serialization/scene/water.js +1105 -21
  287. package/dist/utility/serialization/scene/water.js.map +1 -1
  288. package/dist/utility/serialization/types.js.map +1 -1
  289. package/dist/utility/shprojector.js +9 -8
  290. package/dist/utility/shprojector.js.map +1 -1
  291. package/dist/values.js +49 -2
  292. package/dist/values.js.map +1 -1
  293. package/package.json +3 -3
  294. package/dist/render/rendergraph/frame_graph_context.js +0 -41
  295. package/dist/render/rendergraph/frame_graph_context.js.map +0 -1
  296. package/dist/shaders/ssgi.js +0 -63
  297. package/dist/shaders/ssgi.js.map +0 -1
  298. package/dist/shaders/ssgi_reprojection.js +0 -118
  299. package/dist/shaders/ssgi_reprojection.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"shader.js","sources":["../../src/shadow/shader.ts"],"sourcesContent":["import type { PBInsideFunctionScope, PBShaderExp } from '@zephyr3d/device';\r\nimport { ShaderHelper } from '../material';\r\nimport { LIGHT_TYPE_DIRECTIONAL, LIGHT_TYPE_POINT, LIGHT_TYPE_SPOT } from '../values';\r\n\r\nfunction getShadowReceiverBiasFactors(scope: PBInsideFunctionScope) {\r\n const pb = scope.$builder;\r\n const alphaCutoff = (scope as PBInsideFunctionScope & { zAlphaCutoff?: PBShaderExp }).zAlphaCutoff;\r\n if (alphaCutoff) {\r\n // Thin masked geometry such as layered hair cards needs a tighter receiver bias,\r\n // otherwise nearby layers lose self-shadow before the shadow map is even sampled.\r\n const cutoff = pb.clamp(alphaCutoff, 0, 1);\r\n return pb.vec2(pb.mix(0.22, 0.4, cutoff), pb.mix(0.05, 0.16, cutoff)) as PBShaderExp;\r\n }\r\n return pb.vec2(1, 1) as PBShaderExp;\r\n}\r\n\r\nfunction isMaskedPerspectiveShadowLight(scope: PBInsideFunctionScope, lightType: number) {\r\n const alphaCutoff = (scope as PBInsideFunctionScope & { zAlphaCutoff?: PBShaderExp }).zAlphaCutoff;\r\n return !!alphaCutoff && (lightType === LIGHT_TYPE_SPOT || lightType === LIGHT_TYPE_POINT);\r\n}\r\n\r\nfunction getShadowReceiverNoL(scope: PBInsideFunctionScope, NdotL: PBShaderExp, lightType?: number) {\r\n const pb = scope.$builder;\r\n const alphaCutoff = (scope as PBInsideFunctionScope & { zAlphaCutoff?: PBShaderExp }).zAlphaCutoff;\r\n if (alphaCutoff) {\r\n // Layered masked cards should not explode slope bias at grazing angles.\r\n // This is especially visible for back / rim spot lights on hair cards.\r\n const cutoff = pb.clamp(alphaCutoff, 0, 1);\r\n if (lightType != null && isMaskedPerspectiveShadowLight(scope, lightType)) {\r\n return pb.max(NdotL, pb.mix(0.85, 0.95, cutoff)) as PBShaderExp;\r\n }\r\n return pb.max(NdotL, pb.mix(0.45, 0.65, cutoff)) as PBShaderExp;\r\n }\r\n return NdotL as PBShaderExp;\r\n}\r\n\r\nfunction getShadowReceiverPerspectiveBiasScale(\r\n scope: PBInsideFunctionScope,\r\n lightType: number,\r\n linearDepth: PBShaderExp,\r\n farNearRatio: PBShaderExp\r\n) {\r\n const pb = scope.$builder;\r\n const alphaCutoff = (scope as PBInsideFunctionScope & { zAlphaCutoff?: PBShaderExp }).zAlphaCutoff;\r\n if (alphaCutoff && isMaskedPerspectiveShadowLight(scope, lightType)) {\r\n const cutoff = pb.clamp(alphaCutoff, 0, 1);\r\n // Perspective shadow cameras (spot / point) can over-amplify receiver bias at depth.\r\n // Keep masked layered geometry in a much tighter range so hair cards can still self-occlude.\r\n const cappedFarNearRatio = pb.min(farNearRatio, pb.mix(1.15, 1.6, cutoff));\r\n return pb.mix(1, cappedFarNearRatio, linearDepth) as PBShaderExp;\r\n }\r\n return pb.mix(1, farNearRatio, linearDepth) as PBShaderExp;\r\n}\r\n\r\nexport function computeShadowBiasCSM(scope: PBInsideFunctionScope, NdotL: PBShaderExp, split: PBShaderExp) {\r\n const pb = scope.$builder;\r\n const depthBiasParam = ShaderHelper.getDepthBiasValues(scope);\r\n const splitFlags = pb.vec4(\r\n pb.float(pb.equal(split, 0)),\r\n pb.float(pb.equal(split, 1)),\r\n pb.float(pb.equal(split, 2)),\r\n pb.float(pb.equal(split, 3))\r\n );\r\n const depthBiasScale = pb.dot(ShaderHelper.getDepthBiasScales(scope), splitFlags);\r\n const receiverBiasFactors = getShadowReceiverBiasFactors(scope);\r\n const receiverNoL = getShadowReceiverNoL(scope, NdotL);\r\n return pb.dot(\r\n pb.mul(depthBiasParam.xy, receiverBiasFactors, pb.vec2(1, pb.sub(1, receiverNoL)), depthBiasScale),\r\n pb.vec2(1, 1)\r\n );\r\n}\r\n\r\nexport function computeShadowBias(\r\n lightType: number,\r\n scope: PBInsideFunctionScope,\r\n z: PBShaderExp,\r\n NdotL: PBShaderExp,\r\n linear: boolean\r\n) {\r\n const pb = scope.$builder;\r\n const depthBiasParam = ShaderHelper.getDepthBiasValues(scope);\r\n const receiverBiasFactors = getShadowReceiverBiasFactors(scope);\r\n const receiverNoL = getShadowReceiverNoL(scope, NdotL, lightType);\r\n if (lightType === LIGHT_TYPE_DIRECTIONAL) {\r\n return pb.dot(\r\n pb.mul(depthBiasParam.xy, receiverBiasFactors, pb.vec2(1, pb.sub(1, receiverNoL))),\r\n pb.vec2(1, 1)\r\n );\r\n } else {\r\n const nearFar = ShaderHelper.getShadowCameraParams(scope).xy;\r\n const linearDepth = linear ? z : ShaderHelper.nonLinearDepthToLinearNormalized(scope, z, nearFar);\r\n const biasScaleFactor = getShadowReceiverPerspectiveBiasScale(\r\n scope,\r\n lightType,\r\n linearDepth,\r\n depthBiasParam.w\r\n );\r\n let bias = pb.dot(\r\n pb.mul(depthBiasParam.xy, receiverBiasFactors, pb.vec2(1, pb.sub(1, receiverNoL)), biasScaleFactor),\r\n pb.vec2(1, 1)\r\n );\r\n if (isMaskedPerspectiveShadowLight(scope, lightType)) {\r\n const alphaCutoff = (scope as PBInsideFunctionScope & { zAlphaCutoff?: PBShaderExp }).zAlphaCutoff!;\r\n bias = pb.mul(bias, pb.mix(0.12, 0.22, pb.clamp(alphaCutoff, 0, 1))) as PBShaderExp;\r\n }\r\n return bias;\r\n }\r\n}\r\n"],"names":["getShadowReceiverBiasFactors","scope","pb","$builder","alphaCutoff","zAlphaCutoff","cutoff","clamp","vec2","mix","isMaskedPerspectiveShadowLight","lightType","LIGHT_TYPE_SPOT","LIGHT_TYPE_POINT","getShadowReceiverNoL","NdotL","max","getShadowReceiverPerspectiveBiasScale","linearDepth","farNearRatio","cappedFarNearRatio","min","computeShadowBiasCSM","split","depthBiasParam","ShaderHelper","getDepthBiasValues","splitFlags","vec4","float","equal","depthBiasScale","dot","getDepthBiasScales","receiverBiasFactors","receiverNoL","mul","xy","sub","computeShadowBias","z","linear","LIGHT_TYPE_DIRECTIONAL","nearFar","getShadowCameraParams","nonLinearDepthToLinearNormalized","biasScaleFactor","w","bias"],"mappings":"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"}
1
+ {"version":3,"file":"shader.js","sources":["../../src/shadow/shader.ts"],"sourcesContent":["import type { PBInsideFunctionScope, PBShaderExp } from '@zephyr3d/device';\r\nimport { ShaderHelper } from '../material/shader/helper';\r\nimport { LIGHT_TYPE_DIRECTIONAL } from '../values';\r\nimport { getShadowReceiverBiasFactor, isMaskedPerspectiveShadowLight } from './receiver_bias';\r\n\r\nfunction getShadowReceiverPerspectiveBiasScale(\r\n scope: PBInsideFunctionScope,\r\n lightType: number,\r\n linearDepth: PBShaderExp,\r\n farNearRatio: PBShaderExp\r\n) {\r\n const pb = scope.$builder;\r\n const alphaCutoff = (scope as PBInsideFunctionScope & { zAlphaCutoff?: PBShaderExp }).zAlphaCutoff;\r\n if (alphaCutoff && isMaskedPerspectiveShadowLight(scope, lightType)) {\r\n const cutoff = pb.clamp(alphaCutoff, 0, 1);\r\n // Perspective shadow cameras (spot / point) can over-amplify receiver bias at depth.\r\n // Keep masked layered geometry in a much tighter range so hair cards can still self-occlude.\r\n const cappedFarNearRatio = pb.min(farNearRatio, pb.mix(1.15, 1.6, cutoff));\r\n return pb.mix(1, cappedFarNearRatio, linearDepth) as PBShaderExp;\r\n }\r\n return pb.mix(1, farNearRatio, linearDepth) as PBShaderExp;\r\n}\r\n\r\n/**\r\n * Constant depth bias for a cascaded directional shadow.\r\n *\r\n * @remarks\r\n * The slope-scaled term that used to occupy `depthBiasValues.y` is gone: `.y`\r\n * now carries the normal offset distance, and normal offsetting (see\r\n * `ShaderHelper.applyShadowNormalOffset`) is what handles receiver slope. A\r\n * depth bias cannot, because the depth spanned by one texel diverges as the\r\n * surface turns away from the light.\r\n */\r\nexport function computeShadowBiasCSM(scope: PBInsideFunctionScope, _NdotL: PBShaderExp, split: PBShaderExp) {\r\n const pb = scope.$builder;\r\n return pb.mul(\r\n ShaderHelper.getDepthBiasValues(scope).x,\r\n getShadowReceiverBiasFactor(scope),\r\n ShaderHelper.getShadowCascadeBiasScale(scope, split)\r\n );\r\n}\r\n\r\n/**\r\n * Constant depth bias for a single-cascade shadow. See {@link computeShadowBiasCSM}\r\n * for why there is no slope-scaled term.\r\n */\r\nexport function computeShadowBias(\r\n lightType: number,\r\n scope: PBInsideFunctionScope,\r\n z: PBShaderExp,\r\n _NdotL: PBShaderExp,\r\n linear: boolean\r\n) {\r\n const pb = scope.$builder;\r\n const depthBiasParam = ShaderHelper.getDepthBiasValues(scope);\r\n const receiverBiasFactor = getShadowReceiverBiasFactor(scope);\r\n if (lightType === LIGHT_TYPE_DIRECTIONAL) {\r\n return pb.mul(depthBiasParam.x, receiverBiasFactor);\r\n } else {\r\n const nearFar = ShaderHelper.getShadowCameraParams(scope).xy;\r\n const linearDepth = linear ? z : ShaderHelper.nonLinearDepthToLinearNormalized(scope, z, nearFar);\r\n const biasScaleFactor = getShadowReceiverPerspectiveBiasScale(\r\n scope,\r\n lightType,\r\n linearDepth,\r\n depthBiasParam.w\r\n );\r\n let bias = pb.mul(depthBiasParam.x, receiverBiasFactor, biasScaleFactor) as PBShaderExp;\r\n if (isMaskedPerspectiveShadowLight(scope, lightType)) {\r\n const alphaCutoff = (scope as PBInsideFunctionScope & { zAlphaCutoff?: PBShaderExp }).zAlphaCutoff!;\r\n bias = pb.mul(bias, pb.mix(0.12, 0.22, pb.clamp(alphaCutoff, 0, 1))) as PBShaderExp;\r\n }\r\n return bias;\r\n }\r\n}\r\n"],"names":["getShadowReceiverPerspectiveBiasScale","scope","lightType","linearDepth","farNearRatio","pb","$builder","alphaCutoff","zAlphaCutoff","isMaskedPerspectiveShadowLight","cutoff","clamp","cappedFarNearRatio","min","mix","computeShadowBiasCSM","_NdotL","split","mul","ShaderHelper","getDepthBiasValues","x","getShadowReceiverBiasFactor","getShadowCascadeBiasScale","computeShadowBias","z","linear","depthBiasParam","receiverBiasFactor","LIGHT_TYPE_DIRECTIONAL","nearFar","getShadowCameraParams","xy","nonLinearDepthToLinearNormalized","biasScaleFactor","w","bias"],"mappings":";;;;AAKA,SAASA,sCACPC,KAA4B,EAC5BC,SAAiB,EACjBC,WAAwB,EACxBC,YAAyB,EAAA;IAEzB,MAAMC,EAAAA,GAAKJ,MAAMK,QAAQ;IACzB,MAAMC,WAAAA,GAAc,KAACN,CAAiEO,YAAY;IAClG,IAAID,WAAAA,IAAeE,8BAA+BR,CAAAA,KAAAA,EAAOC,SAAY,CAAA,EAAA;AACnE,QAAA,MAAMQ,MAASL,GAAAA,EAAAA,CAAGM,KAAK,CAACJ,aAAa,CAAG,EAAA,CAAA,CAAA;;;QAGxC,MAAMK,kBAAAA,GAAqBP,GAAGQ,GAAG,CAACT,cAAcC,EAAGS,CAAAA,GAAG,CAAC,IAAA,EAAM,GAAKJ,EAAAA,MAAAA,CAAAA,CAAAA;AAClE,QAAA,OAAOL,EAAGS,CAAAA,GAAG,CAAC,CAAA,EAAGF,kBAAoBT,EAAAA,WAAAA,CAAAA;AACvC;AACA,IAAA,OAAOE,EAAGS,CAAAA,GAAG,CAAC,CAAA,EAAGV,YAAcD,EAAAA,WAAAA,CAAAA;AACjC;AAEA;;;;;;;;;AASC,IACM,SAASY,oBAAAA,CAAqBd,KAA4B,EAAEe,MAAmB,EAAEC,KAAkB,EAAA;IACxG,MAAMZ,EAAAA,GAAKJ,MAAMK,QAAQ;AACzB,IAAA,OAAOD,EAAGa,CAAAA,GAAG,CACXC,YAAAA,CAAaC,kBAAkB,CAACnB,KAAAA,CAAAA,CAAOoB,CAAC,EACxCC,2BAA4BrB,CAAAA,KAAAA,CAAAA,EAC5BkB,YAAaI,CAAAA,yBAAyB,CAACtB,KAAOgB,EAAAA,KAAAA,CAAAA,CAAAA;AAElD;AAEA;;;IAIO,SAASO,iBAAAA,CACdtB,SAAiB,EACjBD,KAA4B,EAC5BwB,CAAc,EACdT,MAAmB,EACnBU,MAAe,EAAA;IAEf,MAAMrB,EAAAA,GAAKJ,MAAMK,QAAQ;IACzB,MAAMqB,cAAAA,GAAiBR,YAAaC,CAAAA,kBAAkB,CAACnB,KAAAA,CAAAA;AACvD,IAAA,MAAM2B,qBAAqBN,2BAA4BrB,CAAAA,KAAAA,CAAAA;AACvD,IAAA,IAAIC,cAAc2B,sBAAwB,EAAA;AACxC,QAAA,OAAOxB,EAAGa,CAAAA,GAAG,CAACS,cAAAA,CAAeN,CAAC,EAAEO,kBAAAA,CAAAA;KAC3B,MAAA;AACL,QAAA,MAAME,OAAUX,GAAAA,YAAAA,CAAaY,qBAAqB,CAAC9B,OAAO+B,EAAE;AAC5D,QAAA,MAAM7B,cAAcuB,MAASD,GAAAA,CAAAA,GAAIN,aAAac,gCAAgC,CAAChC,OAAOwB,CAAGK,EAAAA,OAAAA,CAAAA;AACzF,QAAA,MAAMI,kBAAkBlC,qCACtBC,CAAAA,KAAAA,EACAC,SACAC,EAAAA,WAAAA,EACAwB,eAAeQ,CAAC,CAAA;AAElB,QAAA,IAAIC,OAAO/B,EAAGa,CAAAA,GAAG,CAACS,cAAeN,CAAAA,CAAC,EAAEO,kBAAoBM,EAAAA,eAAAA,CAAAA;QACxD,IAAIzB,8BAAAA,CAA+BR,OAAOC,SAAY,CAAA,EAAA;YACpD,MAAMK,WAAAA,GAAc,KAACN,CAAiEO,YAAY;AAClG4B,YAAAA,IAAAA,GAAO/B,EAAGa,CAAAA,GAAG,CAACkB,IAAAA,EAAM/B,EAAGS,CAAAA,GAAG,CAAC,IAAA,EAAM,IAAMT,EAAAA,EAAAA,CAAGM,KAAK,CAACJ,aAAa,CAAG,EAAA,CAAA,CAAA,CAAA,CAAA;AAClE;QACA,OAAO6B,IAAAA;AACT;AACF;;;;"}
@@ -41,6 +41,95 @@ import { getDevice } from '../app/api.js';
41
41
  const device = getDevice();
42
42
  return device.type !== 'webgl' || REVERSE_Z && device.getDeviceCaps().framebufferCaps.supportDepth32float ? 'd32f' : 'd24s8';
43
43
  }
44
+ /**
45
+ * Number of geometry passes this implementation needs over the shadow casters.
46
+ *
47
+ * Almost every technique rasterises the casters once and post-processes the
48
+ * result, which is what {@link postRenderShadowMap} is for - it runs after the
49
+ * geometry is on the map and receives no scene state, so a blur or resolve
50
+ * fits there but a second draw of the casters does not.
51
+ *
52
+ * Deep opacity maps do need that second draw: the first pass establishes the
53
+ * frontmost depth per texel and the second accumulates transmittance in layers
54
+ * measured from it, so the casters must be rasterised again with the first
55
+ * pass's output already available. Returning a count above one makes
56
+ * `ShadowMapper.render` repeat the draw, calling {@link beginGeometryPass}
57
+ * before each one.
58
+ *
59
+ * @returns The number of passes; one unless overridden.
60
+ */ getGeometryPassCount(_shadowMapParams) {
61
+ return 1;
62
+ }
63
+ /**
64
+ * Whether this implementation can split a directional light into cascades.
65
+ *
66
+ * Cascades need one shadow map per split, which every depth-based technique
67
+ * gets by rendering into array layers. A technique whose storage is already
68
+ * spoken for - a deep opacity map packs layer depth and coverage into the one
69
+ * texture the receiver is allowed to bind - has nowhere to put them, and asks
70
+ * to be driven with a single split instead.
71
+ *
72
+ * @returns True unless overridden.
73
+ */ supportsCascades() {
74
+ return true;
75
+ }
76
+ /**
77
+ * Prepares state for the given geometry pass.
78
+ *
79
+ * Only called when {@link getGeometryPassCount} returns more than one. The
80
+ * index is zero-based. Implementations that vary the emitted shader between
81
+ * passes must fold the index into {@link getShaderHash} as well, since the
82
+ * shadow pass keys its programs on that hash.
83
+ *
84
+ * @param shadowMapParams - Current shadow map state.
85
+ * @param pass - Zero-based index of the geometry pass about to run.
86
+ */ beginGeometryPass(_shadowMapParams, _pass) {}
87
+ /**
88
+ * Last say on the render states a shadow caster is drawn with.
89
+ *
90
+ * Runs after the material has set up its own states, in the same spirit as the
91
+ * OIT hook on the transparent pass. Techniques that rasterise the casters once
92
+ * want the defaults - depth test and write on, no blending - and leave this
93
+ * alone. An accumulating pass wants the opposite and overrides it here rather
94
+ * than teaching the material about shadow implementations.
95
+ *
96
+ * @param shadowMapParams - Current shadow map state.
97
+ * @param stateSet - The state set about to be applied, already populated.
98
+ */ setCasterRenderStates(_shadowMapParams, _stateSet) {}
99
+ /**
100
+ * Declares any extra resources the caster shader needs, in bind group 0.
101
+ *
102
+ * The shadow map pass otherwise gives a caster only the camera and the light,
103
+ * which is all a technique needs when it just records depth. One that reads
104
+ * back its own earlier output - a deep opacity map measuring layers from the
105
+ * frontmost strand - declares that input here instead of the pass hard-coding
106
+ * knowledge of the implementation.
107
+ *
108
+ * @param scope - Global shader scope of the caster program.
109
+ * @param shadowMapParams - Current shadow map state.
110
+ */ declareCasterUniforms(_scope, _shadowMapParams) {}
111
+ /**
112
+ * Binds whatever {@link declareCasterUniforms} declared.
113
+ *
114
+ * @param bindGroup - Bind group 0 of the caster program.
115
+ * @param shadowMapParams - Current shadow map state.
116
+ */ applyCasterUniforms(_bindGroup, _shadowMapParams) {}
117
+ /**
118
+ * Whether casters should still be alpha-clipped before their shadow output.
119
+ *
120
+ * A depth-recording technique wants the clip: a fragment that fails it is not
121
+ * there, and letting it write depth would block light with geometry the eye
122
+ * never sees. A technique that records coverage instead wants the raw alpha,
123
+ * because the clip destroys exactly the quantity it is accumulating - a
124
+ * dithered caster survives with probability alpha and then contributes alpha,
125
+ * which accumulates alpha squared, and a cutoff drops the thin strands that
126
+ * carry most of a groom's transmittance.
127
+ *
128
+ * @param shadowMapParams - Current shadow map state.
129
+ * @returns True unless overridden.
130
+ */ clipsCasterAlpha(_shadowMapParams) {
131
+ return true;
132
+ }
44
133
  }
45
134
 
46
135
  export { ShadowImpl, getPackedShadowMapClearColor, getShadowMapFarthestDepth };
@@ -1 +1 @@
1
- {"version":3,"file":"shadow_impl.js","sources":["../../src/shadow/shadow_impl.ts"],"sourcesContent":["import type { PBInsideFunctionScope, PBShaderExp, TextureFormat } from '@zephyr3d/device';\r\nimport type { ShadowMapParams, ShadowMapType, ShadowMode } from './shadowmapper';\r\nimport { REVERSE_Z, Vector4, type Nullable } from '@zephyr3d/base';\r\nimport { isDeviceDepthShadow } from '../shaders/shadow';\r\nimport { getDevice } from '../app/api';\r\n\r\n/** @internal */\r\nexport function getShadowMapFarthestDepth(lightType: number) {\r\n return REVERSE_Z && isDeviceDepthShadow(lightType) ? 0 : 1;\r\n}\r\n\r\n/** @internal */\r\nexport function getPackedShadowMapClearColor(lightType: number) {\r\n // The RGBA8 decoder assigns the alpha channel a weight of 1, so the\r\n // normalized depth endpoints are represented by transparent/opaque black.\r\n return new Vector4(0, 0, 0, getShadowMapFarthestDepth(lightType));\r\n}\r\n\r\n/** @internal */\r\nexport abstract class ShadowImpl {\r\n protected _resourceDirty: boolean;\r\n constructor() {\r\n this._resourceDirty = true;\r\n }\r\n invalidateResource() {\r\n this._resourceDirty = true;\r\n }\r\n updateResources(shadowMapParams: ShadowMapParams) {\r\n this.doUpdateResources(shadowMapParams);\r\n }\r\n abstract getType(): ShadowMode;\r\n abstract getShadowMapBorder(shadowMapParams: ShadowMapParams): number;\r\n getShadowMapClearColor(shadowMapParams: ShadowMapParams) {\r\n const colorAttachment = shadowMapParams.shadowMapFramebuffer?.getColorAttachments()[0];\r\n // Empty texels must read as \"farthest\": under reverse-Z that is 0 for\r\n // device-encoded directional/rect maps, 1 for linear point/spot maps.\r\n const farthest = getShadowMapFarthestDepth(shadowMapParams.lightType);\r\n return colorAttachment\r\n ? colorAttachment.isFloatFormat()\r\n ? new Vector4(farthest, farthest, farthest, 1)\r\n : getPackedShadowMapClearColor(shadowMapParams.lightType)\r\n : null;\r\n }\r\n /**\r\n * Depth format for shadow maps rendered into a native depth attachment.\r\n *\r\n * Float depth everywhere except WebGL, where d24s8 is kept under\r\n * standard-Z (WebGL1 compatibility, no precision benefit anyway). Under\r\n * reverse-Z the WebGL2 float depth format is used so shadow maps share\r\n * the near-uniform precision distribution (full benefit with\r\n * EXT_clip_control active).\r\n * @internal\r\n */\r\n protected preferredShadowMapDepthFormat(): TextureFormat {\r\n const device = getDevice();\r\n return device.type !== 'webgl' ||\r\n (REVERSE_Z && device.getDeviceCaps().framebufferCaps.supportDepth32float)\r\n ? 'd32f'\r\n : 'd24s8';\r\n }\r\n abstract getParams(out?: Vector4): Vector4;\r\n abstract getShadowMap(shadowMapParams: ShadowMapParams): ShadowMapType;\r\n abstract postRenderShadowMap(shadowMapParams: ShadowMapParams): void;\r\n abstract getDepthScale(): number;\r\n abstract setDepthScale(val: number): void;\r\n abstract resourceDirty(): boolean;\r\n abstract doUpdateResources(shadowMapParams: ShadowMapParams): void;\r\n abstract getShaderHash(): string;\r\n abstract computeShadowMapDepth(\r\n shadowMapParams: ShadowMapParams,\r\n scope: PBInsideFunctionScope,\r\n worldPos: PBShaderExp\r\n ): PBShaderExp;\r\n abstract computeShadow(\r\n shadowMapParams: ShadowMapParams,\r\n scope: PBInsideFunctionScope,\r\n shadowVertex: PBShaderExp,\r\n NdotL: PBShaderExp\r\n ): PBShaderExp;\r\n abstract computeShadowCSM(\r\n shadowMapParams: ShadowMapParams,\r\n scope: PBInsideFunctionScope,\r\n shadowVertex: PBShaderExp,\r\n NdotL: PBShaderExp,\r\n split: PBShaderExp\r\n ): PBShaderExp;\r\n abstract getShadowMapColorFormat(shadowMapParams: ShadowMapParams): Nullable<TextureFormat>;\r\n abstract getShadowMapDepthFormat(shadowMapParams: ShadowMapParams): TextureFormat;\r\n abstract useNativeShadowMap(shadowMapParams: ShadowMapParams): boolean;\r\n}\r\n"],"names":["getShadowMapFarthestDepth","lightType","REVERSE_Z","isDeviceDepthShadow","getPackedShadowMapClearColor","Vector4","ShadowImpl","_resourceDirty","invalidateResource","updateResources","shadowMapParams","doUpdateResources","getShadowMapClearColor","colorAttachment","shadowMapFramebuffer","getColorAttachments","farthest","isFloatFormat","device","getDevice","type","getDeviceCaps","framebufferCaps","supportDepth32float"],"mappings":";;;;AAMA,iBACO,SAASA,yBAAAA,CAA0BC,SAAiB,EAAA;IACzD,OAAOC,SAAAA,IAAaC,mBAAoBF,CAAAA,SAAAA,CAAAA,GAAa,CAAI,GAAA,CAAA;AAC3D;AAEA,iBACO,SAASG,4BAAAA,CAA6BH,SAAiB,EAAA;;;AAG5D,IAAA,OAAO,IAAII,OAAAA,CAAQ,CAAG,EAAA,CAAA,EAAG,GAAGL,yBAA0BC,CAAAA,SAAAA,CAAAA,CAAAA;AACxD;AAEA,iBACO,MAAeK,UAAAA,CAAAA;IACVC,cAAwB;IAClC,WAAc,EAAA;QACZ,IAAI,CAACA,cAAc,GAAG,IAAA;AACxB;IACAC,kBAAqB,GAAA;QACnB,IAAI,CAACD,cAAc,GAAG,IAAA;AACxB;AACAE,IAAAA,eAAAA,CAAgBC,eAAgC,EAAE;QAChD,IAAI,CAACC,iBAAiB,CAACD,eAAAA,CAAAA;AACzB;AAGAE,IAAAA,sBAAAA,CAAuBF,eAAgC,EAAE;AACvD,QAAA,MAAMG,kBAAkBH,eAAgBI,CAAAA,oBAAoB,EAAEC,mBAAAA,EAAqB,CAAC,CAAE,CAAA;;;QAGtF,MAAMC,QAAAA,GAAWhB,yBAA0BU,CAAAA,eAAAA,CAAgBT,SAAS,CAAA;AACpE,QAAA,OAAOY,eACHA,GAAAA,eAAAA,CAAgBI,aAAa,EAAA,GAC3B,IAAIZ,OAAAA,CAAQW,QAAUA,EAAAA,QAAAA,EAAUA,QAAU,EAAA,CAAA,CAAA,GAC1CZ,4BAA6BM,CAAAA,eAAAA,CAAgBT,SAAS,CACxD,GAAA,IAAA;AACN;AACA;;;;;;;;;AASC,MACD,6BAAyD,GAAA;AACvD,QAAA,MAAMiB,MAASC,GAAAA,SAAAA,EAAAA;AACf,QAAA,OAAOD,MAAOE,CAAAA,IAAI,KAAK,OAAA,IACpBlB,SAAagB,IAAAA,MAAAA,CAAOG,aAAa,EAAA,CAAGC,eAAe,CAACC,mBAAmB,GACtE,MACA,GAAA,OAAA;AACN;AA8BF;;;;"}
1
+ {"version":3,"file":"shadow_impl.js","sources":["../../src/shadow/shadow_impl.ts"],"sourcesContent":["import type {\r\n BindGroup,\r\n PBGlobalScope,\r\n PBInsideFunctionScope,\r\n PBShaderExp,\r\n RenderStateSet,\r\n TextureFormat\r\n} from '@zephyr3d/device';\r\nimport type { ShadowMapParams, ShadowMapType, ShadowMode } from './shadowmapper';\r\nimport { REVERSE_Z, Vector4, type Nullable } from '@zephyr3d/base';\r\nimport { isDeviceDepthShadow } from '../shaders/shadow';\r\nimport { getDevice } from '../app/api';\r\n\r\n/** @internal */\r\nexport function getShadowMapFarthestDepth(lightType: number) {\r\n return REVERSE_Z && isDeviceDepthShadow(lightType) ? 0 : 1;\r\n}\r\n\r\n/** @internal */\r\nexport function getPackedShadowMapClearColor(lightType: number) {\r\n // The RGBA8 decoder assigns the alpha channel a weight of 1, so the\r\n // normalized depth endpoints are represented by transparent/opaque black.\r\n return new Vector4(0, 0, 0, getShadowMapFarthestDepth(lightType));\r\n}\r\n\r\n/** @internal */\r\nexport abstract class ShadowImpl {\r\n protected _resourceDirty: boolean;\r\n constructor() {\r\n this._resourceDirty = true;\r\n }\r\n invalidateResource() {\r\n this._resourceDirty = true;\r\n }\r\n updateResources(shadowMapParams: ShadowMapParams) {\r\n this.doUpdateResources(shadowMapParams);\r\n }\r\n abstract getType(): ShadowMode;\r\n abstract getShadowMapBorder(shadowMapParams: ShadowMapParams): number;\r\n getShadowMapClearColor(shadowMapParams: ShadowMapParams) {\r\n const colorAttachment = shadowMapParams.shadowMapFramebuffer?.getColorAttachments()[0];\r\n // Empty texels must read as \"farthest\": under reverse-Z that is 0 for\r\n // device-encoded directional/rect maps, 1 for linear point/spot maps.\r\n const farthest = getShadowMapFarthestDepth(shadowMapParams.lightType);\r\n return colorAttachment\r\n ? colorAttachment.isFloatFormat()\r\n ? new Vector4(farthest, farthest, farthest, 1)\r\n : getPackedShadowMapClearColor(shadowMapParams.lightType)\r\n : null;\r\n }\r\n /**\r\n * Depth format for shadow maps rendered into a native depth attachment.\r\n *\r\n * Float depth everywhere except WebGL, where d24s8 is kept under\r\n * standard-Z (WebGL1 compatibility, no precision benefit anyway). Under\r\n * reverse-Z the WebGL2 float depth format is used so shadow maps share\r\n * the near-uniform precision distribution (full benefit with\r\n * EXT_clip_control active).\r\n * @internal\r\n */\r\n protected preferredShadowMapDepthFormat(): TextureFormat {\r\n const device = getDevice();\r\n return device.type !== 'webgl' ||\r\n (REVERSE_Z && device.getDeviceCaps().framebufferCaps.supportDepth32float)\r\n ? 'd32f'\r\n : 'd24s8';\r\n }\r\n abstract getParams(out?: Vector4): Vector4;\r\n /**\r\n * Number of geometry passes this implementation needs over the shadow casters.\r\n *\r\n * Almost every technique rasterises the casters once and post-processes the\r\n * result, which is what {@link postRenderShadowMap} is for - it runs after the\r\n * geometry is on the map and receives no scene state, so a blur or resolve\r\n * fits there but a second draw of the casters does not.\r\n *\r\n * Deep opacity maps do need that second draw: the first pass establishes the\r\n * frontmost depth per texel and the second accumulates transmittance in layers\r\n * measured from it, so the casters must be rasterised again with the first\r\n * pass's output already available. Returning a count above one makes\r\n * `ShadowMapper.render` repeat the draw, calling {@link beginGeometryPass}\r\n * before each one.\r\n *\r\n * @returns The number of passes; one unless overridden.\r\n */\r\n getGeometryPassCount(_shadowMapParams: ShadowMapParams): number {\r\n return 1;\r\n }\r\n /**\r\n * Whether this implementation can split a directional light into cascades.\r\n *\r\n * Cascades need one shadow map per split, which every depth-based technique\r\n * gets by rendering into array layers. A technique whose storage is already\r\n * spoken for - a deep opacity map packs layer depth and coverage into the one\r\n * texture the receiver is allowed to bind - has nowhere to put them, and asks\r\n * to be driven with a single split instead.\r\n *\r\n * @returns True unless overridden.\r\n */\r\n supportsCascades(): boolean {\r\n return true;\r\n }\r\n /**\r\n * Prepares state for the given geometry pass.\r\n *\r\n * Only called when {@link getGeometryPassCount} returns more than one. The\r\n * index is zero-based. Implementations that vary the emitted shader between\r\n * passes must fold the index into {@link getShaderHash} as well, since the\r\n * shadow pass keys its programs on that hash.\r\n *\r\n * @param shadowMapParams - Current shadow map state.\r\n * @param pass - Zero-based index of the geometry pass about to run.\r\n */\r\n beginGeometryPass(_shadowMapParams: ShadowMapParams, _pass: number): void {}\r\n /**\r\n * Last say on the render states a shadow caster is drawn with.\r\n *\r\n * Runs after the material has set up its own states, in the same spirit as the\r\n * OIT hook on the transparent pass. Techniques that rasterise the casters once\r\n * want the defaults - depth test and write on, no blending - and leave this\r\n * alone. An accumulating pass wants the opposite and overrides it here rather\r\n * than teaching the material about shadow implementations.\r\n *\r\n * @param shadowMapParams - Current shadow map state.\r\n * @param stateSet - The state set about to be applied, already populated.\r\n */\r\n setCasterRenderStates(_shadowMapParams: ShadowMapParams, _stateSet: RenderStateSet): void {}\r\n /**\r\n * Declares any extra resources the caster shader needs, in bind group 0.\r\n *\r\n * The shadow map pass otherwise gives a caster only the camera and the light,\r\n * which is all a technique needs when it just records depth. One that reads\r\n * back its own earlier output - a deep opacity map measuring layers from the\r\n * frontmost strand - declares that input here instead of the pass hard-coding\r\n * knowledge of the implementation.\r\n *\r\n * @param scope - Global shader scope of the caster program.\r\n * @param shadowMapParams - Current shadow map state.\r\n */\r\n declareCasterUniforms(_scope: PBGlobalScope, _shadowMapParams: ShadowMapParams): void {}\r\n /**\r\n * Binds whatever {@link declareCasterUniforms} declared.\r\n *\r\n * @param bindGroup - Bind group 0 of the caster program.\r\n * @param shadowMapParams - Current shadow map state.\r\n */\r\n applyCasterUniforms(_bindGroup: BindGroup, _shadowMapParams: ShadowMapParams): void {}\r\n /**\r\n * Whether casters should still be alpha-clipped before their shadow output.\r\n *\r\n * A depth-recording technique wants the clip: a fragment that fails it is not\r\n * there, and letting it write depth would block light with geometry the eye\r\n * never sees. A technique that records coverage instead wants the raw alpha,\r\n * because the clip destroys exactly the quantity it is accumulating - a\r\n * dithered caster survives with probability alpha and then contributes alpha,\r\n * which accumulates alpha squared, and a cutoff drops the thin strands that\r\n * carry most of a groom's transmittance.\r\n *\r\n * @param shadowMapParams - Current shadow map state.\r\n * @returns True unless overridden.\r\n */\r\n clipsCasterAlpha(_shadowMapParams: ShadowMapParams): boolean {\r\n return true;\r\n }\r\n abstract getShadowMap(shadowMapParams: ShadowMapParams): ShadowMapType;\r\n abstract postRenderShadowMap(shadowMapParams: ShadowMapParams): void;\r\n abstract getDepthScale(): number;\r\n abstract setDepthScale(val: number): void;\r\n abstract resourceDirty(): boolean;\r\n abstract doUpdateResources(shadowMapParams: ShadowMapParams): void;\r\n abstract getShaderHash(): string;\r\n /**\r\n * Fragment output for a shadow caster.\r\n *\r\n * @param shadowMapParams - Current shadow map state.\r\n * @param scope - Caster fragment scope.\r\n * @param worldPos - World position of the fragment.\r\n * @param alpha - The material's own alpha, or null when it computes no fragment\r\n * colour and the fragment is therefore fully opaque. Depth-recording techniques\r\n * ignore it - by the time this runs the fragment has already survived whatever\r\n * alpha test applies - but one that records absorption needs the value itself.\r\n * It must be passed rather than read out of `scope`, because the caller's\r\n * `outColor` is local to the material's own shader function and defaults to a\r\n * zero vector when no colour was computed, which is indistinguishable from a\r\n * genuinely transparent fragment.\r\n */\r\n abstract computeShadowMapDepth(\r\n shadowMapParams: ShadowMapParams,\r\n scope: PBInsideFunctionScope,\r\n worldPos: PBShaderExp,\r\n alpha: Nullable<PBShaderExp>\r\n ): PBShaderExp;\r\n abstract computeShadow(\r\n shadowMapParams: ShadowMapParams,\r\n scope: PBInsideFunctionScope,\r\n shadowVertex: PBShaderExp,\r\n NdotL: PBShaderExp\r\n ): PBShaderExp;\r\n abstract computeShadowCSM(\r\n shadowMapParams: ShadowMapParams,\r\n scope: PBInsideFunctionScope,\r\n shadowVertex: PBShaderExp,\r\n NdotL: PBShaderExp,\r\n split: PBShaderExp\r\n ): PBShaderExp;\r\n abstract getShadowMapColorFormat(shadowMapParams: ShadowMapParams): Nullable<TextureFormat>;\r\n abstract getShadowMapDepthFormat(shadowMapParams: ShadowMapParams): TextureFormat;\r\n abstract useNativeShadowMap(shadowMapParams: ShadowMapParams): boolean;\r\n}\r\n"],"names":["getShadowMapFarthestDepth","lightType","REVERSE_Z","isDeviceDepthShadow","getPackedShadowMapClearColor","Vector4","ShadowImpl","_resourceDirty","invalidateResource","updateResources","shadowMapParams","doUpdateResources","getShadowMapClearColor","colorAttachment","shadowMapFramebuffer","getColorAttachments","farthest","isFloatFormat","device","getDevice","type","getDeviceCaps","framebufferCaps","supportDepth32float","getGeometryPassCount","_shadowMapParams","supportsCascades","beginGeometryPass","_pass","setCasterRenderStates","_stateSet","declareCasterUniforms","_scope","applyCasterUniforms","_bindGroup","clipsCasterAlpha"],"mappings":";;;;AAaA,iBACO,SAASA,yBAAAA,CAA0BC,SAAiB,EAAA;IACzD,OAAOC,SAAAA,IAAaC,mBAAoBF,CAAAA,SAAAA,CAAAA,GAAa,CAAI,GAAA,CAAA;AAC3D;AAEA,iBACO,SAASG,4BAAAA,CAA6BH,SAAiB,EAAA;;;AAG5D,IAAA,OAAO,IAAII,OAAAA,CAAQ,CAAG,EAAA,CAAA,EAAG,GAAGL,yBAA0BC,CAAAA,SAAAA,CAAAA,CAAAA;AACxD;AAEA,iBACO,MAAeK,UAAAA,CAAAA;IACVC,cAAwB;IAClC,WAAc,EAAA;QACZ,IAAI,CAACA,cAAc,GAAG,IAAA;AACxB;IACAC,kBAAqB,GAAA;QACnB,IAAI,CAACD,cAAc,GAAG,IAAA;AACxB;AACAE,IAAAA,eAAAA,CAAgBC,eAAgC,EAAE;QAChD,IAAI,CAACC,iBAAiB,CAACD,eAAAA,CAAAA;AACzB;AAGAE,IAAAA,sBAAAA,CAAuBF,eAAgC,EAAE;AACvD,QAAA,MAAMG,kBAAkBH,eAAgBI,CAAAA,oBAAoB,EAAEC,mBAAAA,EAAqB,CAAC,CAAE,CAAA;;;QAGtF,MAAMC,QAAAA,GAAWhB,yBAA0BU,CAAAA,eAAAA,CAAgBT,SAAS,CAAA;AACpE,QAAA,OAAOY,eACHA,GAAAA,eAAAA,CAAgBI,aAAa,EAAA,GAC3B,IAAIZ,OAAAA,CAAQW,QAAUA,EAAAA,QAAAA,EAAUA,QAAU,EAAA,CAAA,CAAA,GAC1CZ,4BAA6BM,CAAAA,eAAAA,CAAgBT,SAAS,CACxD,GAAA,IAAA;AACN;AACA;;;;;;;;;AASC,MACD,6BAAyD,GAAA;AACvD,QAAA,MAAMiB,MAASC,GAAAA,SAAAA,EAAAA;AACf,QAAA,OAAOD,MAAOE,CAAAA,IAAI,KAAK,OAAA,IACpBlB,SAAagB,IAAAA,MAAAA,CAAOG,aAAa,EAAA,CAAGC,eAAe,CAACC,mBAAmB,GACtE,MACA,GAAA,OAAA;AACN;AAEA;;;;;;;;;;;;;;;;MAiBAC,oBAAAA,CAAqBC,gBAAiC,EAAU;QAC9D,OAAO,CAAA;AACT;AACA;;;;;;;;;;AAUC,MACDC,gBAA4B,GAAA;QAC1B,OAAO,IAAA;AACT;AACA;;;;;;;;;;AAUC,MACDC,iBAAkBF,CAAAA,gBAAiC,EAAEG,KAAa,EAAQ;AAC1E;;;;;;;;;;;AAWC,MACDC,qBAAsBJ,CAAAA,gBAAiC,EAAEK,SAAyB,EAAQ;AAC1F;;;;;;;;;;;AAWC,MACDC,qBAAsBC,CAAAA,MAAqB,EAAEP,gBAAiC,EAAQ;AACtF;;;;;AAKC,MACDQ,mBAAoBC,CAAAA,UAAqB,EAAET,gBAAiC,EAAQ;AACpF;;;;;;;;;;;;;MAcAU,gBAAAA,CAAiBV,gBAAiC,EAAW;QAC3D,OAAO,IAAA;AACT;AA6CF;;;;"}
@@ -170,7 +170,7 @@ import { Disposable, AABB, DWeakRef } from '@zephyr3d/base';
170
170
  return this._staticCasters.some((entry)=>entry.ref.get() === caster) || this._dynamicCasters.some((entry)=>entry.ref.get() === caster);
171
171
  }
172
172
  /** @internal */ isShadowCaster(caster) {
173
- return !caster.disposed && (caster.isMesh() || caster.isClipmapTerrain());
173
+ return !caster.disposed && (caster.isMesh() || caster.isClipmapTerrain() || caster.isHair());
174
174
  }
175
175
  /** @internal */ getCasterAABB(caster) {
176
176
  const bbox = caster.getWorldBoundingVolume()?.toAABB();
@@ -1 +1 @@
1
- {"version":3,"file":"shadow_region.js","sources":["../../src/shadow/shadow_region.ts"],"sourcesContent":["import type { Nullable } from '@zephyr3d/base';\r\nimport { AABB, Disposable, DWeakRef } from '@zephyr3d/base';\r\nimport type { SceneNode } from '../scene/scene_node';\r\n\r\ntype StaticShadowCasterEntry = {\r\n ref: DWeakRef<SceneNode>;\r\n aabb: AABB;\r\n};\r\n\r\ntype DynamicShadowCasterEntry = {\r\n ref: DWeakRef<SceneNode>;\r\n callback: (node: SceneNode) => void;\r\n};\r\n\r\n/**\r\n * Maintains the world-space region used by directional light shadow maps.\r\n *\r\n * @public\r\n */\r\nexport class ShadowRegion extends Disposable {\r\n /** @internal */\r\n private _manualRegion: Nullable<AABB>;\r\n /** @internal */\r\n private readonly _staticRegion: AABB;\r\n /** @internal */\r\n private readonly _dynamicRegion: AABB;\r\n /** @internal */\r\n private readonly _region: AABB;\r\n /** @internal */\r\n private readonly _staticCasters: StaticShadowCasterEntry[];\r\n /** @internal */\r\n private readonly _dynamicCasters: DynamicShadowCasterEntry[];\r\n\r\n /**\r\n * Creates a shadow region.\r\n *\r\n * @param region - Optional manual world-space AABB used to initialize the region.\r\n */\r\n constructor(region?: Nullable<AABB>) {\r\n super();\r\n this._manualRegion = region ? new AABB(region) : null;\r\n this._staticRegion = new AABB().beginExtend();\r\n this._dynamicRegion = new AABB().beginExtend();\r\n this._region = new AABB().beginExtend();\r\n this._staticCasters = [];\r\n this._dynamicCasters = [];\r\n this.updateRegion();\r\n }\r\n\r\n /**\r\n * Final world-space shadow region.\r\n *\r\n * @returns The union of the manual, static caster, and dynamic caster regions, or `null` if no valid region exists.\r\n */\r\n get region(): Nullable<AABB> {\r\n return this._region.isValid() ? this._region : null;\r\n }\r\n\r\n /**\r\n * Region contributed by manually assigned AABB.\r\n *\r\n * @returns The manually assigned region, or `null` if no manual region is set.\r\n */\r\n get manualRegion(): Nullable<AABB> {\r\n return this._manualRegion;\r\n }\r\n\r\n /**\r\n * Region contributed by static shadow casters.\r\n *\r\n * @returns The combined static caster region, or `null` if no valid static caster region exists.\r\n */\r\n get staticRegion(): Nullable<AABB> {\r\n return this._staticRegion.isValid() ? this._staticRegion : null;\r\n }\r\n\r\n /**\r\n * Region contributed by dynamic shadow casters.\r\n *\r\n * @returns The combined dynamic caster region, or `null` if no valid dynamic caster region exists.\r\n */\r\n get dynamicRegion(): Nullable<AABB> {\r\n return this._dynamicRegion.isValid() ? this._dynamicRegion : null;\r\n }\r\n\r\n /**\r\n * Assigns a manual world-space AABB to the region.\r\n *\r\n * @param region - The manual world-space AABB to use, or `null` to clear the manual region.\r\n */\r\n setRegion(region: Nullable<AABB>) {\r\n this._manualRegion = region ? new AABB(region) : null;\r\n this.updateRegion();\r\n }\r\n\r\n /**\r\n * Adds a static shadow caster using a snapshot of its current world-space AABB.\r\n *\r\n * @param caster - The mesh or clipmap terrain node to include as a static caster.\r\n * @returns `this` for chaining.\r\n */\r\n addStaticCaster(caster: SceneNode): this {\r\n if (!this.isShadowCaster(caster) || this.hasCaster(caster)) {\r\n return this;\r\n }\r\n const aabb = this.getCasterAABB(caster);\r\n if (!aabb) {\r\n return this;\r\n }\r\n this._staticCasters.push({\r\n ref: new DWeakRef(caster),\r\n aabb\r\n });\r\n this.extendRegion(this._staticRegion, aabb);\r\n this.updateRegion();\r\n return this;\r\n }\r\n\r\n /**\r\n * Adds a dynamic shadow caster and tracks its bounding volume changes.\r\n *\r\n * @param caster - The mesh or clipmap terrain node to include as a dynamic caster.\r\n * @returns `this` for chaining.\r\n */\r\n addDynamicCaster(caster: SceneNode): this {\r\n if (!this.isShadowCaster(caster) || this.hasCaster(caster)) {\r\n return this;\r\n }\r\n const callback = () => {\r\n this.rebuildDynamicRegion();\r\n };\r\n caster.on('bvchanged', callback);\r\n this._dynamicCasters.push({\r\n ref: new DWeakRef(caster),\r\n callback\r\n });\r\n const aabb = this.getCasterAABB(caster);\r\n if (aabb) {\r\n this.extendRegion(this._dynamicRegion, aabb);\r\n this.updateRegion();\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Removes a previously added shadow caster.\r\n *\r\n * @param caster - The caster node to remove from the static or dynamic caster list.\r\n * @returns `this` for chaining.\r\n */\r\n removeCaster(caster: SceneNode): this {\r\n let removed = false;\r\n let changed = false;\r\n for (let i = this._staticCasters.length - 1; i >= 0; i--) {\r\n const entry = this._staticCasters[i];\r\n const node = entry.ref.get();\r\n if (!node || node === caster) {\r\n entry.ref.dispose();\r\n this._staticCasters.splice(i, 1);\r\n changed = true;\r\n removed ||= node === caster;\r\n }\r\n }\r\n for (let i = this._dynamicCasters.length - 1; i >= 0; i--) {\r\n const entry = this._dynamicCasters[i];\r\n const node = entry.ref.get();\r\n if (!node || node === caster) {\r\n (node ?? caster).off('bvchanged', entry.callback);\r\n entry.ref.dispose();\r\n this._dynamicCasters.splice(i, 1);\r\n changed = true;\r\n removed ||= node === caster;\r\n }\r\n }\r\n if (changed) {\r\n this.rebuildStaticRegion();\r\n this.rebuildDynamicRegion();\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Removes all shadow casters while keeping the manual region.\r\n */\r\n clearCasters() {\r\n for (const entry of this._dynamicCasters) {\r\n entry.ref.get()?.off('bvchanged', entry.callback);\r\n entry.ref.dispose();\r\n }\r\n for (const entry of this._staticCasters) {\r\n entry.ref.dispose();\r\n }\r\n this._staticCasters.length = 0;\r\n this._dynamicCasters.length = 0;\r\n this._staticRegion.beginExtend();\r\n this._dynamicRegion.beginExtend();\r\n this.updateRegion();\r\n }\r\n\r\n /**\r\n * Clears the manual region and all shadow casters.\r\n *\r\n * @returns `this` for chaining.\r\n */\r\n clear(): this {\r\n this._manualRegion = null;\r\n this.clearCasters();\r\n return this;\r\n }\r\n\r\n /** {@inheritDoc Disposable.onDispose} */\r\n protected onDispose(): void {\r\n this.clear();\r\n }\r\n\r\n /** @internal */\r\n private hasCaster(caster: SceneNode): boolean {\r\n return (\r\n this._staticCasters.some((entry) => entry.ref.get() === caster) ||\r\n this._dynamicCasters.some((entry) => entry.ref.get() === caster)\r\n );\r\n }\r\n\r\n /** @internal */\r\n private isShadowCaster(caster: SceneNode): boolean {\r\n return !caster.disposed && (caster.isMesh() || caster.isClipmapTerrain());\r\n }\r\n\r\n /** @internal */\r\n private getCasterAABB(caster: SceneNode): Nullable<AABB> {\r\n const bbox = caster.getWorldBoundingVolume()?.toAABB();\r\n return bbox?.isValid() ? new AABB(bbox) : null;\r\n }\r\n\r\n /** @internal */\r\n private rebuildStaticRegion() {\r\n this._staticRegion.beginExtend();\r\n for (let i = this._staticCasters.length - 1; i >= 0; i--) {\r\n const entry = this._staticCasters[i];\r\n if (!entry.ref.get()) {\r\n entry.ref.dispose();\r\n this._staticCasters.splice(i, 1);\r\n } else {\r\n this.extendRegion(this._staticRegion, entry.aabb);\r\n }\r\n }\r\n this.updateRegion();\r\n }\r\n\r\n /** @internal */\r\n private rebuildDynamicRegion() {\r\n this._dynamicRegion.beginExtend();\r\n for (let i = this._dynamicCasters.length - 1; i >= 0; i--) {\r\n const entry = this._dynamicCasters[i];\r\n const caster = entry.ref.get();\r\n if (!caster || !this.isShadowCaster(caster)) {\r\n caster?.off('bvchanged', entry.callback);\r\n entry.ref.dispose();\r\n this._dynamicCasters.splice(i, 1);\r\n continue;\r\n }\r\n const aabb = this.getCasterAABB(caster);\r\n if (aabb) {\r\n this.extendRegion(this._dynamicRegion, aabb);\r\n }\r\n }\r\n this.updateRegion();\r\n }\r\n\r\n /** @internal */\r\n private updateRegion() {\r\n this._region.beginExtend();\r\n if (this._manualRegion?.isValid()) {\r\n this.extendRegion(this._region, this._manualRegion);\r\n }\r\n if (this._staticRegion.isValid()) {\r\n this.extendRegion(this._region, this._staticRegion);\r\n }\r\n if (this._dynamicRegion.isValid()) {\r\n this.extendRegion(this._region, this._dynamicRegion);\r\n }\r\n }\r\n\r\n /** @internal */\r\n private extendRegion(region: AABB, aabb: AABB) {\r\n region.extend(aabb.minPoint);\r\n region.extend(aabb.maxPoint);\r\n }\r\n}\r\n"],"names":["ShadowRegion","Disposable","region","_manualRegion","AABB","_staticRegion","beginExtend","_dynamicRegion","_region","_staticCasters","_dynamicCasters","updateRegion","isValid","manualRegion","staticRegion","dynamicRegion","setRegion","addStaticCaster","caster","isShadowCaster","hasCaster","aabb","getCasterAABB","push","ref","DWeakRef","extendRegion","addDynamicCaster","callback","rebuildDynamicRegion","on","removeCaster","changed","i","length","entry","node","get","dispose","splice","off","rebuildStaticRegion","clearCasters","clear","some","disposed","isMesh","isClipmapTerrain","bbox","getWorldBoundingVolume","toAABB","extend","minPoint","maxPoint"],"mappings":"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"}
1
+ {"version":3,"file":"shadow_region.js","sources":["../../src/shadow/shadow_region.ts"],"sourcesContent":["import type { Nullable } from '@zephyr3d/base';\r\nimport { AABB, Disposable, DWeakRef } from '@zephyr3d/base';\r\nimport type { SceneNode } from '../scene/scene_node';\r\n\r\ntype StaticShadowCasterEntry = {\r\n ref: DWeakRef<SceneNode>;\r\n aabb: AABB;\r\n};\r\n\r\ntype DynamicShadowCasterEntry = {\r\n ref: DWeakRef<SceneNode>;\r\n callback: (node: SceneNode) => void;\r\n};\r\n\r\n/**\r\n * Maintains the world-space region used by directional light shadow maps.\r\n *\r\n * @public\r\n */\r\nexport class ShadowRegion extends Disposable {\r\n /** @internal */\r\n private _manualRegion: Nullable<AABB>;\r\n /** @internal */\r\n private readonly _staticRegion: AABB;\r\n /** @internal */\r\n private readonly _dynamicRegion: AABB;\r\n /** @internal */\r\n private readonly _region: AABB;\r\n /** @internal */\r\n private readonly _staticCasters: StaticShadowCasterEntry[];\r\n /** @internal */\r\n private readonly _dynamicCasters: DynamicShadowCasterEntry[];\r\n\r\n /**\r\n * Creates a shadow region.\r\n *\r\n * @param region - Optional manual world-space AABB used to initialize the region.\r\n */\r\n constructor(region?: Nullable<AABB>) {\r\n super();\r\n this._manualRegion = region ? new AABB(region) : null;\r\n this._staticRegion = new AABB().beginExtend();\r\n this._dynamicRegion = new AABB().beginExtend();\r\n this._region = new AABB().beginExtend();\r\n this._staticCasters = [];\r\n this._dynamicCasters = [];\r\n this.updateRegion();\r\n }\r\n\r\n /**\r\n * Final world-space shadow region.\r\n *\r\n * @returns The union of the manual, static caster, and dynamic caster regions, or `null` if no valid region exists.\r\n */\r\n get region(): Nullable<AABB> {\r\n return this._region.isValid() ? this._region : null;\r\n }\r\n\r\n /**\r\n * Region contributed by manually assigned AABB.\r\n *\r\n * @returns The manually assigned region, or `null` if no manual region is set.\r\n */\r\n get manualRegion(): Nullable<AABB> {\r\n return this._manualRegion;\r\n }\r\n\r\n /**\r\n * Region contributed by static shadow casters.\r\n *\r\n * @returns The combined static caster region, or `null` if no valid static caster region exists.\r\n */\r\n get staticRegion(): Nullable<AABB> {\r\n return this._staticRegion.isValid() ? this._staticRegion : null;\r\n }\r\n\r\n /**\r\n * Region contributed by dynamic shadow casters.\r\n *\r\n * @returns The combined dynamic caster region, or `null` if no valid dynamic caster region exists.\r\n */\r\n get dynamicRegion(): Nullable<AABB> {\r\n return this._dynamicRegion.isValid() ? this._dynamicRegion : null;\r\n }\r\n\r\n /**\r\n * Assigns a manual world-space AABB to the region.\r\n *\r\n * @param region - The manual world-space AABB to use, or `null` to clear the manual region.\r\n */\r\n setRegion(region: Nullable<AABB>) {\r\n this._manualRegion = region ? new AABB(region) : null;\r\n this.updateRegion();\r\n }\r\n\r\n /**\r\n * Adds a static shadow caster using a snapshot of its current world-space AABB.\r\n *\r\n * @param caster - The mesh or clipmap terrain node to include as a static caster.\r\n * @returns `this` for chaining.\r\n */\r\n addStaticCaster(caster: SceneNode): this {\r\n if (!this.isShadowCaster(caster) || this.hasCaster(caster)) {\r\n return this;\r\n }\r\n const aabb = this.getCasterAABB(caster);\r\n if (!aabb) {\r\n return this;\r\n }\r\n this._staticCasters.push({\r\n ref: new DWeakRef(caster),\r\n aabb\r\n });\r\n this.extendRegion(this._staticRegion, aabb);\r\n this.updateRegion();\r\n return this;\r\n }\r\n\r\n /**\r\n * Adds a dynamic shadow caster and tracks its bounding volume changes.\r\n *\r\n * @param caster - The mesh or clipmap terrain node to include as a dynamic caster.\r\n * @returns `this` for chaining.\r\n */\r\n addDynamicCaster(caster: SceneNode): this {\r\n if (!this.isShadowCaster(caster) || this.hasCaster(caster)) {\r\n return this;\r\n }\r\n const callback = () => {\r\n this.rebuildDynamicRegion();\r\n };\r\n caster.on('bvchanged', callback);\r\n this._dynamicCasters.push({\r\n ref: new DWeakRef(caster),\r\n callback\r\n });\r\n const aabb = this.getCasterAABB(caster);\r\n if (aabb) {\r\n this.extendRegion(this._dynamicRegion, aabb);\r\n this.updateRegion();\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Removes a previously added shadow caster.\r\n *\r\n * @param caster - The caster node to remove from the static or dynamic caster list.\r\n * @returns `this` for chaining.\r\n */\r\n removeCaster(caster: SceneNode): this {\r\n let removed = false;\r\n let changed = false;\r\n for (let i = this._staticCasters.length - 1; i >= 0; i--) {\r\n const entry = this._staticCasters[i];\r\n const node = entry.ref.get();\r\n if (!node || node === caster) {\r\n entry.ref.dispose();\r\n this._staticCasters.splice(i, 1);\r\n changed = true;\r\n removed ||= node === caster;\r\n }\r\n }\r\n for (let i = this._dynamicCasters.length - 1; i >= 0; i--) {\r\n const entry = this._dynamicCasters[i];\r\n const node = entry.ref.get();\r\n if (!node || node === caster) {\r\n (node ?? caster).off('bvchanged', entry.callback);\r\n entry.ref.dispose();\r\n this._dynamicCasters.splice(i, 1);\r\n changed = true;\r\n removed ||= node === caster;\r\n }\r\n }\r\n if (changed) {\r\n this.rebuildStaticRegion();\r\n this.rebuildDynamicRegion();\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Removes all shadow casters while keeping the manual region.\r\n */\r\n clearCasters() {\r\n for (const entry of this._dynamicCasters) {\r\n entry.ref.get()?.off('bvchanged', entry.callback);\r\n entry.ref.dispose();\r\n }\r\n for (const entry of this._staticCasters) {\r\n entry.ref.dispose();\r\n }\r\n this._staticCasters.length = 0;\r\n this._dynamicCasters.length = 0;\r\n this._staticRegion.beginExtend();\r\n this._dynamicRegion.beginExtend();\r\n this.updateRegion();\r\n }\r\n\r\n /**\r\n * Clears the manual region and all shadow casters.\r\n *\r\n * @returns `this` for chaining.\r\n */\r\n clear(): this {\r\n this._manualRegion = null;\r\n this.clearCasters();\r\n return this;\r\n }\r\n\r\n /** {@inheritDoc Disposable.onDispose} */\r\n protected onDispose(): void {\r\n this.clear();\r\n }\r\n\r\n /** @internal */\r\n private hasCaster(caster: SceneNode): boolean {\r\n return (\r\n this._staticCasters.some((entry) => entry.ref.get() === caster) ||\r\n this._dynamicCasters.some((entry) => entry.ref.get() === caster)\r\n );\r\n }\r\n\r\n /** @internal */\r\n private isShadowCaster(caster: SceneNode): boolean {\r\n return !caster.disposed && (caster.isMesh() || caster.isClipmapTerrain() || caster.isHair());\r\n }\r\n\r\n /** @internal */\r\n private getCasterAABB(caster: SceneNode): Nullable<AABB> {\r\n const bbox = caster.getWorldBoundingVolume()?.toAABB();\r\n return bbox?.isValid() ? new AABB(bbox) : null;\r\n }\r\n\r\n /** @internal */\r\n private rebuildStaticRegion() {\r\n this._staticRegion.beginExtend();\r\n for (let i = this._staticCasters.length - 1; i >= 0; i--) {\r\n const entry = this._staticCasters[i];\r\n if (!entry.ref.get()) {\r\n entry.ref.dispose();\r\n this._staticCasters.splice(i, 1);\r\n } else {\r\n this.extendRegion(this._staticRegion, entry.aabb);\r\n }\r\n }\r\n this.updateRegion();\r\n }\r\n\r\n /** @internal */\r\n private rebuildDynamicRegion() {\r\n this._dynamicRegion.beginExtend();\r\n for (let i = this._dynamicCasters.length - 1; i >= 0; i--) {\r\n const entry = this._dynamicCasters[i];\r\n const caster = entry.ref.get();\r\n if (!caster || !this.isShadowCaster(caster)) {\r\n caster?.off('bvchanged', entry.callback);\r\n entry.ref.dispose();\r\n this._dynamicCasters.splice(i, 1);\r\n continue;\r\n }\r\n const aabb = this.getCasterAABB(caster);\r\n if (aabb) {\r\n this.extendRegion(this._dynamicRegion, aabb);\r\n }\r\n }\r\n this.updateRegion();\r\n }\r\n\r\n /** @internal */\r\n private updateRegion() {\r\n this._region.beginExtend();\r\n if (this._manualRegion?.isValid()) {\r\n this.extendRegion(this._region, this._manualRegion);\r\n }\r\n if (this._staticRegion.isValid()) {\r\n this.extendRegion(this._region, this._staticRegion);\r\n }\r\n if (this._dynamicRegion.isValid()) {\r\n this.extendRegion(this._region, this._dynamicRegion);\r\n }\r\n }\r\n\r\n /** @internal */\r\n private extendRegion(region: AABB, aabb: AABB) {\r\n region.extend(aabb.minPoint);\r\n region.extend(aabb.maxPoint);\r\n }\r\n}\r\n"],"names":["ShadowRegion","Disposable","region","_manualRegion","AABB","_staticRegion","beginExtend","_dynamicRegion","_region","_staticCasters","_dynamicCasters","updateRegion","isValid","manualRegion","staticRegion","dynamicRegion","setRegion","addStaticCaster","caster","isShadowCaster","hasCaster","aabb","getCasterAABB","push","ref","DWeakRef","extendRegion","addDynamicCaster","callback","rebuildDynamicRegion","on","removeCaster","changed","i","length","entry","node","get","dispose","splice","off","rebuildStaticRegion","clearCasters","clear","some","disposed","isMesh","isClipmapTerrain","isHair","bbox","getWorldBoundingVolume","toAABB","extend","minPoint","maxPoint"],"mappings":"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"}
@@ -5,8 +5,8 @@ import { ESM } from './esm.js';
5
5
  import { VSM } from './vsm.js';
6
6
  import { PCFOPT } from './pcf_opt.js';
7
7
  import { PCSS } from './pcss.js';
8
- import { LIGHT_TYPE_NONE, LIGHT_TYPE_DIRECTIONAL, LIGHT_TYPE_POINT } from '../values.js';
9
- import { ShaderHelper } from '../material/shader/helper.js';
8
+ import { DOM } from './dom.js';
9
+ import { LIGHT_TYPE_NONE, LIGHT_TYPE_POINT, LIGHT_TYPE_DIRECTIONAL } from '../values.js';
10
10
  import { getDevice } from '../app/api.js';
11
11
  import { ShadowRegion } from './shadow_region.js';
12
12
 
@@ -38,6 +38,9 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
38
38
  /** @internal */ _pcssFilterSampleCount;
39
39
  /** @internal */ _pcssMaxFilterRadius;
40
40
  /** @internal */ _pcssTemporalJitter;
41
+ /** @internal */ _domLayerDistance;
42
+ /** @internal */ _domDensity;
43
+ /** @internal */ _domFilterSize;
41
44
  /** @internal */ _vsmBlurKernelSize;
42
45
  /** @internal */ _vsmBlurRadius;
43
46
  /** @internal */ _vsmDarkness;
@@ -59,7 +62,7 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
59
62
  numCascades: 1,
60
63
  splitLambda: 0.5,
61
64
  depthBias: 0.003,
62
- normalBias: 0.2,
65
+ normalBias: 1.5,
63
66
  nearClip: 1
64
67
  };
65
68
  this._resourceDirty = true;
@@ -74,6 +77,9 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
74
77
  this._pcssFilterSampleCount = 32;
75
78
  this._pcssMaxFilterRadius = 32;
76
79
  this._pcssTemporalJitter = true;
80
+ this._domLayerDistance = 0.25;
81
+ this._domDensity = 1;
82
+ this._domFilterSize = 5;
77
83
  this._vsmBlurKernelSize = 5;
78
84
  this._vsmBlurRadius = 4;
79
85
  this._vsmDarkness = 0.3;
@@ -174,7 +180,20 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
174
180
  set depthBias(val) {
175
181
  this._config.depthBias = val;
176
182
  }
177
- /** Normal bias for the shadow map */ get normalBias() {
183
+ /**
184
+ * Normal offset bias, in shadow map texels.
185
+ *
186
+ * @remarks
187
+ * Before sampling, the receiver is moved along its normal by this many texel
188
+ * widths, scaled by `sin(theta)` between the normal and the light so the
189
+ * offset is zero facing the light and largest at grazing incidence. This is
190
+ * what removes grazing-angle self-shadowing acne; {@link depthBias} alone
191
+ * cannot, because the depth error over one texel grows without bound as the
192
+ * surface turns away from the light.
193
+ *
194
+ * Useful values are roughly 1 to 4. Too little leaves acne on curved surfaces
195
+ * near the terminator; too much detaches contact shadows from thin geometry.
196
+ */ get normalBias() {
178
197
  return this._config.normalBias;
179
198
  }
180
199
  set normalBias(val) {
@@ -215,7 +234,7 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
215
234
  this.shadowDistance = 120;
216
235
  this.splitLambda = 0.75;
217
236
  this.depthBias = 0.005;
218
- this.normalBias = 0.3;
237
+ this.normalBias = 2;
219
238
  this.nearClip = 0.2;
220
239
  this.pcfKernelSize = 5;
221
240
  this.numShadowCascades = this._light.isDirectionLight() ? 3 : 1;
@@ -226,7 +245,7 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
226
245
  this.shadowDistance = 800;
227
246
  this.splitLambda = 0.6;
228
247
  this.depthBias = 0.0035;
229
- this.normalBias = 0.22;
248
+ this.normalBias = 1.5;
230
249
  this.nearClip = 1;
231
250
  this.pcfKernelSize = 3;
232
251
  this.numShadowCascades = this._light.isDirectionLight() ? 4 : 1;
@@ -283,6 +302,69 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
283
302
  }
284
303
  }
285
304
  }
305
+ /**
306
+ * Depth spanned by the deep opacity map's layers, in world units along the
307
+ * light direction. Only meaningful when the shadow mode is `dom`.
308
+ *
309
+ * @remarks
310
+ * Set it to roughly the thickness of the hair. Too small and everything past
311
+ * the outer strands saturates the last layer, which is a hard shadow again;
312
+ * too large and all three layers fall inside the first strand, leaving the
313
+ * interior unshadowed.
314
+ */ get domLayerDistance() {
315
+ return this._domLayerDistance;
316
+ }
317
+ set domLayerDistance(val) {
318
+ val = Math.max(0.0001, Number(val) || 0);
319
+ if (val !== this._domLayerDistance) {
320
+ this._domLayerDistance = val;
321
+ const dom = this.asDOM();
322
+ if (dom) {
323
+ dom.layerDistance = this._domLayerDistance;
324
+ }
325
+ }
326
+ }
327
+ /**
328
+ * How strongly accumulated hair coverage attenuates light. Only meaningful when
329
+ * the shadow mode is `dom`.
330
+ *
331
+ * @remarks
332
+ * Raise it to darken the interior of a groom without moving the layers.
333
+ */ get domDensity() {
334
+ return this._domDensity;
335
+ }
336
+ set domDensity(val) {
337
+ val = Math.max(0, Number(val) || 0);
338
+ if (val !== this._domDensity) {
339
+ this._domDensity = val;
340
+ const dom = this.asDOM();
341
+ if (dom) {
342
+ dom.density = this._domDensity;
343
+ }
344
+ }
345
+ }
346
+ /**
347
+ * Width of the deep opacity map's sampling kernel in texels: 1, 3, 5 or 7. Only
348
+ * meaningful when the shadow mode is `dom`.
349
+ *
350
+ * @remarks
351
+ * The technique's own gradation runs along the light rather than across the
352
+ * map, so without a kernel the silhouette of a groom is as hard an edge as an
353
+ * unfiltered shadow map gives. This is what softens it, at one layer lookup per
354
+ * tap. Defaults to 5.
355
+ */ get domFilterSize() {
356
+ return this._domFilterSize;
357
+ }
358
+ set domFilterSize(val) {
359
+ val = val !== 1 && val !== 3 && val !== 5 && val !== 7 ? 5 : val;
360
+ if (val !== this._domFilterSize) {
361
+ this._domFilterSize = val;
362
+ const dom = this.asDOM();
363
+ if (dom) {
364
+ dom.filterSize = this._domFilterSize;
365
+ }
366
+ }
367
+ }
286
368
  /** Blocker search sample count for PCSS shadow */ get pcssBlockerSampleCount() {
287
369
  return this._pcssBlockerSampleCount;
288
370
  }
@@ -456,25 +538,6 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
456
538
  ctx.shadowMapInfo = null;
457
539
  }
458
540
  }
459
- /** @internal */ static computeShadowBias(shadowMapParams, scope, z, NdotL, linear) {
460
- const pb = scope.$builder;
461
- const depthBiasParam = ShaderHelper.getDepthBiasValues(scope);
462
- if (shadowMapParams.lightType === LIGHT_TYPE_DIRECTIONAL) {
463
- return pb.dot(pb.mul(depthBiasParam.xy, pb.vec2(1, pb.sub(1, NdotL))), pb.vec2(1, 1));
464
- } else {
465
- const nearFar = ShaderHelper.getShadowCameraParams(scope).xy;
466
- const linearDepth = linear ? z : ShaderHelper.nonLinearDepthToLinearNormalized(scope, z, nearFar);
467
- const biasScaleFactor = pb.mix(1, depthBiasParam.w, linearDepth);
468
- return pb.dot(pb.mul(depthBiasParam.xy, pb.vec2(1, pb.sub(1, NdotL)), biasScaleFactor), pb.vec2(1, 1));
469
- }
470
- }
471
- /** @internal */ static computeShadowBiasCSM(shadowMapParams, scope, NdotL, split) {
472
- const pb = scope.$builder;
473
- const depthBiasParam = ShaderHelper.getDepthBiasValues(scope);
474
- const splitFlags = pb.vec4(pb.float(pb.equal(split, 0)), pb.float(pb.equal(split, 1)), pb.float(pb.equal(split, 2)), pb.float(pb.equal(split, 3)));
475
- const depthBiasScale = pb.dot(ShaderHelper.getDepthBiasScales(scope), splitFlags);
476
- return pb.dot(pb.mul(depthBiasParam.xy, pb.vec2(1, pb.sub(1, NdotL)), depthBiasScale), pb.vec2(1, 1));
477
- }
478
541
  /** @internal */ isTextureInvalid(texture, target, format, width, height) {
479
542
  return texture && (texture.target !== target || texture.format !== format || texture.width !== width || texture.height !== height || texture.depth !== this.numShadowCascades);
480
543
  }
@@ -766,8 +829,15 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
766
829
  /** @internal */ calcDepthBiasParams(camera, shadowMapSize, depthBias, normalBias, depthScale, result) {
767
830
  const sizeNear = Math.min(camera.getProjectionMatrix().getNearPlaneWidth(), camera.getProjectionMatrix().getNearPlaneHeight());
768
831
  const sizeFar = Math.min(camera.getProjectionMatrix().getFarPlaneWidth(), camera.getProjectionMatrix().getFarPlaneHeight());
769
- const scaleFactor = sizeNear / shadowMapSize * 2;
770
- result.setXYZW(depthBias * scaleFactor, normalBias * 0.0001, depthScale, sizeFar / sizeNear);
832
+ // World-space size of one shadow map texel at the near plane. Both bias
833
+ // terms are expressed in multiples of it, so a tighter cascade automatically
834
+ // gets a proportionally smaller offset.
835
+ const texelWorldSize = sizeNear / shadowMapSize;
836
+ result.setXYZW(depthBias * texelWorldSize * 2, // Normal offset distance in world units. Unlike a depth bias this moves
837
+ // the receiver sideways out of the texel that produces grazing-angle
838
+ // acne, which no amount of depth bias can fix (the required depth offset
839
+ // diverges as NdotL approaches zero).
840
+ normalBias * texelWorldSize, depthScale, sizeFar / sizeNear);
771
841
  }
772
842
  /** @internal */ postRenderShadowMap(shadowMapParams) {
773
843
  this._impl.postRenderShadowMap(shadowMapParams);
@@ -779,7 +849,7 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
779
849
  const shadowMapParams = ShadowMapper.fetchShadowMapParams();
780
850
  shadowMapParams.impl = this._impl;
781
851
  shadowMapParams.lightType = this.light.lightType;
782
- shadowMapParams.numShadowCascades = shadowMapParams.lightType === LIGHT_TYPE_DIRECTIONAL ? this._config.numCascades ?? 1 : 1;
852
+ shadowMapParams.numShadowCascades = shadowMapParams.lightType === LIGHT_TYPE_DIRECTIONAL && this._impl.supportsCascades() ? this._config.numCascades ?? 1 : 1;
783
853
  ctx.shadowMapInfo.set(this.light, shadowMapParams);
784
854
  const scene = ctx.scene;
785
855
  const camera = ctx.camera;
@@ -791,6 +861,32 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
791
861
  shadowMapParams.depthClampEnabled = false;
792
862
  renderPass.clearColor = this._impl.getShadowMapClearColor(shadowMapParams);
793
863
  const depthScale = this._impl.getDepthScale();
864
+ const geometryPassCount = Math.max(1, this._impl.getGeometryPassCount(shadowMapParams) >> 0);
865
+ // Repeats the caster draw for implementations that need more than one look at
866
+ // the geometry (deep opacity maps). Single-pass implementations - every one
867
+ // but DOM - take the `pass === 0` path once and behave exactly as before.
868
+ const renderGeometry = (renderCamera, cullCamera)=>{
869
+ if (geometryPassCount === 1) {
870
+ renderPass.render(ctx, renderCamera, cullCamera);
871
+ return;
872
+ }
873
+ // Culling depends only on the camera, so the extra passes reuse the first
874
+ // pass's queue rather than walking the scene graph again per pass.
875
+ const renderQueue = renderPass.cullScene(ctx, cullCamera ?? renderCamera);
876
+ try {
877
+ for(let pass = 0; pass < geometryPassCount; pass++){
878
+ this._impl.beginGeometryPass(shadowMapParams, pass);
879
+ // The emitted caster shader differs between passes, and the shadow pass
880
+ // keys its programs on this hash, so it has to be refreshed per pass
881
+ // rather than computed once up front.
882
+ shadowMapParams.shaderHash = this.getShaderHash(shadowMapParams);
883
+ renderPass.clearColor = this._impl.getShadowMapClearColor(shadowMapParams);
884
+ renderPass.render(ctx, renderCamera, cullCamera, renderQueue);
885
+ }
886
+ } finally{
887
+ renderQueue.dispose();
888
+ }
889
+ };
794
890
  const directionalShadowRegion = this._shadowRegion.region;
795
891
  const shadowRegion = this._light.isDirectionLight() && directionalShadowRegion?.isValid() ? directionalShadowRegion : scene.boundingBox;
796
892
  if (this._light.isPointLight()) {
@@ -811,20 +907,24 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
811
907
  shadowMapRenderCamera.lookAtCubeFace(face);
812
908
  fb.setColorAttachmentCubeFace(0, face);
813
909
  fb.setDepthAttachmentCubeFace(face);
814
- renderPass.render(ctx, shadowMapRenderCamera);
910
+ renderGeometry(shadowMapRenderCamera);
815
911
  }
816
912
  shadowMapParams.shadowMatrices.set(Matrix4x4.identity());
817
913
  ShadowMapper.releaseCamera(shadowMapRenderCamera);
818
914
  } else {
819
- if (this._config.numCascades > 1) {
820
- const distances = this.calcSplitDistances(camera.getNearPlane(), Math.min(this._shadowDistance, camera.getFarPlane()), this._config.numCascades);
915
+ // Reads the resolved count, not the configured one: an implementation that
916
+ // cannot store cascades has already collapsed it to a single split, and the
917
+ // cascade path would index array layers its shadow map does not have.
918
+ const numCascades = shadowMapParams.numShadowCascades;
919
+ if (numCascades > 1) {
920
+ const distances = this.calcSplitDistances(camera.getNearPlane(), Math.min(this._shadowDistance, camera.getFarPlane()), numCascades);
821
921
  const cascadeCamera = ShadowMapper.fetchCameraForScene(scene);
822
922
  const shadowMapRenderCamera = ShadowMapper.fetchCameraForScene(scene);
823
923
  const shadowMapCullCamera = ShadowMapper.fetchCameraForScene(scene);
824
924
  shadowMapCullCamera.clipMask = AABB.ClipLeft | AABB.ClipRight | AABB.ClipBottom | AABB.ClipTop;
825
925
  cascadeCamera.parent = camera;
826
926
  shadowMapParams.depthClampEnabled = getDevice().getDeviceCaps().shaderCaps.supportFragmentDepth;
827
- for(let split = 0; split < this._config.numCascades; split++){
927
+ for(let split = 0; split < numCascades; split++){
828
928
  cascadeCamera.setPerspective(camera.getFOV(), camera.getAspect(), distances[split], distances[split + 1]);
829
929
  const snapMatrix = ShadowMapper._snapMatrix;
830
930
  const border = shadowMapParams.impl.getShadowMapBorder(shadowMapParams); //20 / this._config.shadowMapSize;
@@ -839,8 +939,8 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
839
939
  fb.setColorAttachmentLayer(0, split);
840
940
  fb.setDepthAttachmentLayer(split);
841
941
  } else {
842
- const numRows = this._config.numCascades > 2 ? 2 : 1;
843
- const numCols = this._config.numCascades > 1 ? 2 : 1;
942
+ const numRows = numCascades > 2 ? 2 : 1;
943
+ const numCols = numCascades > 1 ? 2 : 1;
844
944
  const adjMatrix = new Matrix4x4();
845
945
  const col = split % 2;
846
946
  const row = split >> 1;
@@ -867,7 +967,7 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
867
967
  }
868
968
  device.setFramebuffer(fb);
869
969
  device.setScissor(scissor);
870
- renderPass.render(ctx, shadowMapRenderCamera, shadowMapCullCamera);
970
+ renderGeometry(shadowMapRenderCamera, shadowMapCullCamera);
871
971
  shadowMapParams.shadowMatrices.set(Matrix4x4.transpose(shadowMapRenderCamera.viewProjectionMatrix), split * 16);
872
972
  shadowMapParams.cascadeDistances[split] = distances[split + 1];
873
973
  }
@@ -890,7 +990,7 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
890
990
  shadowMapParams.cameraParams.setXYZW(shadowMapRenderCamera.getNearPlane(), shadowMapRenderCamera.getFarPlane(), this._config.shadowMapSize, this._shadowDistance);
891
991
  device.setFramebuffer(fb);
892
992
  shadowMapRenderCamera.setProjectionMatrix(Matrix4x4.multiply(snapMatrix, shadowMapRenderCamera.getProjectionMatrix()));
893
- renderPass.render(ctx, shadowMapRenderCamera);
993
+ renderGeometry(shadowMapRenderCamera);
894
994
  shadowMapParams.shadowMatrices.set(Matrix4x4.transpose(shadowMapRenderCamera.viewProjectionMatrix));
895
995
  ShadowMapper.releaseCamera(shadowMapRenderCamera);
896
996
  }
@@ -898,10 +998,18 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
898
998
  this.postRenderShadowMap(shadowMapParams);
899
999
  }
900
1000
  /** @internal */ applyMode(mode) {
901
- if (mode !== 'hard' && mode !== 'vsm' && mode !== 'esm' && mode !== 'pcf' && mode !== 'pcf-pd' && mode !== 'pcf-opt' && mode !== 'pcss') {
1001
+ if (mode !== 'hard' && mode !== 'vsm' && mode !== 'esm' && mode !== 'pcf' && mode !== 'pcf-pd' && mode !== 'pcf-opt' && mode !== 'pcss' && mode !== 'dom') {
902
1002
  console.error(`ShadowMapper.setShadowMode() failed: invalid mode: ${mode}`);
903
1003
  return;
904
1004
  }
1005
+ if (mode === 'dom' && getDevice().type !== 'webgpu') {
1006
+ // The strand geometry this mode exists to shadow is WebGPU only, so the
1007
+ // WebGL paths were never built. Falling back keeps a scene authored with it
1008
+ // renderable rather than failing to produce a shadow map at all.
1009
+ console.warn(`ShadowMapper.setShadowMode(): 'dom' requires WebGPU, falling back to 'pcf'`);
1010
+ this.applyMode('pcf');
1011
+ return;
1012
+ }
905
1013
  this._impl = null;
906
1014
  if (mode === 'hard') {
907
1015
  this._impl = new SSM();
@@ -914,6 +1022,8 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
914
1022
  esm.logSpace = this._esmLogSpace;
915
1023
  } else if (mode === 'pcf' || mode === 'pcf-opt' || mode === 'pcf-pd') {
916
1024
  this._impl = new PCFOPT(this._pcfKernelSize);
1025
+ } else if (mode === 'dom') {
1026
+ this._impl = new DOM(this._domLayerDistance, this._domDensity, this._domFilterSize);
917
1027
  } else if (mode === 'pcss') {
918
1028
  this._impl = new PCSS(this._pcssLightRadius, this._pcssBlockerSampleCount, this._pcssFilterSampleCount, this._pcssMaxFilterRadius, this._pcssTemporalJitter);
919
1029
  }
@@ -921,6 +1031,9 @@ const tmpFrustum = new Frustum(Matrix4x4.identity());
921
1031
  /** @internal */ asVSM() {
922
1032
  return this._impl?.getType() === 'vsm' ? this._impl : null;
923
1033
  }
1034
+ /** @internal */ asDOM() {
1035
+ return this._impl?.getType() === 'dom' ? this._impl : null;
1036
+ }
924
1037
  /** @internal */ asESM() {
925
1038
  return this._impl?.getType() === 'esm' ? this._impl : null;
926
1039
  }