diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index f57ba9adf3..e779dbc841 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -121,7 +121,6 @@ pub struct GpuDirectionalLight { num_cascades: u32, cascades_overlap_proportion: f32, depth_texture_base_index: u32, - skip: u32, } // NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl! @@ -1009,57 +1008,37 @@ pub fn prepare_lights( global_light_meta.entity_to_index.insert(entity, index); } - let mut gpu_directional_lights = [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS]; + // iterate the views once to find the maximum number of cascade shadowmaps we will need let mut num_directional_cascades_enabled = 0usize; - for (index, (_light_entity, _, light)) in directional_lights + for ( + _entity, + _camera_main_entity, + _extracted_view, + _clusters, + maybe_layers, + _no_indirect_drawing, + _maybe_ambient_override, + ) in sorted_cameras + .0 .iter() - .enumerate() - .take(MAX_DIRECTIONAL_LIGHTS) + .filter_map(|sorted_camera| views.get(sorted_camera.entity).ok()) { - let mut flags = DirectionalLightFlags::NONE; + let mut num_directional_cascades_for_this_view = 0usize; + let render_layers = maybe_layers.unwrap_or_default(); - // Lights are sorted, volumetric and shadow enabled lights are first - if light.volumetric - && light.shadows_enabled - && (index < directional_volumetric_enabled_count) - { - flags |= DirectionalLightFlags::VOLUMETRIC; - } - // Shadow enabled lights are second - if light.shadows_enabled && (index < directional_shadow_enabled_count) { - flags |= DirectionalLightFlags::SHADOWS_ENABLED; + for (_light_entity, _, light) in directional_lights.iter() { + if light.shadows_enabled && light.render_layers.intersects(render_layers) { + num_directional_cascades_for_this_view += light + .cascade_shadow_config + .bounds + .len() + .min(MAX_CASCADES_PER_LIGHT); + } } - if light.affects_lightmapped_mesh_diffuse { - flags |= DirectionalLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE; - } - - let num_cascades = light - .cascade_shadow_config - .bounds - .len() - .min(MAX_CASCADES_PER_LIGHT); - gpu_directional_lights[index] = GpuDirectionalLight { - // Set to true later when necessary. - skip: 0u32, - // Filled in later. - cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT], - // premultiply color by illuminance - // we don't use the alpha at all, so no reason to multiply only [0..3] - color: Vec4::from_slice(&light.color.to_f32_array()) * light.illuminance, - // direction is negated to be ready for N.L - dir_to_light: light.transform.back().into(), - flags: flags.bits(), - soft_shadow_size: light.soft_shadow_size.unwrap_or_default(), - shadow_depth_bias: light.shadow_depth_bias, - shadow_normal_bias: light.shadow_normal_bias, - num_cascades: num_cascades as u32, - cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion, - depth_texture_base_index: num_directional_cascades_enabled as u32, - }; - if index < directional_shadow_enabled_count { - num_directional_cascades_enabled += num_cascades; - } + num_directional_cascades_enabled = num_directional_cascades_enabled + .max(num_directional_cascades_for_this_view) + .min(max_texture_array_layers); } global_light_meta @@ -1184,6 +1163,7 @@ pub fn prepare_lights( { live_views.insert(entity); + let view_layers = maybe_layers.unwrap_or_default(); let mut view_lights = Vec::new(); let mut view_occlusion_culling_lights = Vec::new(); @@ -1203,6 +1183,68 @@ pub fn prepare_lights( let n_clusters = clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z; let ambient_light = maybe_ambient_override.unwrap_or(&ambient_light); + + let mut gpu_directional_lights = [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS]; + let mut num_directional_cascades_enabled_for_this_view = 0usize; + let mut num_directional_lights_for_this_view = 0usize; + for (index, (_light_entity, _, light)) in directional_lights + .iter() + .filter(|(_light_entity, _, light)| light.render_layers.intersects(view_layers)) + .enumerate() + .take(MAX_DIRECTIONAL_LIGHTS) + { + num_directional_lights_for_this_view += 1; + + let mut flags = DirectionalLightFlags::NONE; + + // Lights are sorted, volumetric and shadow enabled lights are first + if light.volumetric + && light.shadows_enabled + && (index < directional_volumetric_enabled_count) + { + flags |= DirectionalLightFlags::VOLUMETRIC; + } + + // Shadow enabled lights are second + let mut num_cascades = 0; + if light.shadows_enabled { + let cascades = light + .cascade_shadow_config + .bounds + .len() + .min(MAX_CASCADES_PER_LIGHT); + + if num_directional_cascades_enabled_for_this_view + cascades + <= max_texture_array_layers + { + flags |= DirectionalLightFlags::SHADOWS_ENABLED; + num_cascades += cascades; + } + } + + if light.affects_lightmapped_mesh_diffuse { + flags |= DirectionalLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE; + } + + gpu_directional_lights[index] = GpuDirectionalLight { + // Filled in later. + cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT], + // premultiply color by illuminance + // we don't use the alpha at all, so no reason to multiply only [0..3] + color: Vec4::from_slice(&light.color.to_f32_array()) * light.illuminance, + // direction is negated to be ready for N.L + dir_to_light: light.transform.back().into(), + flags: flags.bits(), + soft_shadow_size: light.soft_shadow_size.unwrap_or_default(), + shadow_depth_bias: light.shadow_depth_bias, + shadow_normal_bias: light.shadow_normal_bias, + num_cascades: num_cascades as u32, + cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion, + depth_texture_base_index: num_directional_cascades_enabled_for_this_view as u32, + }; + num_directional_cascades_enabled_for_this_view += num_cascades; + } + let mut gpu_lights = GpuLights { directional_lights: gpu_directional_lights, ambient_color: Vec4::from_slice(&LinearRgba::from(ambient_light.color).to_f32_array()) @@ -1214,8 +1256,7 @@ pub fn prepare_lights( cluster_factors_zw.y, ), cluster_dimensions: clusters.dimensions.extend(n_clusters), - n_directional_lights: directional_lights.iter().len().min(MAX_DIRECTIONAL_LIGHTS) - as u32, + n_directional_lights: num_directional_lights_for_this_view as u32, // spotlight shadow maps are stored in the directional light array, starting at num_directional_cascades_enabled. // the spot lights themselves start in the light array at point_light_count. so to go from light // 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( } // directional lights + // clear entities for lights that don't intersect the layer + for &(light_entity, _, _) in directional_lights + .iter() + .filter(|(_, _, light)| !light.render_layers.intersects(view_layers)) + { + let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else { + continue; + }; + if let Some(entities) = light_view_entities.remove(&entity) { + despawn_entities(&mut commands, entities); + } + } + let mut directional_depth_texture_array_index = 0u32; - let view_layers = maybe_layers.unwrap_or_default(); for (light_index, &(light_entity, light_main_entity, light)) in directional_lights .iter() + .filter(|(_, _, light)| light.render_layers.intersects(view_layers)) .enumerate() .take(MAX_DIRECTIONAL_LIGHTS) { - let gpu_light = &mut gpu_lights.directional_lights[light_index]; - let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else { continue; }; - // Check if the light intersects with the view. - if !view_layers.intersects(&light.render_layers) { - gpu_light.skip = 1u32; - if let Some(entities) = light_view_entities.remove(&entity) { - despawn_entities(&mut commands, entities); - } - continue; - } + let gpu_light = &mut gpu_lights.directional_lights[light_index]; // Only deal with cascades when shadows are enabled. if (gpu_light.flags & DirectionalLightFlags::SHADOWS_ENABLED.bits()) == 0u32 { diff --git a/crates/bevy_pbr/src/render/mesh_view_types.wgsl b/crates/bevy_pbr/src/render/mesh_view_types.wgsl index 6db72759df..0c14df01ae 100644 --- a/crates/bevy_pbr/src/render/mesh_view_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_types.wgsl @@ -40,7 +40,6 @@ struct DirectionalLight { num_cascades: u32, cascades_overlap_proportion: f32, depth_texture_base_index: u32, - skip: u32, }; const DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index dcda30ee79..11e6d4d874 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -511,9 +511,6 @@ fn apply_pbr_lighting( // check if this light should be skipped, which occurs if this light does not intersect with the view // note point and spot lights aren't skippable, as the relevant lights are filtered in `assign_lights_to_clusters` let light = &view_bindings::lights.directional_lights[i]; - if (*light).skip != 0u { - continue; - } // If we're lightmapped, disable diffuse contribution from the light if // requested, to avoid double-counting light.