Skip to content

Opt-in-Per-Camera Pointlight and Spotlight Shadow Maps#24625

Open
kfc35 wants to merge 30 commits into
bevyengine:mainfrom
kfc35:23978_per_camera_shadow_maps
Open

Opt-in-Per-Camera Pointlight and Spotlight Shadow Maps#24625
kfc35 wants to merge 30 commits into
bevyengine:mainfrom
kfc35:23978_per_camera_shadow_maps

Conversation

@kfc35

@kfc35 kfc35 commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Objective

A future patch may want to add flags to cameras that specify that they should have their own point light and spot light shadow maps that inherit the render layer (and HLOD) behavior of their associated cameras. As this patch is fairly large, though, and because my immediate goal is to fix the regression in #23481, I think those flags are best implemented in a follow-up.

This is intended to be that follow-up if I understood the intention correctly 😅
This idea has also floated around on Discord, with an emphasis on this being configurable, so I preserved that desire in this PR.

Solution

A new property, has_own_shadow_maps, has been added to Camera. By default, this is set to false, preserving existing behavior.

If a camera requests its own shadow maps, new depth attachments and ShadowViews are created for these additional views in prepare_lights in light.rs, which is where the meat of this PR is. prepare_lights also manages what happens if the light itself changes or the views that opt in change. There’s also an added optimization that if all the cameras request their own shadow maps, the view-agnostic pointlight/spotlight shadow map is removed/not created.

The shader code changes are to ensure the new shadow maps are actually being used. Two new indices offset, point_spot_shadow_map_index_mult_offset and …_add_offset, are used to correctly index into the correct shadow maps. A lot of the wgsl code changes are to refactor a variable from light_id to array_index, as this more accurately describes its use and it matches the corresponding shadow sampling code for spotlight shadows. (actively soliciting for less verbose names 😆 )

Testing

For correctness:
cargo run --example testbed_3d --features=“area_light_luts” -- renderlayers
I tested with the render_layers (with the changes I put in for the light to be on the 3 render layers). It works as expected now!
I tested both a PointLight and a SpotLight (have to change the Spotlight transform to add a looking_at: Transform::from_xyz(4.0, 8.0, 4.0).looking_at(Vec3::new(3.0, 0.5, 0.0), Vec3::Y)))

I’ll probably run the bevy example runner against this PR to ensure there are no correctness regressions otherwise.

I have not done any performance testing (nor know if it’s necesssary). I don’t know if there is a performance regression in the regular “no camera has their own shadow map” case or if we should be concerned about that. If someone can help with this, I’d be indebted.


Showcase

The render layers scene in the 3d testbed with a PointLight now after the changes in this PR:
Screenshot 2026-06-14 at 11 31 08 AM

The same scene with a SpotLight instead described above in Testing:
Screenshot 2026-06-14 at 11 30 23 AM

@kfc35 kfc35 added C-Feature A new feature, making something new possible A-Rendering Drawing game state to the screen D-Complex Quite challenging from either a design or technical perspective. Ask for help! S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 14, 2026
@kfc35 kfc35 requested a review from pcwalton June 14, 2026 15:50
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in Rendering Jun 14, 2026
@kfc35 kfc35 requested a review from tychedelia June 14, 2026 15:50
@kfc35 kfc35 added the M-Deliberate-Rendering-Change An intentional change to how tests and examples are rendered label Jun 14, 2026
@kfc35 kfc35 changed the title Per-Camera Pointlight and Spotlight Shadow Maps Opt-in Per-Camera Pointlight and Spotlight Shadow Maps Jun 14, 2026
@kfc35 kfc35 changed the title Opt-in Per-Camera Pointlight and Spotlight Shadow Maps Opt-in-Per-Camera Pointlight and Spotlight Shadow Maps Jun 14, 2026
@kfc35

kfc35 commented Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

Hm there appears to an issue with rendering when there are multiple PointLights since my index math is messed up on the shader code side. Will figure it out

@kfc35 kfc35 marked this pull request as draft June 14, 2026 20:22
@kfc35 kfc35 added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 14, 2026
@kfc35 kfc35 force-pushed the 23978_per_camera_shadow_maps branch from 3d1be2a to ad3d74b Compare June 15, 2026 00:43
@kfc35 kfc35 marked this pull request as ready for review June 15, 2026 20:34
@kfc35 kfc35 added S-Needs-Review Needs reviewer attention (from anyone!) to move forward and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Jun 15, 2026
@kfc35

