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:
robtfm 2025-05-30 21:35:55 +02:00 committed by GitHub
parent 3902804114
commit c617fc49ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 104 additions and 63 deletions

View File

@ -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 {

View File

@ -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;

View File

@ -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.