Skip to content

Commit c617fc4

Browse files
authored
fix distinct directional lights per view (#19147)
# Objective after #15156 it seems like using distinct directional lights on different views is broken (and will probably break spotlights too). fix them ## Solution the reason is a bit hairy so with an example: - camera 0 on layer 0 - camera 1 on layer 1 - dir light 0 on layer 0 (2 cascades) - dir light 1 on layer 1 (2 cascades) in render/lights.rs: - outside of any view loop, - we count the total number of shadow casting directional light cascades (4) and assign an incrementing `depth_texture_base_index` for each (0-1 for one light, 2-3 for the other, depending on iteration order) (line 1034) - allocate a texture array for the total number of cascades plus spotlight maps (4) (line 1106) - in the view loop, for directional lights we - skip lights that don't intersect on renderlayers (line 1440) - assign an incrementing texture layer to each light/cascade starting from 0 (resets to 0 per view) (assigning 0 and 1 each time for the 2 cascades of the intersecting light) (line 1509, init at 1421) then in the rendergraph: - camera 0 renders the shadow map for light 0 to texture indices 0 and 1 - camera 0 renders using shadows from the `depth_texture_base_index` (maybe 0-1, maybe 2-3 depending on the iteration order) - camera 1 renders the shadow map for light 1 to texture indices 0 and 1 - camera 0 renders using shadows from the `depth_texture_base_index` (maybe 0-1, maybe 2-3 depending on the iteration order) issues: - one of the views uses empty shadow maps (bug) - we allocated a texture layer per cascade per light, even though not all lights are used on all views (just inefficient) - I think we're allocating texture layers even for lights with `shadows_enabled: false` (just inefficient) solution: - calculate upfront the view with the largest number of directional cascades - allocate this many layers (plus layers for spotlights) in the texture array - keep using texture layers 0..n in the per-view loop, but build GpuLights.gpu_directional_lights within the loop too so it refers to the same layers we render to nice side effects: - we can now use `max_texture_array_layers / MAX_CASCADES_PER_LIGHT` shadow-casting directional lights per view, rather than overall. - we can remove the `GpuDirectionalLight::skip` field, since the gpu lights struct is constructed per view a simpler approach would be to keep everything the same, and just increment the texture layer index in the view loop even for non-intersecting lights. this pr reduces the total shadowmap vram used as well and isn't *much* extra complexity. but if we want something less risky/intrusive for 16.1 that would be the way. ## Testing i edited the split screen example to put separate lights on layer 1 and layer 2, and put the plane and fox on both layers (using lots of unrelated code for render layer propagation from #17575). without the fix the directional shadows will only render on one of the top 2 views even though there are directional lights on both layers. ```rs //! Renders two cameras to the same window to accomplish "split screen". use std::f32::consts::PI; use bevy::{ pbr::CascadeShadowConfigBuilder, prelude::*, render::camera::Viewport, window::WindowResized, }; use bevy_render::view::RenderLayers; fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugins(HierarchyPropagatePlugin::<RenderLayers>::default()) .add_systems(Startup, setup) .add_systems(Update, (set_camera_viewports, button_system)) .run(); } /// set up a simple 3D scene fn setup( mut commands: Commands, asset_server: Res<AssetServer>, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<StandardMaterial>>, ) { let all_layers = RenderLayers::layer(1).with(2).with(3).with(4); // plane commands.spawn(( Mesh3d(meshes.add(Plane3d::default().mesh().size(100.0, 100.0))), MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))), all_layers.clone() )); commands.spawn(( SceneRoot( asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")), ), Propagate(all_layers.clone()), )); // Light commands.spawn(( Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)), DirectionalLight { shadows_enabled: true, ..default() }, CascadeShadowConfigBuilder { num_cascades: if cfg!(all( feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu") )) { // Limited to 1 cascade in WebGL 1 } else { 2 }, first_cascade_far_bound: 200.0, maximum_distance: 280.0, ..default() } .build(), RenderLayers::layer(1), )); commands.spawn(( Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)), DirectionalLight { shadows_enabled: true, ..default() }, CascadeShadowConfigBuilder { num_cascades: if cfg!(all( feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu") )) { // Limited to 1 cascade in WebGL 1 } else { 2 }, first_cascade_far_bound: 200.0, maximum_distance: 280.0, ..default() } .build(), RenderLayers::layer(2), )); // Cameras and their dedicated UI for (index, (camera_name, camera_pos)) in [ ("Player 1", Vec3::new(0.0, 200.0, -150.0)), ("Player 2", Vec3::new(150.0, 150., 50.0)), ("Player 3", Vec3::new(100.0, 150., -150.0)), ("Player 4", Vec3::new(-100.0, 80., 150.0)), ] .iter() .enumerate() { let camera = commands .spawn(( Camera3d::default(), Transform::from_translation(*camera_pos).looking_at(Vec3::ZERO, Vec3::Y), Camera { // Renders cameras with different priorities to prevent ambiguities order: index as isize, ..default() }, CameraPosition { pos: UVec2::new((index % 2) as u32, (index / 2) as u32), }, RenderLayers::layer(index+1) )) .id(); // Set up UI commands .spawn(( UiTargetCamera(camera), Node { width: Val::Percent(100.), height: Val::Percent(100.), ..default() }, )) .with_children(|parent| { parent.spawn(( Text::new(*camera_name), Node { position_type: PositionType::Absolute, top: Val::Px(12.), left: Val::Px(12.), ..default() }, )); buttons_panel(parent); }); } fn buttons_panel(parent: &mut ChildSpawnerCommands) { parent .spawn(Node { position_type: PositionType::Absolute, width: Val::Percent(100.), height: Val::Percent(100.), display: Display::Flex, flex_direction: FlexDirection::Row, justify_content: JustifyContent::SpaceBetween, align_items: AlignItems::Center, padding: UiRect::all(Val::Px(20.)), ..default() }) .with_children(|parent| { rotate_button(parent, "<", Direction::Left); rotate_button(parent, ">", Direction::Right); }); } fn rotate_button(parent: &mut ChildSpawnerCommands, caption: &str, direction: Direction) { parent .spawn(( RotateCamera(direction), Button, Node { width: Val::Px(40.), height: Val::Px(40.), border: UiRect::all(Val::Px(2.)), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..default() }, BorderColor(Color::WHITE), BackgroundColor(Color::srgb(0.25, 0.25, 0.25)), )) .with_children(|parent| { parent.spawn(Text::new(caption)); }); } } #[derive(Component)] struct CameraPosition { pos: UVec2, } #[derive(Component)] struct RotateCamera(Direction); enum Direction { Left, Right, } fn set_camera_viewports( windows: Query<&Window>, mut resize_events: EventReader<WindowResized>, mut query: Query<(&CameraPosition, &mut Camera)>, ) { // We need to dynamically resize the camera's viewports whenever the window size changes // so then each camera always takes up half the screen. // A resize_event is sent when the window is first created, allowing us to reuse this system for initial setup. for resize_event in resize_events.read() { let window = windows.get(resize_event.window).unwrap(); let size = window.physical_size() / 2; for (camera_position, mut camera) in &mut query { camera.viewport = Some(Viewport { physical_position: camera_position.pos * size, physical_size: size, ..default() }); } } } fn button_system( interaction_query: Query< (&Interaction, &ComputedNodeTarget, &RotateCamera), (Changed<Interaction>, With<Button>), >, mut camera_query: Query<&mut Transform, With<Camera>>, ) { for (interaction, computed_target, RotateCamera(direction)) in &interaction_query { if let Interaction::Pressed = *interaction { // Since TargetCamera propagates to the children, we can use it to find // which side of the screen the button is on. if let Some(mut camera_transform) = computed_target .camera() .and_then(|camera| camera_query.get_mut(camera).ok()) { let angle = match direction { Direction::Left => -0.1, Direction::Right => 0.1, }; camera_transform.rotate_around(Vec3::ZERO, Quat::from_axis_angle(Vec3::Y, angle)); } } } } use std::marker::PhantomData; use bevy::{ app::{App, Plugin, Update}, ecs::query::QueryFilter, prelude::{ Changed, Children, Commands, Component, Entity, Local, Query, RemovedComponents, SystemSet, With, Without, }, }; /// Causes the inner component to be added to this entity and all children. /// A child with a Propagate<C> component of it's own will override propagation from /// that point in the tree #[derive(Component, Clone, PartialEq)] pub struct Propagate<C: Component + Clone + PartialEq>(pub C); /// Internal struct for managing propagation #[derive(Component, Clone, PartialEq)] pub struct Inherited<C: Component + Clone + PartialEq>(pub C); /// Stops the output component being added to this entity. /// Children will still inherit the component from this entity or its parents #[derive(Component, Default)] pub struct PropagateOver<C: Component + Clone + PartialEq>(PhantomData<fn() -> C>); /// Stops the propagation at this entity. Children will not inherit the component. #[derive(Component, Default)] pub struct PropagateStop<C: Component + Clone + PartialEq>(PhantomData<fn() -> C>); pub struct HierarchyPropagatePlugin<C: Component + Clone + PartialEq, F: QueryFilter = ()> { _p: PhantomData<fn() -> (C, F)>, } impl<C: Component + Clone + PartialEq, F: QueryFilter> Default for HierarchyPropagatePlugin<C, F> { fn default() -> Self { Self { _p: Default::default(), } } } #[derive(SystemSet, Clone, PartialEq, PartialOrd, Ord)] pub struct PropagateSet<C: Component + Clone + PartialEq> { _p: PhantomData<fn() -> C>, } impl<C: Component + Clone + PartialEq> std::fmt::Debug for PropagateSet<C> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PropagateSet") .field("_p", &self._p) .finish() } } impl<C: Component + Clone + PartialEq> Eq for PropagateSet<C> {} impl<C: Component + Clone + PartialEq> std::hash::Hash for PropagateSet<C> { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self._p.hash(state); } } impl<C: Component + Clone + PartialEq> Default for PropagateSet<C> { fn default() -> Self { Self { _p: Default::default(), } } } impl<C: Component + Clone + PartialEq, F: QueryFilter + 'static> Plugin for HierarchyPropagatePlugin<C, F> { fn build(&self, app: &mut App) { app.add_systems( Update, ( update_source::<C, F>, update_stopped::<C, F>, update_reparented::<C, F>, propagate_inherited::<C, F>, propagate_output::<C, F>, ) .chain() .in_set(PropagateSet::<C>::default()), ); } } pub fn update_source<C: Component + Clone + PartialEq, F: QueryFilter>( mut commands: Commands, changed: Query<(Entity, &Propagate<C>), (Changed<Propagate<C>>, Without<PropagateStop<C>>)>, mut removed: RemovedComponents<Propagate<C>>, ) { for (entity, source) in &changed { commands .entity(entity) .try_insert(Inherited(source.0.clone())); } for removed in removed.read() { if let Ok(mut commands) = commands.get_entity(removed) { commands.remove::<(Inherited<C>, C)>(); } } } pub fn update_stopped<C: Component + Clone + PartialEq, F: QueryFilter>( mut commands: Commands, q: Query<Entity, (With<Inherited<C>>, F, With<PropagateStop<C>>)>, ) { for entity in q.iter() { let mut cmds = commands.entity(entity); cmds.remove::<Inherited<C>>(); } } pub fn update_reparented<C: Component + Clone + PartialEq, F: QueryFilter>( mut commands: Commands, moved: Query< (Entity, &ChildOf, Option<&Inherited<C>>), ( Changed<ChildOf>, Without<Propagate<C>>, Without<PropagateStop<C>>, F, ), >, parents: Query<&Inherited<C>>, ) { for (entity, parent, maybe_inherited) in &moved { if let Ok(inherited) = parents.get(parent.parent()) { commands.entity(entity).try_insert(inherited.clone()); } else if maybe_inherited.is_some() { commands.entity(entity).remove::<(Inherited<C>, C)>(); } } } pub fn propagate_inherited<C: Component + Clone + PartialEq, F: QueryFilter>( mut commands: Commands, changed: Query< (&Inherited<C>, &Children), (Changed<Inherited<C>>, Without<PropagateStop<C>>, F), >, recurse: Query< (Option<&Children>, Option<&Inherited<C>>), (Without<Propagate<C>>, Without<PropagateStop<C>>, F), >, mut to_process: Local<Vec<(Entity, Option<Inherited<C>>)>>, mut removed: RemovedComponents<Inherited<C>>, ) { // gather changed for (inherited, children) in &changed { to_process.extend( children .iter() .map(|child| (child, Some(inherited.clone()))), ); } // and removed for entity in removed.read() { if let Ok((Some(children), _)) = recurse.get(entity) { to_process.extend(children.iter().map(|child| (child, None))) } } // propagate while let Some((entity, maybe_inherited)) = (*to_process).pop() { let Ok((maybe_children, maybe_current)) = recurse.get(entity) else { continue; }; if maybe_current == maybe_inherited.as_ref() { continue; } if let Some(children) = maybe_children { to_process.extend( children .iter() .map(|child| (child, maybe_inherited.clone())), ); } if let Some(inherited) = maybe_inherited { commands.entity(entity).try_insert(inherited.clone()); } else { commands.entity(entity).remove::<(Inherited<C>, C)>(); } } } pub fn propagate_output<C: Component + Clone + PartialEq, F: QueryFilter>( mut commands: Commands, changed: Query< (Entity, &Inherited<C>, Option<&C>), (Changed<Inherited<C>>, Without<PropagateOver<C>>, F), >, ) { for (entity, inherited, maybe_current) in &changed { if maybe_current.is_some_and(|c| &inherited.0 == c) { continue; } commands.entity(entity).try_insert(inherited.0.clone()); } } ```
1 parent 3902804 commit c617fc4

File tree

3 files changed

+105
-64
lines changed

3 files changed

+105
-64
lines changed

crates/bevy_pbr/src/render/light.rs

Lines changed: 105 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ pub struct GpuDirectionalLight {
121121
num_cascades: u32,
122122
cascades_overlap_proportion: f32,
123123
depth_texture_base_index: u32,
124-
skip: u32,
125124
}
126125

127126
// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl!
@@ -1009,57 +1008,37 @@ pub fn prepare_lights(
10091008
global_light_meta.entity_to_index.insert(entity, index);
10101009
}
10111010

1012-
let mut gpu_directional_lights = [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS];
1011+
// iterate the views once to find the maximum number of cascade shadowmaps we will need
10131012
let mut num_directional_cascades_enabled = 0usize;
1014-
for (index, (_light_entity, _, light)) in directional_lights
1013+
for (
1014+
_entity,
1015+
_camera_main_entity,
1016+
_extracted_view,
1017+
_clusters,
1018+
maybe_layers,
1019+
_no_indirect_drawing,
1020+
_maybe_ambient_override,
1021+
) in sorted_cameras
1022+
.0
10151023
.iter()
1016-
.enumerate()
1017-
.take(MAX_DIRECTIONAL_LIGHTS)
1024+
.filter_map(|sorted_camera| views.get(sorted_camera.entity).ok())
10181025
{
1019-
let mut flags = DirectionalLightFlags::NONE;
1020-
1021-
// Lights are sorted, volumetric and shadow enabled lights are first
1022-
if light.volumetric
1023-
&& light.shadows_enabled
1024-
&& (index < directional_volumetric_enabled_count)
1025-
{
1026-
flags |= DirectionalLightFlags::VOLUMETRIC;
1027-
}
1028-
// Shadow enabled lights are second
1029-
if light.shadows_enabled && (index < directional_shadow_enabled_count) {
1030-
flags |= DirectionalLightFlags::SHADOWS_ENABLED;
1031-
}
1032-
1033-
if light.affects_lightmapped_mesh_diffuse {
1034-
flags |= DirectionalLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE;
1026+
let mut num_directional_cascades_for_this_view = 0usize;
1027+
let render_layers = maybe_layers.unwrap_or_default();
1028+
1029+
for (_light_entity, _, light) in directional_lights.iter() {
1030+
if light.shadows_enabled && light.render_layers.intersects(render_layers) {
1031+
num_directional_cascades_for_this_view += light
1032+
.cascade_shadow_config
1033+
.bounds
1034+
.len()
1035+
.min(MAX_CASCADES_PER_LIGHT);
1036+
}
10351037
}
10361038

1037-
let num_cascades = light
1038-
.cascade_shadow_config
1039-
.bounds
1040-
.len()
1041-
.min(MAX_CASCADES_PER_LIGHT);
1042-
gpu_directional_lights[index] = GpuDirectionalLight {
1043-
// Set to true later when necessary.
1044-
skip: 0u32,
1045-
// Filled in later.
1046-
cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT],
1047-
// premultiply color by illuminance
1048-
// we don't use the alpha at all, so no reason to multiply only [0..3]
1049-
color: Vec4::from_slice(&light.color.to_f32_array()) * light.illuminance,
1050-
// direction is negated to be ready for N.L
1051-
dir_to_light: light.transform.back().into(),
1052-
flags: flags.bits(),
1053-
soft_shadow_size: light.soft_shadow_size.unwrap_or_default(),
1054-
shadow_depth_bias: light.shadow_depth_bias,
1055-
shadow_normal_bias: light.shadow_normal_bias,
1056-
num_cascades: num_cascades as u32,
1057-
cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion,
1058-
depth_texture_base_index: num_directional_cascades_enabled as u32,
1059-
};
1060-
if index < directional_shadow_enabled_count {
1061-
num_directional_cascades_enabled += num_cascades;
1062-
}
1039+
num_directional_cascades_enabled = num_directional_cascades_enabled
1040+
.max(num_directional_cascades_for_this_view)
1041+
.min(max_texture_array_layers);
10631042
}
10641043

10651044
global_light_meta
@@ -1184,6 +1163,7 @@ pub fn prepare_lights(
11841163
{
11851164
live_views.insert(entity);
11861165

1166+
let view_layers = maybe_layers.unwrap_or_default();
11871167
let mut view_lights = Vec::new();
11881168
let mut view_occlusion_culling_lights = Vec::new();
11891169

@@ -1203,6 +1183,68 @@ pub fn prepare_lights(
12031183

12041184
let n_clusters = clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z;
12051185
let ambient_light = maybe_ambient_override.unwrap_or(&ambient_light);
1186+
1187+
let mut gpu_directional_lights = [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS];
1188+
let mut num_directional_cascades_enabled_for_this_view = 0usize;
1189+
let mut num_directional_lights_for_this_view = 0usize;
1190+
for (index, (_light_entity, _, light)) in directional_lights
1191+
.iter()
1192+
.filter(|(_light_entity, _, light)| light.render_layers.intersects(view_layers))
1193+
.enumerate()
1194+
.take(MAX_DIRECTIONAL_LIGHTS)
1195+
{
1196+
num_directional_lights_for_this_view += 1;
1197+
1198+
let mut flags = DirectionalLightFlags::NONE;
1199+
1200+
// Lights are sorted, volumetric and shadow enabled lights are first
1201+
if light.volumetric
1202+
&& light.shadows_enabled
1203+
&& (index < directional_volumetric_enabled_count)
1204+
{
1205+
flags |= DirectionalLightFlags::VOLUMETRIC;
1206+
}
1207+
1208+
// Shadow enabled lights are second
1209+
let mut num_cascades = 0;
1210+
if light.shadows_enabled {
1211+
let cascades = light
1212+
.cascade_shadow_config
1213+
.bounds
1214+
.len()
1215+
.min(MAX_CASCADES_PER_LIGHT);
1216+
1217+
if num_directional_cascades_enabled_for_this_view + cascades
1218+
<= max_texture_array_layers
1219+
{
1220+
flags |= DirectionalLightFlags::SHADOWS_ENABLED;
1221+
num_cascades += cascades;
1222+
}
1223+
}
1224+
1225+
if light.affects_lightmapped_mesh_diffuse {
1226+
flags |= DirectionalLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE;
1227+
}
1228+
1229+
gpu_directional_lights[index] = GpuDirectionalLight {
1230+
// Filled in later.
1231+
cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT],
1232+
// premultiply color by illuminance
1233+
// we don't use the alpha at all, so no reason to multiply only [0..3]
1234+
color: Vec4::from_slice(&light.color.to_f32_array()) * light.illuminance,
1235+
// direction is negated to be ready for N.L
1236+
dir_to_light: light.transform.back().into(),
1237+
flags: flags.bits(),
1238+
soft_shadow_size: light.soft_shadow_size.unwrap_or_default(),
1239+
shadow_depth_bias: light.shadow_depth_bias,
1240+
shadow_normal_bias: light.shadow_normal_bias,
1241+
num_cascades: num_cascades as u32,
1242+
cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion,
1243+
depth_texture_base_index: num_directional_cascades_enabled_for_this_view as u32,
1244+
};
1245+
num_directional_cascades_enabled_for_this_view += num_cascades;
1246+
}
1247+
12061248
let mut gpu_lights = GpuLights {
12071249
directional_lights: gpu_directional_lights,
12081250
ambient_color: Vec4::from_slice(&LinearRgba::from(ambient_light.color).to_f32_array())
@@ -1214,8 +1256,7 @@ pub fn prepare_lights(
12141256
cluster_factors_zw.y,
12151257
),
12161258
cluster_dimensions: clusters.dimensions.extend(n_clusters),
1217-
n_directional_lights: directional_lights.iter().len().min(MAX_DIRECTIONAL_LIGHTS)
1218-
as u32,
1259+
n_directional_lights: num_directional_lights_for_this_view as u32,
12191260
// spotlight shadow maps are stored in the directional light array, starting at num_directional_cascades_enabled.
12201261
// the spot lights themselves start in the light array at point_light_count. so to go from light
12211262
// index to shadow map index, we need to subtract point light count and add directional shadowmap count.
@@ -1445,27 +1486,31 @@ pub fn prepare_lights(
14451486
}
14461487

14471488
// directional lights
1489+
// clear entities for lights that don't intersect the layer
1490+
for &(light_entity, _, _) in directional_lights
1491+
.iter()
1492+
.filter(|(_, _, light)| !light.render_layers.intersects(view_layers))
1493+
{
1494+
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1495+
continue;
1496+
};
1497+
if let Some(entities) = light_view_entities.remove(&entity) {
1498+
despawn_entities(&mut commands, entities);
1499+
}
1500+
}
1501+
14481502
let mut directional_depth_texture_array_index = 0u32;
1449-
let view_layers = maybe_layers.unwrap_or_default();
14501503
for (light_index, &(light_entity, light_main_entity, light)) in directional_lights
14511504
.iter()
1505+
.filter(|(_, _, light)| light.render_layers.intersects(view_layers))
14521506
.enumerate()
14531507
.take(MAX_DIRECTIONAL_LIGHTS)
14541508
{
1455-
let gpu_light = &mut gpu_lights.directional_lights[light_index];
1456-
14571509
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
14581510
continue;
14591511
};
14601512

1461-
// Check if the light intersects with the view.
1462-
if !view_layers.intersects(&light.render_layers) {
1463-
gpu_light.skip = 1u32;
1464-
if let Some(entities) = light_view_entities.remove(&entity) {
1465-
despawn_entities(&mut commands, entities);
1466-
}
1467-
continue;
1468-
}
1513+
let gpu_light = &mut gpu_lights.directional_lights[light_index];
14691514

14701515
// Only deal with cascades when shadows are enabled.
14711516
if (gpu_light.flags & DirectionalLightFlags::SHADOWS_ENABLED.bits()) == 0u32 {

crates/bevy_pbr/src/render/mesh_view_types.wgsl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ struct DirectionalLight {
4040
num_cascades: u32,
4141
cascades_overlap_proportion: f32,
4242
depth_texture_base_index: u32,
43-
skip: u32,
4443
};
4544

4645
const DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u;

crates/bevy_pbr/src/render/pbr_functions.wgsl

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -511,9 +511,6 @@ fn apply_pbr_lighting(
511511
// check if this light should be skipped, which occurs if this light does not intersect with the view
512512
// note point and spot lights aren't skippable, as the relevant lights are filtered in `assign_lights_to_clusters`
513513
let light = &view_bindings::lights.directional_lights[i];
514-
if (*light).skip != 0u {
515-
continue;
516-
}
517514

518515
// If we're lightmapped, disable diffuse contribution from the light if
519516
// requested, to avoid double-counting light.

0 commit comments

Comments
 (0)