1use argh::FromArgs;
4use bevy::{
5 camera::CameraMainTextureUsages,
6 camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},
7 diagnostic::{Diagnostic, DiagnosticPath, DiagnosticsStore},
8 gltf::GltfMaterialName,
9 image::{ImageAddressMode, ImageLoaderSettings},
10 mesh::{Indices, VertexAttributeValues},
11 post_process::bloom::Bloom,
12 prelude::*,
13 render::{diagnostic::RenderDiagnosticsPlugin, render_resource::TextureUsages},
14 solari::{
15 pathtracer::{Pathtracer, PathtracingPlugin},
16 prelude::{RaytracingMesh3d, SolariLighting, SolariPlugins},
17 },
18 world_serialization::WorldInstanceReady,
19};
20use chacha20::ChaCha8Rng;
21use rand::{RngExt, SeedableRng};
22use std::f32::consts::PI;
23
24#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
25use bevy::{
26 anti_alias::dlss::{
27 Dlss, DlssProjectId, DlssRayReconstructionFeature, DlssRayReconstructionSupported,
28 },
29 render::camera::{MipBias, TemporalJitter},
30};
31
32#[derive(FromArgs, Resource, Clone, Copy)]
34struct Args {
35 #[argh(switch)]
37 pathtracer: Option<bool>,
38 #[argh(switch)]
40 many_lights: Option<bool>,
41}
42
43fn main() {
44 let args: Args = argh::from_env();
45
46 let mut app = App::new();
47
48 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
49 app.insert_resource(DlssProjectId(bevy_asset::uuid::uuid!(
50 "5417916c-0291-4e3f-8f65-326c1858ab96" )));
52
53 app.add_plugins((
54 DefaultPlugins,
55 SolariPlugins,
56 FreeCameraPlugin,
57 RenderDiagnosticsPlugin,
58 ))
59 .insert_resource(args);
60
61 if args.many_lights == Some(true) {
62 app.add_systems(Startup, setup_many_lights);
63 } else {
64 app.add_systems(Startup, setup_pica_pica);
65 }
66
67 if args.pathtracer == Some(true) {
68 app.add_plugins(PathtracingPlugin);
69 } else {
70 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
71 app.add_systems(Update, toggle_dlss_rr);
72
73 app.add_systems(Update, toggle_restir);
74
75 if args.many_lights != Some(true) {
76 app.add_systems(Update, (pause_scene, toggle_lights, patrol_path));
77 }
78 app.add_systems(PostUpdate, (update_control_text, update_performance_text));
79 }
80
81 app.run();
82}
83
84fn setup_pica_pica(
85 mut commands: Commands,
86 asset_server: Res<AssetServer>,
87 args: Res<Args>,
88 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] dlss_rr_supported: Option<
89 Res<DlssRayReconstructionSupported>,
90 >,
91) {
92 commands
93 .spawn((
94 WorldAssetRoot(
95 asset_server.load(
96 GltfAssetLabel::Scene(0)
97 .from_asset("https://github.com/bevyengine/bevy_asset_files/raw/2a5950295a8b6d9d051d59c0df69e87abcda58c3/pica_pica/mini_diorama_01.glb")
98 ),
99 ),
100 Transform::from_scale(Vec3::splat(10.0)),
101 ))
102 .observe(add_raytracing_meshes_on_scene_load);
103
104 commands
105 .spawn((
106 WorldAssetRoot(asset_server.load(
107 GltfAssetLabel::Scene(0).from_asset("https://github.com/bevyengine/bevy_asset_files/raw/2a5950295a8b6d9d051d59c0df69e87abcda58c3/pica_pica/robot_01.glb")
108 )),
109 Transform::from_scale(Vec3::splat(2.0))
110 .with_translation(Vec3::new(-2.0, 0.05, -2.1))
111 .with_rotation(Quat::from_rotation_y(PI / 2.0)),
112 PatrolPath {
113 path: vec![
114 (Vec3::new(-2.0, 0.05, -2.1), Quat::from_rotation_y(PI / 2.0)),
115 (Vec3::new(2.2, 0.05, -2.1), Quat::from_rotation_y(0.0)),
116 (
117 Vec3::new(2.2, 0.05, 2.1),
118 Quat::from_rotation_y(3.0 * PI / 2.0),
119 ),
120 (Vec3::new(-2.0, 0.05, 2.1), Quat::from_rotation_y(PI)),
121 ],
122 i: 0,
123 },
124 ))
125 .observe(add_raytracing_meshes_on_scene_load);
126
127 commands.spawn((
128 DirectionalLight {
129 illuminance: light_consts::lux::FULL_DAYLIGHT,
130 shadow_maps_enabled: false, ..default()
132 },
133 Transform::from_rotation(Quat::from_xyzw(
134 -0.13334629,
135 -0.86597735,
136 -0.3586996,
137 0.3219264,
138 )),
139 ));
140
141 let mut camera = commands.spawn((
142 Camera3d::default(),
143 Camera {
144 clear_color: ClearColorConfig::Custom(Color::BLACK),
145 ..default()
146 },
147 FreeCamera {
148 walk_speed: 3.0,
149 run_speed: 10.0,
150 ..Default::default()
151 },
152 Transform::from_translation(Vec3::new(0.219417, 2.5764852, 6.9718704)).with_rotation(
153 Quat::from_xyzw(-0.1466768, 0.013738206, 0.002037309, 0.989087),
154 ),
155 CameraMainTextureUsages::default().with(TextureUsages::STORAGE_BINDING),
157 Msaa::Off,
158 ));
159
160 if args.pathtracer == Some(true) {
161 camera.insert(Pathtracer::default());
162 } else {
163 camera.insert(SolariLighting::default());
164 }
165
166 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
168 if dlss_rr_supported.is_some() {
169 camera.insert(Dlss::<DlssRayReconstructionFeature>::default());
170 }
171
172 commands.spawn((
173 ControlText,
174 Text::default(),
175 Node {
176 position_type: PositionType::Absolute,
177 bottom: px(12.0),
178 left: px(12.0),
179 ..default()
180 },
181 ));
182
183 commands.spawn((
184 Node {
185 position_type: PositionType::Absolute,
186 right: px(0.0),
187 padding: px(4.0).all(),
188 border_radius: BorderRadius::bottom_left(px(4.0)),
189 ..default()
190 },
191 BackgroundColor(Color::srgba(0.10, 0.10, 0.10, 0.8)),
192 children![(
193 PerformanceText,
194 Text::default(),
195 TextFont {
196 font_size: FontSize::Px(8.0),
197 ..default()
198 },
199 )],
200 ));
201}
202
203fn setup_many_lights(
204 mut commands: Commands,
205 asset_server: Res<AssetServer>,
206 mut meshes: ResMut<Assets<Mesh>>,
207 mut materials: ResMut<Assets<StandardMaterial>>,
208 args: Res<Args>,
209 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] dlss_rr_supported: Option<
210 Res<DlssRayReconstructionSupported>,
211 >,
212) {
213 let mut rng = ChaCha8Rng::seed_from_u64(42);
214
215 let mut plane_mesh = Plane3d::default()
216 .mesh()
217 .size(400.0, 400.0)
218 .build()
219 .with_generated_tangents()
220 .unwrap();
221 match plane_mesh.attribute_mut(Mesh::ATTRIBUTE_UV_0).unwrap() {
222 VertexAttributeValues::Float32x2(items) => {
223 items.iter_mut().flatten().for_each(|x| *x *= 3.0);
224 }
225 _ => unreachable!(),
226 }
227 let plane_mesh = meshes.add(plane_mesh);
228 let cube_mesh = meshes.add(
229 Cuboid::default()
230 .mesh()
231 .build()
232 .with_generated_tangents()
233 .unwrap(),
234 );
235 let sphere_mesh = meshes.add(
236 Sphere::new(1.0)
237 .mesh()
238 .build()
239 .with_generated_tangents()
240 .unwrap(),
241 );
242
243 commands
244 .spawn((
245 RaytracingMesh3d(plane_mesh.clone()),
246 MeshMaterial3d(
247 materials.add(StandardMaterial {
248 base_color_texture: Some(
249 asset_server
250 .load_builder()
251 .with_settings::<ImageLoaderSettings>(|settings| {
252 settings
253 .sampler
254 .get_or_init_descriptor()
255 .set_address_mode(ImageAddressMode::Repeat);
256 })
257 .load("textures/uv_checker_bw.png"),
258 ),
259 perceptual_roughness: 0.0,
260 ..default()
261 }),
262 ),
263 ))
264 .insert_if(Mesh3d(plane_mesh), || args.pathtracer != Some(true));
265
266 for _ in 0..8000 {
267 commands
268 .spawn((
269 RaytracingMesh3d(cube_mesh.clone()),
270 MeshMaterial3d(materials.add(StandardMaterial {
271 base_color: Color::srgb(rng.random(), rng.random(), rng.random()),
272 perceptual_roughness: rng.random(),
273 ..default()
274 })),
275 Transform::default()
276 .with_scale(Vec3 {
277 x: rng.random_range(0.2..=2.0),
278 y: rng.random_range(0.2..=2.0),
279 z: rng.random_range(0.2..=2.0),
280 })
281 .with_translation(Vec3::new(
282 rng.random_range(-180.0..=180.0),
283 0.2,
284 rng.random_range(-180.0..=180.0),
285 )),
286 ))
287 .insert_if(Mesh3d(cube_mesh.clone()), || args.pathtracer != Some(true));
288 }
289
290 for x in -10..=10 {
291 for y in -10..=10 {
292 commands
293 .spawn((
294 RaytracingMesh3d(sphere_mesh.clone()),
295 MeshMaterial3d(
296 materials.add(StandardMaterial {
297 emissive: Color::linear_rgb(
298 rng.random::<f32>() * 60000.0,
299 rng.random::<f32>() * 60000.0,
300 rng.random::<f32>() * 60000.0,
301 )
302 .into(),
303 ..default()
304 }),
305 ),
306 Transform::default().with_translation(Vec3::new(
307 (x * 20) as f32,
308 7.0,
309 (y * 20) as f32,
310 )),
311 ))
312 .insert_if(Mesh3d(sphere_mesh.clone()), || {
313 args.pathtracer != Some(true)
314 });
315 }
316 }
317
318 let mut camera = commands.spawn((
319 Camera3d::default(),
320 Camera {
321 clear_color: ClearColorConfig::Custom(Color::BLACK),
322 ..default()
323 },
324 FreeCamera {
325 walk_speed: 3.0,
326 run_speed: 10.0,
327 ..Default::default()
328 },
329 Transform::from_translation(Vec3::new(6.11329, 166.74896, 451.8226)).with_rotation(
330 Quat::from_xyzw(-0.183938, 0.009093744, 0.0017017953, 0.9828943),
331 ),
332 CameraMainTextureUsages::default().with(TextureUsages::STORAGE_BINDING),
334 Msaa::Off,
335 Bloom {
336 intensity: 0.1,
337 ..Bloom::NATURAL
338 },
339 ));
340
341 if args.pathtracer == Some(true) {
342 camera.insert(Pathtracer::default());
343 } else {
344 camera.insert(SolariLighting::default());
345 }
346
347 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
349 if dlss_rr_supported.is_some() {
350 camera.insert(Dlss::<DlssRayReconstructionFeature>::default());
351 }
352
353 commands.spawn((
354 ControlText,
355 Text::default(),
356 Node {
357 position_type: PositionType::Absolute,
358 bottom: px(12.0),
359 left: px(12.0),
360 ..default()
361 },
362 ));
363
364 commands.spawn((
365 Node {
366 position_type: PositionType::Absolute,
367 right: px(0.0),
368 padding: px(4.0).all(),
369 border_radius: BorderRadius::bottom_left(px(4.0)),
370 ..default()
371 },
372 BackgroundColor(Color::srgba(0.10, 0.10, 0.10, 0.8)),
373 children![(
374 PerformanceText,
375 Text::default(),
376 TextFont {
377 font_size: FontSize::Px(8.0),
378 ..default()
379 },
380 )],
381 ));
382}
383
384fn add_raytracing_meshes_on_scene_load(
385 scene_ready: On<WorldInstanceReady>,
386 children: Query<&Children>,
387 mesh_query: Query<(
388 &Mesh3d,
389 &MeshMaterial3d<StandardMaterial>,
390 Option<&GltfMaterialName>,
391 )>,
392 mut meshes: ResMut<Assets<Mesh>>,
393 mut materials: ResMut<Assets<StandardMaterial>>,
394 mut commands: Commands,
395 args: Res<Args>,
396) {
397 for descendant in children.iter_descendants(scene_ready.entity) {
398 if let Ok((Mesh3d(mesh_handle), MeshMaterial3d(material_handle), material_name)) =
399 mesh_query.get(descendant)
400 {
401 commands
403 .entity(descendant)
404 .insert(RaytracingMesh3d(mesh_handle.clone()));
405
406 let mut mesh = meshes.get_mut(mesh_handle).unwrap();
408 if !mesh.contains_attribute(Mesh::ATTRIBUTE_UV_0) {
409 let vertex_count = mesh.count_vertices();
410 mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0]; vertex_count]);
411 mesh.insert_attribute(
412 Mesh::ATTRIBUTE_TANGENT,
413 vec![[0.0, 0.0, 0.0, 0.0]; vertex_count],
414 );
415 }
416 if !mesh.contains_attribute(Mesh::ATTRIBUTE_TANGENT) {
417 mesh.generate_tangents().unwrap();
418 }
419 if mesh.contains_attribute(Mesh::ATTRIBUTE_UV_1) {
420 mesh.remove_attribute(Mesh::ATTRIBUTE_UV_1);
421 }
422 if let Some(indices) = mesh.indices_mut()
423 && let Indices::U16(_) = indices
424 {
425 *indices = Indices::U32(indices.iter().map(|i| i as u32).collect());
426 }
427
428 if args.pathtracer == Some(true) {
430 commands.entity(descendant).remove::<Mesh3d>();
431 }
432
433 if material_name.map(|s| s.0.as_str()) == Some("material") {
435 let mut material = materials.get_mut(material_handle).unwrap();
436 material.emissive = LinearRgba::BLACK;
437 }
438 if material_name.map(|s| s.0.as_str()) == Some("Lights") {
439 let mut material = materials.get_mut(material_handle).unwrap();
440 material.emissive =
441 LinearRgba::from(Color::srgb(0.941, 0.714, 0.043)) * 1_000_000.0;
442 material.alpha_mode = AlphaMode::Opaque;
443 material.specular_transmission = 0.0;
444
445 commands.insert_resource(RobotLightMaterial(material_handle.clone()));
446 }
447 if material_name.map(|s| s.0.as_str()) == Some("Glass_Dark_01") {
448 let mut material = materials.get_mut(material_handle).unwrap();
449 material.alpha_mode = AlphaMode::Opaque;
450 material.specular_transmission = 0.0;
451 }
452 }
453 }
454}
455
456#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
457fn toggle_dlss_rr(
458 key_input: Res<ButtonInput<KeyCode>>,
459 camera: Single<(Entity, Has<Dlss<DlssRayReconstructionFeature>>), With<SolariLighting>>,
460 dlss_rr_supported: Option<Res<DlssRayReconstructionSupported>>,
461 mut commands: Commands,
462) {
463 if key_input.just_pressed(KeyCode::Digit3) && dlss_rr_supported.is_some() {
464 let (entity, dlss) = *camera;
465 if dlss {
466 commands
467 .entity(entity)
468 .remove::<(Dlss<DlssRayReconstructionFeature>, TemporalJitter, MipBias)>();
469 } else {
470 commands
471 .entity(entity)
472 .insert(Dlss::<DlssRayReconstructionFeature>::default());
473 }
474 }
475}
476
477fn toggle_restir(
478 key_input: Res<ButtonInput<KeyCode>>,
479 mut solari_lighting: Single<&mut SolariLighting>,
480) {
481 if key_input.just_pressed(KeyCode::KeyR) {
482 solari_lighting.restir = !solari_lighting.restir;
483 }
484}
485
486fn pause_scene(mut time: ResMut<Time<Virtual>>, key_input: Res<ButtonInput<KeyCode>>) {
487 if key_input.just_pressed(KeyCode::Space) {
488 time.toggle();
489 }
490}
491
492#[derive(Resource)]
493struct RobotLightMaterial(Handle<StandardMaterial>);
494
495fn toggle_lights(
496 key_input: Res<ButtonInput<KeyCode>>,
497 robot_light_material: Option<Res<RobotLightMaterial>>,
498 mut materials: ResMut<Assets<StandardMaterial>>,
499 directional_light: Query<Entity, With<DirectionalLight>>,
500 mut commands: Commands,
501) {
502 if key_input.just_pressed(KeyCode::Digit1) {
503 if let Ok(directional_light) = directional_light.single() {
504 commands.entity(directional_light).despawn();
505 } else {
506 commands.spawn((
507 DirectionalLight {
508 illuminance: light_consts::lux::FULL_DAYLIGHT,
509 shadow_maps_enabled: false, ..default()
511 },
512 Transform::from_rotation(Quat::from_xyzw(
513 -0.13334629,
514 -0.86597735,
515 -0.3586996,
516 0.3219264,
517 )),
518 ));
519 }
520 }
521
522 if key_input.just_pressed(KeyCode::Digit2)
523 && let Some(robot_light_material) = robot_light_material
524 {
525 let mut material = materials.get_mut(&robot_light_material.0).unwrap();
526 if material.emissive == LinearRgba::BLACK {
527 material.emissive = LinearRgba::from(Color::srgb(0.941, 0.714, 0.043)) * 1_000_000.0;
528 } else {
529 material.emissive = LinearRgba::BLACK;
530 }
531 }
532}
533
534#[derive(Component)]
535struct PatrolPath {
536 path: Vec<(Vec3, Quat)>,
537 i: usize,
538}
539
540fn patrol_path(mut query: Query<(&mut PatrolPath, &mut Transform)>, time: Res<Time<Virtual>>) {
541 for (mut path, mut transform) in query.iter_mut() {
542 let (mut target_position, mut target_rotation) = path.path[path.i];
543 let mut distance_to_target = transform.translation.distance(target_position);
544 if distance_to_target < 0.01 {
545 transform.translation = target_position;
546 transform.rotation = target_rotation;
547
548 path.i = (path.i + 1) % path.path.len();
549 (target_position, target_rotation) = path.path[path.i];
550 distance_to_target = transform.translation.distance(target_position);
551 }
552
553 let direction = (target_position - transform.translation).normalize();
554 let movement = direction * time.delta_secs();
555
556 if movement.length() > distance_to_target {
557 transform.translation = target_position;
558 transform.rotation = target_rotation;
559 } else {
560 transform.translation += movement;
561 }
562 }
563}
564
565#[derive(Component)]
566struct ControlText;
567
568fn update_control_text(
569 mut text: Single<&mut Text, With<ControlText>>,
570 solari_lighting: Single<&SolariLighting>,
571 robot_light_material: Option<Res<RobotLightMaterial>>,
572 materials: Res<Assets<StandardMaterial>>,
573 directional_light: Query<Entity, With<DirectionalLight>>,
574 time: Res<Time<Virtual>>,
575 args: Res<Args>,
576 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] dlss_rr_supported: Option<
577 Res<DlssRayReconstructionSupported>,
578 >,
579 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] dlss_camera: Query<
580 Has<Dlss<DlssRayReconstructionFeature>>,
581 With<SolariLighting>,
582 >,
583) {
584 text.0.clear();
585
586 if args.many_lights != Some(true) {
587 if time.is_paused() {
588 text.0.push_str("(Space): Resume");
589 } else {
590 text.0.push_str("(Space): Pause");
591 }
592
593 if directional_light.single().is_ok() {
594 text.0.push_str("\n(1): Disable directional light");
595 } else {
596 text.0.push_str("\n(1): Enable directional light");
597 }
598
599 match robot_light_material.and_then(|m| materials.get(&m.0)) {
600 Some(robot_light_material) if robot_light_material.emissive != LinearRgba::BLACK => {
601 text.0.push_str("\n(2): Disable robot emissive light");
602 }
603 _ => {
604 text.0.push_str("\n(2): Enable robot emissive light");
605 }
606 }
607 }
608
609 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
610 if dlss_rr_supported.is_some() {
611 if matches!(dlss_camera.single(), Ok(true)) {
612 text.0.push_str("\n(3): Disable DLSS Ray Reconstruction");
613 } else {
614 text.0.push_str("\n(3): Enable DLSS Ray Reconstruction");
615 }
616 } else {
617 text.0
618 .push_str("\nDenoising: DLSS Ray Reconstruction not supported");
619 }
620
621 #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))]
622 text.0
623 .push_str("\nDenoising: App not compiled with DLSS support");
624
625 if solari_lighting.restir {
626 text.0.push_str("\n(R): Disable ReSTIR");
627 } else {
628 text.0.push_str("\n(R): Enable ReSTIR");
629 }
630}
631
632#[derive(Component)]
633struct PerformanceText;
634
635fn update_performance_text(
636 mut text: Single<&mut Text, With<PerformanceText>>,
637 diagnostics: Res<DiagnosticsStore>,
638 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] dlss_camera: Query<
639 Has<Dlss<DlssRayReconstructionFeature>>,
640 With<SolariLighting>,
641 >,
642) {
643 text.0.clear();
644
645 let mut total = 0.0;
646 let mut add_diagnostic = |name: &str, path: &'static str| {
647 let path = DiagnosticPath::new(path);
648 if let Some(value) = diagnostics.get(&path).and_then(Diagnostic::smoothed) {
649 text.push_str(&format!("{name:17} {value:.2} ms\n"));
650 total += value;
651 }
652 };
653
654 (add_diagnostic)(
655 "Light tiles",
656 "render/solari_lighting/presample_light_tiles/elapsed_gpu",
657 );
658 (add_diagnostic)(
659 "World cache",
660 "render/solari_lighting/world_cache/elapsed_gpu",
661 );
662 (add_diagnostic)("Lighting", "render/solari_lighting/lighting/elapsed_gpu");
663 #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
664 if matches!(dlss_camera.single(), Ok(true)) {
665 (add_diagnostic)("DLSS-RR", "render/dlss_ray_reconstruction/elapsed_gpu");
666 }
667 text.push_str(&format!("{:17} {total:.2} ms\n", "Total"));
668
669 if let Some(world_cache_active_cells_count) = diagnostics
670 .get(&DiagnosticPath::new(
671 "render/solari_lighting/world_cache_active_cells_count",
672 ))
673 .and_then(Diagnostic::smoothed)
674 {
675 text.push_str(&format!(
676 "\nWorld cache cells {} ({:.0}%)",
677 world_cache_active_cells_count as u32,
678 (world_cache_active_cells_count * 100.0) / (2u64.pow(20) as f64)
679 ));
680 }
681}