Point and spot shadows: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: lamp (line 86), spot (line 96), settings (line 114).
1/// Shadows from a lamp and from a spotlight, drawn into a cube map.
2///
3/// Quoted by `point_light_shadows.md` and shown whole in the Source tab.
4library;
6import 'package:flutter3d/flutter3d.dart';
7import 'package:flutter3d_showcase/src/demo/demo.dart';
8import 'package:vector_math/vector_math.dart';
10final class PointLightShadowsDemo extends ShowcaseDemo {
11 double softness = 4.0;
12 double bias = 0.08;
13 int sizeChoice = 1;
14 int facesChoice = 1;
16 static const List<int> _sizes = <int>[128, 256, 512];
17 static const List<ShadowCasterFaces> _faces = <ShadowCasterFaces>[
18 ShadowCasterFaces.front,
19 ShadowCasterFaces.back,
20 ShadowCasterFaces.both,
21 ];
24 void configureView(DemoContext context) {
25 context.orbit
26 ..distance = 9.0
27 ..pitch = 0.55
28 ..yaw = 0.7;
29 }
32 Scene build(DemoContext context) {
33 final Material stone = Material(
34 name: 'stone',
35 baseColor: Vector4(0.78, 0.76, 0.72, 1.0),
36 roughness: 0.9,
37 );
38 final Material floorStone = stone.copy()..doubleSided = true;
39 final Material clay = Material(
40 name: 'clay',
41 baseColor: Vector4(0.85, 0.45, 0.3, 1.0),
42 roughness: 0.7,
43 );
44 final Scene scene = Scene()
45 ..add(
46 MeshNode(
47 DeviceMesh.upload(
48 context.device,
49 const PlaneShape(width: 12, depth: 12).build(),
50 ),
51 floorStone,
52 name: 'floor',
53 ),
54 )
55 ..add(
56 MeshNode(
57 DeviceMesh.upload(
58 context.device,
59 CuboidShape(size: Vector3(12.0, 4.0, 0.3)).build(),
60 ),
61 stone,
62 name: 'wall',
63 )..setPosition(0.0, 2.0, -5.0),
64 )
65 ..add(
66 MeshNode(
67 DeviceMesh.upload(
68 context.device,
69 CuboidShape(size: Vector3(1.0, 2.0, 1.0)).build(),
70 ),
71 clay,
72 name: 'column',
73 )..setPosition(-1.0, 1.0, -1.0),
74 )
75 ..add(
76 MeshNode(
77 DeviceMesh.upload(
78 context.device,
79 SphereShape(radius: 0.7, segments: 24, rings: 12).build(),
80 ),
81 clay,
82 name: 'ball',
83 )..setPosition(1.8, 0.7, -2.0),
84 );
86 scene.add(
87 LightNode(
88 name: 'lamp',
89 type: LightType.point,
90 intensity: 50.0,
91 range: 16.0,
92 castsShadow: true,
93 )..setPosition(1.5, 3.2, 1.0),
94 );
96 scene.add(
97 LightNode(
98 name: 'spot',
99 type: LightType.spot,
100 intensity: 90.0,
101 range: 16.0,
102 innerConeAngle: 0.2,
103 outerConeAngle: 0.5,
104 castsShadow: true,
105 )
106 ..setPosition(-4.0, 3.5, 3.5)
107 ..lookAt(Vector3(-1.0, 0.5, -1.5)),
108 );
109 return scene;
110 }
113 RenderSettings settings(DemoContext context) => RenderSettings(
114 shadows: ShadowSettings(
115 cubeResolution: _sizes[sizeChoice],
116 pointBias: bias,
117 pointSoftness: softness,
118 casterFaces: _faces[facesChoice],
119 ),
120 );
123 List<DemoControl> controls(DemoContext context) => <DemoControl>[
124 SliderControl(
125 'Softness',
126 min: 0,
127 max: 16,
128 value: () => softness,
129 onChanged: (double v) => softness = v,
130 format: (double v) => '${v.toStringAsFixed(1)} texels',
131 ),
132 SliderControl(
133 'Bias',
134 min: 0,
135 max: 0.5,
136 value: () => bias,
137 onChanged: (double v) => bias = v,
138 format: (double v) => '${v.toStringAsFixed(2)} m',
139 ),
140 ChoiceControl(
141 'Face size',
142 options: <String>[for (final int s in _sizes) '$s'],
143 index: () => sizeChoice,
144 onChanged: (int i) => sizeChoice = i,
145 ),
146 ChoiceControl(
147 'Casters draw',
148 options: const <String>['Front', 'Back', 'Both'],
149 index: () => facesChoice,
150 onChanged: (int i) => facesChoice = i,
151 ),
152 ];
155 void verify(Scene scene, FrameResult frame) {
156 if (!frame.passes.any((FramePass p) => p.name == 'point shadows')) {
157 throw StateError('no cube shadow pass ran');
158 }
159 if (frame.shadowsDenied > 0) {
160 throw StateError(
161 '${frame.shadowsDenied} light(s) asked for a shadow and were refused',
162 );
163 }
164 }
165}