Implement procedural atmospheric scattering from [Sebastien Hillaire's 2020 paper](https://sebh.github.io/publications/egsr2020.pdf). This approach should scale well even down to mobile hardware, and is physically accurate. ## Co-author: @mate-h He helped massively with getting this over the finish line, ensuring everything was physically correct, correcting several places where I had misunderstood or misapplied the paper, and improving the performance in several places as well. Thanks! ## Credits @aevyrie: helped find numerous bugs and improve the example to best show off this feature :) Built off of @mtsr's original branch, which handled the transmittance lut (arguably the most important part) ## Showcase:   ## For followup - Integrate with pcwalton's volumetrics code - refactor/reorganize for better integration with other effects - have atmosphere transmittance affect directional lights - add support for generating skybox/environment map --------- Co-authored-by: Emerson Coskey <56370779+EmersonCoskey@users.noreply.github.com> Co-authored-by: atlv <email@atlasdostal.com> Co-authored-by: JMS55 <47158642+JMS55@users.noreply.github.com> Co-authored-by: Emerson Coskey <coskey@emerlabs.net> Co-authored-by: Máté Homolya <mate.homolya@gmail.com>
		
			
				
	
	
		
			126 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			126 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
//! 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<Assets<Mesh>>,
 | 
						|
    mut materials: ResMut<Assets<StandardMaterial>>,
 | 
						|
    asset_server: Res<AssetServer>,
 | 
						|
) {
 | 
						|
    // 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<DirectionalLight>>, time: Res<Time>) {
 | 
						|
    suns.iter_mut()
 | 
						|
        .for_each(|mut tf| tf.rotate_x(-time.delta_secs() * PI / 10.0));
 | 
						|
}
 |