1use bevy::{
4 camera::{CameraMainTextureUsages, Exposure},
5 camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},
6 diagnostic::{Diagnostic, DiagnosticPath, DiagnosticsStore},
7 image::{ImageAddressMode, ImageLoaderSettings},
8 math::ops,
9 mesh::{Indices, VertexAttributeValues},
10 prelude::*,
11 render::{diagnostic::RenderDiagnosticsPlugin, render_resource::TextureUsages},
12 solari::{
13 pathtracer::{Pathtracer, PathtracingPlugin},
14 prelude::{RaytracingMesh3d, SolariLighting, SolariPlugins},
15 },
16 ui_widgets::{
17 observe, slider_self_update, Slider, SliderRange, SliderThumb, SliderValue, TrackClick,
18 },
19 world_serialization::WorldInstanceReady,
20};
21
22#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
23use bevy::{
24 anti_alias::dlss::{
25 Dlss, DlssProjectId, DlssRayReconstructionFeature, DlssRayReconstructionSupported,
26 },
27 render::camera::{MipBias, TemporalJitter},
28};
29
30fn main() {
31 let mut app = App::new();
32
33 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
34 app.insert_resource(DlssProjectId(bevy_asset::uuid::uuid!(
35 "3f6c1d28-9b04-4a71-bd52-7e8a5c0f1d93"
36 )));
37
38 app.add_plugins((
39 DefaultPlugins,
40 SolariPlugins,
41 PathtracingPlugin,
42 FreeCameraPlugin,
43 RenderDiagnosticsPlugin,
44 ))
45 .init_resource::<DemoState>()
46 .add_systems(Startup, setup)
47 .add_systems(
48 Update,
49 (
50 pause_scene,
51 toggle_metallic,
52 toggle_pathtracer,
53 toggle_mirror_motion,
54 select_preset,
55 update_roughness,
56 move_objects,
57 reset_pathtracer.after(move_objects),
58 ),
59 )
60 .add_systems(PostUpdate, (update_control_text, update_performance_text));
61
62 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
63 app.add_systems(Update, toggle_dlss_rr);
64
65 app.run();
66}
67
68const METALLIC_BASE_COLOR: Color = Color::srgb(0.95, 0.95, 0.97);
69const DIELECTRIC_BASE_COLOR: Color = Color::srgb(0.15, 0.28, 0.55);
70
71const DELTA_ROUGHNESS: f32 = 0.0314;
72
73const GUIDE_ROUGHNESS: f32 = 0.25;
74
75const MATERIAL_PRESETS: [(KeyCode, &str, f32); 5] = [
76 (KeyCode::Digit3, "Mirror", 0.0),
77 (KeyCode::Digit4, "Near-mirror", DELTA_ROUGHNESS),
78 (KeyCode::Digit5, "Glossy", 0.12),
79 (KeyCode::Digit6, "Satin", GUIDE_ROUGHNESS - 0.01),
80 (KeyCode::Digit7, "Diffuse", 1.0),
81];
82
83const INITIAL_ROUGHNESS: f32 = MATERIAL_PRESETS[0].2;
84
85#[derive(Resource)]
86struct DemoState {
87 metallic: bool,
88 roughness: f32,
89 paused: bool,
90 phase: f32,
91 pan_mirror: bool,
92 slide_mirror: bool,
93}
94
95impl Default for DemoState {
96 fn default() -> Self {
97 Self {
98 metallic: true,
99 roughness: INITIAL_ROUGHNESS,
100 paused: false,
101 phase: 0.0,
102 pan_mirror: false,
103 slide_mirror: false,
104 }
105 }
106}
107
108#[derive(Resource)]
109struct TestMaterial(Handle<StandardMaterial>);
110
111#[derive(Component)]
112struct RoughnessSlider;
113
114#[derive(Component)]
115struct RoughnessSliderThumb;
116
117#[derive(Component)]
118struct OrbitingObject;
119
120#[derive(Component)]
121struct SlidingObject;
122
123#[derive(Component)]
124struct PanningMirror;
125
126#[derive(Component)]
127enum ControlRow {
128 Pause,
129 Metallic,
130 Presets,
131 MirrorPan,
132 MirrorSlide,
133 Renderer,
134 Denoising,
135}
136
137#[derive(Component)]
138struct PerformanceText;
139
140fn raytraced(mesh: impl Into<Mesh>) -> Mesh {
141 mesh.into().with_generated_tangents().unwrap()
142}
143
144fn setup(
145 mut commands: Commands,
146 asset_server: Res<AssetServer>,
147 mut meshes: ResMut<Assets<Mesh>>,
148 mut materials: ResMut<Assets<StandardMaterial>>,
149 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] dlss_rr_supported: Option<
150 Res<DlssRayReconstructionSupported>,
151 >,
152) {
153 let test_material = materials.add(StandardMaterial {
154 base_color: METALLIC_BASE_COLOR,
155 metallic: 1.0,
156 perceptual_roughness: INITIAL_ROUGHNESS,
157 ..default()
158 });
159 commands.insert_resource(TestMaterial(test_material.clone()));
160
161 let mirror = materials.add(StandardMaterial {
162 base_color: Color::srgb(0.95, 0.95, 0.97),
163 metallic: 1.0,
164 perceptual_roughness: 0.0,
165 ..default()
166 });
167
168 spawn_room(&mut commands, &asset_server, &mut meshes, &mut materials);
169
170 let panel = meshes.add(raytraced(Plane3d::new(Vec3::Z, Vec2::new(2.0, 1.5))));
171 commands.spawn((
172 RaytracingMesh3d(panel.clone()),
173 Mesh3d(panel),
174 MeshMaterial3d(test_material.clone()),
175 Transform::from_xyz(MIRROR_BASE_X, 1.7, -3.0).with_rotation(mirror_rotation(0.0)),
176 PanningMirror,
177 ));
178
179 commands
180 .spawn((
181 WorldAssetRoot(asset_server.load(
182 GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"),
183 )),
184 Transform::from_xyz(4.8, 0.0, -1.6)
185 .with_scale(Vec3::splat(4.0))
186 .with_rotation(Quat::from_rotation_y(-0.4)),
187 ))
188 .observe(add_raytracing_meshes_on_scene_load);
189
190 let cube = meshes.add(raytraced(Cuboid::from_length(0.6)));
191 commands.spawn((
192 RaytracingMesh3d(cube.clone()),
193 Mesh3d(cube.clone()),
194 MeshMaterial3d(materials.add(StandardMaterial {
195 base_color: Color::srgb(0.9, 0.15, 0.05),
196 perceptual_roughness: 0.6,
197 ..default()
198 })),
199 Transform::from_xyz(3.5, 1.5, 2.5),
200 OrbitingObject,
201 ));
202
203 let moving_sphere = meshes.add(raytraced(Sphere::new(0.8).mesh().build()));
204 commands.spawn((
205 RaytracingMesh3d(moving_sphere.clone()),
206 Mesh3d(moving_sphere),
207 MeshMaterial3d(test_material),
208 Transform::from_xyz(0.0, 0.9, 3.6),
209 SlidingObject,
210 ));
211
212 let nested = meshes.add(raytraced(Plane3d::new(Vec3::Z, Vec2::new(1.3, 1.9))));
213 for (x, yaw) in [(-5.2, 1.04), (-1.8, -1.04)] {
214 commands.spawn((
215 RaytracingMesh3d(nested.clone()),
216 Mesh3d(nested.clone()),
217 MeshMaterial3d(mirror.clone()),
218 Transform::from_xyz(x, 2.0, -3.5).with_rotation(Quat::from_rotation_y(yaw)),
219 ));
220 }
221
222 commands.spawn((
223 RaytracingMesh3d(cube.clone()),
224 Mesh3d(cube),
225 MeshMaterial3d(materials.add(StandardMaterial {
226 base_color: Color::srgb(0.95, 0.75, 0.1),
227 perceptual_roughness: 0.6,
228 ..default()
229 })),
230 Transform::from_xyz(-3.5, 2.0, -4.0).with_scale(Vec3::splat(0.6)),
231 ));
232
233 let mut camera = commands.spawn((
234 Camera3d::default(),
235 Camera {
236 clear_color: ClearColorConfig::Custom(Color::BLACK),
237 ..default()
238 },
239 FreeCamera {
240 walk_speed: 3.0,
241 run_speed: 10.0,
242 ..default()
243 },
244 Transform::from_xyz(0.0, 2.8, 9.5).looking_at(Vec3::new(0.0, 1.6, -1.0), Vec3::Y),
245 Exposure::INDOOR,
246 CameraMainTextureUsages::default().with(TextureUsages::STORAGE_BINDING),
247 Msaa::Off,
248 SolariLighting::default(),
249 ));
250
251 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
252 if dlss_rr_supported.is_some() {
253 camera.insert(Dlss::<DlssRayReconstructionFeature>::default());
254 }
255 let _ = &mut camera;
256
257 spawn_ui(&mut commands);
258}
259
260fn add_raytracing_meshes_on_scene_load(
261 scene_ready: On<WorldInstanceReady>,
262 children: Query<&Children>,
263 mesh_query: Query<&Mesh3d>,
264 mut meshes: ResMut<Assets<Mesh>>,
265 mut commands: Commands,
266) {
267 for descendant in children.iter_descendants(scene_ready.entity) {
268 if let Ok(Mesh3d(mesh_handle)) = mesh_query.get(descendant) {
269 commands
270 .entity(descendant)
271 .insert(RaytracingMesh3d(mesh_handle.clone()));
272
273 let mut mesh = meshes.get_mut(mesh_handle).unwrap();
274 if !mesh.contains_attribute(Mesh::ATTRIBUTE_UV_0) {
275 let vertex_count = mesh.count_vertices();
276 mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0]; vertex_count]);
277 mesh.insert_attribute(
278 Mesh::ATTRIBUTE_TANGENT,
279 vec![[0.0, 0.0, 0.0, 0.0]; vertex_count],
280 );
281 }
282 if !mesh.contains_attribute(Mesh::ATTRIBUTE_TANGENT) {
283 mesh.generate_tangents().unwrap();
284 }
285 if mesh.contains_attribute(Mesh::ATTRIBUTE_UV_1) {
286 mesh.remove_attribute(Mesh::ATTRIBUTE_UV_1);
287 }
288 if let Some(indices) = mesh.indices_mut()
289 && let Indices::U16(_) = indices
290 {
291 *indices = Indices::U32(indices.iter().map(|i| i as u32).collect());
292 }
293 }
294 }
295}
296
297fn spawn_room(
298 commands: &mut Commands,
299 asset_server: &AssetServer,
300 meshes: &mut Assets<Mesh>,
301 materials: &mut Assets<StandardMaterial>,
302) {
303 let mut floor_mesh = raytraced(Plane3d::new(Vec3::Y, Vec2::new(9.0, 10.5)));
304 match floor_mesh.attribute_mut(Mesh::ATTRIBUTE_UV_0).unwrap() {
305 VertexAttributeValues::Float32x2(items) => {
306 items.iter_mut().flatten().for_each(|x| *x *= 9.0);
307 }
308 _ => unreachable!(),
309 }
310 let floor = meshes.add(floor_mesh);
311 commands.spawn((
312 RaytracingMesh3d(floor.clone()),
313 Mesh3d(floor),
314 MeshMaterial3d(
315 materials.add(StandardMaterial {
316 base_color_texture: Some(
317 asset_server
318 .load_builder()
319 .with_settings::<ImageLoaderSettings>(|settings| {
320 settings
321 .sampler
322 .get_or_init_descriptor()
323 .set_address_mode(ImageAddressMode::Repeat);
324 })
325 .load("textures/uv_checker_bw.png"),
326 ),
327 perceptual_roughness: 0.9,
328 ..default()
329 }),
330 ),
331 Transform::from_xyz(0.0, 0.0, 1.5),
332 ));
333
334 let neutral = materials.add(StandardMaterial {
335 base_color: Color::srgb(0.55, 0.54, 0.52),
336 perceptual_roughness: 0.9,
337 ..default()
338 });
339
340 for (z, normal) in [(-9.0, Vec3::Z), (12.0, Vec3::NEG_Z)] {
341 let wall = meshes.add(raytraced(Plane3d::new(normal, Vec2::new(9.0, 4.0))));
342 commands.spawn((
343 RaytracingMesh3d(wall.clone()),
344 Mesh3d(wall),
345 MeshMaterial3d(neutral.clone()),
346 Transform::from_xyz(0.0, 4.0, z),
347 ));
348 }
349
350 for (x, normal, tint) in [
351 (-9.0, Vec3::X, Color::srgb(0.6, 0.25, 0.22)),
352 (9.0, Vec3::NEG_X, Color::srgb(0.22, 0.3, 0.6)),
353 ] {
354 let wall = meshes.add(raytraced(Plane3d::new(normal, Vec2::new(4.0, 10.5))));
355 commands.spawn((
356 RaytracingMesh3d(wall.clone()),
357 Mesh3d(wall),
358 MeshMaterial3d(materials.add(StandardMaterial {
359 base_color: tint,
360 perceptual_roughness: 0.9,
361 ..default()
362 })),
363 Transform::from_xyz(x, 4.0, 1.5),
364 ));
365 }
366
367 let ceiling = meshes.add(raytraced(Plane3d::new(Vec3::NEG_Y, Vec2::splat(10.5))));
368 commands.spawn((
369 RaytracingMesh3d(ceiling.clone()),
370 Mesh3d(ceiling),
371 MeshMaterial3d(neutral),
372 Transform::from_xyz(0.0, 8.0, 1.5),
373 ));
374
375 let lamp = meshes.add(raytraced(Plane3d::new(Vec3::NEG_Y, Vec2::splat(1.75))));
376 commands.spawn((
377 RaytracingMesh3d(lamp.clone()),
378 Mesh3d(lamp),
379 MeshMaterial3d(materials.add(StandardMaterial {
380 emissive: LinearRgba::rgb(40000.0, 38000.0, 34000.0),
381 ..default()
382 })),
383 Transform::from_xyz(0.0, 7.8, 0.5),
384 ));
385}
386
387const SLIDER_TRACK: Color = Color::srgb(0.45, 0.45, 0.48);
388const SLIDER_THUMB: Color = Color::srgb(0.45, 0.95, 0.45);
389const SLIDER_TICK: Color = Color::srgb(0.95, 0.78, 0.25);
390
391const SLIDER_WIDTH: f32 = 180.0;
392const SLIDER_THUMB_SIZE: f32 = 14.0;
393const SLIDER_TRACK_HEIGHT: f32 = 8.0;
394
395const HEADING_COLOR: Color = Color::srgb(0.72, 0.83, 1.0);
396const KEY_COLOR: Color = Color::srgb(0.95, 0.82, 0.4);
397const LABEL_COLOR: Color = Color::srgb(0.92, 0.92, 0.92);
398
399const HEADING_SIZE: f32 = 10.0;
400const LABEL_SIZE: f32 = 11.0;
401
402const KEY_WIDTH: f32 = 42.0;
403const KEY_GAP: f32 = 8.0;
404
405fn spawn_ui(commands: &mut Commands) {
406 commands.spawn((
407 Node {
408 position_type: PositionType::Absolute,
409 bottom: px(12.0),
410 left: px(12.0),
411 flex_direction: FlexDirection::Column,
412 align_items: AlignItems::FlexStart,
413 padding: px(10.0).all(),
414 border_radius: BorderRadius::all(px(4.0)),
415 ..default()
416 },
417 BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.92)),
418 children![
419 heading("PLAYBACK"),
420 row("Space", ControlRow::Pause),
421 static_row("< >", "Scrub objects"),
422 heading("TEST MATERIAL"),
423 row("1", ControlRow::Metallic),
424 row("3-7", ControlRow::Presets),
425 (
426 Node {
427 align_items: AlignItems::Center,
428 column_gap: px(KEY_GAP),
429 margin: UiRect::left(px(KEY_WIDTH + KEY_GAP)),
430 ..default()
431 },
432 children![
433 (
434 RoughnessValueText,
435 Text::default(),
436 TextFont::from_font_size(LABEL_SIZE),
437 TextColor(LABEL_COLOR),
438 ),
439 roughness_slider(),
440 ],
441 ),
442 heading("RIGHT MIRROR"),
443 row("8", ControlRow::MirrorPan),
444 row("9", ControlRow::MirrorSlide),
445 heading("RENDERER"),
446 row("P", ControlRow::Renderer),
447 row("2", ControlRow::Denoising),
448 ],
449 ));
450
451 commands.spawn((
452 Node {
453 position_type: PositionType::Absolute,
454 right: px(0.0),
455 padding: px(4.0).all(),
456 border_radius: BorderRadius::bottom_left(px(4.0)),
457 ..default()
458 },
459 BackgroundColor(Color::srgba(0.10, 0.10, 0.10, 0.8)),
460 children![(
461 PerformanceText,
462 Text::default(),
463 TextFont {
464 font_size: FontSize::Px(8.0),
465 ..default()
466 },
467 )],
468 ));
469}
470
471fn heading(title: &str) -> impl Bundle {
472 (
473 Node {
474 margin: UiRect::top(px(7.0)).with_bottom(px(3.0)),
475 ..default()
476 },
477 Text::new(title),
478 TextFont::from_font_size(HEADING_SIZE),
479 TextColor(HEADING_COLOR),
480 )
481}
482
483fn row(key: &str, label: ControlRow) -> impl Bundle {
484 (
485 row_node(),
486 children![
487 key_label(key),
488 (
489 label,
490 Text::default(),
491 TextFont::from_font_size(LABEL_SIZE),
492 TextColor(LABEL_COLOR),
493 ),
494 ],
495 )
496}
497
498fn static_row(key: &str, label: &str) -> impl Bundle {
499 (
500 row_node(),
501 children![
502 key_label(key),
503 (
504 Text::new(label),
505 TextFont::from_font_size(LABEL_SIZE),
506 TextColor(LABEL_COLOR),
507 ),
508 ],
509 )
510}
511
512fn row_node() -> Node {
513 Node {
514 align_items: AlignItems::Center,
515 column_gap: px(KEY_GAP),
516 ..default()
517 }
518}
519
520fn key_label(key: &str) -> impl Bundle {
521 (
522 Node {
523 width: px(KEY_WIDTH),
524 justify_content: JustifyContent::FlexEnd,
525 ..default()
526 },
527 children![(
528 Text::new(key),
529 TextFont::from_font_size(LABEL_SIZE),
530 TextColor(KEY_COLOR),
531 )],
532 )
533}
534
535fn roughness_slider() -> impl Bundle {
536 (
537 Node {
538 width: px(SLIDER_WIDTH),
539 height: px(SLIDER_THUMB_SIZE),
540 flex_direction: FlexDirection::Column,
541 align_items: AlignItems::Stretch,
542 justify_content: JustifyContent::Center,
543 ..default()
544 },
545 RoughnessSlider,
546 Slider {
547 track_click: TrackClick::Snap,
548 ..default()
549 },
550 SliderValue(INITIAL_ROUGHNESS),
551 SliderRange::new(0.0, 1.0),
552 observe(slider_self_update),
553 Children::spawn((
554 Spawn((
555 Node {
556 height: px(SLIDER_TRACK_HEIGHT),
557 border_radius: BorderRadius::all(px(SLIDER_TRACK_HEIGHT / 2.0)),
558 ..default()
559 },
560 BackgroundColor(SLIDER_TRACK),
561 )),
562 Spawn((
563 Node {
564 position_type: PositionType::Absolute,
565 left: px(0.0),
566 right: px(SLIDER_THUMB_SIZE),
567 top: px(0.0),
568 bottom: px(0.0),
569 ..default()
570 },
571 children![
572 threshold_tick(DELTA_ROUGHNESS),
573 threshold_tick(GUIDE_ROUGHNESS),
574 (
575 RoughnessSliderThumb,
576 SliderThumb,
577 Node {
578 position_type: PositionType::Absolute,
579 width: px(SLIDER_THUMB_SIZE),
580 height: px(SLIDER_THUMB_SIZE),
581 left: percent(INITIAL_ROUGHNESS * 100.0),
582 border_radius: BorderRadius::MAX,
583 ..default()
584 },
585 BackgroundColor(SLIDER_THUMB),
586 )
587 ],
588 )),
589 )),
590 )
591}
592
593#[derive(Component)]
594struct RoughnessValueText;
595
596fn threshold_tick(roughness: f32) -> impl Bundle {
597 (
598 Node {
599 position_type: PositionType::Absolute,
600 left: percent(roughness * 100.0),
601 width: px(SLIDER_THUMB_SIZE),
602 height: px(SLIDER_THUMB_SIZE),
603 justify_content: JustifyContent::Center,
604 ..default()
605 },
606 children![(
607 Node {
608 width: px(2.0),
609 height: px(SLIDER_THUMB_SIZE),
610 ..default()
611 },
612 BackgroundColor(SLIDER_TICK),
613 )],
614 )
615}
616
617fn pause_scene(key_input: Res<ButtonInput<KeyCode>>, mut state: ResMut<DemoState>) {
618 if key_input.just_pressed(KeyCode::Space) {
619 state.paused = !state.paused;
620 }
621}
622
623fn set_metallic(material: &mut StandardMaterial, metallic: bool) {
624 material.metallic = if metallic { 1.0 } else { 0.0 };
625 material.base_color = if metallic {
626 METALLIC_BASE_COLOR
627 } else {
628 DIELECTRIC_BASE_COLOR
629 };
630}
631
632fn toggle_metallic(
633 key_input: Res<ButtonInput<KeyCode>>,
634 mut state: ResMut<DemoState>,
635 test_material: Res<TestMaterial>,
636 mut materials: ResMut<Assets<StandardMaterial>>,
637) {
638 if key_input.just_pressed(KeyCode::Digit1) {
639 state.metallic = !state.metallic;
640 set_metallic(
641 &mut materials.get_mut(&test_material.0).unwrap(),
642 state.metallic,
643 );
644 }
645}
646
647fn toggle_mirror_motion(key_input: Res<ButtonInput<KeyCode>>, mut state: ResMut<DemoState>) {
648 if key_input.just_pressed(KeyCode::Digit8) {
649 state.pan_mirror = !state.pan_mirror;
650 }
651 if key_input.just_pressed(KeyCode::Digit9) {
652 state.slide_mirror = !state.slide_mirror;
653 }
654}
655
656fn toggle_pathtracer(
657 key_input: Res<ButtonInput<KeyCode>>,
658 camera: Single<(Entity, Has<Pathtracer>), With<Camera3d>>,
659 mut commands: Commands,
660) {
661 if key_input.just_pressed(KeyCode::KeyP) {
662 let (entity, pathtracing) = *camera;
663 let mut camera = commands.entity(entity);
664
665 if pathtracing {
666 camera.remove_with_requires::<Pathtracer>();
667 camera.insert(SolariLighting::default());
668 } else {
669 camera.remove_with_requires::<SolariLighting>();
670 camera.insert(Pathtracer::default());
671
672 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
673 camera.remove::<(Dlss<DlssRayReconstructionFeature>, TemporalJitter, MipBias)>();
674 }
675 }
676}
677
678fn reset_pathtracer(state: Res<DemoState>, pathtracer: Option<Single<&mut Pathtracer>>) {
679 if let Some(mut pathtracer) = pathtracer
680 && state.is_changed()
681 {
682 pathtracer.reset = true;
683 }
684}
685
686fn select_preset(
687 key_input: Res<ButtonInput<KeyCode>>,
688 slider: Single<Entity, With<RoughnessSlider>>,
689 test_material: Res<TestMaterial>,
690 mut materials: ResMut<Assets<StandardMaterial>>,
691 mut commands: Commands,
692) {
693 for (key, _, roughness) in MATERIAL_PRESETS {
694 if key_input.just_pressed(key) {
695 materials
696 .get_mut(&test_material.0)
697 .unwrap()
698 .perceptual_roughness = roughness;
699
700 commands.entity(*slider).insert(SliderValue(roughness));
701 }
702 }
703}
704
705fn update_roughness(
706 slider: Query<
707 (Entity, &SliderValue, &SliderRange),
708 (Changed<SliderValue>, With<RoughnessSlider>),
709 >,
710 children: Query<&Children>,
711 mut thumb: Query<&mut Node, With<RoughnessSliderThumb>>,
712 mut value_text: Single<&mut Text, With<RoughnessValueText>>,
713 mut state: ResMut<DemoState>,
714 test_material: Res<TestMaterial>,
715 mut materials: ResMut<Assets<StandardMaterial>>,
716) {
717 for (slider_entity, value, range) in &slider {
718 state.roughness = value.0;
719 materials
720 .get_mut(&test_material.0)
721 .unwrap()
722 .perceptual_roughness = value.0;
723
724 value_text.0 = format!("Roughness {:.2}", value.0);
725
726 for child in children.iter_descendants(slider_entity) {
727 if let Ok(mut node) = thumb.get_mut(child) {
728 node.left = percent(range.thumb_position(value.0) * 100.0);
729 }
730 }
731 }
732}
733
734const PHASE_RATE: f32 = 0.8;
735
736const MIRROR_YAW: f32 = 0.22;
737const MIRROR_PITCH: f32 = -0.16;
738const MIRROR_PAN_RANGE: f32 = 0.55;
739const MIRROR_BASE_X: f32 = 2.4;
740const MIRROR_SLIDE_RANGE: f32 = 1.5;
741
742fn mirror_rotation(pan: f32) -> Quat {
743 Quat::from_rotation_y(MIRROR_YAW + pan) * Quat::from_rotation_x(MIRROR_PITCH)
744}
745
746fn move_objects(
747 time: Res<Time>,
748 key_input: Res<ButtonInput<KeyCode>>,
749 mut state: ResMut<DemoState>,
750 mut orbiting: Single<&mut Transform, With<OrbitingObject>>,
751 mut sliding: Single<&mut Transform, (With<SlidingObject>, Without<OrbitingObject>)>,
752 mut mirror: Single<
753 &mut Transform,
754 (
755 With<PanningMirror>,
756 Without<OrbitingObject>,
757 Without<SlidingObject>,
758 ),
759 >,
760) {
761 let scrub = key_input.pressed(KeyCode::ArrowRight) as i32 as f32
762 - key_input.pressed(KeyCode::ArrowLeft) as i32 as f32;
763 let playback = if state.paused { 0.0 } else { 1.0 };
764 let delta = (playback + scrub) * time.delta_secs() * PHASE_RATE;
765
766 if delta != 0.0 {
767 state.phase += delta;
768 }
769
770 orbiting.translation = Vec3::new(
771 3.5 + ops::sin(state.phase) * 2.8,
772 1.5,
773 0.5 + ops::cos(state.phase) * 2.6,
774 );
775 orbiting.rotation = Quat::from_rotation_y(state.phase * 0.7);
776
777 sliding.translation.x = ops::sin(state.phase * 0.7) * 5.5;
778
779 let pan = if state.pan_mirror {
780 ops::sin(state.phase * 1.1) * MIRROR_PAN_RANGE
781 } else {
782 0.0
783 };
784 mirror.rotation = mirror_rotation(pan);
785
786 mirror.translation.x = if state.slide_mirror {
787 MIRROR_BASE_X + ops::sin(state.phase * 0.9) * MIRROR_SLIDE_RANGE
788 } else {
789 MIRROR_BASE_X
790 };
791}
792
793#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
794fn toggle_dlss_rr(
795 key_input: Res<ButtonInput<KeyCode>>,
796 camera: Single<(Entity, Has<Dlss<DlssRayReconstructionFeature>>), With<SolariLighting>>,
797 dlss_rr_supported: Option<Res<DlssRayReconstructionSupported>>,
798 mut commands: Commands,
799) {
800 if key_input.just_pressed(KeyCode::Digit2) && dlss_rr_supported.is_some() {
801 let (entity, dlss) = *camera;
802 if dlss {
803 commands
804 .entity(entity)
805 .remove::<(Dlss<DlssRayReconstructionFeature>, TemporalJitter, MipBias)>();
806 } else {
807 commands
808 .entity(entity)
809 .insert(Dlss::<DlssRayReconstructionFeature>::default());
810 }
811 }
812}
813
814fn update_control_text(
815 mut rows: Query<(&ControlRow, &mut Text)>,
816 state: Res<DemoState>,
817 pathtracing: Query<(), With<Pathtracer>>,
818 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] dlss_rr_supported: Option<
819 Res<DlssRayReconstructionSupported>,
820 >,
821 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] dlss_camera: Query<
822 Has<Dlss<DlssRayReconstructionFeature>>,
823 With<SolariLighting>,
824 >,
825) {
826 for (row, mut text) in &mut rows {
827 text.0.clear();
828
829 match row {
830 ControlRow::Pause => text
831 .0
832 .push_str(if state.paused { "Resume" } else { "Pause" }),
833 ControlRow::Metallic => text.0.push_str(if state.metallic {
834 "Switch to dielectric"
835 } else {
836 "Switch to metallic"
837 }),
838 ControlRow::Presets => {
839 for (_, name, roughness) in MATERIAL_PRESETS {
840 if (roughness - state.roughness).abs() < 1e-4 {
841 text.0.push_str(&format!("[{name}] "));
842 } else {
843 text.0.push_str(&format!("{name} "));
844 }
845 }
846 }
847 ControlRow::MirrorPan => text.0.push_str(if state.pan_mirror {
848 "Stop panning"
849 } else {
850 "Pan back and forth"
851 }),
852 ControlRow::MirrorSlide => text.0.push_str(if state.slide_mirror {
853 "Stop sliding"
854 } else {
855 "Slide back and forth"
856 }),
857 ControlRow::Renderer => text.0.push_str(if pathtracing.is_empty() {
858 "Switch to reference pathtracer"
859 } else {
860 "Switch to realtime lighting - pause to let the pathtracer converge"
861 }),
862 ControlRow::Denoising => {
863 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
864 if dlss_rr_supported.is_some() {
865 if matches!(dlss_camera.single(), Ok(true)) {
866 text.0.push_str("Disable DLSS Ray Reconstruction");
867 } else {
868 text.0.push_str("Enable DLSS Ray Reconstruction");
869 }
870 } else {
871 text.0.push_str("DLSS Ray Reconstruction not supported");
872 }
873
874 #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))]
875 text.0.push_str("App not compiled with DLSS support");
876 }
877 }
878 }
879}
880
881fn update_performance_text(
882 mut text: Single<&mut Text, With<PerformanceText>>,
883 diagnostics: Res<DiagnosticsStore>,
884 pathtracing: Query<(), With<Pathtracer>>,
885 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] dlss_camera: Query<
886 Has<Dlss<DlssRayReconstructionFeature>>,
887 With<SolariLighting>,
888 >,
889) {
890 text.0.clear();
891
892 if !pathtracing.is_empty() {
893 text.push_str("Pathtracer (untimed)");
894 return;
895 }
896
897 let mut total = 0.0;
898 let mut add_diagnostic = |name: &str, path: &'static str| {
899 let path = DiagnosticPath::new(path);
900 if let Some(value) = diagnostics.get(&path).and_then(Diagnostic::smoothed) {
901 text.push_str(&format!("{name:17} {value:.2} ms\n"));
902 total += value;
903 }
904 };
905
906 (add_diagnostic)(
907 "Light tiles",
908 "render/solari_lighting/presample_light_tiles/elapsed_gpu",
909 );
910 (add_diagnostic)(
911 "World cache",
912 "render/solari_lighting/world_cache/elapsed_gpu",
913 );
914 (add_diagnostic)("Lighting", "render/solari_lighting/lighting/elapsed_gpu");
915 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
916 if matches!(dlss_camera.single(), Ok(true)) {
917 (add_diagnostic)("DLSS-RR", "render/dlss_ray_reconstruction/elapsed_gpu");
918 }
919 text.push_str(&format!("{:17} {total:.2} ms\n", "Total"));
920}