Soft shadows: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: casters (line 41), lights (line 78), settings (line 105).
1/// A penumbra that widens with distance: sharp where a shadow starts, soft
2/// where it has travelled.
3///
4/// Quoted by `soft_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 SoftShadowsDemo extends ShowcaseDemo {
12 double sunRadius = 0.05;
13 double lampRadius = 0.3;
14 int lightChoice = 0;
16 late final LightNode _sun;
17 late final LightNode _lamp;
20 void configureView(DemoContext context) {
21 context.orbit
22 ..distance = 8.0
23 ..pitch = 0.75
24 ..yaw = 0.9;
25 }
28 Scene build(DemoContext context) {
29 final Material stone = Material(
30 name: 'stone',
31 baseColor: Vector4(0.78, 0.76, 0.72, 1.0),
32 roughness: 0.9,
33 doubleSided: true,
34 );
35 final Material clay = Material(
36 name: 'clay',
37 baseColor: Vector4(0.85, 0.45, 0.3, 1.0),
38 roughness: 0.7,
39 );
41 final Scene scene = Scene()
42 ..add(
43 MeshNode(
44 DeviceMesh.upload(
45 context.device,
46 const PlaneShape(width: 12, depth: 12).build(),
47 ),
48 stone,
49 name: 'ground',
50 ),
51 )
52 ..add(
53 MeshNode(
54 DeviceMesh.upload(
55 context.device,
56 const CylinderShape(
57 radiusTop: 0.12,
58 radiusBottom: 0.12,
59 height: 3.0,
60 segments: 16,
61 ).build(),
62 ),
63 clay,
64 name: 'pole',
65 )..setPosition(-1.5, 1.5, 0.0),
66 )
67 ..add(
68 MeshNode(
69 DeviceMesh.upload(
70 context.device,
71 CuboidShape(size: Vector3(1.6, 0.15, 1.6)).build(),
72 ),
73 clay,
74 name: 'slab',
75 )..setPosition(1.5, 1.6, 0.0),
76 );
78 _sun = LightNode(name: 'sun', intensity: 3.0, castsShadow: true)
79 ..setLocalForward(Vector3(-0.55, -0.7, -0.35));
80 _lamp = LightNode(
81 name: 'lamp',
82 type: LightType.point,
83 intensity: 60.0,
84 range: 20.0,
85 )..setPosition(2.5, 3.2, 2.5);
86 scene
87 ..add(_sun)
88 ..add(_lamp);
89 return scene;
90 }
93 void update(DemoContext context, double dt) {
94 final bool lamp = lightChoice == 1;
95 _sun
96 ..castsShadow = !lamp
97 ..intensity = lamp ? 0.0 : 3.0;
98 _lamp
99 ..castsShadow = lamp
100 ..intensity = lamp ? 60.0 : 0.0;
101 }
104 RenderSettings settings(DemoContext context) => RenderSettings(
105 shadows: ShadowSettings(
106 cascades: 1,
107 resolution: 1024,
108 cubeResolution: 256,
109 directionalLightRadius: sunRadius,
110 pointLightRadius: lampRadius,
111 ),
112 );
115 List<DemoControl> controls(DemoContext context) => <DemoControl>[
116 ChoiceControl(
117 'Light',
118 options: const <String>['Sun', 'Lamp'],
119 index: () => lightChoice,
120 onChanged: (int i) => lightChoice = i,
121 ),
122 SliderControl(
123 'Sun size',
124 min: 0,
125 max: 0.2,
126 value: () => sunRadius,
127 onChanged: (double v) => sunRadius = v,
128 format: (double v) => '${(v * 57.29578).toStringAsFixed(1)}°',
129 ),
130 SliderControl(
131 'Lamp size',
132 min: 0,
133 max: 0.6,
134 value: () => lampRadius,
135 onChanged: (double v) => lampRadius = v,
136 format: (double v) => '${v.toStringAsFixed(2)} m',
137 ),
138 ];
141 void verify(Scene scene, FrameResult frame) {
142 if (!frame.passes.any((FramePass p) => p.name == 'directional shadows')) {
143 throw StateError('the sun drew no shadow map to soften');
144 }
145 if (frame.shadowsDenied > 0) {
146 throw StateError('the light was denied its shadow');
147 }
148 }
149}