ShadowReceiverGlsl

Reusable GLSL fragments concatenated into every shadow receiver's fragment shader.

Receiver vertex shader must emit:

  • varying vec3 worldPos — camera-relative world position (worldVertex - cameraPoint), computed from uniforms the drawable composed against the camera point in double precision on the CPU. A raw ECEF position quantizes to ~0.5 m in a float32 varying — enough to erase street-scale shadow structure entirely.

  • varying float viewDepth — positive distance to the camera plane (gl_Position.w)

Receiver contract - one lighting model engine-wide (see earth.worldwind.render.program.LightingGlsl): shadows attenuate only the direct sun term, never the ambient term, so material Lambert and the cascade term can't double-darken the same fragment.

  • Lit receivers call shadowLitFactor(lambert, worldPos, viewDepth, worldNormal) - the whole lighting multiplier with the sun term shadow-attenuated. Emitted when fragmentDeclarations is built with lit = true; requires LightingGlsl.DECLARATIONS earlier in the shader. A zero normal degrades to depth-only sampling.

  • Unlit receivers (terrain imagery, photogrammetry, points) call shadowAlbedoFactor(worldPos, viewDepth) - the shadow term is their only sun shading, so it darkens the whole albedo toward ambientShadow.

  • shadowSunVisibility overloads expose the raw [0, 1] visibility; the normal-aware overload adds a normal-offset receiver bias scaled by the active cascade's texel world size. All short-circuit to 1.0 when applyShadow is false.

Occlusion resolve is a single hardware depth-compare tap by default (its free 2x2 bilinear PCF softens the edge by one texel), or a bilinear-weighted 3x3 nine-tap kernel when DrawContext.SOFT_SHADOWS is set. Stable under camera motion thanks to the cascade texel snapping.

Properties

Link copied to clipboard
const val CASCADE_COUNT: Int

Number of cascades the shader code is unrolled for. Matches ShadowState.DEFAULT_CASCADE_COUNT.

Link copied to clipboard

Receiver-side constant depth bias for opaque primitives (shapes, 3D-Tile meshes, models), in world metres — normalized per cascade on the CPU (metres / depth range) so a caster-inflated depth window can't balloon the bias and detach contact shadows. Small because the caster pass already applies slope-scaled polygon offset.

Link copied to clipboard

Switches the occlusion resolves (cascades AND the sightline cube) to hardware depth-compare samplers (sampler2DShadow / samplerCubeShadow).

Link copied to clipboard

Toggled by each receiver program's shadowsEnabled flag.

Link copied to clipboard
const val TERRAIN_DEPTH_BIAS_METERS: Float = 0.08f

Receiver-side constant depth bias for terrain-draped receivers, in world metres — larger than the primitive bias because terrain triangles are big and often nearly light-parallel.

Functions

Link copied to clipboard

Builds the fragment-shader declaration block. lit additionally emits shadowLitFactor for receivers with a lighting model (requires LightingGlsl.DECLARATIONS above this block).

Link copied to clipboard
fun glslPrefix(dc: DrawContext, receiverEnabled: Boolean): String

#version + define prefix for receiver programs. Shadow-capable variants compile as modern GLSL with hardware depth-compare samplers where the platform supports them (earth.worldwind.util.kgl.Kgl.hasShadowSamplers) - which also makes GLES3 depth-texture reads spec-defined; everything else keeps the platform's legacy default.