EVSM shadows: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: filters (line 16), casters (line 39), sun (line 57), settings (line 64).
1/// The sun's shadow map turned into exponential moments, blurred once, and
2/// read back with one filtered tap per pixel.
3///
4/// Quoted by `evsm_shadows.md` and shown whole in the Source tab.
5library;
7import 'package:flutter3d/flutter3d.dart';
8import 'package:flutter3d_showcase/src/demo/demo.dart';
9import 'package:vector_math/vector_math.dart';
11final class EvsmShadowsDemo extends ShowcaseDemo {
12 int filterChoice = 2;
13 double blurRadius = 4;
14 double bleedReduction = 0.2;
16 static const List<ShadowFilter> _filters = <ShadowFilter>[
17 ShadowFilter.pcf,
18 ShadowFilter.pcss,
19 ShadowFilter.evsm,
20 ];
23 void configureView(DemoContext context) {
24 context.orbit
25 ..distance = 8.3
26 ..pitch = 0.81
27 ..yaw = 0.27;
28 }
31 Scene build(DemoContext context) {
32 MeshNode slab(String name, Vector3 size, Vector3 at, Material material) =>
33 MeshNode(
34 DeviceMesh.upload(context.device, CuboidShape(size: size).build()),
35 material,
36 name: name,
37 )..setPosition(at.x, at.y, at.z);
39 final Material grey = Material(
40 name: 'grey',
41 baseColor: Vector4(0.6, 0.6, 0.62, 1.0),
42 );
43 final Scene scene = Scene()
44 ..add(
45 slab(
46 'floor',
47 Vector3(14.0, 0.2, 14.0),
48 Vector3(0.0, -0.1, 0.0),
49 Material(name: 'floor', baseColor: Vector4(0.5, 0.5, 0.52, 1.0)),
50 ),
51 )
52 ..add(slab('box', Vector3(1.6, 0.2, 1.6), Vector3(0.0, 0.6, 0.0), grey))
53 ..add(
54 slab('post', Vector3(0.2, 1.5, 0.2), Vector3(-2.0, 0.75, 1.0), grey),
55 );
57 final LightNode sun = LightNode(name: 'sun', intensity: 1.5)
58 ..setLocalForward(Vector3(-0.85, -1.0, -0.2).normalized());
59 return scene..add(sun);
60 }
63 RenderSettings settings(DemoContext context) => RenderSettings(
64 shadows: ShadowSettings(
65 cascades: 1,
66 viewDistance: 20.0,
67 filter: _filters[filterChoice],
68 evsmBlurRadius: blurRadius.round(),
69 evsmBleedReduction: bleedReduction,
70 directionalLightRadius: 0.02,
71 ),
72 );
75 List<DemoControl> controls(DemoContext context) => <DemoControl>[
76 ChoiceControl(
77 'Filter',
78 options: const <String>['PCF', 'PCSS', 'EVSM'],
79 index: () => filterChoice,
80 onChanged: (int i) => filterChoice = i,
81 ),
82 SliderControl(
83 'Blur radius',
84 min: 0,
85 max: 8,
86 divisions: 8,
87 value: () => blurRadius,
88 onChanged: (double v) => blurRadius = v,
89 format: (double v) => '${v.round()} texels',
90 ),
91 SliderControl(
92 'Bleed reduction',
93 min: 0,
94 max: 0.95,
95 value: () => bleedReduction,
96 onChanged: (double v) => bleedReduction = v,
97 ),
98 ];
101 void verify(Scene scene, FrameResult frame) {
102 if (!frame.passes.any((FramePass p) => p.name == 'directional shadows')) {
103 throw StateError('the sun drew no shadow map to filter');
104 }
105 if (_filters[filterChoice] != ShadowFilter.evsm) return;
106 final PassSkip? skipped = frame.skipReasonOf('shadow moments');
107 if (skipped != null) {
108 throw StateError('the moments were refused: $skipped');
109 }
110 if (!frame.passes.any((FramePass p) => p.name == 'shadow moments')) {
111 throw StateError('the shadow map was never turned into moments');
112 }
113 }
114}