andrey_chen_
< cd ../arboreal
Content › Voyager › WeatherSystem › Blueprints › BP_DynamicSky Compiled Unreal Engine 5.8 ▶ Treehouse_Forest
PerspectiveLitShowHyperlapse · 1080p30 · 36 s

Arboreal Dynamic Sky & Weather

One Blueprint actor drives the sun, moon, sky, clouds, fog and snow for a whole level. Weather is data-driven, snow builds up on every surface through a single material function, and the system previews live in the editor. Below: how it is wired, the maths behind the day/night cycle, and the production fixes behind the final hyperlapse.

Sunny midday
11:14
Late afternoon
17:42
Sunset
18:18
Early night
19:27
Midnight
01:11
Morning before snow
07:46
Snow settled, late afternoon
17:19
Snowy night
01:25
Winter morning
11:17
▼

Summary

19 graphs · 39 variables · 2 presets
Time of Day

One TimeOfDay float (0–24 h) rotates the sun and moon and decides which one is active, with a short twilight overlap.

Weather Presets

DA_Sunny and DA_Snowy hold every lighting value as typed structs. Switching the preset re-lights the level.

Snow Everywhere

A Material Parameter Collection drives one material function used by the landscape, foliage, rocks, props and buildings.

Editor Preview

The update runs from the Construction Script, so changing TimeOfDay or the preset in the Details panel updates the viewport live.

▼

Architecture

data in · components out

The actor reads the clock and the weather preset, then pushes values to engine components, two dynamic material instances, Niagara and one global parameter collection. Every material that reacts to weather reads that collection, so the Blueprint never holds references to individual meshes.

▼

Construction Script

UserConstructionScript · 9 nodes

Everything runs in a fixed order from the Construction Script, which is what makes the editor preview work. The sky sphere first gets a dynamic material instance; the Branch stops the chain if that failed, so the sky is never left half-updated. Weather runs first because it sets the lighting baseline that the time-of-day functions then adjust.

Rebuilt from the asset with an editor script, not a mock-up.

Function
Behaviour
InitSkySphereMaterial
Creates a dynamic material instance on the sky sphere, caches it in Created_DynMI_SkySphere and returns whether it is valid.
HandleWeatherSettings
Switches on the preset's EWeatherType. Sunny: snow off, apply lighting. Snowy: snow on, push particle settings to Niagara, apply lighting.
HandleSunMoonRotation
Maps TimeOfDay to sun and moon pitch. Yaw comes from SunMoonRotation, so the sun path can be turned to suit the level layout.
HandleVisibility
Shows the sun light only while IsDayTime and the moon light only while IsNightTime, and drives the sun disk on the sky sphere material.
HandleNightSettings
Night: moon intensity, colour, source angle and temperature; night Rayleigh and multi-scattering on the atmosphere; stars and moon disk on. Day: hides them and reapplies the weather lighting.
HandleCloudSettings
Switches on ECloudMode: none, 2D clouds in the sky sphere material, or volumetric clouds with their own dynamic material instance. Snowy weather turns both off and lets the overcast atmosphere and fog carry the sky.
▼

Time of Day

HandleSunMoonRotation · IsDayTime

The sun's pitch is a MapRangeUnclamped of TimeOfDay from [DawnTime, DuskTime] to [0°, −180°]. It rises at dawn, peaks at midday and sets at dusk for any day length the artist picks, and because the range is unclamped it keeps rotating below the horizon at night.

The moon uses two segments: from DuskTime − 0.3 to 24 h it climbs from −180° to −90° (overhead), and from 0 h to DawnTime − 0.3 it falls back to 0°. IsDayTime and IsNightTime use the same 0.3 h (18 min) offset, so both lights exist briefly at each twilight and the hand-over is not a hard cut.

Sun Pitch
MapRangeUnclamped(TimeOfDay, DawnTime, DuskTime, 0, -180)
Moon Pitch (evening)
MapRangeUnclamped(TimeOfDay, DuskTime - 0.3, 24, -180, -90)
Moon Pitch (morning)
MapRangeUnclamped(TimeOfDay, 0, DawnTime - 0.3, -90, 0)
Is Day Time
TimeOfDay ≥ DawnTime - 0.3 AND TimeOfDay < DuskTime + 0.3

Shown with DawnTime 6 and DuskTime 18. Elevation is the negative of pitch until the light passes overhead, which gives the triangular profile. A longer day changes the slope; the twilight bands stay fixed.

▼

Weather Presets

BP_WeatherDataAssetBase

Each preset is a Data Asset whose values are grouped into one struct per engine component. SetWeatherLightingProperties breaks each struct and applies it to that component, so adding a weather type means a new enum entry and a new Data Asset, with no change to the lighting code.

