1use bevy::{
4 asset::RenderAssetUsages,
5 camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},
6 image::{ImageAddressMode, ImageFilterMode},
7 input::common_conditions::input_just_pressed,
8 pbr::experimental::meshlet::{
9 MeshletMesh, MeshletMesh3d, MeshletPlugin,
10 MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR,
11 },
12 prelude::*,
13 render::render_resource::{AsBindGroup, Extent3d, TextureDimension, TextureFormat},
14};
15
16fn main() {
17 App::new()
18 .add_plugins((
19 DefaultPlugins,
20 MeshletPlugin {
21 cluster_buffer_slots: 1 << 14,
22 },
23 MaterialPlugin::<MeshletDebugMaterial>::default(),
24 FreeCameraPlugin,
25 ))
26 .add_systems(Startup, setup)
27 .add_systems(
28 Update,
29 toggle_materials.run_if(input_just_pressed(KeyCode::KeyM)),
30 )
31 .insert_resource(GlobalAmbientLight {
32 brightness: 500.0,
33 ..default()
34 })
35 .run();
36}
37
38fn setup(
39 mut commands: Commands,
40 mut standard_materials: ResMut<Assets<StandardMaterial>>,
41 mut debug_materials: ResMut<Assets<MeshletDebugMaterial>>,
42 mut meshes: ResMut<Assets<Mesh>>,
43 mut meshlets: ResMut<Assets<MeshletMesh>>,
44 mut images: ResMut<Assets<Image>>,
45) {
46 commands.spawn_scene_list(bsn_list! {
47 Camera3d
48 Transform::from_translation(Vec3::new(0.0, 1.5, -3.0))
49 .looking_at(vec3(0.0, 0.0, 1.5), Vec3::Y)
50 Msaa::Off
51 FreeCamera
52 --
53 DirectionalLight {
54 illuminance: light_consts::lux::FULL_DAYLIGHT
55 }
56 Transform::from_xyz(0.0, 1.0, -0.5).looking_at(Vec3::ZERO, Vec3::Z)
57 --
58 Text::new("M: toggle debug material")
59 Node {
60 position_type: PositionType::Absolute,
61 top: px(12),
62 left: px(12),
63 }
64 --
65 Text::new("meshlet")
66 Node {
67 position_type: PositionType::Absolute,
68 bottom: px(12),
69 left: percent(25),
70 }
71 --
72 Text::new("regular")
73 Node {
74 position_type: PositionType::Absolute,
75 bottom: px(12),
76 right: percent(25),
77 }
78 });
79
80 let sphere_mesh = Sphere::default().mesh().uv(48, 27);
81 let cube_mesh = Cuboid::from_length(0.8).mesh().build();
82
83 let sphere_meshlet = MeshletMesh::from_mesh(
84 &sphere_mesh,
85 MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR,
86 )
87 .unwrap();
88
89 let cube_meshlet = MeshletMesh::from_mesh(
90 &cube_mesh,
91 MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR,
92 )
93 .unwrap();
94
95 let sphere_mesh_asset = meshes.add(sphere_mesh);
96 let cube_mesh_asset = meshes.add(cube_mesh);
97
98 let sphere_meshlet_asset = meshlets.add(sphere_meshlet);
99 let cube_meshlet_asset = meshlets.add(cube_meshlet);
100
101 let mipmap_material = mipmap_material(&mut standard_materials, &mut images);
102 let debug_material = debug_materials.add(MeshletDebugMaterial::default());
103
104 for distance in [0.0, 8.0, 32.0, 128.0] {
105 commands.spawn_scene_list(bsn_list! {
106 Mesh3d({sphere_mesh_asset.clone()})
107 MeshMaterial3d::<StandardMaterial>({mipmap_material.clone()})
108 Transform::from_xyz(-0.5, 0.0, distance)
109 --
110 MeshletMesh3d({sphere_meshlet_asset.clone()})
111 MeshMaterial3d::<StandardMaterial>({mipmap_material.clone()})
112 Transform::from_xyz(0.5, 0.0, distance)
113 --
114 Mesh3d({cube_mesh_asset.clone()})
115 MeshMaterial3d::<StandardMaterial>({mipmap_material.clone()})
116 Transform::from_xyz(-1.5, 0.0, distance)
117 --
118 MeshletMesh3d({cube_meshlet_asset.clone()})
119 MeshMaterial3d::<StandardMaterial>({mipmap_material.clone()})
120 Transform::from_xyz(1.5, 0.0, distance)
121 });
122 }
123
124 commands.insert_resource(Materials {
125 mipmap: mipmap_material,
126 debug: debug_material,
127 });
128}
129
130fn toggle_materials(
131 mut commands: Commands,
132 materials: Res<Materials>,
133 meshlet_materials: Query<
134 (
135 Entity,
136 Option<&MeshMaterial3d<StandardMaterial>>,
137 Option<&MeshMaterial3d<MeshletDebugMaterial>>,
138 ),
139 With<MeshletMesh3d>,
140 >,
141) {
142 for (entity, mipmap, debug) in meshlet_materials {
143 if mipmap.is_some() {
144 commands
145 .entity(entity)
146 .remove::<MeshMaterial3d<StandardMaterial>>()
147 .insert(MeshMaterial3d(materials.debug.clone()));
148 } else if debug.is_some() {
149 commands
150 .entity(entity)
151 .remove::<MeshMaterial3d<MeshletDebugMaterial>>()
152 .insert(MeshMaterial3d(materials.mipmap.clone()));
153 }
154 }
155}
156
157#[derive(Resource)]
158struct Materials {
159 mipmap: Handle<StandardMaterial>,
160 debug: Handle<MeshletDebugMaterial>,
161}
162
163#[derive(Asset, TypePath, AsBindGroup, Clone, Default)]
164struct MeshletDebugMaterial {
165 _dummy: (),
166}
167
168impl Material for MeshletDebugMaterial {}
169
170fn checkerboard(size: usize, color: Srgba) -> Vec<u8> {
171 let color = color.to_u8_array();
172 let black = Srgba::BLACK.to_u8_array();
173 let shift = size.ilog2().saturating_sub(3);
174
175 (0..(size * size))
176 .into_iter()
177 .flat_map(|i| {
178 let x = i.rem_euclid(size) >> shift;
179 let y = (i / size) >> shift;
180 if ((x + y) & 1) == 0 {
181 color
182 } else {
183 black
184 }
185 })
186 .collect()
187}
188
189fn mipmap_material(
191 standard_materials: &mut Assets<StandardMaterial>,
192 images: &mut Assets<Image>,
193) -> Handle<StandardMaterial> {
194 let mip_colors = [
195 Srgba::rgb(1.0, 0.0, 0.0),
196 Srgba::rgb(0.0, 1.0, 0.0),
197 Srgba::rgb(0.0, 0.0, 1.0),
198 Srgba::rgb(1.0, 1.0, 0.0),
199 Srgba::rgb(0.0, 1.0, 1.0),
200 Srgba::rgb(1.0, 0.0, 1.0),
201 ];
202
203 let size = 256;
204
205 let mut image = Image::new_uninit(
206 Extent3d {
207 width: size as u32,
208 height: size as u32,
209 depth_or_array_layers: 1,
210 },
211 TextureDimension::D2,
212 TextureFormat::Rgba8UnormSrgb,
213 RenderAssetUsages::default(),
214 );
215
216 image.texture_descriptor.mip_level_count = mip_colors.len() as u32;
217
218 image
219 .sampler
220 .get_or_init_descriptor()
221 .set_filter(ImageFilterMode::Linear)
222 .set_address_mode(ImageAddressMode::Repeat);
223
224 image.data = Some(
225 mip_colors
226 .iter()
227 .enumerate()
228 .flat_map(|(level, color)| checkerboard(size / 2_usize.pow(level as u32), *color))
229 .collect(),
230 );
231
232 standard_materials.add(StandardMaterial {
233 base_color_texture: Some(images.add(image)),
234 perceptual_roughness: 1.0,
235 ..default()
236 })
237}