Class GlobalIllumination

java.lang.Object
eu.svjatoslav.aukio.e3d.renderer.raster.gi.GlobalIllumination
All Implemented Interfaces:
GiLightProvider

public class GlobalIllumination extends Object implements GiLightProvider
CPU global illumination baked in strict bounce generations, running on dedicated low-priority threads (never on the render ForkJoinPool).

Generations: the scene is lit pass by pass, each walking all snapshot entries near-to-far from the camera, with a global barrier between passes:

  1. Stage 1 (generation 0) — uniform centroid direct: every entry gets one centroid evaluation against every tracked lamp. Plain polygons and mesh-block triangles record it as their final-resolution direct state; lightmapped triangles display it immediately through the per-triangle GI shade stamped on the triangle (no texel work — the painters multiply texels by the triplet until the first real composite lands). The whole scene shows per-polygon direct light with shadows within the first seconds.
  2. Generation 1 — per-texel direct: every lightmap texel casts a shadow ray toward every light; the outcome is cached in the texel's visibility bytes. Direct light is always derived from the cached visibility, never stored separately.
  3. Generation k >= 2 — one more bounce: every texel casts e3d.gi.samples cosine-weighted bounce rays and averages them (a plain running mean — within a generation the sampled field is frozen by the barrier, so the samples are independent and no EMA is needed). Generation 2 gathers the hit point's direct light; generation k >= 3 gathers the hit surface's generation-(k-1) delta only, so each generation adds exactly one more light bounce to the solution.

Termination: the bake stops when a generation's mean delta falls below e3d.gi.deltaThreshold (later generations only shrink — the 1/pi diffuse gain keeps the loop gain below one) or when e3d.gi.generations generations have run (default 6 = direct + up to 5 bounce generations). The uncaptured series tail is the price of the hard stop; raise the knob for bright interiors.

Storage lifetime: per-texel arrays (indirect total, two delta buffers, visibility) are allocated lazily when the bake reaches a lightmap and freed for the whole scene when the bake completes — a finished lightmap keeps only its final composite texture. Plain polygons keep their handful of per-polygon floats (render threads read them every frame through GiLightProvider). A scene or light change rebuilds the snapshot and restarts the bake from stage 1; the old textures stay on screen until the new direct pass lands (a lightmap that has already shown traced lighting keeps it — no uniform preview on rebake).

Two sampling resolutions:

  • Lightmapped triangles (LightmappedShape, e.g. the wrapped polygons of a lightmapping-enabled LightmappedCompositeShape): per-texel sampling. Shadows and gradients live INSIDE the polygon surface; the painted texture is the premultiplied composite (baseColor x ambient+direct+indirect), regenerated on GI threads every 500 ms during the bake and swapped in double-buffered — painters never see a half-updated texture.
  • Plain solid polygons: per-polygon sampling; the result feeds the flat-shading path through GiLightProvider (shadow tests + indirect add). Polygons stay single-colored.
  • Mesh blocks (TriangleMeshBlock, lean flat-array game geometry): per-triangle sampling like plain polygons. Light totals (direct + indirect) are written straight into the block's giLight array at each generation barrier; the paint path multiplies texels by them.

Render-side cost: zero ray casting. Lightmapped triangles paint from their current composite texture; the flat-shading path only reads cached per-polygon values. Frame rate is unaffected by GI quality.

Limitations: diffuse light only; polygon vertices are used in composite-local space, so scenes combining composites with non-identity transforms are traced incorrectly.

Radius gating (e3d.giStage2Radius/ giStage3Radius/ giStage4Radius in ~/.config/aukio/config.yaml, or the corresponding -De3d.gi.stage*Radius properties): in huge streaming worlds the full-scene stages would never reach the camera area, so each stage past 1 can be limited to a distance bubble around the camera. Entries beyond the stage-2 radius keep their stage-1 direct stamp; lightmapped triangles beyond the stage-3 radius keep the stage-2 display and never allocate texels; lightmaps beyond the stage-4 radius are never refined to full density. Eligibility is re-evaluated from the LIVE camera position at every generation barrier, so the bubble follows the camera; entries that become eligible after the bake converged get a local catch-up (one bounce against the frozen converged field). Stage 1 stays global on purpose: bounce rays read the hit surface's stage-1 visibility bits everywhere. Mesh-block triangles additionally gain per-texel lightmaps inside the stage-3 bubble (they are the only geometry kind in streaming game worlds); their composites carry the block's photo texture resampled into lightmap space, and finished composites are kept in a geometry-keyed LightmapCache so a cell that streams out and back in restores its lightmaps instantly. A stage-3 radius of 0 disables mesh-block lightmapping entirely (the historical behavior).

Usage:

GlobalIllumination gi = viewPanel.enableGlobalIllumination(); // 2 threads
  • Constructor Details

    • GlobalIllumination

      public GlobalIllumination(ShapeCollection shapes, LightingManager lightingManager, int threadCount)
      Creates the GI system. Call start() to begin tracing. Distance ordering is disabled (uniform order).
      Parameters:
      shapes - the scene to trace
      lightingManager - the lights to sample
      threadCount - dedicated worker threads (2 is a good default)
    • GlobalIllumination

      public GlobalIllumination(ShapeCollection shapes, LightingManager lightingManager, int threadCount, Supplier<Point3D> cameraPosition)
      Creates the GI system. Call start() to begin tracing.
      Parameters:
      shapes - the scene to trace
      lightingManager - the lights to sample
      threadCount - dedicated worker threads (2 is a good default)
      cameraPosition - supplies the camera position for the near-to-far bake order; null disables distance ordering (uniform order)
  • Method Details

    • lightmapUnitsPerTexel

      public static double lightmapUnitsPerTexel(double base)
      Units-per-texel that lightmap construction should use while the pipeline starts coarse (stage 3): the configured value scaled by STAGE3_TEXEL_FACTOR. Callers pass the fine value when constructing at final resolution (fine phase, or late entrants after the bake). Read at LightmappedTriangle construction time — changing it takes effect on the next render-list rebuild.
      Parameters:
      base - the composite's configured units per texel
      Returns:
      the effective units per texel for new lightmaps
    • getStatus

      public GlobalIllumination.Status getStatus()
      Reads a consistent-enough status snapshot for display; safe to call from any thread (UI polls it a few times a second).
      Returns:
      the current GI status
    • setRadiusOverrides

      public void setRadiusOverrides(Double stage2, Double stage3, Double stage4)
      Overrides the radius-gating config values for THIS GI system only (EngineConfig giStage2/3/4Radius untouched). Needed when one JVM hosts scenes of wildly different scales (the demo launcher: an FO4 cell at 1 unit = 1.43 cm vs a 48 000-unit procedural district) — a global -D/config.yaml value cannot fit both. Pass null to keep the configured value for a stage.

      Call BEFORE start() (or via the ViewPanel enable-overload that applies it pre-start) so the first eligibility evaluation already uses the overrides.

    • setMeshUnitsPerTexelOverride

      public void setMeshUnitsPerTexelOverride(Double unitsPerTexel)
      Overrides the mesh-block lightmap texel density for THIS GI system (EngineConfig giMeshUnitsPerTexel untouched). Call before start(); see setRadiusOverrides(Double, Double, Double).
    • start

      public void start()
      Registers the GI provider and starts the worker threads.
    • stop

      public void stop()
      Stops the worker threads and unregisters the provider.
    • isRunning

      public boolean isRunning()
      Returns whether the GI worker threads are running.
      Returns:
      true after start() and before stop()
    • isConverged

      public boolean isConverged()
      Returns whether the bake has completed: all generations traced (or the delta threshold reached) and the per-texel arrays freed. The workers then idle at a low cadence, watching for scene changes.
      Returns:
      true when baked
    • getWorkItemCount

      public int getWorkItemCount()
      Returns the number of traceable entries (triangles) in the current scene snapshot. 0 when no snapshot has been built yet.
      Returns:
      the entry count
    • isLightVisible

      public boolean isLightVisible(SolidPolygon polygon, LightSource light)
      Description copied from interface: GiLightProvider
      Tells whether a light source currently has an unobstructed path to the polygon. Until the provider has computed the answer, it should return true (light visible): shadows then fade in gradually instead of popping out.
      Specified by:
      isLightVisible in interface GiLightProvider
      Parameters:
      polygon - the shaded polygon
      light - the light source being evaluated
      Returns:
      true if the light reaches the polygon
    • addIndirectLight

      public void addIndirectLight(SolidPolygon polygon, Color baseColor, Color result)
      Description copied from interface: GiLightProvider
      Adds the polygon's indirect (bounced light) contribution to an already computed direct-lighting color, in place.
      Specified by:
      addIndirectLight in interface GiLightProvider
      Parameters:
      polygon - the shaded polygon
      baseColor - the polygon's unlit color (for albedo scaling)
      result - the direct-lighted color to augment (modified in place)