flutter3d
Showcase Changelog 38 packages API reference

Alpha modes: the source

since 0.7.0 Shading and materials

Regions the guide quotes: texture (line 31), materials (line 59), panels (line 70), live (line 108).

1/// The four ways a material treats its alpha channel: opaque, mask, blend and
2/// hashed.
3///
4/// Quoted by `alpha_modes.md` and shown whole in the Source tab.
5library;
6
7import 'dart:math' as math;
8import 'dart:typed_data';
9
10import 'package:flutter3d/flutter3d.dart';
11import 'package:flutter3d_showcase/src/demo/demo.dart';
12import 'package:vector_math/vector_math.dart';
14final class AlphaModesDemo extends ShowcaseDemo {
15 double opacity = 0.9;
16 double cutoff = 0.5;
17 bool doubleSided = false;
19 static const int _size = 64;
21 late final List<Material> _materials;
23 @override
24 void configureView(DemoContext context) {
25 context.orbit
26 ..distance = 7.0
27 ..yaw = 0.0
28 ..pitch = 0.05;
29 }
31 /// White everywhere, with an alpha that is one at the centre and falls to
32 /// zero at the rim: a soft disc.
33 ByteData _softDisc() {
34 final Uint8List bytes = Uint8List(_size * _size * 4);
35 for (var y = 0; y < _size; y++) {
36 for (var x = 0; x < _size; x++) {
37 final double dx = (x + 0.5) / _size * 2.0 - 1.0;
38 final double dy = (y + 0.5) / _size * 2.0 - 1.0;
39 final double alpha = (1.0 - math.sqrt(dx * dx + dy * dy)).clamp(0, 1);
40 final int at = (y * _size + x) * 4;
41 bytes[at] = 255;
42 bytes[at + 1] = 255;
43 bytes[at + 2] = 255;
44 bytes[at + 3] = (alpha * 255).round();
45 }
46 }
47 return bytes.buffer.asByteData();
48 }
50 @override
51 Scene build(DemoContext context) {
52 final TextureHandle disc = context.device.createTextureFromPixels(
53 width: _size,
54 height: _size,
55 format: TextureFormat.r8g8b8a8UNormInt,
56 pixels: _softDisc(),
57 )!;
59 _materials = <Material>[
60 for (final MaterialAlphaMode mode in MaterialAlphaMode.values)
61 Material(
62 name: mode.name,
63 baseColor: Vector4(0.95, 0.55, 0.2, opacity),
64 albedo: disc,
65 albedoSampler: SamplerOptions.linearClamp,
66 alphaMode: mode,
67 ),
68 ];
70 final DeviceMesh panel = DeviceMesh.upload(
71 context.device,
72 const PlaneShape(width: 1.4, depth: 1.4).build(),
73 );
74 final Quaternion upright = Quaternion.axisAngle(
75 Vector3(1.0, 0.0, 0.0),
76 math.pi / 2,
77 );
78 final Scene scene = Scene();
79 for (var i = 0; i < _materials.length; i++) {
80 scene.add(
81 MeshNode(panel, _materials[i], name: _materials[i].name)
82 ..setRotation(upright)
83 ..setPosition((i - 1.5) * 1.6, 0.0, 0.0),
84 );
85 }
87 final MeshNode wall =
88 MeshNode(
89 DeviceMesh.upload(
90 context.device,
91 const PlaneShape(width: 9, depth: 3).build(),
92 ),
93 Material(name: 'wall', baseColor: Vector4(0.2, 0.35, 0.55, 1.0)),
94 name: 'wall',
95 )
96 ..setRotation(upright)
97 ..setPosition(0.0, 0.0, -0.6);
98 return scene
99 ..add(wall)
100 ..add(
101 LightNode(name: 'sun', intensity: 3.0)
102 ..setLocalForward(Vector3(-0.2, -0.3, -1.0)),
103 );
104 }
106 @override
107 void update(DemoContext context, double dt) {
108 for (final Material material in _materials) {
109 material
110 ..baseColor.w = opacity
111 ..alphaCutoff = cutoff
112 ..doubleSided = doubleSided;
113 }
114 }
116 @override
117 List<DemoControl> controls(DemoContext context) => <DemoControl>[
118 SliderControl(
119 'Opacity',
120 min: 0.1,
121 max: 1,
122 value: () => opacity,
123 onChanged: (double v) => opacity = v,
124 ),
125 SliderControl(
126 'Alpha cutoff',
127 min: 0.05,
128 max: 0.95,
129 value: () => cutoff,
130 onChanged: (double v) => cutoff = v,
131 ),
132 ToggleControl(
133 'Double sided',
134 value: () => doubleSided,
135 onChanged: (bool v) => doubleSided = v,
136 ),
137 ];
139 @override
140 void verify(Scene scene, FrameResult frame) {
141 final Set<MaterialAlphaMode> modes = <MaterialAlphaMode>{
142 for (final MeshNode mesh in scene.meshes) mesh.material.alphaMode,
143 };
144 if (modes.length != MaterialAlphaMode.values.length) {
145 throw StateError('the panels do not cover every alpha mode: $modes');
146 }
147 final int sceneDraws = frame.passes
148 .where((FramePass pass) => pass.name == 'scene')
149 .fold(0, (int sum, FramePass pass) => sum + pass.drawCalls);
150 if (sceneDraws != scene.meshes.length) {
151 throw StateError(
152 'the scene pass drew $sceneDraws of ${scene.meshes.length} meshes',
153 );
154 }
155 }