kfc35 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

I addressed the fog issues reported from the initial example runner with e9f8e87 and fixed the lighting/free_camera_controller/ multi pointlight issue with cf1ab11

So this should be ready for review finally!

@pcwalton

Copy link
Copy Markdown
Contributor

Awesome! I’ll review this shortly.

@pcwalton pcwalton left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review only—I’ll have to finish this tomorrow—but this is really awesome and fiddly work. Thank you for doing it! It really makes our story around shadow maps a lot better.

/// will generate 6 additional shadow maps per camera that opts to have its own shadow maps.
///
/// Defaults to `false`.
pub has_own_shadow_maps: bool,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has_own_point_and_spot_light_shadow_maps might be better, if verbose, since views always have own directional shadow maps.

pub mip_bias: f32,
pub frame_count: u32,
/// This offset is used to fetch the correct point or spot shadow map to use for this view.
/// This is used to accommodates views that may be configured to have their own point or spot shadow maps.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: should be “to accommodate”

/// This must be multiplied to the initial index.
pub point_spot_shadow_map_index_mult_offset: u32,
/// This offset is used to fetch the correct point or spot shadow map to use for this view.
/// This is used to accommodates views that may be configured to have their own point or spot shadow maps.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same typo as above

/// This is used to accommodates views that may be configured to have their own point or spot shadow maps.
/// This represents the additive index of the point/spot shadow map that this view should use.
/// This must be added to the index after the multiplicative offset has been applied.
pub point_spot_shadow_map_index_add_offset: u32,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I’d prefer calling this something like point_spot_shadow_map_index.

/// This represents the total number of shadow maps (not counting shadow maps of different face indices)
/// that have been generated to be utilized by all possible views.
/// This must be multiplied to the initial index.
pub point_spot_shadow_map_index_mult_offset: u32,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I’d prefer calling this something like point_spot_shadow_map_count.

#[derive(Component)]
pub struct SpotLightShadowViewEntity {
/// The spotlight shadow view associated with a camera that has opted
/// in to its own shadow maps.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment above.

Some((entity, MainEntity::from(main_entity)))
})
.collect();
if views_count - point_spot_shadow_aux_entities.len() > 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if views_count - point_spot_shadow_aux_entities.len() > 0 {
if views_count > point_spot_shadow_aux_entities.len() {

Comment on lines +1821 to +1824
let insert_shadow_map = match auxiliary_entity {
Some((entity, _)) => point_and_spot_light_view_entities.1.get(entity).is_none(),
None => point_and_spot_light_view_entities.0.is_empty(),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let insert_shadow_map = match auxiliary_entity {
Some((entity, _)) => point_and_spot_light_view_entities.1.get(entity).is_none(),
None => point_and_spot_light_view_entities.0.is_empty(),
};
match auxiliary_entity {
Some((entity, _)) => {
if point_and_spot_light_view_entities.1.get(entity).is_some() {
continue;
}
}
None => {
if !point_and_spot_light_view_entities.0.is_empty() {
continue;
}
}
}

And then remove the if insert_shadow_map check below, eliminating some rightward drift.

maybe_layers,
no_indirect_drawing,
maybe_ambient_override,
_extracted_camera,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the _ here since the variable is now used.

/// Creates six point shadow maps for a `RetainedViewEntity` identified by the `light_main_entity`
/// and the six cube face rotation indices. These shadow maps are shared across
/// all cameras.
/// Creates six point shadow map for a `RetainedViewEntity` identified by the `light_main_entity`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Creates six point shadow map for a `RetainedViewEntity` identified by the `light_main_entity`,
/// Creates six point shadow maps for a `RetainedViewEntity` identified by the `light_main_entity`,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Rendering Drawing game state to the screen C-Feature A new feature, making something new possible D-Complex Quite challenging from either a design or technical perspective. Ask for help! M-Deliberate-Rendering-Change An intentional change to how tests and examples are rendered S-Needs-Review Needs reviewer attention (from anyone!) to move forward

Projects

Status: Needs SME Triage

Development

Successfully merging this pull request may close these issues.

Multi-Camera (Per Camera) Shadow Maps

2 participants