@woosh/meep-engine 3.11.1 → 3.11.3

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 (225) hide show
  1. package/build/bundle-worker-terrain.js +1 -1
  2. package/package.json +1 -1
  3. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
  4. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +294 -290
  5. package/src/engine/graphics3/ParticipatingMedia.d.ts +111 -0
  6. package/src/engine/graphics3/ParticipatingMedia.d.ts.map +1 -0
  7. package/src/engine/graphics3/ParticipatingMedia.js +203 -0
  8. package/src/engine/graphics3/ParticipatingMediaSerializationAdapter.d.ts +34 -0
  9. package/src/engine/graphics3/ParticipatingMediaSerializationAdapter.d.ts.map +1 -0
  10. package/src/engine/graphics3/ParticipatingMediaSerializationAdapter.js +73 -0
  11. package/src/engine/graphics3/ParticipatingMediaSystem3.d.ts +87 -0
  12. package/src/engine/graphics3/ParticipatingMediaSystem3.d.ts.map +1 -0
  13. package/src/engine/graphics3/ParticipatingMediaSystem3.js +184 -0
  14. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  15. package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.d.ts +16 -2
  16. package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.d.ts.map +1 -1
  17. package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.js +28 -4
  18. package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts +1 -1
  19. package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts.map +1 -1
  20. package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.js +5 -2
  21. package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +564 -451
  22. package/src/shade/device/ShadeGPUCommandContext.d.ts +18 -2
  23. package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
  24. package/src/shade/device/ShadeGPUCommandContext.js +994 -940
  25. package/src/shade/device/immediate_data_supported.d.ts +33 -0
  26. package/src/shade/device/immediate_data_supported.d.ts.map +1 -0
  27. package/src/shade/device/immediate_data_supported.js +53 -0
  28. package/src/shade/device/mock/SoftwareGPUCommandEncoder.d.ts +18 -0
  29. package/src/shade/device/mock/SoftwareGPUCommandEncoder.d.ts.map +1 -1
  30. package/src/shade/device/mock/SoftwareGPUCommandEncoder.js +24 -0
  31. package/src/shade/device/mock/SoftwareGPUComputePassEncoder.d.ts +7 -0
  32. package/src/shade/device/mock/SoftwareGPUComputePassEncoder.d.ts.map +1 -1
  33. package/src/shade/device/mock/SoftwareGPUComputePassEncoder.js +49 -1
  34. package/src/shade/device/mock/SoftwareGPUDevice.d.ts.map +1 -1
  35. package/src/shade/device/mock/SoftwareGPUDevice.js +20 -2
  36. package/src/shade/device/mock/SoftwareGPUImmediateData.d.ts +65 -0
  37. package/src/shade/device/mock/SoftwareGPUImmediateData.d.ts.map +1 -0
  38. package/src/shade/device/mock/SoftwareGPUImmediateData.js +160 -0
  39. package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts +7 -0
  40. package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts.map +1 -1
  41. package/src/shade/device/mock/SoftwareGPURenderPassEncoder.js +50 -0
  42. package/src/shade/device/mock/pipeline_immediate_size.d.ts +11 -0
  43. package/src/shade/device/mock/pipeline_immediate_size.d.ts.map +1 -0
  44. package/src/shade/device/mock/pipeline_immediate_size.js +18 -0
  45. package/src/shade/device/pipeline/PipelineLayoutManager.d.ts.map +1 -1
  46. package/src/shade/device/pipeline/PipelineLayoutManager.js +1 -0
  47. package/src/shade/playground/volumetrics_froxel/README.md +104 -1
  48. package/src/shade/playground/volumetrics_froxel/index.html +20 -1
  49. package/src/shade/playground/volumetrics_froxel/main.d.ts.map +1 -1
  50. package/src/shade/playground/volumetrics_froxel/main.js +298 -0
  51. package/src/shade/playground/volumetrics_froxel/measure.d.ts +119 -0
  52. package/src/shade/playground/volumetrics_froxel/measure.d.ts.map +1 -0
  53. package/src/shade/playground/volumetrics_froxel/measure.js +559 -0
  54. package/src/shade/renderer/GraphicsContext.d.ts +9 -2
  55. package/src/shade/renderer/GraphicsContext.d.ts.map +1 -1
  56. package/src/shade/renderer/GraphicsContext.js +13 -1
  57. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  58. package/src/shade/renderer/Renderer.js +22 -18
  59. package/src/shade/renderer/atmosphere/GPUSky.d.ts.map +1 -1
  60. package/src/shade/renderer/atmosphere/GPUSky.js +145 -142
  61. package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts +34 -0
  62. package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts.map +1 -0
  63. package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.js +36 -0
  64. package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.d.ts +20 -0
  65. package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.d.ts.map +1 -0
  66. package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.js +25 -0
  67. package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts +14 -1
  68. package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts.map +1 -1
  69. package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.js +163 -177
  70. package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.d.ts +20 -0
  71. package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.d.ts.map +1 -0
  72. package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.js +46 -0
  73. package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.d.ts +11 -1
  74. package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.d.ts.map +1 -1
  75. package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.js +57 -27
  76. package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.d.ts +19 -1
  77. package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.d.ts.map +1 -1
  78. package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.js +69 -33
  79. package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.d.ts +13 -0
  80. package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.d.ts.map +1 -1
  81. package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.js +35 -35
  82. package/src/shade/renderer/atmosphere/chunk_sky_lut_params.d.ts +13 -0
  83. package/src/shade/renderer/atmosphere/chunk_sky_lut_params.d.ts.map +1 -0
  84. package/src/shade/renderer/atmosphere/chunk_sky_lut_params.js +38 -0
  85. package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.d.ts +17 -0
  86. package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.d.ts.map +1 -0
  87. package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.js +29 -0
  88. package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.d.ts +21 -0
  89. package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.d.ts.map +1 -0
  90. package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.js +42 -0
  91. package/src/shade/renderer/atmosphere/shader_multiscatter_lut.d.ts +2 -2
  92. package/src/shade/renderer/atmosphere/shader_multiscatter_lut.d.ts.map +1 -1
  93. package/src/shade/renderer/atmosphere/shader_multiscatter_lut.js +165 -84
  94. package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.d.ts.map +1 -1
  95. package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.js +215 -204
  96. package/src/shade/renderer/atmosphere/shader_transmittance_lut.d.ts.map +1 -1
  97. package/src/shade/renderer/atmosphere/shader_transmittance_lut.js +79 -67
  98. package/src/shade/renderer/light/cluster/graph_assign_clsuters.d.ts.map +1 -1
  99. package/src/shade/renderer/light/cluster/graph_assign_clsuters.js +6 -2
  100. package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
  101. package/src/shade/renderer/postprocess/gtao/GTAO.js +4 -0
  102. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  103. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  104. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +341 -345
  105. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts +0 -2
  106. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
  107. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +58 -57
  108. package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.d.ts.map +1 -1
  109. package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.js +7 -3
  110. package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.d.ts.map +1 -1
  111. package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.js +77 -81
  112. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
  113. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +5 -1
  114. package/src/shade/renderer/shader/ComputeShader.d.ts.map +1 -1
  115. package/src/shade/renderer/shader/ComputeShader.js +6 -2
  116. package/src/shade/renderer/shader/ImageShader.d.ts.map +1 -1
  117. package/src/shade/renderer/shader/ImageShader.js +4 -1
  118. package/src/shade/renderer/shader/ShaderDescriptor.d.ts.map +1 -1
  119. package/src/shade/renderer/shader/ShaderDescriptor.js +26 -0
  120. package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.d.ts +18 -0
  121. package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.d.ts.map +1 -0
  122. package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.js +32 -0
  123. package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.d.ts +7 -0
  124. package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.d.ts.map +1 -1
  125. package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.js +17 -6
  126. package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.d.ts +21 -0
  127. package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.d.ts.map +1 -0
  128. package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.js +69 -0
  129. package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.d.ts +9 -0
  130. package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.d.ts.map +1 -0
  131. package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.js +22 -0
  132. package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.d.ts +16 -0
  133. package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.d.ts.map +1 -0
  134. package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.js +41 -0
  135. package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.d.ts +11 -0
  136. package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.d.ts.map +1 -1
  137. package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.js +15 -19
  138. package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.d.ts +23 -0
  139. package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.d.ts.map +1 -0
  140. package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.js +44 -0
  141. package/src/shade/renderer/shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_decode.d.ts.map +1 -1
  142. package/src/shade/renderer/shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_decode.js +1 -4
  143. package/src/shade/renderer/shader/compiler/CodeChunk.d.ts +7 -1
  144. package/src/shade/renderer/shader/compiler/CodeChunk.d.ts.map +1 -1
  145. package/src/shade/renderer/shader/compiler/CodeChunk.js +37 -2
  146. package/src/shade/renderer/shader/graph/compute/compute_binding_read_names.d.ts.map +1 -1
  147. package/src/shade/renderer/shader/graph/compute/compute_binding_read_names.js +6 -0
  148. package/src/shade/renderer/shader/graph/compute/graph_compute_indirect_pass.js +1 -1
  149. package/src/shade/renderer/shader/graph/compute/graph_compute_pass.js +1 -1
  150. package/src/shade/renderer/shader/graph/graph_image_pass.js +1 -1
  151. package/src/shade/renderer/shader/graph/graph_prepare_bindings.d.ts.map +1 -1
  152. package/src/shade/renderer/shader/graph/graph_prepare_bindings.js +7 -0
  153. package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.d.ts +3 -1
  154. package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.d.ts.map +1 -1
  155. package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.js +12 -1
  156. package/src/shade/renderer/shader/pass/RenderPassDescriptor.d.ts.map +1 -1
  157. package/src/shade/renderer/shader/pass/RenderPassDescriptor.js +1 -0
  158. package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.d.ts +21 -0
  159. package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.d.ts.map +1 -0
  160. package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.js +21 -0
  161. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts +59 -1
  162. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts.map +1 -1
  163. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.js +195 -2
  164. package/src/shade/renderer/shader/type/type_has_arrays.d.ts +14 -0
  165. package/src/shade/renderer/shader/type/type_has_arrays.d.ts.map +1 -0
  166. package/src/shade/renderer/shader/type/type_has_arrays.js +33 -0
  167. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_point2.d.ts +14 -13
  168. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_point2.d.ts.map +1 -1
  169. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_point2.js +133 -129
  170. package/src/shade/renderer/view/GPUViewSkyContext.d.ts.map +1 -1
  171. package/src/shade/renderer/view/GPUViewSkyContext.js +169 -173
  172. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
  173. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +480 -315
  174. package/src/shade/renderer/volumetrics/NOTES.md +887 -8
  175. package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.d.ts +5 -0
  176. package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.d.ts.map +1 -1
  177. package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.js +21 -2
  178. package/src/shade/renderer/volumetrics/SceneVolumetrics.d.ts +26 -0
  179. package/src/shade/renderer/volumetrics/SceneVolumetrics.d.ts.map +1 -1
  180. package/src/shade/renderer/volumetrics/SceneVolumetrics.js +64 -21
  181. package/src/shade/renderer/volumetrics/VOLUMETRICS_METADATA_STRUCT.d.ts.map +1 -1
  182. package/src/shade/renderer/volumetrics/VOLUMETRICS_METADATA_STRUCT.js +84 -28
  183. package/src/shade/renderer/volumetrics/VOLUMETRICS_SUN_SHADOW_RESOLUTION.d.ts +98 -0
  184. package/src/shade/renderer/volumetrics/VOLUMETRICS_SUN_SHADOW_RESOLUTION.d.ts.map +1 -0
  185. package/src/shade/renderer/volumetrics/VOLUMETRICS_SUN_SHADOW_RESOLUTION.js +111 -0
  186. package/src/shade/renderer/volumetrics/build_volumetrics_sun_shadow_transform.d.ts +84 -0
  187. package/src/shade/renderer/volumetrics/build_volumetrics_sun_shadow_transform.d.ts.map +1 -0
  188. package/src/shade/renderer/volumetrics/build_volumetrics_sun_shadow_transform.js +252 -0
  189. package/src/shade/renderer/volumetrics/chunk_integrate_optical_depth.js +75 -75
  190. package/src/shade/renderer/volumetrics/chunk_volumetrics_local_light_shadow.d.ts +43 -0
  191. package/src/shade/renderer/volumetrics/chunk_volumetrics_local_light_shadow.d.ts.map +1 -0
  192. package/src/shade/renderer/volumetrics/chunk_volumetrics_local_light_shadow.js +145 -0
  193. package/src/shade/renderer/volumetrics/chunk_volumetrics_sample_froxel_extinction.d.ts +20 -0
  194. package/src/shade/renderer/volumetrics/chunk_volumetrics_sample_froxel_extinction.d.ts.map +1 -0
  195. package/src/shade/renderer/volumetrics/chunk_volumetrics_sample_froxel_extinction.js +48 -0
  196. package/src/shade/renderer/volumetrics/chunk_volumetrics_sun_optical_depth.d.ts +38 -0
  197. package/src/shade/renderer/volumetrics/chunk_volumetrics_sun_optical_depth.d.ts.map +1 -0
  198. package/src/shade/renderer/volumetrics/chunk_volumetrics_sun_optical_depth.js +130 -0
  199. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +18 -3
  200. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
  201. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +193 -70
  202. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts +16 -0
  203. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
  204. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +83 -15
  205. package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.d.ts.map +1 -1
  206. package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.js +29 -18
  207. package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.js +260 -260
  208. package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.d.ts +58 -0
  209. package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.d.ts.map +1 -0
  210. package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.js +290 -0
  211. package/src/shade/wgsl/emulator/CPUBitmapData.d.ts +25 -0
  212. package/src/shade/wgsl/emulator/CPUBitmapData.d.ts.map +1 -1
  213. package/src/shade/wgsl/emulator/CPUBitmapData.js +33 -0
  214. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +2228 -2215
  215. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  216. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +210 -2
  217. package/src/shade/wgsl/emulator/cpu_texture_gather.d.ts +69 -0
  218. package/src/shade/wgsl/emulator/cpu_texture_gather.d.ts.map +1 -0
  219. package/src/shade/wgsl/emulator/cpu_texture_gather.js +148 -0
  220. package/src/shade/wgsl/emulator/cpu_texture_sample.d.ts +16 -0
  221. package/src/shade/wgsl/emulator/cpu_texture_sample.d.ts.map +1 -1
  222. package/src/shade/wgsl/emulator/cpu_texture_sample.js +176 -172
  223. package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.d.ts +0 -2
  224. package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.d.ts.map +0 -1
  225. package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.js +0 -1
