From 7669522b88546239310065c062fa4e18dfed5f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81te=CC=81=20Homolya?= Date: Sat, 30 Aug 2025 21:46:10 -0700 Subject: [PATCH 01/19] Occlude directional light by atmosphere --- .../src/atmosphere/bruneton_functions.wgsl | 16 +++--- crates/bevy_pbr/src/atmosphere/functions.wgsl | 28 +++++++++- crates/bevy_pbr/src/atmosphere/mod.rs | 6 ++ crates/bevy_pbr/src/atmosphere/resources.rs | 52 ++++++++++++++++++ crates/bevy_pbr/src/atmosphere/types.wgsl | 5 ++ crates/bevy_pbr/src/render/mesh.rs | 21 ++++++- .../bevy_pbr/src/render/mesh_view_bindings.rs | 55 ++++++++++++++++++- .../src/render/mesh_view_bindings.wgsl | 7 +++ crates/bevy_pbr/src/render/pbr_lighting.wgsl | 33 ++++++++++- 9 files changed, 206 insertions(+), 17 deletions(-) diff --git a/crates/bevy_pbr/src/atmosphere/bruneton_functions.wgsl b/crates/bevy_pbr/src/atmosphere/bruneton_functions.wgsl index b7e0fc4e7cc08..1e559ca92991b 100644 --- a/crates/bevy_pbr/src/atmosphere/bruneton_functions.wgsl +++ b/crates/bevy_pbr/src/atmosphere/bruneton_functions.wgsl @@ -64,19 +64,19 @@ // Assuming r between ground and top atmosphere boundary, and mu= cos(zenith_angle) // Chosen to increase precision near the ground and to work around a discontinuity at the horizon // See Bruneton and Neyret 2008, "Precomputed Atmospheric Scattering" section 4 -fn transmittance_lut_r_mu_to_uv(r: f32, mu: f32) -> vec2 { +fn transmittance_lut_r_mu_to_uv(atm: Atmosphere, r: f32, mu: f32) -> vec2 { // Distance along a horizontal ray from the ground to the top atmosphere boundary - let H = sqrt(atmosphere.top_radius * atmosphere.top_radius - atmosphere.bottom_radius * atmosphere.bottom_radius); + let H = sqrt(atm.top_radius * atm.top_radius - atm.bottom_radius * atm.bottom_radius); // Distance from a point at height r to the horizon // ignore the case where r <= atmosphere.bottom_radius - let rho = sqrt(max(r * r - atmosphere.bottom_radius * atmosphere.bottom_radius, 0.0)); + let rho = sqrt(max(r * r - atm.bottom_radius * atm.bottom_radius, 0.0)); // Distance from a point at height r to the top atmosphere boundary at zenith angle mu - let d = distance_to_top_atmosphere_boundary(r, mu); + let d = distance_to_top_atmosphere_boundary(atm, r, mu); // Minimum and maximum distance to the top atmosphere boundary from a point at height r - let d_min = atmosphere.top_radius - r; // length of the ray straight up to the top atmosphere boundary + let d_min = atm.top_radius - r; // length of the ray straight up to the top atmosphere boundary let d_max = rho + H; // length of the ray to the top atmosphere boundary and grazing the horizon let u = (d - d_min) / (d_max - d_min); @@ -121,9 +121,9 @@ fn transmittance_lut_uv_to_r_mu(uv: vec2) -> vec2 { /// Center of sphere, c = [0,0,0] /// Radius of sphere, r = atmosphere.top_radius /// This function solves the quadratic equation for line-sphere intersection simplified under these assumptions -fn distance_to_top_atmosphere_boundary(r: f32, mu: f32) -> f32 { - // ignore the case where r > atmosphere.top_radius - let positive_discriminant = max(r * r * (mu * mu - 1.0) + atmosphere.top_radius * atmosphere.top_radius, 0.0); +fn distance_to_top_atmosphere_boundary(atm: Atmosphere, r: f32, mu: f32) -> f32 { + // ignore the case where r > atm.top_radius + let positive_discriminant = max(r * r * (mu * mu - 1.0) + atm.top_radius * atm.top_radius, 0.0); return max(-r * mu + sqrt(positive_discriminant), 0.0); } diff --git a/crates/bevy_pbr/src/atmosphere/functions.wgsl b/crates/bevy_pbr/src/atmosphere/functions.wgsl index e63f22d4d58cc..8c8c1d8cb11e3 100644 --- a/crates/bevy_pbr/src/atmosphere/functions.wgsl +++ b/crates/bevy_pbr/src/atmosphere/functions.wgsl @@ -114,7 +114,7 @@ fn sky_view_lut_uv_to_zenith_azimuth(r: f32, uv: vec2) -> vec2 { // LUT SAMPLING fn sample_transmittance_lut(r: f32, mu: f32) -> vec3 { - let uv = transmittance_lut_r_mu_to_uv(r, mu); + let uv = transmittance_lut_r_mu_to_uv(atmosphere, r, mu); return textureSampleLevel(transmittance_lut, transmittance_lut_sampler, uv, 0.0).rgb; } @@ -300,10 +300,34 @@ fn sample_sun_radiance(ray_dir_ws: vec3) -> vec3 { return sun_radiance; } +fn calculate_visible_sun_ratio(atmosphere: Atmosphere, r: f32, mu: f32, sun_angular_size: f32) -> f32 { + let bottom_radius = atmosphere.bottom_radius; + // Calculate the angle between horizon and sun center + // Invert the horizon angle calculation to fix shading direction + let horizon_cos = -sqrt(1.0 - (bottom_radius * bottom_radius) / (r * r)); + let horizon_angle = acos(horizon_cos); + let sun_zenith_angle = acos(mu); + + // If sun is completely above horizon + if sun_zenith_angle + sun_angular_size * 0.5 <= horizon_angle { + return 1.0; + } + + // If sun is completely below horizon + if sun_zenith_angle - sun_angular_size * 0.5 >= horizon_angle { + return 0.0; + } + + // Calculate partial visibility using circular segment area formula + let d = (horizon_angle - sun_zenith_angle) / (sun_angular_size * 0.5); + let visible_ratio = 0.5 + d * 0.5; + return clamp(visible_ratio, 0.0, 1.0); +} + // TRANSFORM UTILITIES fn max_atmosphere_distance(r: f32, mu: f32) -> f32 { - let t_top = distance_to_top_atmosphere_boundary(r, mu); + let t_top = distance_to_top_atmosphere_boundary(atmosphere, r, mu); let t_bottom = distance_to_bottom_atmosphere_boundary(r, mu); let hits = ray_intersects_ground(r, mu); return mix(t_top, t_bottom, f32(hits)); diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index 608cf02314bf4..2ae7db3336803 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -76,6 +76,8 @@ use resources::{ }; use tracing::warn; +use crate::resources::{prepare_atmosphere_buffer, AtmosphereBuffer}; + use self::{ node::{AtmosphereLutsNode, AtmosphereNode, RenderSkyNode}, resources::{ @@ -142,6 +144,7 @@ impl Plugin for AtmospherePlugin { .init_resource::() .init_resource::() .init_resource::() + .init_resource::() .init_resource::>() .add_systems( RenderStartup, @@ -159,6 +162,9 @@ impl Plugin for AtmospherePlugin { prepare_atmosphere_probe_bind_groups.in_set(RenderSystems::PrepareBindGroups), prepare_atmosphere_transforms.in_set(RenderSystems::PrepareResources), prepare_atmosphere_bind_groups.in_set(RenderSystems::PrepareBindGroups), + prepare_atmosphere_buffer + .in_set(RenderSystems::PrepareResources) + .before(RenderSystems::PrepareBindGroups), ), ) .add_render_graph_node::>( diff --git a/crates/bevy_pbr/src/atmosphere/resources.rs b/crates/bevy_pbr/src/atmosphere/resources.rs index fe487975e8d6f..70fd7458bc463 100644 --- a/crates/bevy_pbr/src/atmosphere/resources.rs +++ b/crates/bevy_pbr/src/atmosphere/resources.rs @@ -698,3 +698,55 @@ pub(super) fn prepare_atmosphere_bind_groups( }); } } + +#[derive(ShaderType)] +#[repr(C)] +pub(crate) struct AtmosphereData { + pub atmosphere: Atmosphere, + pub settings: AtmosphereSettings, +} + +impl FromWorld for AtmosphereBuffer { + fn from_world(world: &mut World) -> Self { + let data = world + .query_filtered::<(&Atmosphere, &AtmosphereSettings), With>() + .iter(world) + .next() + .map_or_else( + || AtmosphereData { + atmosphere: Atmosphere::default(), + settings: AtmosphereSettings::default(), + }, + |(atmosphere, settings)| AtmosphereData { + atmosphere: atmosphere.clone(), + settings: settings.clone(), + }, + ); + + Self { + buffer: StorageBuffer::from(data), + } + } +} + +#[derive(Resource)] +pub struct AtmosphereBuffer { + pub(crate) buffer: StorageBuffer, +} + +pub(crate) fn prepare_atmosphere_buffer( + device: Res, + queue: Res, + atmosphere_entity: Query<(&Atmosphere, &AtmosphereSettings), With>, + mut atmosphere_buffer: ResMut, +) { + let Ok((atmosphere, settings)) = atmosphere_entity.single() else { + return; + }; + + atmosphere_buffer.buffer.set(AtmosphereData { + atmosphere: atmosphere.clone(), + settings: settings.clone(), + }); + atmosphere_buffer.buffer.write_buffer(&device, &queue); +} diff --git a/crates/bevy_pbr/src/atmosphere/types.wgsl b/crates/bevy_pbr/src/atmosphere/types.wgsl index f9207dd7228c5..29ab3eb56bf21 100644 --- a/crates/bevy_pbr/src/atmosphere/types.wgsl +++ b/crates/bevy_pbr/src/atmosphere/types.wgsl @@ -44,3 +44,8 @@ struct AtmosphereSettings { struct AtmosphereTransforms { world_from_atmosphere: mat4x4, } + +struct AtmosphereData { + atmosphere: Atmosphere, + settings: AtmosphereSettings, +} \ No newline at end of file diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 922294f0657f4..5b128e6714b88 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1,4 +1,7 @@ -use crate::material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot}; +use crate::{ + material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot}, + resources::prepare_atmosphere_buffer, +}; use bevy_asset::{embedded_asset, load_embedded_asset, AssetId}; use bevy_camera::{ primitives::Aabb, @@ -194,7 +197,8 @@ impl Plugin for MeshRenderPlugin { prepare_mesh_bind_groups.in_set(RenderSystems::PrepareBindGroups), prepare_mesh_view_bind_groups .in_set(RenderSystems::PrepareBindGroups) - .after(prepare_oit_buffers), + .after(prepare_oit_buffers) + .after(prepare_atmosphere_buffer), no_gpu_preprocessing::clear_batched_cpu_instance_buffers:: .in_set(RenderSystems::Cleanup) .after(RenderSystems::Render), @@ -324,6 +328,7 @@ pub fn check_views_need_specialization( Has>, ), Has, + Has, )>, ticks: SystemChangeTick, ) { @@ -341,6 +346,7 @@ pub fn check_views_need_specialization( distance_fog, (has_environment_maps, has_irradiance_volumes), has_oit, + has_atmosphere, ) in views.iter_mut() { let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) @@ -378,6 +384,10 @@ pub fn check_views_need_specialization( view_key |= MeshPipelineKey::OIT_ENABLED; } + if has_atmosphere { + view_key |= MeshPipelineKey::ATMOSPHERE; + } + if let Some(projection) = projection { view_key |= match projection { Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE, @@ -2093,7 +2103,8 @@ bitflags::bitflags! { const HAS_PREVIOUS_MORPH = 1 << 19; const OIT_ENABLED = 1 << 20; const DISTANCE_FOG = 1 << 21; - const LAST_FLAG = Self::DISTANCE_FOG.bits(); + const ATMOSPHERE = 1 << 22; + const LAST_FLAG = Self::ATMOSPHERE.bits(); // Bitfields const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; @@ -2563,6 +2574,10 @@ impl SpecializedMeshPipeline for MeshPipeline { shader_defs.push("DISTANCE_FOG".into()); } + if key.contains(MeshPipelineKey::ATMOSPHERE) { + shader_defs.push("ATMOSPHERE".into()); + } + if self.binding_arrays_are_usable { shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into()); shader_defs.push("MULTIPLE_LIGHTMAPS_IN_ARRAY".into()); diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 5c1e0e8a47b7b..da440c99a17c7 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -43,7 +43,9 @@ use crate::{ irradiance_volume::{ self, RenderViewIrradianceVolumeBindGroupEntries, IRRADIANCE_VOLUMES_ARE_USABLE, }, - prepass, EnvironmentMapUniformBuffer, FogMeta, GlobalClusterableObjectMeta, + prepass, + resources::{AtmosphereBuffer, AtmosphereData, AtmosphereSamplers, AtmosphereTextures}, + Atmosphere, EnvironmentMapUniformBuffer, FogMeta, GlobalClusterableObjectMeta, GpuClusterableObjects, GpuFog, GpuLights, LightMeta, LightProbesBuffer, LightProbesUniform, MeshPipeline, MeshPipelineKey, RenderViewLightProbes, ScreenSpaceAmbientOcclusionResources, ScreenSpaceReflectionsBuffer, ScreenSpaceReflectionsUniform, ShadowSamplers, @@ -81,6 +83,7 @@ bitflags::bitflags! { const MOTION_VECTOR_PREPASS = 1 << 3; const DEFERRED_PREPASS = 1 << 4; const OIT_ENABLED = 1 << 5; + const ATMOSPHERE = 1 << 6; } } @@ -93,7 +96,7 @@ impl MeshPipelineViewLayoutKey { use MeshPipelineViewLayoutKey as Key; format!( - "mesh_view_layout{}{}{}{}{}{}", + "mesh_view_layout{}{}{}{}{}{}{}", if self.contains(Key::MULTISAMPLED) { "_multisampled" } else { @@ -124,6 +127,11 @@ impl MeshPipelineViewLayoutKey { } else { Default::default() }, + if self.contains(Key::ATMOSPHERE) { + "_atmosphere" + } else { + Default::default() + }, ) } } @@ -150,6 +158,9 @@ impl From for MeshPipelineViewLayoutKey { if value.contains(MeshPipelineKey::OIT_ENABLED) { result |= MeshPipelineViewLayoutKey::OIT_ENABLED; } + if value.contains(MeshPipelineKey::ATMOSPHERE) { + result |= MeshPipelineViewLayoutKey::ATMOSPHERE; + } result } @@ -385,6 +396,20 @@ fn layout_entries( } } + // Atmosphere + if layout_key.contains(MeshPipelineViewLayoutKey::ATMOSPHERE) { + entries = entries.extend_with_indices(( + // transmittance LUT + ( + 29, + texture_2d(TextureSampleType::Float { filterable: true }), + ), + (30, sampler(SamplerBindingType::Filtering)), + // atmosphere data buffer + (31, storage_buffer_read_only::(false)), + )); + } + let mut binding_array_entries = DynamicBindGroupLayoutEntries::new(ShaderStages::FRAGMENT); binding_array_entries = binding_array_entries.extend_with_indices(( (0, environment_map_entries[0]), @@ -558,6 +583,8 @@ pub fn prepare_mesh_view_bind_groups( Option<&RenderViewLightProbes>, Option<&RenderViewLightProbes>, Has, + Option<&AtmosphereTextures>, + Has, )>, (images, mut fallback_images, fallback_image, fallback_image_zero): ( Res>, @@ -571,7 +598,12 @@ pub fn prepare_mesh_view_bind_groups( visibility_ranges: Res, ssr_buffer: Res, oit_buffers: Res, - (decals_buffer, render_decals): (Res, Res), + (decals_buffer, render_decals, atmosphere_buffer, atmosphere_samplers): ( + Res, + Res, + Res, + Res, + ), ) { if let ( Some(view_binding), @@ -606,6 +638,8 @@ pub fn prepare_mesh_view_bind_groups( render_view_environment_maps, render_view_irradiance_volumes, has_oit, + atmosphere_textures, + has_atmosphere, ) in &views { let fallback_ssao = fallback_images @@ -621,6 +655,9 @@ pub fn prepare_mesh_view_bind_groups( if has_oit { layout_key |= MeshPipelineViewLayoutKey::OIT_ENABLED; } + if has_atmosphere { + layout_key |= MeshPipelineViewLayoutKey::ATMOSPHERE; + } let layout = mesh_pipeline.get_view_layout(layout_key); @@ -701,6 +738,18 @@ pub fn prepare_mesh_view_bind_groups( )); } + if has_atmosphere { + if let (Some(atmosphere_textures), Some(atmosphere_buffer_binding)) = + (atmosphere_textures, atmosphere_buffer.buffer.binding()) + { + entries = entries.extend_with_indices(( + (29, &atmosphere_textures.transmittance_lut.default_view), + (30, &atmosphere_samplers.transmittance_lut), + (31, atmosphere_buffer_binding), + )); + } + } + let mut entries_binding_array = DynamicBindGroupEntries::new(); let environment_map_bind_group_entries = RenderViewEnvironmentMapBindGroupEntries::get( diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index 0f650e6e54dbb..5aef30ba05637 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -1,6 +1,7 @@ #define_import_path bevy_pbr::mesh_view_bindings #import bevy_pbr::mesh_view_types as types +#import bevy_pbr::atmosphere::types as atmosphere #import bevy_render::{ view::View, globals::Globals, @@ -94,6 +95,12 @@ const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: u32 = 64u; @group(0) @binding(28) var oit_settings: types::OrderIndependentTransparencySettings; #endif // OIT_ENABLED +#ifdef ATMOSPHERE +@group(0) @binding(29) var atmosphere_transmittance_texture: texture_2d; +@group(0) @binding(30) var atmosphere_transmittance_sampler: sampler; +@group(0) @binding(31) var atmosphere_data: atmosphere::AtmosphereData; +#endif // ATMOSPHERE + #ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY @group(1) @binding(0) var diffuse_environment_maps: binding_array, 8u>; @group(1) @binding(1) var specular_environment_maps: binding_array, 8u>; diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index 7496dea4fc050..c359109aad91e 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -3,6 +3,8 @@ #import bevy_pbr::{ mesh_view_types::POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE, mesh_view_bindings as view_bindings, + atmosphere::functions::calculate_visible_sun_ratio, + atmosphere::bruneton_functions::transmittance_lut_r_mu_to_uv, } #import bevy_render::maths::PI @@ -852,5 +854,34 @@ fn directional_light( } #endif - return color * (*light).color.rgb * texture_sample; +color *= (*light).color.rgb * texture_sample; + +#ifdef ATMOSPHERE + let P = (*input).P; + let atmosphere = view_bindings::atmosphere_data.atmosphere; + let O = vec3(0.0, atmosphere.bottom_radius, 0.0); + let P_scaled = P * vec3(view_bindings::atmosphere_data.settings.scene_units_to_m); + let P_as = P_scaled + O; + let r = length(P_as); + let local_up = normalize(P_as); + let mu_light = dot(L, local_up); + + // Sample atmosphere + let transmittance = sample_transmittance_lut(r, mu_light); + let sun_visibility = calculate_visible_sun_ratio(atmosphere, r, mu_light, (*light).sun_disk_angular_size); + + // Apply atmospheric effects + color *= transmittance * sun_visibility; +#endif + + return color; +} + +#ifdef ATMOSPHERE +fn sample_transmittance_lut(r: f32, mu: f32) -> vec3 { + let uv = transmittance_lut_r_mu_to_uv(view_bindings::atmosphere_data.atmosphere, r, mu); + return textureSampleLevel( + view_bindings::atmosphere_transmittance_texture, + view_bindings::atmosphere_transmittance_sampler, uv, 0.0).rgb; } +#endif // ATMOSPHERE From fd9f145ee4958160d54c6ea56788ffea4f0add7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81te=CC=81=20Homolya?= Date: Sun, 31 Aug 2025 11:28:12 -0700 Subject: [PATCH 02/19] Cleanup from world add volumetric fog support --- crates/bevy_pbr/src/atmosphere/mod.rs | 17 ++++++----- crates/bevy_pbr/src/atmosphere/resources.rs | 30 ++++++-------------- crates/bevy_pbr/src/render/mesh.rs | 4 +-- crates/bevy_pbr/src/volumetric_fog/render.rs | 5 +++- 4 files changed, 24 insertions(+), 32 deletions(-) diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index 2ae7db3336803..268f4687e13e1 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -76,13 +76,12 @@ use resources::{ }; use tracing::warn; -use crate::resources::{prepare_atmosphere_buffer, AtmosphereBuffer}; - use self::{ node::{AtmosphereLutsNode, AtmosphereNode, RenderSkyNode}, resources::{ - prepare_atmosphere_bind_groups, prepare_atmosphere_textures, AtmosphereBindGroupLayouts, - AtmosphereLutPipelines, AtmosphereSamplers, + init_atmosphere_buffer, prepare_atmosphere_bind_groups, prepare_atmosphere_textures, + write_atmosphere_buffer, AtmosphereBindGroupLayouts, AtmosphereLutPipelines, + AtmosphereSamplers, }, }; @@ -144,11 +143,15 @@ impl Plugin for AtmospherePlugin { .init_resource::() .init_resource::() .init_resource::() - .init_resource::() .init_resource::>() .add_systems( RenderStartup, - (init_atmosphere_probe_layout, init_atmosphere_probe_pipeline).chain(), + ( + init_atmosphere_probe_layout, + init_atmosphere_probe_pipeline, + init_atmosphere_buffer, + ) + .chain(), ) .add_systems( Render, @@ -162,7 +165,7 @@ impl Plugin for AtmospherePlugin { prepare_atmosphere_probe_bind_groups.in_set(RenderSystems::PrepareBindGroups), prepare_atmosphere_transforms.in_set(RenderSystems::PrepareResources), prepare_atmosphere_bind_groups.in_set(RenderSystems::PrepareBindGroups), - prepare_atmosphere_buffer + write_atmosphere_buffer .in_set(RenderSystems::PrepareResources) .before(RenderSystems::PrepareBindGroups), ), diff --git a/crates/bevy_pbr/src/atmosphere/resources.rs b/crates/bevy_pbr/src/atmosphere/resources.rs index 70fd7458bc463..bddfa588220a5 100644 --- a/crates/bevy_pbr/src/atmosphere/resources.rs +++ b/crates/bevy_pbr/src/atmosphere/resources.rs @@ -706,27 +706,13 @@ pub(crate) struct AtmosphereData { pub settings: AtmosphereSettings, } -impl FromWorld for AtmosphereBuffer { - fn from_world(world: &mut World) -> Self { - let data = world - .query_filtered::<(&Atmosphere, &AtmosphereSettings), With>() - .iter(world) - .next() - .map_or_else( - || AtmosphereData { - atmosphere: Atmosphere::default(), - settings: AtmosphereSettings::default(), - }, - |(atmosphere, settings)| AtmosphereData { - atmosphere: atmosphere.clone(), - settings: settings.clone(), - }, - ); - - Self { - buffer: StorageBuffer::from(data), - } - } +pub fn init_atmosphere_buffer(mut commands: Commands) { + commands.insert_resource(AtmosphereBuffer { + buffer: StorageBuffer::from(AtmosphereData { + atmosphere: Atmosphere::default(), + settings: AtmosphereSettings::default(), + }), + }); } #[derive(Resource)] @@ -734,7 +720,7 @@ pub struct AtmosphereBuffer { pub(crate) buffer: StorageBuffer, } -pub(crate) fn prepare_atmosphere_buffer( +pub(crate) fn write_atmosphere_buffer( device: Res, queue: Res, atmosphere_entity: Query<(&Atmosphere, &AtmosphereSettings), With>, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 5b128e6714b88..a7d8dd4766835 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1,6 +1,6 @@ use crate::{ material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot}, - resources::prepare_atmosphere_buffer, + resources::write_atmosphere_buffer, }; use bevy_asset::{embedded_asset, load_embedded_asset, AssetId}; use bevy_camera::{ @@ -198,7 +198,7 @@ impl Plugin for MeshRenderPlugin { prepare_mesh_view_bind_groups .in_set(RenderSystems::PrepareBindGroups) .after(prepare_oit_buffers) - .after(prepare_atmosphere_buffer), + .after(write_atmosphere_buffer), no_gpu_preprocessing::clear_batched_cpu_instance_buffers:: .in_set(RenderSystems::Cleanup) .after(RenderSystems::Render), diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index 0c558fe2e804d..2b8cdde16b24a 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -50,7 +50,7 @@ use bevy_utils::prelude::default; use bitflags::bitflags; use crate::{ - MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, + Atmosphere, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset, }; @@ -626,6 +626,7 @@ pub fn prepare_volumetric_fog_pipelines( Has, Has, Has, + Has, ), With, >, @@ -644,6 +645,7 @@ pub fn prepare_volumetric_fog_pipelines( depth_prepass, motion_vector_prepass, deferred_prepass, + atmosphere, ) in view_targets.iter() { // Create a mesh pipeline view layout key corresponding to the view. @@ -658,6 +660,7 @@ pub fn prepare_volumetric_fog_pipelines( MeshPipelineViewLayoutKey::DEFERRED_PREPASS, deferred_prepass, ); + mesh_pipeline_view_key.set(MeshPipelineViewLayoutKey::ATMOSPHERE, atmosphere); let mut textureless_flags = VolumetricFogPipelineKeyFlags::empty(); textureless_flags.set(VolumetricFogPipelineKeyFlags::HDR, view.hdr); From 14c2760ef65b3585ac5e3c653e3bbd93d8eb367e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81te=CC=81=20Homolya?= Date: Sun, 31 Aug 2025 13:11:37 -0700 Subject: [PATCH 03/19] Update example and test ssr --- .../bevy_pbr/src/render/mesh_view_bindings.rs | 12 ++- crates/bevy_pbr/src/ssr/mod.rs | 24 +++++- examples/3d/atmosphere.rs | 75 ++++++++++++------- 3 files changed, 80 insertions(+), 31 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index da440c99a17c7..69e5f254dfe7a 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -56,7 +56,10 @@ use crate::{ use bevy_render::render_resource::binding_types::texture_cube; #[cfg(debug_assertions)] -use {crate::MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES, bevy_utils::once, tracing::warn}; +use { + crate::MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES, bevy_utils::once, tracing::info, + tracing::warn, +}; #[derive(Clone)] pub struct MeshPipelineViewLayout { @@ -659,6 +662,13 @@ pub fn prepare_mesh_view_bind_groups( layout_key |= MeshPipelineViewLayoutKey::ATMOSPHERE; } + #[cfg(debug_assertions)] + info!( + target: "bevy_pbr::render::mesh_view_bindings", + "mesh_view_layout label = {}", + layout_key.label() + ); + let layout = mesh_pipeline.get_view_layout(layout_key); let mut entries = DynamicBindGroupEntries::new_with_indices(( diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index f39cf417c2492..eb5eec0cc4ced 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -47,9 +47,10 @@ use bevy_utils::{once, prelude::default}; use tracing::info; use crate::{ - binding_arrays_are_usable, graph::NodePbr, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, - MeshViewBindGroup, RenderViewLightProbes, ViewEnvironmentMapUniformOffset, - ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset, + binding_arrays_are_usable, graph::NodePbr, Atmosphere, MeshPipelineViewLayoutKey, + MeshPipelineViewLayouts, MeshViewBindGroup, RenderViewLightProbes, + ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset, + ViewLightsUniformOffset, }; /// Enables screen-space reflections for a camera. @@ -177,6 +178,7 @@ pub struct ScreenSpaceReflectionsPipelineKey { mesh_pipeline_view_key: MeshPipelineViewLayoutKey, is_hdr: bool, has_environment_maps: bool, + has_atmosphere: bool, } impl Plugin for ScreenSpaceReflectionsPlugin { @@ -420,11 +422,13 @@ pub fn prepare_ssr_pipelines( Has>, Has, Has, + Has, ), ( With, With, With, + With, ), >, ) { @@ -434,6 +438,7 @@ pub fn prepare_ssr_pipelines( has_environment_maps, has_normal_prepass, has_motion_vector_prepass, + has_atmosphere, ) in &views { // SSR is only supported in the deferred pipeline, which has no MSAA @@ -449,6 +454,7 @@ pub fn prepare_ssr_pipelines( MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS, has_motion_vector_prepass, ); + mesh_pipeline_view_key.set(MeshPipelineViewLayoutKey::ATMOSPHERE, has_atmosphere); // Build the pipeline. let pipeline_id = pipelines.specialize( @@ -458,9 +464,17 @@ pub fn prepare_ssr_pipelines( mesh_pipeline_view_key, is_hdr: extracted_view.hdr, has_environment_maps, + has_atmosphere, }, ); + #[cfg(debug_assertions)] + info!( + target: "bevy_pbr::ssr", + "ssr mesh_view_layout label = {}", + mesh_pipeline_view_key.label() + ); + // Note which pipeline ID was used. commands .entity(entity) @@ -541,6 +555,10 @@ impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline { shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into()); } + if key.has_atmosphere { + shader_defs.push("ATMOSPHERE".into()); + } + RenderPipelineDescriptor { label: Some("SSR pipeline".into()), layout, diff --git a/examples/3d/atmosphere.rs b/examples/3d/atmosphere.rs index ff70d34fe6fe6..c7cf624f3c466 100644 --- a/examples/3d/atmosphere.rs +++ b/examples/3d/atmosphere.rs @@ -3,9 +3,13 @@ use std::f32::consts::PI; use bevy::{ + anti_aliasing::fxaa::Fxaa, camera::Exposure, core_pipeline::tonemapping::Tonemapping, - light::{light_consts::lux, AtmosphereEnvironmentMapLight, CascadeShadowConfigBuilder}, + light::{ + light_consts::lux, AtmosphereEnvironmentMapLight, CascadeShadowConfigBuilder, FogVolume, + VolumetricFog, VolumetricLight, + }, pbr::{Atmosphere, AtmosphereSettings}, post_process::bloom::Bloom, prelude::*, @@ -14,38 +18,48 @@ use bevy::{ fn main() { App::new() .add_plugins(DefaultPlugins) + .insert_resource(DefaultOpaqueRendererMethod::deferred()) .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(), - 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, - // Enables the atmosphere to drive reflections and ambient lighting (IBL) for this view - AtmosphereEnvironmentMapLight::default(), - )); + commands + .spawn(( + Camera3d::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 { 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(), + // Volumetric fog + VolumetricFog { + ambient_intensity: 0.0, + ..default() + }, + )) + .insert(Msaa::Off) + .insert(Fxaa::default()) + .insert(ScreenSpaceReflections::default()); } #[derive(Component)] @@ -65,6 +79,12 @@ fn setup_terrain_scene( } .build(); + // Fog volume + commands.spawn(( + FogVolume::default(), + Transform::from_scale(Vec3::splat(35.0)), + )); + // Sun commands.spawn(( DirectionalLight { @@ -77,6 +97,7 @@ fn setup_terrain_scene( illuminance: lux::RAW_SUNLIGHT, ..default() }, + VolumetricLight, Transform::from_xyz(1.0, -0.4, 0.0).looking_at(Vec3::ZERO, Vec3::Y), cascade_shadow_config, )); From 26f70c5046e46c1b14b065015d0cc02b51d3eb46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81te=CC=81=20Homolya?= Date: Sun, 31 Aug 2025 22:09:39 -0700 Subject: [PATCH 04/19] Fix deferred and ssr rendering --- crates/bevy_pbr/src/deferred/mod.rs | 10 +++++++++- crates/bevy_pbr/src/render/mesh_view_bindings.rs | 12 +----------- crates/bevy_pbr/src/ssr/mod.rs | 7 ------- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index 2eee4303795b3..ce4398ff5d959 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -4,7 +4,9 @@ use crate::{ ViewLightProbesUniformOffset, ViewScreenSpaceReflectionsUniformOffset, TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, }; -use crate::{DistanceFog, MeshPipelineKey, ViewFogUniformOffset, ViewLightsUniformOffset}; +use crate::{ + Atmosphere, DistanceFog, MeshPipelineKey, ViewFogUniformOffset, ViewLightsUniformOffset, +}; use bevy_app::prelude::*; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_core_pipeline::{ @@ -453,6 +455,7 @@ pub fn prepare_deferred_lighting_pipelines( Has>, Has>, Has, + Has, )>, ) { for ( @@ -466,6 +469,7 @@ pub fn prepare_deferred_lighting_pipelines( has_environment_maps, has_irradiance_volumes, skip_deferred_lighting, + has_atmosphere, ) in &views { // If there is no deferred prepass or we want to skip the deferred lighting pass, @@ -490,6 +494,10 @@ pub fn prepare_deferred_lighting_pipelines( view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; } + if has_atmosphere { + view_key |= MeshPipelineKey::ATMOSPHERE; + } + // Always true, since we're in the deferred lighting pipeline view_key |= MeshPipelineKey::DEFERRED_PREPASS; diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 69e5f254dfe7a..da440c99a17c7 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -56,10 +56,7 @@ use crate::{ use bevy_render::render_resource::binding_types::texture_cube; #[cfg(debug_assertions)] -use { - crate::MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES, bevy_utils::once, tracing::info, - tracing::warn, -}; +use {crate::MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES, bevy_utils::once, tracing::warn}; #[derive(Clone)] pub struct MeshPipelineViewLayout { @@ -662,13 +659,6 @@ pub fn prepare_mesh_view_bind_groups( layout_key |= MeshPipelineViewLayoutKey::ATMOSPHERE; } - #[cfg(debug_assertions)] - info!( - target: "bevy_pbr::render::mesh_view_bindings", - "mesh_view_layout label = {}", - layout_key.label() - ); - let layout = mesh_pipeline.get_view_layout(layout_key); let mut entries = DynamicBindGroupEntries::new_with_indices(( diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index eb5eec0cc4ced..86a1abb225ec9 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -468,13 +468,6 @@ pub fn prepare_ssr_pipelines( }, ); - #[cfg(debug_assertions)] - info!( - target: "bevy_pbr::ssr", - "ssr mesh_view_layout label = {}", - mesh_pipeline_view_key.label() - ); - // Note which pipeline ID was used. commands .entity(entity) From ef4b39fdab629412dedd1bda07159e635de89ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81te=CC=81=20Homolya?= Date: Mon, 1 Sep 2025 11:28:04 -0700 Subject: [PATCH 05/19] Volumetric fog atmosphere light attenuation, shader defs, example update --- crates/bevy_pbr/src/deferred/mod.rs | 3 + crates/bevy_pbr/src/volumetric_fog/render.rs | 7 + .../src/volumetric_fog/volumetric_fog.wgsl | 43 ++++- examples/3d/atmosphere.rs | 149 ++++++++++++------ 4 files changed, 148 insertions(+), 54 deletions(-) diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index ce4398ff5d959..a1c91bd1318e1 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -327,6 +327,9 @@ impl SpecializedRenderPipeline for DeferredLightingLayout { if key.contains(MeshPipelineKey::DISTANCE_FOG) { shader_defs.push("DISTANCE_FOG".into()); } + if key.contains(MeshPipelineKey::ATMOSPHERE) { + shader_defs.push("ATMOSPHERE".into()); + } // Always true, since we're in the deferred lighting pipeline shader_defs.push("DEFERRED_PREPASS".into()); diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index 2b8cdde16b24a..c776ee23369c5 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -549,6 +549,13 @@ impl SpecializedRenderPipeline for VolumetricFogPipeline { shader_defs.push("MULTISAMPLED".into()); } + if key + .mesh_pipeline_view_key + .contains(MeshPipelineViewLayoutKey::ATMOSPHERE) + { + shader_defs.push("ATMOSPHERE".into()); + } + if key .flags .contains(VolumetricFogPipelineKeyFlags::DENSITY_TEXTURE) diff --git a/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl b/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl index 43e3fc9278b81..726330ca24271 100644 --- a/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl +++ b/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl @@ -14,8 +14,15 @@ // [2]: http://www.alexandre-pestana.com/volumetric-lights/ #import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput +#import bevy_pbr::atmosphere::{ + functions::calculate_visible_sun_ratio, + bruneton_functions::transmittance_lut_r_mu_to_uv, +} #import bevy_pbr::mesh_functions::{get_world_from_local, mesh_position_local_to_clip} -#import bevy_pbr::mesh_view_bindings::{globals, lights, view, clusterable_objects} +#import bevy_pbr::mesh_view_bindings::{ + globals, lights, view, clusterable_objects, + atmosphere_data, atmosphere_transmittance_texture, atmosphere_transmittance_sampler +} #import bevy_pbr::mesh_view_types::{ DIRECTIONAL_LIGHT_FLAGS_VOLUMETRIC_BIT, POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT, @@ -220,12 +227,14 @@ fn fragment(@builtin(position) position: vec4) -> @location(0) vec4 { break; } + let L = (*light).direction_to_light.xyz; + // Offset the depth value by the bias. - let depth_offset = (*light).shadow_depth_bias * (*light).direction_to_light.xyz; + let depth_offset = (*light).shadow_depth_bias * L; // Compute phase, which determines the fraction of light that's // scattered toward the camera instead of away from it. - let neg_LdotV = dot(normalize((*light).direction_to_light.xyz), Rd_world); + let neg_LdotV = dot(normalize(L), Rd_world); let phase = henyey_greenstein(neg_LdotV); // Reset `background_alpha` for a new raymarch. @@ -291,9 +300,24 @@ fn fragment(@builtin(position) position: vec4) -> @location(0) vec4 { if (local_light_attenuation != 0.0) { let light_attenuation = exp(-density * bounding_radius * (absorption + scattering)); - let light_factors_per_step = fog_color * light_tint * light_attenuation * + var light_factors_per_step = fog_color * light_tint * light_attenuation * scattering * density * step_size_world * light_intensity * exposure; +#ifdef ATMOSPHERE + // attenuate by atmospheric scattering + let P = P_world + depth_offset; + let P_scaled = P * vec3(atmosphere_data.settings.scene_units_to_m); + let O = vec3(0.0, atmosphere_data.atmosphere.bottom_radius, 0.0); + let P_as = P_scaled + O; + let r = length(P_as); + let local_up = normalize(P_as); + let mu_light = dot(L, local_up); + + let transmittance = sample_transmittance_lut(r, mu_light); + let sun_visibility = calculate_visible_sun_ratio(atmosphere_data.atmosphere, r, mu_light, (*light).sun_disk_angular_size); + light_factors_per_step *= transmittance * sun_visibility; +#endif + // Modulate the factor we calculated above by the phase, fog color, // light color, light tint. let light_color_per_step = (*light).color.rgb * phase * light_factors_per_step; @@ -483,4 +507,13 @@ fn fetch_spot_shadow_without_normal(light_id: u32, frag_position: vec4) -> i32(light_id) + lights.spot_light_shadowmap_offset, SPOT_SHADOW_TEXEL_SIZE ); -} \ No newline at end of file +} + +#ifdef ATMOSPHERE +fn sample_transmittance_lut(r: f32, mu: f32) -> vec3 { + let uv = transmittance_lut_r_mu_to_uv(atmosphere_data.atmosphere, r, mu); + return textureSampleLevel( + atmosphere_transmittance_texture, + atmosphere_transmittance_sampler, uv, 0.0).rgb; +} +#endif // ATMOSPHERE \ No newline at end of file diff --git a/examples/3d/atmosphere.rs b/examples/3d/atmosphere.rs index c7cf624f3c466..78a4ba97de957 100644 --- a/examples/3d/atmosphere.rs +++ b/examples/3d/atmosphere.rs @@ -1,11 +1,16 @@ //! This example showcases pbr atmospheric scattering +#[path = "../helpers/camera_controller.rs"] +mod camera_controller; +use camera_controller::{CameraController, CameraControllerPlugin}; use std::f32::consts::PI; use bevy::{ anti_aliasing::fxaa::Fxaa, camera::Exposure, core_pipeline::tonemapping::Tonemapping, + diagnostic::LogDiagnosticsPlugin, + input::keyboard::KeyCode, light::{ light_consts::lux, AtmosphereEnvironmentMapLight, CascadeShadowConfigBuilder, FogVolume, VolumetricFog, VolumetricLight, @@ -15,51 +20,90 @@ use bevy::{ prelude::*, }; +#[derive(Resource, Default)] +struct GameState { + paused: bool, + atmosphere_mode: u32, +} + fn main() { App::new() - .add_plugins(DefaultPlugins) .insert_resource(DefaultOpaqueRendererMethod::deferred()) - .add_systems(Startup, (setup_camera_fog, setup_terrain_scene)) - .add_systems(Update, dynamic_scene) + .insert_resource(ClearColor(Color::BLACK)) + .insert_resource(GameState::default()) + .add_plugins(( + DefaultPlugins, + CameraControllerPlugin, + LogDiagnosticsPlugin::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!(" Spacebar - Cycle through atmosphere modes"); + println!(" 1 - Switch to default 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>, + mut atmosphere_settings: Query<&mut AtmosphereSettings>, + mut atmosphere: Query<&mut Atmosphere>, + mut game_state: ResMut, + mut camera_exposure: Query<&mut Exposure, With>, + time: Res