-
Notifications
You must be signed in to change notification settings - Fork 3
Description
Context
Roadmap Phase 2 item (see #57). Current rendering uses a single resolution for the entire terrain and full-detail geometry at all distances. This limits scene scale — large DEMs either run out of GPU memory or render slowly. LOD enables city-scale and regional scenes by reducing detail where the camera can't perceive it.
Goal
Three LOD systems that work together:
- Terrain LOD — distance-based subsampling of terrain tiles
- Instanced Geometry LOD — simplified meshes and billboard imposters for far objects
- Hybrid Cluster GAS — cluster-accelerated GAS for nearby geometry, simplified GAS for distant
1. Distance-Based Terrain LOD
Current State
_rebuild_at_resolution() applies a uniform subsample factor across the entire terrain. The chunk manager (_MeshChunkManager) loads/unloads spatial chunks but all at the same resolution.
Design
- Divide terrain into tiles (reuse chunk grid or define LOD-specific tile size)
- Assign LOD level per tile based on distance from camera to tile center
- LOD 0 = full resolution, LOD 1 = 2x subsample, LOD 2 = 4x, etc.
- Each LOD level is a pre-built or on-demand
triangulate_terrain()at reduced resolution - Tile boundaries need skirt geometry or stitching to hide T-junction cracks between LOD levels (skirt approach already exists in codebase)
- Update LOD assignments each frame (or on camera move beyond threshold)
Key Considerations
- Chunk manager cache keyed by
(chunk_id, lod_level)— existing cache invalidation logic applies - Z re-snapping for placed geometry must use the LOD-appropriate terrain (see MEMORY.md gotcha)
- OptiX IAS instance transforms can reference different GAS per LOD tile
2. Instanced Geometry LOD
Current State
place_mesh() creates instanced geometry via IAS instance transforms pointing to shared GAS. All instances render the full mesh regardless of distance.
Design
Mesh LOD Levels
- LOD 0: original mesh (full detail)
- LOD 1: simplified mesh (e.g., 50% triangle reduction via quadric decimation)
- LOD 2: low-poly proxy (e.g., bounding box or convex hull)
- LOD 3: billboard imposter (camera-facing quad with pre-rendered texture)
Billboard Imposters
- Pre-render each mesh from 8–16 azimuth angles × 2–3 elevation angles into a texture atlas
- At runtime, select the closest viewing angle and render a textured quad
- Imposters use a separate SBT hit group with alpha-tested texture lookup
- Transition distance: where screen-space size of object drops below ~16px
Runtime Selection
- Per-instance LOD selection based on distance to camera
- IAS rebuild when LOD assignments change (batch updates, not per-frame)
- Hysteresis band to prevent LOD flickering at boundaries
3. Hybrid Cluster GAS
Current State
Cluster GAS support exists (see MEMORY.md — OptiX 9.1 upgrade notes). Terrain can be partitioned into cluster blocks. Currently used uniformly.
Design
- Near range (< threshold): cluster GAS — full BVH quality, best ray traversal for detailed close-up geometry
- Far range (> threshold): simplified GAS — fewer triangles, standard (non-cluster) build for faster BVH construction
- Threshold based on screen-space projected size or fixed distance bands
- Both GAS types coexist in the same IAS with per-instance transforms
- Cluster GAS blocks can double as terrain LOD 0 tiles
Transition Strategy
- Hybrid boundary aligned with terrain LOD tile grid
- Cross-fade or dither at transition boundary to hide pops (optional, may not be needed for terrain)
- Rebuild schedule: cluster GAS is expensive to build, so rebuild only when camera moves significantly (>1 tile width)
Implementation Plan
- Terrain tile grid and LOD assignment — camera-distance metric, LOD level calculation, tile data structure
- Multi-resolution terrain GAS — build separate GAS per (tile, LOD), wire into IAS
- Tile boundary stitching — extend existing skirt approach or add edge-matched vertices
- Mesh LOD pipeline — mesh simplification (quadric decimation), generate LOD chain per mesh source
- Billboard imposter generation — offline pre-render pass, texture atlas, alpha-test hit group
- Per-instance LOD selection — distance bucketing, IAS instance LOD assignment, batch rebuild
- Hybrid cluster/standard GAS — distance-based GAS type selection, mixed IAS construction
- LOD budget system — global triangle/instance budget, auto-adjust LOD thresholds to hit target frame time
- Cache management — LOD-aware chunk cache, memory pressure eviction (farthest tiles first)
Scope Boundaries
- No streaming from disk/network (LOD data is generated in-memory from source meshes/terrain)
- No continuous LOD (CLOD) or geometry clipmaps — discrete LOD levels are sufficient
- Billboard imposters are stretch goal — mesh LOD levels come first
- No per-frame IAS rebuild; batch on camera movement threshold
References
- Chunk manager:
engine.py(_MeshChunkManager) - Terrain meshing:
mesh.py(triangulate_terrain) - Cluster GAS:
rtx.pycluster accel APIs, MEMORY.md notes - Skirt geometry: recent commit
9b30627 - Resolution rebuild:
engine.py(_rebuild_at_resolution())