Struct
Fields
Applied To
S_DirectionalLightSettings
Intensity, LightColor, LightSourceAngle, Temperature
Sun light (day only)
S_SkylightSettings
Intensity
SkyLight
S_SkyAtmosphereSettings
MultiScattering, Rayleigh, Mie scattering / absorption / anisotropy, aerial perspective
SkyAtmosphere
S_ExponentialHeightFogSettings
Volumetric fog emissive, extinction scale
Height fog
S_SnowParticlesSettings
FlakeParticles, CloudParticleCount, SpawnBoxSize, coverage animation settings
Niagara user params, BeginPlay timeline

ToggleSnow writes IsSnowing and SnowAmount to MPC_WeatherProperties and enables both Niagara components. The snowy branch pins TimeOfDay to 11:00; the hyperlapse renderer adds a LockTimeOfDayInSnowyWeather branch at render time so snow can fall through a full day and night.

▼

Snow Material

MF_SnowyWeatherBlend

MF_SnowyWeatherBlend sits at the end of each surface material (landscape, foliage, trees, rocks, the well, the cart and the tree house). It takes the material's own attributes and returns them blended with a snow surface.

Where snow lands
World-space normal Z raised to a SnowMaskStrength power: tops collect snow, walls stay clear.
How much
Tiling noise gated by saturate(noise + SnowAmount·2 − 1), so snow grows outward from noise peaks instead of fading in uniformly.
Surface
ambientCG Snow006 base colour and normal, with occlusion, roughness and displacement packed into one ORD texture.
Landscape
Terrain layers run through the same blend, so terrain and props share one snow look.
Accumulation
On BeginPlay with the snowy preset, a timeline animates SnowAmount 0 → 1 after a short delay.
Final-render tuning
Snow normal partly flattened toward up and a roughness floor, so snow reads soft without specular shimmer in motion.
Morning before snow
SnowAmount 0.0 · DA_Sunny
Full snow cover
SnowAmount 1.0 · DA_Snowy
▼

Production

Python render pipeline

The final video was rendered by an editor Python pipeline that plays the level in PIE, drives the Blueprint frame by frame and captures each frame with HighResShot. Render-only changes are applied in memory, so project assets stay untouched. Most of the work went into finding why light changes popped instead of flowing: mean luminance was measured on every frame, and each jump was traced to its cause.

LogHyperlapse: 1080 frames · max frame-to-frame luma step at first dusk 1.9 (was 8.4) · dawn pop removed · 0 overlay errors
Symptom
Root Cause
Fix
Ambient halves in one frame at sunset
Sun intensity reached exactly 0; a zero-intensity directional light is culled, which changes how the atmosphere is lit.
Fade the sun from +8.6° to +0.6° elevation and keep a small non-zero floor.
Second drop near −4.5° sun elevation
Sun and moon shared atmosphere light slot 0; enabling the moon took over sky lighting.
Moon on slot 1, always visible, intensity driven by the night blend.
Drop only in long renders
The time-sliced real-time SkyLight capture lagged a fast-changing sky, then caught up at once.
Full SkyLight recapture every frame.
Moonlight jumps at midnight
The two-segment moon mapping is discontinuous at the 24 → 0 wrap.
Moon placed opposite the sun for a continuous path.
Overblown canopy at dusk
Night exposure and sky light came in while the sun still lit the treetops.
Separate blends keyed to real sun elevation: grade +18° → −9°, exposure and sky light +10° → −9°.
Window lights snap on
Fixed candela values while exposure rose 3 stops at dusk.
Light and emissive scaled by 2(night bias − current bias), so perceived brightness follows each window's schedule.
Living Windows

21 panes found by scanning emissive mesh sections. Each gets a rect light and spill light, a 2350–3000 K tint and its own nightly schedule. A Custom HLSL node masks the shared emissive material per pane by world position, with factors packed into MPC vectors.

Paced Clock

A time-density warp slows the clock around sunsets and sunrises (about 3× at the first dusk) and eases into a long final shot, keeping the total at 36 s.

Editor Tooling

A C++ editor plugin, generated with AI assistance for this project’s needs, exposes struct, enum, timeline, macro and Niagara-parameter editing to Python, so Blueprint and data changes are scripted and repeatable.

Overlay

Per-frame state logged to JSON becomes an ASS subtitle track styled like this Details panel, composited with ffmpeg alongside a cinematic grade and letterbox.

▼

Stills

3840 × 2160 · HighResShot

Snow textures: ambientCG Snow006 (CC0) · Music: "Cozy Winter" by Pan-Pan Gou, panpangou.com · Unreal Engine 5.8