flutter3d
Showcase Changelog 38 packages API reference

One bounce of diffuse light: the source

since 0.7.0 Light and environment

Regions the guide quotes: field (line 71), gather (line 79), live (line 84), shadow (line 90).

1/// One bounce of diffuse light: a grid of probes, each filled by casting rays
2/// out from it and recording what colour comes back.
3///
4/// Quoted by `irradiance_field.md` and shown whole in the Source tab.
5library;
6
7import 'package:flutter3d/flutter3d.dart';
8import 'package:flutter3d_showcase/src/demo/demo.dart';
9import 'package:vector_math/vector_math.dart';
11final class IrradianceFieldDemo extends ShowcaseDemo {
12 bool baked = true;
13 double ambient = 0.5;
15 late final IrradianceField _field;
16 late final Scene _scene;
18 @override
19 void configureView(DemoContext context) {
20 context.orbit
21 ..distance = 6.0
22 ..pitch = 0.35
23 ..yaw = 0.5;
24 context.orbit.target.setValues(-0.5, 1.0, 0.0);
25 }
27 @override
28 Scene build(DemoContext context) {
29 final Material floor = Material(
30 name: 'floor',
31 baseColor: Vector4(0.55, 0.55, 0.55, 1.0),
32 roughness: 0.95,
33 doubleSided: true,
34 );
35 final Material wall = Material(
36 name: 'red wall',
37 baseColor: Vector4(0.85, 0.08, 0.08, 1.0),
38 roughness: 0.95,
39 doubleSided: true,
40 );
42 final MeshNode wallNode =
43 MeshNode(
44 DeviceMesh.upload(
45 context.device,
46 const PlaneShape(width: 3, depth: 4).build(),
47 ),
48 wall,
49 name: 'wall',
50 )
51 ..setPosition(-2.0, 1.5, 0.0)
52 ..setRotation(Quaternion.axisAngle(Vector3(0.0, 0.0, 1.0), -1.5708));
54 final Scene scene = _scene = Scene()
55 ..add(
56 MeshNode(
57 DeviceMesh.upload(
58 context.device,
59 const PlaneShape(width: 6, depth: 6).build(),
60 ),
61 floor,
62 name: 'floor',
63 ),
64 )
65 ..add(wallNode)
66 ..add(
67 LightNode(name: 'sun', intensity: 4.0)
68 ..setLocalForward(Vector3(-0.6, -0.7, 0.15)),
69 );
71 _field = IrradianceField(
72 origin: Vector3(-1.0, 0.5, -1.0),
73 spacing: Vector3(1.0, 1.0, 1.0),
74 countX: 2,
75 countY: 2,
76 countZ: 2,
77 );
79 gather(_field, scene, rays: 96);
81 return scene;
82 }
84 @override
85 void update(DemoContext context, double dt) {
86 _scene.irradianceField = baked ? _field : null;
87 _scene.ambientIntensity = ambient;
88 }
90 // A one-sided wall, not a closed brush. A pass that records only back
91 // faces sees straight through a plane like this one, so the floor beside
92 // the wall would never fall into shadow and the field's bounce light would
93 // have nothing but full sun to compete with. The sun records both sides by
94 // default since 0.8.2; named here because the shadow depends on it.
95 @override
96 RenderSettings settings(DemoContext context) => const RenderSettings(
97 shadows: ShadowSettings(directionalCasterFaces: ShadowCasterFaces.both),
98 );
100 @override
101 List<DemoControl> controls(DemoContext context) => <DemoControl>[
102 ToggleControl(
103 'Field on',
104 value: () => baked,
105 onChanged: (bool v) => baked = v,
106 ),
107 SliderControl(
108 'Ambient strength',
109 min: 0,
110 max: 2,
111 value: () => ambient,
112 onChanged: (double v) => ambient = v,
113 ),
114 ];
116 @override
117 void verify(Scene scene, FrameResult frame) {
118 final Vector3 towardWall = _field.sample(
119 Vector3(-0.5, 1.0, -0.5),
120 Vector3(-1.0, 0.0, 0.0),
121 );
122 final Vector3 awayFromWall = _field.sample(
123 Vector3(-0.5, 1.0, -0.5),
124 Vector3(1.0, 0.0, 0.0),
125 );
126 if (towardWall.x < 0.01 || towardWall.x <= awayFromWall.x * 1.5) {
127 throw StateError(
128 'the probe facing the red wall ($towardWall) is not redder than the '
129 'one facing away from it ($awayFromWall); the bounce never arrived',
130 );
131 }
132 }