@woosh/meep-engine 3.59.0 → 3.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/shade/playground/probe_clipmap/index.html +12 -0
- package/src/shade/playground/probe_clipmap/main.d.ts.map +1 -1
- package/src/shade/playground/probe_clipmap/main.js +5 -0
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +4 -6
- package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts +63 -1
- package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +228 -16
- package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts +27 -4
- package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts.map +1 -1
- package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.js +526 -23
- package/src/shade/renderer/geometry/sdf/cards/MeshCardCaptureMode.d.ts +11 -0
- package/src/shade/renderer/geometry/sdf/cards/MeshCardCaptureMode.d.ts.map +1 -0
- package/src/shade/renderer/geometry/sdf/cards/MeshCardCaptureMode.js +21 -0
- package/src/shade/renderer/geometry/sdf/cards/mesh_cards_raster_pass_descriptor.d.ts +65 -0
- package/src/shade/renderer/geometry/sdf/cards/mesh_cards_raster_pass_descriptor.d.ts.map +1 -0
- package/src/shade/renderer/geometry/sdf/cards/mesh_cards_raster_pass_descriptor.js +376 -0
- package/src/shade/renderer/geometry/sdf/cards/shader_mesh_cards_capture.d.ts +7 -3
- package/src/shade/renderer/geometry/sdf/cards/shader_mesh_cards_capture.d.ts.map +1 -1
- package/src/shade/renderer/geometry/sdf/cards/shader_mesh_cards_capture.js +70 -6
- package/src/shade/renderer/geometry/sdf/cards/shader_mesh_cards_raster_args.d.ts +20 -0
- package/src/shade/renderer/geometry/sdf/cards/shader_mesh_cards_raster_args.d.ts.map +1 -0
- package/src/shade/renderer/geometry/sdf/cards/shader_mesh_cards_raster_args.js +80 -0
- package/src/shade/renderer/geometry/sdf/cards/shader_mesh_cards_raster_scatter.d.ts +18 -0
- package/src/shade/renderer/geometry/sdf/cards/shader_mesh_cards_raster_scatter.d.ts.map +1 -0
- package/src/shade/renderer/geometry/sdf/cards/shader_mesh_cards_raster_scatter.js +113 -0
- package/src/shade/renderer/geometry/virtual/sdf/VGEO_SDF.d.ts +47 -0
- package/src/shade/renderer/geometry/virtual/sdf/VGEO_SDF.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/sdf/VGEO_SDF.js +80 -0
- package/src/shade/renderer/geometry/virtual/sdf/VGeoSDFProxies.d.ts +91 -0
- package/src/shade/renderer/geometry/virtual/sdf/VGeoSDFProxies.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/sdf/VGeoSDFProxies.js +902 -0
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_collect.d.ts +16 -0
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_collect.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_collect.js +67 -0
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_decode.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_decode.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_decode.js +145 -0
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_traverse.d.ts +12 -0
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_traverse.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_traverse.js +105 -0
- package/src/shade/renderer/global_illumination/probe/octahedral/shader_lpv_build_gbuffer.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe/octahedral/shader_lpv_build_gbuffer.js +2 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts +3 -9
- package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +13 -31
- package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +764 -48
- package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md +410 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts +3 -2
- package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.js +4 -3
- package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSettings.d.ts +41 -17
- package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSettings.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSettings.js +57 -33
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/BRICK_CLIPMAP.d.ts +11 -3
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/BRICK_CLIPMAP.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/BRICK_CLIPMAP.js +13 -4
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/GPUBrickClipmap.d.ts +9 -18
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/GPUBrickClipmap.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/GPUBrickClipmap.js +8 -10
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/chunk_brick_clipmap.d.ts +12 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/chunk_brick_clipmap.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/chunk_brick_clipmap.js +28 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/chunk_brick_clipmap_trace.d.ts +1 -29
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/chunk_brick_clipmap_trace.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/chunk_brick_clipmap_trace.js +1 -74
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/MESH_BRICK.d.ts +161 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/MESH_BRICK.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/MESH_BRICK.js +219 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/MESH_BRICK_INSTANCE_STRUCT.d.ts +1 -8
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/MESH_BRICK_INSTANCE_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/MESH_BRICK_INSTANCE_STRUCT.js +1 -9
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/MeshSDFBrickProvider.d.ts +40 -12
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/MeshSDFBrickProvider.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/MeshSDFBrickProvider.js +373 -31
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_instance.d.ts +2 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_instance.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_instance.js +2 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_texel.d.ts +15 -8
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_texel.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_texel.js +28 -14
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_walk.d.ts +23 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_walk.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_walk.js +66 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_batch.d.ts +29 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_batch.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_batch.js +210 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_command.d.ts +29 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_command.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_command.js +86 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_gather.d.ts +41 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_gather.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_gather.js +439 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_resolve.d.ts +23 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_resolve.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_resolve.js +135 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/shader_brick_clipmap_free.d.ts +3 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/shader_brick_clipmap_free.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/shader_brick_clipmap_free.js +12 -3
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +21 -18
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.js +254 -73
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.d.ts +149 -8
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.js +181 -13
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.d.ts +179 -7
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.js +412 -31
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_pick.js +4 -4
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.js +4 -3
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.d.ts +36 -10
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.js +218 -29
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_admit.d.ts +5 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_admit.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_admit.js +24 -14
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_due.d.ts +15 -6
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_due.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_due.js +21 -22
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_guide.d.ts +36 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_guide.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_guide.js +257 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_light.d.ts +18 -7
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_light.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_light.js +157 -51
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_order.d.ts +23 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_order.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_order.js +110 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_project.d.ts +24 -2
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_project.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_project.js +90 -10
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_ray_layout.d.ts +45 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_ray_layout.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_ray_layout.js +205 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_relocate.js +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_retire.d.ts +16 -3
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_retire.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_retire.js +55 -10
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_schedule.d.ts +19 -4
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_schedule.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_schedule.js +60 -6
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts +4 -8
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.js +21 -36
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace.d.ts +21 -14
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace.js +100 -30
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace_command.d.ts +6 -2
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace_command.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace_command.js +26 -10
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_want_hits.d.ts +3 -2
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_want_hits.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_want_hits.js +12 -12
- package/src/shade/renderer/gpu_primitive/sort/SORT_DIGIT_SETTINGS_STRUCT.d.ts +10 -0
- package/src/shade/renderer/gpu_primitive/sort/SORT_DIGIT_SETTINGS_STRUCT.d.ts.map +1 -0
- package/src/shade/renderer/gpu_primitive/sort/SORT_DIGIT_SETTINGS_STRUCT.js +21 -0
- package/src/shade/renderer/gpu_primitive/sort/SORT_PASS_SETTINGS_STRUCT.d.ts +5 -2
- package/src/shade/renderer/gpu_primitive/sort/SORT_PASS_SETTINGS_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/gpu_primitive/sort/SORT_PASS_SETTINGS_STRUCT.js +31 -23
- package/src/shade/renderer/gpu_primitive/sort/SORT_SIZE_STRUCT.d.ts +10 -0
- package/src/shade/renderer/gpu_primitive/sort/SORT_SIZE_STRUCT.d.ts.map +1 -0
- package/src/shade/renderer/gpu_primitive/sort/SORT_SIZE_STRUCT.js +22 -0
- package/src/shade/renderer/gpu_primitive/sort/chunk_sort_constants.d.ts +9 -0
- package/src/shade/renderer/gpu_primitive/sort/chunk_sort_constants.d.ts.map +1 -0
- package/src/shade/renderer/gpu_primitive/sort/chunk_sort_constants.js +18 -0
- package/src/shade/renderer/gpu_primitive/sort/chunk_sort_digit.d.ts +8 -3
- package/src/shade/renderer/gpu_primitive/sort/chunk_sort_digit.d.ts.map +1 -1
- package/src/shade/renderer/gpu_primitive/sort/chunk_sort_digit.js +41 -32
- package/src/shade/renderer/gpu_primitive/sort/chunk_sort_histogram_tile.d.ts +31 -0
- package/src/shade/renderer/gpu_primitive/sort/chunk_sort_histogram_tile.d.ts.map +1 -0
- package/src/shade/renderer/gpu_primitive/sort/chunk_sort_histogram_tile.js +71 -0
- package/src/shade/renderer/gpu_primitive/sort/chunk_sort_scatter_tile.d.ts +47 -0
- package/src/shade/renderer/gpu_primitive/sort/chunk_sort_scatter_tile.d.ts.map +1 -0
- package/src/shade/renderer/gpu_primitive/sort/chunk_sort_scatter_tile.js +210 -0
- package/src/shade/renderer/gpu_primitive/sort/defs.d.ts +5 -4
- package/src/shade/renderer/gpu_primitive/sort/defs.d.ts.map +1 -1
- package/src/shade/renderer/gpu_primitive/sort/defs.js +5 -4
- package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.d.ts +56 -15
- package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.d.ts.map +1 -1
- package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.js +410 -61
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram.d.ts +4 -1
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram.d.ts.map +1 -1
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram.js +16 -42
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram_indirect.d.ts +15 -0
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram_indirect.d.ts.map +1 -0
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram_indirect.js +60 -0
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_prepare.d.ts +25 -0
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_prepare.d.ts.map +1 -0
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_prepare.js +89 -0
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter.d.ts +3 -8
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter.d.ts.map +1 -1
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter.js +28 -160
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter_indirect.d.ts +12 -0
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter_indirect.d.ts.map +1 -0
- package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter_indirect.js +74 -0
- package/src/shade/renderer/gpu_primitive/sort/verify_radix_sort.d.ts +3 -1
- package/src/shade/renderer/gpu_primitive/sort/verify_radix_sort.d.ts.map +1 -1
- package/src/shade/renderer/gpu_primitive/sort/verify_radix_sort.js +387 -188
- package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_irradiance_flat.js +1 -1
- package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_sh.js +1 -1
- package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
- package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +90 -72
- package/src/shade/renderer/material/resident/RESIDENT_MATERIAL_TEXTURE_LAYOUT.d.ts +5 -0
- package/src/shade/renderer/material/resident/RESIDENT_MATERIAL_TEXTURE_LAYOUT.d.ts.map +1 -1
- package/src/shade/renderer/material/resident/RESIDENT_MATERIAL_TEXTURE_LAYOUT.js +9 -0
- package/src/shade/renderer/path_tracer/shader/INDIRECT_TEXTURE_FILTER.d.ts +13 -0
- package/src/shade/renderer/path_tracer/shader/INDIRECT_TEXTURE_FILTER.d.ts.map +1 -0
- package/src/shade/renderer/path_tracer/shader/INDIRECT_TEXTURE_FILTER.js +14 -0
- package/src/shade/renderer/path_tracer/shader/IndirectTextureFilter.d.ts +10 -0
- package/src/shade/renderer/path_tracer/shader/IndirectTextureFilter.d.ts.map +1 -0
- package/src/shade/renderer/path_tracer/shader/IndirectTextureFilter.js +15 -0
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_sample_tangent_normal.d.ts +4 -3
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_sample_tangent_normal.d.ts.map +1 -1
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_sample_tangent_normal.js +6 -5
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_sample_texture.d.ts +17 -2
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_sample_texture.d.ts.map +1 -1
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_sample_texture.js +25 -57
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_sample_texture_exact.d.ts +13 -0
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_sample_texture_exact.d.ts.map +1 -0
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_sample_texture_exact.js +70 -0
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_sample_texture_stochastic.d.ts +10 -0
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_sample_texture_stochastic.d.ts.map +1 -0
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_sample_texture_stochastic.js +59 -0
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_texture_load.d.ts +12 -0
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_texture_load.d.ts.map +1 -0
- package/src/shade/renderer/path_tracer/shader/chunk_indirect_texture_load.js +49 -0
- package/src/shade/renderer/path_tracer/shader/chunk_ray_hit_to_opacity.d.ts.map +1 -1
- package/src/shade/renderer/path_tracer/shader/chunk_ray_hit_to_opacity.js +1 -0
- package/src/shade/renderer/path_tracer/shader/chunk_render_trace_path.d.ts.map +1 -1
- package/src/shade/renderer/path_tracer/shader/chunk_render_trace_path.js +1 -0
- package/src/shade/renderer/path_tracer/shader/chunk_sample_material.d.ts.map +1 -1
- package/src/shade/renderer/path_tracer/shader/chunk_sample_material.js +2 -1
- package/src/shade/renderer/path_tracer/shader/chunk_sample_material_data.d.ts +7 -0
- package/src/shade/renderer/path_tracer/shader/chunk_sample_material_data.d.ts.map +1 -1
- package/src/shade/renderer/path_tracer/shader/chunk_sample_material_data.js +15 -6
- package/src/shade/renderer/scene/GPUSceneContext.d.ts +7 -1
- package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
- package/src/shade/renderer/scene/GPUSceneContext.js +26 -1
- package/src/shade/renderer/shader/chunk/bvh/chunk_bvh_query_aabb_cooperative.d.ts +7 -7
- package/src/shade/renderer/shader/chunk/bvh/chunk_bvh_query_aabb_cooperative.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/bvh/chunk_bvh_query_aabb_cooperative.js +262 -294
- package/src/shade/renderer/shader/chunk/filter/MULTISCALE_MEAN_STRUCT.d.ts +9 -0
- package/src/shade/renderer/shader/chunk/filter/MULTISCALE_MEAN_STRUCT.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/filter/MULTISCALE_MEAN_STRUCT.js +21 -0
- package/src/shade/renderer/shader/chunk/filter/chunk_multiscale_mean.d.ts +43 -0
- package/src/shade/renderer/shader/chunk/filter/chunk_multiscale_mean.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/filter/chunk_multiscale_mean.js +87 -0
- package/src/shade/renderer/shader/chunk/noise/r/chunk_r2_canonical.d.ts +7 -1
- package/src/shade/renderer/shader/chunk/noise/r/chunk_r2_canonical.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/noise/r/chunk_r2_canonical.js +1 -1
- package/src/shade/renderer/shader/chunk/subgroup/chunk_subgroup_reserve_workgroup.d.ts +21 -0
- package/src/shade/renderer/shader/chunk/subgroup/chunk_subgroup_reserve_workgroup.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/subgroup/chunk_subgroup_reserve_workgroup.js +42 -0
- package/src/shade/renderer/wind/GPUSceneWind.d.ts +1 -1
- package/src/shade/wgsl/emulator/CPUBitmapData.d.ts +19 -0
- package/src/shade/wgsl/emulator/CPUBitmapData.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/CPUBitmapData.js +27 -0
- package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/WGSLLanguageCore.js +39 -18
- package/src/view/elements/progress/segmented/GhostTrail.d.ts +56 -0
- package/src/view/elements/progress/segmented/GhostTrail.d.ts.map +1 -0
- package/src/view/elements/progress/segmented/GhostTrail.js +98 -0
- package/src/view/elements/progress/segmented/RESOURCE_BAR_SEGMENTS.d.ts +3 -1
- package/src/view/elements/progress/segmented/RESOURCE_BAR_SEGMENTS.d.ts.map +1 -1
- package/src/view/elements/progress/segmented/RESOURCE_BAR_SEGMENTS.js +3 -1
- package/src/view/elements/progress/segmented/SEGMENT_TIERS_DEFAULT.d.ts +11 -0
- package/src/view/elements/progress/segmented/SEGMENT_TIERS_DEFAULT.d.ts.map +1 -0
- package/src/view/elements/progress/segmented/SEGMENT_TIERS_DEFAULT.js +15 -0
- package/src/view/elements/progress/segmented/SegmentDefinition.d.ts +3 -0
- package/src/view/elements/progress/segmented/SegmentDefinition.d.ts.map +1 -1
- package/src/view/elements/progress/segmented/SegmentDefinition.js +3 -0
- package/src/view/elements/progress/segmented/SegmentTier.d.ts +38 -0
- package/src/view/elements/progress/segmented/SegmentTier.d.ts.map +1 -0
- package/src/view/elements/progress/segmented/SegmentTier.js +50 -0
- package/src/view/elements/progress/segmented/SegmentedResourceBarView.d.ts +155 -10
- package/src/view/elements/progress/segmented/SegmentedResourceBarView.d.ts.map +1 -1
- package/src/view/elements/progress/segmented/SegmentedResourceBarView.js +427 -101
- package/src/view/elements/progress/segmented/playground/index.html +78 -0
- package/src/view/elements/progress/segmented/playground/main.d.ts +2 -0
- package/src/view/elements/progress/segmented/playground/main.d.ts.map +1 -0
- package/src/view/elements/progress/segmented/playground/main.js +173 -0
- package/src/view/elements/progress/segmented/segment_cell_layout.d.ts +48 -0
- package/src/view/elements/progress/segmented/segment_cell_layout.d.ts.map +1 -0
- package/src/view/elements/progress/segmented/segment_cell_layout.js +75 -0
- package/src/view/elements/progress/segmented/segment_notch_place.d.ts +18 -0
- package/src/view/elements/progress/segmented/segment_notch_place.d.ts.map +1 -0
- package/src/view/elements/progress/segmented/segment_notch_place.js +28 -0
- package/src/view/elements/progress/segmented/segment_tick_positions.d.ts +32 -0
- package/src/view/elements/progress/segmented/segment_tick_positions.d.ts.map +1 -0
- package/src/view/elements/progress/segmented/segment_tick_positions.js +103 -0
- package/src/view/elements/progress/segmented/segmented-resource-bar.css +142 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_provider.d.ts +0 -35
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_provider.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_provider.js +0 -238
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/VGEO_BRICK.d.ts +0 -99
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/VGEO_BRICK.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/VGEO_BRICK.js +0 -139
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/VGeoBrickProvider.d.ts +0 -65
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/VGeoBrickProvider.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/VGeoBrickProvider.js +0 -393
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_box.d.ts +0 -17
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_box.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_box.js +0 -36
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_pair.d.ts +0 -29
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_pair.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_pair.js +0 -108
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_texel.d.ts +0 -31
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_texel.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_texel.js +0 -113
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_triangle.d.ts +0 -36
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_triangle.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_triangle.js +0 -85
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_distance.d.ts +0 -23
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_distance.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_distance.js +0 -273
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_gather.d.ts +0 -13
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_gather.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_gather.js +0 -151
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_traverse.d.ts +0 -13
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_traverse.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_traverse.js +0 -133
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_wait.d.ts +0 -22
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_wait.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_wait.js +0 -135
package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md
CHANGED
|
@@ -22,7 +22,8 @@ Alex reviewed revision (a). Items 1–8 are his; item 9 follows from them.
|
|
|
22
22
|
performance (§6.1).
|
|
23
23
|
4. **Brick providers.** A pass that writes requested bricks plugs in; outputs merge per brick, so the
|
|
24
24
|
representation stays sparse and only bricks that scroll in or change are written. Virtual geometry
|
|
25
|
-
|
|
25
|
+
has caches of its own, baked from a proxy cut from each geometry and composed as a mesh's are (§7.8,
|
|
26
|
+
S8).
|
|
26
27
|
5. **Skinned meshes** use their unskinned representation, with hooks for a dense animated patch later
|
|
27
28
|
(§6.3, §7.7, §17).
|
|
28
29
|
6. **Pixels pick 2 of the 8 cage probes** by Brick4's weighted reservoir sampling, after a
|
|
@@ -79,8 +80,9 @@ What was asked for:
|
|
|
79
80
|
3. **The probe clipmap is what shading samples (§8)**, as in (a).
|
|
80
81
|
- A sparse cache, wanted from the depth buffer. Octahedral radiance `rgba16float`, depth moments
|
|
81
82
|
`rg16float`, SH2 in the engine's YCoCg L1 (`METRO_L1_IRRADIANCE_STRUCT`).
|
|
82
|
-
- A fixed ray budget. Hits are lit by emissive, the sun
|
|
83
|
-
last frame's probes. Their diffuse albedo and emissive come from the
|
|
83
|
+
- A fixed ray budget. Hits are lit by emissive, the sun and one picked local light through shadow
|
|
84
|
+
rays traced in the bricks, and last frame's probes. Their diffuse albedo and emissive come from the
|
|
85
|
+
cards.
|
|
84
86
|
4. **Pixels pick two probes (§9).**
|
|
85
87
|
- A visibility-only pass weighs the eight cage probes: trilinear × wrap back face × Chebyshev ×
|
|
86
88
|
crush.
|
|
@@ -281,8 +283,11 @@ clipmap toolkit (§16).
|
|
|
281
283
|
- it interpolates uv, vertex colour, normal and tangent from a triangle and barycentrics;
|
|
282
284
|
- it samples the material: albedo × colour factor × vertex colour, ORM, emissive × factor, opacity.
|
|
283
285
|
- It reads `GPUResidentMaterialContext` (on `GraphicsContext`): the material table
|
|
284
|
-
(`INDIRECT_MATERIAL_STRUCT`) and a 2-D array of 64² texture slots,
|
|
285
|
-
|
|
286
|
+
(`INDIRECT_MATERIAL_STRUCT`) and a 2-D array of 64² texture slots, each with its mip chain down to
|
|
287
|
+
one texel since 2026-10-30, sampled by `indirect_sample_texture` at an explicit level with each
|
|
288
|
+
sampler's wrap mode: one stochastic tap of the trilinear filter by default, which at level 0 is the
|
|
289
|
+
stochastic bilinear tap it always was, or the filter itself where the shader's
|
|
290
|
+
`INDIRECT_TEXTURE_FILTER` is `Exact`.
|
|
286
291
|
- Its taps and dither draw on the path tracer's random state. Alpha is stochastic: a hit is opaque
|
|
287
292
|
with probability equal to its opacity (`chunk_ray_shaded_query_occluded`, through
|
|
288
293
|
`sample_material_alpha`).
|
|
@@ -296,7 +301,7 @@ clipmap toolkit (§16).
|
|
|
296
301
|
|
|
297
302
|
**What rays could not see.** vgeo rows are in the TLAS with an empty BLAS, and DynamicMesh has no row.
|
|
298
303
|
Under (b):
|
|
299
|
-
- virtual geometry is
|
|
304
|
+
- virtual geometry is baked from a proxy cut from it, and composed as a mesh (§7.8, S8);
|
|
300
305
|
- DynamicMesh stays out by construction: it changes every frame, and a cache of it would be a cache of
|
|
301
306
|
nothing (§17).
|
|
302
307
|
|
|
@@ -420,8 +425,8 @@ the SDF [49:08].
|
|
|
420
425
|
| octahedral radiance + border; distance with mean/mean² | same, `rgba16float` and `rg16float` with the engine's skirt chunks (§8.2) | hardware bilinear for every consumer tap |
|
|
421
426
|
| irradiance as octahedral 12² (frustum) or SH (world cache) | SH2 YCoCg L1 for every probe | the request; 32 B a probe |
|
|
422
427
|
| fixed ray budget; per-probe requests scaled to it | budget in rays a frame; period follows the live count (§8.5) | same intent, deterministic schedule |
|
|
423
|
-
| importance-sampled directions + every-5th-frame uniform |
|
|
424
|
-
| moving-average estimator, 5 u32 per texel |
|
|
428
|
+
| importance-sampled directions + every-5th-frame uniform | the same: every 5th update uniform, the rest a CDF walk over the probe's history from a rotating corner, each texel held under 64 rays (§8.6, as built 2026-10-29) | Alex found the noise of one jittered ray a texel visible (D11) |
|
|
429
|
+
| moving-average estimator, 5 u32 per texel | the same estimator, its short mean over a response time in seconds; 6 words a texel with the atlas's (§8.8) | built 2026-10-29, once Alex judged the noise visible (D11); the knob stays physical |
|
|
425
430
|
| SDF shadow ray per hit for the sun | same (§8.6) | shadow maps answer "lit" off screen (§1) |
|
|
426
431
|
| light cache for point lights | the world light clusters, keyed by this clipmap (§8.7) | reuse |
|
|
427
432
|
| world-cache probes at hits | hits want the probes they read, one step from the screen, the one probe kind (§8.3) | the off-screen bounce measured short at S5a (D31) |
|
|
@@ -692,7 +697,7 @@ Embark Studios' research renderer.
|
|
|
692
697
|
| Mesh SDF | sparse bricks in size classes, with levels | `GPUGeometrySDFManager`'s dense tiles | the review; memory follows the surface |
|
|
693
698
|
| Material | cards holding diffuse albedo and emissive captured per (geometry, material), recaptured when the material's version moves | the voxel lattice, (a); cards holding uvs with the material evaluated live | the review: no material work at a hit (§15) |
|
|
694
699
|
| Probe residency | sparse cache from the screen | dense lattice | 10× less memory and work at the same lattice (§11) |
|
|
695
|
-
| Rays | one jittered ray per texel
|
|
700
|
+
| Rays | Enshrouded's: guided by the probe's radiance, every 5th update uniform (§8.6) | one jittered ray per texel, (b) as first built | its noise measured visible (D11) |
|
|
696
701
|
| Temporal | exponential over a response time | moving-average estimator | one history, 8 B a texel |
|
|
697
702
|
| Pixel sampling | 2 of 8 by WRS | all eight, (a); 1 of 8 + reservoirs + denoise (Enshrouded) | the review; two probe reads, no denoiser |
|
|
698
703
|
| Specular | radiance tap ⊕ GGX-convolved SH2 | prefiltered levels | the review; LPV's blend |
|
|
@@ -795,10 +800,10 @@ Verdicts:
|
|
|
795
800
|
| Pick 2 of 8 | `sample_discrete_wrs_mat2x4`; the selection inside `brick4_probe_meta_pick2` | **REUSE**; **EXTRACT** `pick2_wrs_mat2x4`, Brick4 keeps its address mapping |
|
|
796
801
|
| Noise | `chunk_stbn_sample_vec2`; `jitter_from_vectors` (private to Brick4's forward chunk) | **REUSE**; **EXTRACT** the hash |
|
|
797
802
|
| Directional lights | `directional_lights_iteration_mask`, the light database records | **REUSE** |
|
|
798
|
-
| Local lights at a world point | `world_light_cluster_metadata_by_texel`, `irradiance_clipmap_get_point_light_info_by_index` / `_spot_
|
|
803
|
+
| Local lights at a world point | `world_light_cluster_metadata_by_texel`, `irradiance_clipmap_get_point_light_info_by_index` / `_spot_…` | **GENERALISE** the cluster keying from `IRRADIANCE_CLIPMAP_STRUCT` to a `CLIPMAP_LEVEL_ARRAY`; the light chunks **REUSE**. Their shadows were `volumetrics_{point,spot}_light_shadow` until 2026-10-30, and are shadow rays in the bricks since (§8.7) |
|
|
799
804
|
| Sky for misses; final fallback | `sample_environment_color`, `get_ibl_irradiance` | **REUSE** |
|
|
800
805
|
| Specular split at a pixel | `compute_indirect_specular`, `tSplitSum`, inside the deferred resolve | **REUSE** |
|
|
801
|
-
| SDF soft-shadow penumbra | inside `shader_scene_sdf_soft_shadows` | **EXTRACT** the estimator as a pure chunk; the dense-volume shader keeps calling it. Not done (S3e): its classic estimate cannot meet §12's oracle, and the brick clipmap's
|
|
806
|
+
| SDF soft-shadow penumbra | inside `shader_scene_sdf_soft_shadows` | **EXTRACT** the estimator as a pure chunk; the dense-volume shader keeps calling it. Not done (S3e): its classic estimate cannot meet §12's oracle, and the brick clipmap's was Quílez's two-sided one (§7.9), itself replaced by shadow rays on 2026-10-30 (§8.7) |
|
|
802
807
|
| Appends | `chunk_collection_u32_append` | **REUSE** |
|
|
803
808
|
| Parallax toward a probe's hit | LPV's iterative correction in `lpv_sample_light` | **GENERALISE** to take a depth-moment reader (§9.3) |
|
|
804
809
|
|
|
@@ -843,7 +848,7 @@ brick: scroll ─► movers ─► take ─► clear ─► providers (mesh, v
|
|
|
843
848
|
│ brick map, SDF atlas, object cells, occupancy
|
|
844
849
|
probe: retire ─► want (depth) ─► admit ─► schedule ─┐
|
|
845
850
|
▼
|
|
846
|
-
trace (+ sun
|
|
851
|
+
trace (+ sun shadow rays) ─► shade ─► light (+ local shadow ray) ─► accumulate ─► publish
|
|
847
852
|
(rays = trace list × rays_per_probe ≤ ray_budget)
|
|
848
853
|
│ indirection, states, records (SH2),
|
|
849
854
|
│ radiance + depth atlases
|
|
@@ -877,8 +882,8 @@ How the bit travels:
|
|
|
877
882
|
- A row outside the SDF world is drawn by the camera and invisible to every GI ray. It occludes
|
|
878
883
|
nothing, emits nothing into GI and bounces nothing. That is the point of the bit: performance.
|
|
879
884
|
|
|
880
|
-
**A geometry is cached** when at least one row in the SDF world draws it
|
|
881
|
-
-
|
|
885
|
+
**A geometry is cached** when at least one row in the SDF world draws it.
|
|
886
|
+
- A virtual geometry's cache is baked from a proxy cut from it, and adopted into it (§7.8, S8).
|
|
882
887
|
- DynamicMesh has no row, so it is absent by construction.
|
|
883
888
|
|
|
884
889
|
**What is cached, and keyed by what**:
|
|
@@ -1129,11 +1134,38 @@ the row's pair.
|
|
|
1129
1134
|
is empty.
|
|
1130
1135
|
3. At the opaque hit, `sample_material_data` runs with the material's resident row and an identity
|
|
1131
1136
|
instance transform (the card is in local space).
|
|
1132
|
-
-
|
|
1133
|
-
|
|
1137
|
+
- Since 2026-10-30 both reads take the material's textures at the texel's footprint, trilinearly
|
|
1138
|
+
(`mesh_card_texture_level`; the capture is `INDIRECT_TEXTURE_FILTER` `Exact`): the card's texel
|
|
1139
|
+
steps carried along δ onto the triangle and through its uv per position, the level
|
|
1140
|
+
log2(texels × 64), the last level for a triangle edge-on.
|
|
1141
|
+
- Its random state is seeded from (pair, texel), which fixes its alpha draws and its dither.
|
|
1134
1142
|
- A recapture of an unchanged material therefore writes the same bytes.
|
|
1135
1143
|
4. The texel stores √(albedo × (1 − metalness)), depth, and emissive / s_e.
|
|
1136
1144
|
|
|
1145
|
+
**Capture by raster** (`MeshCardCaptureMode.Raster`, the default since 2026-10-30), UE's way: the
|
|
1146
|
+
cards are drawn rather than traced, through the material's own template.
|
|
1147
|
+
- The trace above took one level-0 tap of the material's 64² resident copy a texel and kept it, a
|
|
1148
|
+
point sample of a texture a card texel covers many texels of. On Sponza's outer wall the brick
|
|
1149
|
+
texture's dark band repeats every 12.8 card texels; it landed on a row of sample points once and
|
|
1150
|
+
between rows at its other repeats, a hard seam across the wall. It now reads the copy's mips at
|
|
1151
|
+
the footprint (step 3), and over that wall its cards differ from the raster's by 0.77 codes on
|
|
1152
|
+
average, against 10.5 before.
|
|
1153
|
+
- A pair is taken whole. Its cards are drawn by one indirect draw, an instance a meshlet of each card
|
|
1154
|
+
the layout has (a compute pass writes the count), each card into a cell of the pair's region of a
|
|
1155
|
+
scratch: orthographic along δ, the lattice point at the texel's centre, the slab the depth range,
|
|
1156
|
+
so the hardware clips to it as the trace's t ≤ depth does. Culling is off: a card sees either side.
|
|
1157
|
+
- The fragment stage is the template's `material_surface` and `material_opacity`, with the material's
|
|
1158
|
+
one bind group, the G-buffer pass's: its textures, their mips and hardware filtering, at the card
|
|
1159
|
+
texel's footprint, unbiased as UE's card capture leaves `MaterialTextureMipBias` at 0. Coverage is
|
|
1160
|
+
drawn as the trace draws it, from a seed fixed per (pair, card, texel); the depth test keeps the
|
|
1161
|
+
nearest opaque draw. A template without a surface function is traced.
|
|
1162
|
+
- A scatter then moves each page's texels from the pair's cells into its atlas tiles, in the trace's
|
|
1163
|
+
encoding.
|
|
1164
|
+
- Measured on Sponza: the cards' colours agree with the trace's to 3 codes on average over the wall,
|
|
1165
|
+
floor, ceiling and banners, without the seam or the trace's speckle. Recapturing all 154,138 pages
|
|
1166
|
+
at the largest budget, interleaved in one process, cost the trace 9.1–12.7 ms and the raster 0.6 ms
|
|
1167
|
+
on the RTX 4090, and 335 ms against 33 ms on the AMD iGPU.
|
|
1168
|
+
|
|
1137
1169
|
**Recapture.**
|
|
1138
1170
|
- `GPUResidentMaterialContext` signals when it repacks a material whose version moved (§4.1). The GI
|
|
1139
1171
|
keeps the reverse map from a material to its pairs and queues each for recapture: O(changes), E5.
|
|
@@ -1435,12 +1467,23 @@ This is the plugin system.
|
|
|
1435
1467
|
cannot name a row writes 0, and its hits take the material of no row: zero albedo, counted.
|
|
1436
1468
|
6. **Invalidation.** When a provider's data for a region changes (streaming, an edit), it appends that
|
|
1437
1469
|
world box to the invalidation list, and next frame's movers queue the cells.
|
|
1470
|
+
7. **Retry** (S9). A provider that could not finish a cell this frame sets the cell's retry word in the
|
|
1471
|
+
staging (`BRICK_STAGING_RETRY_WORD`). Free decides the cell as any other, keeps its key pending and
|
|
1472
|
+
puts it back on the ring, so the cell is built again from the start. What was merged meanwhile is
|
|
1473
|
+
published for those frames, and can over-estimate where the unfinished work would have read lower;
|
|
1474
|
+
the frame that finishes the cell keeps contract 4.
|
|
1438
1475
|
|
|
1439
1476
|
A provider sees every taken key. One with nothing in a cell writes nothing and pays only its own
|
|
1440
1477
|
early-out.
|
|
1441
1478
|
|
|
1442
1479
|
### 7.7 The mesh provider
|
|
1443
1480
|
|
|
1481
|
+
**Superseded in part by S9 (2026-10-29).** Alex, after S8's holes: "Go with (b): batches of 64 spread over
|
|
1482
|
+
workgroups by count, with an atomic min/max merge." The cap of 64 instances below bound from cascade 2 on Zorah, and
|
|
1483
|
+
the instances it kept were the lowest rows, in load order: the ground under the greenhouse's ring lost to the kitbash
|
|
1484
|
+
numbered before it. The accept, the per-instance level and the texel stand; the cap, and one workgroup doing all of a
|
|
1485
|
+
cell, are gone. What is built now is "As built (S9)", at the end of this section.
|
|
1486
|
+
|
|
1444
1487
|
`MeshSDFBrickProvider` runs one 64-lane workgroup per taken key.
|
|
1445
1488
|
|
|
1446
1489
|
**Gather.** A TLAS cooperative query with box = cell ⊕ R_c. The leaf accept takes a row when all of
|
|
@@ -1510,11 +1553,83 @@ count (E3), and no triangle is read.
|
|
|
1510
1553
|
- With EMPTY signed (§6.3) the building's air reads inside its open hull (geometry 102), so the
|
|
1511
1554
|
courtyard is solid in the world bricks too; put to Alex.
|
|
1512
1555
|
|
|
1556
|
+
**As built (S9, 2026-10-29)**: every instance whose box comes within the band of a taken cell is sampled there,
|
|
1557
|
+
however many. Three passes, each sized by a count on the device, and a one-thread command
|
|
1558
|
+
(`shader_mesh_brick_command`) before each but the walks from the root:
|
|
1559
|
+
- **Walk** (`shader_mesh_brick_gather`), a workgroup a walk: the cooperative query's level-at-a-time expansion and
|
|
1560
|
+
the S3 accept, nothing dropped.
|
|
1561
|
+
- A level keeps 512 nodes (`MESH_BRICK_FRONTIER`); the children past them leave in task records of 64 nodes, each
|
|
1562
|
+
walked by the next round of the same kernel. On Zorah's TLAS (12,608 leaves, 27 levels) the widest level of a
|
|
1563
|
+
cell's walk is 758 nodes at cascade 2 and 2,410 at cascade 5, so every walk ends in its third round.
|
|
1564
|
+
- Rounds start at two and grow by one, from a read-back count, when the last round could not finish a walk, up to
|
|
1565
|
+
six: enough for any LBVH of 2^20 leaves.
|
|
1566
|
+
- Accepted rows leave in batch records of 64, and the rows left when the walk ends in one more.
|
|
1567
|
+
- An iteration of a level costs the workgroup a barrier and a `workgroupUniformLoad`: lane 0 hands every lane
|
|
1568
|
+
where the next iteration's children start and, on the level's last, how many nodes the next level holds.
|
|
1569
|
+
- **Batch** (`shader_mesh_brick_batch`), a workgroup a record: its rows sorted, its 64 instances in workgroup memory,
|
|
1570
|
+
512 texels × 64 as S3c samples them, merged into the cell's accumulators by atomic maxima of 255 less the byte and
|
|
1571
|
+
of the object key.
|
|
1572
|
+
- **Resolve** (`shader_mesh_brick_resolve`), a workgroup a cell the walks listed: its accumulators into the staging
|
|
1573
|
+
by contracts 2 and 3. A walk lists a cell the first time it flags it, for a batch made or a walk left unfinished;
|
|
1574
|
+
most taken cells have no instance within their band and cost the walk from the root alone.
|
|
1575
|
+
- Contract 7: a record past its capacity, a level past the budget, or nodes past the last round's frontier leave the
|
|
1576
|
+
cell unfinished. It is retried, and the capacity grows to the power of two at or past the most asked for, or the
|
|
1577
|
+
rounds by one. Of the records a walk asks for at once, those below the capacity are kept, so a command never
|
|
1578
|
+
dispatches a record that went unwritten.
|
|
1579
|
+
- The texel compares in the staged byte, the lowest row on a tie, so the object no longer depends on the order of
|
|
1580
|
+
the instances; the batch sorts its rows, so a cell of one batch reads the same whatever order its walk found them.
|
|
1581
|
+
- **Device, Zorah** (S8's load, filled; from the aerial view, the GI released and all 196,608 cells of the windows
|
|
1582
|
+
built again, 60 frames; S8's provider against this one in one process, old/new/new/old), with the frontier at 1,024
|
|
1583
|
+
nodes as S9 landed it; the 512 it has now follows below:
|
|
1584
|
+
|
|
1585
|
+
| | iGPU, S8 → S9 | 4090, S8 → S9 |
|
|
1586
|
+
|---|---|---|
|
|
1587
|
+
| brick view's hole pixels | 10,323–10,379 → 41 | 10,071–10,078 → 36 |
|
|
1588
|
+
| the provider over the rebuild | 76.9–78.0 → 132.6–132.7 ms | 7.2–7.6 → 16.0–18.1 ms |
|
|
1589
|
+
| walk / batches / resolve | 71.4 / 56.4 / 4.7 ms | 5.5–7.6 / 8.5–9.7 / 0.5–1.1 ms |
|
|
1590
|
+
| a frame of it, p50 / p90 / worst | 1.15 / 2.0 / 6.4 → 1.08 / 3.6–4.2 / 17.7 ms | 0.09–0.11 / 0.17 / 0.9–1.4 → 0.20 / 0.65–0.73 / 1.3–1.4 ms |
|
|
1591
|
+
| at rest | 1.4–1.5 → 8.5–11.2 µs | 3.1 → 16.4 µs |
|
|
1592
|
+
| rebuild against rebuild, bricks that differ | 276 → 0 | 254 → 363 |
|
|
1593
|
+
|
|
1594
|
+
- The 36–41 pixels left are the dome's lattice, thinner than cascade 2's voxel: S8's open item, not the cap. The
|
|
1595
|
+
ground cells under the ring, S8's open item, were the cap.
|
|
1596
|
+
- Nothing retried, no record past a capacity, and every walk ended in the second round: at most 1,431 batches and
|
|
1597
|
+
220 tasks a frame (iGPU), within the initial capacities; at most 805 cells a frame listed for the resolve.
|
|
1598
|
+
- Where the cost goes, from one-off arms in the same process with a part of the work deleted:
|
|
1599
|
+
- The batches sample every instance: 257,216 rows a rebuild, where S8's provider dropped 37,780 instances past 64
|
|
1600
|
+
a cell, 93,700 candidates past 128 and 43,500 frontier nodes, so sampled at most 125,736. The batch pass with
|
|
1601
|
+
nothing sampled costs 0.3–0.5 ms, and its sort 1.7 ms on the iGPU (within the noise on the 4090).
|
|
1602
|
+
- The walk's floor, a workgroup a taken cell with its memory cleared and its task read, is 25.8 ms on the iGPU and
|
|
1603
|
+
0.67 ms on the 4090; S8's walk alone, 48.3 and 2.3–2.4 ms. On the 4090 this walk cut to what S8's walked (256 nodes
|
|
1604
|
+
a level, the rest dropped) costs 3.3 ms against its 4.4 ms: 1.1 ms is walking every node, and the 0.9–1.0 ms
|
|
1605
|
+
left over S8's walk is this walk's own bookkeeping, not split further.
|
|
1606
|
+
- Alex asked for a hand-off once a window of four iterations instead of each, the spill and the ring sized for the
|
|
1607
|
+
window (11.3 KiB of workgroup memory against 9.1). Measured paired against this walk, it was no faster on the 4090
|
|
1608
|
+
(the walk's median 4.36 against 4.29 ms, six trials each) and 27% slower on the iGPU (90.3 against 71.3 ms, four
|
|
1609
|
+
each); a window of two was 4% slower there. Not taken: on the iGPU the walk slows with its workgroup memory.
|
|
1610
|
+
- So the frontier went the other way, halved to 512 nodes (Alex, on this measurement): the walk's workgroup memory
|
|
1611
|
+
from 9.1 to 5.1 KiB. Paired against 1,024 (four trials each on the iGPU, six on the 4090, after a warm-up whose
|
|
1612
|
+
rounds grew to three, retrying 8 and 2 cells; none after): on the iGPU the walk 71.1 → 43.0 ms, the provider
|
|
1613
|
+
over the rebuild 132.0 → 104.1 ms, its median frame 1.08–1.27 → 0.65 ms; on the 4090 the rebuild even (17.9 ms
|
|
1614
|
+
by the mean). At rest the third round's command and walk add 2.0 µs on the iGPU and 4.1 µs on the 4090 (9.8 and
|
|
1615
|
+
20.5 µs); batches 7,077 → 7,331, the rows of a cell split over more walks; holes alike. Against 1,024, 30 of the
|
|
1616
|
+
iGPU's 2,684 bricks differ, the texel's box test being order-dependent for a one-sided field, as before.
|
|
1617
|
+
- At rest, seven dispatches (three one-thread commands, and four passes dispatched with no workgroup) against S8's
|
|
1618
|
+
one.
|
|
1619
|
+
- Tier 2: free's retry (red on S8's free), the resolve's contracts, list and tail, the command, the walk's spill
|
|
1620
|
+
partition, the texel's order-free row (red on S8's chunk). Tier 3: the provider's dispatches and their commands, its
|
|
1621
|
+
buffers and their growth. The walk and the batches run on the device only. 25 of 25 mutants were killed.
|
|
1622
|
+
|
|
1513
1623
|
**TODO hook (skinned).** The leaf accept, where a clone resolves to its source's header, is where a
|
|
1514
1624
|
dense animated patch takes over later.
|
|
1515
1625
|
|
|
1516
1626
|
### 7.8 The virtual geometry provider
|
|
1517
1627
|
|
|
1628
|
+
**Superseded by S8 (2026-10-29).** Alex, after S7e: "Move vgeo to per-geometry signed SDF caches (baked once from a
|
|
1629
|
+
cut, composed per instance) instead of fixing the per-cell sign in place." The per-cell provider below (S7b–S7e) is
|
|
1630
|
+
deleted; S7a's cut refactor and S7e's measurements stand. What is built now is "As built (S8)", at the end of this
|
|
1631
|
+
section.
|
|
1632
|
+
|
|
1518
1633
|
S7, its own stage. vgeo rows are mesh rows with a virtual geometry: their BLAS is empty and they have
|
|
1519
1634
|
no cache. The provider, per taken key:
|
|
1520
1635
|
1. **Gather**: the mesh provider's TLAS query, accepting vgeo rows in the SDF world only.
|
|
@@ -1792,6 +1907,86 @@ arrives (contract 6).
|
|
|
1792
1907
|
- Open, measured, cause not established: on both GPUs, the lit frame at the manifest camera has dark blotches on a
|
|
1793
1908
|
lattice over a column whose bricks are clean.
|
|
1794
1909
|
|
|
1910
|
+
**As built (S8, 2026-10-29)**: a virtual geometry's cache is baked from a proxy cut from it, adopted into it, and
|
|
1911
|
+
composed by the mesh provider; the per-cell provider is deleted. The plan and its one fork are the shared note
|
|
1912
|
+
`s8-vgeo-geometry-caches-plan-2026-10-29.md`; Alex took (a), a readback of the cut's triangles into the host's own
|
|
1913
|
+
geometry path.
|
|
1914
|
+
- **The caches** (`GPUGeometrySDFManager`).
|
|
1915
|
+
- `onRequested` is sent with a record each time its cache is queued: at its first holder, at a new voxel size, and
|
|
1916
|
+
when its holders' side changes. `is_requested` says a cache waits queued, held or not, as the bake bakes it until
|
|
1917
|
+
a trim; `holds_two_sided` says the side a bake of it takes.
|
|
1918
|
+
- **Sources** (`add_source`, a `MeshSDFSource`) run first in each `graph_bake`. A source makes the caches of
|
|
1919
|
+
geometries with no tree of their own, which wait queued as a geometry whose tree is not resident yet does.
|
|
1920
|
+
- `adopt(target, proxy)`: the resident cache baked for the proxy becomes the target's, nothing copied. The target's
|
|
1921
|
+
header takes the proxy's layout, entries and side, and the proxy's reads None, as dropped; `onBaked` is sent with
|
|
1922
|
+
the target. The cache has no cards, the target having no tree to capture them with, so a vgeo hit keeps the
|
|
1923
|
+
material's mean (§17). Held for another side than the proxy was baked for, it is queued again at the next bake.
|
|
1924
|
+
- A bake is recorded only for work there is: a queue of geometries no bake can admit yet, and placements dropped or
|
|
1925
|
+
adopted away, record none.
|
|
1926
|
+
- **The source** (`VGeoSDFProxies`, made with the runtime by `GPUSceneContext.enable_virtual_geometry`).
|
|
1927
|
+
- A job a geometry asked for, 32 at once. Each frame a job's geometry is cut whole by the camera cut's own descent:
|
|
1928
|
+
`shader_vgeo_sdf_collect` writes its record (`chunk_vgeo_cut_instance`), the claim widths are scanned, and
|
|
1929
|
+
`graph_vgeo_cut_descent` runs `shader_vgeo_sdf_traverse`, which culls nothing and puts every cluster in the cut onto
|
|
1930
|
+
a list under its job, counting stand-ins and the clusters past the list. It cuts under a slot of its own at τ = 1,
|
|
1931
|
+
k = 1/(½·v₀) in the geometry's own units, v₀ its cache's level-0 voxel. A clamp wants its page there; since a want
|
|
1932
|
+
lasts one reading, the job is cut again each frame until its cut stands nothing in.
|
|
1933
|
+
- `shader_vgeo_sdf_decode`, a workgroup a listed cluster, counts every job's vertices and triangles. For a job
|
|
1934
|
+
whose last cut stood nothing in, it writes the clusters out in room those counts made: positions as stored,
|
|
1935
|
+
triangles as indices into the job's own vertices.
|
|
1936
|
+
- One readback a frame: the jobs' counters, the cut's own (a subtree left undescended voids the frame for every
|
|
1937
|
+
job), and the meshes. A write-out whose counts disagree with its room's is cut again.
|
|
1938
|
+
- The proxy: one a frame on the host, `meshlet_geometry_build_from_geometry` (2.2–4.1 µs a triangle, measured),
|
|
1939
|
+
added to the geometry manager, its cache held with the side the geometry's holders draw. When it turns resident,
|
|
1940
|
+
the next update adopts it into the geometry and removes the proxy. A proxy whose mesh another's has is that one's
|
|
1941
|
+
record: only one adopts, and the other geometry is extracted again.
|
|
1942
|
+
- A job whose geometry is no longer queued is let go; a new voxel size restarts every job.
|
|
1943
|
+
- **The holds** (`ProbeClipmapSceneHolds`): a vgeo row holds its cache as a mesh row does. Its pair's card set waits for
|
|
1944
|
+
good, since an adopted cache has no card layout.
|
|
1945
|
+
- **Deleted**: `brick/vgeo/` (the gather, the box cut, the distances, the wait and their specs), its registration in
|
|
1946
|
+
the GI, and the provider context's `budget` and `pending`, which only it used.
|
|
1947
|
+
- **Device, Zorah** (as S7e loads it: 1,012 geometries, 12,597 instances, one grey material, the manifest's sun,
|
|
1948
|
+
1280×720), entered once with the default settings.
|
|
1949
|
+
- The fill: every one of the 1,012 caches resident after 3,160 frames on the 4090 (31 s) and 3,200 on the AMD iGPU
|
|
1950
|
+
(247 s). 1,013 proxies were built, one twice (a proxy came out alike to another's), from 423,584 triangles: step
|
|
1951
|
+
0's "a few hundred thousand". 8 geometries were coarsened (step 0's 8). The bake evaluated 52,911 bricks, the
|
|
1952
|
+
atlas grew to 65,536 slots, and the caches hold 35.3 MB. Their cuts took 34–42k clusters over ~1,180 readings and
|
|
1953
|
+
stood one in 6–9k times while their pages came; no wavefront ran out, nothing was dropped, and the cluster list
|
|
1954
|
+
never grew past 4,096.
|
|
1955
|
+
- The fill's pace, 0.32 geometries a frame, is mostly the bake's budget of 23 bricks a frame, at 52 bricks a
|
|
1956
|
+
geometry: it averaged 16.7 a frame, idle in 27% of the frames while proxies waited on their pages, their write-out
|
|
1957
|
+
or their build. Zorah's proxies cost less a frame than Sponza's meshes: the evaluate is 652 µs at p50 and 9.0 ms at worst on
|
|
1958
|
+
the 4090, 3.7 ms at p50, 17.7 ms at p90 and 48.9 ms at worst on the iGPU, inside what the budget costs on Sponza
|
|
1959
|
+
(§6.4: 26 ms at the median and 79 ms at worst on the iGPU). The extraction's own passes, in the 1,734 frames that
|
|
1960
|
+
cut: the cut 48 µs at p50 and 499 µs at worst on the 4090 (36 and 337 µs on the iGPU), the decode 4 µs at p50.
|
|
1961
|
+
- The host: a frame that builds a proxy is 4.4 ms at p50 against 3.0 ms without on the 4090's host, 20.7 ms at p99,
|
|
1962
|
+
and 190 ms once, for the largest geometry's proxy (the iGPU run's host: 4.8 / 3.2 / 20.4 / 150 ms).
|
|
1963
|
+
- The frame while filling, p50 / p90: 3.82 / 5.56 ms on the 4090 and 66.0 / 80.5 ms on the iGPU (158.9 ms at worst).
|
|
1964
|
+
At rest nothing of the extraction runs: 3.28 ms on the 4090 and 63.0 ms on the iGPU, S7e's 62.8 ms.
|
|
1965
|
+
- The probes and their rays, after the fill, both GPUs alike:
|
|
1966
|
+
|
|
1967
|
+
| view | dead probes, S7e → S8 | back-face rays, S7e → S8 | lit luma, S7e → S8 |
|
|
1968
|
+
|---|---|---|---|
|
|
1969
|
+
| manifest | 23% → 3.2% | 12% → 4.7–5.0% | 89 → 91 |
|
|
1970
|
+
| aerial | 18% → 2.0% | 9% → 4.5–5.1% | 74 → 76 |
|
|
1971
|
+
| ground | 49% → 6.8–7.0% | 17% → 5.5–6.6% | 21 → 79–83 |
|
|
1972
|
+
|
|
1973
|
+
Sponza on the mesh caches has 1.6–4% dead and 2% back-face rays. The arcade at ground level, which S7e lit at 19 to
|
|
1974
|
+
37, is lit at 79 to 83 from the sunlit floor. From the air the bricks read as Zorah, the greenhouse's dome and the
|
|
1975
|
+
tower whole, where S7e's read inside out (`s8f_igpu_aerial_bricks.png` against `s7e2_aerial_bricks.png`).
|
|
1976
|
+
- Sponza's census is unchanged (4090): 7,171 bricks, every cascade's counts, 104 geometries baked, resident at frame
|
|
1977
|
+
355.
|
|
1978
|
+
- Open, measured, cause not established: from the air, cells of the ground under the greenhouse's ring are still
|
|
1979
|
+
black (the ray passed through); the column at the manifest camera still has its dark blotches on a lattice; its
|
|
1980
|
+
flutes, narrower than a voxel, read as back faces in the brick view. A proxy's id is not given back
|
|
1981
|
+
(`GPUGeometryManager` hands ids out once), so the fill takes Zorah's ids from 1,012 to 2,024.
|
|
1982
|
+
- Tier 2 (`vgeo_sdf_extract.spec.js`, over a built torus in the runtime's tables and in a meshlet pool): a job's list is
|
|
1983
|
+
the CPU oracle's cut at its error with no cull, and its counts are the listed clusters'; written out, its mesh is
|
|
1984
|
+
every triangle of those clusters as the meshlet pool's own reader reads them back; a room too small takes no cluster
|
|
1985
|
+
past it; a missing page is a stand-in, wanted through the slot; past the list's capacity the count runs on. Tier 3:
|
|
1986
|
+
the caches' adopt, sources, requests and waits (`GPUGeometrySDFManager.spec.js`, a bake done on the device standing
|
|
1987
|
+
in its log); the proxies' host half end to end (`VGeoSDFProxies.spec.js`, the device's readbacks standing in); a vgeo
|
|
1988
|
+
row's hold. 43 of 43 mutants were killed.
|
|
1989
|
+
|
|
1795
1990
|
### 7.9 Tracing
|
|
1796
1991
|
|
|
1797
1992
|
`brick_clipmap_trace(origin, direction, t_min, t_max)` is a pure chunk over seam readers, so the
|
|
@@ -1907,6 +2102,10 @@ left the estimator to the measurement. It is Quílez's two-sided estimate.
|
|
|
1907
2102
|
- A linear map in place of `¼(1 + x)²(2 − x)` survives within the oracle's tolerance. On the CPU its
|
|
1908
2103
|
mean error is 35% more, and its worst no more.
|
|
1909
2104
|
- Never stopping in the umbra is equivalent: the least only falls.
|
|
2105
|
+
- **Replaced 2026-10-30** by a shadow ray toward a point of the sun's disk, and the chunk deleted (§8.7, as
|
|
2106
|
+
built): the estimate reads the field's clearance about one ray, so two occluders from either side of
|
|
2107
|
+
the cone read as one. Under a slit narrower than the disk it saw 0.675 of the sun where the disk is
|
|
2108
|
+
0.327 open.
|
|
1910
2109
|
|
|
1911
2110
|
**As built (S3f)**: `ProbeClipmapDebugView.BrickClipmap` traces each pixel's camera ray through the
|
|
1912
2111
|
world's bricks (`debug/graph_probe_clipmap_debug_brick_clipmap.js`).
|
|
@@ -1975,7 +2174,7 @@ Measured on Sponza with its caches resident, at the defaults: 6 cascades of 32³
|
|
|
1975
2174
|
| States | storage buffer, one `u32` a slot: NEW / ALIVE / DEAD and the update count. Its own array, so the pick reads 4 B and not a record (§9.1) |
|
|
1976
2175
|
| Records | storage buffer, 64 B a slot: cascade and world cell (3 × 21-bit signed, two words), wanted frame, traced time (s), relocation offset (3 × f16), back-face fraction, SH2 as `METRO_L1_IRRADIANCE_STRUCT` (32 B) |
|
|
1977
2176
|
| Radiance | 2-D `rgba16float` atlas; a slot's tile is `(n_r + 2)²`: the octahedral map plus a one-texel skirt. One level only (§9.3). Slots are placed by `index_to_grid2d`, as in `lpv_atlas_patch_texel_coord` |
|
|
1978
|
-
| Depth | 2-D `rg16float` atlas, tile `(n_d + 2)²`. Moments of `min(t,
|
|
2177
|
+
| Depth | 2-D `rg16float` atlas, tile `(n_d + 2)²`. Moments of `min(t, (1 + 0.45)·√3·s_c)/s_c`: normalised by spacing, so every cascade has the same precision, and clamped at the farthest a cage tests a relocated probe (D9), past which a hit cannot occlude anything the cage shades |
|
|
1979
2178
|
| Wanted bits | `N_p³·C_p` bits, cleared each frame |
|
|
1980
2179
|
| Lists | active (double-buffered), wanted, trace (≤ U, §8.5) |
|
|
1981
2180
|
| Free list | the extracted slot stack (§4.1) |
|
|
@@ -1991,6 +2190,19 @@ hardware taps.
|
|
|
1991
2190
|
The price: a texture cannot be read and written in one dispatch without `texture-formats-tier2`, so
|
|
1992
2191
|
the update is two passes (§8.8). That is LPV's own answer for its depth atlas.
|
|
1993
2192
|
|
|
2193
|
+
**As built (2026-10-30), the record packed**, 48 B a slot, `PROBE_RECORD_STRUCT.pack()`:
|
|
2194
|
+
- the cell, `vec3<i32>`;
|
|
2195
|
+
- one flags word: the back-face share as unorm16 in the low half; ten bits of flags above it, the screen's
|
|
2196
|
+
mark the first; the cascade in the top six bits;
|
|
2197
|
+
- the wanted and the traced frame, `u32`, of the frames the clipmap has recorded: exact where f32 seconds
|
|
2198
|
+
lose their step as the clock runs on, and an update happens in a frame anyway. The blend's Δt is the
|
|
2199
|
+
frames since the update and τ is `response_frames` (default 30, half a second at 60 fps), so the probes
|
|
2200
|
+
take no clock from the caller. Eviction's age buckets count frames: 0, 1, 2–3, 4–7, … Retire evicts by the bucket a probe was
|
|
2201
|
+
counted in, its age a frame before. With integer ages a probe one frame younger than an asked bucket
|
|
2202
|
+
aged into it between schedule and retire, and was taken uncounted;
|
|
2203
|
+
- the SH, `vec3<u32>` of half floats: Y's xy, Y's zw, CoCg, held to ±65,504. Half the Metro L1 struct's
|
|
2204
|
+
32 B.
|
|
2205
|
+
|
|
1994
2206
|
### 8.3 Lifetime
|
|
1995
2207
|
|
|
1996
2208
|
Per frame, in this order:
|
|
@@ -1998,7 +2210,9 @@ Per frame, in this order:
|
|
|
1998
2210
|
1. **retire** (thread per active probe). A probe is freed when either holds:
|
|
1999
2211
|
- its world cell is no longer in its cascade's window (`clipmap_contains` against the current
|
|
2000
2212
|
corner);
|
|
2001
|
-
- it was last wanted more than `retention_time` ago (default 1 s).
|
|
2213
|
+
- it was last wanted more than `retention_time` ago (default 1 s). Since 2026-10-30 instead: the last
|
|
2214
|
+
frame's schedule evicted it, by the pressure on the slots, least recently wanted first (as built,
|
|
2215
|
+
below the measured churn).
|
|
2002
2216
|
|
|
2003
2217
|
Freeing it means:
|
|
2004
2218
|
- its slot is pushed;
|
|
@@ -2112,6 +2326,108 @@ and admit's command, through the launch the last frame's rays had, `PROBE_DISPAT
|
|
|
2112
2326
|
- Tier 3, `GPUProbeClipmap.spec.js`: the pass's place, its command, and the last frame's U and R.
|
|
2113
2327
|
- Mutants: 24 of 24 killed.
|
|
2114
2328
|
|
|
2329
|
+
**Measured (2026-10-30), the churn in a still scene.** Sponza at 1280×720, the clock at 60 Hz, the camera
|
|
2330
|
+
and the light still, 3,600 frames settled. Then 600 frames, each read back: every slot's state and record,
|
|
2331
|
+
the wanted list, the trace list and the period. A slot's probe is followed by its point and its update
|
|
2332
|
+
count. Modelled as §8.5 has it, due agreed with the trace list on every probe past its first update, those
|
|
2333
|
+
a full list turned away aside: 2.7 million checks on each GPU, none apart.
|
|
2334
|
+
- **Hits' probes come and go, about four a frame.**
|
|
2335
|
+
- The RTX 4090 admitted 3.9 a frame and retired 3.8–3.9 (three runs); the AMD iGPU 4.1 and 4.0.
|
|
2336
|
+
- The screen admitted none.
|
|
2337
|
+
- Every probe retired had gone unwanted for 1.017 s, the retention time and a frame.
|
|
2338
|
+
- **They are the probes up to three updates old**, §8.6's 11%. Of the 4090's 501 (11.4% of the alive),
|
|
2339
|
+
487 were admitted within the 600 frames; of the iGPU's 532 (12.2%), 509. The few others are hits'
|
|
2340
|
+
probes wanted on about one frame in twenty, whose want seldom falls on their turn.
|
|
2341
|
+
- **The same points come back.** Of the 896 points admitted in the 10 s, 650 were admitted again, three
|
|
2342
|
+
times at the median.
|
|
2343
|
+
- **Each is wanted by one far ray.** A twin of the hits' wants kept, for each point, its shortest wanting
|
|
2344
|
+
hit and the rays that wanted it.
|
|
2345
|
+
- A point admitted was wanted by one ray (p50 and p90), 10.8 m from its probe at the median (p10
|
|
2346
|
+
5.3 m). Of those rays 60% were a uniform update's.
|
|
2347
|
+
- A point kept was wanted by 6 rays a frame at the median, at 3.2 m.
|
|
2348
|
+
- **Why far.** A radiance texel's ray stands for the texel's cone, t·√(4π)/n_r across where it lands:
|
|
2349
|
+
0.22·t at n_r = 16, which past t = 4.5 m is wider than a cage of the finest cascade. That cascade holds
|
|
2350
|
+
95% of the admissions. Such a cage is landed in by chance, once every few seconds (a probe both
|
|
2351
|
+
admitted and retired within the 600 frames lived 1.3 s at the median), and the retention frees its
|
|
2352
|
+
probes in between. The next landing finds no probe, so its ray takes no bounce from them.
|
|
2353
|
+
- **What it costs**: the admitted probes' first updates and their next three cast 2,560–2,870 of the
|
|
2354
|
+
frame's 52,600–53,700 rays on both GPUs, 4.8–5.5%, and are 1.3% of the updates. On the AMD iGPU the
|
|
2355
|
+
passes a ray (trace, shade, light, the hits' wants) take 5.4 ms at the median and those a probe 2.0 ms,
|
|
2356
|
+
so by those shares about 0.3 ms a frame; not timed against a build without it.
|
|
2357
|
+
- **What moves it**, on the 4090, in admissions a frame against 3.9:
|
|
2358
|
+
- a retention of 3 s: 0.7. Those up to three updates old fall to 5.9%, and 3.5% more probes are live;
|
|
2359
|
+
- every request held at R, 256: 3.0. Those are the rays before the variable requests (§8.6), 2.3 times
|
|
2360
|
+
as many, so the requests are not the cause;
|
|
2361
|
+
- half the ray budget, T from 6 to 10: 5.1, and 15.9% up to three updates old. A hit renews a probe
|
|
2362
|
+
at most once a hitter's period, while the retention is in seconds, so the churn grows with the live
|
|
2363
|
+
count;
|
|
2364
|
+
- the hits' wants and the light pass's pick at the finest cascade whose spacing covers the texel's
|
|
2365
|
+
footprint, a twin of both: 2.4, with 11% fewer probes live and 4% fewer rays. At twice the footprint:
|
|
2366
|
+
1.9, with 30% fewer probes and 9% fewer rays. Both gather their admissions where the cascade changes
|
|
2367
|
+
(t of 4.4 m and 2.2 m at the median). The cascade then follows each ray's distance, not the point, so
|
|
2368
|
+
a point's wants split between two cascades.
|
|
2369
|
+
- The frame's mean luma: 72.3 as built (72.4 on the iGPU), 71.5 at 3 s, 71.9 and 71.8 by footprint.
|
|
2370
|
+
As built read 71.0–72.0 between runs (§8.6).
|
|
2371
|
+
- **Hits' probes are retraced less often than every T frames.**
|
|
2372
|
+
- The 3,077 hits' probes alive through the 600 frames were wanted on 67% of frames at the median, 11%
|
|
2373
|
+
at p10.
|
|
2374
|
+
- They were updated 6.8 times a second at the median and 0.8 at p10; the screen's, at least 6.6.
|
|
2375
|
+
- At p90, 1.2 s had passed since their last update; at p99, 6 s.
|
|
2376
|
+
- A hit wants a probe in the frame after its hitter's update, so its point's bit is set on fewer of
|
|
2377
|
+
the probe's turns than a pixel's is.
|
|
2378
|
+
- The passes did what this section and §8.5 said. The churn was the retention's, on points that hits land
|
|
2379
|
+
in once every few seconds; a fixed retention was the departure from Enshrouded, whose cache evicts by
|
|
2380
|
+
pressure (Alex, 2026-10-30), built below.
|
|
2381
|
+
|
|
2382
|
+
**As built (2026-10-30), eviction by pressure, not by a retention time.** Enshrouded's cache stays full and
|
|
2383
|
+
evicts every frame, more the fuller it is, so a probe keeps its history as long as there is room.
|
|
2384
|
+
- **Retire** frees a probe whose point left its window, or one the last frame's schedule asked for.
|
|
2385
|
+
`retention_time` is gone, from the settings and the passes: a hard break.
|
|
2386
|
+
- **How many**: as many slots as the free ones fall short of the frame's wanted points and 5% of the
|
|
2387
|
+
capacity (`PROBE_FREE_SLACK`, Alex's). The wanted points are the most a frame can admit, a camera cut's
|
|
2388
|
+
included, and the slack is room for a burst of them the frame it comes.
|
|
2389
|
+
- **Which, the least recently wanted first**: retire counts each probe it keeps and could evict into one of
|
|
2390
|
+
32 binades of the seconds since it was last wanted (`probe_clipmap_age_bucket`, the float's exponent, 1 s
|
|
2391
|
+
beginning the middle one), `PROBE_COUNTER_AGES` on. Schedule walks them from the oldest, taking a bucket
|
|
2392
|
+
whole while the shortfall takes it and the rest of the shortfall from the next, and writes that to
|
|
2393
|
+
`probe_eviction`; retire's threads take that bucket's while a counter, `PROBE_COUNTER_EVICTED`, is under
|
|
2394
|
+
it. Past the youngest bucket anything was counted in, schedule asks for nothing. No sort, no pass of its
|
|
2395
|
+
own, nothing read back.
|
|
2396
|
+
- **In use, never evicted**: a probe a want waits on, NEW or wanted since its last update (§8.5), and one
|
|
2397
|
+
wanted the last frame (retire takes that frame's time). Where those fill the slots, an admission starves
|
|
2398
|
+
and the capacity grows, as before.
|
|
2399
|
+
- **Device, Sponza, the 4090**, the still view, 600 frames, against the base before §8.5's as-built note:
|
|
2400
|
+
- admitted 0.007 probes a frame against 3.9, retired none against 3.8;
|
|
2401
|
+
- the young probes' rays 19 a frame against 2,600; the frame's 54,800 against 53,800 (with §8.5's change
|
|
2402
|
+
alone 58,500, the churn's probes taking their second updates);
|
|
2403
|
+
- 5,400 probes live against 4,500;
|
|
2404
|
+
- the probes up to three updates old still 11.3% of the alive: probes kept that hits want once in a few
|
|
2405
|
+
seconds, updated when they are.
|
|
2406
|
+
- **A walk**, the courtyard's length and back over 1,200 frames, the 4090:
|
|
2407
|
+
- at the default capacity, 16,384, at least 6,849 slots stayed free: nothing was evicted;
|
|
2408
|
+
- at N = 16, 4,096 slots, 488 frames evicted, no admission starved and the capacity never grew. The
|
|
2409
|
+
frame's wanted points were half the slots there, so a frame short of room freed every probe out of use
|
|
2410
|
+
older than about two frames.
|
|
2411
|
+
- **The sun doubled**, frames to 50%, 90% and 95% of the change in the mean band 0 of the probes wanted:
|
|
2412
|
+
37–42, 98–106 and 117–133, against the base's 38–41, 85–99 and 117–135, two runs each.
|
|
2413
|
+
- **The AMD iGPU**, the still view, separate processes and not paired, against the base:
|
|
2414
|
+
- admitted 0.007 a frame against 4.0, the young probes' rays 24 against 2,840, the frame's 54,700
|
|
2415
|
+
against 53,700;
|
|
2416
|
+
- a want waited 6 frames for its update at the median, the screen's 13 and the hits' 20 at p99;
|
|
2417
|
+
- the trace list held U, 512, nearly every frame against 477 a frame: the period counts every probe a
|
|
2418
|
+
want waits on (§8.5), so the update fills its budget, where the hits' probes it skipped left it
|
|
2419
|
+
short. The probe passes summed 7.91 ms at the median against 7.55, of which trace, shade and light
|
|
2420
|
+
5.25 against 5.02;
|
|
2421
|
+
- retire 26.6 µs against 21.3, admit 13.6 against 8.3, schedule 3.5 against 0.8, the due passes 9.4
|
|
2422
|
+
against 15.4.
|
|
2423
|
+
- Tier 2, `probe_clipmap_lifetime.spec.js`: a probe nobody wants keeps its slot while there is room; past
|
|
2424
|
+
the room the probes wanted longest ago go first, as many as the room lacks, a bucket in part where it
|
|
2425
|
+
lacks part of one; one that leaves its window as its bucket is partly evicted takes none of it; a NEW
|
|
2426
|
+
probe and one wanted the frame before are never evicted, and an admission the room still lacks starves;
|
|
2427
|
+
a shortfall the evictable probes cannot cover asks for those counted, none younger. The first three
|
|
2428
|
+
were red against the retention time; the rest guard the protections. Mutants: 13 of 13 killed.
|
|
2429
|
+
- Tier 3, `GPUProbeClipmap.spec.js`: retire's times, schedule's slack, the eviction zero as made.
|
|
2430
|
+
|
|
2115
2431
|
### 8.4 Relocation and classification
|
|
2116
2432
|
|
|
2117
2433
|
**Relocation**, at the start of every update:
|
|
@@ -2184,6 +2500,31 @@ assumes.
|
|
|
2184
2500
|
- Mutants: 19 of 21 killed. The two survivors are equivalent on the fields the specs build: the early
|
|
2185
2501
|
exit where the field is flat, and the gradient's flatness threshold on fields exactly flat.
|
|
2186
2502
|
|
|
2503
|
+
**As built (2026-10-30), due waits on a want, not on this frame's bit.** The hits' wants (§8.3) set a
|
|
2504
|
+
point's bit the frame after their probe's update, so a probe only hits keep had its bit on few of its
|
|
2505
|
+
turns: measured on Sponza (§8.3), the hits' probes were updated 0.8 times a second at p10 against the
|
|
2506
|
+
screen's 6.6 at the least, and 6 s had passed since the last update at p99, 21 s at most.
|
|
2507
|
+
- **Waiting**: a probe is NEW, or a pixel or a hit wanted it since its last update, its wanted time past
|
|
2508
|
+
its traced time. The update comes after admit in its frame, so a want in the frame of an update is
|
|
2509
|
+
served by it. Due lists the NEW in its first pass, and a waiting ALIVE or DEAD probe on its turn.
|
|
2510
|
+
- **The period counts the waiting**, `T = max(1, ⌈P_w / U⌉)` with P_w the probes waiting, not the
|
|
2511
|
+
points wanted this frame. Retire counts the waiting probes it keeps and admit those the frame's wants
|
|
2512
|
+
add, one atomic a subgroup each (`subgroup_atomic_add_u32`), into `PROBE_COUNTER_WAITING`, the
|
|
2513
|
+
counters' fourth word; schedule takes the period from it and empties it.
|
|
2514
|
+
- **Device, Sponza, the 4090**, the same still view and 600 frames, against master:
|
|
2515
|
+
- the hits' probes alive through them, 2,984: 4.7 updates a second at p10 and 8.2 at the median,
|
|
2516
|
+
against 0.8 and 6.8; 0.42 s since the last update at p99 and 1 s at most, against 7.2 s and 21 s;
|
|
2517
|
+
- the screen's: 8.3 a second at the median against 10.8, the period 7 frames against 5–6;
|
|
2518
|
+
- the frame's rays 58,500 against 53,800, the young probes' 5,300 of them against 2,600: the churn's
|
|
2519
|
+
probes now take their next updates when a hit wants them again (§8.3);
|
|
2520
|
+
- the trace list held more than U on 454 of the 600 frames, 524 a frame on average: due lists every
|
|
2521
|
+
NEW probe besides a period's share of the waiting, so its count sits at U, and the last ones past it
|
|
2522
|
+
wait their next turn.
|
|
2523
|
+
- Tier 2: `probe_clipmap_schedule.spec.js` (the period over the waiting; a probe wanted between its turns
|
|
2524
|
+
due on its turn; one whose wants its update served, the same frame's included, not due; a NEW one at
|
|
2525
|
+
once), `probe_clipmap_lifetime.spec.js` (retire and admit counting each waiting probe once, through the
|
|
2526
|
+
period). Each was red against the shaders before. Mutants: 8 of 8 killed.
|
|
2527
|
+
|
|
2187
2528
|
### 8.6 Rays
|
|
2188
2529
|
|
|
2189
2530
|
**trace**: one thread per ray, `trace_count × rays_per_probe` rays.
|
|
@@ -2203,13 +2544,28 @@ assumes.
|
|
|
2203
2544
|
visibilities}.
|
|
2204
2545
|
|
|
2205
2546
|
**As built (S4c, 2026-10-28)**, `probe/shader_probe_clipmap_trace.js`:
|
|
2206
|
-
- **The ray record** is
|
|
2207
|
-
normal,
|
|
2547
|
+
- **The ray record** is 24 B (`PROBE_RAY_STRUCT`; 28 B with the light in three half floats until
|
|
2548
|
+
2026-10-30, now one RGB9E5 word): `t`; the direction and the hit's normal,
|
|
2208
2549
|
each octahedral in two unorm16; the `meshes` row of the hit's surface, from `brick_clipmap_object`;
|
|
2209
|
-
the flags (kind, back face, the hit's cascade); and the light
|
|
2210
|
-
direct irradiance there and shade writes the radiance over it. The hit's position is the
|
|
2211
|
-
|
|
2550
|
+
the flags (kind, back face, the hit's cascade); and the light. Trace writes the
|
|
2551
|
+
direct irradiance there and shade writes the radiance over it. The hit's position is the ray's
|
|
2552
|
+
start plus `t` along the direction, which shading recomputes; the start was the probe's origin until
|
|
2553
|
+
the ray origins below. (S4c's record held the two suns'
|
|
2212
2554
|
visibility in place of the light; S4d moved E_dir into the trace, below.)
|
|
2555
|
+
- **The light as RGB9E5 (2026-10-30, Alex: the half floats were a poor encoding).** The engine's
|
|
2556
|
+
`rgbe9995` chunks: one word, the three channels sharing an exponent. The encoder clamps to its
|
|
2557
|
+
largest value, 65,408, which replaced the half-float holds in shade and the light pass and still
|
|
2558
|
+
keeps the half-float atlases finite. A channel far below the brightest loses precision: beside
|
|
2559
|
+
65,408, 2 and 3 round to 0.
|
|
2560
|
+
- Measured paired in one process. The five passes that read or write the record (trace, shade,
|
|
2561
|
+
light, accumulate, the hits' wants) ran as twins built from master's WGSL, swapped in at
|
|
2562
|
+
`setPipeline`, the two encodings each twice. There were 120 frames an arm, the clock at 60 Hz:
|
|
2563
|
+
- 4090: Sponza +14 µs a frame across the five, all in the trace (+3%; its repeats differ by 3–10
|
|
2564
|
+
µs); Emerald Square's avenue the same.
|
|
2565
|
+
- iGPU: Sponza −124 µs (−2%; shade and light −4% each); Emerald the same.
|
|
2566
|
+
- The image, settled 1,200 frames, master and the tree in separate processes: on Sponza they differ
|
|
2567
|
+
by 1.6–1.7 codes a channel on average, and two runs of the tree by 1.8. The probes' noise is not
|
|
2568
|
+
the same from process to process. On Emerald's avenue they differ by 0.01.
|
|
2213
2569
|
- **The texel mapping** is `chunk_probe_clipmap_ray_texel`, both ways (a ray's texel, a texel's rays),
|
|
2214
2570
|
so accumulation finds each texel's rays without a scatter. The bit-reversed subset repeats every
|
|
2215
2571
|
n_r²/R updates, which keeps its product in range.
|
|
@@ -2227,7 +2583,7 @@ assumes.
|
|
|
2227
2583
|
zero there. The light keeps its place among the shadowed, so which lights are shadowed does not turn
|
|
2228
2584
|
on the surface's orientation.
|
|
2229
2585
|
- **E_dir is the trace's** since S4d: Σ colour·max(0, n·l)·visibility over every directional light,
|
|
2230
|
-
the first two that cast shadows by their soft shadow, at a front-face hit. The trace reads the lights
|
|
2586
|
+
the first two that cast shadows by their soft shadow (a shadow ray since 2026-10-30), at a front-face hit. The trace reads the lights
|
|
2231
2587
|
already, and shade would have needed the light table as an eleventh storage buffer (below).
|
|
2232
2588
|
- **On the device**, Sponza at 1280×720 with the caches baked, 131,072 rays a frame (U = 512 × R = 256):
|
|
2233
2589
|
96% hit, 3.4% miss, 0.2% unresolved, 2.5% back faces; the mean hit at 2.6 m; 13% of the sun seen at
|
|
@@ -2257,6 +2613,236 @@ assumes.
|
|
|
2257
2613
|
starts at the relocated probe; the rays of a probe past U are not traced.
|
|
2258
2614
|
- Mutants: 18 of 18 killed.
|
|
2259
2615
|
|
|
2616
|
+
**As built (ray guiding, 2026-10-29): Enshrouded's importance-sampled rays** (D11 reversed in its rays'
|
|
2617
|
+
half). Alex found the noise of one jittered ray a texel visible and asked for Enshrouded's scheme,
|
|
2618
|
+
rotating corner and all.
|
|
2619
|
+
- **Which updates are guided**: every `PROBE_UNIFORM_PERIOD`-th update (5, the talk's) is uniform, as
|
|
2620
|
+
above. So are a probe's first n_r²/R updates, one when R ≥ n_r², which together reach every texel:
|
|
2621
|
+
Enshrouded gives a new probe its most rays for the same reason, and the guide would weigh a texel no
|
|
2622
|
+
ray has reached as dark. Every other update is guided, except a DEAD probe's, which are all uniform:
|
|
2623
|
+
readers weigh it at nothing, and only uniform rays reclassify it. Guided, its rare updates kept it dead
|
|
2624
|
+
for 40 periods, and Sponza's dead probes rose by a quarter.
|
|
2625
|
+
- **The guide** (`probe/shader_probe_clipmap_guide.js`), three passes a thread a row of each listed
|
|
2626
|
+
probe's radiance map before the trace, per-invocation as projection is:
|
|
2627
|
+
- rows: the luminance of the row's history texels;
|
|
2628
|
+
- weights: each texel's share of the probe's luminance in fixed point (`PROBE_GUIDE_SCALE`), held to
|
|
2629
|
+
`(PROBE_TEXEL_RAYS − 2 − R/n_r²)/R`;
|
|
2630
|
+
- starts: the first ray of each walk position, `⌈R·Σ_{q<p}(w_q + e)/W − φ⌉`, with `e` the even share
|
|
2631
|
+
of what the weights leave of the scale, and `φ` a hash of slot and update count. The walk is the
|
|
2632
|
+
map's rows from the corner `updates mod 4` names.
|
|
2633
|
+
- Integer weights make the walk's sums exact, so trace and accumulation read the same rays for each
|
|
2634
|
+
texel. The hold leaves every texel under 64 rays, the gather budget, and what it takes off is spread
|
|
2635
|
+
over every texel. A dark history spreads its rays evenly, which classification can trust.
|
|
2636
|
+
- Kept: `probe_clipmap_guide_words` = 2·n_r² + 2·n_r + 2 words a listed probe (1.1 MiB at U = 512).
|
|
2637
|
+
- **The trace** bisects the first rays for its walk position, at most log2(n_r²) steps. A texel's later
|
|
2638
|
+
rays rotate its STBN jitter by R2 (`probe_clipmap_guided_ray_jitter`). The light pass, shade and the
|
|
2639
|
+
hits' wants are unchanged: any per-ray noise serves the light pass's pick.
|
|
2640
|
+
- **What it does to the rays**: on Sponza the back faces fall from 3–4% of the rays to 2.5–3%. The
|
|
2641
|
+
hits shift toward lit surfaces, whose sun shadow rays are the trace's cost.
|
|
2642
|
+
- **Measured, Sponza 1280×720.** Every comparison is the tree at period 1 (every update uniform, the
|
|
2643
|
+
tree without guiding) against period 5, in separate runs on each GPU; values are given as AMD iGPU /
|
|
2644
|
+
RTX 4090.
|
|
2645
|
+
- **Screen, S4g's stability method.** TAA and AO were off; SSR on, so that the deferred diffuse and its
|
|
2646
|
+
twins draw. The runs took 240 frames after the bake and 240 to settle, with the probes' clock fixed
|
|
2647
|
+
at 60 Hz. Each number is the mean relative luma change of a pixel against the previous frame at the
|
|
2648
|
+
same surface point, and **none moves**:
|
|
2649
|
+
|
|
2650
|
+
| p50, period 1 → 5 | still | orbit |
|
|
2651
|
+
|---|---|---|
|
|
2652
|
+
| eight corners blended (the probes alone) | 0.55 → 0.56% / 0.54 → 0.55% | 3.83 → 4.01% / 3.95 → 4.09% |
|
|
2653
|
+
| the shipped pick | 14.1 → 14.4% / 14.3 → 14.2% | 19.0 → 19.7% / 18.7 → 20.1% |
|
|
2654
|
+
| the frame, SSR off (fused pass) | 10.7 → 10.3% / 10.3 → 10.2% | |
|
|
2655
|
+
|
|
2656
|
+
Per frame, the pick's draw of two of eight is most of the diffuse's change (14% against 0.55% with
|
|
2657
|
+
all eight blended), as S6's estimator session also found.
|
|
2658
|
+
- **Probes**, the estimator session's probe-level method (`msme.js`, `probe_reads`):
|
|
2659
|
+
|
|
2660
|
+
| period 1 → 5 | AMD iGPU | RTX 4090 |
|
|
2661
|
+
|---|---|---|
|
|
2662
|
+
| texels, an update apart, p50 | 1.05 → 1.37% | 0.92–1.03 → 1.27–1.29% |
|
|
2663
|
+
| SH band 0, an update apart, p50 | 0.27 → 0.40% | 0.26–0.28 → 0.36–0.38% |
|
|
2664
|
+
| atlas texels, 24 frames apart, Σ abs(ΔL) / Σ L | 8.7 → 7.5% | 8.7 → 7.2% |
|
|
2665
|
+
| a doubled sun, 50% / 90% taken | | 44 / 160 → 34 / 117 frames |
|
|
2666
|
+
| its halving, 50% / 90% | | 55 / 183 → 44 / 140 frames |
|
|
2667
|
+
|
|
2668
|
+
Guiding makes the probes follow a light change about a quarter sooner. Their noise between one
|
|
2669
|
+
update and the next rises a third, while over four updates it falls by a sixth. The uniform update
|
|
2670
|
+
every fifth, which gives a texel one ray where the guided ones gave it several, is the likely source
|
|
2671
|
+
of the update-to-update jitter; this is not isolated.
|
|
2672
|
+
- **Before the multi-scale mean** (master at d1ca833d6, the exponential blend) the probes' noise fell
|
|
2673
|
+
without the rise: the atlas 24.9 → 18.0% / 24.6 → 17.9%, the eight corners' p90 1.30 → 0.92% /
|
|
2674
|
+
1.24 → 0.87%. The screen did not move then either. The estimator took most of what guiding
|
|
2675
|
+
took off.
|
|
2676
|
+
- Dead probes fall by 9–17%. An alive probe is re-tested only on its uniform updates, a dead one on
|
|
2677
|
+
every update, so a probe near the quarter spends more of its time alive. A probe inside a solid is
|
|
2678
|
+
dead from its first update, which is uniform.
|
|
2679
|
+
- **Paired cost**: one page, periods 1 and 5 alternating in blocks of 16 frames (ABBA, the first 4 of
|
|
2680
|
+
each dropped), the camera still, p50 in each arm, on master with the multi-scale mean:
|
|
2681
|
+
|
|
2682
|
+
| | AMD iGPU | RTX 4090 |
|
|
2683
|
+
|---|---:|---:|
|
|
2684
|
+
| trace | 6.79 → 7.58 ms | 335 → 470 µs |
|
|
2685
|
+
| accumulate | 429 → 676 µs | 11 → 21 µs |
|
|
2686
|
+
| the guide's three passes (both arms) | 496 µs | 28 µs |
|
|
2687
|
+
| shade and light | +76 µs | ±1 µs |
|
|
2688
|
+
|
|
2689
|
+
In all, **+1.6 ms a frame on the iGPU and +0.17 ms on the 4090**. The trace's rise is the sun's soft
|
|
2690
|
+
shadow at the lit hits that guided rays favour.
|
|
2691
|
+
- Tier 2: `probe_clipmap_guide.spec.js` (a dark history's even spread, shares by luminance, the hold,
|
|
2692
|
+
the corner, U), `probe_clipmap_trace.spec.js` (each walk position's run in its texel, a texel's rays
|
|
2693
|
+
apart, a uniform update and a DEAD probe ignoring the guide), `probe_clipmap_accumulate.spec.js`
|
|
2694
|
+
(a texel's run averaged, the period by rarity on uniform updates for radiance and depth, a depth texel
|
|
2695
|
+
as a mean of means), `probe_clipmap_project.spec.js` (a guided update keeps the kind; a dark guide's
|
|
2696
|
+
update and a DEAD probe's classify).
|
|
2697
|
+
- Mutants: 16 of 16 killed, among them the trace, accumulation and projection as they were.
|
|
2698
|
+
|
|
2699
|
+
**The adversarial review (2026-10-29) found two defects in the first cut, both fixed and pinned:**
|
|
2700
|
+
- **With R < n_r², a new probe took 5× the updates to see its sphere.** Its first update reached R texels
|
|
2701
|
+
and cleared the rest, and the guide weighed the cleared ones dark. At n_r = 16 and R = 64, the whole
|
|
2702
|
+
sphere was seen at update 15, not 3.
|
|
2703
|
+
- Now a probe's first n_r²/R updates are uniform (above).
|
|
2704
|
+
- Measured on the 4090, Sponza orbiting at R = 64, two runs each: the alive probes' mean irradiance
|
|
2705
|
+
(E/π) was 0.0137 every update uniform, 0.0079 before the fix, and 0.0121 after it.
|
|
2706
|
+
- The 12% that remains is not traced. The likely source is the guide itself at R < n_r²: a texel
|
|
2707
|
+
whose sample read dark takes its next ray from the uniform updates, which reach it once in
|
|
2708
|
+
5·n_r²/R updates. At the default R = n_r², still, the guided irradiance matches the uniform within
|
|
2709
|
+
0.5%.
|
|
2710
|
+
- **A guided depth texel took only the radiance texels that took rays**, so where it mixes lit and dark
|
|
2711
|
+
directions (a back face, a shadowed wall) its depth leaned to the lit ones'.
|
|
2712
|
+
- Now a guided update takes a depth texel only when every radiance texel inside it took rays. Its
|
|
2713
|
+
period counts the least reached of them.
|
|
2714
|
+
- Measured on Sponza at the defaults, still: the median depth moment was 1.590 every update uniform,
|
|
2715
|
+
1.599 before the fix, and 1.584 after it. The means agree within 0.3%, so on Sponza the bias was
|
|
2716
|
+
small.
|
|
2717
|
+
|
|
2718
|
+
**As built (ray origins, 2026-10-29): each ray starts at a point of its own**, uniform in a ball of
|
|
2719
|
+
`PROBE_RAY_ORIGIN_JITTER` = 1/16 of the spacing around the probe, as Brick4's bake jitters its probes
|
|
2720
|
+
(Alex's request).
|
|
2721
|
+
- The point is `chunk_probe_clipmap_ray_origin`'s, a hash of the probe's slot and the ray's packed
|
|
2722
|
+
direction. Shade, light and want hits put the hit back together from the same start, so the record
|
|
2723
|
+
carries nothing new. The trace's jitter moves the direction every update, and the start with it.
|
|
2724
|
+
- A sixteenth is half a brick voxel at the default sizes, well inside the relocation clearance (¼), so
|
|
2725
|
+
wherever relocation reached the clearance every ray starts outside the solid.
|
|
2726
|
+
- Measured on Sponza, the camera still, two runs a radius on the 4090 and one on the iGPU (harness:
|
|
2727
|
+
session 481e40f0's scratchpad, `device/oj.js`):
|
|
2728
|
+
|
|
2729
|
+
| radius, of the spacing | texel change an update apart, p50 | dead probes | depth σ, p50 |
|
|
2730
|
+
|---|---:|---:|---:|
|
|
2731
|
+
| 0 | 1.11–1.19% | 3.2% | 0.046 |
|
|
2732
|
+
| 1/16 | 1.00–1.01% | 3.5% | 0.051 |
|
|
2733
|
+
| 1/8 | 1.17–1.26% | 4.5% | 0.073 |
|
|
2734
|
+
| 1/4 | 1.40–1.67% | 5.9% | 0.122 |
|
|
2735
|
+
|
|
2736
|
+
- Alex compared the converged eight-corner diffuse at ¼ against 0. The jittered image is markedly
|
|
2737
|
+
smoother, and sharp-contrast artifacts are gone. The whole-image |Δ|/L read under its own noise floor,
|
|
2738
|
+
so the change is local, and that metric misses it. He chose 1/16.
|
|
2739
|
+
- Brick4's own radius, 5e-5 m, changes nothing here: an SDF march has no float ties for it to break.
|
|
2740
|
+
- Rebuilding hits from the probe instead of the ray's start is measurably wrong from ⅛ up: E/π +3.5–4%
|
|
2741
|
+
and 10–13% more probes, from hits wanting the wrong cells. Matching costs at most one timer step a
|
|
2742
|
+
pass on either GPU.
|
|
2743
|
+
- Cost, measured at ¼ (paired by blocks of frames in one page): the trace +3.5% on the 4090 (442 →
|
|
2744
|
+
458 µs), nothing on the iGPU.
|
|
2745
|
+
- Tier 2: `probe_clipmap_trace.spec.js` (the starts within the ball and spread over it; a wall's `t`
|
|
2746
|
+
from each ray's own start), `probe_clipmap_light.spec.js` (a probe of one ray placed by its start; the
|
|
2747
|
+
pick's hits from it), `probe_clipmap_lifetime.spec.js` (a hit across a lattice plane from its probe
|
|
2748
|
+
wants the cage its start puts it in).
|
|
2749
|
+
- Mutants: 5 of 6 killed. The survivor is shade rebuilding its hit from the probe: its spec has no card
|
|
2750
|
+
resident, so no hit position reaches its output.
|
|
2751
|
+
|
|
2752
|
+
**As built (a probe's own count of rays, 2026-10-30): the frame's rays are laid out by listed probe**, the
|
|
2753
|
+
first stage of `PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md`. Every probe still casts R; what
|
|
2754
|
+
changes is that nothing assumes it.
|
|
2755
|
+
- **The layout.** After the trace command, the ray counts pass writes each listed probe's count of rays
|
|
2756
|
+
(`probe_ray_counts`), and `graph_prefix_scan_csdldf`'s shaders scan them into `probe_ray_sums`. The k-th
|
|
2757
|
+
probe's rays are `[sums[k − 1], sums[k])`, and the last sum is the frame's count, which a one-thread pass
|
|
2758
|
+
turns into the rays' command.
|
|
2759
|
+
- **Each pass finds its probe.** Trace bisects the sums (`lower_bound_branchless`, as the mesh expansion
|
|
2760
|
+
does) and writes the probe's place in the list into the ray's flags, above the cascade, which is now
|
|
2761
|
+
three bits. Shade and the hits' wants read it there. The light pass, accumulation and the guide also
|
|
2762
|
+
read the probe's own count from the sums.
|
|
2763
|
+
- **Past the last ray.** The threads of the launch's last workgroup past the frame's last ray write a
|
|
2764
|
+
record of no ray (kind 3, `PROBE_RAY_NONE`). Every pass after trace stops there, so none needs the
|
|
2765
|
+
frame's count. The rays buffer is rounded up to whole workgroups for them.
|
|
2766
|
+
- **R still means the uniform update.** A probe's first n_r²/R updates are uniform, and the rate's
|
|
2767
|
+
uniform term (§8.8) keeps the settings' R. A guided update's walk, its gather and its expected rays
|
|
2768
|
+
take the probe's own count.
|
|
2769
|
+
- **Storage buffers:** shade stays at 9, the light pass and accumulation go to 9, and trace goes to 8.
|
|
2770
|
+
- **Device, Sponza, 4090,** clock at 60 Hz, 900 frames settled, two runs of each build alternating:
|
|
2771
|
+
master and the layout agree within master's own run-to-run spread (frame luma 71.8/71.8 against
|
|
2772
|
+
71.0/72.0, alive probes 4,393–4,463 against 4,400–4,403, SH E/π mean 0.0131–0.0134 against 0.0133).
|
|
2773
|
+
The passes' times are unchanged. The ray counts cost 4 µs and the rays' command 2 µs.
|
|
2774
|
+
- Tier 2: `probe_clipmap_trace.spec.js` (a probe casting its own count after the probes before it, each
|
|
2775
|
+
ray naming it; the records of no ray), `probe_clipmap_accumulate.spec.js` (two rays a texel averaged
|
|
2776
|
+
where R says one), `probe_clipmap_guide.spec.js` (the walk sharing out the update's own count),
|
|
2777
|
+
`probe_clipmap_schedule.spec.js` (the ray counts and the rays' command). Each was red against the
|
|
2778
|
+
shaders before it.
|
|
2779
|
+
|
|
2780
|
+
**As built (variable rays, 2026-10-30): each probe asks for its next update's rays**, the second stage of
|
|
2781
|
+
`PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md`: Enshrouded's requests, on the period kept (F1).
|
|
2782
|
+
- **Counts and requests.** The ray counts pass writes two parts for each listed probe. The first is what
|
|
2783
|
+
the budget does not scale: a uniform update's R (a NEW probe's among them), and a DEAD probe's fewest,
|
|
2784
|
+
n_r²/4 (`PROBE_RAYS_PER_TEXEL_MIN`). The second is what it does scale: a guided update's request, at
|
|
2785
|
+
least the fewest. Both are scanned, and the ray layout pass gives the requests what the budget leaves
|
|
2786
|
+
past the counts: all of it when they fit, in proportion when they do not (F2). The k-th probe's rays
|
|
2787
|
+
end at its counts' sum plus its requests' sum so scaled, rounded down, so the frame never passes the
|
|
2788
|
+
budget and needs no third scan.
|
|
2789
|
+
- **The request** is the slot's own record, `probe_requests` (16 bytes a slot, not packed into `screen`
|
|
2790
|
+
as F6 had it: the statistics below need three words). Projection writes it after every update:
|
|
2791
|
+
- after a probe's first update, the most, 4·n_r² (`PROBE_RAYS_PER_TEXEL_MAX`), Enshrouded's new probe;
|
|
2792
|
+
- when its light has moved, the most again;
|
|
2793
|
+
- otherwise half the last request, down to the fewest.
|
|
2794
|
+
- **Moved.** Accumulation leaves each blended texel's fall from its mean, luminance, in its own
|
|
2795
|
+
deviation and held to ±3 (`PROBE_CHANGE_SIGNIFICANCE`), in a fourth word of the maps. Projection sums
|
|
2796
|
+
them over the root of their count: the update's change score. Moved is the score past 3 of the probe's
|
|
2797
|
+
own running deviation (at least 1) from its own running mean, both kept over the estimator's variance
|
|
2798
|
+
window, 2τ, a running mean while young. A score past their reach counts at it, so a change in the light
|
|
2799
|
+
does not widen the probe's spread at once, while a lasting shift of its own is taken in within a few
|
|
2800
|
+
windows. A first update clears the slot's statistics, which were the last owner's.
|
|
2801
|
+
- **DEAD probes** are due every period at the fewest rays (F4), not every eighth at R;
|
|
2802
|
+
`PROBE_DEAD_PERIODS` is gone.
|
|
2803
|
+
- **Not built: F3**, weighing a sample by its rays in the estimator. It waits on a measurement.
|
|
2804
|
+
- **Device, Sponza, 4090,** clock at 60 Hz, the camera and the light still:
|
|
2805
|
+
- false alarms, the probes past 64 updates asking for the most, 3,600 frames settled: 4.2% when the
|
|
2806
|
+
change score summed the raw falls against zero, 3.2% with each texel's fall bounded, 1.1–1.3% as
|
|
2807
|
+
built, against the normal tail's 0.27%;
|
|
2808
|
+
- the probes up to three updates old are 11% of the alive ones (477 of 4,387) and 69–76% of them ask
|
|
2809
|
+
for the most, as new probes do. That churn is the lifetime's admissions and retirements, measured in
|
|
2810
|
+
§8.3, not the request's;
|
|
2811
|
+
- the frame's rays, the last frame's layout of each run: 48,704–65,856, against master's U·R;
|
|
2812
|
+
- trace p50 404–600 µs against master's 726–790 µs, separate processes and not paired;
|
|
2813
|
+
- the noise between reads six frames apart: texels 1.25–1.49% against master's 1.23%, band 0
|
|
2814
|
+
0.38–0.46% against 0.35%;
|
|
2815
|
+
- the sun doubled, the alive probes' mean band 0 reaches 50%, 90% and 95% of the change in 40, 156
|
|
2816
|
+
and 246 frames, against master's 42, 177 and 257;
|
|
2817
|
+
- the post-load dark spell is unchanged: measured with the first build of the request, the probes
|
|
2818
|
+
were resident at 17.6 s and converged at about 32–35 s, master's timeline.
|
|
2819
|
+
- **Storage buffers:** the ray counts pass binds 5, the ray layout 3, projection's probe pass 6.
|
|
2820
|
+
- Tier 2: `probe_clipmap_project.spec.js` (the most after a first update; moved past three deviations
|
|
2821
|
+
and the halving to the fewest; a steady offset sips once held; a jump asks the most while it lasts; a
|
|
2822
|
+
lasting shift is taken in; a new probe forgets the slot's last owner), `probe_clipmap_accumulate.spec.js`
|
|
2823
|
+
(the fall in its own deviation, bounded), `probe_clipmap_schedule.spec.js` (the counts by kind, the
|
|
2824
|
+
budget's proportion, DEAD every period). Each was red against the shaders before it.
|
|
2825
|
+
|
|
2826
|
+
**As built (the probes' order, 2026-10-30): the trace list is sorted by its probes' places, and the rays
|
|
2827
|
+
are not sorted**, the end of `PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md` §2, where every
|
|
2828
|
+
measurement is.
|
|
2829
|
+
- **The order pass** (`shader_probe_clipmap_order.js`) runs after the update's command: a workgroup a run
|
|
2830
|
+
of 1,024 listed probes (`PROBE_ORDER_RUN`, through `PROBE_DISPATCH_ORDER` over the live count) sorts
|
|
2831
|
+
its run in place by the cascade above the Morton code of the probe's lattice point in the cascade's
|
|
2832
|
+
window, with the bitonic workgroup sort (`chunk_sort_bitonic_workgroup`). Every pass after it reads a
|
|
2833
|
+
probe's place from the sorted list, so neighbouring probes' rays run in neighbouring waves. Past one
|
|
2834
|
+
run, the list is ordered within each run.
|
|
2835
|
+
- **Why not the rays.** A wave already holds one probe's rays walking its texels in rows. Every ray sort
|
|
2836
|
+
measured costs more than it buys: global radix keys, local sorts by direction (8×8 or 4×4 bins, chunks
|
|
2837
|
+
of 2,048 to 512), within each probe, and position bits above the bin. On the iGPU at 131k rays the
|
|
2838
|
+
local sorts lose 4–22%, a hash-keyed control 29–57% of the trace. The 4090 is level throughout.
|
|
2839
|
+
- **Device:** iGPU 1–4% off the rays' stretch at 131k rays in most batches, the 4090 level; the pass
|
|
2840
|
+
costs 11.8 µs on the 4090. Storage buffers: 3.
|
|
2841
|
+
- Tier 1: the validity spec. Tier 2: `probe_clipmap_schedule.spec.js` (a workgroup a run, up to U). Tier
|
|
2842
|
+
3: `GPUProbeClipmap.spec.js` (the pass after the update's command, through its command). The sort
|
|
2843
|
+
itself runs workgroup barriers the emulator cannot, so the device checks it: each run ordered and a
|
|
2844
|
+
permutation of the list.
|
|
2845
|
+
|
|
2260
2846
|
### 8.7 Hit shading
|
|
2261
2847
|
|
|
2262
2848
|
**shade**: one thread per ray.
|
|
@@ -2276,9 +2862,11 @@ The three irradiance terms of a HIT:
|
|
|
2276
2862
|
- **E_loc** comes from the world light clusters at the hit, generalised to this mode's brick levels
|
|
2277
2863
|
so that every hit position has a cell.
|
|
2278
2864
|
- At most 32 lights, `IRRADIANCE_CLIPMAP_LIGHT_BUDGET`'s value. Past that, lights are dropped.
|
|
2279
|
-
- Each light goes through `irradiance_clipmap_get_{point,spot}_light_info_by_index
|
|
2280
|
-
|
|
2281
|
-
|
|
2865
|
+
- Each light goes through `irradiance_clipmap_get_{point,spot}_light_info_by_index`. One of them is
|
|
2866
|
+
picked by WRS on its luminance there and divided by the chance of the pick; its shadow is a ray
|
|
2867
|
+
traced in the bricks (as built 2026-10-30, below). Until then each light was summed, its shadow from
|
|
2868
|
+
`volumetrics_{point,spot}_light_shadow`, and a light whose shadow map was cleared lit hits
|
|
2869
|
+
unshadowed.
|
|
2282
2870
|
- **E_ind** is this mode's own diffuse sampling (§9.2) at the hit, with last frame's SH: the next
|
|
2283
2871
|
bounce.
|
|
2284
2872
|
- Its pick takes the ray's own STBN sample as noise; the probe blend averages it.
|
|
@@ -2294,7 +2882,7 @@ The three irradiance terms of a HIT:
|
|
|
2294
2882
|
|
|
2295
2883
|
**As built (S4d, 2026-10-28)**, `probe/shader_probe_clipmap_shade.js`, a thread a ray after trace
|
|
2296
2884
|
through the same command:
|
|
2297
|
-
- **Its radiance goes over the ray's light
|
|
2885
|
+
- **Its radiance goes over the ray's light** (RGB9E5 since 2026-10-30): no buffer of its own. With one, shade
|
|
2298
2886
|
would bind eleven storage buffers, past the ten a stage has (§12): the rays, the origins, the trace
|
|
2299
2887
|
list, the scene database, the texture means, the SDF headers and indirection, the card sets, the card
|
|
2300
2888
|
rows, and the radiance. In place it binds nine.
|
|
@@ -2425,16 +3013,67 @@ shade, through shade's own launch.
|
|
|
2425
3013
|
cascade count makes redundant; the cull box's far corner, which the host spec has no light to cull
|
|
2426
3014
|
against, and which the device check shows holding its light.
|
|
2427
3015
|
|
|
3016
|
+
**As built (2026-10-30), shadow rays at hits.** Alex: a shadow map is made for the main view, so where rays
|
|
3017
|
+
reach it is wrong in most places, and the sun's cone soft shadow is made in another domain than the rays',
|
|
3018
|
+
so it is bound to leak light and shadow. Both are traced now, through the bricks the hit's own ray met:
|
|
3019
|
+
- **The sun**, in the trace pass: for each of the first `PROBE_SHADOWED_SUNS` lights that cast shadows, one
|
|
3020
|
+
ray from a voxel off the hit along its normal toward a point of the light's disk, drawn as the path
|
|
3021
|
+
tracer draws it (`cone_sample_direction`), from `pcg3d` of the ray's slot and packed direction and the
|
|
3022
|
+
light's place among the shadowed. The sun is seen whole if the ray misses, not at all if it hits or
|
|
3023
|
+
spends its steps. The probe's blend of its rays integrates the penumbra.
|
|
3024
|
+
- **The local lights**, in the light pass: one of the cell's point and spot lights, picked by weighted
|
|
3025
|
+
reservoir sampling over their luminance at the hit, with one number stretched over the candidates as
|
|
3026
|
+
`sample_discrete_wrs` does. The pick lights the hit by its irradiance over the chance of its pick, w/W,
|
|
3027
|
+
so the probe's blend averages the picks to the cell's sum. Its shadow is one ray from a voxel off the
|
|
3028
|
+
surface toward a point of the light's sphere (`sphere_sample_volume`, as the path tracer), stopping short
|
|
3029
|
+
of it by the light's near clip or a voxel, whichever is more: the field's half-voxel expand at that end,
|
|
3030
|
+
as the lift is at the other. A light that casts no shadow traces none. The pick and the point are
|
|
3031
|
+
`pcg4d` of the ray's slot and direction, apart from the origin's draw.
|
|
3032
|
+
- What a ray cannot answer is no light, not all of it: a march that spends its steps shadows.
|
|
3033
|
+
- The light pass binds the brick textures in place of the shadow atlas, the same 8 storage buffers. The
|
|
3034
|
+
GI no longer reads the shadow maps, and the renderer no longer hands it their atlas.
|
|
3035
|
+
- `brick_clipmap_soft_shadow` is deleted with its specs; nothing else called it.
|
|
3036
|
+
- **Cost**, paired on Sponza at 1280×720 (the sun and the playground's 8 shadowed point lights along the
|
|
3037
|
+
nave; HEAD's passes and these in blocks of 4 frames, ABBA, each block's first frame dropped), p50:
|
|
3038
|
+
|
|
3039
|
+
| | 4090 | iGPU |
|
|
3040
|
+
|---|---:|---:|
|
|
3041
|
+
| trace (the sun) | 364.5 against 415.7 µs | 9.01 against 9.25 ms |
|
|
3042
|
+
| light (local lights) | 43.0 against 28.7 µs | 3.38 against 2.43 ms |
|
|
3043
|
+
| shade, unchanged, the clock's check | ×1.000 | ×0.995 |
|
|
3044
|
+
|
|
3045
|
+
A hit ray stops at its first occluder, where the cone estimate marched on into the umbra, so the sun
|
|
3046
|
+
costs less. One traced ray in the light pass costs more than reading every light's map. On the iGPU the
|
|
3047
|
+
two together are 0.72 ms a frame more.
|
|
3048
|
+
- Tier 2:
|
|
3049
|
+
- `probe_clipmap_trace.spec.js`: under a slit 0.5 m wide in a roof, a sun of 0.25 rad, 128 probes'
|
|
3050
|
+
rays each see the whole sun or none, and on average 0.258 of it against the 0.327 of the disk the
|
|
3051
|
+
slit lets through, computed on the CPU with the walls standing out half a voxel (1,024 rays read
|
|
3052
|
+
0.295: the bricks round the slit's corners). The cone estimate read 0.675 there.
|
|
3053
|
+
- `probe_clipmap_light.spec.js`, over bricks built from boxes: a slab between a point light and the
|
|
3054
|
+
hit shadows it, the floor under the hit and a slab past the light do not, and a light that casts no
|
|
3055
|
+
shadow is lit through the slab; a slab shadows a spot; a light 3 cm under a ceiling lights 16 hits
|
|
3056
|
+
from under it to 4 m out; over 256 rays at one hit, each ray is one of four lights over the chance of
|
|
3057
|
+
its pick, each light picked within four sigma of its share, and the mean is their sum.
|
|
3058
|
+
- Red against HEAD's passes, its light pass given a cleared atlas, as a light never drawn has: the
|
|
3059
|
+
slab between the point light and the hit lit it, so did the slab over the spot, and every ray was the
|
|
3060
|
+
four lights' sum.
|
|
3061
|
+
- Mutants: 11 of 11 killed: no division by the pick's chance; the first light always; the number not
|
|
3062
|
+
stretched; the ray to the light's point, or short of it by its near clip only; from the surface, not
|
|
3063
|
+
a voxel off it; a light's no-shadow flag ignored; a hit counted as light, for the sun and the local
|
|
3064
|
+
light; the sun's disk centre alone; one draw for every probe.
|
|
3065
|
+
|
|
2428
3066
|
### 8.8 Update
|
|
2429
3067
|
|
|
2430
3068
|
**accumulate**: one workgroup per traced probe.
|
|
2431
3069
|
- **Blend weight**: α = 1 on the first update. After that, α = max(1/(n+1), 1 − e^(−Δt/τ)), with Δt
|
|
2432
3070
|
the seconds since the probe's previous update and τ = `response_time` (default 0.5 s).
|
|
2433
3071
|
- DDGI's 0.97 a frame at 60 fps is τ = 0.55 s.
|
|
3072
|
+
- As built (2026-10-30): Δt is the frames since the update and τ = `response_frames` (default 30), §8.2.
|
|
2434
3073
|
- **Radiance**: each texel hit this update takes the mean of its rays, blended with its history.
|
|
2435
3074
|
The history is read from the atlas through a sampled binding.
|
|
2436
|
-
- **Depth**: each depth texel takes the moments of the rays landing in it (a miss counts as
|
|
2437
|
-
blended the same way.
|
|
3075
|
+
- **Depth**: each depth texel takes the moments of the rays landing in it (a miss counts as the
|
|
3076
|
+
depth limit, D9), blended the same way.
|
|
2438
3077
|
- Texels without a ray keep their history; so does an unresolved ray's texel.
|
|
2439
3078
|
- **The transient holds the probe's whole maps**, new texels and kept ones: n_r² × 8 B + n_d² × 4 B
|
|
2440
3079
|
per traced probe, 1.2 MiB for U = 512. `publish` cannot read the atlas it writes, and it needs
|
|
@@ -2466,10 +3105,61 @@ a per-invocation function instead. Each is tier 2 whole, and no thread's loop ru
|
|
|
2466
3105
|
between a texel's rays (n_r²/R below a ray a texel). Below a ray a texel, a texel's first ray is
|
|
2467
3106
|
then taken whole whenever it comes, which the probe's count would get wrong. Depth blends by the
|
|
2468
3107
|
probe's α.
|
|
3108
|
+
- **The radiance is a multi-scale mean since 2026-10-29** (D11; `shader/chunk/filter/chunk_multiscale_mean.js`):
|
|
3109
|
+
the texel's sample feeds Enshrouded's estimator. Its state past the mean is a slot's n_r² ×
|
|
3110
|
+
16 B in `probe_estimators` (short mean, deviation, blend reduction, inconsistency, as halves
|
|
3111
|
+
rounded stochastically): 4 KiB a slot at n_r = 16.
|
|
3112
|
+
- The short mean blends by the α above, its variance by the same with 2τ. The mean takes at least
|
|
3113
|
+
1/(a+1) until the count's cap, so a young texel is its running mean; the estimator's blend
|
|
3114
|
+
reduction starts at nothing and would otherwise hold a new probe on its first ray.
|
|
3115
|
+
- **The blend reduction follows by the reference's 0.1 a sample** (fixed 2026-10-29), not by the short
|
|
3116
|
+
blend as first built. The catch-up takes the last sample's reduction. Once samples are a response
|
|
3117
|
+
time apart, the short blend is 1 − 1/e, the reduction tracked each sample, and the long mean fell back
|
|
3118
|
+
fastest from the bright ones. Measured on the emulator over samples of mean 1 and deviation 1, at
|
|
3119
|
+
short blends of 0.55, 0.63, 0.78 and 0.9:
|
|
3120
|
+
- the texel settled 7, 18, 28 and 39% low;
|
|
3121
|
+
- at 0.1 a sample, 2, 3, 7 and 15%, with the same step response.
|
|
3122
|
+
|
|
3123
|
+
A texel the guide passes over has a short blend of 0.63 at 60 Hz with a period of 6 frames. At
|
|
3124
|
+
15 fps, every texel's short blend is past 0.55. At the short blend of 0.18 that the table below ran
|
|
3125
|
+
at, the fix moves nothing (error 13.9 → 14.0%, update to update 0.69 → 0.70%), and that table was
|
|
3126
|
+
not measured again.
|
|
3127
|
+
- The firefly clip, `0.1 + short + 8·deviation`, waits until 1/(a+1) is no longer the variance's
|
|
3128
|
+
blend. Its 0.1 is the reference's, in radiance units; on Sponza under a sun of 7.2 a texel's
|
|
3129
|
+
luma is under 0.26 at p99, so it clips only far outliers there.
|
|
3130
|
+
- Measured on Sponza, the camera still, 4090 (`scratchpad/device/msme.js`, 2026-10-29):
|
|
3131
|
+
|
|
3132
|
+
| τ | texel change an update | band 0 change | 50% / 90% of a doubled sun | 50% / 90% of its halving |
|
|
3133
|
+
|---|---|---|---|---|
|
|
3134
|
+
| 0.5 s, before | 10.8% | 1.54% | 29 / 105 frames | 29 / 102 |
|
|
3135
|
+
| 0.5 s | 1.0% | 0.27% | 55 / 389 | 54 / 212 |
|
|
3136
|
+
| 0.25 s, before | 20.5% | 3.0% | 17 / 79 | 17 / 77 |
|
|
3137
|
+
| 0.25 s | 5.0% | 1.24% | 23 / 234 | 24 / 99 |
|
|
3138
|
+
| 0.125 s | 26% | 4.9% | 19 / >390 | 6 / 37 |
|
|
3139
|
+
|
|
3140
|
+
The screen's diffuse barely moved: a frame against the last at the same point, 4.72% → 4.66% of
|
|
3141
|
+
all eight corners and 16.1% → 15.8% of the pick, still; 9.4% → 9.0% orbiting. The SH averages
|
|
3142
|
+
a probe's texels, so what changes a still frame's diffuse is not the probes' radiance.
|
|
3143
|
+
- Cost, the old pass beside the new in the same frames, which goes first alternating: 4090 15–16
|
|
3144
|
+
against 13 µs; AMD iGPU 860–1,264 against 337–523 µs, 2.0–2.5×. A twin with the estimator's
|
|
3145
|
+
arithmetic replaced by a plain blend costs what the shipped pass does: the time is the state's
|
|
3146
|
+
load and store, not the estimator. Run order moves the iGPU's figures by 2×, so the split
|
|
3147
|
+
between loading and packing the state is not settled.
|
|
2469
3148
|
- **A new probe's history is its slot's last owner's**: on its first update a texel no ray reached is
|
|
2470
|
-
cleared, radiance to nothing and depth to a miss's moments (
|
|
2471
|
-
- Depth samples, in spacings: a miss
|
|
3149
|
+
cleared, radiance to nothing and depth to a miss's moments (L, L²), L the depth limit (D9).
|
|
3150
|
+
- Depth samples, in spacings: a miss L, a back face 0.2·t, a hit t, each held to L. A depth texel
|
|
2472
3151
|
gathers R/n_d² rays, so the GI refuses R > 64·n_d².
|
|
3152
|
+
- **Since ray guiding (2026-10-29, §8.6)**: on a guided update a texel's rays are its run in the guide.
|
|
3153
|
+
Its mean needs no weight for the guide's choice, since its rays spread evenly over it.
|
|
3154
|
+
- p is one over the share of updates that reach the texel, averaged over the uniform period:
|
|
3155
|
+
(min(R/n_r², 1) + (P − 1)·min(R·pdf, 1))/P. A texel the guide passes over then blends what the
|
|
3156
|
+
uniform updates bring as fully as its rarity asks, and the response stays τ.
|
|
3157
|
+
- A guided depth texel is the mean of its radiance texels' own means, taken only when every one of
|
|
3158
|
+
them took rays. A plain mean would lean to the brighter ones' depths, and so would a mean over
|
|
3159
|
+
only the lit ones. Its period counts a uniform update as reaching it, and a guided one as often
|
|
3160
|
+
as its least reached radiance texel.
|
|
3161
|
+
- Classification takes only uniform rays: a guided update keeps the kind and the back-face share,
|
|
3162
|
+
since guided rays shun back faces (§8.4).
|
|
2473
3163
|
- **the projection** (`probe/shader_probe_clipmap_project.js`): a thread a row of a probe's radiance
|
|
2474
3164
|
map sums its texels as `convert_to_irradiance_sh(L, ω)·dω/(2π)` and its rays' counts; then a thread a
|
|
2475
3165
|
probe sums its rows into the record's SH, its back-face share, its time, and its state: dead past a
|
|
@@ -2959,7 +3649,7 @@ the shadow knobs' pattern.
|
|
|
2959
3649
|
- **The mesh cache fields** (`mesh_sdf_voxel_size`, `mesh_sdf_max_bricks`): a change rebakes every
|
|
2960
3650
|
cached geometry.
|
|
2961
3651
|
- **Budgets and times** (`brick_build_budget`, `mesh_sdf_bake_budget`, `card_capture_budget`,
|
|
2962
|
-
`rays_per_probe`, `ray_budget`, `
|
|
3652
|
+
`rays_per_probe`, `ray_budget`, `response_frames`): uniforms and transient sizes, so
|
|
2963
3653
|
they take effect the next frame without a rebuild.
|
|
2964
3654
|
|
|
2965
3655
|
| Setting | Default | Range | Changes |
|
|
@@ -2979,8 +3669,9 @@ the shadow knobs' pattern.
|
|
|
2979
3669
|
| `depth_resolution` | 8 | 4–32, power of two | octahedral depth texels per side |
|
|
2980
3670
|
| `rays_per_probe` | 256 | a power of two in [n_r²/4, 4·n_r²] | rays per probe update; below n_r² an update covers a spread subset of texels, above it each texel averages several |
|
|
2981
3671
|
| `ray_budget` | 131,072 | ≥ `rays_per_probe` | rays per frame (a time knob) |
|
|
2982
|
-
| `
|
|
2983
|
-
|
|
3672
|
+
| `response_frames` | 30 | > 0 | the radiance's short mean's time constant τ in frames (§8.8), and the depth's blend's |
|
|
3673
|
+
|
|
3674
|
+
`retention_time` (1 s) was removed on 2026-10-30: probes are evicted by the pressure on the slots (§8.3).
|
|
2984
3675
|
|
|
2985
3676
|
The defaults:
|
|
2986
3677
|
- They sit between Enshrouded's low and mid tiers (100k and 250k rays; radiance 8 and 16).
|
|
@@ -2993,7 +3684,8 @@ The defaults:
|
|
|
2993
3684
|
- the size-class step of two bricks;
|
|
2994
3685
|
- the hit threshold and minimum step, ½ voxel;
|
|
2995
3686
|
- 64 steps a ray;
|
|
2996
|
-
- 64
|
|
3687
|
+
- the mesh provider's 64 rows a batch and 512 nodes a level of a walk, past which the work goes on in more
|
|
3688
|
+
batches and rounds (S9);
|
|
2997
3689
|
- 32 cards per geometry, and 4 alpha continuations per capture texel;
|
|
2998
3690
|
- the 2³-texel object cell;
|
|
2999
3691
|
- 2 probes per pick;
|
|
@@ -3074,6 +3766,8 @@ specs. A real compile on a device catches what the validator misses.
|
|
|
3074
3766
|
- The soft-shadow penumbra matches the analytic disk light at a sphere's edge.
|
|
3075
3767
|
- As built (S3e): against the disk past the surface the tracer hits, half a voxel out, within 0.1;
|
|
3076
3768
|
half at the edge within 0.1.
|
|
3769
|
+
- Since 2026-10-30 the hits' shadows are rays: under a slit narrower than the sun's disk, each ray
|
|
3770
|
+
sees all of the sun or none, and their mean the part of the disk the slit lets through (§8.7).
|
|
3077
3771
|
- `mesh_card_surface`: a hit on a card's surface returns that texel's diffuse albedo and emissive,
|
|
3078
3772
|
and a hit hidden from every card returns the material mean.
|
|
3079
3773
|
- **Probes**:
|
|
@@ -3088,7 +3782,7 @@ specs. A real compile on a device catches what the validator misses.
|
|
|
3088
3782
|
- The cascade blend is continuous across the skirt.
|
|
3089
3783
|
- `resolve_irradiance_sh_to_specular` at g₁ = 2/3 equals `resolve_irradiance_sh_to_diffuse`, and
|
|
3090
3784
|
returns L under constant radiance L at every α.
|
|
3091
|
-
- **Extracted pieces** (probe weight, pick, hash,
|
|
3785
|
+
- **Extracted pieces** (probe weight, pick, hash, free-list pop/push): their
|
|
3092
3786
|
old callers' specs stay green. LPV's and Brick4's sampling are unchanged to the bit.
|
|
3093
3787
|
|
|
3094
3788
|
**Tier 3, SoftwareGPUDevice**: host orchestration, black-box on what reaches the device.
|
|
@@ -3269,7 +3963,8 @@ Each stage lands suite-green and is measured before the next.
|
|
|
3269
3963
|
| S5 | next bounce (E_ind); local lights via generalised world clusters; the forward, water, SSR-hit and irradiance-clipmap alternatives | furnace (multi-bounce), Cornell vs path tracer |
|
|
3270
3964
|
| S6 | specular: radiance ⊕ GGX-SH2, parallax, fused pass, SSR fallback, forward and water specular | parallax A/B, reflections in a closed room |
|
|
3271
3965
|
| S7 | the virtual geometry provider: box cut, streaming invalidation | Zorah lit by its own geometry; timings |
|
|
3272
|
-
| S8 | measured follow-ups only (§17) |
|
|
3966
|
+
| S8 | virtual geometry's caches per geometry (Alex, 2026-10-29): a proxy cut from each, baked by the mesh path, adopted; the per-cell provider deleted; then measured follow-ups only (§17) | Zorah's holes, back faces and dead probes against S7e's and Sponza's; the frames and cost of the bake; the frame at rest |
|
|
3967
|
+
| S9 | the mesh provider without its cap (Alex, 2026-10-29): walks that hand on what they cannot hold, batches of 64 spread over workgroups by count, an atomic merge, the resolve on the cells the walks listed; contract 7, retry | Zorah's holes against S8's; the provider's cost on both GPUs, paired with S8's |
|
|
3273
3968
|
|
|
3274
3969
|
## 14. Decisions taken here
|
|
3275
3970
|
|
|
@@ -3295,8 +3990,8 @@ Each says what would reverse it.
|
|
|
3295
3990
|
- A hit does no material work.
|
|
3296
3991
|
- *Reverse:* none of mine; Alex chose it over cards that hold uvs.
|
|
3297
3992
|
- **D6. No triangle is read per frame.** Meshes are read at bakes (the SDF, per geometry) and at
|
|
3298
|
-
captures (the cards, per pair and per material change).
|
|
3299
|
-
|
|
3993
|
+
captures (the cards, per pair and per material change). A virtual geometry's cut is read once, for
|
|
3994
|
+
its proxy's bake (S8), never per frame. *Reverse:* none; it is the review's rule.
|
|
3300
3995
|
- **D7. Hits are diffuse only: `L = emissive + ρ_d·E/π`.**
|
|
3301
3996
|
- That is what Lumen's payload holds, and Lumen's surface cache lights diffusely too.
|
|
3302
3997
|
- (b) first had the full BRDF's directional albedo at the hit's NoV (`tSplitSum`), which needed
|
|
@@ -3306,22 +4001,44 @@ Each says what would reverse it.
|
|
|
3306
4001
|
of the diffuse one. That needs the same storage and no hit-side change.
|
|
3307
4002
|
- **D8. Radiance `rgba16float`, two-pass update.** Hardware bilinear for every consumer.
|
|
3308
4003
|
*Reverse:* if the stall bias measures visible, f32 history in a buffer.
|
|
3309
|
-
- **D9. Depth moments normalised by spacing, clamped at
|
|
4004
|
+
- **D9. Depth moments normalised by spacing, clamped at (1 + 0.45)·√3·s.**
|
|
4005
|
+
- Chebyshev's bound asks only how many samples lie past the point, which a clamp past every point the
|
|
4006
|
+
cage tests leaves as it was, and a larger clamp only loosens it. The clamp is one constant, so a
|
|
4007
|
+
reader de-normalises with the spacing alone.
|
|
4008
|
+
- **Corrected 2026-10-30**: it was √3·s, the cage diagonal, which holds only for a probe on its
|
|
4009
|
+
lattice point. Relocation (§8.4) moves a probe up to 0.45 of a spacing on each axis, so a biased
|
|
4010
|
+
point can be (1 + 0.45)·√3 ≈ 2.51 spacings from a corner probe. A direction that sees nothing read as
|
|
4011
|
+
occluded past √3: visibility ≤ 0.41 at 1.8 spacings, 0.043 at 2.0. Over a cell, a corner moved 0.45
|
|
4012
|
+
on every axis away from it lost up to 0.70% of the cage's trilinear weight; 0.25 on each, 0.013%.
|
|
4013
|
+
- RTXGI clamps at `1.5·|spacing|` (`ProbeBlendingCS.hlsl`), the same reach over DDGI 2021's
|
|
4014
|
+
half-spacing relocation bound; neither DDGI paper's text gives a value. RTXGI's own relocation is a
|
|
4015
|
+
sphere of 0.45, so its clamp has headroom.
|
|
3310
4016
|
- **D10. SH2 projected from blended radiance, not blended separately.**
|
|
3311
|
-
- **D11.
|
|
4017
|
+
- **D11. Radiance by a multi-scale mean whose short mean follows a response time in seconds; depth
|
|
4018
|
+
by an exponential blend; jittered stratified rays.**
|
|
3312
4019
|
- Lumen overwrites without jitter, but its screen probes filter downstream. Here consumers read
|
|
3313
4020
|
the probes directly.
|
|
3314
4021
|
- DDGI 2021's per-texel change boost is left out. A texel here sees one ray an update, so a change
|
|
3315
4022
|
test on it is a noise test.
|
|
3316
|
-
-
|
|
3317
|
-
estimator,
|
|
4023
|
+
- **Reversed for the radiance, 2026-10-29**: Alex judged the noise visible, and Enshrouded's
|
|
4024
|
+
estimator (RT Gems ch. 25) was built (§8.8, as built). Measured on Sponza at τ = 0.5 s: a texel
|
|
4025
|
+
moves 1.0% an update against 10.8%, the SH's band 0 0.27% against 1.54%; a doubled sun is half
|
|
4026
|
+
taken in 55 frames against 29, and 90% in 389 against 105.
|
|
4027
|
+
- A variant testing the means' gap against the short mean's own deviation, as SSR's accumulation
|
|
4028
|
+
does, was modelled on the CPU and followed a step on a noisy texel more slowly still; not built.
|
|
4029
|
+
- **The rays' half reversed too (2026-10-29).** Alex found the probes' noise visible, and asked for
|
|
4030
|
+
Enshrouded's importance-sampled rays, rotating corner and all (§8.6, as built). The estimator's short
|
|
4031
|
+
mean now takes a texel's period from its share of the updates that reach it (§8.8).
|
|
3318
4032
|
- **D12. Schedule: NEW first, then a slot-hashed phase with a period from the live count.**
|
|
3319
4033
|
- **D13. Pixels pick 2 of 8 by WRS after visibility-only weights** (the review). Brick4's sampler and
|
|
3320
4034
|
noise. *Reverse:* if the measured noise under TAA is visible, then a screen-space reservoir resolve,
|
|
3321
4035
|
argued from that measurement.
|
|
3322
|
-
- **D14. Sun shadows at hits by
|
|
3323
|
-
|
|
3324
|
-
|
|
4036
|
+
- **D14. Sun shadows at hits by shadow rays traced in the bricks**, toward a point of the disk.
|
|
4037
|
+
Shadow maps read lit off screen; the cone soft shadow S3e built leaked through gaps narrower than
|
|
4038
|
+
the disk, and was replaced on 2026-10-30 (§8.7).
|
|
4039
|
+
- **D15. Local lights from the world clusters, one picked by WRS over the chance of its pick, its
|
|
4040
|
+
shadow a ray traced in the bricks** (2026-10-30, §8.7). Until then every light of the cell, each
|
|
4041
|
+
through its shadow map, a light with a cleared map unshadowed at hits.
|
|
3325
4042
|
- **D16. Settings sub-object, rebuild on mismatch.**
|
|
3326
4043
|
- **D17. Capacity learned from reported failures, never shrinks.**
|
|
3327
4044
|
- **D18. The mode is named `ProbeClipmap`**, after what shading samples, as Brick4 and LPV are.
|
|
@@ -3450,7 +4167,6 @@ Each item is added only if a measurement asks for it, or, where marked, when Ale
|
|
|
3450
4167
|
emissive. The measured fix is the BRDF's hemispherical albedo captured in place of the diffuse
|
|
3451
4168
|
one.
|
|
3452
4169
|
- **Caches persisted across sessions**, an offline bake format.
|
|
3453
|
-
- Importance-sampled ray directions, if noise at the defaults measures visible.
|
|
3454
4170
|
- Ray and history reprojection between probes, if long-range propagation into enclosed spaces
|
|
3455
4171
|
measures short.
|
|
3456
4172
|
- Foliage and translucency in the field, Enshrouded's open problem too.
|