flutter3d
Showcase Changelog 38 packages API reference

Soft shadows

since 0.7.0 Shadows

A real light has a size, so the edge of its shadow is not a line. Right where a shadow starts, at the foot of the thing casting it, the edge is sharp. The further the shadow travels, the wider the blurred band around its edge gets. That band is the penumbra, and by default the engine draws the same narrow edge everywhere.

Step 1: Something that casts near and far #

A pole standing on the ground and a thin slab held a metre and a half above it. The foot of the pole touches the ground, so its shadow should start sharp. The slab hangs in the air, so its whole shadow has travelled and should be soft.

final Scene scene = Scene()
  ..add(
    MeshNode(
      DeviceMesh.upload(
        context.device,
        const PlaneShape(width: 12, depth: 12).build(),
      ),
      stone,
      name: 'ground',
    ),
  )
  ..add(
    MeshNode(
      DeviceMesh.upload(
        context.device,
        const CylinderShape(
          radiusTop: 0.12,
          radiusBottom: 0.12,
          height: 3.0,
          segments: 16,
        ).build(),
      ),
      clay,
      name: 'pole',
    )..setPosition(-1.5, 1.5, 0.0),
  )
  ..add(
    MeshNode(
      DeviceMesh.upload(
        context.device,
        CuboidShape(size: Vector3(1.6, 0.15, 1.6)).build(),
      ),
      clay,
      name: 'slab',
    )..setPosition(1.5, 1.6, 0.0),
  );

Step 2: A sun and a lamp #

One directional light and one point light, both in the scene. The page lets one of them cast at a time, so you can compare. The lamp has a range because a point light fades out; the sun does not.

_sun = LightNode(name: 'sun', intensity: 3.0, castsShadow: true)
  ..setLocalForward(Vector3(-0.55, -0.7, -0.35));
_lamp = LightNode(
  name: 'lamp',
  type: LightType.point,
  intensity: 60.0,
  range: 20.0,
)..setPosition(2.5, 3.2, 2.5);
scene
  ..add(_sun)
  ..add(_lamp);

Step 3: Give the light a size #

directionalLightRadius is the sun's apparent radius, in radians. The real sun's is about 0.0047; a larger one reads as a hazier sky. At zero, the shadow uses the fixed narrow edge every earlier frame had. Above zero the renderer looks for what is blocking each shadowed point and widens the edge by how far away the blocker is. Drag Sun size up and watch the slab's shadow blur while the foot of the pole stays crisp.

pointLightRadius does the same for the lamp, in metres. Switch Light to Lamp and move Lamp size. The change is gentler here, because the lamp's shadow map is small.

shadows: ShadowSettings(
  cascades: 1,
  resolution: 1024,
  cubeResolution: 256,
  directionalLightRadius: sunRadius,
  pointLightRadius: lampRadius,
),

Note. The sun's number is in the units of the shadow map's own depth, so it depends on the scene. Pick it by looking at the picture, not from a table.