diff --git a/assets/models/terrain/terrain.glb b/assets/models/terrain/terrain.glb index b09e0f414832e..c040600dbeb4e 100644 Binary files a/assets/models/terrain/terrain.glb and b/assets/models/terrain/terrain.glb differ 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..268f4687e13e1 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -79,8 +79,9 @@ use tracing::warn; 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, }, }; @@ -145,7 +146,12 @@ impl Plugin for AtmospherePlugin { .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, @@ -159,6 +165,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), + write_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..95a2f7d90b12f 100644 --- a/crates/bevy_pbr/src/atmosphere/resources.rs +++ b/crates/bevy_pbr/src/atmosphere/resources.rs @@ -698,3 +698,41 @@ pub(super) fn prepare_atmosphere_bind_groups( }); } } + +#[derive(ShaderType)] +#[repr(C)] +pub(crate) struct AtmosphereData { + pub atmosphere: Atmosphere, + pub settings: GpuAtmosphereSettings, +} + +pub fn init_atmosphere_buffer(mut commands: Commands) { + commands.insert_resource(AtmosphereBuffer { + buffer: StorageBuffer::from(AtmosphereData { + atmosphere: Atmosphere::default(), + settings: GpuAtmosphereSettings::default(), + }), + }); +} + +#[derive(Resource)] +pub struct AtmosphereBuffer { + pub(crate) buffer: StorageBuffer, +} + +pub(crate) fn write_atmosphere_buffer( + device: Res, + queue: Res, + atmosphere_entity: Query<(&Atmosphere, &GpuAtmosphereSettings), 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/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index 2eee4303795b3..a1c91bd1318e1 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::{ @@ -325,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()); @@ -453,6 +458,7 @@ pub fn prepare_deferred_lighting_pipelines( Has>, Has>, Has, + Has, )>, ) { for ( @@ -466,6 +472,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 +497,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.rs b/crates/bevy_pbr/src/render/mesh.rs index 922294f0657f4..a7d8dd4766835 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::write_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(write_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..f20d4d405a3c2 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,17 @@ pub fn prepare_mesh_view_bind_groups( )); } + if has_atmosphere + && 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 diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index f39cf417c2492..86a1abb225ec9 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,6 +464,7 @@ pub fn prepare_ssr_pipelines( mesh_pipeline_view_key, is_hdr: extracted_view.hdr, has_environment_maps, + has_atmosphere, }, ); @@ -541,6 +548,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/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index 0c558fe2e804d..c776ee23369c5 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, }; @@ -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) @@ -626,6 +633,7 @@ pub fn prepare_volumetric_fog_pipelines( Has, Has, Has, + Has, ), With, >, @@ -644,6 +652,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 +667,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); 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 ff70d34fe6fe6..fe2ff76dd2df2 100644 --- a/examples/3d/atmosphere.rs +++ b/examples/3d/atmosphere.rs @@ -1,43 +1,118 @@ //! 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_alias::fxaa::Fxaa, camera::Exposure, + color::palettes::css::BLACK, core_pipeline::tonemapping::Tonemapping, - light::{light_consts::lux, AtmosphereEnvironmentMapLight, CascadeShadowConfigBuilder}, - pbr::{Atmosphere, AtmosphereSettings}, + diagnostic::LogDiagnosticsPlugin, + 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, ScreenSpaceReflections, + }, post_process::bloom::Bloom, prelude::*, + render::render_resource::{AsBindGroup, ShaderType}, + shader::ShaderRef, }; +#[derive(Resource, Default)] +struct GameState { + paused: bool, +} + fn main() { App::new() - .add_plugins(DefaultPlugins) - .add_systems(Startup, (setup_camera_fog, setup_terrain_scene)) - .add_systems(Update, dynamic_scene) + .insert_resource(DefaultOpaqueRendererMethod::deferred()) + .insert_resource(ClearColor(Color::BLACK)) + .insert_resource(GameState::default()) + .add_plugins(( + DefaultPlugins, + CameraControllerPlugin, + LogDiagnosticsPlugin::default(), + )) + .add_plugins(MaterialPlugin::>::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 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 game_state: ResMut, + mut camera_exposure: Query<&mut Exposure, With>, + time: Res