Shadow Layer
Adds directional sun-shadow rendering to the scene. When this layer is in the layer list, shapes (3D meshes, polygons, COLLADA / glTF models, OGC 3D-Tile meshes, etc.) cast shadows onto terrain and onto each other from the same world-space sun direction that drives shape lighting and atmospheric scattering (RenderContext.lightDirection).
Implementation is Cascaded Shadow Maps (cascadeCount cascades, default 4) rendered as plain depth textures and resolved with a bilinear-weighted percentage-closer filter for smooth, stable penumbrae. Three properties matter for city-scale quality:
Scene-fit splits. The cascade depth range is fit to the view-depth extent of the content actually on screen (RenderContext.shadowSceneBounds, accumulated from drawable bounding spheres on the previous frame) rather than to a terrain-ray probe. When the camera is close to a small object, per-cascade windows are additionally clamped to street-scale distances (maximumCascadeDistances) so the closest cascade stays centimetre-sharp.
Camera-relative receiver math. Cascade matrices handed to receivers map
worldPos - cameraPoint, composed in double precision on the CPU. Raw ECEF positions quantize to ~0.5 m in float32 varyings, which would destroy street-scale shadows.Slope-scaled caster bias. The depth pass renders true depth, so hardware
glPolygonOffsetprovides per-triangle slope-proportional bias; receivers add only a small constant bias plus a normal-offset term scaled by the cascade's texel world size.
Receivers sample the cascade depth textures in their own fragment shaders and modulate their output colour by the resulting visibility. The ambientShadow knob controls how dark fully-occluded fragments appear: 0.0 = pure black shadow, 1.0 = no darkening. Per-shape opt-out is via ShapeAttributes.shadowMode (and ColladaScene.shadowMode / GltfScene.shadowMode / Ogc3dTilesLayer.shadowMode) — the ShadowMode enum is decoupled from isLightingEnabled and separates cast vs receive. Terrain always receives.
Place this layer after earth.worldwind.layer.atmosphere.AtmosphereLayer in the layer list so the atmosphere has already populated RenderContext.lightDirection before the cascade matrices are computed.
Properties
Ambient floor for fully-occluded fragments. See ShadowState.ambientShadow.
Number of cascades. Currently fixed at ShadowState.DEFAULT_CASCADE_COUNT.
Debug: 0 off, 1 cascade bands, 2 footprint coverage, 3 raw shadow-map depth; logs re-fits when non-zero.
Whether terrain rasterises into the cascade depth maps. false makes terrain a receive-only surface: shapes still cast onto it, but mountainsides no longer blanket valleys (and themselves) in hard self-shadow — the cartographic look, best combined with terrainLambert for relief. Terrain always receives regardless.
Street-scale cap on each cascade's depth window, in metres, at a street-level fit anchor; the caps relax proportionally as the nearest content recedes. The last entry is unbounded so distant coverage never disappears. At the reference 1024² maps the 50 m closest cascade keeps ~5 cm texels.
Floor for the last cascade's far cap, in metres. The actual cap each frame is max(maximumDistance, viewingDistance * 2.0) — tilted / far-focused views push the cascades out toward the focal point without per-app tuning — further tightened to the scene's own depth extent when drawable bounds are available. Receivers fade shadows out over the last 20% of the effective cap.
Floor for the scene-fit near distance, in metres. Keeps the logarithmic split from collapsing the closest cascade to centimetres when the camera projection's near plane is very close.
How many cascades (closest first) recruit OFF-SCREEN shadow casters. A caster outside the view frustum is kept alive - rasterizing into the depth maps but skipping the color pass - when its bounds intersect one of the first N cascades' light-space boxes, so its shadow still reaches visible ground. 0 disables keepalive: off-screen objects cast nothing (cheapest, matches most engines). Higher values extend correct shadows to coarser cascades at the cost of a larger resident caster set - in dense 3D-Tiles scenes each step roughly grows the kept-alive tile ring by the next cascade's footprint. Clamped to cascadeCount.
Cascade split interpolation between uniform (0.0) and logarithmic (1.0) spacing. The default 0.9 is log-dominant so close-range cascades stay tight; the uniform fraction keeps far cascades from collapsing when the near plane is very close.
Lambertian terrain shading strength in [0, 1]. 0 (default) keeps classic unshaded imagery. Non-zero shades terrain with the engine lighting model (earth.worldwind.render.program.LightingGlsl) from smooth per-vertex terrain normals (built by earth.worldwind.globe.terrain.TerrainTile.getNormalBuffer, relief resolution follows terrain mesh LoD): sun-averted slopes darken toward the scene ambient, a hillshade-style relief whose contrast intermediate values scale down. Received shadows then attenuate the direct sun term instead of multiplying the whole albedo by ambientShadow, so shadowed terrain reads much lighter. Fades out across the terminator band with the shadows.