@@ -0,0 +1,252 @@
1
+ import { assert } from "../../../core/assert.js";
2
+
3
+ /**
4
+ * How many voxels of empty margin the box keeps around the frustum's bounding sphere, per side.
5
+ *
6
+ * One is load-bearing, the rest is comfort. The sun-side face is where the optical-depth scan
7
+ * starts, so the slab it begins on must be guaranteed empty for the snap below to be free: the
8
+ * medium only exists inside the camera frustum, the sphere contains the frustum, so any slab
9
+ * outside the sphere integrates to zero and shifting the box by whole voxels cannot change the
10
+ * answer. Snapping moves the box by up to one voxel, which spends one of these.
11
+ *
12
+ * @type {number}
13
+ */
14
+ const MARGIN_VOXELS = 2;
15
+
16
+ /**
17
+ * Far distance of cascade `index`, i.e. the view depth its box has to cover.
18
+ *
19
+ * A logarithmic split, `far / ratio^(count - 1 - index)`. The choice is forced by what the cascades
20
+ * are for: every box is sized from the bounding sphere of the sub-frustum it covers, so its voxel is
21
+ * proportional to its far distance, and a constant ratio between far distances is a constant ratio
22
+ * between voxel sizes. Any other split leaves one cascade carrying a resolution jump the others do
23
+ * not.
24
+ *
25
+ * The innermost cascade still starts at the camera rather than at the previous cascade's far plane —
26
+ * cascades here nest rather than tile, because the box is sized from a *frustum's* bounding sphere
27
+ * and a shell would need a sphere around the shell, which is the outer sphere again.
28
+ *
29
+ * @param {number} far the full range the structure must cover
30
+ * @param {number} index
31
+ * @param {number} count
32
+ * @param {number} ratio
33
+ * @returns {number}
34
+ */
35
+ export function volumetrics_sun_shadow_cascade_far(far, index, count, ratio) {
36
+ assert.isNumber(far, 'far');
37
+ assert.isNumber(index, 'index');
38
+
39
+ return far / Math.pow(ratio, count - 1 - index);
40
+ }
41
+
42
+ /**
43
+ * Radius of the smallest sphere containing a symmetric perspective frustum, and where its centre
44
+ * sits along the view axis.
45
+ *
46
+ * This is the quantity that makes the structure temporally stable, and the reason it is a sphere
47
+ * rather than a tight fit: it depends only on `fov`, `aspect`, `near` and `far`, so it does not
48
+ * change when the camera turns. A box sized from it therefore has a fixed extent, hence a fixed
49
+ * voxel size, hence a world-space voxel lattice that only ever translates.
50
+ *
51
+ * Both frustum corner rings are equidistant from the centre when
52
+ * `z = (far + near)(k^2 + 1) / 2` with `k^2 = tan^2(theta_x) + tan^2(theta_y)`. Past `z = far` that
53
+ * solution leaves the sphere behind the far plane, and the far ring alone bounds it.
54
+ *
55
+ * @param {number[]} out `[center_view_z, radius]`
56
+ * @param {number} fov_y vertical field of view, radians
57
+ * @param {number} aspect width / height
58
+ * @param {number} near
59
+ * @param {number} far
60
+ * @returns {number[]} out
61
+ */
62
+ export function frustum_bounding_sphere(out, fov_y, aspect, near, far) {
63
+ const tan_y = Math.tan(fov_y * 0.5);
64
+ const tan_x = tan_y * aspect;
65
+
66
+ const k2 = tan_x * tan_x + tan_y * tan_y;
67
+
68
+ const center_z = (far + near) * (k2 + 1) * 0.5;
69
+
70
+ if (center_z >= far) {
71
+ out[0] = far;
72
+ out[1] = far * Math.sqrt(k2);
73
+ } else {
74
+ const d = center_z - far;
75
+ out[0] = center_z;
76
+ out[1] = Math.sqrt(k2 * far * far + d * d);
77
+ }
78
+
79
+ return out;
80
+ }
81
+
82
+ /**
83
+ * Orthonormal basis whose third axis is `direction`.
84
+ *
85
+ * A function of the direction alone, so a stationary sun produces a bit-identical basis every
86
+ * frame. The reference axis is the world axis least aligned with the direction, which keeps the
87
+ * cross product well conditioned; the frame necessarily has a seam somewhere (no continuous
88
+ * tangent frame exists on the sphere), and it sits where two components of `direction` tie.
89
+ *
90
+ * @param {Float32Array} out 9 floats, `[x_axis, y_axis, direction]`
91
+ * @param {ArrayLike<number>} direction unit length
92
+ */
93
+ function orthonormal_basis(out, direction) {
94
+ const dx = direction[0], dy = direction[1], dz = direction[2];
95
+
96
+ const ax = Math.abs(dx), ay = Math.abs(dy), az = Math.abs(dz);
97
+
98
+ let rx = 0, ry = 0, rz = 0;
99
+
100
+ if (ax <= ay && ax <= az) {
101
+ rx = 1;
102
+ } else if (ay <= az) {
103
+ ry = 1;
104
+ } else {
105
+ rz = 1;
106
+ }
107
+
108
+ // x = normalize(cross(reference, direction))
109
+ let xx = ry * dz - rz * dy;
110
+ let xy = rz * dx - rx * dz;
111
+ let xz = rx * dy - ry * dx;
112
+
113
+ const x_length = Math.hypot(xx, xy, xz);
114
+ xx /= x_length;
115
+ xy /= x_length;
116
+ xz /= x_length;
117
+
118
+ // y = cross(direction, x)
119
+ const yx = dy * xz - dz * xy;
120
+ const yy = dz * xx - dx * xz;
121
+ const yz = dx * xy - dy * xx;
122
+
123
+ out[0] = xx;
124
+ out[1] = xy;
125
+ out[2] = xz;
126
+ out[3] = yx;
127
+ out[4] = yy;
128
+ out[5] = yz;
129
+ out[6] = dx;
130
+ out[7] = dy;
131
+ out[8] = dz;
132
+ }
133
+
134
+ const _sphere = [0, 0];
135
+ const _basis = new Float32Array(9);
136
+
137
+ /**
138
+ * Place the sun's optical-depth volume for this frame.
139
+ *
140
+ * The volume is a box aligned to the sun: its third axis is the direction light travels, so a
141
+ * column of voxels along that axis is a light ray, and the optical depth of every voxel in the
142
+ * column is one running sum away from its neighbour's. That is what
143
+ * `shader_volumetrics_build_sun_shadow_volume` computes and what
144
+ * `chunk_volumetrics_sun_optical_depth` reads back in a single fetch.
145
+ *
146
+ * Two properties matter and both are about time rather than space, because the froxel lighting this
147
+ * feeds goes through a 0.95-blend temporal filter that turns any instability into either ghosting
148
+ * or flicker:
149
+ *
150
+ * - **The extent never changes with camera orientation.** It comes from the frustum's bounding
151
+ * sphere, which is a function of the projection only. A tight fit of the frustum corners in sun
152
+ * space — the obvious thing, and what this engine's CSM deliberately does for its cascades —
153
+ * would breathe as the camera turned, moving every voxel centre continuously and making the
154
+ * whole structure swim. The CSM can afford the tight fit because it is two orders of magnitude
155
+ * denser and its consumer is a filtered depth comparison; this is neither.
156
+ *
157
+ * - **The origin is snapped to a whole voxel.** With a fixed extent that makes the world-space
158
+ * voxel lattice identical from frame to frame no matter how the camera moves: voxel centres land
159
+ * on the same world positions, so the sampled medium is the same and the reconstruction is the
160
+ * same function of world position. Only the indexing changes. The box shifts by whole voxels,
161
+ * and the slab it gains or loses lies outside the frustum where the medium is zero, so even the
162
+ * scan's starting value is unaffected.
163
+ *
164
+ * @param {Float32Array} ws_to_uvw 16 floats, column-major. World position -> [0,1]^3 volume coords.
165
+ * @param {Float32Array} uvw_to_ws 16 floats, column-major. Its inverse, for the build pass.
166
+ * @param {ArrayLike<number>} light_direction unit vector along which light TRAVELS (the
167
+ * `light_directional` table's `direction`, i.e. NOT the direction toward the light).
168
+ * @param {ArrayLike<number>} camera_position world space
169
+ * @param {ArrayLike<number>} camera_forward unit, world space
170
+ * @param {number} fov_y vertical field of view, radians
171
+ * @param {number} aspect width / height
172
+ * @param {number} near
173
+ * @param {number} far the depth the medium actually extends to — `min(froxel_far, camera.far)`,
174
+ * since the froxel grid is where the extinction is read from and it ends there.
175
+ * @param {ArrayLike<number>} resolution `[x, y, z]` voxel counts
176
+ * @returns {number} world-space length of one voxel along the sun axis, i.e. the scan's `dz`
177
+ */
178
+ export function build_volumetrics_sun_shadow_transform({
179
+ ws_to_uvw,
180
+ uvw_to_ws,
181
+ light_direction,
182
+ camera_position,
183
+ camera_forward,
184
+ fov_y,
185
+ aspect,
186
+ near,
187
+ far,
188
+ resolution,
189
+ }) {
190
+
191
+ assert.greaterThanOrEqual(ws_to_uvw.length, 16, 'ws_to_uvw must hold 16 floats');
192
+ assert.greaterThanOrEqual(uvw_to_ws.length, 16, 'uvw_to_ws must hold 16 floats');
193
+
194
+ frustum_bounding_sphere(_sphere, fov_y, aspect, near, far);
195
+
196
+ const center_distance = _sphere[0];
197
+ const radius = _sphere[1];
198
+
199
+ orthonormal_basis(_basis, light_direction);
200
+
201
+ const center_x = camera_position[0] + camera_forward[0] * center_distance;
202
+ const center_y = camera_position[1] + camera_forward[1] * center_distance;
203
+ const center_z = camera_position[2] + camera_forward[2] * center_distance;
204
+
205
+ uvw_to_ws[12] = 0;
206
+ uvw_to_ws[13] = 0;
207
+ uvw_to_ws[14] = 0;
208
+
209
+ for (let axis = 0; axis < 3; axis++) {
210
+ const ax = _basis[axis * 3];
211
+ const ay = _basis[axis * 3 + 1];
212
+ const az = _basis[axis * 3 + 2];
213
+
214
+ const count = resolution[axis];
215
+
216
+ // The sphere spans 2r; the box is MARGIN_VOXELS wider on each side, so solving
217
+ // `step * (count - 2 * MARGIN_VOXELS) == 2r` gives a step that leaves that margin exactly.
218
+ const step = (2 * radius) / (count - 2 * MARGIN_VOXELS);
219
+ const extent = step * count;
220
+
221
+ // where the box's centre sits on this axis, in sun space
222
+ const center = center_x * ax + center_y * ay + center_z * az;
223
+
224
+ const origin = Math.floor((center - extent * 0.5) / step) * step;
225
+
226
+ // ws_to_uvw row `axis`: dot(position, axis) / extent - origin / extent
227
+ ws_to_uvw[axis] = ax / extent;
228
+ ws_to_uvw[4 + axis] = ay / extent;
229
+ ws_to_uvw[8 + axis] = az / extent;
230
+ ws_to_uvw[12 + axis] = -origin / extent;
231
+
232
+ // uvw_to_ws column `axis`: the axis scaled to the full extent
233
+ uvw_to_ws[axis * 4] = ax * extent;
234
+ uvw_to_ws[axis * 4 + 1] = ay * extent;
235
+ uvw_to_ws[axis * 4 + 2] = az * extent;
236
+ uvw_to_ws[axis * 4 + 3] = 0;
237
+
238
+ // and accumulate the box origin into its translation column
239
+ uvw_to_ws[12] += ax * origin;
240
+ uvw_to_ws[13] += ay * origin;
241
+ uvw_to_ws[14] += az * origin;
242
+ }
243
+
244
+ ws_to_uvw[3] = 0;
245
+ ws_to_uvw[7] = 0;
246
+ ws_to_uvw[11] = 0;
247
+ ws_to_uvw[15] = 1;
248
+
249
+ uvw_to_ws[15] = 1;
250
+
251
+ return (2 * radius) / (resolution[2] - 2 * MARGIN_VOXELS);
252
+ }
@@ -1,76 +1,76 @@
1
- import { chunk_frustum_clip_ray } from "../shader/chunk/geometry/frustum/chunk_frustum_clip_ray.js";
2
- import { RAY_STRUCT } from "../shader/chunk/geometry/ray/RAY_STRUCT.js";
3
- import { CodeChunk } from "../shader/compiler/CodeChunk.js";
4
- import { volumetrics_position_world_to_froxel_uvw } from "./volumetrics_position_world_to_froxel_uvw.js";
5
-
6
- export const chunk_integrate_optical_depth = CodeChunk.from(
7
- //language=WGSL
8
- `
9
- fn integrate_optical_depth(ray_ws: ${RAY_STRUCT.wgsl_ref}) -> vec3<f32>{
10
-
11
- // volumetrics_metadata.frustum, NOT camera.frustum. The camera's frustum has no usable far
12
- // plane: an infinite reverse-Z projection puts (0,0,0,near) in row 2 of the VP, a normal that
13
- // cannot be normalized, so PerspectiveCamera.update_frustum zeroes the entry outright.
14
- // frustum_clip_ray then discards it — correctly — which leaves the frustum OPEN at the far end,
15
- // and a ray whose direction lies inside the view cone crosses no plane outward at all. t_exit
16
- // stays at its 1e20 sentinel; capped by a directional light's tmax of 1.496e11 that gives a
17
- // step of ~9.35e9, so the first sample lands far outside the grid, the loop below breaks on
18
- // iteration one, and this returns zero — no media self-shadowing at all whenever the sun is in
19
- // front of the camera, which is exactly when shafts are wanted.
20
- //
21
- // The metadata frustum is the same one rebuilt with a FINITE far plane
22
- // (update_frustum_far_plane), which the participating-media pass already slices against.
23
- var frustum_tmax = min(
24
- ray_ws.tmax,
25
- frustum_clip_ray(ray_ws.origin, ray_ws.direction, volumetrics_metadata.frustum)
26
- );
27
-
28
- if(frustum_tmax <= 0.0){
29
- return vec3<f32>(0.0);
30
- }
31
-
32
- // go as far as we can in frustum space
33
- const STEP_COUNT = 16;
34
-
35
- let step_size = frustum_tmax / STEP_COUNT;
36
-
37
- var optical_depth = vec3<f32>(0.0);
38
-
39
- // offset ray forward a tiny mount to avoid aliasing
40
- var t_previous = step_size * 0.1;
41
- var t_current = step_size;
42
-
43
- for(var i = 0; i < STEP_COUNT; i++){
44
-
45
- let position_ws = ray_ws.origin + ray_ws.direction * t_current;
46
-
47
- let uvw = volumetrics_position_world_to_froxel_uvw(position_ws);
48
-
49
- if(any(abs(uvw.xyz - 0.5) >= vec3(0.5))){
50
- // out of bounds, stop integration
51
- break;
52
- }
53
-
54
- let t_delta = t_current - t_previous;
55
-
56
- let sample_extinction = textureSampleLevel(tParticipatingMedia1, sLinear, uvw, 0.0).rgb;
57
-
58
- optical_depth += sample_extinction * t_delta;
59
-
60
- // update
61
- t_previous = t_current;
62
- t_current += step_size;
63
- }
64
-
65
- // if ray goes outside of the frustum, integrate over fog volumes
66
- // TODO implement
67
-
68
- return optical_depth;
69
- }
70
- `,
71
- [
72
- RAY_STRUCT.declaration_chunk,
73
- volumetrics_position_world_to_froxel_uvw,
74
- chunk_frustum_clip_ray,
75
- ]
1
+ import { chunk_frustum_clip_ray } from "../shader/chunk/geometry/frustum/chunk_frustum_clip_ray.js";
2
+ import { RAY_STRUCT } from "../shader/chunk/geometry/ray/RAY_STRUCT.js";
3
+ import { CodeChunk } from "../shader/compiler/CodeChunk.js";
4
+ import { volumetrics_position_world_to_froxel_uvw } from "./volumetrics_position_world_to_froxel_uvw.js";
5
+
6
+ export const chunk_integrate_optical_depth = CodeChunk.from(
7
+ //language=WGSL
8
+ `
9
+ fn integrate_optical_depth(ray_ws: ${RAY_STRUCT.wgsl_ref}) -> vec3<f32>{
10
+
11
+ // volumetrics_metadata.frustum, NOT camera.frustum. The camera's frustum has no usable far
12
+ // plane: an infinite reverse-Z projection puts (0,0,0,near) in row 2 of the VP, a normal that
13
+ // cannot be normalized, so PerspectiveCamera.update_frustum zeroes the entry outright.
14
+ // frustum_clip_ray then discards it — correctly — which leaves the frustum OPEN at the far end,
15
+ // and a ray whose direction lies inside the view cone crosses no plane outward at all. t_exit
16
+ // stays at its 1e20 sentinel; capped by a directional light's tmax of 1.496e11 that gives a
17
+ // step of ~9.35e9, so the first sample lands far outside the grid, the loop below breaks on
18
+ // iteration one, and this returns zero — no media self-shadowing at all whenever the sun is in
19
+ // front of the camera, which is exactly when shafts are wanted.
20
+ //
21
+ // The metadata frustum is the same one rebuilt with a FINITE far plane
22
+ // (update_frustum_far_plane), which the participating-media pass already slices against.
23
+ var frustum_tmax = min(
24
+ ray_ws.tmax,
25
+ frustum_clip_ray(ray_ws.origin, ray_ws.direction, volumetrics_metadata.frustum)
26
+ );
27
+
28
+ if(frustum_tmax <= 0.0){
29
+ return vec3<f32>(0.0);
30
+ }
31
+
32
+ // go as far as we can in frustum space
33
+ const STEP_COUNT = 16;
34
+
35
+ let step_size = frustum_tmax / STEP_COUNT;
36
+
37
+ var optical_depth = vec3<f32>(0.0);
38
+
39
+ // offset ray forward a tiny mount to avoid aliasing
40
+ var t_previous = step_size * 0.1;
41
+ var t_current = step_size;
42
+
43
+ for(var i = 0; i < STEP_COUNT; i++){
44
+
45
+ let position_ws = ray_ws.origin + ray_ws.direction * t_current;
46
+
47
+ let uvw = volumetrics_position_world_to_froxel_uvw(position_ws);
48
+
49
+ if(any(abs(uvw.xyz - 0.5) >= vec3(0.5))){
50
+ // out of bounds, stop integration
51
+ break;
52
+ }
53
+
54
+ let t_delta = t_current - t_previous;
55
+
56
+ let sample_extinction = textureSampleLevel(tParticipatingMedia1, sLinear, uvw, 0.0).rgb;
57
+
58
+ optical_depth += sample_extinction * t_delta;
59
+
60
+ // update
61
+ t_previous = t_current;
62
+ t_current += step_size;
63
+ }
64
+
65
+ // if ray goes outside of the frustum, integrate over fog volumes
66
+ // TODO implement
67
+
68
+ return optical_depth;
69
+ }
70
+ `,
71
+ [
72
+ RAY_STRUCT.declaration_chunk,
73
+ volumetrics_position_world_to_froxel_uvw,
74
+ chunk_frustum_clip_ray,
75
+ ]
76
76
  )
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Shadow visibility for clustered point and spot lights, sampled the way a *volume* wants rather
3
+ * than the way a surface does: one nearest texel out of the shadow atlas, no filter kernel, no
4
+ * receiver bias.
5
+ *
6
+ * This exists alongside {@link chunk_shadowmap_get_light_visibility} rather than replacing it. The
7
+ * deferred path needs its filtering — a hard shadow edge on a surface is a visible artefact — and
8
+ * its cost is paid once per pixel. Here the cost is paid once per light per froxel, with up to 255
9
+ * point and 255 spot lights in a cluster, and the playground scene alone carries 130 shadow-casting
10
+ * point lights. The two callers want genuinely different things out of the same atlas, so they get
11
+ * two functions over it; the atlas, its tile rects and its projections are untouched.
12
+ *
13
+ * Three reasons the cheap tap is not a compromise here:
14
+ *
15
+ * - The value being sampled is already an average over a froxel slab several pixels across, it is
16
+ * jittered in xy and in depth every frame, and it then goes through a 3x3x3 Blackman-Harris
17
+ * temporal resample at a 0.95 blend. A PCF kernel would be filtering something about to be
18
+ * filtered three more times. Frostbite takes exactly this route for its froxel pass and accepts
19
+ * the aliasing; Wronski's alternative (a separate pre-filtered low-resolution ESM) needs its own
20
+ * target and build pass, and cannot be a downsample of THIS atlas because blurring moments
21
+ * bleeds across tile borders.
22
+ *
23
+ * - There is no receiver bias because there is no receiver. Normal-offset and slope-scaled depth
24
+ * bias exist to stop a surface shadowing itself; a medium cannot. Note that the deferred helpers
25
+ * reach the same answer when a volumetric caller passes the light direction in place of a normal:
26
+ * `get_shadow_offsets(N, L)` with `N == L` gives `cos_alpha = 1`, hence `sqrt(1 - 1) = 0` for the
27
+ * normal scale and `0` for the depth scale, so both offsets vanish. Dropping them here therefore
28
+ * does not change the sampled position relative to those helpers — only the tap count does.
29
+ *
30
+ * - `textureLoad` rather than `textureGatherCompare`: the comparison is one instruction either way,
31
+ * and the raw load carries no uniformity requirement, so it is legal in the middle of a
32
+ * non-uniform light loop without the gather dance `shadowmap_sample_filtered` needs.
33
+ *
34
+ * Both functions early-out to 1.0 on a light with no `CASTS_SHADOW_BIT`, so a non-shadowing light in
35
+ * the cluster stays free.
36
+ *
37
+ * Requires `shadowmapAtlas` in the calling shader's scope.
38
+ *
39
+ * @type {CodeChunk}
40
+ */
41
+ export const chunk_volumetrics_local_light_shadow: CodeChunk;
42
+ import { CodeChunk } from "../shader/compiler/CodeChunk.js";
43
+ //# sourceMappingURL=chunk_volumetrics_local_light_shadow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk_volumetrics_local_light_shadow.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/chunk_volumetrics_local_light_shadow.js"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,mDAFU,SAAS,CA6FjB;0BA1IwB,iCAAiC"}
@@ -0,0 +1,145 @@
1
+ import { LIGHT_DATABASE_SPEC } from "../light/LIGHT_DATABASE_SPEC.js";
2
+ import { LIGHT_FLAGS } from "../light/model/io/LIGHT_FLAGS.js";
3
+ import {
4
+ chunk_uv_octahedral_unit_encode
5
+ } from "../shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_encode.js";
6
+ import { chunk_ndc_to_uv } from "../shader/chunk/texture/chunk_ndc_to_uv.js";
7
+ import { CodeChunk } from "../shader/compiler/CodeChunk.js";
8
+
9
+ const POINT_DESCRIPTOR = LIGHT_DATABASE_SPEC.get('light_point');
10
+ const SPOT_DESCRIPTOR = LIGHT_DATABASE_SPEC.get('light_spot');
11
+ const SHADOW_POINT_DESCRIPTOR = LIGHT_DATABASE_SPEC.get('shadow_point');
12
+ const SHADOW_SPOT_DESCRIPTOR = LIGHT_DATABASE_SPEC.get('shadow_spot');
13
+
14
+ /**
15
+ * Shadow visibility for clustered point and spot lights, sampled the way a *volume* wants rather
16
+ * than the way a surface does: one nearest texel out of the shadow atlas, no filter kernel, no
17
+ * receiver bias.
18
+ *
19
+ * This exists alongside {@link chunk_shadowmap_get_light_visibility} rather than replacing it. The
20
+ * deferred path needs its filtering — a hard shadow edge on a surface is a visible artefact — and
21
+ * its cost is paid once per pixel. Here the cost is paid once per light per froxel, with up to 255
22
+ * point and 255 spot lights in a cluster, and the playground scene alone carries 130 shadow-casting
23
+ * point lights. The two callers want genuinely different things out of the same atlas, so they get
24
+ * two functions over it; the atlas, its tile rects and its projections are untouched.
25
+ *
26
+ * Three reasons the cheap tap is not a compromise here:
27
+ *
28
+ * - The value being sampled is already an average over a froxel slab several pixels across, it is
29
+ * jittered in xy and in depth every frame, and it then goes through a 3x3x3 Blackman-Harris
30
+ * temporal resample at a 0.95 blend. A PCF kernel would be filtering something about to be
31
+ * filtered three more times. Frostbite takes exactly this route for its froxel pass and accepts
32
+ * the aliasing; Wronski's alternative (a separate pre-filtered low-resolution ESM) needs its own
33
+ * target and build pass, and cannot be a downsample of THIS atlas because blurring moments
34
+ * bleeds across tile borders.
35
+ *
36
+ * - There is no receiver bias because there is no receiver. Normal-offset and slope-scaled depth
37
+ * bias exist to stop a surface shadowing itself; a medium cannot. Note that the deferred helpers
38
+ * reach the same answer when a volumetric caller passes the light direction in place of a normal:
39
+ * `get_shadow_offsets(N, L)` with `N == L` gives `cos_alpha = 1`, hence `sqrt(1 - 1) = 0` for the
40
+ * normal scale and `0` for the depth scale, so both offsets vanish. Dropping them here therefore
41
+ * does not change the sampled position relative to those helpers — only the tap count does.
42
+ *
43
+ * - `textureLoad` rather than `textureGatherCompare`: the comparison is one instruction either way,
44
+ * and the raw load carries no uniformity requirement, so it is legal in the middle of a
45
+ * non-uniform light loop without the gather dance `shadowmap_sample_filtered` needs.
46
+ *
47
+ * Both functions early-out to 1.0 on a light with no `CASTS_SHADOW_BIT`, so a non-shadowing light in
48
+ * the cluster stays free.
49
+ *
50
+ * Requires `shadowmapAtlas` in the calling shader's scope.
51
+ *
52
+ * @type {CodeChunk}
53
+ */
54
+ export const chunk_volumetrics_local_light_shadow = CodeChunk.from(
55
+ //language=WGSL
56
+ `
57
+ /**
58
+ * Nearest texel of an atlas tile, given a uv over that tile. Clamped to the tile so a uv that lands
59
+ * exactly on the far edge (or drifts a hair past it) cannot read a neighbouring light's slot.
60
+ */
61
+ fn volumetrics_shadow_atlas_texel(uv: vec2f, atlas_aabb: vec4f) -> vec2u {
62
+ let local = clamp(floor(uv * atlas_aabb.zw), vec2f(0.0), atlas_aabb.zw - 1.0);
63
+
64
+ return vec2u(local) + vec2u(atlas_aabb.xy);
65
+ }
66
+
67
+ fn volumetrics_point_light_shadow(
68
+ database: ptr<storage, array<u32>>,
69
+ point_index: u32,
70
+ position_ws: vec3f,
71
+ ) -> f32 {
72
+ let light = ${POINT_DESCRIPTOR.marshalling_method_read}(database, point_index);
73
+
74
+ if ((light.flags & ${LIGHT_FLAGS.CASTS_SHADOW_BIT}u) == 0u) {
75
+ return 1.0;
76
+ }
77
+
78
+ let atlas_aabb = ${SHADOW_POINT_DESCRIPTOR.marshalling_method_read}(database, light.shadow_id);
79
+
80
+ // the cube faces are captured FROM the light, so the octahedral encode takes light -> sample
81
+ let to_sample = position_ws - light.position;
82
+ let distance_to_center = length(to_sample);
83
+
84
+ let direction_light_to_sample = to_sample / max(distance_to_center, 1e-6);
85
+
86
+ // reverse-Z radial, matching what the point shadow pass stores: at the light -> 1, at or beyond
87
+ // the light's range -> 0.
88
+ let reference_depth = 1.0 - saturate(distance_to_center / max(light.distance, 1e-6));
89
+
90
+ let texel = volumetrics_shadow_atlas_texel(
91
+ uv_octahedral_unit_encode(direction_light_to_sample),
92
+ atlas_aabb
93
+ );
94
+
95
+ let stored = textureLoad(shadowmapAtlas, texel, 0);
96
+
97
+ // A larger stored value is NEARER the light, so an occluder in front of this froxel reads
98
+ // greater than the froxel's own radial depth. Cleared atlas regions hold 0 and therefore never
99
+ // occlude, which is the right default for a light whose map was not rendered this frame.
100
+ return select(1.0, 0.0, stored > reference_depth);
101
+ }
102
+
103
+ fn volumetrics_spot_light_shadow(
104
+ database: ptr<storage, array<u32>>,
105
+ spot_index: u32,
106
+ position_ws: vec3f,
107
+ ) -> f32 {
108
+ let light = ${SPOT_DESCRIPTOR.marshalling_method_read}(database, spot_index);
109
+
110
+ if ((light.flags & ${LIGHT_FLAGS.CASTS_SHADOW_BIT}u) == 0u) {
111
+ return 1.0;
112
+ }
113
+
114
+ let view_meta = ${SHADOW_SPOT_DESCRIPTOR.marshalling_method_read}(database, light.shadow_id);
115
+
116
+ // Spot shadows are a single perspective view, so the divide is load-bearing (unlike the ortho
117
+ // cascades, where w is always 1). No bias offset is added before the transform — see the header.
118
+ let clip_light = view_meta.projection * vec4(position_ws, 1.0);
119
+
120
+ if (clip_light.w <= 0.0) {
121
+ // behind the light's apex: outside the map entirely, and the cone attenuation has already
122
+ // zeroed this light's contribution, so the value is moot. Unshadowed is the safe answer.
123
+ return 1.0;
124
+ }
125
+
126
+ let pos_light = clip_light.xyz / clip_light.w;
127
+
128
+ let texel = volumetrics_shadow_atlas_texel(ndc_to_uv(pos_light.xy), view_meta.atlas);
129
+
130
+ let stored = textureLoad(shadowmapAtlas, texel, 0);
131
+
132
+ // SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR compares "greater" — visible where the receiver's depth
133
+ // exceeds the stored one, which under reverse-Z means nearer to the light. Reproduced here by
134
+ // hand because the raw load skips the comparison sampler.
135
+ return select(0.0, 1.0, pos_light.z > stored);
136
+ }
137
+ `, [
138
+ POINT_DESCRIPTOR.chunk_read,
139
+ SPOT_DESCRIPTOR.chunk_read,
140
+ SHADOW_POINT_DESCRIPTOR.chunk_read,
141
+ SHADOW_SPOT_DESCRIPTOR.chunk_read,
142
+ chunk_ndc_to_uv,
143
+ chunk_uv_octahedral_unit_encode,
144
+ ]
145
+ );
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Extinction of the participating medium at a world position, or zero where the froxel grid does
3
+ * not reach.
4
+ *
5
+ * The grid is camera-shaped, so "does not reach" means outside the view frustum or past its far
6
+ * face — which is the medium's real extent as far as any consumer here is concerned, and the reason
7
+ * the sun's optical depth still stops at the frustum (Q-05) after this change as it did before it.
8
+ *
9
+ * The depth guard is not redundant with the bounds test. Behind the camera `view_depth` goes
10
+ * negative, the depth curve takes `log2` of a negative and yields NaN, and every comparison against
11
+ * NaN is false — so `any(abs(uvw - 0.5) >= 0.5)` reports the sample as *inside* and the fetch reads
12
+ * whatever the clamp lands on. The optical-depth march never had to care because it starts inside
13
+ * the frustum and breaks at the first sample outside it; a pass that sweeps a box does.
14
+ *
15
+ * @see shader_volumetrics_build_sun_shadow_volume
16
+ * @type {CodeChunk}
17
+ */
18
+ export const chunk_volumetrics_sample_froxel_extinction: CodeChunk;
19
+ import { CodeChunk } from "../shader/compiler/CodeChunk.js";
20
+ //# sourceMappingURL=chunk_volumetrics_sample_froxel_extinction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk_volumetrics_sample_froxel_extinction.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/chunk_volumetrics_sample_froxel_extinction.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AACH,yDAFU,SAAS,CA4BjB;0BA9CwB,iCAAiC"}
@@ -0,0 +1,48 @@
1
+ import { CAMERA_UNIFORM_STRUCT } from "../camera/CAMERA_UNIFORM_STRUCT.js";
2
+ import { CodeChunk } from "../shader/compiler/CodeChunk.js";
3
+ import { volumetrics_position_world_to_froxel_uvw } from "./volumetrics_position_world_to_froxel_uvw.js";
4
+
5
+ /**
6
+ * Extinction of the participating medium at a world position, or zero where the froxel grid does
7
+ * not reach.
8
+ *
9
+ * The grid is camera-shaped, so "does not reach" means outside the view frustum or past its far
10
+ * face — which is the medium's real extent as far as any consumer here is concerned, and the reason
11
+ * the sun's optical depth still stops at the frustum (Q-05) after this change as it did before it.
12
+ *
13
+ * The depth guard is not redundant with the bounds test. Behind the camera `view_depth` goes
14
+ * negative, the depth curve takes `log2` of a negative and yields NaN, and every comparison against
15
+ * NaN is false — so `any(abs(uvw - 0.5) >= 0.5)` reports the sample as *inside* and the fetch reads
16
+ * whatever the clamp lands on. The optical-depth march never had to care because it starts inside
17
+ * the frustum and breaks at the first sample outside it; a pass that sweeps a box does.
18
+ *
19
+ * @see shader_volumetrics_build_sun_shadow_volume
20
+ * @type {CodeChunk}
21
+ */
22
+ export const chunk_volumetrics_sample_froxel_extinction = CodeChunk.from(
23
+ //language=WGSL
24
+ `
25
+ fn volumetrics_sample_froxel_extinction(position_ws: vec3<f32>) -> vec3<f32> {
26
+
27
+ let position_vs = camera.view_matrix * vec4<f32>(position_ws, 1.0);
28
+
29
+ // view space looks down -Z
30
+ let view_depth = -position_vs.z;
31
+
32
+ if (view_depth <= 0.0) {
33
+ return vec3<f32>(0.0);
34
+ }
35
+
36
+ let uvw = volumetrics_position_world_to_froxel_uvw(position_ws);
37
+
38
+ if (any(abs(uvw - 0.5) >= vec3<f32>(0.5))) {
39
+ return vec3<f32>(0.0);
40
+ }
41
+
42
+ return textureSampleLevel(tParticipatingMedia1, sLinear, uvw, 0.0).rgb;
43
+ }
44
+ `, [
45
+ CAMERA_UNIFORM_STRUCT.declaration_chunk,
46
+ volumetrics_position_world_to_froxel_uvw,
47
+ ]
48
+ );