Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions crates/bevy_camera/src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,51 @@ impl Camera {
}
}

/// The entity that Bevy uses to resolve visibility ranges when no specific
/// camera is applicable.
///
/// For efficiency, Bevy currently renders point and spot light shadow maps only
/// once per frame, regardless of the number of cameras in use, rather than
/// rendering the shadow maps anew for each camera each frame. Most of the time,
/// this optimization doesn't change the result relative to a rendering that
/// rendered such shadow maps separately for each camera. However, there's one
/// exception: visibility ranges. Visibility ranges cause meshes to be visible
/// or invisible depending on the distance from the mesh to the camera. When
/// rendering a shadow map for a point or spot light, Bevy must therefore select
/// an entity to use as the reference point for the purposes of visibility
/// ranges. This entity is called the *LOD origin*.
///
/// Placing this component on an entity makes that entity the origin from which
/// LOD distances are computed for the purposes of shadow mapping of point and
/// spot lights. Typically, you place this component on a camera, but you may
/// place it on another entity if you wish.
///
/// The exact algorithm that Bevy uses to determine the LOD origin is as
/// follows. Once the LOD origin is determined, all further steps are skipped.
///
/// 1. If an entity has this [`ShadowLodOrigin`] component, then it's the LOD
/// origin. If there's more than one entity with the [`ShadowLodOrigin`]
/// component, one is chosen arbitrarily in a manner that's stable from frame to
/// frame.
///
/// 2. If a camera renders to a window (that is, the camera's [`RenderTarget`]
/// is [`RenderTarget::Window`]), then that camera is the shadow LOD origin. If
/// there's more than one such camera that renders to a window, then one is
/// chosen arbitrarily in a manner that's stable from frame to frame.
///
/// 3. A camera is chosen to be the LOD origin arbitrarily from all cameras in
/// the scene in a manner that's stable from frame to frame.
///
/// This algorithm means that, in most cases, you don't need to add this
/// [`ShadowLodOrigin`] component explicitly to the scene; usually, Bevy chooses
/// the right origin automatically. You only need to use this component
/// explicitly if you have multiple cameras rendering to the window: e.g. in a
/// split-screen game.
#[derive(Clone, Copy, Default, Component, Debug, Reflect)]
#[reflect(Clone, Default, Component)]
#[require(Transform)]
pub struct ShadowLodOrigin;
Comment thread
pcwalton marked this conversation as resolved.

/// Control how this [`Camera`] outputs once rendering is completed.
#[derive(Debug, Clone, Copy, Reflect)]
pub enum CameraOutputMode {
Expand Down
18 changes: 3 additions & 15 deletions crates/bevy_camera/src/visibility/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use bevy_app::{App, Plugin, PostUpdate};
use bevy_ecs::{
component::Component,
entity::{Entity, EntityHashMap},
query::With,
query::{Or, With},
reflect::ReflectComponent,
resource::Resource,
schedule::IntoScheduleConfigs as _,
Expand All @@ -22,7 +22,7 @@ use bevy_transform::components::GlobalTransform;
use bevy_utils::Parallel;

use super::{check_visibility_cpu_culling, VisibilitySystems};
use crate::{camera::Camera, primitives::Aabb};
use crate::{camera::Camera, primitives::Aabb, ShadowLodOrigin};

/// A plugin that enables [`VisibilityRange`]s, which allow entities to be
/// hidden or shown based on distance to the camera.
Expand Down Expand Up @@ -220,18 +220,6 @@ impl VisibleEntityRanges {
};
(visibility_bitmask & (1 << view_index)) != 0
}

/// Returns true if the entity is in range of any view.
///
/// This only checks [`VisibilityRange`]s and doesn't perform any frustum or
/// occlusion culling. Thus the entity might not *actually* be visible.
///
/// The entity is assumed to have a [`VisibilityRange`] component. If the
/// entity doesn't have that component, this method will return false.
#[inline]
pub fn entity_is_in_range_of_any_view(&self, entity: Entity) -> bool {
self.entities.contains_key(&entity)
}
}

