Sky and fog
Put a sky behind the scene, either a two-colour gradient or a cubemap image, fade distant surfaces into fog, and turn day into night.
component: skybox
component: fog
component: day_cycle
Vulkan
WebGPU
Desktop · Android · iOS · Web
A scene document and the scene DSL are two ways to write the same ECS components. Each pair of tabs
below builds equal Skybox and Fog components.
Add a gradient sky and fog
Put a skybox and a fog on one entity. The sky blends from horizonColor at the horizon to
zenithColor overhead.
- Open the Environment tab of the Inspector.
- Under Skybox & Horizon, turn Sky: On and choose Procedural. Set Horizon Color and Zenith Color.
- Under Atmosphere & Fog, set Fog Density.
Studio creates the skybox and fog components on first edit. You can also select an entity
and use Add component > Skybox or Fog; their Inspector sections have Enabled,
colour, Density and Type fields.
The gradient sky draws only when the app's render plan carries it:
contentFeatures = listOf(skyboxContentFeature(PackShaderSets.Skybox)). See
Render plans and shaders.
Use a cubemap sky
Set type to Cubemap and point cubemapPath at a strip image: six square faces side by side,
+X, −X, +Y, −Y, +Z, −Z from the left.
- Open the Environment tab of the Inspector.
- Under Skybox & Horizon, choose Cubemap.
- Set Cubemap Asset to the strip's path in the project, and Exposure.
Studio loads the image from the project's files and draws it in the viewport.
A cubemap sky needs SkyboxCubemapSystem to load the image and hand it to the renderer. It is not
installed by default. Add it to the scene with the coroutine scope and asset source your app loads
files with:
/** Loads the scene's cubemap sky from [assets] off the frame thread. */
fun SceneAppDsl.cubemapSky(scope: CoroutineScope, assets: AssetSource) {
frameSystem("cubemap-sky") {
SkyboxCubemapSystem(renderer as ContentFeatureHost, scope, assets)
}
}
The system reads the strip off the frame thread and attaches it on a later frame. Changing the path
or exposure loads the new image; leaving Cubemap mode, disabling the sky, or removing the component
takes it away.
Day and night
Put a day_cycle on the node of the scene's directional light. During play the sun crosses the sky
once every dayLengthSeconds, and each stop sets how the sky, the light, its ambient share and the
fog look at that time of day. Between stops they blend.
{
"version": 1,
"nodes": [
{
"name": "sun",
"components": [
{ "component": "light", "type": "Directional", "shadowDistance": 60.0 },
{
"component": "day_cycle",
"dayLengthSeconds": 600,
"time": 0.3,
"stops": [
{ "time": 0.0, "horizonColor": "#0B1020", "zenithColor": "#02040A", "lightIntensity": 0.0, "ambient": 0.08 },
{ "time": 0.25, "horizonColor": "#F2A65A", "zenithColor": "#3A4E7A", "lightColor": "#FFB070", "lightIntensity": 0.6, "ambient": 0.2 },
{ "time": 0.5, "horizonColor": "#B8CCE0", "zenithColor": "#3361AD", "lightColor": "#FFF4E0", "lightIntensity": 1.0, "ambient": 0.3 },
{ "time": 0.75, "horizonColor": "#F08A4B", "zenithColor": "#2E3A66", "lightColor": "#FF9A5C", "lightIntensity": 0.6, "ambient": 0.2 }
]
}
]
},
{
"name": "environment",
"components": [ { "component": "skybox", "type": "Procedural" } ]
}
]
}
time is a fraction of the day: 0 midnight, 0.25 sunrise, 0.5 noon, 0.75 sunset. The sun
rises at sunriseAzimuthDegrees and is highest at noon, at noonElevationDegrees. Below the horizon
the light still points at the sun, so the ground gets no direct light and the sky's moon stays
opposite the sun. The night stop's low lightIntensity, low ambient and dark sky make it night.
A field no stop sets keeps the value its own component authors, and blending wraps across midnight.
The day advances in play: runProject, or the systems sceneSystemsFor returns. Outside play the
scene shows its light, sky and fog as authored; DayCycleSystem().update(world, 0f) applies the
current time without advancing it. Saving writes the authored time back, never the time play
reached. See day_cycle for every field.
Keep the sun's node unrotated
A rotated light node shines along its rotation and ignores the direction the day cycle writes.
Properties
skybox:
| Property | Type | Default | What it does |
|---|---|---|---|
enabled |
boolean | true |
Whether the sky draws. |
type |
Procedural · Cubemap · SolidColor |
Procedural |
Gradient, image, or one flat colour. |
horizonColor |
color | (0.72, 0.80, 0.88) |
Gradient colour at the horizon. For SolidColor, the whole sky. |
zenithColor |
color | (0.20, 0.38, 0.68) |
Gradient colour overhead. |
cubemapPath |
string | none | Cubemap only. The strip image, read through the app's asset source. |
exposure |
number | 1.0 |
Cubemap only. Multiplies the sampled colour. |
fog:
| Property | Type | Default | What it does |
|---|---|---|---|
enabled |
boolean | true |
Whether fog applies. |
density |
number | 0.001 |
How fast fog thickens with distance. 0 is no fog. |
color |
color | (0.55, 0.62, 0.70) |
The colour distant surfaces fade to. |
A color is an object ({ "r": …, "g": …, "b": … }), an array, or a hex string such as "#C8D2DC".
In the scene DSL, skybox(Skybox(...)) and fog(Fog(...)) attach them to the current entity;
skyboxEntity() and fogEntity() create an entity for each.
How it works
The renderer reads the first Skybox and the first Fog in the world. With no Skybox, no sky is
drawn. With no Fog, there is no fog.
Fog is exponential distance fog, computed per pixel in the lit and textured shaders: a surface
d units from the camera shows 1 − e^(−density × d) of the fog colour. Surfaces drawn by shaders
that do not read fog, such as your own, are not fogged.
SolidColor uses horizonColor for the whole sky. The cubemap sky draws over the gradient sky when
the plan has both.
Tune density to the scene's scale
Density is per world unit. At 0.02, a surface 50 units away is about 63% fog; at the default
0.001 it is about 5%.
A missing plan feature leaves the sky blank
Without skyboxContentFeature(...) in the render plan, the gradient sky is never drawn, whatever
the skybox component says.
The cubemap image must be six squares wide
A strip whose width is not six times its height fails to load. The sky stays as it was.
Debugging
A cubemap that fails to load logs Sky '<path>' could not load: <reason> on the scene-sky logger
and keeps the previous sky. Any debug view, such as Shadow visibility, hides the sky so it does not
read as data.
See also
- Lights and shadows for the sun and ambient level.
- Render plans and shaders for
skyboxContentFeatureanddepthFogContentFeature.