Skip to main content

solari_reflections/
solari_reflections.rs

1//! Test bed for Solari reflections.
2
3use 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}