1#[cfg(feature = "free_camera")]
3use bevy::camera_controller::free_camera::{FreeCamera, FreeCameraPlugin};
4use std::f32::consts::PI;
5
6use bevy::{
7 anti_alias::taa::TemporalAntiAliasing,
8 camera::Exposure,
9 color::palettes::css::BLACK,
10 core_pipeline::tonemapping::Tonemapping,
11 image::{
12 ImageAddressMode, ImageFilterMode, ImageLoaderSettings, ImageSampler,
13 ImageSamplerDescriptor,
14 },
15 input::keyboard::KeyCode,
16 light::{
17 atmosphere::ScatteringMedium, light_consts::lux, Atmosphere, AtmosphereEnvironmentMapLight,
18 FogVolume, VolumetricFog, VolumetricLight,
19 },
20 pbr::{
21 AtmosphereMode, AtmosphereSettings, DefaultOpaqueRendererMethod, ExtendedMaterial,
22 MaterialExtension, ScreenSpaceReflections,
23 },
24 post_process::bloom::Bloom,
25 prelude::*,
26 render::render_resource::{AsBindGroup, ShaderType},
27 shader::ShaderRef,
28};
29
30#[derive(Resource, Default)]
31struct GameState {
32 paused: bool,
33}
34
35#[derive(Resource)]
36struct AtmospherePresets {
37 earth: Handle<ScatteringMedium>,
38 mars: Handle<ScatteringMedium>,
39}
40
41fn main() {
42 App::new()
43 .insert_resource(DefaultOpaqueRendererMethod::deferred())
44 .insert_resource(ClearColor(Color::BLACK))
45 .insert_resource(GameState::default())
46 .insert_resource(GlobalAmbientLight::NONE)
47 .add_plugins((
48 DefaultPlugins,
49 #[cfg(feature = "free_camera")]
50 FreeCameraPlugin,
51 ))
52 .add_plugins(MaterialPlugin::<ExtendedMaterial<StandardMaterial, Water>>::default())
53 .add_systems(
54 Startup,
55 (setup_camera_fog, setup_terrain_scene, print_controls),
56 )
57 .add_systems(Update, (dynamic_scene, atmosphere_controls))
58 .run();
59}
60
61fn print_controls() {
62 println!("Atmosphere Example Controls:");
63 println!(" 1 - Switch to lookup texture rendering method");
64 println!(" 2 - Switch to raymarched rendering method");
65 println!(" 3 - Switch to Earth atmosphere");
66 println!(" 4 - Switch to Mars atmosphere");
67 println!(" Enter - Pause/Resume sun motion");
68 println!(" Up/Down - Increase/Decrease exposure");
69}
70
71fn atmosphere_controls(
72 keyboard_input: Res<ButtonInput<KeyCode>>,
73 mut planet_atmosphere: Query<(&mut Atmosphere, &mut GlobalTransform)>,
74 mut camera_settings: Query<&mut AtmosphereSettings, With<Camera3d>>,
75 atmosphere_presets: Res<AtmospherePresets>,
76 mut game_state: ResMut<GameState>,
77 mut camera_exposure: Query<&mut Exposure, With<Camera3d>>,
78 time: Res<Time>,
79) {
80 if keyboard_input.just_pressed(KeyCode::Digit3) {
81 for (mut atmosphere, mut transform) in &mut planet_atmosphere {
82 *atmosphere = Atmosphere::earth(atmosphere_presets.earth.clone());
83 *transform = GlobalTransform::from_translation(-Vec3::Y * atmosphere.inner_radius);
84 println!("Switched to Earth atmosphere");
85 }
86 }
87
88 if keyboard_input.just_pressed(KeyCode::Digit4) {
89 for (mut atmosphere, mut transform) in &mut planet_atmosphere {
90 *atmosphere = Atmosphere::mars(atmosphere_presets.mars.clone());
91 *transform = GlobalTransform::from_translation(-Vec3::Y * atmosphere.inner_radius);
92 println!("Switched to Mars atmosphere");
93 }
94 }
95
96 if keyboard_input.just_pressed(KeyCode::Digit1) {
97 for mut settings in &mut camera_settings {
98 settings.rendering_method = AtmosphereMode::LookupTexture;
99 println!("Switched to lookup texture rendering method");
100 }
101 }
102
103 if keyboard_input.just_pressed(KeyCode::Digit2) {
104 for mut settings in &mut camera_settings {
105 settings.rendering_method = AtmosphereMode::Raymarched;
106 println!("Switched to raymarched rendering method");
107 }
108 }
109
110 if keyboard_input.just_pressed(KeyCode::Enter) {
111 game_state.paused = !game_state.paused;
112 }
113
114 if keyboard_input.pressed(KeyCode::ArrowUp) {
115 for mut exposure in &mut camera_exposure {
116 exposure.ev100 -= time.delta_secs() * 2.0;
117 }
118 }
119
120 if keyboard_input.pressed(KeyCode::ArrowDown) {
121 for mut exposure in &mut camera_exposure {
122 exposure.ev100 += time.delta_secs() * 2.0;
123 }
124 }
125}
126
127fn setup_camera_fog(
128 mut commands: Commands,
129 mut scattering_mediums: ResMut<Assets<ScatteringMedium>>,
130 asset_server: Res<AssetServer>,
131) {
132 let earth_medium = scattering_mediums.add(ScatteringMedium::earth(256, 256));
133 let mars_phase = asset_server.load("textures/mars_mie_phase.ktx2");
134 let mars_medium = scattering_mediums.add(ScatteringMedium::mars(256, 256, mars_phase));
135
136 commands.insert_resource(AtmospherePresets {
137 earth: earth_medium.clone(),
138 mars: mars_medium.clone(),
139 });
140
141 commands.spawn(Atmosphere::earth(earth_medium));
143
144 commands.spawn((
145 Camera3d::default(),
146 Transform::from_xyz(-2.8, 0.045, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
147 AtmosphereSettings::default(),
149 Exposure { ev100: 13.0 },
154 Tonemapping::AcesFitted,
157 Bloom::NATURAL,
159 AtmosphereEnvironmentMapLight::default(),
161 #[cfg(feature = "free_camera")]
162 FreeCamera::default(),
163 VolumetricFog {
164 ambient_intensity: 0.0,
165 ..default()
166 },
167 Msaa::Off,
168 TemporalAntiAliasing::default(),
169 ScreenSpaceReflections {
170 min_perceptual_roughness: 0.0..0.0,
171 ..default()
172 },
173 ));
174}
175
176#[derive(Component)]
177struct Terrain;
178
179#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
181struct Water {
182 #[texture(100)]
186 #[sampler(101)]
187 normals: Handle<Image>,
188
189 #[uniform(102)]
191 settings: WaterSettings,
192}
193
194#[derive(ShaderType, Debug, Clone)]
196struct WaterSettings {
197 octave_vectors: [Vec4; 2],
200 octave_scales: Vec4,
202 octave_strengths: Vec4,
204}
205
206impl MaterialExtension for Water {
207 fn deferred_fragment_shader() -> ShaderRef {
208 "shaders/water_material.wgsl".into()
209 }
210}
211
212fn setup_terrain_scene(
213 mut commands: Commands,
214 mut meshes: ResMut<Assets<Mesh>>,
215 mut water_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, Water>>>,
216 asset_server: Res<AssetServer>,
217) {
218 commands.spawn((
220 DirectionalLight {
221 shadow_maps_enabled: true,
222 illuminance: lux::RAW_SUNLIGHT,
228 ..default()
229 },
230 Transform::from_xyz(1.0, 0.4, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
231 VolumetricLight,
232 ));
233
234 commands.spawn((
236 FogVolume::default(),
237 Transform::from_scale(Vec3::new(10.0, 1.0, 10.0)).with_translation(Vec3::Y * 0.5),
238 ));
239
240 commands.spawn((
242 Terrain,
243 WorldAssetRoot(
244 asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/terrain/terrain.glb")),
245 ),
246 Transform::from_xyz(-1.0, 0.0, -0.5)
247 .with_scale(Vec3::splat(0.5))
248 .with_rotation(Quat::from_rotation_y(PI / 2.0)),
249 ));
250
251 spawn_water(
252 &mut commands,
253 &asset_server,
254 &mut meshes,
255 &mut water_materials,
256 );
257}
258
259fn spawn_water(
261 commands: &mut Commands,
262 asset_server: &AssetServer,
263 meshes: &mut Assets<Mesh>,
264 water_materials: &mut Assets<ExtendedMaterial<StandardMaterial, Water>>,
265) {
266 commands.spawn((
267 Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0)))),
268 MeshMaterial3d(
269 water_materials.add(ExtendedMaterial {
270 base: StandardMaterial {
271 base_color: BLACK.into(),
272 perceptual_roughness: 0.0,
273 ..default()
274 },
275 extension: Water {
276 normals: asset_server
277 .load_builder()
278 .with_settings(|settings: &mut ImageLoaderSettings| {
279 settings.is_srgb = false;
280 settings.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
281 address_mode_u: ImageAddressMode::Repeat,
282 address_mode_v: ImageAddressMode::Repeat,
283 mag_filter: ImageFilterMode::Linear,
284 min_filter: ImageFilterMode::Linear,
285 ..default()
286 });
287 })
288 .load("textures/water_normals.png"),
289 settings: WaterSettings {
292 octave_vectors: [
293 vec4(0.080, 0.059, 0.073, -0.062),
294 vec4(0.153, 0.138, -0.149, -0.195),
295 ],
296 octave_scales: vec4(1.0, 2.1, 7.9, 14.9) * 500.0,
297 octave_strengths: vec4(0.16, 0.18, 0.093, 0.044) * 0.2,
298 },
299 },
300 }),
301 ),
302 Transform::from_scale(Vec3::splat(100.0)),
303 ));
304}
305
306fn dynamic_scene(
307 mut suns: Query<&mut Transform, With<DirectionalLight>>,
308 time: Res<Time>,
309 sun_motion_state: Res<GameState>,
310) {
311 if !sun_motion_state.paused {
313 suns.iter_mut()
314 .for_each(|mut tf| tf.rotate_x(-time.delta_secs() * PI / 10.0));
315 }
316}