flutter3d
Showcase Changelog 38 packages API reference

Image-based lighting: the source

since 0.3.0 Light and environment

Regions the guide quotes: environments (line 35), balls (line 46), use (line 71).

1/// Light that comes from the surroundings: a cube map of the sky, blurred once
2/// per roughness, is all that lights these spheres.
3///
4/// Quoted by `image_based_lighting.md` and shown whole in the Source tab.
5library;
6
7import 'dart:typed_data';
8
9import 'package:flutter3d/flutter3d.dart';
10import 'package:flutter3d_showcase/src/demo/demo.dart';
11import 'package:vector_math/vector_math.dart';
13final class ImageBasedLightingDemo extends ShowcaseDemo {
14 bool lit = true;
15 int source = 0;
16 double strength = 1.0;
17 double metallic = 1.0;
19 ({TextureHandle texture, int levels})? _fromSky;
20 ({TextureHandle texture, int levels})? _fromPanorama;
21 late final List<Material> _balls;
22 late final Scene _scene;
24 @override
25 void configureView(DemoContext context) {
26 context.orbit
27 ..distance = 7.5
28 ..pitch = 0.1
29 ..yaw = 0.0;
30 context.orbit.target.setValues(0.0, 0.0, 0.0);
31 }
33 @override
34 Scene build(DemoContext context) {
35 _fromSky = EnvironmentMap.fromSky(
36 context.device,
37 const SkySettings(enabled: true, glowStrength: 0.4, sunIntensity: 2.0),
38 );
39 _fromPanorama = EnvironmentMap.fromPanorama(
40 context.device,
41 _studioPanorama(),
42 width: 64,
43 height: 32,
44 );
46 final DeviceMesh sphere = DeviceMesh.upload(
47 context.device,
48 SphereShape(radius: 0.8, segments: 32, rings: 16).build(),
49 );
50 final Scene scene = _scene = Scene();
51 _balls = <Material>[];
52 for (var i = 0; i < 5; i++) {
53 final Material material = Material(
54 name: 'ball $i',
55 lighting: LightingModel.pbr,
56 baseColor: Vector4(0.95, 0.75, 0.45, 1.0),
57 metallic: metallic,
58 roughness: 0.05 + i * 0.24,
59 );
60 _balls.add(material);
61 scene.add(
62 MeshNode(sphere, material, name: 'ball $i')
63 ..setPosition(i * 1.9 - 3.8, 0.0, 0.0),
64 );
65 }
66 return scene;
67 }
69 @override
70 void update(DemoContext context, double dt) {
71 final ({TextureHandle texture, int levels})? chosen = source == 0
72 ? _fromSky
73 : _fromPanorama;
74 _scene
75 ..environment = lit ? chosen?.texture : null
76 ..environmentLevels = chosen?.levels ?? 0
77 ..ambientIntensity = strength;
78 for (final Material ball in _balls) {
79 ball.metallic = metallic;
80 }
81 }
83 @override
84 List<DemoControl> controls(DemoContext context) => <DemoControl>[
85 ToggleControl(
86 'Environment on',
87 value: () => lit,
88 onChanged: (bool v) => lit = v,
89 ),
90 ChoiceControl(
91 'Environment',
92 options: const <String>['Sky', 'Studio panorama'],
93 index: () => source,
94 onChanged: (int i) => source = i,
95 ),
96 SliderControl(
97 'Strength',
98 min: 0,
99 max: 2,
100 value: () => strength,
101 onChanged: (double v) => strength = v,
102 ),
103 SliderControl(
104 'Metallic',
105 min: 0,
106 max: 1,
107 value: () => metallic,
108 onChanged: (double v) => metallic = v,
109 ),
110 ];
112 @override
113 void verify(Scene scene, FrameResult frame) {
114 if (scene.lights.isNotEmpty) {
115 throw StateError('this page has no direct light on purpose');
116 }
117 if (scene.environment == null || scene.environmentLevels < 1) {
118 throw StateError('the environment cube was not built or not bound');
119 }
120 }
123// A 64 by 32 equirectangular picture: a cool sky, a warm floor and one bright
124// window to the side, so the two halves of the spheres differ.
125ByteData _studioPanorama() {
126 const int width = 64;
127 const int height = 32;
128 final ByteData pixels = ByteData(width * height * 4);
129 for (var y = 0; y < height; y++) {
130 for (var x = 0; x < width; x++) {
131 final bool sky = y < height ~/ 2;
132 final bool window = x > 40 && x < 52 && y > 8 && y < 16;
133 final (int, int, int) rgb = window
134 ? (255, 250, 235)
135 : sky
136 ? (70, 100, 150)
137 : (110, 80, 60);
138 final int at = (y * width + x) * 4;
139 pixels
140 ..setUint8(at, rgb.$1)
141 ..setUint8(at + 1, rgb.$2)
142 ..setUint8(at + 2, rgb.$3)
143 ..setUint8(at + 3, 255);
144 }
145 }
146 return pixels;