//! This example shows how irradiance volumes affect the indirect lighting of //! objects in a scene. //! //! The controls are as follows: //! //! * Space toggles the irradiance volume on and off. //! //! * Enter toggles the camera rotation on and off. //! //! * Tab switches the object between a plain sphere and a running fox. //! //! * Backspace shows and hides the voxel cubes. //! //! * Clicking anywhere moves the object. use bevy::color::palettes::css::*; use bevy::core_pipeline::Skybox; use bevy::math::{uvec3, vec3}; use bevy::pbr::irradiance_volume::IrradianceVolume; use bevy::pbr::{ExtendedMaterial, MaterialExtension, NotShadowCaster}; use bevy::prelude::*; use bevy::render::render_resource::{AsBindGroup, ShaderRef, ShaderType}; use bevy::window::PrimaryWindow; // Rotation speed in radians per frame. const ROTATION_SPEED: f32 = 0.2; const FOX_SCALE: f32 = 0.05; const SPHERE_SCALE: f32 = 2.0; const IRRADIANCE_VOLUME_INTENSITY: f32 = 1800.0; const AMBIENT_LIGHT_BRIGHTNESS: f32 = 0.06; const VOXEL_CUBE_SCALE: f32 = 0.4; static DISABLE_IRRADIANCE_VOLUME_HELP_TEXT: &str = "Space: Disable the irradiance volume"; static ENABLE_IRRADIANCE_VOLUME_HELP_TEXT: &str = "Space: Enable the irradiance volume"; static HIDE_VOXELS_HELP_TEXT: &str = "Backspace: Hide the voxels"; static SHOW_VOXELS_HELP_TEXT: &str = "Backspace: Show the voxels"; static STOP_ROTATION_HELP_TEXT: &str = "Enter: Stop rotation"; static START_ROTATION_HELP_TEXT: &str = "Enter: Start rotation"; static SWITCH_TO_FOX_HELP_TEXT: &str = "Tab: Switch to a skinned mesh"; static SWITCH_TO_SPHERE_HELP_TEXT: &str = "Tab: Switch to a plain sphere mesh"; static CLICK_TO_MOVE_HELP_TEXT: &str = "Left click: Move the object"; static GIZMO_COLOR: Color = Color::Srgba(YELLOW); static VOXEL_TRANSFORM: Mat4 = Mat4::from_cols_array_2d(&[ [-42.317566, 0.0, 0.0, 0.0], [0.0, 0.0, 44.601563, 0.0], [0.0, 16.73776, 0.0, 0.0], [0.0, 6.544792, 0.0, 1.0], ]); // The mode the application is in. #[derive(Resource)] struct AppStatus { // Whether the user wants the irradiance volume to be applied. irradiance_volume_present: bool, // Whether the user wants the unskinned sphere mesh or the skinned fox mesh. model: ExampleModel, // Whether the user has requested the scene to rotate. rotating: bool, // Whether the user has requested the voxels to be displayed. voxels_visible: bool, } // Which model the user wants to display. #[derive(Clone, Copy, PartialEq)] enum ExampleModel { // The plain sphere. Sphere, // The fox, which is skinned. Fox, } // Handles to all the assets used in this example. #[derive(Resource)] struct ExampleAssets { // The glTF scene containing the colored floor. main_scene: Handle, // The 3D texture containing the irradiance volume. irradiance_volume: Handle, // The plain sphere mesh. main_sphere: Handle, // The material used for the sphere. main_sphere_material: Handle, // The glTF scene containing the animated fox. fox: Handle, // The animation that the fox will play. fox_animation: Handle, // The voxel cube mesh. voxel_cube: Handle, // The skybox. skybox: Handle, } // The sphere and fox both have this component. #[derive(Component)] struct MainObject; // Marks each of the voxel cubes. #[derive(Component)] struct VoxelCube; // Marks the voxel cube parent object. #[derive(Component)] struct VoxelCubeParent; type VoxelVisualizationMaterial = ExtendedMaterial; #[derive(Asset, TypePath, AsBindGroup, Debug, Clone)] struct VoxelVisualizationExtension { #[uniform(100)] irradiance_volume_info: VoxelVisualizationIrradianceVolumeInfo, } #[derive(ShaderType, Debug, Clone)] struct VoxelVisualizationIrradianceVolumeInfo { transform: Mat4, inverse_transform: Mat4, resolution: UVec3, intensity: f32, } fn main() { // Create the example app. App::new() .add_plugins(DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { title: "Bevy Irradiance Volumes Example".into(), ..default() }), ..default() })) .add_plugins(MaterialPlugin::::default()) .init_resource::() .init_resource::() .insert_resource(AmbientLight { color: Color::WHITE, brightness: 0.0, }) .add_systems(Startup, setup) .add_systems(PreUpdate, create_cubes) .add_systems(Update, rotate_camera) .add_systems(Update, play_animations) .add_systems( Update, handle_mouse_clicks .after(rotate_camera) .after(play_animations), ) .add_systems( Update, change_main_object .after(rotate_camera) .after(play_animations), ) .add_systems( Update, toggle_irradiance_volumes .after(rotate_camera) .after(play_animations), ) .add_systems( Update, toggle_voxel_visibility .after(rotate_camera) .after(play_animations), ) .add_systems( Update, toggle_rotation.after(rotate_camera).after(play_animations), ) .add_systems( Update, draw_gizmo .after(handle_mouse_clicks) .after(change_main_object) .after(toggle_irradiance_volumes) .after(toggle_voxel_visibility) .after(toggle_rotation), ) .add_systems( Update, update_text .after(handle_mouse_clicks) .after(change_main_object) .after(toggle_irradiance_volumes) .after(toggle_voxel_visibility) .after(toggle_rotation), ) .run(); } // Spawns all the scene objects. fn setup( mut commands: Commands, assets: Res, app_status: Res, asset_server: Res, ) { spawn_main_scene(&mut commands, &assets); spawn_camera(&mut commands, &assets); spawn_irradiance_volume(&mut commands, &assets); spawn_light(&mut commands); spawn_sphere(&mut commands, &assets); spawn_voxel_cube_parent(&mut commands); spawn_fox(&mut commands, &assets); spawn_text(&mut commands, &app_status, &asset_server); } fn spawn_main_scene(commands: &mut Commands, assets: &ExampleAssets) { commands.spawn(SceneBundle { scene: assets.main_scene.clone(), ..SceneBundle::default() }); } fn spawn_camera(commands: &mut Commands, assets: &ExampleAssets) { commands .spawn(Camera3dBundle { transform: Transform::from_xyz(-10.012, 4.8605, 13.281).looking_at(Vec3::ZERO, Vec3::Y), ..default() }) .insert(Skybox { image: assets.skybox.clone(), brightness: 150.0, }); } fn spawn_irradiance_volume(commands: &mut Commands, assets: &ExampleAssets) { commands .spawn(SpatialBundle { transform: Transform::from_matrix(VOXEL_TRANSFORM), ..SpatialBundle::default() }) .insert(IrradianceVolume { voxels: assets.irradiance_volume.clone(), intensity: IRRADIANCE_VOLUME_INTENSITY, }) .insert(LightProbe); } fn spawn_light(commands: &mut Commands) { commands.spawn(PointLightBundle { point_light: PointLight { intensity: 250000.0, shadows_enabled: true, ..default() }, transform: Transform::from_xyz(4.0762, 5.9039, 1.0055), ..default() }); } fn spawn_sphere(commands: &mut Commands, assets: &ExampleAssets) { commands .spawn(PbrBundle { mesh: assets.main_sphere.clone(), material: assets.main_sphere_material.clone(), transform: Transform::from_xyz(0.0, SPHERE_SCALE, 0.0) .with_scale(Vec3::splat(SPHERE_SCALE)), ..default() }) .insert(MainObject); } fn spawn_voxel_cube_parent(commands: &mut Commands) { commands .spawn(SpatialBundle { visibility: Visibility::Hidden, ..default() }) .insert(VoxelCubeParent); } fn spawn_fox(commands: &mut Commands, assets: &ExampleAssets) { commands .spawn(SceneBundle { scene: assets.fox.clone(), visibility: Visibility::Hidden, transform: Transform::from_scale(Vec3::splat(FOX_SCALE)), ..default() }) .insert(MainObject); } fn spawn_text(commands: &mut Commands, app_status: &AppStatus, asset_server: &AssetServer) { commands.spawn( TextBundle { text: app_status.create_text(asset_server), ..TextBundle::default() } .with_style(Style { position_type: PositionType::Absolute, bottom: Val::Px(10.0), left: Val::Px(10.0), ..default() }), ); } // A system that updates the help text. fn update_text( mut text_query: Query<&mut Text>, app_status: Res, asset_server: Res, ) { for mut text in text_query.iter_mut() { *text = app_status.create_text(&asset_server); } } impl AppStatus { // Constructs the help text at the bottom of the screen based on the // application status. fn create_text(&self, asset_server: &AssetServer) -> Text { let irradiance_volume_help_text = if self.irradiance_volume_present { DISABLE_IRRADIANCE_VOLUME_HELP_TEXT } else { ENABLE_IRRADIANCE_VOLUME_HELP_TEXT }; let voxels_help_text = if self.voxels_visible { HIDE_VOXELS_HELP_TEXT } else { SHOW_VOXELS_HELP_TEXT }; let rotation_help_text = if self.rotating { STOP_ROTATION_HELP_TEXT } else { START_ROTATION_HELP_TEXT }; let switch_mesh_help_text = match self.model { ExampleModel::Sphere => SWITCH_TO_FOX_HELP_TEXT, ExampleModel::Fox => SWITCH_TO_SPHERE_HELP_TEXT, }; Text::from_section( format!( "{}\n{}\n{}\n{}\n{}", CLICK_TO_MOVE_HELP_TEXT, voxels_help_text, irradiance_volume_help_text, rotation_help_text, switch_mesh_help_text ), TextStyle { font: asset_server.load("fonts/FiraMono-Medium.ttf"), font_size: 24.0, ..default() }, ) } } // Rotates the camera a bit every frame. fn rotate_camera( mut camera_query: Query<&mut Transform, With>, time: Res