NatureGL Skyv1.1.0

Guides

Lighting your scene

The sky is also your scene's light source. It provides a key light that switches between sun and moon, hemispherical ambient, an HDR environment for PBR, a cloud-shadow map and a fog colour, and they all update with the time and the weather.

#The outputs

OutputWhat it is
sky.sun.direction / color / intensityDirect sunlight at the camera, through the atmosphere, in three.js units (0 below the horizon)
sky.moon.direction / color / intensityThe same for the moon, plus phase, illumination, phaseName
sky.keyLightSun by day, moon once the sun is 6° down: what bindLights uses
sky.ambient.skyColor / groundColor / intensityReady for a HemisphereLight; cloud cover greys and brightens it
sky.ambient.colorUp-facing irradiance with magnitude included
sky.envMapEquirect HDR sky + clouds (no sun disc), EquirectangularReflectionMapping
sky.environmentPMREM of envMap for scene.environment / material.envMap
sky.cloudShadow.texture / matrix / uniformsCloud transmittance map and its world→uv matrix
sky.fogColor, sky.fogSigmaLinear horizon colour and sea-level extinction (1/m) for THREE.Fog*
sky.exposure, sky.displayExposureMetered exposure, in sky and in toneMappingExposure units
sky.lightningCurrent flash, 0 – 1.5

#Lights: bindLights

js
const sun = new THREE.DirectionalLight();
const hemi = new THREE.HemisphereLight();   // optional
scene.add(sun, sun.target, hemi);

sky.bindLights(sun, hemi, { distance: 100 });

On every update the directional light moves to target.position + keyLight.direction × distance, takes the key light's colour and intensity, and hides itself below the horizon. The hemisphere light takes the ambient colours and intensity, plus lightning flashes.

OptionDefault
distance100World units from the target, which matters for the shadow camera
cloudDimmingfalseMultiply by sun.visibility. Leave it off when your materials are patched (they already shadow)
intensityScale1Scale the driven intensities

#Environment and reflections

js
scene.environment = sky.environment;
sky.addEventListener('environmentchange', () => { scene.environment = sky.environment; });
// or let the sky do it:
await SkySystem.create({ renderer, scene, camera, setSceneEnvironment: true });

The equirect bake is time-sliced. One refresh is spread over 1–4 frames, depending on the tier, so it never spikes. The PMREM refresh is throttled and uses a reduced GGX sample count. When the quality tier changes the texture objects, the sky re-points scene.environment, scene.background and any material envMap that used the old texture, and then fires environmentchange.

#Cloud shadows on built-in materials

js
sky.patchMaterial(ground.material);
sky.patchMaterial(instancedTrees.material);   // instancing and batching work

The patch multiplies each directional light's contribution by skyCloudShadow(worldPosition). It chains any existing onBeforeCompile, it's idempotent, and it returns the material. sky.cloudShadow.strength (0–1) scales it globally.

#Cloud shadows in your own shaders

js
import { CLOUD_SHADOW_GLSL } from 'naturegl-sky';

const mat = new THREE.ShaderMaterial({
  uniforms: { ...sky.cloudShadow.uniforms },
  vertexShader: /* glsl */ `
    varying vec3 vWorld;
    void main() {
      vec4 w = modelMatrix * vec4(position, 1.0);
      vWorld = w.xyz;
      gl_Position = projectionMatrix * viewMatrix * w;
    }`,
  fragmentShader: /* glsl */ `
    ${CLOUD_SHADOW_GLSL}
    varying vec3 vWorld;
    void main() { gl_FragColor = vec4(vec3(skyCloudShadow(vWorld)), 1.0); }`,
});

skyCloudShadow(worldPos) returns 1 in full sun, and it fades back to 1 at the edge of the map. The uniforms are shared objects, so the material follows the live map with no per-frame copying.

#Fog without the post pass

js
scene.fog = new THREE.FogExp2(sky.fogColor, sky.fogSigma);
// per frame, if the weather or the time changes:
scene.fog.color.copy(sky.fogColor);
scene.fog.density = sky.fogSigma * sky.current.fogDensity;

For depth-aware height fog with god rays, use the post pass.

#Exposure

With autoExposure (the default), the sky writes renderer.toneMappingExposure on every update from a metered value, so noon and dusk both read well. With it off, you set the exposure yourself. The sky reads it back, so the moon, stars and lightning stay correctly scaled. See Keep nights dark for the 1.1 ceilings.