Glass that shows the scene: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: wall (line 38), glass (line 66), live (line 112).
1/// A pane of glass in front of a striped wall, showing the wall through it.
2///
3/// Quoted by `transmission.md` and shown whole in the Source tab.
4library;
6import 'dart:math' as math;
8import 'package:flutter3d/flutter3d.dart';
9import 'package:flutter3d_showcase/src/demo/demo.dart';
10import 'package:vector_math/vector_math.dart';
12final class TransmissionDemo extends ShowcaseDemo {
13 double transmission = 1.0;
14 double roughness = 0.0;
15 double thickness = 0.9;
16 double ior = 1.5;
18 late final Material _pane;
19 late final Material _ball;
22 void configureView(DemoContext context) {
23 context.orbit
24 ..distance = 2.6
25 ..yaw = 0.0
26 ..pitch = 0.0;
27 }
30 Scene build(DemoContext context) {
31 final Quaternion upright = Quaternion.axisAngle(
32 Vector3(1.0, 0.0, 0.0),
33 math.pi / 2,
34 );
35 Material flat(Vector4 colour) =>
36 Material(lighting: LightingModel.unlit, baseColor: colour);
38 final DeviceMesh half = DeviceMesh.upload(
39 context.device,
40 const PlaneShape(width: 3.0, depth: 4.0).build(),
41 );
42 final DeviceMesh stripe = DeviceMesh.upload(
43 context.device,
44 const PlaneShape(width: 0.08, depth: 4.0).build(),
45 );
46 final Scene scene = Scene()
47 ..ambientIntensity = 0.2
48 ..add(
49 MeshNode(half, flat(Vector4(0.8, 0.1, 0.1, 1.0)), name: 'red wall')
50 ..setRotation(upright)
51 ..setPosition(-1.5, 0.0, -2.0),
52 )
53 ..add(
54 MeshNode(half, flat(Vector4(0.1, 0.2, 0.9, 1.0)), name: 'blue wall')
55 ..setRotation(upright)
56 ..setPosition(1.5, 0.0, -2.0),
57 );
58 for (var i = 0; i < 9; i++) {
59 scene.add(
60 MeshNode(stripe, flat(Vector4(0.95, 0.95, 0.9, 1.0)), name: 'stripe')
61 ..setRotation(upright)
62 ..setPosition(-2.0 + i * 0.5, 0.0, -1.98),
63 );
64 }
66 Material glass(String name, {required double thickness}) => Material(
67 name: name,
68 lighting: LightingModel.pbrLayered,
69 baseColor: Vector4(1.0, 1.0, 1.0, 1.0),
70 roughness: roughness,
71 doubleSided: true,
72 extensions: MaterialExtensions(
73 transmission: transmission,
74 thickness: thickness,
75 ior: ior,
76 ),
77 );
78 _pane = glass('pane', thickness: 0.0);
79 _ball = glass('ball', thickness: thickness);
80 scene
81 ..add(
82 MeshNode(
83 DeviceMesh.upload(
84 context.device,
85 const PlaneShape(width: 0.9, depth: 1.4).build(),
86 ),
87 _pane,
88 name: 'pane',
89 )
90 ..setRotation(upright)
91 ..setPosition(-0.6, 0.0, 0.0),
92 )
93 ..add(
94 MeshNode(
95 DeviceMesh.upload(
96 context.device,
97 SphereShape(radius: 0.45, segments: 48, rings: 24).build(),
98 ),
99 _ball,
100 name: 'ball',
101 )..setPosition(0.6, 0.0, 0.0),
102 )
103 ..add(
104 LightNode(name: 'sun', intensity: 3.0)
105 ..setLocalForward(Vector3(-0.3, -0.6, -1.0).normalized()),
106 );
107 return scene;
108 }
111 void update(DemoContext context, double dt) {
112 for (final (Material material, double depth) in <(Material, double)>[
113 (_pane, 0.0),
114 (_ball, thickness),
115 ]) {
116 material
117 ..roughness = roughness
118 ..extensions = MaterialExtensions(
119 transmission: transmission,
120 thickness: depth,
121 ior: ior,
122 );
123 }
124 }
127 List<DemoControl> controls(DemoContext context) => <DemoControl>[
128 SliderControl(
129 'Transmission',
130 min: 0,
131 max: 1,
132 value: () => transmission,
133 onChanged: (double v) => transmission = v,
134 ),
135 SliderControl(
136 'Roughness',
137 min: 0,
138 max: 1,
139 value: () => roughness,
140 onChanged: (double v) => roughness = v,
141 ),
142 SliderControl(
143 'Thickness',
144 min: 0,
145 max: 1.5,
146 value: () => thickness,
147 onChanged: (double v) => thickness = v,
148 // In the mesh's own units, as glTF measures it, not metres.
149 format: (double v) => '${v.toStringAsFixed(2)} units',
150 ),
151 SliderControl(
152 'Index of refraction',
153 min: 1.0,
154 max: 2.4,
155 value: () => ior,
156 onChanged: (double v) => ior = v,
157 format: (double v) => v.toStringAsFixed(2),
158 ),
159 ];
162 void verify(Scene scene, FrameResult frame) {
163 // A pane with no transmission is opaque, drawn in one pass as it always
164 // was; at an index of 1.5 it is ordinary metal-rough, and at another
165 // index it keeps that index's reflectance.
166 if (transmission <= 0.0) return;
167 final Set<String> ran = <String>{
168 for (final FramePass pass in frame.passes) pass.name,
169 };
170 if (!ran.contains('scene colour copy')) {
171 throw StateError(
172 'the glass is in the frame but the scene was not copied',
173 );
174 }
175 final FramePass? glass = frame.passes
176 .where((FramePass p) => p.name == 'transparent')
177 .firstOrNull;
178 if (glass == null || glass.drawCalls < 1) {
179 throw StateError('the pass that draws the glass drew nothing');
180 }
181 }
182}