/// Checks all entities against all views in order to determine which entities
Expand All @@ -241,7 +229,7 @@ impl VisibleEntityRanges {
/// cull.
pub fn check_visibility_ranges(
mut visible_entity_ranges: ResMut<VisibleEntityRanges>,
view_query: Query<(Entity, &GlobalTransform), With<Camera>>,
view_query: Query<(Entity, &GlobalTransform), Or<(With<Camera>, With<ShadowLodOrigin>)>>,
mut par_local: Local<Parallel<Vec<(Entity, u32)>>>,
entity_query: Query<(Entity, &GlobalTransform, Option<&Aabb>, &VisibilityRange)>,
) {
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_light/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ bevy_color = { path = "../bevy_color", version = "0.19.0-dev", features = [
"serialize",
] }
bevy_gizmos = { path = "../bevy_gizmos", version = "0.19.0-dev", optional = true }
bevy_log = { path = "../bevy_log", version = "0.19.0-dev" }

# other
tracing = { version = "0.1", default-features = false }
Expand Down
76 changes: 72 additions & 4 deletions crates/bevy_light/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@ use bevy_camera::{
NoFrustumCulling, RenderLayers, ViewVisibility, VisibilityRange, VisibilitySystems,
VisibleEntities, VisibleEntityRanges, VisibleMeshEntities,
},
Camera3d, CameraUpdateSystems,
Camera, Camera3d, CameraUpdateSystems, RenderTarget, ShadowLodOrigin,
};
use bevy_ecs::{entity::EntityHashSet, prelude::*};
use bevy_ecs::{entity::EntityHashSet, prelude::*, system::QueryLens};
#[cfg(feature = "bevy_gizmos")]
use bevy_gizmos::frustum::FrustumGizmoSystems;
use bevy_log::warn_once;
use bevy_math::Vec3A;
use bevy_mesh::Mesh3d;
use bevy_reflect::prelude::*;
use bevy_transform::{components::GlobalTransform, TransformSystems};
use bevy_utils::Parallel;
use core::{any::TypeId, mem, ops::DerefMut};
use smallvec::{smallvec, SmallVec};

pub mod cluster;
use cluster::assign::assign_objects_to_clusters;
Expand Down Expand Up @@ -533,13 +535,22 @@ pub fn check_point_light_mesh_visibility(
With<Mesh3d>,
),
>,
mut camera_query: Query<(Entity, &RenderTarget), With<Camera>>,
mut shadow_lod_origin_query: Query<Entity, With<ShadowLodOrigin>>,
mut point_and_spot_light_query: Query<Entity, Or<(With<PointLight>, With<SpotLight>)>>,
visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
mut cubemap_visible_entities_queue: Local<Parallel<[Vec<Entity>; 6]>>,
mut spot_visible_entities_queue: Local<Parallel<Vec<Entity>>>,
mut checked_lights: Local<EntityHashSet>,
) {
checked_lights.clear();

let shadow_lod_origin = get_shadow_lod_origin(
camera_query.transmute_lens_filtered(),
shadow_lod_origin_query.transmute_lens_filtered(),
point_and_spot_light_query.transmute_lens_filtered(),
);

let visible_entity_ranges = visible_entity_ranges.as_deref();
for visible_lights in &visible_point_lights {
for &light_entity in visible_lights.get(TypeId::of::<ClusterVisibilityClass>()) {
Expand Down Expand Up @@ -589,7 +600,10 @@ pub fn check_point_light_mesh_visibility(
}
if has_visibility_range
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
!visible_entity_ranges.entity_is_in_range_of_any_view(entity)
shadow_lod_origin.is_none_or(|shadow_lod_origin| {
!visible_entity_ranges
.entity_is_in_range_of_view(entity, shadow_lod_origin)
})
})
{
return;
Expand Down Expand Up @@ -679,7 +693,10 @@ pub fn check_point_light_mesh_visibility(
// Check visibility ranges.
if has_visibility_range
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
!visible_entity_ranges.entity_is_in_range_of_any_view(entity)
shadow_lod_origin.is_none_or(|shadow_lod_origin| {
!visible_entity_ranges
.entity_is_in_range_of_view(entity, shadow_lod_origin)
})
})
{
return;
Expand Down Expand Up @@ -717,3 +734,54 @@ pub fn check_point_light_mesh_visibility(
}
}
}

