From d84604c0c7895526d17c6e17cdb4b4fc99ee81b3 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sun, 31 Aug 2025 15:40:34 -0700 Subject: [PATCH 1/4] use lut sampling in shaders --- .../src/atmosphere/aerial_view_lut.wgsl | 15 ++- crates/bevy_pbr/src/atmosphere/bindings.wgsl | 21 ++-- .../bevy_pbr/src/atmosphere/environment.wgsl | 4 +- crates/bevy_pbr/src/atmosphere/functions.wgsl | 105 +++++------------- .../src/atmosphere/multiscattering_lut.wgsl | 23 ++-- .../bevy_pbr/src/atmosphere/render_sky.wgsl | 4 +- .../bevy_pbr/src/atmosphere/sky_view_lut.wgsl | 6 +- .../src/atmosphere/transmittance_lut.wgsl | 13 ++- crates/bevy_pbr/src/atmosphere/types.wgsl | 19 +--- 9 files changed, 77 insertions(+), 133 deletions(-) diff --git a/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl b/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl index 0201165edaf45..4f9a05e56a360 100644 --- a/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl +++ b/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl @@ -4,7 +4,7 @@ types::{Atmosphere, AtmosphereSettings}, bindings::{atmosphere, settings, view, lights, aerial_view_lut_out}, functions::{ - sample_transmittance_lut, sample_atmosphere, rayleigh, henyey_greenstein, + sample_transmittance_lut, sample_density_lut, rayleigh, henyey_greenstein, sample_multiscattering_lut, AtmosphereSample, sample_local_inscattering, uv_to_ndc, max_atmosphere_distance, uv_to_ray_direction, MIDPOINT_RATIO, get_view_position @@ -13,7 +13,7 @@ } -@group(0) @binding(13) var aerial_view_lut_out: texture_storage_3d; +@group(0) @binding(16) var aerial_view_lut_out: texture_storage_3d; @compute @workgroup_size(16, 16, 1) @@ -41,15 +41,18 @@ fn main(@builtin(global_invocation_id) idx: vec3) { let local_r = length(sample_pos); let local_up = normalize(sample_pos); - let local_atmosphere = sample_atmosphere(local_r); - let sample_optical_depth = local_atmosphere.extinction * dt; + let absorption = sample_density_lut(local_r, 0.0); + let scattering = sample_density_lut(local_r, 1.0); + let extinction = absorption + scattering; + + let sample_optical_depth = extinction * dt; let sample_transmittance = exp(-sample_optical_depth); // evaluate one segment of the integral - var inscattering = sample_local_inscattering(local_atmosphere, ray_dir, sample_pos); + var inscattering = sample_local_inscattering(scattering, ray_dir.xyz, sample_pos); // Analytical integration of the single scattering term in the radiance transfer equation - let s_int = (inscattering - inscattering * sample_transmittance) / local_atmosphere.extinction; + let s_int = (inscattering - inscattering * sample_transmittance) / extinction; total_inscattering += throughput * s_int; throughput *= sample_transmittance; diff --git a/crates/bevy_pbr/src/atmosphere/bindings.wgsl b/crates/bevy_pbr/src/atmosphere/bindings.wgsl index fe4e0c9070532..6a966f13980a7 100644 --- a/crates/bevy_pbr/src/atmosphere/bindings.wgsl +++ b/crates/bevy_pbr/src/atmosphere/bindings.wgsl @@ -12,11 +12,16 @@ @group(0) @binding(2) var atmosphere_transforms: AtmosphereTransforms; @group(0) @binding(3) var view: View; @group(0) @binding(4) var lights: Lights; -@group(0) @binding(5) var transmittance_lut: texture_2d; -@group(0) @binding(6) var transmittance_lut_sampler: sampler; -@group(0) @binding(7) var multiscattering_lut: texture_2d; -@group(0) @binding(8) var multiscattering_lut_sampler: sampler; -@group(0) @binding(9) var sky_view_lut: texture_2d; -@group(0) @binding(10) var sky_view_lut_sampler: sampler; -@group(0) @binding(11) var aerial_view_lut: texture_3d; -@group(0) @binding(12) var aerial_view_lut_sampler: sampler; + +@group(0) @binding(5) var medium_density_lut: texture_2d; +@group(0) @binding(6) var medium_scattering_lut: texture_2d; +@group(0) @binding(7) var medium_sampler: sampler; + +@group(0) @binding(8) var transmittance_lut: texture_2d; +@group(0) @binding(9) var transmittance_lut_sampler: sampler; +@group(0) @binding(10) var multiscattering_lut: texture_2d; +@group(0) @binding(11) var multiscattering_lut_sampler: sampler; +@group(0) @binding(12) var sky_view_lut: texture_2d; +@group(0) @binding(13) var sky_view_lut_sampler: sampler; +@group(0) @binding(14) var aerial_view_lut: texture_3d; +@group(0) @binding(15) var aerial_view_lut_sampler: sampler; diff --git a/crates/bevy_pbr/src/atmosphere/environment.wgsl b/crates/bevy_pbr/src/atmosphere/environment.wgsl index 3e96b41120f6e..efe61ae76dc66 100644 --- a/crates/bevy_pbr/src/atmosphere/environment.wgsl +++ b/crates/bevy_pbr/src/atmosphere/environment.wgsl @@ -5,7 +5,7 @@ utils::sample_cube_dir } -@group(0) @binding(13) var output: texture_storage_2d_array; +@group(0) @binding(16) var output: texture_storage_2d_array; @compute @workgroup_size(8, 8, 1) fn main(@builtin(global_invocation_id) global_id: vec3) { @@ -36,4 +36,4 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { let color = vec4(inscattering, 1.0); textureStore(output, vec2(global_id.xy), i32(slice_index), color); -} \ No newline at end of file +} diff --git a/crates/bevy_pbr/src/atmosphere/functions.wgsl b/crates/bevy_pbr/src/atmosphere/functions.wgsl index e63f22d4d58cc..09491a3c8030c 100644 --- a/crates/bevy_pbr/src/atmosphere/functions.wgsl +++ b/crates/bevy_pbr/src/atmosphere/functions.wgsl @@ -7,7 +7,8 @@ bindings::{ atmosphere, settings, view, lights, transmittance_lut, transmittance_lut_sampler, multiscattering_lut, multiscattering_lut_sampler, sky_view_lut, sky_view_lut_sampler, - aerial_view_lut, aerial_view_lut_sampler, atmosphere_transforms + aerial_view_lut, aerial_view_lut_sampler, atmosphere_transforms, medium_density_lut, + medium_scattering_lut, medium_sampler, }, bruneton_functions::{ transmittance_lut_r_mu_to_uv, transmittance_lut_uv_to_r_mu, @@ -178,72 +179,22 @@ fn sample_aerial_view_lut(uv: vec2, t: f32) -> vec3 { return exp(sample.rgb) * fade; } -// PHASE FUNCTIONS - -// -(L . V) == (L . -V). -V here is our ray direction, which points away from the view -// instead of towards it (which would be the *view direction*, V) - -// evaluates the rayleigh phase function, which describes the likelihood -// of a rayleigh scattering event scattering light from the light direction towards the view -fn rayleigh(neg_LdotV: f32) -> f32 { - return FRAC_3_16_PI * (1 + (neg_LdotV * neg_LdotV)); -} - -// evaluates the henyey-greenstein phase function, which describes the likelihood -// of a mie scattering event scattering light from the light direction towards the view -fn henyey_greenstein(neg_LdotV: f32) -> f32 { - let g = atmosphere.mie_asymmetry; - let denom = 1.0 + g * g - 2.0 * g * neg_LdotV; - return FRAC_4_PI * (1.0 - g * g) / (denom * sqrt(denom)); -} - // ATMOSPHERE SAMPLING -struct AtmosphereSample { - /// units: m^-1 - rayleigh_scattering: vec3, - - /// units: m^-1 - mie_scattering: f32, - - /// the sum of scattering and absorption. Since the phase function doesn't - /// matter for this, we combine rayleigh and mie extinction to a single - // value. - // - /// units: m^-1 - extinction: vec3 +fn sample_density_lut(r: f32, component: f32) -> vec3 { + let normed_altitude = clamp((r - atmosphere.bottom_radius) / (atmosphere.top_radius - atmosphere.bottom_radius), 0.0, 1.0); + let uv = vec2(1.0 - normed_altitude, component); + return atmosphere.density_max * textureSampleLevel(medium_density_lut, medium_sampler, uv, 0.0).xyz; } -/// Samples atmosphere optical densities at a given radius -fn sample_atmosphere(r: f32) -> AtmosphereSample { - let altitude = clamp(r, atmosphere.bottom_radius, atmosphere.top_radius) - atmosphere.bottom_radius; - - // atmosphere values at altitude - let mie_density = exp(-atmosphere.mie_density_exp_scale * altitude); - let rayleigh_density = exp(-atmosphere.rayleigh_density_exp_scale * altitude); - var ozone_density: f32 = max(0.0, 1.0 - (abs(altitude - atmosphere.ozone_layer_altitude) / (atmosphere.ozone_layer_width * 0.5))); - - let mie_scattering = mie_density * atmosphere.mie_scattering; - let mie_absorption = mie_density * atmosphere.mie_absorption; - let mie_extinction = mie_scattering + mie_absorption; - - let rayleigh_scattering = rayleigh_density * atmosphere.rayleigh_scattering; - // no rayleigh absorption - // rayleigh extinction is the sum of scattering and absorption - - // ozone doesn't contribute to scattering - let ozone_absorption = ozone_density * atmosphere.ozone_absorption; - - var sample: AtmosphereSample; - sample.rayleigh_scattering = rayleigh_scattering; - sample.mie_scattering = mie_scattering; - sample.extinction = rayleigh_scattering + mie_extinction + ozone_absorption; - - return sample; +fn sample_scattering_lut(r: f32, neg_LdotV: f32) -> vec3 { + let normed_altitude = clamp((r - atmosphere.bottom_radius) / (atmosphere.top_radius - atmosphere.bottom_radius), 0.0, 1.0); + let uv = vec2(1.0 - normed_altitude, neg_LdotV * 0.5 + 0.5); + return atmosphere.scattering_max * textureSampleLevel(medium_scattering_lut, medium_sampler, uv, 0.0).xyz; } /// evaluates L_scat, equation 3 in the paper, which gives the total single-order scattering towards the view at a single point -fn sample_local_inscattering(local_atmosphere: AtmosphereSample, ray_dir: vec3, world_pos: vec3) -> vec3 { +fn sample_local_inscattering(local_scattering: vec3, ray_dir: vec3, world_pos: vec3) -> vec3 { let local_r = length(world_pos); let local_up = normalize(world_pos); var inscattering = vec3(0.0); @@ -256,21 +207,16 @@ fn sample_local_inscattering(local_atmosphere: AtmosphereSample, ray_dir: vec3 = vec2(1.3247179572447460259609088, 1.7548776662466927600495087); -@group(0) @binding(13) var multiscattering_lut_out: texture_storage_2d; +@group(0) @binding(16) var multiscattering_lut_out: texture_storage_2d; fn s2_sequence(n: u32) -> vec2 { return fract(0.5 + f32(n) * PHI_2); @@ -100,23 +100,24 @@ fn sample_multiscattering_dir(r: f32, ray_dir: vec3, light_dir: vec3) let local_r = get_local_r(r, mu_view, t_i); let local_up = get_local_up(r, t_i, ray_dir); - let local_atmosphere = sample_atmosphere(local_r); - let sample_optical_depth = local_atmosphere.extinction * dt; + let absorption = sample_density_lut(local_r, 0.0); + let scattering = sample_density_lut(local_r, 1.0); + let extinction = absorption + scattering; + + let sample_optical_depth = extinction * dt; let sample_transmittance = exp(-sample_optical_depth); optical_depth += sample_optical_depth; - let mu_light = dot(light_dir, local_up); - let scattering_no_phase = local_atmosphere.rayleigh_scattering + local_atmosphere.mie_scattering; - - let ms = scattering_no_phase; - let ms_int = (ms - ms * sample_transmittance) / local_atmosphere.extinction; + let ms = scattering; + let ms_int = (ms - ms * sample_transmittance) / extinction; f_ms += throughput * ms_int; + let mu_light = dot(light_dir, local_up); let transmittance_to_light = sample_transmittance_lut(local_r, mu_light); let shadow_factor = transmittance_to_light * f32(!ray_intersects_ground(local_r, mu_light)); - let s = scattering_no_phase * shadow_factor * FRAC_4_PI; - let s_int = (s - s * sample_transmittance) / local_atmosphere.extinction; + let s = scattering * shadow_factor * FRAC_4_PI; + let s_int = (s - s * sample_transmittance) / extinction; l_2 += throughput * s_int; throughput *= sample_transmittance; diff --git a/crates/bevy_pbr/src/atmosphere/render_sky.wgsl b/crates/bevy_pbr/src/atmosphere/render_sky.wgsl index 0e0d5485c963b..8312319510432 100644 --- a/crates/bevy_pbr/src/atmosphere/render_sky.wgsl +++ b/crates/bevy_pbr/src/atmosphere/render_sky.wgsl @@ -16,9 +16,9 @@ enable dual_source_blending; #import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput #ifdef MULTISAMPLED -@group(0) @binding(13) var depth_texture: texture_depth_multisampled_2d; +@group(0) @binding(16) var depth_texture: texture_depth_multisampled_2d; #else -@group(0) @binding(13) var depth_texture: texture_depth_2d; +@group(0) @binding(16) var depth_texture: texture_depth_2d; #endif struct RenderSkyOutput { diff --git a/crates/bevy_pbr/src/atmosphere/sky_view_lut.wgsl b/crates/bevy_pbr/src/atmosphere/sky_view_lut.wgsl index 5bb7b9417df98..dc8edcb13b583 100644 --- a/crates/bevy_pbr/src/atmosphere/sky_view_lut.wgsl +++ b/crates/bevy_pbr/src/atmosphere/sky_view_lut.wgsl @@ -4,11 +4,9 @@ types::{Atmosphere, AtmosphereSettings}, bindings::{atmosphere, view, settings}, functions::{ - sample_atmosphere, AtmosphereSample, - sample_local_inscattering, get_view_position, + get_view_position, raymarch_atmosphere, max_atmosphere_distance, direction_atmosphere_to_world, sky_view_lut_uv_to_zenith_azimuth, zenith_azimuth_to_ray_dir, - MIDPOINT_RATIO, raymarch_atmosphere, EPSILON }, } } @@ -19,7 +17,7 @@ } #import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput -@group(0) @binding(13) var sky_view_lut_out: texture_storage_2d; +@group(0) @binding(16) var sky_view_lut_out: texture_storage_2d; @compute @workgroup_size(16, 16, 1) diff --git a/crates/bevy_pbr/src/atmosphere/transmittance_lut.wgsl b/crates/bevy_pbr/src/atmosphere/transmittance_lut.wgsl index 233391e1c83f8..9a0cb1c5316a9 100644 --- a/crates/bevy_pbr/src/atmosphere/transmittance_lut.wgsl +++ b/crates/bevy_pbr/src/atmosphere/transmittance_lut.wgsl @@ -1,14 +1,14 @@ #import bevy_pbr::atmosphere::{ types::{Atmosphere, AtmosphereSettings}, bindings::{settings, atmosphere}, - functions::{AtmosphereSample, sample_atmosphere, get_local_r, max_atmosphere_distance, MIDPOINT_RATIO}, + functions::{AtmosphereSample, sample_density_lut, get_local_r, max_atmosphere_distance, MIDPOINT_RATIO}, bruneton_functions::{transmittance_lut_uv_to_r_mu, distance_to_bottom_atmosphere_boundary, distance_to_top_atmosphere_boundary}, } #import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput -@group(0) @binding(13) var transmittance_lut_out: texture_storage_2d; +@group(0) @binding(16) var transmittance_lut_out: texture_storage_2d; @compute @workgroup_size(16, 16, 1) @@ -38,10 +38,11 @@ fn ray_optical_depth(r: f32, mu: f32, sample_count: u32) -> vec3 { let r_i = get_local_r(r, mu, t_i); - let atmosphere_sample = sample_atmosphere(r_i); - let sample_optical_depth = atmosphere_sample.extinction * dt; - - optical_depth += sample_optical_depth; + // sampling halfway between the two rows will give the *average* + // of absorption and scattering, so need to multiply by 2 to get + // the sum. + let extinction = sample_density_lut(r_i, 0.5) * 2.0; + optical_depth += extinction * dt; } return optical_depth; diff --git a/crates/bevy_pbr/src/atmosphere/types.wgsl b/crates/bevy_pbr/src/atmosphere/types.wgsl index f9207dd7228c5..10045002f14d7 100644 --- a/crates/bevy_pbr/src/atmosphere/types.wgsl +++ b/crates/bevy_pbr/src/atmosphere/types.wgsl @@ -1,25 +1,13 @@ #define_import_path bevy_pbr::atmosphere::types struct Atmosphere { + ground_albedo: vec3, // Radius of the planet bottom_radius: f32, // units: m - + density_max: vec3, // Radius at which we consider the atmosphere to 'end' for out calculations (from center of planet) top_radius: f32, // units: m - - ground_albedo: vec3, - - rayleigh_density_exp_scale: f32, - rayleigh_scattering: vec3, - - mie_density_exp_scale: f32, - mie_scattering: f32, // units: m^-1 - mie_absorption: f32, // units: m^-1 - mie_asymmetry: f32, // the "asymmetry" value of the phase function, unitless. Domain: (-1, 1) - - ozone_layer_altitude: f32, // units: m - ozone_layer_width: f32, // units: m - ozone_absorption: vec3, // ozone absorption. units: m^-1 + scattering_max: vec3, } struct AtmosphereSettings { @@ -38,7 +26,6 @@ struct AtmosphereSettings { rendering_method: u32, } - // "Atmosphere space" is just the view position with y=0 and oriented horizontally, // so the horizon stays a horizontal line in our luts struct AtmosphereTransforms { From 5c6b9dbfcdc29a6a6cd22eac50fe4df4930f4028 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Tue, 2 Sep 2025 13:04:08 -0700 Subject: [PATCH 2/4] fixed stuff but big memory usage --- crates/bevy_pbr/src/atmosphere/functions.wgsl | 8 +- crates/bevy_pbr/src/atmosphere/mod.rs | 3 +- crates/bevy_pbr/src/atmosphere/resources.rs | 4 - crates/bevy_pbr/src/atmosphere/types.wgsl | 2 - crates/bevy_pbr/src/medium.rs | 78 +++++-------------- 5 files changed, 24 insertions(+), 71 deletions(-) diff --git a/crates/bevy_pbr/src/atmosphere/functions.wgsl b/crates/bevy_pbr/src/atmosphere/functions.wgsl index 09491a3c8030c..daf66a6f8e60e 100644 --- a/crates/bevy_pbr/src/atmosphere/functions.wgsl +++ b/crates/bevy_pbr/src/atmosphere/functions.wgsl @@ -182,15 +182,15 @@ fn sample_aerial_view_lut(uv: vec2, t: f32) -> vec3 { // ATMOSPHERE SAMPLING fn sample_density_lut(r: f32, component: f32) -> vec3 { - let normed_altitude = clamp((r - atmosphere.bottom_radius) / (atmosphere.top_radius - atmosphere.bottom_radius), 0.0, 1.0); + let normed_altitude = saturate((r - atmosphere.bottom_radius) / (atmosphere.top_radius - atmosphere.bottom_radius)); let uv = vec2(1.0 - normed_altitude, component); - return atmosphere.density_max * textureSampleLevel(medium_density_lut, medium_sampler, uv, 0.0).xyz; + return textureSampleLevel(medium_density_lut, medium_sampler, uv, 0.0).xyz; } fn sample_scattering_lut(r: f32, neg_LdotV: f32) -> vec3 { - let normed_altitude = clamp((r - atmosphere.bottom_radius) / (atmosphere.top_radius - atmosphere.bottom_radius), 0.0, 1.0); + let normed_altitude = saturate((r - atmosphere.bottom_radius) / (atmosphere.top_radius - atmosphere.bottom_radius)); let uv = vec2(1.0 - normed_altitude, neg_LdotV * 0.5 + 0.5); - return atmosphere.scattering_max * textureSampleLevel(medium_scattering_lut, medium_sampler, uv, 0.0).xyz; + return textureSampleLevel(medium_scattering_lut, medium_sampler, uv, 0.0).xyz; } /// evaluates L_scat, equation 3 in the paper, which gives the total single-order scattering towards the view at a single point diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index 7c3dfd00909b0..4f547fa89d9af 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -55,7 +55,6 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ extract_component::UniformComponentPlugin, render_resource::{DownlevelFlags, ShaderType, SpecializedRenderPipelines}, - sync_world::MainEntity, view::Hdr, RenderStartup, }; @@ -124,7 +123,7 @@ impl Plugin for AtmospherePlugin { .resource_mut::>() .add(ScatteringMedium::earth_atmosphere()); world.insert_resource(EarthAtmosphere(Atmosphere { - bottom_radius: 6_460_000.0, + bottom_radius: 6_360_000.0, top_radius: 6_460_000.0, ground_albedo: Vec3::splat(0.3), medium: earth_atmosphere, diff --git a/crates/bevy_pbr/src/atmosphere/resources.rs b/crates/bevy_pbr/src/atmosphere/resources.rs index a27ed7044d07f..b4fc2525c4d05 100644 --- a/crates/bevy_pbr/src/atmosphere/resources.rs +++ b/crates/bevy_pbr/src/atmosphere/resources.rs @@ -526,9 +526,7 @@ struct ScatteringMediumMissingError(AssetId); pub struct GpuAtmosphere { pub ground_albedo: Vec3, pub bottom_radius: f32, - pub density_max: Vec3, pub top_radius: f32, - pub scattering_max: Vec3, } pub fn prepare_atmosphere_uniforms( @@ -543,9 +541,7 @@ pub fn prepare_atmosphere_uniforms( commands.entity(entity).insert(GpuAtmosphere { ground_albedo: atmosphere.ground_albedo, bottom_radius: atmosphere.bottom_radius, - density_max: gpu_medium.density_max, top_radius: atmosphere.top_radius, - scattering_max: gpu_medium.scattering_max, }); } Ok(()) diff --git a/crates/bevy_pbr/src/atmosphere/types.wgsl b/crates/bevy_pbr/src/atmosphere/types.wgsl index 10045002f14d7..a96efe961ecb3 100644 --- a/crates/bevy_pbr/src/atmosphere/types.wgsl +++ b/crates/bevy_pbr/src/atmosphere/types.wgsl @@ -4,10 +4,8 @@ struct Atmosphere { ground_albedo: vec3, // Radius of the planet bottom_radius: f32, // units: m - density_max: vec3, // Radius at which we consider the atmosphere to 'end' for out calculations (from center of planet) top_radius: f32, // units: m - scattering_max: vec3, } struct AtmosphereSettings { diff --git a/crates/bevy_pbr/src/medium.rs b/crates/bevy_pbr/src/medium.rs index 3982050e9ce0b..0d4c51b422091 100644 --- a/crates/bevy_pbr/src/medium.rs +++ b/crates/bevy_pbr/src/medium.rs @@ -10,7 +10,7 @@ use bevy_ecs::{ }; use bevy_math::{ curve::{EaseFunction, EasingCurve, Interval}, - ops, Curve, FloatPow, U8Vec3, Vec3, VectorSpace, + ops, Curve, FloatPow, U8Vec3, Vec3, Vec4, VectorSpace, }; use bevy_reflect::TypePath; use bevy_render::{ @@ -143,18 +143,14 @@ impl ScatteringMedium { ScatteringTerm { absorption: Vec3::ZERO, scattering: Vec3::new(5.802e-6, 13.558e-6, 33.100e-6), - // falloff: Falloff::Exponential { scale: 12.5 }, //TODO: check if matches reference - falloff: Falloff::Linear, + falloff: Falloff::Exponential { scale: 12.5 }, //TODO: check if matches reference phase: PhaseFunction::Rayleigh, - // phase: PhaseFunction::Isotropic, }, ScatteringTerm { absorption: Vec3::splat(3.996e-6), scattering: Vec3::splat(0.444e-6), - // falloff: Falloff::Exponential { scale: 83.5 }, //TODO: check if matches reference - falloff: Falloff::Linear, + falloff: Falloff::Exponential { scale: 83.5 }, //TODO: check if matches reference phase: PhaseFunction::Mie { bias: 0.8 }, - // phase: PhaseFunction::Isotropic, }, ScatteringTerm { absorption: Vec3::new(0.650e-6, 1.881e-6, 0.085e-6), @@ -264,10 +260,8 @@ pub struct GpuScatteringMedium { pub terms: SmallVec<[ScatteringTerm; 1]>, pub falloff_resolution: u32, pub phase_resolution: u32, - pub density_max: Vec3, pub density_lut: Texture, pub density_lut_view: TextureView, - pub scattering_max: Vec3, pub scattering_lut: Texture, pub scattering_lut_view: TextureView, } @@ -283,55 +277,33 @@ impl RenderAsset for GpuScatteringMedium { (render_device, render_queue): &mut SystemParamItem, _previous_asset: Option<&Self>, ) -> Result> { - let mut density: Vec = Vec::with_capacity(source_asset.falloff_resolution as usize); - let mut density_lut_data: Vec = - Vec::with_capacity(2 * source_asset.falloff_resolution as usize * size_of::()); - - let mut density_max = Vec3::ZERO; + let mut density: Vec = + Vec::with_capacity(2 * source_asset.falloff_resolution as usize); density.extend((0..source_asset.falloff_resolution).map(|i| { let falloff = (i as f32 + 0.5) / source_asset.falloff_resolution as f32; - let absorption = source_asset + source_asset .terms .iter() - .map(|term| term.absorption * term.falloff.sample(falloff)) - .sum(); - - density_max = density_max.max(absorption); - absorption + .map(|term| term.absorption.extend(0.0) * term.falloff.sample(falloff)) + .sum::() })); density.extend((0..source_asset.falloff_resolution).map(|i| { let falloff = (i as f32 + 0.5) / source_asset.falloff_resolution as f32; - let scattering = source_asset + source_asset .terms .iter() - .map(|term| term.scattering * term.falloff.sample(falloff)) - .sum(); - - density_max = density_max.max(scattering); - scattering - })); - - density_lut_data.extend(density.iter().flat_map(|absorption| { - (*absorption * 255.0 / density_max) - .as_u8vec3() - .extend(0) - .to_array() + .map(|term| term.scattering.extend(0.0) * term.falloff.sample(falloff)) + .sum::() })); - let mut scattering: Vec = Vec::with_capacity( + let mut scattering: Vec = Vec::with_capacity( source_asset.falloff_resolution as usize * source_asset.phase_resolution as usize, ); - let mut scattering_lut_data: Vec = Vec::with_capacity( - source_asset.falloff_resolution as usize - * source_asset.phase_resolution as usize - * size_of::(), - ); - let mut scattering_max = Vec3::ZERO; scattering.extend( (0..source_asset.falloff_resolution * source_asset.phase_resolution).map(|raw_i| { let i = raw_i % source_asset.phase_resolution; @@ -340,28 +312,18 @@ impl RenderAsset for GpuScatteringMedium { let phase = (j as f32 + 0.5) / source_asset.phase_resolution as f32; let neg_l_dot_v = phase * 2.0 - 1.0; - let scattering = source_asset + source_asset .terms .iter() .map(|term| { - term.scattering + term.scattering.extend(0.0) * term.falloff.sample(falloff) * term.phase.sample(neg_l_dot_v) }) - .sum(); - - scattering_max = scattering_max.max(scattering); - scattering + .sum::() }), ); - scattering_lut_data.extend(scattering.iter().flat_map(|scattering| { - (*scattering * 255.0 / scattering_max) - .as_u8vec3() - .extend(0) - .to_array() - })); - let density_lut = render_device.create_texture_with_data( render_queue, &TextureDescriptor { @@ -379,12 +341,12 @@ impl RenderAsset for GpuScatteringMedium { mip_level_count: 1, sample_count: 1, dimension: TextureDimension::D2, - format: TextureFormat::Rgba8Unorm, + format: TextureFormat::Rgba32Float, usage: TextureUsages::TEXTURE_BINDING, view_formats: &[], }, TextureDataOrder::LayerMajor, - density_lut_data.as_slice(), + bytemuck::cast_slice(density.as_slice()), ); let density_lut_view = density_lut.create_view(&TextureViewDescriptor { @@ -414,12 +376,12 @@ impl RenderAsset for GpuScatteringMedium { mip_level_count: 1, sample_count: 1, dimension: TextureDimension::D2, - format: TextureFormat::Rgba8Unorm, + format: TextureFormat::Rgba32Float, usage: TextureUsages::TEXTURE_BINDING, view_formats: &[], }, TextureDataOrder::LayerMajor, - scattering_lut_data.as_slice(), + bytemuck::cast_slice(scattering.as_slice()), ); let scattering_lut_view = scattering_lut.create_view(&TextureViewDescriptor { @@ -436,10 +398,8 @@ impl RenderAsset for GpuScatteringMedium { terms: source_asset.terms, falloff_resolution: source_asset.falloff_resolution, phase_resolution: source_asset.phase_resolution, - density_max, density_lut, density_lut_view, - scattering_max, scattering_lut, scattering_lut_view, }) From 14518e9daf04583dc3e6343f1b3c5eb6b8493b67 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Tue, 2 Sep 2025 14:33:45 -0700 Subject: [PATCH 3/4] cleanup --- crates/bevy_pbr/src/atmosphere/environment.rs | 2 -- crates/bevy_pbr/src/atmosphere/mod.rs | 6 ++---- crates/bevy_pbr/src/atmosphere/node.rs | 3 +-- crates/bevy_pbr/src/atmosphere/resources.rs | 4 ---- crates/bevy_pbr/src/medium.rs | 16 ++++++---------- examples/3d/atmosphere.rs | 2 +- 6 files changed, 10 insertions(+), 23 deletions(-) diff --git a/crates/bevy_pbr/src/atmosphere/environment.rs b/crates/bevy_pbr/src/atmosphere/environment.rs index f9b86e37e7257..f0fa7d38f30cc 100644 --- a/crates/bevy_pbr/src/atmosphere/environment.rs +++ b/crates/bevy_pbr/src/atmosphere/environment.rs @@ -29,8 +29,6 @@ use bevy_render::{ use bevy_utils::default; use tracing::warn; -use super::Atmosphere; - // Render world representation of an environment map light for the atmosphere #[derive(Component, ExtractComponent, Clone)] pub struct AtmosphereEnvironmentMap { diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index 4f547fa89d9af..0f407c28c9de7 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -42,9 +42,7 @@ use bevy_asset::{embedded_asset, AssetId, Assets, Handle}; use bevy_camera::Camera3d; use bevy_core_pipeline::core_3d::graph::Node3d; use bevy_ecs::{ - archetype::Archetype, - component::{Component, Components}, - entity::Entity, + component::Component, query::{Changed, QueryItem, With}, resource::Resource, schedule::IntoScheduleConfigs, @@ -77,7 +75,7 @@ use resources::{ prepare_atmosphere_transforms, queue_render_sky_pipelines, AtmosphereTransforms, RenderSkyBindGroupLayouts, }; -use tracing::{debug, warn}; +use tracing::warn; use crate::{ resources::{prepare_atmosphere_uniforms, GpuAtmosphere}, diff --git a/crates/bevy_pbr/src/atmosphere/node.rs b/crates/bevy_pbr/src/atmosphere/node.rs index 7dc2c8be2ed8b..41dc593613729 100644 --- a/crates/bevy_pbr/src/atmosphere/node.rs +++ b/crates/bevy_pbr/src/atmosphere/node.rs @@ -8,7 +8,6 @@ use bevy_render::{ renderer::RenderContext, view::{ViewTarget, ViewUniformOffset}, }; -use tracing::warn; use crate::{resources::GpuAtmosphere, ViewLightsUniformOffset}; @@ -17,7 +16,7 @@ use super::{ AtmosphereBindGroups, AtmosphereLutPipelines, AtmosphereTransformsOffset, RenderSkyPipelineId, }, - Atmosphere, GpuAtmosphereSettings, + GpuAtmosphereSettings, }; #[derive(PartialEq, Eq, Debug, Copy, Clone, Hash, RenderLabel)] diff --git a/crates/bevy_pbr/src/atmosphere/resources.rs b/crates/bevy_pbr/src/atmosphere/resources.rs index b4fc2525c4d05..680ae9091fdaf 100644 --- a/crates/bevy_pbr/src/atmosphere/resources.rs +++ b/crates/bevy_pbr/src/atmosphere/resources.rs @@ -532,12 +532,8 @@ pub struct GpuAtmosphere { pub fn prepare_atmosphere_uniforms( mut commands: Commands, atmospheres: Query<(Entity, &ExtractedAtmosphere)>, - gpu_media: Res>, ) -> Result<(), BevyError> { for (entity, atmosphere) in atmospheres { - let gpu_medium = gpu_media - .get(atmosphere.medium) - .ok_or(ScatteringMediumMissingError(atmosphere.medium))?; commands.entity(entity).insert(GpuAtmosphere { ground_albedo: atmosphere.ground_albedo, bottom_radius: atmosphere.bottom_radius, diff --git a/crates/bevy_pbr/src/medium.rs b/crates/bevy_pbr/src/medium.rs index 0d4c51b422091..66d94d559749a 100644 --- a/crates/bevy_pbr/src/medium.rs +++ b/crates/bevy_pbr/src/medium.rs @@ -1,17 +1,13 @@ +use alloc::{borrow::Cow, sync::Arc}; use core::f32::{self, consts::PI}; -use std::{borrow::Cow, fs::canonicalize, mem, sync::Arc}; use bevy_app::{App, Plugin}; -use bevy_asset::{Asset, AssetApp, AssetId, Assets, Handle}; -use bevy_color::{Color, LinearRgba}; +use bevy_asset::{Asset, AssetApp, AssetId}; use bevy_ecs::{ resource::Resource, system::{Commands, Res, SystemParamItem}, }; -use bevy_math::{ - curve::{EaseFunction, EasingCurve, Interval}, - ops, Curve, FloatPow, U8Vec3, Vec3, Vec4, VectorSpace, -}; +use bevy_math::{ops, Curve, FloatPow, Vec3, Vec4}; use bevy_reflect::TypePath; use bevy_render::{ render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin}, @@ -23,7 +19,7 @@ use bevy_render::{ renderer::{RenderDevice, RenderQueue}, RenderApp, RenderStartup, }; -use smallvec::{smallvec_inline, SmallVec}; +use smallvec::SmallVec; #[doc(hidden)] pub struct ScatteringMediumPlugin; @@ -143,13 +139,13 @@ impl ScatteringMedium { ScatteringTerm { absorption: Vec3::ZERO, scattering: Vec3::new(5.802e-6, 13.558e-6, 33.100e-6), - falloff: Falloff::Exponential { scale: 12.5 }, //TODO: check if matches reference + falloff: Falloff::Exponential { scale: 12.5 }, phase: PhaseFunction::Rayleigh, }, ScatteringTerm { absorption: Vec3::splat(3.996e-6), scattering: Vec3::splat(0.444e-6), - falloff: Falloff::Exponential { scale: 83.5 }, //TODO: check if matches reference + falloff: Falloff::Exponential { scale: 83.5 }, phase: PhaseFunction::Mie { bias: 0.8 }, }, ScatteringTerm { diff --git a/examples/3d/atmosphere.rs b/examples/3d/atmosphere.rs index 7371196385271..cb35e2d4dc738 100644 --- a/examples/3d/atmosphere.rs +++ b/examples/3d/atmosphere.rs @@ -6,7 +6,7 @@ use bevy::{ camera::Exposure, core_pipeline::{bloom::Bloom, tonemapping::Tonemapping}, light::{light_consts::lux, AtmosphereEnvironmentMapLight, CascadeShadowConfigBuilder}, - pbr::{Atmosphere, AtmosphereSettings, EarthAtmosphere}, + pbr::{AtmosphereSettings, EarthAtmosphere}, prelude::*, }; From b7c2103eea19e3605895d3ed05c16744cd65e0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81te=CC=81=20Homolya?= Date: Thu, 4 Sep 2025 14:20:08 -0700 Subject: [PATCH 4/4] Fix NaNs in scattering integral --- crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl | 4 ++-- crates/bevy_pbr/src/atmosphere/functions.wgsl | 3 ++- crates/bevy_pbr/src/atmosphere/multiscattering_lut.wgsl | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl b/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl index 4f9a05e56a360..cb37d774b2b27 100644 --- a/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl +++ b/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl @@ -7,7 +7,7 @@ sample_transmittance_lut, sample_density_lut, rayleigh, henyey_greenstein, sample_multiscattering_lut, AtmosphereSample, sample_local_inscattering, uv_to_ndc, max_atmosphere_distance, uv_to_ray_direction, - MIDPOINT_RATIO, get_view_position + MIDPOINT_RATIO, get_view_position, MIN_EXTINCTION }, } } @@ -52,7 +52,7 @@ fn main(@builtin(global_invocation_id) idx: vec3) { var inscattering = sample_local_inscattering(scattering, ray_dir.xyz, sample_pos); // Analytical integration of the single scattering term in the radiance transfer equation - let s_int = (inscattering - inscattering * sample_transmittance) / extinction; + let s_int = (inscattering - inscattering * sample_transmittance) / max(extinction, MIN_EXTINCTION); total_inscattering += throughput * s_int; throughput *= sample_transmittance; diff --git a/crates/bevy_pbr/src/atmosphere/functions.wgsl b/crates/bevy_pbr/src/atmosphere/functions.wgsl index daf66a6f8e60e..ebb8c4da984f7 100644 --- a/crates/bevy_pbr/src/atmosphere/functions.wgsl +++ b/crates/bevy_pbr/src/atmosphere/functions.wgsl @@ -43,6 +43,7 @@ const FRAC_3_16_PI: f32 = 0.0596831036594607509; // 3 / (16π) const FRAC_4_PI: f32 = 0.07957747154594767; // 1 / (4π) const ROOT_2: f32 = 1.41421356; // √2 const EPSILON: f32 = 1.0; // 1 meter +const MIN_EXTINCTION: vec3 = vec3(1e-12); // During raymarching, each segment is sampled at a single point. This constant determines // where in the segment that sample is taken (0.0 = start, 0.5 = middle, 1.0 = end). @@ -445,7 +446,7 @@ fn raymarch_atmosphere( sample_pos ); - let s_int = (inscattering - inscattering * sample_transmittance) / extinction; + let s_int = (inscattering - inscattering * sample_transmittance) / max(extinction, MIN_EXTINCTION); result.inscattering += result.transmittance * s_int; result.transmittance *= sample_transmittance; diff --git a/crates/bevy_pbr/src/atmosphere/multiscattering_lut.wgsl b/crates/bevy_pbr/src/atmosphere/multiscattering_lut.wgsl index 03d55c9819961..8afe0c3be366f 100644 --- a/crates/bevy_pbr/src/atmosphere/multiscattering_lut.wgsl +++ b/crates/bevy_pbr/src/atmosphere/multiscattering_lut.wgsl @@ -7,7 +7,7 @@ multiscattering_lut_uv_to_r_mu, sample_transmittance_lut, get_local_r, get_local_up, sample_density_lut, FRAC_4_PI, max_atmosphere_distance, rayleigh, henyey_greenstein, - zenith_azimuth_to_ray_dir, + zenith_azimuth_to_ray_dir, MIN_EXTINCTION }, bruneton_functions::{ distance_to_top_atmosphere_boundary, distance_to_bottom_atmosphere_boundary, ray_intersects_ground @@ -109,7 +109,7 @@ fn sample_multiscattering_dir(r: f32, ray_dir: vec3, light_dir: vec3) optical_depth += sample_optical_depth; let ms = scattering; - let ms_int = (ms - ms * sample_transmittance) / extinction; + let ms_int = (ms - ms * sample_transmittance) / max(extinction, MIN_EXTINCTION); f_ms += throughput * ms_int; let mu_light = dot(light_dir, local_up); @@ -117,7 +117,7 @@ fn sample_multiscattering_dir(r: f32, ray_dir: vec3, light_dir: vec3) let shadow_factor = transmittance_to_light * f32(!ray_intersects_ground(local_r, mu_light)); let s = scattering * shadow_factor * FRAC_4_PI; - let s_int = (s - s * sample_transmittance) / extinction; + let s_int = (s - s * sample_transmittance) / max(extinction, MIN_EXTINCTION); l_2 += throughput * s_int; throughput *= sample_transmittance;