#[cfg(feature = "free_camera")]
use bevy::camera_controller::free_camera::{FreeCamera, FreeCameraPlugin};
use std::f32::consts::PI;
use bevy::{
anti_alias::fxaa::Fxaa,
camera::Exposure,
color::palettes::css::BLACK,
core_pipeline::tonemapping::Tonemapping,
image::{
ImageAddressMode, ImageFilterMode, ImageLoaderSettings, ImageSampler,
ImageSamplerDescriptor,
},
input::keyboard::KeyCode,
light::{
light_consts::lux, AtmosphereEnvironmentMapLight, CascadeShadowConfigBuilder, FogVolume,
VolumetricFog, VolumetricLight,
},
pbr::{
Atmosphere, AtmosphereMode, AtmosphereSettings, DefaultOpaqueRendererMethod,
ExtendedMaterial, MaterialExtension, ScatteringMedium, ScreenSpaceReflections,
},
post_process::bloom::Bloom,
prelude::*,
render::render_resource::{AsBindGroup, ShaderType},
shader::ShaderRef,
};
#[derive(Resource, Default)]
struct GameState {
paused: bool,
}
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!(" Enter - Pause/Resume sun motion");
println!(" Up/Down - Increase/Decrease exposure");
}
fn atmosphere_controls(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut atmosphere_settings: Query<&mut AtmosphereSettings>,
mut game_state: ResMut<GameState>,
mut camera_exposure: Query<&mut Exposure, With<Camera3d>>,
time: Res<Time>,
) {
if keyboard_input.just_pressed(KeyCode::Digit1) {
for mut settings in &mut atmosphere_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 atmosphere_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>>,
) {
commands.spawn((
Camera3d::default(),
Transform::from_xyz(-2.4, 0.04, 0.0).looking_at(Vec3::Y * 0.1, Vec3::Y),
Atmosphere::earthlike(scattering_mediums.add(ScatteringMedium::default())),
AtmosphereSettings::default(),
Exposure { ev100: 13.0 },
Tonemapping::AcesFitted,
Bloom::NATURAL,
AtmosphereEnvironmentMapLight::default(),
#[cfg(feature = "free_camera")]
FreeCamera::default(),
VolumetricFog {
ambient_intensity: 0.0,
..default()
},
Msaa::Off,
Fxaa::default(),
ScreenSpaceReflections::default(),
));
}
#[derive(Component)]
struct Terrain;
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
struct Water {
#[texture(100)]
#[sampler(101)]
normals: Handle<Image>,
#[uniform(102)]
settings: WaterSettings,
}
#[derive(ShaderType, Debug, Clone)]
struct WaterSettings {
octave_vectors: [Vec4; 2],
octave_scales: Vec4,
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 materials: ResMut<Assets<StandardMaterial>>,
mut water_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, Water>>>,
asset_server: Res<AssetServer>,
) {
let cascade_shadow_config = CascadeShadowConfigBuilder {
first_cascade_far_bound: 0.3,
maximum_distance: 15.0,
..default()
}
.build();
commands.spawn((
DirectionalLight {
shadows_enabled: true,
illuminance: lux::RAW_SUNLIGHT,
..default()
},
Transform::from_xyz(1.0, 0.4, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
VolumetricLight,
cascade_shadow_config,
));
commands.spawn((
FogVolume::default(),
Transform::from_scale(Vec3::new(10.0, 1.0, 10.0)).with_translation(Vec3::Y * 0.5),
));
let sphere_mesh = meshes.add(Mesh::from(Sphere { radius: 1.0 }));
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(-1.0, 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(-1.0, 0.1, 0.1).with_scale(Vec3::splat(0.05)),
));
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)),
));
spawn_water(
&mut commands,
&asset_server,
&mut meshes,
&mut water_materials,
);
}
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_with_settings::<Image, ImageLoaderSettings>(
"textures/water_normals.png",
|settings| {
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()
});
},
),
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>,
) {
if !sun_motion_state.paused {
suns.iter_mut()
.for_each(|mut tf| tf.rotate_x(-time.delta_secs() * PI / 10.0));
}
}