Shadow Simulation Addon - #779
Conversation
mxfh
commented
Aug 28, 2026
|
View your CI Pipeline Execution ↗ for commit 99befce
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
🚀 Deployed
|
🚀 Deployed
|
🚀 Deployed
|
🚀 Deployed
|
🚀 Deployed
|
🚀 Deployed
|
9cb5734 to
d21ee9c
Compare
🚀 Deployed
|
🚀 Deployed
|
🚀 Deployed
|
🚀 Deployed
|
🚀 Deployed
|
ec04799 to
8c4cce0
Compare
🚀 Deployed
|
Adds the tiled shadow controller with its light-space tile layout, the Takram-based atmospheric sunlight evaluator (transmittance and sky irradiance LUTs), and the shadow projection diagnostics that visualize the fitted buffers.
The frame cost had crept from ~16 ms to over 500 ms while the camera stood still. Four causes, all fixed: - The terrain LoD selection ran on every rendered frame, and each run cost hundreds of milliseconds: the orthographic shadow screen-space error recomputed a full matrix chain per candidate tile, including an updateMatrixWorld over every loaded terrain mesh. The pixels-per-meter term is tile-independent and is now computed once per selection, the refinement loop uses a binary heap instead of re-sorting per split, and the whole selection only runs when a quantized camera/shadow-fit signature changes. It is frozen entirely while a gesture is in flight and re-selects on moveend. - The fitted shadow cameras were handed to the terrain on every dirty update, and the terrain fed elevations back into the next fit, so the two re-triggered each other; the hand-over now waits for the camera to rest and is quantized to a couple of metres so it converges. - Every move event re-sampled the atmosphere and rebuilt the sun gizmo geometry; both now happen on moveend and time changes only, while a pan just carries the anchor along. Content changes coming from streaming tiles are debounced, materials re-sync only after content changes, and the runtime no longer schedules an extra repaint from inside its own render frame. - Projection debug snapshots published (and re-rendered the debug panel) even while nothing listened; publishing is now gated on subscribers and throttled to 10 Hz during gestures. While a camera gesture is in flight the shadow buffer drops to half resolution and the moveend update restores it. The shadow terrain's shadowLevelOffset returns to 3: offset 0 demands full-resolution terrain across the whole shadow frustum and was the single biggest regression against the fast state. The camera restriction addon now allows tilting to 5 degrees above the horizon (maxPitch 85) whenever it decides the camera is free, and the shadow scene waits for the map style before building, so enabling the simulation from a URL or persisted state no longer crashes the app on a slow style load. Dev builds expose the map and per-phase frame timings for console profiling.
Which ground has to stay loaded for shadows is a question about the view and the sun. Deriving it from the fitted render camera - as before - coupled the tile selection to a camera that follows every fit, empties on a failed fit and rests during gestures, which unloaded sun-side tiles while their shadows were on screen: the bright hole in the middle of a dusk view. The coverage is now one analytic orthographic volume, the light-space box of the visible extent swept toward the sun by the caster reach, handed to the terrain runtime whenever view or sun move. A relief margin in the reach accounts for ridges beyond the viewport that still shade into it, in the render fit as well as in the coverage. Shadow resolution: screen corners near the horizon unproject kilometres away and used to stretch the single buffer across the whole valley. The receiver extent is now clamped to a 4 km radius around the view anchor, which keeps the texels where the viewer looks. The shading itself sheds its per-texel bias battery. Acne is answered where it comes from: buildings render their far walls into the depth map (shadowSide BackSide), which also removes the bright leak line along their bases, and terrain gets a slope-scaled polygon offset in its own depth pass. One small constant normal bias and a one-texel filter radius remain. Shadow intensity defaults to 100 percent. A gesture no longer coarsens streamed building tiles: the progressive error target only tightens after load, so detail already on screen stays through a pan.
Three ways the ground between the view and the sun could still go missing, all closed: - The sweep took its vertical extent and caster reach from the elevation range of the tiles currently visible. That range grows as tiles stream in, the sweep reshaped, and every reshape superseded the tile batch that was still loading - at low sun the up-sun tiles never settled. The sweep now spans a fixed regional elevation band (0-500 m over Wuppertal) and is a pure function of view and sun; its box is snapped to a 50 m grid on top. Superseded batches keep their fetched tiles as hidden meshes, so the selection that follows activates them instead of re-fetching. - The visible relief also underestimated the reach itself: the ridges that shade a valley at dusk are usually not in the viewport. The relief margin rises to 300 m of regional headroom, in the coverage and in the render fit's far plane alike; at 10 degrees of sun that is roughly a 5 km sweep. - Refining an available parent whose child quadrant has no data swapped real coarse ground for a sea-level plate - a hole in the view, and up-sun a hole in the shadow. Such parents now stay whole; refinement ends at the availability boundary. Dev builds expose the sweep as window.__carmaShadowCoverage.
releaseTilesOutsideCameraUnion unmarked the REPLACE parents that 3d-tiles-renderer keeps as anti-flicker retention when all of their children lie outside the frustum. Once evicted, an external tileset stub loses its children, is traversed as in-frustum again on the next frame and is requested anew: about 60 tileset.json fetches per second on the Mesh 2024 layer while the camera stands still. The LRU cache already evicts unused content by itself, so the release goes.
…ls draped The point-label overlay rewrote the coverage filter of every point symbol layer on each idle and styledata event: 179 layers with a 677-polygon `within` filter, ~32 ms per setFilter, and each write forced a repaint whose idle landed in the same listener again. A single pass blocked the main thread for up to 8 s and the terrain drape never settled. - shared-three-scene-registry: rate limit the label overlay maintenance to once per second (trailing), cache the point-label layer list by layer order instead of serializing the style per event, union adjacent tile boxes before building the coverage polygon, skip untouched filters by reference and write filters without style-spec validation. User toggles stay immediate. - map-style-layer-suppression: only a literal or omitted `symbol-placement` counts as point-anchored; basemap.de street names with a zoom function stay in the draped style. - map-controls-layout: swap a re-rendered control in place instead of a remove/add pair and memoize the layout context. - geoportal main: reuse the React root across hot updates that bubble up to the export-less entry, which previously created a second root and broke the old tree's teardown. - shadow-simulation: point-label overlay toggle (Beschriftungen) from the quick settings.
Back-face casting left a lit gap under LOD2 solids that do not sit flush on the terrain. Closed building solids now write both faces into the shadow map, so the sun-facing wall shadows the ground beneath and behind the building. Open surfaces keep front-face casting; the per-texel normal bias covers the lit faces that are now part of the shadow map.
…errain retries Textured mesh tilesets that provide the terrain now take a label-only drape: the basemap pass below Three keeps only symbol layers and contour lines, the capture is composited over the mesh texture by alpha instead of replacing it, and MapLibre's DEM depth of the same frame occludes labels behind roofs and facades. Label color is applied after lighting so white street names stay white in the sun and darken in shadow. Registry label rules for the mesh drape: street names white with a grey halo, enlarged and crisp; point labels (places, POIs, house numbers, stations) in the sun color with a grey halo; water names blue without halo; elevation labels sun colored without halo; road shields untouched in every mode. Sprites are lit by the sun color (albedo times light). Place names float by prominence (150 to 600 m), other point labels 10 m, as a per-frame viewport translate. Near-white halos count as white for the default halo tint. Terrain requests never stay stale: Cesium terrain tiles and the provider bootstrap retry with backoff until a confirmed 4xx, a partly failed selection is asked for again, and the MapLibre DEM source fetches through a retrying protocol. Shadow scene: double-sided casting for closed solids, label drape mode chosen from the shared scene's terrain provider, overlay label toggle.
The label drape (hide fills, strokes and rasters below Three, keep symbols and contour lines at half opacity) moves from the shadow scene into the shared scene registry and switches on whenever a textured mesh supplies the terrain, so a loaded mesh looks the same with the shadow simulation off: draped white street names, lifted point labels, no basemap fills. Without a sun color nothing is tinted: point labels and sprites keep their authored paint, elevation labels are white. The shadow scene keeps the opaque basemap drape for bare terrain and only hands its changes back in labels mode.
de58ac6 to
751cbb0
Compare
🚀 Deployed
|
🚀 Deployed
|