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
| Output | What it is |
|---|---|
sky.sun.direction / color / intensity | Direct sunlight at the camera, through the atmosphere, in three.js units (0 below the horizon) |
sky.moon.direction / color / intensity | The same for the moon, plus phase, illumination, phaseName |
sky.keyLight | Sun by day, moon once the sun is 6° down: what bindLights uses |
sky.ambient.skyColor / groundColor / intensity | Ready for a HemisphereLight; cloud cover greys and brightens it |
sky.ambient.color | Up-facing irradiance with magnitude included |
sky.envMap | Equirect HDR sky + clouds (no sun disc), EquirectangularReflectionMapping |
sky.environment | PMREM of envMap for scene.environment / material.envMap |
sky.cloudShadow.texture / matrix / uniforms | Cloud transmittance map and its world→uv matrix |
sky.fogColor, sky.fogSigma | Linear horizon colour and sea-level extinction (1/m) for THREE.Fog* |
sky.exposure, sky.displayExposure | Metered exposure, in sky and in toneMappingExposure units |
sky.lightning | Current flash, 0 – 1.5 |
#Lights: bindLights
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.
| Option | Default | |
|---|---|---|
distance | 100 | World units from the target, which matters for the shadow camera |
cloudDimming | false | Multiply by sun.visibility. Leave it off when your materials are patched (they already shadow) |
intensityScale | 1 | Scale the driven intensities |
#Environment and reflections
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
sky.patchMaterial(ground.material);
sky.patchMaterial(instancedTrees.material); // instancing and batching workThe 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
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
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.