Bevy version and features
- v0.19.0 (default features, Linux x86_64, Vulkan)
What you did
Built a foggy street scene: a world-scale FogVolume with street lamps and headlights as volumetric point/spot lights. Minimal form (ambient at 0 so the point light is the only fog illumination):
commands.spawn((
Camera3d::default(),
Hdr,
VolumetricFog {
ambient_intensity: 0.0,
..default()
},
Transform::from_xyz(0.0, 4.0, 16.0).looking_at(Vec3::new(0.0, 3.0, 0.0), Vec3::Y),
));
// A world-scale fog volume...
commands.spawn((
FogVolume {
density_factor: 0.15,
scattering: 0.6,
absorption: 0.05,
..default()
},
Transform::from_xyz(0.0, 5.0, -22.0).with_scale(Vec3::new(100.0, 20.0, 60.0)),
));
// ...swap the scale for Vec3::splat(4.0) and the same light below produces
// the expected glow ball.
// Shadow-mapped volumetric point light inside the fog.
commands.spawn((
PointLight {
intensity: 300_000.0,
range: 40.0,
shadow_maps_enabled: true,
..default()
},
VolumetricLight,
Transform::from_xyz(0.0, 2.5, 0.0),
));
Full runnable reproduction:
https://github.com/blaind/bevy-volumetric-ambient-repro (MODE=pointlight)
What went wrong
- Expected: a glow ball around the light in both volume sizes.
- Actually: the 4x4x4 volume shows the expected glow; the 100x20x60 volume crushes the same light, same position, same intensity, to a barely visible pinprick.
Cause, in crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl (applied per raymarch step in the point/spot loop, and in the directional loop too):
let light_attenuation = exp(-density * bounding_radius * (absorption + scattering));
bounding_radius is the radius of the sphere bounding the entire fog volume, a constant. Every light at every sample is charged the cost of crossing the whole volume regardless of the actual light-to-sample distance, and the local sample density is extrapolated over that constant distance. For the large volume here: density 0.15 x radius ~59 x extinction 0.65 gives exp(-5.75), 0.3% of the light's scatter. The falloff is exponential in volume size, so realistic-lumen punctual lights die completely at world scale; directional god rays survive in practice only because tens of thousands of lux have headroom.
Additional information
4-unit volume: the expected glow ball:

100x20x60 volume, same light: the glow is crushed to a pinprick by the bounding-radius term:

same large volume with the fix: the light-to-sample distance replaces the bounding radius, and the glow returns

fix regression check: the small volume still renders a comparable glow ball:

Bevy version and features
What you did
Built a foggy street scene: a world-scale
FogVolumewith street lamps and headlights as volumetric point/spot lights. Minimal form (ambient at 0 so the point light is the only fog illumination):Full runnable reproduction:
https://github.com/blaind/bevy-volumetric-ambient-repro (
MODE=pointlight)What went wrong
Cause, in
crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl(applied per raymarch step in the point/spot loop, and in the directional loop too):bounding_radiusis the radius of the sphere bounding the entire fog volume, a constant. Every light at every sample is charged the cost of crossing the whole volume regardless of the actual light-to-sample distance, and the local sample density is extrapolated over that constant distance. For the large volume here: density 0.15 x radius ~59 x extinction 0.65 givesexp(-5.75), 0.3% of the light's scatter. The falloff is exponential in volume size, so realistic-lumen punctual lights die completely at world scale; directional god rays survive in practice only because tens of thousands of lux have headroom.Additional information
fix-punctual-light-fog-attenuation.patch4-unit volume: the expected glow ball:

100x20x60 volume, same light: the glow is crushed to a pinprick by the bounding-radius term:

same large volume with the fix: the light-to-sample distance replaces the bounding radius, and the glow returns

fix regression check: the small volume still renders a comparable glow ball:
