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:📷: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 for PropagateSet<C> { fn hash<H: std:#️⃣: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()); } } ```
This commit is contained in:
parent
56bdd5c3c1
commit
75d92f4434
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user