flutter3d
Showcase Changelog 38 packages API reference

Procedural sky

since 0.2.0 Light and environment

A sky does not need a photograph. Three colours (overhead, at the horizon and underfoot), a soft glow around the sun and a small bright disc are enough for an outdoor scene, and the renderer works all of it out per pixel. Nothing is loaded and nothing has to be kept in step with a file.

Step 1: A light that agrees with the sun #

The sky paints a sun, but it does not light anything. Light still comes from a LightNode. The two are separate on purpose: you can have sunlight with no sky, or a sky with a moon that lights nothing. When you want them to match, aim the light the opposite way from where the sun is painted. Here update does that every frame from one number, the sun height.

_sun = LightNode(name: 'sun', intensity: 3.0);

Step 2: Turn the sky on #

The sky is a setting of the frame, not an object in the scene. It is off until enabled is true. directionToSun points at the sun, which is the reverse of the way a light points. sunIntensity is the brightness of the disc and glowStrength the soft halo around it. Drag Sun height and watch the disc climb, then drag Glow and Sun disc to see which part is which.

The colours of the sky itself (zenith, horizon and nadir) have defaults, so this page leaves them alone.

sky: SkySettings(
  enabled: !dome,
  directionToSun: _towardsSun,
  sunIntensity: sunDisc,
  glowStrength: glow,
),

Note. Sky colours are linear light, not the values an eyedropper reads off a screenshot. They pass through exposure and the tone curve like everything else, so a sky written from a paint swatch will look too bright.

Step 3: The painted dome #

There is an older way that needs no shader: a small dome around the camera with the gradient painted into its vertices. paintSky fills the colours from a SkyGradient, skyNode sets the flags a backdrop needs (drawn first, no depth write, no shadow), and followCamera keeps it centred on the eye. Turn on Painted dome instead and the sky setting is switched off so the dome is what you see.

The dome has no sun disc. Its one advantage is that each view can have a different sky, which a setting on the whole frame cannot do.

final MeshData shell = const SkyDome().build();
paintSky(
  shell,
  SkyGradient(
    zenith: Vector3(0.08, 0.2, 0.5),
    horizon: Vector3(0.5, 0.58, 0.68),
    nadir: Vector3(0.06, 0.06, 0.07),
  ).colour,
);
_dome = skyNode(DeviceMesh.upload(context.device, shell))..visible = false;