Shadows

The engine supports real-time shadow mapping for all three light types: directional, spot, and point lights. Shadows use depth-only render passes, PCF soft filtering, and configurable resolution/bias.

Enabling Shadows

Set CastShadow = true on any Light component:

int sun = world.Spawn(
    new Transform(),
    new Light {
        Type = LightType.Directional,
        Direction = new Vec3(-1f, -0.5f, -0.5f),
        Intensity = 1.0f,
        CastShadow = true
    }
);

Shadows work with all light types:

// Point light with shadows
world.Spawn(
    new Transform { Position = new Vec3(0f, 4f, 0f) },
    new Light {
        Type = LightType.Point,
        Intensity = 2.0f,
        Radius = 15f,
        CastShadow = true
    }
);

// Spot light with shadows
world.Spawn(
    new Transform { Position = new Vec3(0f, 5f, 0f) },
    new Light {
        Type = LightType.Spot,
        Direction = new Vec3(0f, -1f, 0f),
        Intensity = 3.0f,
        Radius = 20f,
        OuterConeDeg = 30f,
        CastShadow = true
    }
);

Shadow Limits

Light TypeMax Shadow CastersTechnique
Directional / Spot4 combined2D depth map
Point2Cubemap (6 faces)

Lights beyond these limits still illuminate the scene but do not cast shadows.

Configuration API

All shadow parameters can be tuned at runtime from C#:

Shadow Map Resolution

NativeBridge.SetShadowMapSize(2048);  // default: 2048

Higher values produce sharper shadows but use more VRAM. The resolution applies to all shadow maps (directional, spot, and point cubemap faces).

ResolutionVRAM (approx)Quality
1024~64 MBLow
2048~256 MBMedium
4096~1 GBHigh

Shadow Bias

Controls the depth offset to prevent shadow acne (dark striping on lit surfaces):

NativeBridge.SetShadowBias(0.003f);       // directional/spot (default: 0.003)
NativeBridge.SetPointShadowBias(0.002f);   // point lights (default: 0.002)
  • Too low: shadow acne (dark stripes on surfaces)
  • Too high: peter panning (shadow detaches from object base)

Shadow Bounds

Controls how large an area the directional light shadow covers:

NativeBridge.SetShadowBounds(25f);  // default: 25.0 (covers 50x50 unit area)

Smaller values give higher shadow detail but cover less of the scene. The directional shadow frustum follows the camera using a stable frustum-fitting algorithm that snaps the shadow center to the texel grid in light space, preventing shadow shimmering when the camera moves.

How It Works

Directional and Spot Shadows

  1. A depth-only render pass renders the scene from the light's perspective into a 2D depth texture
  2. Directional lights use an orthographic projection; spot lights use a perspective projection matching the cone angle
  3. The main fragment shader samples the shadow map and compares fragment depth to determine if the pixel is in shadow
  4. A 5x5 PCF (Percentage Closer Filtering) kernel samples 25 neighboring texels for soft shadow edges

Point Light Shadows

  1. Each point light renders the scene 6 times (one per cubemap face: +X, -X, +Y, -Y, +Z, -Z)
  2. The fragment shader writes linear distance from the light (not raw depth) for correct omnidirectional comparison
  3. The main shader samples the cubemap using the fragment-to-light direction vector
  4. A 20-sample disk PCF kernel provides soft shadow edges

Light Properties Reference

FieldTypeDefaultDescription
CastShadowboolfalseEnable shadow casting for this light

Shadow API Reference

MethodDefaultDescription
NativeBridge.SetShadowMapSize(int)2048Shadow map resolution (all types)
NativeBridge.SetShadowBias(float)0.003Directional/spot shadow bias
NativeBridge.SetPointShadowBias(float)0.002Point shadow bias
NativeBridge.SetShadowBounds(float)25.0Directional shadow ortho half-extent