//! This example showcases pbr atmospheric scattering use std::f32::consts::PI; use bevy::{ core_pipeline::{bloom::Bloom, tonemapping::Tonemapping}, pbr::{light_consts::lux, Atmosphere, AtmosphereSettings, CascadeShadowConfigBuilder}, prelude::*, render::camera::Exposure, }; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, (setup_camera_fog, setup_terrain_scene)) .add_systems(Update, dynamic_scene) .run(); } fn setup_camera_fog(mut commands: Commands) { commands.spawn(( Camera3d::default(), // HDR is required for atmospheric scattering to be properly applied to the scene Camera { hdr: true, ..default() }, Transform::from_xyz(-1.2, 0.15, 0.0).looking_at(Vec3::Y * 0.1, Vec3::Y), // This is the component that enables atmospheric scattering for a camera Atmosphere::EARTH, // The scene is in units of 10km, so we need to scale up the // aerial view lut distance and set the scene scale accordingly. // Most usages of this feature will not need to adjust this. AtmosphereSettings { aerial_view_lut_max_distance: 3.2e5, scene_units_to_m: 1e+4, ..Default::default() }, // The directional light illuminance used in this scene // (the one recommended for use with this feature) is // quite bright, so raising the exposure compensation helps // bring the scene to a nicer brightness range. Exposure::SUNLIGHT, // Tonemapper chosen just because it looked good with the scene, any // tonemapper would be fine :) Tonemapping::AcesFitted, // Bloom gives the sun a much more natural look. Bloom::NATURAL, )); } #[derive(Component)] struct Terrain; fn setup_terrain_scene( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, asset_server: Res, ) { // Configure a properly scaled cascade shadow map for this scene (defaults are too large, mesh units are in km) let cascade_shadow_config = CascadeShadowConfigBuilder { first_cascade_far_bound: 0.3, maximum_distance: 3.0, ..default() } .build(); // Sun commands.spawn(( DirectionalLight { shadows_enabled: true, // lux::RAW_SUNLIGHT is recommended for use with this feature, since // other values approximate sunlight *post-scattering* in various // conditions. RAW_SUNLIGHT in comparison is the illuminance of the // sun unfiltered by the atmosphere, so it is the proper input for // sunlight to be filtered by the atmosphere. illuminance: lux::RAW_SUNLIGHT, ..default() }, Transform::from_xyz(1.0, -0.4, 0.0).looking_at(Vec3::ZERO, Vec3::Y), cascade_shadow_config, )); let sphere_mesh = meshes.add(Mesh::from(Sphere { radius: 1.0 })); // light probe spheres commands.spawn(( Mesh3d(sphere_mesh.clone()), MeshMaterial3d(materials.add(StandardMaterial { base_color: Color::WHITE, metallic: 1.0, perceptual_roughness: 0.0, ..default() })), Transform::from_xyz(-0.3, 0.1, -0.1).with_scale(Vec3::splat(0.05)), )); commands.spawn(( Mesh3d(sphere_mesh.clone()), MeshMaterial3d(materials.add(StandardMaterial { base_color: Color::WHITE, metallic: 0.0, perceptual_roughness: 1.0, ..default() })), Transform::from_xyz(-0.3, 0.1, 0.1).with_scale(Vec3::splat(0.05)), )); // Terrain commands.spawn(( Terrain, SceneRoot( asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/terrain/terrain.glb")), ), Transform::from_xyz(-1.0, 0.0, -0.5) .with_scale(Vec3::splat(0.5)) .with_rotation(Quat::from_rotation_y(PI / 2.0)), )); } fn dynamic_scene(mut suns: Query<&mut Transform, With>, time: Res