Contact shadows: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: props (line 31), sun (line 74), map (line 81), settings (line 86).
1/// A short march toward the sun that darkens the crease where an object meets
2/// what it stands on.
3///
4/// Quoted by `contact_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 ContactShadowsDemo extends ShowcaseDemo {
12 bool contact = true;
13 bool shadowMap = false;
14 double length = 0.4;
15 double steps = 12.0;
16 double thickness = 0.15;
17 double strength = 1.0;
19 late final LightNode _sun;
22 void configureView(DemoContext context) {
23 context.orbit
24 ..distance = 6.0
25 ..pitch = 0.5
26 ..yaw = 0.5;
27 }
30 Scene build(DemoContext context) {
31 final Material stone = Material(
32 name: 'stone',
33 baseColor: Vector4(0.78, 0.76, 0.72, 1.0),
34 roughness: 0.9,
35 doubleSided: true,
36 );
37 final Material clay = Material(
38 name: 'clay',
39 baseColor: Vector4(0.85, 0.45, 0.3, 1.0),
40 roughness: 0.7,
41 );
42 final Scene scene = Scene()
43 ..add(
44 MeshNode(
45 DeviceMesh.upload(
46 context.device,
47 const PlaneShape(width: 10, depth: 10).build(),
48 ),
49 stone,
50 name: 'ground',
51 ),
52 )
53 ..add(
54 MeshNode(
55 DeviceMesh.upload(
56 context.device,
57 CuboidShape(size: Vector3(1.4, 1.0, 1.0)).build(),
58 ),
59 clay,
60 name: 'crate',
61 )..setPosition(-1.2, 0.5, 0.0),
62 )
63 ..add(
64 MeshNode(
65 DeviceMesh.upload(
66 context.device,
67 SphereShape(radius: 0.6, segments: 32, rings: 16).build(),
68 ),
69 clay,
70 name: 'ball',
71 )..setPosition(1.3, 0.6, 0.3),
72 );
74 _sun = LightNode(name: 'sun', intensity: 3.0)
75 ..setLocalForward(Vector3(-0.6, -0.55, -0.4));
76 return scene..add(_sun);
77 }
80 void update(DemoContext context, double dt) {
81 _sun.castsShadow = shadowMap;
82 }
85 RenderSettings settings(DemoContext context) => RenderSettings(
86 contactShadows: ContactShadowSettings(
87 enabled: contact,
88 length: length,
89 steps: steps.round(),
90 thickness: thickness,
91 strength: strength,
92 ),
93 );
96 List<DemoControl> controls(DemoContext context) => <DemoControl>[
97 ToggleControl(
98 'Contact shadows',
99 value: () => contact,
100 onChanged: (bool v) => contact = v,
101 ),
102 ToggleControl(
103 'Shadow map too',
104 value: () => shadowMap,
105 onChanged: (bool v) => shadowMap = v,
106 ),
107 SliderControl(
108 'Length',
109 min: 0.05,
110 max: 1.5,
111 value: () => length,
112 onChanged: (double v) => length = v,
113 format: (double v) => '${v.toStringAsFixed(2)} m',
114 ),
115 SliderControl(
116 'Steps',
117 min: 1,
118 max: 16,
119 divisions: 15,
120 value: () => steps,
121 onChanged: (double v) => steps = v,
122 format: (double v) => '${v.round()}',
123 ),
124 SliderControl(
125 'Thickness',
126 min: 0.02,
127 max: 0.6,
128 value: () => thickness,
129 onChanged: (double v) => thickness = v,
130 format: (double v) => '${v.toStringAsFixed(2)} m',
131 ),
132 SliderControl(
133 'Strength',
134 min: 0,
135 max: 1,
136 value: () => strength,
137 onChanged: (double v) => strength = v,
138 ),
139 ];
142 void verify(Scene scene, FrameResult frame) {
143 final FramePass? pass = frame.passes
144 .where((FramePass p) => p.name == 'contact shadows')
145 .firstOrNull;
146 if (pass == null) {
147 throw StateError('the contact shadow pass did not run');
148 }
149 if (pass.drawCalls < 1) {
150 throw StateError('the contact shadow pass ran and drew nothing');
151 }
152 }
153}