1use crate::radio::{feathers_option_buttons, main_ui_node_scene, RadioButtonOptionValue};
8use bevy::{
9 color::palettes::css::{BLUE, GREEN, RED, YELLOW},
10 core_pipeline::{oit::OrderIndependentTransparencySettings, prepass::DepthPrepass},
11 ecs::system::SystemParam,
12 feathers::{dark_theme::create_dark_theme, theme::UiTheme, FeathersPlugins},
13 pbr::{ExtendedMaterial, MaterialExtension},
14 picking::window::update_window_hits,
15 prelude::*,
16 render::render_resource::AsBindGroup,
17 shader::ShaderRef,
18 ui::Checked,
19 ui_widgets::{RadioButton, RadioGroup, ValueChange},
20 window::{CursorIcon, PrimaryWindow, SystemCursorIcon},
21};
22
23#[path = "../helpers/radio.rs"]
24mod radio;
25
26const SCENES: &[(&str, &str, fn(&mut Commands, &mut SceneResources))] = &[
28 ("1", "Three balls", spawn_spheres),
29 ("2", "Stacked quads", spawn_quads),
30 ("3", "Opaque occlusion test", spawn_occlusion_test),
31 ("4", "Auto instancing test", spawn_auto_instancing_test),
32 ("5", "Custom material demo", spawn_custom_material),
33];
34
35#[derive(Resource)]
37struct AppState {
38 oit_settings: OrderIndependentTransparencySettings,
40 use_oit: bool,
42 use_depth_prepass: bool,
44 current_scene_id: usize,
46}
47
48impl Default for AppState {
49 fn default() -> Self {
50 Self {
51 oit_settings: Default::default(),
52 use_oit: true,
53 use_depth_prepass: true,
54 current_scene_id: 0,
55 }
56 }
57}
58
59#[derive(Clone, PartialEq)]
61enum AppSetting {
62 EnableOIT(bool),
64 UseDepthPrepass(bool),
66 ChangeScene(usize),
68}
69
70impl Default for AppSetting {
72 fn default() -> Self {
73 Self::EnableOIT(true)
74 }
75}
76
77#[derive(Component, Clone, Default, PartialEq)]
79enum RadioGroupSetting {
80 #[default]
81 EnableOIT,
82 UseDepthPrepass,
83 ChangeScene,
84}
85
86#[derive(SystemParam)]
88struct SceneResources<'w> {
89 meshes: ResMut<'w, Assets<Mesh>>,
90 materials: ResMut<'w, Assets<StandardMaterial>>,
91 extended_materials:
92 ResMut<'w, Assets<ExtendedMaterial<StandardMaterial, CheckeredMaterialExtension>>>,
93 custom_materials: ResMut<'w, Assets<NoisyOpacityMaterial>>,
94 asset_server: Res<'w, AssetServer>,
95}
96
97fn main() {
98 App::new()
99 .add_plugins((DefaultPlugins, FeathersPlugins))
100 .add_plugins(MaterialPlugin::<NoisyOpacityMaterial>::default())
101 .add_plugins(MaterialPlugin::<
102 ExtendedMaterial<StandardMaterial, CheckeredMaterialExtension>,
103 >::default())
104 .insert_resource(UiTheme(create_dark_theme()))
105 .init_resource::<AppState>()
106 .add_systems(Startup, setup)
107 .add_systems(Update, handle_keyboard_shortcuts)
108 .add_systems(Update, scene_change_watcher)
109 .add_systems(Update, update_window_hits)
110 .add_observer(on_radio_value_change)
111 .run();
112}
113
114#[derive(Component)]
116struct BaseTransform(Transform);
117
118fn setup(
120 mut commands: Commands,
121 mut resources: SceneResources,
122 app_state: Res<AppState>,
123 window: Single<Entity, With<PrimaryWindow>>,
124) {
125 spawn_ui(&mut commands, &app_state);
127
128 commands
130 .entity(*window)
131 .observe(
132 |event: On<PointerDrag>,
133 mut commands: Commands,
134 mut camera_transforms: Single<
135 (&mut Transform, &BaseTransform),
136 With<Camera3d>,
137 >| {
138 commands
139 .entity(event.entity)
140 .insert(CursorIcon::System(SystemCursorIcon::Grabbing));
141
142 let (ref mut transform, base_transform) = *camera_transforms;
144 **transform = base_transform.0;
145
146 const RADIANS_PER_PIXEL: f32 = -std::f32::consts::PI / 600.0;
147 let angle = event.distance.x * RADIANS_PER_PIXEL;
148 transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(angle));
149 },
150 )
151 .observe(
152 |_: On<PointerDragStart>,
153 mut commands: Commands,
154 camera: Single<(Entity, &Transform), With<Camera3d>>| {
155 let (camera, transform) = *camera;
156 commands
158 .entity(camera)
159 .insert(BaseTransform(*transform));
160 },
161 )
162 .observe(
163 |_: On<PointerDragEnd>,
164 mut commands: Commands,
165 window: Single<Entity, With<PrimaryWindow>>| {
166 commands
167 .entity(*window)
168 .insert(CursorIcon::System(SystemCursorIcon::Default));
169 },
170 );
171
172 let mut camera = commands.spawn((
174 Camera3d::default(),
175 Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
176 Msaa::Off,
178 ));
179
180 if app_state.use_oit {
181 camera.insert(OrderIndependentTransparencySettings {
183 ..app_state.oit_settings
184 });
185 }
186
187 if app_state.use_depth_prepass {
188 camera.insert(DepthPrepass);
190 }
191
192 commands.spawn((
194 PointLight {
195 shadow_maps_enabled: false,
196 ..default()
197 },
198 Transform::from_xyz(4.0, 8.0, 4.0),
199 ));
200
201 SCENES[0].2(&mut commands, &mut resources); }
204
205fn handle_keyboard_shortcuts(
207 keyboard_input: Res<ButtonInput<KeyCode>>,
208 mut app_state: ResMut<AppState>,
209 camera: Single<(Entity, Has<OrderIndependentTransparencySettings>), With<Camera3d>>,
210 radio_group_q: Query<(&Children, &RadioGroupSetting), With<RadioGroup>>,
211 setting_q: Query<(Entity, &RadioButtonOptionValue<AppSetting>), With<RadioButton>>,
212 mut commands: Commands,
213) {
214 let new_setting = if keyboard_input.just_pressed(KeyCode::ArrowRight)
215 || keyboard_input.just_pressed(KeyCode::ArrowLeft)
216 {
217 let n = app_state.current_scene_id + SCENES.len();
218 if keyboard_input.pressed(KeyCode::ArrowLeft) {
219 AppSetting::ChangeScene((n - 1) % SCENES.len())
220 } else {
221 AppSetting::ChangeScene((n + 1) % SCENES.len())
222 }
223 } else if keyboard_input.just_pressed(KeyCode::KeyT) {
224 AppSetting::EnableOIT(!app_state.use_oit)
225 } else if keyboard_input.just_pressed(KeyCode::KeyD) {
226 AppSetting::UseDepthPrepass(!app_state.use_depth_prepass)
227 } else {
228 return;
229 };
230
231 change_setting(
232 &new_setting,
233 &mut app_state,
234 camera,
235 radio_group_q,
236 setting_q,
237 &mut commands,
238 );
239}
240
241fn spawn_ui(commands: &mut Commands, app_state: &AppState) {
242 commands.spawn((
244 Node {
245 position_type: PositionType::Absolute,
246 top: px(12.0),
247 left: px(0.0),
248 right: px(0.0),
249 justify_content: JustifyContent::Center,
250 ..default()
251 },
252 children![(
253 Text::new("Drag view to spin around"),
254 TextFont {
255 font_size: FontSize::Px(18.0),
256 ..default()
257 },
258 )],
259 ));
260
261 commands.spawn_scene(bsn! {
263 @main_ui_node_scene()
264 on (|mut event: On<PointerDrag>| {
266 event.propagate(false);
267 })
268 Children [
269 RadioGroupSetting::ChangeScene
270 @feathers_option_buttons(
271 "Scene ([←] or [→])",
272 &(SCENES
273 .iter()
274 .enumerate()
275 .map(|(i, scene)| (AppSetting::ChangeScene(i), scene.1))
276 .collect::<Vec<_>>()),
277 app_state.current_scene_id,
278 )
279 --
280 RadioGroupSetting::EnableOIT
281 @feathers_option_buttons(
282 "Order Independent [T]ransparency",
283 &[
284 (AppSetting::EnableOIT(true), "On"),
285 (AppSetting::EnableOIT(false), "Off")
286 ],
287 if app_state.use_oit {
288 0
289 } else {
290 1
291 }
292 )
293 --
294 RadioGroupSetting::UseDepthPrepass
295 @feathers_option_buttons(
296 "[D]epth Prepass",
297 &[
298 (AppSetting::UseDepthPrepass(true), "On"),
299 (AppSetting::UseDepthPrepass(false), "Off")
300 ],
301 if app_state.use_depth_prepass {
302 0
303 } else {
304 1
305 }
306 )
307 ]
308 });
309}
310
311fn on_radio_value_change(
313 event: On<ValueChange<Entity>>,
314 new_value_q: Query<&RadioButtonOptionValue<AppSetting>>,
315 mut commands: Commands,
316 mut app_state: ResMut<AppState>,
317 camera: Single<(Entity, Has<OrderIndependentTransparencySettings>), With<Camera3d>>,
318 radio_group_q: Query<(&Children, &RadioGroupSetting), With<RadioGroup>>,
319 setting_q: Query<(Entity, &RadioButtonOptionValue<AppSetting>), With<RadioButton>>,
320) {
321 let Ok(RadioButtonOptionValue(setting)) = new_value_q.get(event.value) else {
322 return;
323 };
324
325 change_setting(
326 setting,
327 &mut app_state,
328 camera,
329 radio_group_q,
330 setting_q,
331 &mut commands,
332 );
333}
334
335fn change_setting(
338 new_app_setting: &AppSetting,
339 app_state: &mut AppState,
340 camera: Single<(Entity, Has<OrderIndependentTransparencySettings>), With<Camera3d>>,
341 radio_group_q: Query<(&Children, &RadioGroupSetting), With<RadioGroup>>,
342 setting_q: Query<(Entity, &RadioButtonOptionValue<AppSetting>), With<RadioButton>>,
343 commands: &mut Commands,
344) {
345 let radio_group_setting = match *new_app_setting {
346 AppSetting::EnableOIT(value) => {
347 app_state.use_oit = value;
348 if app_state.use_oit {
349 commands.entity(camera.0).insert(app_state.oit_settings);
350 } else {
351 commands
352 .entity(camera.0)
353 .remove::<OrderIndependentTransparencySettings>();
354 }
355 RadioGroupSetting::EnableOIT
356 }
357 AppSetting::UseDepthPrepass(value) => {
358 app_state.use_depth_prepass = value;
359
360 if app_state.use_depth_prepass {
361 commands.entity(camera.0).insert(DepthPrepass);
362 } else {
363 commands.entity(camera.0).remove::<DepthPrepass>();
364 }
365 RadioGroupSetting::UseDepthPrepass
366 }
367 AppSetting::ChangeScene(id) => {
368 if id != app_state.current_scene_id {
369 app_state.current_scene_id = id;
370 }
371 RadioGroupSetting::ChangeScene
372 }
374 };
375
376 for (children, group_setting) in radio_group_q.iter() {
378 if *group_setting != radio_group_setting {
379 continue;
380 }
381 for (entity, setting) in setting_q.iter_many(children).matched() {
382 if setting.0 == *new_app_setting {
383 commands.entity(entity).insert(Checked);
384 } else {
385 commands.entity(entity).remove::<Checked>();
386 }
387 }
388 }
389}
390
391fn scene_change_watcher(
393 app_state: Res<AppState>,
394 mut prev_scene_id: Local<usize>,
395 entities: Query<Entity, With<Mesh3d>>,
396 mut resources: SceneResources<'_>,
397 mut commands: Commands,
398) {
399 if app_state.is_changed() && *prev_scene_id != app_state.current_scene_id {
400 for e in &entities {
402 commands.entity(e).despawn();
403 }
404 SCENES[app_state.current_scene_id].2(&mut commands, &mut resources);
405 *prev_scene_id = app_state.current_scene_id;
406 }
407}
408
409fn spawn_spheres(commands: &mut Commands, resources: &mut SceneResources) {
413 let meshes = &mut resources.meshes;
414 let materials = &mut resources.materials;
415
416 let pos_a = Vec3::new(-1.0, 0.75, 0.0);
417 let pos_b = Vec3::new(0.0, -0.75, 0.0);
418 let pos_c = Vec3::new(1.0, 0.75, 0.0);
419
420 let offset = Vec3::new(0.0, 0.0, 0.0);
421
422 let sphere_handle = meshes.add(Sphere::new(2.0).mesh());
423
424 let alpha = 0.25;
425
426 commands.spawn((
427 Mesh3d(sphere_handle.clone()),
428 MeshMaterial3d(materials.add(StandardMaterial {
429 base_color: RED.with_alpha(alpha).into(),
430 alpha_mode: AlphaMode::Blend,
431 ..default()
432 })),
433 Transform::from_translation(pos_a + offset),
434 ));
435 commands.spawn((
436 Mesh3d(sphere_handle.clone()),
437 MeshMaterial3d(materials.add(StandardMaterial {
438 base_color: GREEN.with_alpha(alpha).into(),
439 alpha_mode: AlphaMode::Premultiplied,
440 ..default()
441 })),
442 Transform::from_translation(pos_b + offset),
443 ));
444 commands.spawn((
445 Mesh3d(sphere_handle.clone()),
446 MeshMaterial3d(materials.add(StandardMaterial {
447 base_color: BLUE.with_alpha(alpha).into(),
448 alpha_mode: AlphaMode::Add,
449 ..default()
450 })),
451 Transform::from_translation(pos_c + offset),
452 ));
453}
454
455fn spawn_quads(commands: &mut Commands, resources: &mut SceneResources) {
458 let meshes = &mut resources.meshes;
459 let materials = &mut resources.materials;
460
461 let quad_handle = meshes.add(Rectangle::new(3.0, 3.0).mesh());
462 let xform = |x, y, z| {
463 Transform::from_rotation(Quat::from_rotation_y(0.5))
464 .mul_transform(Transform::from_xyz(x, y, z))
465 };
466
467 let common_params = StandardMaterial {
469 alpha_mode: AlphaMode::Blend,
470 cull_mode: None,
471 ..default()
472 };
473
474 commands.spawn((
475 Mesh3d(quad_handle.clone()),
476 MeshMaterial3d(materials.add(StandardMaterial {
477 base_color: RED.with_alpha(0.5).into(),
478 ..common_params.clone()
479 })),
480 xform(1.0, -0.1, 0.),
481 ));
482 commands.spawn((
483 Mesh3d(quad_handle.clone()),
484 MeshMaterial3d(materials.add(StandardMaterial {
485 base_color: BLUE.with_alpha(0.8).into(),
486 ..common_params.clone()
487 })),
488 xform(0.5, 0.2, -0.5),
489 ));
490 commands.spawn((
491 Mesh3d(quad_handle.clone()),
492 MeshMaterial3d(materials.add(StandardMaterial {
493 base_color: GREEN.with_green(1.0).with_alpha(0.5).into(),
494 ..common_params.clone()
495 })),
496 xform(0.0, 0.4, -1.),
497 ));
498 commands.spawn((
499 Mesh3d(quad_handle.clone()),
500 MeshMaterial3d(materials.add(StandardMaterial {
501 base_color: YELLOW.with_alpha(0.3).into(),
502 ..common_params.clone()
503 })),
504 xform(-0.5, 0.6, -1.1),
505 ));
506 commands.spawn((
507 Mesh3d(quad_handle.clone()),
508 MeshMaterial3d(materials.add(StandardMaterial {
509 base_color: BLUE.with_alpha(0.2).into(),
510 ..common_params
511 })),
512 xform(-0.8, 0.8, -1.2),
513 ));
514}
515
516fn spawn_occlusion_test(commands: &mut Commands, resources: &mut SceneResources) {
520 let meshes = &mut resources.meshes;
521 let materials = &mut resources.materials;
522
523 let sphere_handle = meshes.add(Sphere::new(1.0).mesh());
524 let cube_handle = meshes.add(Cuboid::from_size(Vec3::ONE).mesh());
525 let cube_material = materials.add(Color::srgb(0.8, 0.7, 0.6));
526
527 let x = -2.5;
529 commands.spawn((
530 Mesh3d(cube_handle.clone()),
531 MeshMaterial3d(cube_material.clone()),
532 Transform::from_xyz(x, 0.0, 2.0),
533 ));
534 commands.spawn((
535 Mesh3d(sphere_handle.clone()),
536 MeshMaterial3d(materials.add(StandardMaterial {
537 base_color: RED.with_alpha(0.5).into(),
538 alpha_mode: AlphaMode::Blend,
539 ..default()
540 })),
541 Transform::from_xyz(x, 0., 0.),
542 ));
543
544 commands.spawn((
546 Mesh3d(cube_handle.clone()),
547 MeshMaterial3d(cube_material.clone()),
548 Transform::from_xyz(x, 0.0, 1.0),
549 ));
550 commands.spawn((
551 Mesh3d(sphere_handle.clone()),
552 MeshMaterial3d(materials.add(StandardMaterial {
553 base_color: RED.with_alpha(0.5).into(),
554 alpha_mode: AlphaMode::Blend,
555 ..default()
556 })),
557 Transform::from_xyz(0., 0., 0.),
558 ));
559
560 let x = 2.5;
562 commands.spawn((
563 Mesh3d(cube_handle.clone()),
564 MeshMaterial3d(cube_material.clone()),
565 Transform::from_xyz(x, 0.0, -2.0),
566 ));
567 commands.spawn((
568 Mesh3d(sphere_handle.clone()),
569 MeshMaterial3d(materials.add(StandardMaterial {
570 base_color: RED.with_alpha(0.5).into(),
571 alpha_mode: AlphaMode::Blend,
572 ..default()
573 })),
574 Transform::from_xyz(x, 0., 0.),
575 ));
576}
577
578fn spawn_auto_instancing_test(commands: &mut Commands, resources: &mut SceneResources) {
581 let meshes = &mut resources.meshes;
582 let materials = &mut resources.materials;
583 let asset_server = &mut resources.asset_server;
584
585 let cube = meshes.add(Cuboid::new(1.0, 1.0, 1.0));
586 let material_handle = materials.add(StandardMaterial {
587 alpha_mode: AlphaMode::Blend,
588 base_color_texture: Some(asset_server.load("textures/slice_square.png")),
589 ..Default::default()
590 });
591 let mut bundles = Vec::with_capacity(3 * 3 * 3);
592
593 for z in -1..=1 {
594 for y in -1..=1 {
595 for x in -1..=1 {
596 bundles.push((
597 Mesh3d(cube.clone()),
598 MeshMaterial3d(material_handle.clone()),
599 Transform::from_xyz(x as f32 * 2.0, y as f32 * 2.0, z as f32 * 2.0),
600 ));
601 }
602 }
603 }
604 commands.spawn_batch(bundles);
605}
606
607const EXTENDED_MATERIAL_SHADER_ASSET_PATH: &str = "shaders/oit_compatible_extended_material.wesl";
608
609#[derive(Asset, AsBindGroup, Reflect, Debug, Clone, Default)]
612struct CheckeredMaterialExtension {
613 #[uniform(100)]
614 color_1: LinearRgba,
615 #[uniform(100)]
616 color_2: LinearRgba,
617}
618
619impl MaterialExtension for CheckeredMaterialExtension {
620 fn fragment_shader() -> ShaderRef {
621 EXTENDED_MATERIAL_SHADER_ASSET_PATH.into()
622 }
623
624 fn alpha_mode() -> Option<AlphaMode> {
625 Some(AlphaMode::Blend)
626 }
627}
628
629const CUSTOM_MATERIAL_SHADER_ASSET_PATH: &str = "shaders/oit_compatible_custom_material.wesl";
630
631#[derive(Asset, AsBindGroup, Reflect, Debug, Clone, Default)]
633struct NoisyOpacityMaterial {
634 #[uniform(0)]
635 color: LinearRgba,
636}
637
638impl Material for NoisyOpacityMaterial {
639 fn fragment_shader() -> ShaderRef {
640 CUSTOM_MATERIAL_SHADER_ASSET_PATH.into()
641 }
642
643 fn alpha_mode(&self) -> AlphaMode {
645 AlphaMode::Blend
646 }
647
648 fn specialize(
650 _pipeline: &bevy::pbr::MaterialPipeline,
651 descriptor: &mut bevy_render::render_resource::RenderPipelineDescriptor,
652 _layout: &bevy::mesh::MeshVertexBufferLayoutRef,
653 _key: bevy::pbr::MaterialPipelineKey<Self>,
654 ) -> Result<(), bevy_render::render_resource::SpecializedMeshPipelineError> {
655 descriptor.primitive.cull_mode = None;
656 Ok(())
657 }
658}
659fn spawn_custom_material(commands: &mut Commands, resources: &mut SceneResources) {
661 let meshes = &mut resources.meshes;
662 let ext_materials = &mut resources.extended_materials;
663 let custom_materials = &mut resources.custom_materials;
664 let materials = &mut resources.materials;
665
666 let torus = meshes.add(Torus::new(2.0, 3.0));
667
668 commands.spawn((
670 Mesh3d(torus.clone()),
671 MeshMaterial3d(ext_materials.add(ExtendedMaterial {
672 base: StandardMaterial {
673 cull_mode: None,
674 ..default()
675 },
676 extension: CheckeredMaterialExtension {
677 color_1: LinearRgba::new(0.9, 0.1, 0.2, 0.4),
678 color_2: LinearRgba::new(0.2, 0.1, 0.9, 0.7),
679 },
680 })),
681 Transform::from_rotation(Quat::from_rotation_z(0.4)),
682 ));
683
684 commands.spawn((
686 Mesh3d(torus.clone()),
687 MeshMaterial3d(custom_materials.add(NoisyOpacityMaterial {
688 color: LinearRgba::new(0.9, 0.6, 0.0, 0.5),
689 })),
690 Transform::from_rotation(Quat::from_rotation_z(1.0)),
691 ));
692
693 commands.spawn((
695 Mesh3d(torus.clone()),
696 MeshMaterial3d(materials.add(StandardMaterial {
697 base_color: LinearRgba::new(0.3, 1.0, 0.1, 0.5).into(),
698 alpha_mode: AlphaMode::Blend,
699 cull_mode: None,
700 ..default()
701 })),
702 Transform::from_rotation(Quat::from_rotation_x(1.0)),
703 ));
704}