Cascaded shadows
Open the live demo · Read the source · View on GitHub
One shadow map cannot be sharp under your feet and still cover a field a hundred metres long: there are only so many pixels in it. A cascaded shadow splits the view into a few slices, near to far, and gives each its own map. The near slice is small and sharp, the far one large and soft.
Step 1: A place to cast on #
Shadows need a surface to fall on. The floor is a flat PlaneShape, eighty
metres each way, in one plain stone material, so the far pillars have somewhere
to put their shadow too.
final Material stone = Material(
name: 'stone',
baseColor: Vector4(0.72, 0.7, 0.66, 1.0),
roughness: 0.85,
);
final MeshNode floor = MeshNode(
DeviceMesh.upload(
context.device,
const PlaneShape(width: 80, depth: 80).build(),
),
stone.copy()..doubleSided = true,
name: 'floor',
);
Step 2: Things that cast #
Ten pillars in a row, from your feet toward the horizon. They share one mesh: a mesh is uploaded once and any number of nodes can draw it.
final Scene scene = Scene()..add(floor);
final DeviceMesh pillar = DeviceMesh.upload(
context.device,
CuboidShape(size: Vector3(1.0, 3.0, 1.0)).build(),
);
for (var i = 0; i < 10; i++) {
scene.add(
MeshNode(pillar, stone, name: 'pillar $i')
..setPosition(i.isEven ? -3.0 : 3.0, 1.5, -i * 5.0),
);
}
Step 3: A sun that asks for a shadow #
A light casts no shadow unless it says so. castsShadow: true asks the renderer to
draw the scene from the sun's side; only a directional light is cascaded.
scene.add(
LightNode(name: 'sun', intensity: 3.0, castsShadow: true)
..setLocalForward(Vector3(-0.5, -0.8, -0.3)),
);
Warning. A light asking for a shadow is a request, not a promise: the renderer shadows one directional light and a few point lights, and reports in
FrameResult.shadowsDeniedwhen it had to refuse.
Step 4: Choose the cascades #
ShadowSettings is where the split is decided. cascades is how many maps,
cascadeSplit how strongly the near ones are made smaller than the far ones and
viewDistance how far the last one reaches. Drag the sliders and watch the edge
of the near shadows.
shadows: ShadowSettings(
cascades: cascades,
cascadeSplit: split,
viewDistance: viewDistance,
resolution: _resolutions[resolutionChoice],
),