@woosh/meep-engine 3.60.4 → 3.60.6
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/CHANGELOG.md +2621 -0
- package/CONTRIBUTING.md +7 -0
- package/MIGRATION_2_TO_3.md +136 -0
- package/SECURITY.md +4 -0
- package/editor/EditorCameraNavigation.d.ts +2 -1
- package/editor/EditorCameraNavigation.d.ts.map +1 -1
- package/editor/EditorCameraNavigation.js +51 -57
- package/package.json +6 -2
- package/src/engine/EngineHarness.d.ts +3 -9
- package/src/engine/EngineHarness.d.ts.map +1 -1
- package/src/engine/EngineHarness.js +6 -8
- package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts +9 -6
- package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts.map +1 -1
- package/src/engine/graphics/camera/makeOrbitalCameraController.js +44 -71
- package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts +64 -0
- package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts.map +1 -0
- package/src/engine/graphics/camera/orbital/MomentumAxis.js +187 -0
- package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts +86 -0
- package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts.map +1 -0
- package/src/engine/graphics/camera/orbital/OrbitalCameraRig.js +319 -0
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts +34 -10
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts.map +1 -1
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +116 -30
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts +1 -7
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +1 -37
- package/src/engine/graphics/ecs/water/Water.d.ts +8 -0
- package/src/engine/graphics/ecs/water/Water.d.ts.map +1 -1
- package/src/engine/graphics/ecs/water/Water.js +8 -0
- package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
- package/src/engine/graphics3/WaterSystem.js +731 -734
- package/src/engine/graphics3/decal/DecalTileShader.d.ts +61 -0
- package/src/engine/graphics3/decal/DecalTileShader.d.ts.map +1 -0
- package/src/engine/graphics3/decal/DecalTileShader.js +138 -0
- package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts +10 -4
- package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts.map +1 -1
- package/src/engine/graphics3/decal/GPUDecalRenderer.js +149 -44
- package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts +25 -0
- package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts.map +1 -0
- package/src/engine/graphics3/decal/chunk_decal_tile_vertex.js +55 -0
- package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts +22 -0
- package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts.map +1 -0
- package/src/engine/graphics3/decal/graph_gather_decal_tiles.js +70 -0
- package/src/engine/graphics3/decal/shader_decal_composite.d.ts +7 -5
- package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
- package/src/engine/graphics3/decal/shader_decal_composite.js +48 -44
- package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts +12 -0
- package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts.map +1 -0
- package/src/engine/graphics3/decal/shader_decal_tile_copy.js +78 -0
- package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts +11 -0
- package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts.map +1 -0
- package/src/engine/graphics3/decal/shader_gather_decal_tiles.js +80 -0
- package/src/engine/input/devices/mouse/MouseEventButtons.d.ts +13 -0
- package/src/engine/input/devices/mouse/MouseEventButtons.d.ts.map +1 -0
- package/src/engine/input/devices/mouse/MouseEventButtons.js +23 -0
- package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts +4 -0
- package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts.map +1 -1
- package/src/engine/input/ecs/controllers/KeyboardCameraController.js +9 -45
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/playground/water_refraction/README.md +5 -6
- package/src/shade/playground/water_refraction/index.html +1 -1
- package/src/shade/renderer/Renderer.d.ts +11 -0
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +50 -6
- package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts +40 -80
- package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts.map +1 -1
- package/src/shade/renderer/camera/orbital/OrbitalCameraController.js +197 -147
- package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
- package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
- package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
- package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
- package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
- package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts +28 -45
- package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +111 -119
- package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts +22 -3
- package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts.map +1 -1
- package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.js +84 -3
- package/src/shade/renderer/geometry/virtual/proxy/vgeo_proxy_triangle_limit.d.ts +19 -0
- package/src/shade/renderer/geometry/virtual/proxy/vgeo_proxy_triangle_limit.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/proxy/vgeo_proxy_triangle_limit.js +25 -0
- package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts +31 -0
- package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.js +343 -0
- package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts +15 -0
- package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.js +38 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.js +29 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +7 -13
- package/src/shade/renderer/geometry/virtual/sdf/VGeoSDFProxies.d.ts +28 -59
- package/src/shade/renderer/geometry/virtual/sdf/VGeoSDFProxies.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/sdf/VGeoSDFProxies.js +84 -789
- package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +1 -8
- package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts +7 -6
- package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.js +12 -11
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_instance.d.ts +2 -2
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_instance.js +2 -2
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +1 -4
- 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 +1 -10
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts +5 -15
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.js +6 -34
- 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 +1 -7
- package/src/shade/renderer/hiz/HZBFootprint.d.ts +14 -0
- package/src/shade/renderer/hiz/HZBFootprint.d.ts.map +1 -0
- package/src/shade/renderer/hiz/HZBFootprint.js +16 -0
- package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts +10 -0
- package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts.map +1 -0
- package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.js +11 -0
- package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts +10 -0
- package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts.map +1 -0
- package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.js +11 -0
- package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +0 -7
- package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
- package/src/shade/renderer/hiz/HierarchicalZBuffer.js +405 -350
- package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts +12 -0
- package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts.map +1 -0
- package/src/shade/renderer/hiz/chunk_hzb_footprint_x.js +27 -0
- package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts +12 -0
- package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts.map +1 -0
- package/src/shade/renderer/hiz/chunk_hzb_footprint_y.js +27 -0
- package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts +14 -0
- package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts.map +1 -0
- package/src/shade/renderer/hiz/hzb_footprint_texels.js +34 -0
- package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts +0 -5
- package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
- package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +37 -57
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts +11 -0
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts.map +1 -0
- package/src/shade/renderer/hiz/{shader_hzb_reduce_mip0_clip.js → shader_hzb_reduce_mip0.js} +120 -111
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/material/GPUMaterialManager.d.ts +11 -1
- package/src/shade/renderer/material/GPUMaterialManager.d.ts.map +1 -1
- package/src/shade/renderer/material/GPUMaterialManager.js +16 -8
- package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts +12 -12
- package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
- package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +17 -118
- package/src/shade/renderer/postprocess/gtao/GTAO.d.ts +7 -3
- package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/GTAO.js +26 -13
- package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.d.ts +14 -0
- package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.js +196 -68
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.js +42 -28
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.js +429 -414
- package/src/shade/renderer/postprocess/gtao/shader_gtao_spatial_denoise.d.ts +16 -0
- package/src/shade/renderer/postprocess/gtao/shader_gtao_spatial_denoise.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/gtao/shader_gtao_spatial_denoise.js +156 -0
- package/src/shade/renderer/scene/GPUSceneContext.d.ts +2 -2
- package/src/shade/renderer/scene/GPUSceneContext.js +5 -5
- package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
- package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +7 -2
- package/src/shade/renderer/water/GPUWaterRenderer.d.ts +0 -6
- package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
- package/src/shade/renderer/water/GPUWaterRenderer.js +12 -17
- package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts +35 -0
- package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts.map +1 -0
- package/src/shade/renderer/water/medium/graph_water_meshlets.js +89 -0
- package/src/shade/renderer/water/query/GPUWaterQueries.d.ts +1 -1
- package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
- package/src/shade/renderer/water/shader_water.js +22 -13
- package/src/shade/renderer/water/shader_water_avboit.d.ts.map +1 -1
- package/src/shade/renderer/water/shader_water_avboit.js +7 -7
- package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts +1 -1
- package/src/shade/renderer/water/surface/graph_water_tessellation.js +1 -1
- package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +9 -1
- package/editor/REWORK_PLAN_2026_08.md +0 -692
- package/src/CODEC_LAYOUT_VERIFICATION.md +0 -419
- package/src/CORRECTNESS_HUNT_2026_07_24.md +0 -142
- package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +0 -738
- package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +0 -79
- package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +0 -93
- package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +0 -80
- package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +0 -164
- package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +0 -69
- package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +0 -110
- package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +0 -129
- package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +0 -72
- package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +0 -74
- package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +0 -169
- package/src/DUPLICATION_AUDIT_2026_08_07.md +0 -254
- package/src/DUPLICATION_AUDIT_REMEDIATION.md +0 -250
- package/src/DUPLICATION_REVIEW_2026_10_24.md +0 -1072
- package/src/REVIEW_2026_08_06/adv-core.md +0 -511
- package/src/REVIEW_2026_08_06/adv-gfx.md +0 -480
- package/src/REVIEW_2026_08_06/adv-view.md +0 -446
- package/src/REVIEW_2026_08_06/api-core.md +0 -296
- package/src/REVIEW_2026_08_06/api-engine.md +0 -195
- package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +0 -507
- package/src/REVIEW_2026_08_06/core-binary.md +0 -348
- package/src/REVIEW_2026_08_06/core-collection.md +0 -376
- package/src/REVIEW_2026_08_06/core-math.md +0 -221
- package/src/REVIEW_2026_08_06/core-process.md +0 -266
- package/src/REVIEW_2026_08_06/core-science.md +0 -223
- package/src/REVIEW_2026_08_06/engine-ai-nav.md +0 -279
- package/src/REVIEW_2026_08_06/engine-animation.md +0 -167
- package/src/REVIEW_2026_08_06/engine-asset.md +0 -166
- package/src/REVIEW_2026_08_06/engine-ecs.md +0 -920
- package/src/REVIEW_2026_08_06/engine-input.md +0 -197
- package/src/REVIEW_2026_08_06/engine-misc.md +0 -248
- package/src/REVIEW_2026_08_06/engine-terrain.md +0 -52
- package/src/REVIEW_2026_08_06/generation.md +0 -176
- package/src/REVIEW_2026_08_06/geom-2d.md +0 -434
- package/src/REVIEW_2026_08_06/geom-3d.md +0 -117
- package/src/REVIEW_2026_08_06/gfx-gi.md +0 -143
- package/src/REVIEW_2026_08_06/gfx-render.md +0 -740
- package/src/REVIEW_2026_08_06/gfx-texture.md +0 -690
- package/src/REVIEW_2026_08_06/prior-criticals.md +0 -49
- package/src/REVIEW_2026_08_06/prior-highs.md +0 -138
- package/src/REVIEW_2026_08_06/test-gaps.md +0 -364
- package/src/REVIEW_2026_08_06/view.md +0 -667
- package/src/REVIEW_2026_08_06.md +0 -610
- package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +0 -542
- package/src/core/bvh2/bvh3/NOTES.md +0 -2
- package/src/core/bvh8/NOTES.md +0 -63
- package/src/core/cache/wtinylfu/benchmark/CacheWTinylfu.bench.md +0 -243
- package/src/core/color/COLOR_FIX_PLAN_2026_08_28.md +0 -417
- package/src/core/color/COLOR_REVIEW_2026_08_28.md +0 -779
- package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +0 -433
- package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +0 -486
- package/src/core/geom/3d/atlas/BFF_PLAN.md +0 -174
- package/src/core/geom/3d/atlas/REVIEW_02.md +0 -392
- package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +0 -146
- package/src/core/geom/3d/gjk/NOTES.md +0 -8
- package/src/core/geom/3d/topology/TOPO_MESH_RETIREMENT_PLAN.md +0 -135
- package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +0 -381
- package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +0 -684
- package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +0 -554
- package/src/core/model/reactive/evaluation/PREDICATE_APPLICABILITY_PROPOSAL.md +0 -446
- package/src/engine/REVIEW_2026_07_03.md +0 -211
- package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +0 -815
- package/src/engine/control/first-person/DESIGN.md +0 -658
- package/src/engine/control/first-person/DESIGN_COLLISION.md +0 -382
- package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +0 -563
- package/src/engine/control/first-person/TODO.md +0 -160
- package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +0 -531
- package/src/engine/ecs/HIERARCHY_REVIEW.md +0 -717
- package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +0 -262
- package/src/engine/ecs/ik/PLAN.md +0 -179
- package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +0 -272
- package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +0 -199
- package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +0 -521
- package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +0 -463
- package/src/engine/graphics/ecs/camera/pp/NOTES.md +0 -48
- package/src/engine/graphics/render/frame_graph/CORRECTNESS_FIXES_2026_09_04.md +0 -659
- package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +0 -235
- package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +0 -490
- package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +0 -466
- package/src/engine/input/GAMEPAD_API_PROPOSAL.md +0 -717
- package/src/engine/input/ecs/ism/ISM_DESIGN.md +0 -1523
- package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +0 -627
- package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +0 -661
- package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +0 -229
- package/src/engine/network/CONGESTION_CONTROL.md +0 -87
- package/src/engine/network/MESSAGE_TRANSPORT.md +0 -364
- package/src/engine/network/SMOOTHNESS_DESIGN.md +0 -400
- package/src/engine/physics/cloth/MEASUREMENTS.md +0 -5304
- package/src/engine/physics/cloth/PLAN.md +0 -3056
- package/src/engine/physics/fluid/GPU_FLUID_CASCADES_PROPOSAL_2026_10_16.md +0 -1382
- package/src/engine/physics/fluid/REVIEW_02_PLAN.md +0 -243
- package/src/engine/physics/fluid/solver/NOTES.md +0 -46
- package/src/engine/sound/SOUND_EMITTER_RETIREMENT_PLAN.md +0 -536
- package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +0 -432
- package/src/engine/sound/simulation/PROBE_PLACEMENT.md +0 -250
- package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +0 -322
- package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +0 -261
- package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +0 -121
- package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +0 -999
- package/src/format/image/avif/DECISIONS.md +0 -545
- package/src/format/image/jpeg/NOTES.md +0 -3
- package/src/format/scene/gltf/MESHOPT_COMPRESSION_PLAN.md +0 -814
- package/src/generation/COORDINATES.md +0 -54
- package/src/generation/REVIEW_01_ACTION_PLAN.md +0 -628
- package/src/generation/foliage/ANNUAL_SHOOTS_BRIEF_2026_10_25.md +0 -220
- package/src/generation/foliage/CROWN_FILLING_BRIEF_2026_10_26.md +0 -172
- package/src/generation/foliage/CROWN_FULLNESS_BRIEF_2026_10_26.md +0 -86
- package/src/generation/foliage/FOLIAGE_DESIGN.md +0 -547
- package/src/generation/foliage/FOLIAGE_PLAN.md +0 -198
- package/src/generation/foliage/LEAF_ARCHITECTURE_BRIEF_2026_10_26.md +0 -158
- package/src/generation/foliage/LOD_REWORK_BRIEF_2026_10_27.md +0 -521
- package/src/generation/foliage/REAL_TIME_TREES_BRIEF_2026_10_27.md +0 -86
- package/src/generation/foliage/SELF_ORGANIZING_FRAMEWORK_BRIEF_2026_10_26.md +0 -347
- package/src/generation/foliage/SHOOT_SYSTEMS_ALL_SPECIES_BRIEF_2026_10_26.md +0 -248
- package/src/generation/foliage/SPEEDTREE_COMPARISON_2026_10_24.md +0 -694
- package/src/generation/foliage/TWO_FRONT_ENDS_BRIEF_2026_10_28.md +0 -552
- package/src/shade/RENDERER_CONTRACT.md +0 -697
- package/src/shade/SHADER_F16_PROPOSAL_2026_10_24.md +0 -263
- package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +0 -569
- package/src/shade/device/PASS_ENCODER_PROPOSAL_2026_08_31.md +0 -755
- package/src/shade/device/mock/EXTENSION_PROPOSAL_2026_08_22.md +0 -458
- package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +0 -255
- package/src/shade/device/mock/REVIEW_2026_08_22.md +0 -527
- package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +0 -894
- package/src/shade/device/timing/profile/SGPT_FORMAT.md +0 -345
- package/src/shade/renderer/NOTES.md +0 -78
- package/src/shade/renderer/PLAN.md +0 -8
- package/src/shade/renderer/animation/ADDITIVE_BLENDING_PLAN.md +0 -145
- package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +0 -357
- package/src/shade/renderer/atmosphere/NOTES.md +0 -6
- package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts +0 -76
- package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts.map +0 -1
- package/src/shade/renderer/camera/orbital/MomentumAxis.js +0 -164
- package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts +0 -45
- package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts.map +0 -1
- package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.js +0 -66
- package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts +0 -98
- package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts.map +0 -1
- package/src/shade/renderer/camera/orbital/OrbitalCameraInput.js +0 -184
- package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts +0 -175
- package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts.map +0 -1
- package/src/shade/renderer/camera/orbital/OrbitalCameraRig.js +0 -411
- package/src/shade/renderer/camera/orbital/README.md +0 -81
- package/src/shade/renderer/camera/orbital/index.d.ts +0 -6
- package/src/shade/renderer/camera/orbital/index.d.ts.map +0 -1
- package/src/shade/renderer/camera/orbital/index.js +0 -9
- package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +0 -964
- package/src/shade/renderer/fluid/GPU_FLUID_OCCUPANCY_AND_SOTA_REVIEW_2026_10_26.md +0 -361
- package/src/shade/renderer/geometry/bvh/BLAS_REFIT_BATCH_PLAN.md +0 -619
- package/src/shade/renderer/geometry/meshlet/COMPACTION_PLAN_2026_09_16.md +0 -674
- package/src/shade/renderer/geometry/sdf/NOTES.md +0 -6
- package/src/shade/renderer/geometry/virtual/ATTRIBUTE_PLAN_2026_09_18.md +0 -540
- package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +0 -305
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +0 -913
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +0 -787
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +0 -481
- package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +0 -301
- package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +0 -448
- package/src/shade/renderer/geometry/virtual/runtime/CULL_PLAN_2026_09_16.md +0 -385
- package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +0 -656
- package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +0 -308
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +0 -657
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +0 -551
- package/src/shade/renderer/geometry/virtual/runtime/SHADOW_PLAN_2026_09_28.md +0 -529
- package/src/shade/renderer/geometry/virtual/sdf/VGEO_SDF.d.ts +0 -47
- package/src/shade/renderer/geometry/virtual/sdf/VGEO_SDF.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/sdf/VGEO_SDF.js +0 -80
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_collect.d.ts +0 -16
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_collect.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_collect.js +0 -67
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_decode.d.ts +0 -17
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_decode.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_decode.js +0 -145
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_traverse.d.ts +0 -12
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_traverse.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_traverse.js +0 -105
- package/src/shade/renderer/global_illumination/lpv/NOTES.md +0 -3
- package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +0 -4306
- package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md +0 -410
- package/src/shade/renderer/global_illumination/sharc/NOTES.md +0 -8
- package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +0 -1369
- package/src/shade/renderer/gpu_primitive/bvh/PHASE1_HANDOVER.md +0 -202
- package/src/shade/renderer/gpu_primitive/indirect/NOTES.md +0 -94
- package/src/shade/renderer/hiz/NOTES.md +0 -3
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts +0 -8
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +0 -1
- package/src/shade/renderer/light/cluster/NOTES.md +0 -11
- package/src/shade/renderer/light/cluster/PROPOSAL_ALGORITHM_2026_10_16.md +0 -524
- package/src/shade/renderer/light/cluster/PROPOSAL_HIERARCHICAL_2026_10_16.md +0 -1101
- package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts +0 -24
- package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts.map +0 -1
- package/src/shade/renderer/material/resident/shader_resident_texture_means.js +0 -120
- package/src/shade/renderer/particles/DESIGN.md +0 -1011
- package/src/shade/renderer/particles/EMITTER_GLOBAL_STATE_PLAN_2026_10_29.md +0 -280
- package/src/shade/renderer/particles/ORIENTATION_PLAN_2026_10_15.md +0 -453
- package/src/shade/renderer/path_tracer/PATH_TRACED_MEDIA_PLAN_2026_10_02.md +0 -520
- package/src/shade/renderer/postprocess/bloom/NOTES.md +0 -6
- package/src/shade/renderer/postprocess/fsr/NOTES.md +0 -4
- package/src/shade/renderer/postprocess/gtao/NOTES.md +0 -6
- package/src/shade/renderer/postprocess/nss/NOTES.md +0 -332
- package/src/shade/renderer/postprocess/perspective/NOTES.md +0 -3
- package/src/shade/renderer/postprocess/sscs/NOTES.md +0 -5
- package/src/shade/renderer/postprocess/ssr/NOTES.md +0 -14
- package/src/shade/renderer/postprocess/ssr/REPROJECTION_PROPOSAL.md +0 -286
- package/src/shade/renderer/postprocess/ssr/prefilter/NOTES.md +0 -39
- package/src/shade/renderer/postprocess/taa/NOTES.md +0 -11
- package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +0 -78
- package/src/shade/renderer/postprocess/vrs/NOTES.md +0 -4
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +0 -1218
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +0 -600
- package/src/shade/renderer/restir/reservoir/NOTES.md +0 -6
- package/src/shade/renderer/shader/chunk/atmosphere/NOTES.md +0 -6
- package/src/shade/renderer/shader/chunk/brdf/FRESNEL_BRIEF_2026_10_16.md +0 -391
- package/src/shade/renderer/shader/chunk/color/tonemap/ACES_TONE_MAPPING_BRIEF_2026_10_17.md +0 -153
- package/src/shade/renderer/shader/chunk/color/tonemap/HDR_TONE_MAPPING_FORMS_BRIEF_2026_10_16.md +0 -322
- package/src/shade/renderer/shader/chunk/color/tonemap/SDR_TONE_MAPPING_BRIEF_2026_10_16.md +0 -247
- package/src/shade/renderer/shader/chunk/fsr/NOTES.md +0 -3
- package/src/shade/renderer/shader/chunk/fsr/spd/NOTES.md +0 -4
- package/src/shade/renderer/shader/chunk/geometry/ray/NOTES.md +0 -4
- package/src/shade/renderer/shader/chunk/material/MULTISCATTER.md +0 -159
- package/src/shade/renderer/shader/chunk/mboit/NOTES.md +0 -38
- package/src/shade/renderer/shader/chunk/spherical_harmonics/l1i/NOTES.md +0 -3
- package/src/shade/renderer/shader/chunk/spherical_harmonics/sampling/NOTES.md +0 -8
- package/src/shade/renderer/shader/compiler/SHADER_SWITCH_PROPOSAL_2026_10_16.md +0 -927
- package/src/shade/renderer/shadow/contact/FINDINGS.md +0 -413
- package/src/shade/renderer/shadow/map/csm/NOTES.md +0 -5
- package/src/shade/renderer/shadow/ray/NOTES.md +0 -31
- package/src/shade/renderer/shadow/sdf/NOTES.md +0 -9
- package/src/shade/renderer/shadow/sdf/brixelizer/NOTES.md +0 -5
- package/src/shade/renderer/shadow/sdf/cascade/NOTES.md +0 -9
- package/src/shade/renderer/shadow/vsm/NOTES.md +0 -12
- package/src/shade/renderer/shadow/vsm/PLAN.md +0 -442
- package/src/shade/renderer/shadow/vsm/REVIEW_LEDGER.md +0 -43
- package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +0 -2444
- package/src/shade/renderer/texture/mipmaps/NOTES.md +0 -3
- package/src/shade/renderer/volumetrics/NOTES.md +0 -1329
- package/src/shade/renderer/volumetrics/VOLUMETRIC_NOISE_PLAN_2026_10_15.md +0 -336
- package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts +0 -23
- package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts.map +0 -1
- package/src/shade/renderer/water/medium/GPUWaterBodies.js +0 -75
- package/src/shade/wgsl/NOTES.md +0 -4
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
# Fresnel for the direct lobe and the split-sum LUT: a brief (2026-10-16)
|
|
2
|
-
|
|
3
|
-
**Status: decided and built.** Alex chose Schlick, in the form Unreal's rasterizer uses; it landed in dbdb1cdb1
|
|
4
|
-
(§7). The sections before §7 are the brief as it was written, with the shading it describes as current. Every number
|
|
5
|
-
here was measured; how, and where the scripts are, is in §8.
|
|
6
|
-
|
|
7
|
-
Shade evaluates one material with two Fresnels. The direct lobe (`chunk_BRDF_GGX`) uses `F_Hauber`; the split-sum LUT
|
|
8
|
-
`tSplitSum`, read by every indirect specular path and by `re_direct_multiscatter`, encodes `f0 · A + f90 · B` with
|
|
9
|
-
Schlick's form at the fourth power. `MULTISCATTER.md` records three consequences: a dielectric's indirect specular is up
|
|
10
|
-
to 0.068 of a uniform sky above its direct, a white metal's direct path reaches only 0.93, and Turquin's eq. 16
|
|
11
|
-
overshoots the Heitz 2016 random walk partly because Hauber's dip compounds on every bounce.
|
|
12
|
-
|
|
13
|
-
## 1. Recommendation
|
|
14
|
-
|
|
15
|
-
**Use Schlick's `(1 − VoH)⁵` in the direct lobe, and regenerate the split-sum LUT with the same weight in both
|
|
16
|
-
channels.** Keep the LUT, a 64² rg16float texture read with one fetch, and replace its contents. In §5 this is
|
|
17
|
-
pairing E.
|
|
18
|
-
|
|
19
|
-
- **All three inconsistencies close.** Direct and indirect agree to the LUT's precision: the worst gap in the f64
|
|
20
|
-
model goes from 0.071 to 0.000. A white metal's direct furnace goes from 0.929 to 1.000. At roughness 0.99, eq. 16
|
|
21
|
-
against a random walk using the same Fresnel goes from +4.7% to 0.0% for a white metal and from +5.7% to +1.0% for
|
|
22
|
-
silver.
|
|
23
|
-
- **It costs one multiply per light.** Per vec3, it is 13 DXIL float operations against Hauber's 12, with no
|
|
24
|
-
transcendental. The LUT fetch and the per-pixel compensation are unchanged.
|
|
25
|
-
- **Against exact Fresnel it ties with Hauber.**
|
|
26
|
-
- Dielectrics: the two are within 0.0033 of each other at every angle.
|
|
27
|
-
- Five measured metals: mean |ΔF| 0.0139 against Hauber's 0.0148. Each wins on different metals (§3).
|
|
28
|
-
- It is far more accurate than what the indirect path uses now. The shipped x⁴ LUT is up to +0.079 in albedo at
|
|
29
|
-
IOR 1.5 (up to +29%).
|
|
30
|
-
- **What it changes visually** (§6):
|
|
31
|
-
- Dielectric reflections at grazing view darken toward the exact value: −12% to −34% of a sphere rim's linear
|
|
32
|
-
luminance. At roughness 0.26, N·V 0.26, the indirect albedo goes from 0.288 to 0.2225, and exact is 0.229.
|
|
33
|
-
- Metal spheres move by less than 2.3% at the rim.
|
|
34
|
-
- A back-lit white-metal or silver rim brightens by about 1%.
|
|
35
|
-
- **It is the white-tint case of F82-tint**, the metal Fresnel of OpenPBR and Adobe's Standard Material. If a metal
|
|
36
|
-
edge tint is ever added to the material, it becomes a third LUT channel on top of this, not a rewrite (§4).
|
|
37
|
-
|
|
38
|
-
**What would reverse it: preferring the grazing dip of high-index grey metals over silver, gold, copper and the perfect
|
|
39
|
-
conductor.** For aluminium and iron at low roughness, Hauber is 0.03 to 0.06 closer in albedo; chromium, nickel and
|
|
40
|
-
titanium follow the same physics. For silver, Hauber is 0.06 worse.
|
|
41
|
-
|
|
42
|
-
If that look matters, Hauber's consistent form is pairing B (§5): it adds an `Ess` channel to the LUT. That closes the
|
|
43
|
-
gap, but the white furnace stays at 0.93 and the walk overshoot at +4.7%.
|
|
44
|
-
|
|
45
|
-
## 2. Why the two paths disagree: Hauber's dip forces a choice
|
|
46
|
-
|
|
47
|
-
At `f90 = 1`, which every caller passes, `F_Hauber = f0 (1 − x⁴) + x⁵` with `x = 1 − VoH`. At `F0 = 1` it is
|
|
48
|
-
`1 − VoH x⁴`, which dips to 0.918 at VoH = 0.2.
|
|
49
|
-
|
|
50
|
-
Turquin's compensation needs `Ess`, the albedo with `F ≡ 1`, and Shade reads it as `A + B`. A two-channel LUT can hold
|
|
51
|
-
Hauber's split or `A + B = Ess`, but not both:
|
|
52
|
-
|
|
53
|
-
- **Before 933feac74**, the asset held Hauber's split exactly. That commit's own check found A within 4.9e-4 of the
|
|
54
|
-
`1 − f⁴` term and B within 4.8e-4 of the `f⁵` term. Its `A + B` was therefore the dipped white albedo. 933feac74 read
|
|
55
|
-
that shortfall (up to −8.2%) as a stale asset.
|
|
56
|
-
- **933feac74 moved B to x⁴.** Now `A + B = Ess`, but `f0 · A + B` is Schlick at the fourth power, not Hauber, and the
|
|
57
|
-
indirect path has used a different Fresnel since. The furnace's 0.068 dielectric gap is that difference:
|
|
58
|
-
`(1 − f0)x⁴ − (x − f0)x⁴ = VoH x⁴`, up to 0.082.
|
|
59
|
-
|
|
60
|
-
**The f64 model of the pre-933feac74 pairing gives no gap and a white furnace of 1.000, but for the wrong reason.**
|
|
61
|
-
`Kms = (1 − A − B) / (A + B)` counts the dip as missing multiple scattering, so it stays positive even as roughness
|
|
62
|
-
goes to 0.
|
|
63
|
-
|
|
64
|
-
**Any Fresnel that is linear in F0, with `F(1, μ) ≡ 1`, removes the choice.** `f0 · A + f90 · B` is then its split,
|
|
65
|
-
and `A + B` is its `Ess`. Schlick at any exponent qualifies; Hauber does not.
|
|
66
|
-
|
|
67
|
-
## 3. Hauber's approximation, re-examined
|
|
68
|
-
|
|
69
|
-
### Source
|
|
70
|
-
|
|
71
|
-
Niklas Hauber, "Finding Alternatives to Schlick's Approximation of the Fresnel Equations", photometric.io, 2024-06-27
|
|
72
|
-
(the URL in `chunk_F_Hauber`). He found it by GPU symbolic regression, with parameters R0 and 1 − cos θ:
|
|
73
|
-
|
|
74
|
-
R(θ) = R0 + (1 − cos θ − R0)(1 − cos θ)⁴
|
|
75
|
-
|
|
76
|
-
**What he fitted it to:**
|
|
77
|
-
|
|
78
|
-
- 4,096 points, θ uniform "from 0 to π" as written.
|
|
79
|
-
- 40% at IOR 1.5, and 40% at an IOR uniform in [1.34, 1.7].
|
|
80
|
-
- 20% from seven complex IORs: 0.3+3i, 0.15+4i, 0.3+3.35i, 1.4+7i, 1.84+3.73i, 2.1+4i, 2.9+3.1i.
|
|
81
|
-
|
|
82
|
-
So the fit covers both dielectrics and conductors, mostly dielectrics, and most of its metals have a real part large
|
|
83
|
-
enough to dip.
|
|
84
|
-
|
|
85
|
-
**What he reports:** 34% lower MSE than Schlick, essentially the same shape for dielectrics, almost 50% better on his
|
|
86
|
-
metals, with the warning that another dataset would differ. He also reports 5 CUDA instructions against 6. He offers a
|
|
87
|
-
shape blend, `R0 + (1 − R0 − s cos θ)(1 − cos θ)⁴`, where `s = 1` is his form and `s = 0` is Schlick at the fourth
|
|
88
|
-
power. The post does not discuss F0 → 1.
|
|
89
|
-
|
|
90
|
-
### Shade's form
|
|
91
|
-
|
|
92
|
-
`mix(f0, f90 − VoH, x⁴)` is `f0 + (f90 − VoH − f0) x⁴`. At `f90 = 1` this is Hauber's formula exactly, and it equals
|
|
93
|
-
Schlick x⁵ minus `F0 · VoH x⁴`: the dip is proportional to F0, so it is deepest at F0 = 1. The `f90` is
|
|
94
|
-
Shade's own generalisation; the post has none. The dip term does not scale with it, so at `f90 = 0` the form is
|
|
95
|
-
negative (`−VoH x⁴`). Every caller passes 1, so that never runs.
|
|
96
|
-
|
|
97
|
-
**His claim reproduces on his dataset:** 35.5% lower MSE (θ in [0, π/2]; the same with |cos θ| over [0, π]), 1.6% on
|
|
98
|
-
dielectrics, 49% on metals. So the formula is read correctly.
|
|
99
|
-
|
|
100
|
-
### Against exact Fresnel
|
|
101
|
-
|
|
102
|
-
**Dielectrics** use the exact unpolarised Fresnel. Error over θ uniform in [0°, 90°]; "albedo" is the GGX lobe's
|
|
103
|
-
directional albedo at the 25 furnace points (roughness 0.26–0.99, N·V 0.10–0.99):
|
|
104
|
-
|
|
105
|
-
| IOR 1.5 (F0 0.04) | max \|ΔF\| | mean \|ΔF\| | ΔF_avg | worst Δalbedo | worst relative Δalbedo |
|
|
106
|
-
|---|---|---|---|---|---|
|
|
107
|
-
| Hauber | 0.033 at 85° | 0.0093 | −0.0068 | −0.0185 | −21.5% |
|
|
108
|
-
| Schlick x⁵ | 0.036 at 85° | 0.0092 | −0.0061 | +0.0194 | −20.2% |
|
|
109
|
-
| Schlick x⁴ (the shipped LUT) | 0.103 at 82° | 0.0214 | +0.0122 | +0.0785 | +28.7% |
|
|
110
|
-
|
|
111
|
-
The worst absolute and the worst relative errors fall at different points. For Hauber and Schlick x⁵ the worst
|
|
112
|
-
relative error is at mid angles, where the albedo is small and both read low (0.054 against 0.069 at roughness 0.51,
|
|
113
|
-
N·V 0.51).
|
|
114
|
-
|
|
115
|
-
Over IOR 1.3–2.0, the worst case for each:
|
|
116
|
-
|
|
117
|
-
| | max \|ΔF\| | mean \|ΔF\| | worst \|Δalbedo\| | worst relative |
|
|
118
|
-
|---|---|---|---|---|
|
|
119
|
-
| Hauber | 0.067 | 0.0119 | 0.047 | −21.5% |
|
|
120
|
-
| Schlick x⁵ | 0.068 | 0.0121 | 0.048 | −20.2% |
|
|
121
|
-
| Schlick x⁴ | 0.142 | 0.0313 | 0.109 | +54.1% (IOR 1.3) |
|
|
122
|
-
|
|
123
|
-
For a dielectric, Hauber is Schlick x⁵ minus `F0 · VoH x⁴`, at most 0.0033 at F0 0.04.
|
|
124
|
-
|
|
125
|
-
**Conductors** use measured n and k: Johnson & Christy 1972 for gold, silver and copper; Johnson & Christy 1974 for
|
|
126
|
-
iron; McPeak et al. 2015 for aluminium (Rakić 1995 has 13 rows in the visible, and its dip agrees to 0.012). The full
|
|
127
|
-
complex Fresnel equations are integrated over 380–780 nm against D65 and the CIE 1931 2° observer (the CIE's own
|
|
128
|
-
1 nm tables, taken at 5 nm), then projected to linear sRGB. That gives gold F0 (1.038, 0.729, 0.365) and aluminium
|
|
129
|
-
(0.906, 0.916, 0.923). Each candidate is fed the exact curve's own F0; gold's red is left above 1.
|
|
130
|
-
|
|
131
|
-
Worst channel per metal. The Δalbedo column is the worst over the 25 lobes:
|
|
132
|
-
|
|
133
|
-
| metal | Hauber max \|ΔF\| | Schlick x⁵ max \|ΔF\| | Hauber Δalbedo | Schlick x⁵ Δalbedo | measured dip below F0 |
|
|
134
|
-
|---|---|---|---|---|---|
|
|
135
|
-
| silver | 0.078 | 0.007 | −0.065 | +0.005 | 0.3% |
|
|
136
|
-
| gold | 0.062 | 0.033 | −0.051 | +0.029 | 4.5% (red) |
|
|
137
|
-
| copper | 0.055 | 0.043 | −0.044 | +0.033 | 1.3% (red) |
|
|
138
|
-
| aluminium | 0.049 | 0.109 | −0.026 | +0.082 | 7.1% (red) |
|
|
139
|
-
| iron | 0.141 | 0.178 | +0.103 | +0.135 | 4.7% (red) |
|
|
140
|
-
| mean of the 15 channels' mean \|ΔF\| | 0.0148 | 0.0139 | | | |
|
|
141
|
-
|
|
142
|
-
Over all five metals, Hauber's MSE is 22% below Schlick's. That comes from aluminium (85% lower) and iron (47%). On
|
|
143
|
-
gold it is 5× Schlick's, on silver 212×, on copper about equal.
|
|
144
|
-
|
|
145
|
-
**As F0 → 1**, Hauber's minimum over angle falls further below F0:
|
|
146
|
-
|
|
147
|
-
| F0 | 0.5 | 0.8 | 0.9 | 0.95 | 0.98 | 1.0 |
|
|
148
|
-
|---|---|---|---|---|---|---|
|
|
149
|
-
| Hauber, minimum | −0.5% | −3.4% | −5.4% | −6.7% | −7.6% | −8.2% (0.918 at VoH 0.20) |
|
|
150
|
-
|
|
151
|
-
Schlick at either exponent never falls below F0, and is exactly 1 at F0 = 1. Only a perfect conductor has F0 = 1, and
|
|
152
|
-
it has no dip. Silver, the nearest real metal (F0 0.98), dips 0.3%.
|
|
153
|
-
|
|
154
|
-
### The dip belongs to n, not to F0
|
|
155
|
-
|
|
156
|
-
Fitting Hauber's shape parameter `s` to each measured channel by least squares:
|
|
157
|
-
|
|
158
|
-
| | silver | gold | copper | aluminium | iron |
|
|
159
|
-
|---|---|---|---|---|---|
|
|
160
|
-
| best `s` (R, G, B) | 0.09, 0.09, 0.05 | 0.38, 0.24, 0.58 | 0.34, 0.86, 0.88 | 1.15, 0.88, 0.64 | 2.24, 2.21, 1.96 |
|
|
161
|
-
| F0 (R, G, B) | 0.99, 0.98, 0.98 | 1.04, 0.73, 0.36 | 0.93, 0.62, 0.52 | 0.91, 0.92, 0.92 | 0.53, 0.51, 0.49 |
|
|
162
|
-
|
|
163
|
-
Silver and aluminium have nearly the same F0 and need opposite shapes. The dip comes from the real part of the index
|
|
164
|
-
(Lazányi's correction has its slope at 2n), and the G-buffer carries only F0.
|
|
165
|
-
|
|
166
|
-
**So no function of F0 alone can carry the dip.** Hauber's fit applies the average dip of its seven metals to every
|
|
167
|
-
F0, including F0 = 1. It is right for aluminium-like metals and wrong for silver-like ones.
|
|
168
|
-
|
|
169
|
-
## 4. The state of the art in pure functions
|
|
170
|
-
|
|
171
|
-
### Fresnel
|
|
172
|
-
|
|
173
|
-
DXIL is from Chrome's own Tint and DXC on the RTX 4090. Float operations per vec3 are counted, with the input loads
|
|
174
|
-
excluded.
|
|
175
|
-
|
|
176
|
-
| form | inputs | DXIL float ops (div, sqrt, exp/log) | five metals: worst max \|ΔF\| / mean \|ΔF\| | dielectric IOR 1.5 max \|ΔF\| | F0 = 1 |
|
|
177
|
-
|---|---|---|---|---|---|
|
|
178
|
-
| Schlick 1994, x⁵ (multiplies) | F0 | 13 (0) | 0.178 / 0.0139 | 0.036 | 1 |
|
|
179
|
-
| Schlick x⁵ as `chunk_F_Schlick` (`pow`) | F0 | 13 (log + exp) | same | same | 1 |
|
|
180
|
-
| Schlick x⁴ (the shipped LUT) | F0 | 12 (0) | 0.213 / 0.0186 | 0.103 | 1 |
|
|
181
|
-
| Hauber 2024 (shipped direct) | F0 | 12 (0) | 0.141 / 0.0148 | 0.033 | 0.918 |
|
|
182
|
-
| Lazányi–Schlick 2005 | n, k → F0, a, α | 25 (log + exp) | needs n | — | — |
|
|
183
|
-
| F82-tint: Hoffman 2019, Adobe ASM, OpenPBR | F0 + edge tint | 40 (3 div) | 0.020 / 0.0026 (tint from data) | — | 1 at tint 1 |
|
|
184
|
-
| Gulbrandsen 2014, (r, g) → n, k | F0 + edge tint | 109 (15 div, 3 sqrt) | exact up to its approximate conductor formula | — | singular, clamped to 0.99 |
|
|
185
|
-
| exact dielectric (`F_dielectric`) | IOR | 28, scalar (4 div, 1 sqrt), branch | — | 0 | — |
|
|
186
|
-
| exact conductor | n, k | 85 (6 div, 6 sqrt) | 0 | — | — |
|
|
187
|
-
|
|
188
|
-
Notes on the sources (the literature summaries are with the scripts, §8):
|
|
189
|
-
|
|
190
|
-
- **Schlick 1994** was not read first-hand: the only reachable copy is an image-only scan. Its formula is as Hoffman
|
|
191
|
-
2019 (eq. 1) and OpenPBR give it.
|
|
192
|
-
|
|
193
|
-
- **Lazányi & Szirmay-Kalos 2005** (WSCG): subtract `a μ (1 − μ)^α` from Schlick. `a = 2n` comes from the slope at
|
|
194
|
-
grazing incidence; α is fitted at μ = 0.15. Their stated relative errors: Cu 1.9–4.9%, Au 5.0–5.4%, Ag 4.4–9.0%,
|
|
195
|
-
Al 2.0–17.5%. The inputs are n and k.
|
|
196
|
-
- **Gulbrandsen 2014** (JCGT 3(4)): maps reflectivity and edge tint to n, k, then evaluates PBRT's approximate
|
|
197
|
-
conductor formula. Standard Surface uses it; MaterialX feeds the result to its full conductor Fresnel.
|
|
198
|
-
- **Hoffman 2019 and 2023, Kutz et al. 2021/2023 (Adobe Standard Material), OpenPBR 1.1.1:**
|
|
199
|
-
`F = Schlick(μ) − b μ (1 − μ)⁶`, with `b = Schlick(1/7)(1 − tint)/(μ̄(1 − μ̄)⁶)` and μ̄ = 1/7, so that F(82°) =
|
|
200
|
-
tint · Schlick(82°). It is linear in F0, F90 and b, so a split-sum LUT needs one more channel, `∫ μ(1 − μ)⁶`.
|
|
201
|
-
The OpenPBR companion paper gives its hemispherical average as `F0 + (1 − F0)/21 − b/126`. Unreal 5.4 has the
|
|
202
|
-
Adobe F82 tint only in its Substrate path tracer (disabled by default); its rasterizer runs Schlick.
|
|
203
|
-
- **Filament and Frostbite** use Schlick with a shadowed F90. Belcour et al. 2020 needs a tabulated basis.
|
|
204
|
-
Vernooij 2024 fits the Lazányi form per metal. No F0-only metal approximation newer than Hauber's was found.
|
|
205
|
-
|
|
206
|
-
### Split-sum (DFG) fits
|
|
207
|
-
|
|
208
|
-
**Keep the LUT.** It is one fetch that both paths already pay, since the compensation reads it anyway. It is also an
|
|
209
|
-
order of magnitude more accurate than the best published fit, which costs 26 to 89 float operations.
|
|
210
|
-
|
|
211
|
-
Each fit was measured against the f64 x⁵ split of Shade's lobe (GGX, height-correlated Smith, α = r²). The points are
|
|
212
|
-
a 32² grid of N·V ≥ 0.05 and roughness, which falls midway between the 64-wide LUT's texel centres, its worst place to
|
|
213
|
-
interpolate. DXIL is counted as above.
|
|
214
|
-
|
|
215
|
-
| fit | fitted to | DXIL float ops | white (F0 1) albedo: max \|ΔE\| / mean | F0 0.04: max \|ΔE\| |
|
|
216
|
-
|---|---|---|---|---|
|
|
217
|
-
| **the LUT, regenerated at x⁵** | Shade's lobe | **1 sample** | **0.0027 (0.3%) / 0.0002** | **0.0009** |
|
|
218
|
-
| Karis 2014 `EnvBRDFApprox` (`DFGApprox`) | UE4's G, "adapted" | 16 (1 exp) | 0.342 (−43%) / 0.106 | 0.083 |
|
|
219
|
-
| Narkowicz 2014, polynomial | uncorrelated Smith, α = (1 − gloss)⁴ | 36 (1 sqrt) | 0.221 (−28%) / 0.019 | 0.080 |
|
|
220
|
-
| Narkowicz 2014, Lazarov form | same | — | 0.315 (−39%) / 0.041 | 0.080 |
|
|
221
|
-
| Pesce & Iwanicki 2015 | not stated; roughness = √r, the best of three readings | 15 (1 sqrt, 1 exp) | 0.126 (−15%) / 0.028 | 0.135 |
|
|
222
|
-
| UE 5.4 analytic energy fit ("WIP") | height-correlated table | 26 (2 div, 3 log, 3 exp) | 0.058 (+6.5%) / 0.0064 | 0.015 |
|
|
223
|
-
| MaterialX `mx_ggx_dir_albedo_analytic` (2022) | height-correlated, Schlick | 89 (2 div) | 0.084 (−8.4%) / 0.0060 | 0.025 |
|
|
224
|
-
| Kulla & Conty 2017, E only | height-correlated | 38 (div, sqrt, log, exp) | 0.011 (3.5%) / 0.0022 | — |
|
|
225
|
-
| Sforza & Pellacini 2022, E only | uncorrelated Smith | — | 0.226 (−28%) / 0.016 | — |
|
|
226
|
-
|
|
227
|
-
Notes on the table:
|
|
228
|
-
|
|
229
|
-
- **Worst cases.** Most fits are worst at grazing (N·V 0.08) and rough. The LUT's three columns below N·V 0.05 reach
|
|
230
|
-
0.019.
|
|
231
|
-
- **Fdez-Agüera 2019 is not a fit.** It reads Epic's LUT, and its contribution is a multiple-scattering form, with
|
|
232
|
-
`Favg = F0 + (1 − F0)/21` for Schlick.
|
|
233
|
-
- **Kulla & Conty keep tables.** Their slides favour tables over fits. The E-only fits can stand in for Turquin's
|
|
234
|
-
`Ess`, but not for the split.
|
|
235
|
-
- **Turquin lists analytic fits as future work.** His conclusion names good analytic fits for all the smooth tables as
|
|
236
|
-
future work, to save texture fetches in real time. No published fit reaches the LUT's accuracy for a
|
|
237
|
-
height-correlated lobe.
|
|
238
|
-
- **Engines moved from fits to LUTs in 2025.** three.js dropped Karis's fit for a height-correlated LUT (PR #32054,
|
|
239
|
-
October 2025). Godot's Forward+ renderer moved to a LUT in March 2025; its mobile renderers keep Karis's fit.
|
|
240
|
-
Filament, EEVEE and Unity HDRP use LUTs only.
|
|
241
|
-
- **One Shade pass still uses a fit:** `ssr_combine_shader.js` weights by `DFGApprox`, which is 0.34 (43%) below Shade's
|
|
242
|
-
white albedo at grazing, rough. That is already reported (2026-10-03) and separate from this brief.
|
|
243
|
-
|
|
244
|
-
## 5. The pairings against the three inconsistencies
|
|
245
|
-
|
|
246
|
-
Measured in an f64 model of Shade's two paths under a uniform sky, at the 25 furnace points, for white, a 0.04
|
|
247
|
-
dielectric and the five measured metals:
|
|
248
|
-
|
|
249
|
-
- direct = `E_F(F0) · (1 + F0 Kms)`
|
|
250
|
-
- indirect = `(F0 A + B)(1 + F0 Kms)`
|
|
251
|
-
|
|
252
|
-
The model reproduces the real passes' furnace sweep (`white_furnace.spec.js`'s helpers, run through the emulator):
|
|
253
|
-
white direct 0.9301 against 0.9302, dielectric 0.2198 against 0.2196, gap 0.0682 against 0.0684.
|
|
254
|
-
|
|
255
|
-
The walk is the shared bench's port of Heitz 2016, 4M paths per point. Each Fresnel candidate and the exact Fresnel
|
|
256
|
-
are applied at every bounce, and the conductors are walked per wavelength (10 nm) before projection.
|
|
257
|
-
|
|
258
|
-
| pairing | worst indirect − direct | white direct, min | eq. 16 vs its own walk, r 0.99: white / silver | LUT |
|
|
259
|
-
|---|---|---|---|---|
|
|
260
|
-
| A. shipped: Hauber direct, LUT x⁴ | +0.071 (white), +0.068 (dielectric) | 0.929 | +4.7% / +5.7% | as is |
|
|
261
|
-
| B. Hauber direct, LUT (1 − x⁴, x⁵) + an `Ess` channel | 0.000 | 0.929 | +4.7% / +5.7% | 3 channels |
|
|
262
|
-
| C. Hauber direct, LUT (1 − x⁴, x⁵), `Kms` from A + B (pre-933feac74) | 0.000 | 1.000, by booking the dip as multiple scattering | — | regenerate |
|
|
263
|
-
| D. Schlick x⁴ in both | 0.000 | 1.000 | 0.0% / +0.9% | as is |
|
|
264
|
-
| **E. Schlick x⁵ in both** | **0.000** | **1.000** | **0.0% / +1.0%** | **regenerate** |
|
|
265
|
-
|
|
266
|
-
**D needs no new asset, but it moves the direct path to the least accurate form.** For a dielectric, it puts the
|
|
267
|
-
+29% albedo error of today's indirect path into the direct one, and it is worse on every metal (§3).
|
|
268
|
-
|
|
269
|
-
**E is the shipped LUT's two channels, regenerated.**
|
|
270
|
-
|
|
271
|
-
**Against the walk with exact Fresnel**, the worst over N·V and channels, at roughness 0.26:
|
|
272
|
-
|
|
273
|
-
| | white | silver | gold | copper | aluminium | iron | dielectric IOR 1.5 |
|
|
274
|
-
|---|---|---|---|---|---|---|---|
|
|
275
|
-
| Hauber (A's, B's direct) | −7.1% | −6.9% | −8.0% | −5.4% | +3.4% | +17.3% | −22% |
|
|
276
|
-
| Schlick x⁵ (E) | 0.0% | +0.5% | −4.1% | +4.3% | +10.7% | +23.4% | −21% |
|
|
277
|
-
| Schlick x⁴ (A's indirect, D) | 0.0% | +0.6% | +6.4% | +9.2% | +11.5% | +28.9% | +25% |
|
|
278
|
-
|
|
279
|
-
The dielectric's relative errors are on small values. At N·V 0.51 they are 0.0687 (x⁵) and 0.0962 (x⁴) against an
|
|
280
|
-
exact 0.0869. At N·V 0.26 they are 0.2225 and 0.2879 against 0.2294.
|
|
281
|
-
|
|
282
|
-
**At roughness 0.99, the Fresnel no longer decides; Turquin's `Fms = F0` does.** Against the exact walk, eq. 16 is
|
|
283
|
-
+21.7% for iron, +17.2% for copper and +23.8% in gold's blue under every Fresnel. That is the compensation's own
|
|
284
|
-
saturation error, which `MULTISCATTER.md` already ledgers.
|
|
285
|
-
|
|
286
|
-
**Hauber's dip compounding is the part E removes.** Against walks with their own Fresnel at roughness 0.99, eq. 16's
|
|
287
|
-
mean overshoot is:
|
|
288
|
-
|
|
289
|
-
| | white | silver | aluminium | gold | copper | iron |
|
|
290
|
-
|---|---|---|---|---|---|---|
|
|
291
|
-
| Hauber | +0.026 | +0.029 | +0.036 | +0.024 | +0.031 | +0.025 |
|
|
292
|
-
| Schlick x⁵ | 0.000 | +0.004 | +0.016 | +0.010 | +0.020 | +0.021 |
|
|
293
|
-
|
|
294
|
-
Against the exact walk the totals move the other way, by 0.003 to 0.008 for the metals: Hauber's dip had been
|
|
295
|
-
offsetting part of `Fms = F0`'s overshoot.
|
|
296
|
-
|
|
297
|
-
## 6. The visual change
|
|
298
|
-
|
|
299
|
-
Rendered on the RTX 4090 with the multiscatter bench's sphere sweep: seven roughnesses (0.1–1.0) by seven materials
|
|
300
|
-
(gold, copper, aluminium, iron and silver at their table F0, a white metal at F0 1, and a black dielectric), IBL, TAA,
|
|
301
|
-
EV −1.
|
|
302
|
-
|
|
303
|
-
Two lightings:
|
|
304
|
-
|
|
305
|
-
- the bench's sun, which comes from the camera's side, so VoH is about 0.9 on every sphere;
|
|
306
|
-
- the same sun from behind the spheres, so their rims see VoH ≈ 0.27, where Hauber's dip lives.
|
|
307
|
-
|
|
308
|
-
No engine file was edited. The "after" page's prelude rewrites `F_Hauber`'s body to Schlick x⁵ in the one module that
|
|
309
|
-
declares it, and replaces `split_sum.bin`'s texels as they are fetched. The replacement is the shipped texels plus the
|
|
310
|
-
f64 change of weight, `∫ VoH x⁴`, so only the Fresnel differs, not the integration. A repeat of the shipped arm
|
|
311
|
-
differed by at most 0.27% (front) and 0.09% (back).
|
|
312
|
-
|
|
313
|
-
Change in mean linear luminance, after against before, over the outer rim (0.75–0.92 of the radius):
|
|
314
|
-
|
|
315
|
-
| | 0.1 | 0.25 | 0.4 | 0.55 | 0.7 | 0.85 | 1.0 |
|
|
316
|
-
|---|---|---|---|---|---|---|---|
|
|
317
|
-
| black dielectric, front | −34.1% | −32.8% | −27.6% | −21.4% | −18.4% | −14.9% | −12.3% |
|
|
318
|
-
| black dielectric, back | −33.7% | −17.3% | −7.5% | −11.6% | −17.2% | −15.6% | −13.4% |
|
|
319
|
-
| iron, front | −2.3% | −2.2% | −1.6% | −0.9% | −0.6% | −0.4% | −0.3% |
|
|
320
|
-
| copper, front | −1.5% | −1.4% | −1.0% | −0.5% | −0.3% | −0.2% | −0.1% |
|
|
321
|
-
| white metal, back | 0.0% | +0.9% | +1.3% | +0.8% | +0.2% | 0.0% | 0.0% |
|
|
322
|
-
| silver, back | −0.1% | +0.6% | +1.0% | +0.5% | +0.1% | 0.0% | 0.0% |
|
|
323
|
-
|
|
324
|
-
Over whole spheres, metals move by at most 0.4% and the black dielectric by 2–8%.
|
|
325
|
-
|
|
326
|
-
**Where the eye sees it is the dielectric's Fresnel rim under the sky.** It loses the x⁴ LUT's excess and lands near
|
|
327
|
-
exact: 0.2225 against 0.2294 at roughness 0.26, N·V 0.26, where it was 0.288.
|
|
328
|
-
|
|
329
|
-
Swapping only the direct Fresnel to x⁴ (pairing D) brightens the back-lit black dielectric's rim by up to 28%. The
|
|
330
|
-
images are with the scripts (§8).
|
|
331
|
-
|
|
332
|
-
## 7. As built (dbdb1cdb1)
|
|
333
|
-
|
|
334
|
-
**The form is standard Schlick.** `F_Schlick(F0, F90, VoH)` computes `Fc = (1 − VoH)^5` by multiplies, then returns `F90 Fc + (1 − Fc) F0`, and the split-sum lookup reads a LUT integrated with the same weight. `chunk_F_Schlick` now has that form; its `pow(x, 5.0)` had compiled to a log and an exp.
|
|
335
|
-
|
|
336
|
-
**Every `F_Hauber` caller moved to it,** and `chunk_F_Hauber` is deleted:
|
|
337
|
-
|
|
338
|
-
- `chunk_BRDF_GGX`;
|
|
339
|
-
- `graph_lpv_shade.js`;
|
|
340
|
-
- the path tracer: `chunk_render_trace_path.js` ×2 and `chunk_sample_material_data.js`;
|
|
341
|
-
- the forward fragment;
|
|
342
|
-
- the view Fresnel of `shader_avboit_splat.js` and `shader_oit_generate_moments.js`.
|
|
343
|
-
|
|
344
|
-
**`chunk_integrate_brdf` weights `(1 − x⁵, x⁵)`, and `split_sum.bin` was regenerated once on the RTX 4090.** It
|
|
345
|
-
followed the 16cca4c69 procedure: 2^18 samples in 1024-sample blocks through rg32float, f16 encoded on the CPU, then
|
|
346
|
-
the chunk put back at 1024 samples. Checked against the old asset:
|
|
347
|
-
|
|
348
|
-
- its A + B moved by at most one f16 ulp anywhere;
|
|
349
|
-
- against the old asset plus the f64 change of weight, it is within 5.8e-4 everywhere.
|
|
350
|
-
|
|
351
|
-
**Pinned red then green:**
|
|
352
|
-
|
|
353
|
-
- `chunk_BRDF_GGX.spec.js` (new): a perfect conductor's lobe is D V alone, and the lobe's Fresnel is Schlick's fifth
|
|
354
|
-
power.
|
|
355
|
-
- `split_sum_asset.spec.js`: weight x⁵, and the generator itself held to the same integral.
|
|
356
|
-
- `white_furnace.spec.js`: the dip's slack goes from four checks.
|
|
357
|
-
- A white metal's direct light reads 0.997 – 1.000 of the sky.
|
|
358
|
-
- A pure material's direct and indirect specular agree to 0.0031.
|
|
359
|
-
- A white dielectric's totals agree to 0.0010.
|
|
360
|
-
- The metalness blend holds to 1.3e-5.
|
|
361
|
-
- `chunk_F_Schlick.spec.js` (new): pins the form.
|
|
362
|
-
|
|
363
|
-
**The diffuse coupling of 835cba76b now balances exactly.** It keeps 1 − the LUT's dielectric albedo, which is now the
|
|
364
|
-
direct lobe's own, so a white dielectric's direct light reads 1.000 – 1.001 of the sky (it was 0.932 – 1.000).
|
|
365
|
-
|
|
366
|
-
**Device checks:**
|
|
367
|
-
|
|
368
|
-
- Every shader that links a changed chunk compiles on the 4090: 19 unique modules, found through the reverse import
|
|
369
|
-
closure. The lightmap bake pair fails on a struct member already missing on master (a224bc59c1).
|
|
370
|
-
- The sphere sweep, rendered from a detached master worktree and from the change, moves as §6 predicted, to 0.1
|
|
371
|
-
point.
|
|
372
|
-
|
|
373
|
-
**Not part of this:** `ssr_combine_shader.js` weights by Karis's fit, not the LUT (§4).
|
|
374
|
-
|
|
375
|
-
## 8. Method and reproduction
|
|
376
|
-
|
|
377
|
-
The scripts, data and renders are in the side-session notes folder
|
|
378
|
-
`local_9644ea11-c179-40dd-a485-620475f78a45/fresnel-brief/`, outside the repo:
|
|
379
|
-
|
|
380
|
-
- `dfg_lib.mjs`: the lobe integrated over microfacet normals, with GGX's CDF as the variable, so that narrow lobes
|
|
381
|
-
resolve. It is held to `split_sum_asset.spec.js`'s light-direction quadrature and to the shipped texels to about
|
|
382
|
-
1e-4.
|
|
383
|
-
- `fresnel_lib.mjs`: exact dielectric and complex conductor Fresnel, and the spectral projection.
|
|
384
|
-
- `optical_data.json`: n and k, CIE 1931 and D65, each with its source URL.
|
|
385
|
-
- `study_dielectric.mjs`, `study_metal.mjs`, `hauber_claim.mjs`, `study_dfg.mjs`, `study_dfg_e.mjs`,
|
|
386
|
-
`pesce_mapping.mjs`, `combos_study.mjs` and `model_check.mjs`, each with its `.txt` output.
|
|
387
|
-
- `dfg_fits.mjs`: every published fit, with its constants.
|
|
388
|
-
- `lit_fresnel.md` and `lit_dfg.md`: the literature, read from the primary sources, with URLs and sections.
|
|
389
|
-
- `walk_candidates.mjs`: the shared bench's Heitz port, with every Fresnel. Run with `analyse_walk.mjs`.
|
|
390
|
-
- `alu/`: the dump page and counter.
|
|
391
|
-
- `render_drive.mjs`, the sphere page and `sphere_diff.py`, with the renders.
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
# ACES tone mapping, Unreal's default look: a brief (2026-10-17)
|
|
2
|
-
|
|
3
|
-
**Status: built.** `ShadeToneMappingMode.ACES` is the default. It is added beside GT7, PBR Neutral and NONE, and
|
|
4
|
-
replaces none of them. Alex asked for a close match to Unreal Engine's current default look, on SDR and HDR alike,
|
|
5
|
-
as fixed-function tone mapping: no settings and no LUTs.
|
|
6
|
-
|
|
7
|
-
## 1. What it is
|
|
8
|
-
|
|
9
|
-
- **SDR is Unreal's "Filmic" tone mapper at its default settings.** It evaluates UE 5.4's sRGB LUT colour-grading and filmic tone-mapping pipeline in `chunk_tonemap_ue_filmic` per pixel where UE bakes it into a 32³ LUT:
|
|
10
|
-
1. ACEScg from UE's default sRGB working space;
|
|
11
|
-
2. Expand Gamut at 1.0;
|
|
12
|
-
3. the colour corrections at their defaults, which only clamp at zero;
|
|
13
|
-
4. Blue Correction at 0.6;
|
|
14
|
-
5. the curve at Slope 0.88, Toe 0.55, Shoulder 0.26, Black Clip 0 and White Clip 0.04 (the default post-process settings);
|
|
15
|
-
6. the inverse blue correction, then Rec.709 clipped to [0, 1].
|
|
16
|
-
- **HDR is what Unreal 5.8 does with Filmic on a display past 100 cd/m²:** the SDR picture, taken back to the scene
|
|
17
|
-
through the inverse ACES 2.0 output transform for a 100 cd/m² display, and rendered again with the ACES 2.0 output
|
|
18
|
-
transform for this display's headroom, both limited to Rec.709. The ACES 2.0 part is OpenColorIO 2.6.0's
|
|
19
|
-
implementation, in `chunk_aces2_output_transform`, with three limits on its inverse (D5). Its per-display parameters
|
|
20
|
-
and gamut tables are built on the CPU in `aces2_transform_block.js` and owned by `TonemapAces2Tables`.
|
|
21
|
-
|
|
22
|
-
| | SDR | HDR, 1000 cd/m² | HDR, 2000 cd/m² | HDR, 4000 cd/m² |
|
|
23
|
-
|---|---|---|---|---|
|
|
24
|
-
| exposed 0.18 shows at, × SDR white | 0.180 | 0.257 | 0.287 | 0.313 |
|
|
25
|
-
| white reaches, × SDR white | 1.0 at +6 EV | 4.90 | 9.62 | 18.64 |
|
|
26
|
-
|
|
27
|
-
HDR columns are 1015, 2030 and 4060 cd/m² displays, whose headroom over the 203 cd/m² paper white puts ACES 2.0 at
|
|
28
|
-
500, 1000 and 2000 cd/m². The values are OpenColorIO's.
|
|
29
|
-
|
|
30
|
-
Mid-grey rises with the display's peak. That is ACES 2.0's design, and Unreal 5.8 keeps it: 10 cd/m² at a 100 cd/m²
|
|
31
|
-
display, plus 14% of that for every doubling of the peak. A display with no headroom shows the SDR picture exactly.
|
|
32
|
-
|
|
33
|
-
## 2. Sources
|
|
34
|
-
|
|
35
|
-
- **UE 5.4 documentation:** "Color Grading and the Filmic Tonemapper in Unreal Engine", Epic Games.
|
|
36
|
-
- **UE after 5.4, release notes** (Epic, verified):
|
|
37
|
-
- 5.6 adds ACES 2.0 for HDR as an option.
|
|
38
|
-
- 5.8 adds "support for ACES 2.0 in SDR", moves ACES into a secondary LUT, and drops the HDR UI paper white from 300
|
|
39
|
-
to 203 cd/m² to match BT.2408.
|
|
40
|
-
- 5.8 offers Filmic (default) and Standard ACES tone-mapping methods.
|
|
41
|
-
- **UE 5.8's behaviour, Epic staff** (Yutaro Sawada, forums.unrealengine.com/t/tonemappingmethod/2747218, 2026-07-30,
|
|
42
|
-
in Japanese):
|
|
43
|
-
- ACES 2.0 is now the default HDR version.
|
|
44
|
-
- Filmic, the default, aims at one look on SDR and HDR. Past 100 cd/m² it is "applied, converted back to linear
|
|
45
|
-
through the inverse ACES 2.0, then output again through ACES 2.0 at the target luminance".
|
|
46
|
-
- The scene colour multiplier applies to Standard ACES only.
|
|
47
|
-
- The peak comes from the hardware, and mid and minimum luminance derive from it.
|
|
48
|
-
- **ACES 2.0:**
|
|
49
|
-
- ampas/aces-core, Lib.Academy.OutputTransform.ctl and Lib.Academy.Tonescale.ctl;
|
|
50
|
-
- OpenColorIO v2.6.0, ops/fixedfunction/ACES2/Transform.cpp, FixedFunctionOpCPU.cpp and transforms/builtins/ACES.cpp,
|
|
51
|
-
which this ports.
|
|
52
|
-
- The glue between Filmic and ACES 2.0 (§3, D2) is inferred from the staff description.
|
|
53
|
-
|
|
54
|
-
The state of the art elsewhere, for context:
|
|
55
|
-
|
|
56
|
-
- Godot defaults to Linear, three.js to none, and Unity HDRP to none; Filament defaults to a legacy ACES fit.
|
|
57
|
-
- Blender added ACES 1.3 and 2.0 views in 5.0, with AgX the default since 4.0.
|
|
58
|
-
- No real-time engine but Unreal ships ACES 2.0 by default.
|
|
59
|
-
|
|
60
|
-
## 3. Decisions
|
|
61
|
-
|
|
62
|
-
- **D1. SDR is Filmic exactly, Unreal's pass excepted.** Left out because they are not tone mapping:
|
|
63
|
-
- vignette (UE's default intensity 0.4), grain, sharpening and bloom;
|
|
64
|
-
- the LUT itself: UE interpolates a 32³ table, where this evaluates the function;
|
|
65
|
-
- the LUT's input range: UE's log encoding stops at 43.7, so it shades anything brighter as 43.7. This does not,
|
|
66
|
-
which moves saturated highlights past 43.7 by a few codes.
|
|
67
|
-
- **D2. HDR, the glue I inferred:**
|
|
68
|
-
- The inverse runs at 100 cd/m² with Rec.709 limiting primaries, because Filmic's output is a Rec.709 SDR picture.
|
|
69
|
-
- The forward runs at 100 × the display's peak over the paper white, also limited to Rec.709. So both transforms
|
|
70
|
-
take SDR white for 100 cd/m², and SDR white lands on the paper white, as in every other mode.
|
|
71
|
-
- Rec.709 rather than the canvas's Display P3, because the review measured what P3 does: just past the paper white
|
|
72
|
-
(203.01 cd/m²) saturated colours jumped up to 67 codes from the SDR picture, 213 of 512 by more than 2. Limited
|
|
73
|
-
to the SDR picture's own gamut, the HDR picture becomes the SDR one as the headroom shrinks to none. HDR gives up
|
|
74
|
-
colours outside Rec.709, which the SDR picture never had either.
|
|
75
|
-
- A display at or under the paper white shows the SDR picture, Unreal's "past 100 cd/m²" rule in this engine's
|
|
76
|
-
units.
|
|
77
|
-
- Unreal sets the HDR paper-white level in nits and runs the forward transform at the display's absolute
|
|
78
|
-
peak. If its paper white scales the SDR picture onto the paper white, which its default of 203, BT.2408 and ours
|
|
79
|
-
suggest, the two agree.
|
|
80
|
-
- **D3. No scene colour multiplier.** UE applies its 1.5 to Standard ACES only.
|
|
81
|
-
- **D4. Tables are built on the CPU, per display.** ACES 2.0 needs a reach table, a hue table and a gamut-cusp
|
|
82
|
-
table with an upper-hull gamma per hue. They are found by searches no pixel can afford.
|
|
83
|
-
- Built in 7–13 ms in JS, on the first HDR frame and then on a change of peak, not every frame and not with the
|
|
84
|
-
renderer.
|
|
85
|
-
- Bound as one 15 KB storage buffer: two blocks of 1,893 floats.
|
|
86
|
-
- These are the algorithm's own data, not a grading LUT.
|
|
87
|
-
- **D5. The inverse is limited where OCIO's is ill-conditioned.** Filmic clips channels onto the Rec.709 cube's
|
|
88
|
-
surface, and ACES 2.0's estimate of the gamut boundary lies a hair inside that surface. So a bright tinted highlight,
|
|
89
|
-
SDR (0.981, 1, 1) say, sits on or past the boundary, where OCIO's inverse extrapolates. The review measured it as a
|
|
90
|
-
jump of 0.24 × SDR white between neighbouring scene values on a smooth ramp, which a picture shows as speckle or a
|
|
91
|
-
ring. Three limits, only in the inverse, and only where it is ill-conditioned:
|
|
92
|
-
- colourfulness past the estimated boundary is clamped to it, so it comes back as the reach boundary, which the
|
|
93
|
-
forward transform takes back to the boundary;
|
|
94
|
-
- the decompression's gain is held to 5. Below the last unit of J it stays under 4.9 for 3,000 Filmic outputs, and
|
|
95
|
-
within that unit it reaches 600 to 11,700, which multiplies float32 rounding by as much;
|
|
96
|
-
- over that last unit of J the decompression fades to none, so a near-white colour meets the neutral that white,
|
|
97
|
-
past J_max, is sent to.
|
|
98
|
-
|
|
99
|
-
A colour whose SDR picture is not in that region is untouched and matches OCIO. Two more departures are where OCIO
|
|
100
|
-
gives NaN: the gamut boundary's power takes a base clamped at zero, and an achromatic response at or under zero
|
|
101
|
-
tone-maps to zero, which is where OCIO's `max(0, NaN)` lands it.
|
|
102
|
-
|
|
103
|
-
## 4. Measured
|
|
104
|
-
|
|
105
|
-
**Against the references**, in `shader_tonemap_aces.spec.js`, under the WGSL emulator, in Display P3:
|
|
106
|
-
|
|
107
|
-
- SDR is within the dither of a float64 Python reference of the Filmic curve and grading pipeline, at 29 colours from black to 1000.
|
|
108
|
-
- HDR is within 1e-4 (encoded) of OpenColorIO 2.6.0 at four display peaks: its builtin inverse at 100 cd/m², then its
|
|
109
|
-
ACES 2.0 fixed function limited to Rec.709 between its builtins' clamps.
|
|
110
|
-
- The exceptions are three bright saturated colours whose SDR picture is near white, where D5's limits apply, and
|
|
111
|
-
a grey at +4 EV on 8120 cd/m², where OCIO's float32 inverse drifts by more than that. These four are held to a
|
|
112
|
-
float64 Python port with the same limits instead.
|
|
113
|
-
- A tinted highlight's HDR picture moves continuously: the largest step along a ramp shrinks when the interval is cut
|
|
114
|
-
ten times finer. Without D5's clamp and gain limit it does not, nor with P3 limiting at 203.01 cd/m² is an HDR
|
|
115
|
-
display within 2 codes of SDR; both tests failed before the change.
|
|
116
|
-
- Mutation testing: 110 mutants by an adversarial reviewer, then the survivors and every limit in D5 again. Three
|
|
117
|
-
survive, each unreachable or under the tolerance (the inverse's nd ≥ 1 branch, which the clamp makes unreachable;
|
|
118
|
-
the gain ≥ 1 escape; a looser cusp-search tolerance).
|
|
119
|
-
- The JS table builder matches a float64 Python port to 6e-8.
|
|
120
|
-
- That Python port matches OCIO's builtins to 2e-5, forward and inverse, at 100, 500 and 1000 cd/m².
|
|
121
|
-
|
|
122
|
-
**On the device** (RTX 4090, headless Chrome, volumetrics_froxel, 1258×646, exposure fixed). OKLab lightness and HSV
|
|
123
|
-
saturation are on the displayed frame:
|
|
124
|
-
|
|
125
|
-
| view | mode | L mean | L p05 | L p95 | saturation |
|
|
126
|
-
|---|---|---|---|---|---|
|
|
127
|
-
| overview | ACES | 0.434 | 0.141 | 0.655 | 0.323 |
|
|
128
|
-
| overview | GT7 | 0.445 | 0.180 | 0.635 | 0.281 |
|
|
129
|
-
| broadside | ACES | 0.512 | 0.224 | 0.868 | 0.263 |
|
|
130
|
-
| broadside | GT7 | 0.522 | 0.258 | 0.891 | 0.241 |
|
|
131
|
-
| volume edge | ACES | 0.472 | 0.256 | 0.725 | 0.342 |
|
|
132
|
-
| volume edge | GT7 | 0.480 | 0.288 | 0.699 | 0.300 |
|
|
133
|
-
|
|
134
|
-
- Mid-grey is where GT7 puts it, 0.18 in and 0.18 out.
|
|
135
|
-
- The mid-tone contrast is 1.36 against GT7's 1.05. Shadows sit deeper and highlights brighter, and saturation rises
|
|
136
|
-
12–15%.
|
|
137
|
-
|
|
138
|
-
The pass's cost, three interleaved rounds of 24 frames:
|
|
139
|
-
|
|
140
|
-
| pass | ACES | GT7 |
|
|
141
|
-
|---|---|---|
|
|
142
|
-
| SDR | 12–13 µs | 14–21 µs |
|
|
143
|
-
| HDR | 55–102 µs | 16–33 µs |
|
|
144
|
-
|
|
145
|
-
The HDR pass was forced through `matchMedia` and ran on the device with no validation errors and no non-finite output.
|
|
146
|
-
It has not been looked at on an HDR display.
|
|
147
|
-
|
|
148
|
-
## 5. Open, measured
|
|
149
|
-
|
|
150
|
-
- **The HDR pass costs three to four times GT7's**: two ACES 2.0 transforms a pixel, 55–102 µs at 0.8 MP on a 4090.
|
|
151
|
-
Unreal pays this once into a LUT, which was ruled out here.
|
|
152
|
-
- **The glue to Filmic is inferred** (D2, D5). Unreal evaluates this composite into a LUT, whose interpolation smooths
|
|
153
|
-
what D5 limits.
|