mirror of
https://github.com/bevyengine/bevy.git
synced 2026-06-29 23:35:35 -04:00
7496e1b639
In the atmosphere example we can change between Earth and Mars, but the size of the sun does not change. This somewhat bothered me from an astronomical accuarcy. This commit introduces a new `SunDisk::MARS` constant to accurately represent the sun’s apparent size when viewed from Mars. The implementation updates the documentation and adds the corresponding constant with an angular size of ~21 arcminutes (0.00615 radians). Key modifications: - Added `SunDisk::MARS` constant in `directional_light.rs` - Updated documentation to reference the new Mars preset - Modified `atmosphere` example to switch sun disk alongside atmosphere presets I tested this manually. --------- Co-authored-by: Máté Homolya <mate.homolya@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
325 lines
11 KiB
Rust
325 lines
11 KiB
Rust
//! This example showcases pbr atmospheric scattering
|
|
#[cfg(feature = "free_camera")]
|
|
use bevy::camera_controller::free_camera::{FreeCamera, FreeCameraPlugin};
|
|
use std::f32::consts::PI;
|
|
|
|
use bevy::{
|
|
anti_alias::taa::TemporalAntiAliasing,
|
|
camera::Exposure,
|
|
color::palettes::css::BLACK,
|
|
core_pipeline::tonemapping::Tonemapping,
|
|
image::{
|
|
ImageAddressMode, ImageFilterMode, ImageLoaderSettings, ImageSampler,
|
|
ImageSamplerDescriptor,
|
|
},
|
|
input::keyboard::KeyCode,
|
|
light::{
|
|
atmosphere::ScatteringMedium, light_consts::lux, Atmosphere, AtmosphereEnvironmentMapLight,
|
|
FogVolume, SunDisk, VolumetricFog, VolumetricLight,
|
|
},
|
|
pbr::{
|
|
AtmosphereMode, AtmosphereSettings, DefaultOpaqueRendererMethod, ExtendedMaterial,
|
|
MaterialExtension, ScreenSpaceReflections,
|
|
},
|
|
post_process::bloom::Bloom,
|
|
prelude::*,
|
|
render::render_resource::{AsBindGroup, ShaderType},
|
|
shader::ShaderRef,
|
|
};
|
|
|
|
#[derive(Resource, Default)]
|
|
struct GameState {
|
|
paused: bool,
|
|
}
|
|
|
|
#[derive(Resource)]
|
|
struct AtmospherePresets {
|
|
earth: Handle<ScatteringMedium>,
|
|
mars: Handle<ScatteringMedium>,
|
|
}
|
|
|
|
fn main() {
|
|
App::new()
|
|
.insert_resource(DefaultOpaqueRendererMethod::deferred())
|
|
.insert_resource(ClearColor(Color::BLACK))
|
|
.insert_resource(GameState::default())
|
|
.insert_resource(GlobalAmbientLight::NONE)
|
|
.add_plugins((
|
|
DefaultPlugins,
|
|
#[cfg(feature = "free_camera")]
|
|
FreeCameraPlugin,
|
|
))
|
|
.add_plugins(MaterialPlugin::<ExtendedMaterial<StandardMaterial, Water>>::default())
|
|
.add_systems(
|
|
Startup,
|
|
(setup_camera_fog, setup_terrain_scene, print_controls),
|
|
)
|
|
.add_systems(Update, (dynamic_scene, atmosphere_controls))
|
|
.run();
|
|
}
|
|
|
|
fn print_controls() {
|
|
println!("Atmosphere Example Controls:");
|
|
println!(" 1 - Switch to lookup texture rendering method");
|
|
println!(" 2 - Switch to raymarched rendering method");
|
|
println!(" 3 - Switch to Earth atmosphere");
|
|
println!(" 4 - Switch to Mars atmosphere");
|
|
println!(" Enter - Pause/Resume sun motion");
|
|
println!(" Up/Down - Increase/Decrease exposure");
|
|
}
|
|
|
|
fn atmosphere_controls(
|
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
|
mut planet_atmosphere: Query<(&mut Atmosphere, &mut GlobalTransform)>,
|
|
mut camera_settings: Query<&mut AtmosphereSettings, With<Camera3d>>,
|
|
mut sun_disks: Query<&mut SunDisk, With<DirectionalLight>>,
|
|
atmosphere_presets: Res<AtmospherePresets>,
|
|
mut game_state: ResMut<GameState>,
|
|
mut camera_exposure: Query<&mut Exposure, With<Camera3d>>,
|
|
time: Res<Time>,
|
|
) {
|
|
if keyboard_input.just_pressed(KeyCode::Digit3) {
|
|
for (mut atmosphere, mut transform) in &mut planet_atmosphere {
|
|
*atmosphere = Atmosphere::earth(atmosphere_presets.earth.clone());
|
|
*transform = GlobalTransform::from_translation(-Vec3::Y * atmosphere.inner_radius);
|
|
}
|
|
for mut sun_disk in &mut sun_disks {
|
|
sun_disk.angular_size = SunDisk::EARTH.angular_size;
|
|
}
|
|
println!("Switched to Earth atmosphere");
|
|
}
|
|
|
|
if keyboard_input.just_pressed(KeyCode::Digit4) {
|
|
for (mut atmosphere, mut transform) in &mut planet_atmosphere {
|
|
*atmosphere = Atmosphere::mars(atmosphere_presets.mars.clone());
|
|
*transform = GlobalTransform::from_translation(-Vec3::Y * atmosphere.inner_radius);
|
|
}
|
|
for mut sun_disk in &mut sun_disks {
|
|
sun_disk.angular_size = SunDisk::MARS.angular_size;
|
|
}
|
|
println!("Switched to Mars atmosphere");
|
|
}
|
|
|
|
if keyboard_input.just_pressed(KeyCode::Digit1) {
|
|
for mut settings in &mut camera_settings {
|
|
settings.rendering_method = AtmosphereMode::LookupTexture;
|
|
println!("Switched to lookup texture rendering method");
|
|
}
|
|
}
|
|
|
|
if keyboard_input.just_pressed(KeyCode::Digit2) {
|
|
for mut settings in &mut camera_settings {
|
|
settings.rendering_method = AtmosphereMode::Raymarched;
|
|
println!("Switched to raymarched rendering method");
|
|
}
|
|
}
|
|
|
|
if keyboard_input.just_pressed(KeyCode::Enter) {
|
|
game_state.paused = !game_state.paused;
|
|
}
|
|
|
|
if keyboard_input.pressed(KeyCode::ArrowUp) {
|
|
for mut exposure in &mut camera_exposure {
|
|
exposure.ev100 -= time.delta_secs() * 2.0;
|
|
}
|
|
}
|
|
|
|
if keyboard_input.pressed(KeyCode::ArrowDown) {
|
|
for mut exposure in &mut camera_exposure {
|
|
exposure.ev100 += time.delta_secs() * 2.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn setup_camera_fog(
|
|
mut commands: Commands,
|
|
mut scattering_mediums: ResMut<Assets<ScatteringMedium>>,
|
|
asset_server: Res<AssetServer>,
|
|
) {
|
|
let earth_medium = scattering_mediums.add(ScatteringMedium::earth(256, 256));
|
|
let mars_phase = asset_server.load("textures/mars_mie_phase.ktx2");
|
|
let mars_medium = scattering_mediums.add(ScatteringMedium::mars(256, 256, mars_phase));
|
|
|
|
commands.insert_resource(AtmospherePresets {
|
|
earth: earth_medium.clone(),
|
|
mars: mars_medium.clone(),
|
|
});
|
|
|
|
// Spawn earth atmosphere
|
|
commands.spawn(Atmosphere::earth(earth_medium));
|
|
|
|
commands.spawn((
|
|
Camera3d::default(),
|
|
Transform::from_xyz(-2.8, 0.045, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
|
|
// Can be adjusted to change the rendering quality
|
|
AtmosphereSettings::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 { ev100: 13.0 },
|
|
// 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,
|
|
// Enables the atmosphere to drive reflections and ambient lighting (IBL) for this view
|
|
AtmosphereEnvironmentMapLight::default(),
|
|
#[cfg(feature = "free_camera")]
|
|
FreeCamera::default(),
|
|
VolumetricFog {
|
|
ambient_intensity: 0.0,
|
|
..default()
|
|
},
|
|
Msaa::Off,
|
|
TemporalAntiAliasing::default(),
|
|
ScreenSpaceReflections {
|
|
min_perceptual_roughness: 0.0..0.0,
|
|
..default()
|
|
},
|
|
));
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct Terrain;
|
|
|
|
/// A custom [`ExtendedMaterial`] that creates animated water ripples.
|
|
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
|
|
struct Water {
|
|
/// The normal map image.
|
|
///
|
|
/// Note that, like all normal maps, this must not be loaded as sRGB.
|
|
#[texture(100)]
|
|
#[sampler(101)]
|
|
normals: Handle<Image>,
|
|
|
|
// Parameters to the water shader.
|
|
#[uniform(102)]
|
|
settings: WaterSettings,
|
|
}
|
|
|
|
/// Parameters to the water shader.
|
|
#[derive(ShaderType, Debug, Clone)]
|
|
struct WaterSettings {
|
|
/// How much to displace each octave each frame, in the u and v directions.
|
|
/// Two octaves are packed into each `vec4`.
|
|
octave_vectors: [Vec4; 2],
|
|
/// How wide the waves are in each octave.
|
|
octave_scales: Vec4,
|
|
/// How high the waves are in each octave.
|
|
octave_strengths: Vec4,
|
|
}
|
|
|
|
impl MaterialExtension for Water {
|
|
fn deferred_fragment_shader() -> ShaderRef {
|
|
"shaders/water_material.wgsl".into()
|
|
}
|
|
}
|
|
|
|
fn setup_terrain_scene(
|
|
mut commands: Commands,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut water_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, Water>>>,
|
|
asset_server: Res<AssetServer>,
|
|
) {
|
|
// Sun
|
|
commands.spawn((
|
|
DirectionalLight {
|
|
shadow_maps_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),
|
|
VolumetricLight,
|
|
SunDisk::EARTH,
|
|
));
|
|
|
|
// spawn the fog volume
|
|
commands.spawn((
|
|
FogVolume::default(),
|
|
Transform::from_scale(Vec3::new(10.0, 1.0, 10.0)).with_translation(Vec3::Y * 0.5),
|
|
));
|
|
|
|
// Terrain
|
|
commands.spawn((
|
|
Terrain,
|
|
WorldAssetRoot(
|
|
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)),
|
|
));
|
|
|
|
spawn_water(
|
|
&mut commands,
|
|
&asset_server,
|
|
&mut meshes,
|
|
&mut water_materials,
|
|
);
|
|
}
|
|
|
|
// Spawns the water plane.
|
|
fn spawn_water(
|
|
commands: &mut Commands,
|
|
asset_server: &AssetServer,
|
|
meshes: &mut Assets<Mesh>,
|
|
water_materials: &mut Assets<ExtendedMaterial<StandardMaterial, Water>>,
|
|
) {
|
|
commands.spawn((
|
|
Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0)))),
|
|
MeshMaterial3d(
|
|
water_materials.add(ExtendedMaterial {
|
|
base: StandardMaterial {
|
|
base_color: BLACK.into(),
|
|
perceptual_roughness: 0.0,
|
|
..default()
|
|
},
|
|
extension: Water {
|
|
normals: asset_server
|
|
.load_builder()
|
|
.with_settings(|settings: &mut ImageLoaderSettings| {
|
|
settings.is_srgb = false;
|
|
settings.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
|
|
address_mode_u: ImageAddressMode::Repeat,
|
|
address_mode_v: ImageAddressMode::Repeat,
|
|
mag_filter: ImageFilterMode::Linear,
|
|
min_filter: ImageFilterMode::Linear,
|
|
..default()
|
|
});
|
|
})
|
|
.load("textures/water_normals.png"),
|
|
// These water settings are just random values to create some
|
|
// variety.
|
|
settings: WaterSettings {
|
|
octave_vectors: [
|
|
vec4(0.080, 0.059, 0.073, -0.062),
|
|
vec4(0.153, 0.138, -0.149, -0.195),
|
|
],
|
|
octave_scales: vec4(1.0, 2.1, 7.9, 14.9) * 500.0,
|
|
octave_strengths: vec4(0.16, 0.18, 0.093, 0.044) * 0.2,
|
|
},
|
|
},
|
|
}),
|
|
),
|
|
Transform::from_scale(Vec3::splat(100.0)),
|
|
));
|
|
}
|
|
|
|
fn dynamic_scene(
|
|
mut suns: Query<&mut Transform, With<DirectionalLight>>,
|
|
time: Res<Time>,
|
|
sun_motion_state: Res<GameState>,
|
|
) {
|
|
// Only rotate the sun if motion is not paused
|
|
if !sun_motion_state.paused {
|
|
suns.iter_mut()
|
|
.for_each(|mut tf| tf.rotate_x(-time.delta_secs() * PI / 10.0));
|
|
}
|
|
}
|