Skip to content

Volumetric point/spot light in-scatter vanishes in large FogVolumes #25282

Description

@blaind

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:
Image

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

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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-RenderingDrawing game state to the screenC-BugAn unexpected or incorrect behaviorS-Ready-For-ImplementationThis issue is ready for an implementation PR. Go for it!

    Type

    No type

    Projects

    Status
    Needs SME Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions