Shadows
Sky Pro renders cloud coverage into a top-down shadow map. Standard and custom materials can sample this map for animated cloud shadows.
This guide covers standard materials, custom TSL materials, and performance settings.
Quick Setup
For MeshStandardNodeMaterial and other materials using Three.js lighting, assign the cloud shadow node to the sun's DirectionalLight. Receiving meshes only need receiveShadow = true:
import * as THREE from "three/webgpu";
import { positionWorld } from "three/tsl";
const sunLight = new THREE.DirectionalLight(0xffffff, sky.sun.intensity.value);
sunLight.castShadow = true;
sunLight.shadow.mapSize.set(64, 64); // the depth-compare map goes unused, keep it tiny
sunLight.shadow.shadowNode = sky.cloudShadow(positionWorld);
scene.add(sunLight, sunLight.target);
terrainMesh.receiveShadow = true;Each frame, point the light from the sun toward the receiving geometry:
const sunDir = sky.sun.direction.value; // unit vector toward the sun
sunLight.target.position.set(camera.position.x, 0, camera.position.z);
sunLight.position.set(
camera.position.x + sunDir.x * 5000,
sunDir.y * 5000,
camera.position.z + sunDir.z * 5000,
);Custom Materials
In a TSL colorNode, multiply sky.cloudShadow(worldPos) into direct light only. Do not apply it to ambient or sky lighting.
import { positionWorld } from "three/tsl";
const direct = albedo.mul(sunColor).mul(sky.cloudShadow(positionWorld));
const lit = direct.add(albedo.mul(ambientColor));To reduce shadow strength for one material without changing the global intensity, mix the result toward 1.0:
const cloudShade = sky.cloudShadow(positionWorld);
const shadowFactor = mix(float(1.0), cloudShade, float(0.6)); // 60% as dark as the global bakeTuning
Use sky.pipeline.cloudShadow to configure the shadow map:
| Setting | Default | Description |
|---|---|---|
setResolution(n) / .resolution | tier-driven, 128–1024 | Square shadow-map size. Higher values sharpen cloud edges and increase rendering cost. See Quality Levels. |
extent.value | 4000 | Half-width of the world-XZ footprint in meters — 4000 covers an 8 km box centered on the camera. Raising it trades texel density for reach at a fixed resolution. |
intensity.value | 1.0 | Global shadow strength: 0 = no darkening, 1 = full. |
mipLevel.value | tier-driven, 1–3 | Cloud-field mip level. Higher values produce softer shadows at lower cost. |
lightSteps.value | 8 | March step count through the shell per texel. Raise it if thin, low cloud layers show banded shadow edges. |
bakeInterval | 1 | Re-bake every Nth frame instead of every frame. Cloud shadows lag behind fast-moving weather by up to bakeInterval frames. |
enabled | tracks sky.clouds.enabled | false skips the bake; every receiver reads full sun. |
sky.pipeline.cloudShadow.extent.value = 8000;
sky.pipeline.cloudShadow.intensity.value = 0.9;
sky.pipeline.cloudShadow.setResolution(1024);Moon Shadows
When TimeOfDay is active, the shadow map follows the moon after sunset. Assign the same shadow.shadowNode to a separate moon DirectionalLight if the scene uses one.
Limitations
- Cloud-cast only. No shadows between scene objects — that's Three's own shadow-mapping, run separately.
- Flat top-down lookup, not a true directional shadow map — least accurate at very low sun/moon elevation.
- Finite footprint. Shadows and god-ray shafts fade to nothing past
extent; a receiver far outside the box reads full sun. - Shared with god rays. The same texture supplies ground shadows and light shafts, so
extent,intensity, and resolution affect both.
See Also
sky.cloudShadow(worldPos)— full API reference- GodRays — light shafts driven by the same bake
- Quality Levels — tier defaults for
cloudShadowResolution/cloudShadowMipLevel - Tuning Performance
