bevyengine / bevyengine/bevy

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

Open
#25,360 0 comments 0 reactions 0 assignees View on GitHub
A-Rendering C-Bug D-Modest S-Ready-For-Implementation
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

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.

```ignore
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

https://github.com/user-attachments/assets/8aa52f8a-bfd7-4614-9b9b-1309bd0e4249

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.

```rs
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>,
mut materials: ResMut>,
) {
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

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;
}
}
```

Contributor guide

Open the contributing guide

Research direction

Start from the main() entry point in the provided minimal reproduction and run it with SpotLight shadow_maps_enabled, inner_angle, and outer_angle set to zero. Inspect the spotlight and shadow-map rendering paths, then verify that zero-angle lights no longer produce garbage artifacts and that the reported brightness behavior is understood.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
computer-graphics
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.