atmosphere/
atmosphere.rs

1//! This example showcases pbr atmospheric scattering
2
3use std::f32::consts::PI;
4
5use bevy::{
6    camera::Exposure,
7    core_pipeline::tonemapping::Tonemapping,
8    light::{light_consts::lux, AtmosphereEnvironmentMapLight, CascadeShadowConfigBuilder},
9    pbr::{Atmosphere, AtmosphereSettings},
10    post_process::bloom::Bloom,
11    prelude::*,
12};
13
14fn main() {
15    App::new()
16        .add_plugins(DefaultPlugins)
17        .add_systems(Startup, (setup_camera_fog, setup_terrain_scene))
18        .add_systems(Update, dynamic_scene)
19        .run();
20}
21
22fn setup_camera_fog(mut commands: Commands) {
23    commands.spawn((
24        Camera3d::default(),
25        Transform::from_xyz(-1.2, 0.15, 0.0).looking_at(Vec3::Y * 0.1, Vec3::Y),
26        // This is the component that enables atmospheric scattering for a camera
27        Atmosphere::EARTH,
28        // The scene is in units of 10km, so we need to scale up the
29        // aerial view lut distance and set the scene scale accordingly.
30        // Most usages of this feature will not need to adjust this.
31        AtmosphereSettings {
32            aerial_view_lut_max_distance: 3.2e5,
33            scene_units_to_m: 1e+4,
34            ..Default::default()
35        },
36        // The directional light illuminance used in this scene
37        // (the one recommended for use with this feature) is
38        // quite bright, so raising the exposure compensation helps
39        // bring the scene to a nicer brightness range.
40        Exposure::SUNLIGHT,
41        // Tonemapper chosen just because it looked good with the scene, any
42        // tonemapper would be fine :)
43        Tonemapping::AcesFitted,
44        // Bloom gives the sun a much more natural look.
45        Bloom::NATURAL,
46        // Enables the atmosphere to drive reflections and ambient lighting (IBL) for this view
47        AtmosphereEnvironmentMapLight::default(),
48    ));
49}
50
51#[derive(Component)]
52struct Terrain;
53
54fn setup_terrain_scene(
55    mut commands: Commands,
56    mut meshes: ResMut<Assets<Mesh>>,
57    mut materials: ResMut<Assets<StandardMaterial>>,
58    asset_server: Res<AssetServer>,
59) {
60    // Configure a properly scaled cascade shadow map for this scene (defaults are too large, mesh units are in km)
61    let cascade_shadow_config = CascadeShadowConfigBuilder {
62        first_cascade_far_bound: 0.3,
63        maximum_distance: 3.0,
64        ..default()
65    }
66    .build();
67
68    // Sun
69    commands.spawn((
70        DirectionalLight {
71            shadows_enabled: true,
72            // lux::RAW_SUNLIGHT is recommended for use with this feature, since
73            // other values approximate sunlight *post-scattering* in various
74            // conditions. RAW_SUNLIGHT in comparison is the illuminance of the
75            // sun unfiltered by the atmosphere, so it is the proper input for
76            // sunlight to be filtered by the atmosphere.
77            illuminance: lux::RAW_SUNLIGHT,
78            ..default()
79        },
80        Transform::from_xyz(1.0, -0.4, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
81        cascade_shadow_config,
82    ));
83
84    let sphere_mesh = meshes.add(Mesh::from(Sphere { radius: 1.0 }));
85
86    // light probe spheres
87    commands.spawn((
88        Mesh3d(sphere_mesh.clone()),
89        MeshMaterial3d(materials.add(StandardMaterial {
90            base_color: Color::WHITE,
91            metallic: 1.0,
92            perceptual_roughness: 0.0,
93            ..default()
94        })),
95        Transform::from_xyz(-0.3, 0.1, -0.1).with_scale(Vec3::splat(0.05)),
96    ));
97
98    commands.spawn((
99        Mesh3d(sphere_mesh.clone()),
100        MeshMaterial3d(materials.add(StandardMaterial {
101            base_color: Color::WHITE,
102            metallic: 0.0,
103            perceptual_roughness: 1.0,
104            ..default()
105        })),
106        Transform::from_xyz(-0.3, 0.1, 0.1).with_scale(Vec3::splat(0.05)),
107    ));
108
109    // Terrain
110    commands.spawn((
111        Terrain,
112        SceneRoot(
113            asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/terrain/terrain.glb")),
114        ),
115        Transform::from_xyz(-1.0, 0.0, -0.5)
116            .with_scale(Vec3::splat(0.5))
117            .with_rotation(Quat::from_rotation_y(PI / 2.0)),
118    ));
119}
120
121fn dynamic_scene(mut suns: Query<&mut Transform, With<DirectionalLight>>, time: Res<Time>) {
122    suns.iter_mut()
123        .for_each(|mut tf| tf.rotate_x(-time.delta_secs() * PI / 10.0));
124}