Volumetric fog: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: torches (line 36), cells (line 66), fog (line 67).
1/// Volumetric fog: air with a thickness that the lights shine through, so
2/// each lamp glows in the air around it.
3///
4/// Quoted by `volumetric_fog.md` and shown whole in the Source tab.
5library;
7import 'dart:math' as math;
9import 'package:flutter3d/flutter3d.dart';
10import 'package:flutter3d_showcase/src/demo/demo.dart';
11import 'package:vector_math/vector_math.dart';
13final class VolumetricFogDemo extends ShowcaseDemo {
14 bool fog = true;
15 bool litByLamps = true;
16 double density = 0.3;
17 double heightFalloff = 1.0;
20 void configureView(DemoContext context) {
21 context.orbit
22 ..distance = 9.8
23 ..pitch = 1.15
24 ..yaw = 0.0;
25 }
27 /// A saturated colour at [t] of the way round the hue circle.
28 static Vector3 _hue(double t) {
29 double channel(double offset) =>
30 (0.5 + 0.5 * math.cos(2 * math.pi * (t + offset))).clamp(0.0, 1.0);
31 return Vector3(channel(0.0), channel(2 / 3), channel(1 / 3));
32 }
35 Scene build(DemoContext context) {
36 final Scene scene = Scene()
37 ..ambientIntensity = 0.03
38 ..add(
39 MeshNode(
40 DeviceMesh.upload(
41 context.device,
42 CuboidShape(size: Vector3(12.0, 0.1, 12.0)).build(),
43 ),
44 Material(name: 'floor', lighting: LightingModel.lambert),
45 name: 'floor',
46 )..setPosition(0.0, -0.05, 0.0),
47 );
48 for (var i = 0; i < 8; i++) {
49 for (var j = 0; j < 8; j++) {
50 scene.add(
51 LightNode(
52 name: 'torch $i $j',
53 type: LightType.point,
54 intensity: 1.0,
55 range: 1.0,
56 color: _hue((i * 8 + j) / 64.0),
57 )..setPosition(i - 3.5, 0.3, j - 3.5),
58 );
59 }
60 }
61 return scene;
62 }
65 RenderSettings settings(DemoContext context) => RenderSettings(
66 clusteredLights: litByLamps,
67 volumetricFog: VolumetricFogSettings(
68 enabled: fog,
69 density: density,
70 heightFalloff: heightFalloff,
71 steps: 16,
72 distance: 20.0,
73 color: Vector3.all(1.0),
74 ),
75 );
78 List<DemoControl> controls(DemoContext context) => <DemoControl>[
79 ToggleControl(
80 'Volumetric fog',
81 value: () => fog,
82 onChanged: (bool v) => fog = v,
83 ),
84 ToggleControl(
85 'Clustered lights',
86 value: () => litByLamps,
87 onChanged: (bool v) => litByLamps = v,
88 ),
89 SliderControl(
90 'Density',
91 min: 0,
92 max: 0.8,
93 value: () => density,
94 onChanged: (double v) => density = v,
95 format: (double v) => '${v.toStringAsFixed(2)} per m',
96 ),
97 SliderControl(
98 'Height falloff',
99 min: 0,
100 max: 3,
101 value: () => heightFalloff,
102 onChanged: (double v) => heightFalloff = v,
103 format: (double v) => '${v.toStringAsFixed(2)} per m',
104 ),
105 ];
108 void verify(Scene scene, FrameResult frame) {
109 final PassSkip? skipped = frame.skipReasonOf('volumetric fog');
110 if (skipped != null) {
111 throw StateError('the fog pass was skipped: $skipped');
112 }
113 if (!frame.passes.any((FramePass p) => p.name == 'volumetric fog')) {
114 throw StateError('the fog pass did not run');
115 }
116 }
117}