Skip to content

spotlight produces garbage artifacts when outer angle and inner angle are set to zero and shadowmaps enabled #25360

Description

@Saratii

bevy main branch and 0.19

[Optional] Relevant system information

If you cannot get Bevy to build or run on your machine, please include:

  • the Rust version you're using (you can get this by running cargo --version)
    • Bevy relies on the "latest stable release" of Rust
    • nightly should generally work, but there are sometimes regressions: please let us know!
  • the operating system or browser used, including its version
    • e.g. Windows 10, Ubuntu 18.04, iOS 14

If your bug is rendering-related, copy the adapter info that appears when you run Bevy.

2026-08-10T21:34:32.592774Z  INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "Windows 11 Pro", kernel: "26200", cpu: "AMD Ryzen 7 7700X 8-Core Processor", core_count: "8", memory: "63.2 GiB" }
2026-08-10T21:34:33.170960Z  INFO bevy_render::renderer: AdapterInfo { name: "NVIDIA GeForce RTX 5070 Ti", vendor: 4318, device: 11269, device_type: DiscreteGpu, device_pci_bus_id: "0000:01:00.0", driver: "NVIDIA", driver_info: "610.74", backend: Vulkan, subgroup_min_size: 32, subgroup_max_size: 32, transient_saves_memory: false }
2026-08-10T21:34:33.439985Z  INFO bevy_pbr::cluster: GPU clustering is supported on this device.
2026-08-10T21:34:33.440367Z  INFO bevy_render::batching::gpu_preprocessing: GPU preprocessing is fully supported on this device.`

What you did

I made a minimal reproduction to debug why the brightness was wrong. I set the inner and outer paramaters to zero. it only breaks when shadow maps are enabled on the spotlight.

What went wrong

Firstly, im using 40 million lumens testing at 50 meters. The light should not be nearly as dim as it is. Something is wrong with that.

Secondly with inner and outer angles set to zero garbage artifacts are rendered as shown below.

Additional information

Recording.2026-08-10.163714.mp4
Image

Image shows the brightness of 40 million lumens at 50 meters. I expected much brighter. It is only bright at very close so distances like 2 meters.

use bevy::camera_controller::free_camera::{FreeCamera, FreeCameraPlugin};
use bevy::prelude::*;
use std::f32::consts::FRAC_PI_2;

const LAMP_INTENSITY: f32 = 40_000_000.0;
const LAMP_RANGE: f32 = 250.0;
const LAMP_OUTER_ANGLE: f32 = 0.35;
const LAMP_INNER_ANGLE: f32 = 0.24;
const WALL_Z: f32 = -10.0;
const WALL_THICKNESS: f32 = 0.5;
const WALL_FACE_Z: f32 = WALL_Z + WALL_THICKNESS * 0.5;
const LAMP_START_DISTANCE: f32 = -WALL_FACE_Z;
const MIN_DISTANCE: f32 = 0.5;
const ANGLE_SPEED: f32 = 0.4;
const DISTANCE_SPEED: f32 = 6.0;

#[derive(Component)]
struct LightReadout;

fn main() {
    App::new()
        .add_plugins((DefaultPlugins, FreeCameraPlugin))
        .add_systems(Startup, setup)
        .add_systems(Update, adjust_light)
        .run();
}

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    commands.insert_resource(ClearColor(Color::BLACK));

    commands.spawn((
        Mesh3d(meshes.add(Cuboid::new(40.0, 40.0, WALL_THICKNESS))),
        MeshMaterial3d(materials.add(StandardMaterial {
            base_color: Color::srgb(0.8, 0.8, 0.8),
            ..default()
        })),
        Transform::from_xyz(0.0, 0.0, WALL_Z),
    ));
    commands.spawn((
        SpotLight {
            color: Color::srgb(1.0, 0.95, 0.8),
            intensity: LAMP_INTENSITY,
            range: LAMP_RANGE,
            outer_angle: LAMP_OUTER_ANGLE,
            inner_angle: LAMP_INNER_ANGLE,
            shadow_maps_enabled: true,
            ..default()
        },
        Transform::from_xyz(0.0, 0.0, WALL_FACE_Z + LAMP_START_DISTANCE)
            .looking_at(Vec3::new(0.0, 0.0, WALL_Z), Vec3::Y),
        children![(
            Mesh3d(meshes.add(Sphere::new(0.15))),
            MeshMaterial3d(materials.add(StandardMaterial {
                base_color: Color::BLACK,
                emissive: LinearRgba::rgb(10.0, 9.5, 8.0),
                ..default()
            })),
        )],
    ));
    commands.spawn((
        Camera3d::default(),
        Transform::from_xyz(6.0, 2.0, 4.0).looking_at(Vec3::new(0.0, 0.0, -10.0), Vec3::Y),
        FreeCamera {
            key_up: KeyCode::Space,
            key_down: KeyCode::ControlLeft,
            ..default()
        },
    ));
    commands.spawn((
        LightReadout,
        Text::default(),
        Node {
            position_type: PositionType::Absolute,
            top: px(12),
            left: px(12),
            ..default()
        },
    ));
}

fn adjust_light(
    time: Res<Time>,
    keyboard: Res<ButtonInput<KeyCode>>,
    light: Single<(&mut SpotLight, &mut Transform)>,
    mut readout: Single<&mut Text, With<LightReadout>>,
) {
    let (mut light, mut transform) = light.into_inner();
    let step = ANGLE_SPEED * time.delta_secs();

    let mut outer = light.outer_angle;
    if keyboard.pressed(KeyCode::ArrowUp) {
        outer += step;
    }
    if keyboard.pressed(KeyCode::ArrowDown) {
        outer -= step;
    }

    let mut inner = light.inner_angle;
    if keyboard.pressed(KeyCode::ArrowRight) {
        inner += step;
    }
    if keyboard.pressed(KeyCode::ArrowLeft) {
        inner -= step;
    }
    let outer = outer.clamp(0.0, FRAC_PI_2);
    let inner = inner.clamp(0.0, outer);
    if outer != light.outer_angle || inner != light.inner_angle {
        light.outer_angle = outer;
        light.inner_angle = inner;
    }
    let mut distance = transform.translation.z - WALL_FACE_Z;
    let distance_step = DISTANCE_SPEED * time.delta_secs();
    if keyboard.pressed(KeyCode::BracketRight) {
        distance += distance_step;
    }
    if keyboard.pressed(KeyCode::BracketLeft) {
        distance -= distance_step;
    }
    let distance = distance.clamp(MIN_DISTANCE, LAMP_RANGE);
    let z = WALL_FACE_Z + distance;
    if z != transform.translation.z {
        transform.translation.z = z;
    }
    let spot_radius = distance * outer.tan();
    let text = format!(
        "outer angle: {outer:.3} rad ({:.1} deg)  -  keys Up / Down\n\
         inner angle: {inner:.3} rad ({:.1} deg)  -  keys Left / Right\n\
         distance: {distance:.2} m  -  keys [ / ]\n\
         cone radius on wall: {spot_radius:.2} m",
        outer.to_degrees(),
        inner.to_degrees(),
    );
    if readout.0 != text {
        readout.0 = text;
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-RenderingDrawing game state to the screenC-BugAn unexpected or incorrect behaviorD-ModestA "normal" level of difficulty; suitable for simple features or challenging fixesS-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