Ground Fog
Place terrain-relative mist banks, bake collision height, and tune density, motion, and fade
Ground Fog
Agentic Atmos adds terrain-relative mist inside placed banks using Unreal Engine 5.8 native volumetric fog. Each bank bakes the collision height beneath its footprint into a texture it owns. It does not create ground or add a second fog system.

Rendered with Agentic Atmos in the plugin showcase level, with graphic overlays. Demonstration scenery is not all included. This is a rendered example, not an editor screenshot.
Assigning a day cycle does not place or bake ground-fog banks. A bank needs selected ground collision, a valid saved bake, a resolved renderer, and native Volumetric Fog enabled. An unresolved renderer means off, not a substitute renderer.
Ownership and prerequisites
- Look preset → Ground Fog modifier.
UAtmosLookPreset.GroundFogholds an instancedUAtmosGroundFogModifier. Its typedGroundFogsettings describe the medium;RendererTagselects the content. Author here — not in a composed segment field, and not on a bank's transient material instance. - Day cycle → Render Profile → renderer. The active
UAgenticDayCycle.RenderProfilemounts aUAtmosRenderProfile. ItsRenderersmap must containAtmos.Renderer.GroundFog.Native→/AgenticAtmosphere/Core/Renderers/DA_Renderer_GroundFog_Native(aUAtmosGroundFogRenderer). Preserve the profile's other renderer entries. - Sky actor.
AAtmosSkyActorpublishes the composed fog settings and the resolved world wind. The active Fog group must be asserted, its height-fog component visible, and Volumetric Fog enabled. - Placed bank.
AAtmosGroundFogBankowns spatial inclusion, the explicitly selected ground, exclusions, the saved terrain bake, and its rendering surfaces. It does not create a landscape or guess which floors in your level count as ground.
Add ground fog to an existing level
- Work on owned assets. Duplicate the relevant cycle, profile and look if the originals must stay unchanged, then assign your cycle to the existing sky actor. Do not add a second lighting authority just to get fog.
- Mount the native renderer in the cycle's profile under
Atmos.Renderer.GroundFog.Native. The renderer supplies/AgenticAtmosphere/Materials/GroundFog/M_Atmos_GroundFogandNoiseVolume→/AgenticAtmosphere/Textures/GroundFog/VT_Atmos_GroundFogNoise. Keep its binding rows andTerrainTextureParameter = TerrainHeight; the bank supplies that texture per instance. The production material needsUseTerrainHeighton. - Author the look's Ground Fog slot. Add a Ground Fog modifier, set its renderer tag,
enable its typed settings, and start from one of the library looks below. Keep the look's
existing lighting, exposure and Fog settings, and make sure native Volumetric Fog is enabled
in the composed Fog modifier.
- A ground-fog-only library look can be a slot's
LookPresetover a suitable cycleBaseLook— its absent lighting categories inherit that base. - When a slot already has its own lighting look, fork that look and copy the library's Ground Fog settings into a new modifier on the fork. Replacing a full look with a fog-only layer would lose that slot's lighting assertions.
- A ground-fog-only library look can be a slot's
- Place an
AtmosGroundFogBankactor. Leave rotation zero and scale(1,1,1). Put its XY centre over the intended patch and size it withFootprintSize(full width/length in cm), not actor scale. SetAtmosphereSourceOverrideto the intended sky actor for explicit ownership; if unset it uses the registered sky source.bEnabledis a bank-local switch, and optionalActiveAtmosTagsrestrict it to matching composed atmosphere/segment tags (any match; empty means unrestricted). - Populate
GroundActorsdeliberately. Add unique, loaded actors in this world with registered, query-enabled Landscape or static-mesh collision. Select each Landscape streaming proxy explicitly. Do not select the bank, roofs, props or bridges unless their upper collision surface really is the ground you want. - Set bake bounds. Use a power-of-two
TerrainResolutionfrom 8 to 256 (64 default). SetTerrainTraceTopZabove andTerrainTraceBottomZbelow the selected collision — these are absolute world-Z centimetres, not offsets from the bank. Keep the range tight.bTerrainTraceComplexselects the trace mode (default true). - Add exclusions before baking. Each enabled
TerrainExclusionsentry is an axis-aligned world-XY rectangle with aCenterand half extents in cm — use them for clear paths or holes around obstacles. The bake conservatively excludes intersecting texels, including rectangles narrower than one texel.TerrainEdgeFadeCmandTerrainExclusionFadeCmsoften the baked support boundaries. - Click Bake Terrain in the bank's details panel (
BakeTerrain, orbake_terrain()from Python). It runs only on a placed actor in an editor world — never in PIE or a cooked game. Require success,HasValidTerrainBake() == true,IsTerrainBakeStale() == false, and a nonzeroBakedValidTexels. ReadTerrainBakeStatusfor refusal details. - Save the bank's level or external-actor package, plus the edited look, profile and cycle. The bake marks its package dirty; clicking Bake is not a save. Use the sky actor's Refresh Now and the bank's Refresh Ground Fog after repairs. Reopen the saved scene and confirm the bake is still valid before relying on it.
- Check a controlled on/off pair. Hold camera, time, lighting, exposure and existing fog
constant and toggle only the bank's
bEnabled. Let the volumetric temporal history settle. Bank-off does not mean all atmospheric fog is off.
Terrain data and rebaking
Bake Terrain traces the selected collision components downward at texel centres, and the highest accepted hit wins at each XY sample. Unselected props cannot block the trace or become ground. No hit means no fog — there is no fabricated flat plane.
TerrainHeightTexture is a bank-owned texture sub-object saved in the bank's package (including
external-actor packages). It encodes:
| Channel / metadata | Meaning |
|---|---|
| R | normalized collision height; decode with BakedHeightMin + R * BakedHeightRange |
| G | validity — missing collision and excluded texels contribute no fog |
| B | baked edge/exclusion fade; the native material multiplies G × B |
| Bank metadata | world-XY origin and size, resolution, valid count, configuration signature, source snapshots |
The texture is linear half-float data, nearest-sampled, clamped, without mips, and marked non-streaming. Runtime consumes the saved data; a refresh does not trace, rebake or write assets.
Rebake explicitly after moving a bank, changing footprint / resolution / trace limits, changing the collision mode or baked fades, editing the ground selection or exclusions, or changing the selected terrain itself. Staleness checks cover component transforms, mesh identity and collision revision, instance transforms, Landscape collision revision and the editor heightmap source revision. Confirmed stale terrain reports "Explicitly Bake Terrain again."
Use IsTerrainBakeStale() / HasValidTerrainBake() as the live check, not the last successful
status string. Disabled, invalid or stale banks fail closed when refreshed. A failed bake
invalidates the bank: read TerrainBakeStatus and the LogAtmosGroundFogTerrain output, fix the
selection, bounds or collision, then bake and save again. Density, colour, fade and wind edits do
not need a rebake; bank-side baked fades do.
Tune the look
Distances are centimetres unless stated otherwise (100 cm = 1 m). The native material takes extinction in inverse metres — do not divide it by 100 yourself.
| Control | Designer meaning |
|---|---|
bEnabled, Strength, ExtinctionPerMetre | explicit on/off and optical density; Strength multiplies extinction once. Zero effective extinction or zero Coverage produces no active medium. |
TerrainBaseOffsetCm, LayerDepthCm, VerticalFalloff | offset from baked ground, layer thickness, vertical falloff exponent. Higher falloff holds density closer to the floor; the layer is bounded below as well as above. |
Coverage, Breakup | coverage threshold and its softness. Smaller Breakup makes harder banks; larger values soften the threshold. These are 0–1 controls, not percentages. |
MacroNoiseScaleCm, DetailNoiseScaleCm, DetailAmount, NoiseContrast | world size of banks and wisps, detail perturbation, contrast around the noise midpoint. |
ScatteringTint | scattering albedo, clamped 0–1. Not emission, and not a replacement for sun/moon lighting. Directional scattering uses the Fog group's shared distribution. |
EdgeFadeCm | live material feather at the bank's volume bounds; distinct from the baked terrain-edge and exclusion fades. |
NearFadeStartCm / NearFadeEndCm | camera-distance interval opening from no fog to full fog. Keep end greater than start. |
FarFadeStartCm / FarFadeEndCm | camera-distance interval closing from full fog to none. Keep end greater than start. |
WindFollow, DriftDirection, DriftSpeedCmPerSecond, AnimationRate | fraction of resolved world wind to follow, plus a normalized world-space authored direction × drift speed; animation rate scales the resulting velocity. Zero direction removes authored drift; zero rate freezes it. |
SignalChangeEpsilon | advanced relative numeric tolerance for re-deriving signals. Not a quality or frame-rate budget — leave it alone. |
A missing modifier does not disable fog. No Ground Fog modifier means "inherit", and a lower layer may still
assert fog. To author a genuinely clear interval, use a present modifier with
GroundFog.bEnabled = false. The bank's own switch disables only that placed bank.
Advection uses ordinary world GameTime — the same clock as material Time in the level editor,
scene captures and PIE — not the authored solar time-of-day clock. Pausing the day cycle is not a
request to freeze the mist. Velocity changes preserve accumulated displacement through a rebased
epoch, so the fog never pops when the wind changes. Renderer replacements crossfade over
CrossfadeSeconds (default 1 s, bounded 0.01–10 s) across at most two bank surfaces.
Start from a library look
Three ground-fog-only UAtmosLookPreset assets live under
/AgenticAtmosphere/Looks/Library/GroundFog/. None of them author lighting, exposure or
emission — they are starting points, not a promise that the same values suit every camera and
landscape.
| Asset | Intended for | Extinction /m | Coverage | Depth cm | Drift cm/s | WindFollow |
|---|---|---|---|---|---|---|
LP_GroundMist_Dawn | taller translucent dawn pockets | 0.08 | 0.32 | 500 | 8 | 0.15 |
LP_GroundMist_Backlit | taller translucent backlit banks | 0.06 | 0.32 | 600 | 14 | 0.25 |
LP_GroundMist_Moonlit | taller translucent moonlit pockets | 0.07 | 0.36 | 450 | 5 | 0.10 |
Dawn's near fade is 600–2400 cm; Backlit and Moonlit use 0–300 cm. All three use far fade
15000–20000 cm, Strength 1, base offset 0, AnimationRate 1 and EdgeFade 800 cm. Macro scales are
3200 / 3400 / 5600 cm, detail scales 220 / 240 / 200 cm, breakup 0.45 / 0.45 / 0.70, detail
amount 0.55, vertical falloff 1.0, and noise contrast 1 / 1 / 0.8. Scattering is neutral
(1, 1, 1, 1) in all three: native volumetric lighting supplies the sun, moon and skylight
colour once, so do not multiply an ambient colour into this lit volume's albedo.
Base offset stays at 0 on purpose — depth increases above the terrain rather than lifting the fog into a floating slab.
The shared renderer owns the content and the binding rows; each look owns its modifier and each bank owns its terrain. Do not rewrite the shared renderer, or tune a bank's transient material instances, to adjust one look.
Requirements and limitations
- Targets UE 5.8 and its native volumetric-fog path. It needs usable volumetric-fog render/scalability settings, the shipped material and volume texture, and selected collision. A bank uses an engine cube volume host, not a Niagara effect.
- The noise is generated by Agentic Atmos, deterministic and periodic — a 64³ RGB volume, generated procedurally with no third-party pixel input. The native graph samples the macro channel and the detail channel with two volume taps. It is periodic, not mathematically non-repeating.
- Landscape: registered, query-enabled heightfield collision is supported, including explicitly selected loaded proxies. Render appearance alone is not collision. Missing or unloaded selected sources make the bake stale, and soft references do not force World Partition cells to load.
- Static meshes, including Nanite-rendered meshes: supported through
UStaticMeshComponentcollision, including tracked instanced-mesh transforms. A Nanite flag is not a source of traceable triangles — the collision representation supplied to the trace is what counts, so verify it agrees with the visible ground. Render-only displacement does not become baked height. - This is one highest collision height per XY texel, not a 3D terrain volume. Overhangs, caves and stacked walkable floors cannot all be represented at one XY: select ground carefully, partition banks, or exclude the unwanted regions. Finite resolution and conservative safety fades can remove narrow strips — inspect the baked support rather than raising extinction to hide holes.
- Skeletal, procedural and dynamic-mesh components are not accepted ground types. Runtime deforming or moving terrain is not followed or rebaked automatically, and even supported components go stale when their tracked geometry or transform changes. There is no runtime collision tracing and no generated fallback ground.
Cost
Measured on an RTX 4080 SUPER at 1920×1080, screen percentage 100, with running world time and a moving camera. Five samples per case, toggling only the ground-fog enable flag while native height and volumetric fog stay on. The typical case is one 6300×6300 cm bank; the stress case is four overlapping banks of that size.
| Native volumetric grid | Case | Median fog-pass ms | Increment over matched OFF |
|---|---|---|---|
| GridPixelSize 8 / GridSizeZ 128 | off | 0.250 | — |
| 8 / 128 | one bank | 0.406 | +0.156 ms |
| 8 / 128 | four overlapping banks | 0.424 | +0.174 ms |
| 16 / 64 | off | 0.049 | — |
| 16 / 64 | one bank | 0.084 | +0.035 ms |
Those medians meet the design targets of 0.2 ms typical and 0.5 ms with overlap in this bounded run. Individual samples were noisy (0.31–0.98 ms for one bank at high quality), so treat this as a cost class, not a worst-frame bound or a platform guarantee.
Resource footprint in the same measurement setup: 192 KiB for the shared noise volume texture and 32 KiB per bank for terrain textures. Those are listed footprints, not exclusive VRAM residency — editor and PIE copies coexist, and disabling render work does not unload authored resources.
Next steps
See day cycles for look layering and wind for your content for shared motion. If a bank stays invisible, use troubleshooting or Discord support.