/// Determines the LOD origin for spot and point light shadow maps.
///
/// The selection priority is, from highest to lowest:
///
/// 1. An entity explicitly marked with the [`ShadowLodOrigin`] component.
///
/// 2. A camera that renders to a window.
///
/// 3. Any camera.
pub fn get_shadow_lod_origin(
mut camera_query: QueryLens<(Entity, &RenderTarget), With<Camera>>,
mut shadow_lod_origin_query: QueryLens<Entity, With<ShadowLodOrigin>>,
mut lights_query: QueryLens<Entity, Or<(With<PointLight>, With<SpotLight>)>>,
) -> Option<Entity> {
let (camera_query, shadow_lod_origin_query) =
(camera_query.query(), shadow_lod_origin_query.query());

let mut entities: SmallVec<[Entity; 4]> = smallvec![];
entities.extend(shadow_lod_origin_query.iter());
if let Some(lod_origin) = entities.iter().min() {
return Some(*lod_origin);
}

entities.extend(
camera_query
.iter()
.filter_map(|(main_entity, render_target)| match *render_target {
RenderTarget::Window(_) => Some(main_entity),
_ => None,
}),
);
if let Some(lod_origin) = entities.iter().min() {
return Some(*lod_origin);
};

entities.extend(camera_query.iter().map(|(main_entity, _)| main_entity));
if let Some(lod_origin) = entities.iter().min() {
if !lights_query.query().is_empty() {
warn_once!(
"Point lights and/or spot lights are present, but no entity has \
`ShadowLodOrigin`, and no camera that renders to the window has been found. \
Consider using the `ShadowLodOrigin` component to set a LOD origin."
);
}

return Some(*lod_origin);
};

None
}
7 changes: 6 additions & 1 deletion crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ mod gltf;
use bevy_light::cluster::GlobalClusterSettings;
use bevy_render::{
sync_component::SyncComponent,
view::{RenderExtractedShadowMapVisibleEntities, RenderShadowMapVisibleEntities},
view::{
RenderExtractedShadowMapVisibleEntities, RenderShadowLodOrigin,
RenderShadowMapVisibleEntities,
},
};
pub use contact_shadows::{
ContactShadows, ContactShadowsBuffer, ContactShadowsPlugin, ContactShadowsUniform,
Expand Down Expand Up @@ -386,6 +389,7 @@ impl Plugin for PbrPlugin {
extract_ambient_light_resource,
extract_ambient_light,
extract_shadow_filtering_method,
extract_shadow_lod_origin,
late_sweep_material_instances,
),
)
Expand All @@ -406,6 +410,7 @@ impl Plugin for PbrPlugin {
)
.init_gpu_resource::<LightMeta>()
.init_gpu_resource::<RenderMaterialBindings>()
.init_resource::<RenderShadowLodOrigin>()
.allow_ambiguous_resource::<RenderMaterialBindings>();

render_app.world_mut().add_observer(add_light_view_entities);
Expand Down
33 changes: 30 additions & 3 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use bevy_camera::visibility::{
CascadesVisibleEntities, CubemapVisibleEntities, RenderLayers, ViewVisibility,
VisibleMeshEntities,
};
use bevy_camera::Camera3d;
use bevy_camera::{Camera, Camera3d, RenderTarget, ShadowLodOrigin};
use bevy_color::ColorToComponents;
use bevy_core_pipeline::core_3d::CORE_3D_DEPTH_FORMAT;
use bevy_core_pipeline::schedule::RootNonCameraView;
Expand Down Expand Up @@ -47,8 +47,8 @@ use bevy_render::occlusion_culling::{
};
use bevy_render::sync_world::{MainEntity, MainEntityHashMap, RenderEntity};
use bevy_render::view::{
RenderExtractedShadowMapVisibleEntities, RenderShadowMapVisibleEntities, RenderVisibleEntities,
VisibilityExtractionSystemParam,
RenderExtractedShadowMapVisibleEntities, RenderShadowLodOrigin, RenderShadowMapVisibleEntities,
RenderVisibleEntities, VisibilityExtractionSystemParam,
};
use bevy_render::{
batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
Expand Down Expand Up @@ -2888,3 +2888,30 @@ fn get_shadow_map_visible_entities<'w, 's: 'w>(
}
}
}

/// An extraction system that determines the origin for LOD computation for
/// point and spot light shadow maps and updates the [`RenderShadowLodOrigin`]
/// with the result.
///
/// See [`ShadowLodOrigin`] for more details on the algorithm that this system
/// uses.
pub fn extract_shadow_lod_origin(
global_transform_query: Extract<Query<&GlobalTransform>>,
mut camera_query: Extract<Query<(Entity, &RenderTarget), With<Camera>>>,
mut shadow_lod_origin_query: Extract<Query<Entity, With<ShadowLodOrigin>>>,
mut lights_query: Extract<Query<Entity, Or<(With<PointLight>, With<SpotLight>)>>>,
mut render_shadow_lod_origin: ResMut<RenderShadowLodOrigin>,
) {
match bevy_light::get_shadow_lod_origin(
camera_query.transmute_lens_filtered(),
shadow_lod_origin_query.transmute_lens_filtered(),
lights_query.transmute_lens_filtered(),
)
.and_then(|shadow_lod_origin_entity| global_transform_query.get(shadow_lod_origin_entity).ok())
{
Some(global_transform) => {
render_shadow_lod_origin.0 = global_transform.translation();
}
None => render_shadow_lod_origin.0 = Default::default(),
}
}
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/render/mesh_functions.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ fn get_visibility_range_dither_level(instance_index: u32, world_position: vec4<f
}

let lod_range = visibility_ranges[visibility_buffer_index];
let camera_distance = length(view.world_position.xyz - world_position.xyz);
let camera_distance = length(view.lod_view_world_position.xyz - world_position.xyz);

// This encodes the following mapping:
//
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_render/src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ pub fn extract_cameras(
NoIndirectDrawing,
ViewUniformOffset,
);

for (
main_entity,
render_entity,
Expand Down
Loading
Loading