diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 1bc693fac863d..b0593bbbe1c77 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -405,7 +405,7 @@ pub fn extract_core_2d_camera_phases( } // This is the main 2D camera, so we use the first subview index (0). - let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); + let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, None, 0); transparent_2d_phases.prepare_for_new_frame(retained_view_entity); opaque_2d_phases.prepare_for_new_frame(retained_view_entity, GpuPreprocessingMode::None); diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index c24ae999d69f2..aed4768436507 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -517,7 +517,7 @@ pub fn extract_core_3d_camera_phases( }); // This is the main 3D camera, so use the first subview index (0). - let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); + let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, None, 0); opaque_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); alpha_mask_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); @@ -587,7 +587,7 @@ pub fn extract_camera_prepass_phase( }); // This is the main 3D camera, so we use the first subview index (0). - let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); + let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, None, 0); if depth_prepass || normal_prepass || motion_vector_prepass { opaque_3d_prepass_phases diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index bfd67faac1fc8..abe5f9f883200 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -399,6 +399,7 @@ pub fn extract_lights( ), >, >, + aux_render_entity: Extract>, rect_lights: Extract< Query< ( @@ -485,6 +486,7 @@ pub fn extract_lights( let retained_view_entity = RetainedViewEntity { main_entity: MainEntity::from(main_entity), auxiliary_entity: MainEntity::from(Entity::PLACEHOLDER), + render_auxiliary_entity: Entity::PLACEHOLDER, subview_index: face_index, }; render_shadow_map_visible_entities @@ -596,6 +598,7 @@ pub fn extract_lights( let retained_view_entity = RetainedViewEntity { main_entity: MainEntity::from(main_entity), auxiliary_entity: MainEntity::from(Entity::PLACEHOLDER), + render_auxiliary_entity: Entity::PLACEHOLDER, subview_index: 0, }; render_shadow_map_visible_entities @@ -738,43 +741,47 @@ pub fn extract_lights( for (main_auxiliary_entity, visible_mesh_entities_list) in visible_entities.entities.iter() { - for subview_index in 0..(cascade_config.bounds.len() as u32) { - let retained_view_entity = RetainedViewEntity { - main_entity: MainEntity::from(main_entity), - auxiliary_entity: MainEntity::from(*main_auxiliary_entity), - subview_index, - }; - all_cascades_seen.insert(retained_view_entity); - - existing_shadow_map_visible_entities - .subviews - .entry(retained_view_entity) - .or_default(); - - // Extract the visible CPU culled entities to the list. - let extracted_entities = &mut existing_extracted_shadow_map_visible_entities - .subviews - .entry(retained_view_entity) - .or_default() - .classes - .entry(TypeId::of::()) - .or_default() - .entities; - extracted_entities.clear(); - let Some(visible_mesh_entities) = - visible_mesh_entities_list.get(subview_index as usize) - else { - continue; - }; - extracted_entities.extend(visible_mesh_entities.entities.iter().map( - |main_entity| { - let render_entity = match mapper.get(*main_entity) { - Ok(render_entity) => **render_entity, - Err(_) => Entity::PLACEHOLDER, - }; - (render_entity, MainEntity::from(*main_entity)) - }, - )); + if let Ok(render_auxiliary_entity) = aux_render_entity.get(*main_auxiliary_entity) { + for subview_index in 0..(cascade_config.bounds.len() as u32) { + let retained_view_entity = RetainedViewEntity { + main_entity: MainEntity::from(main_entity), + auxiliary_entity: MainEntity::from(*main_auxiliary_entity), + render_auxiliary_entity, + subview_index, + }; + all_cascades_seen.insert(retained_view_entity); + + existing_shadow_map_visible_entities + .subviews + .entry(retained_view_entity) + .or_default(); + + // Extract the visible CPU culled entities to the list. + let extracted_entities = + &mut existing_extracted_shadow_map_visible_entities + .subviews + .entry(retained_view_entity) + .or_default() + .classes + .entry(TypeId::of::()) + .or_default() + .entities; + extracted_entities.clear(); + let Some(visible_mesh_entities) = + visible_mesh_entities_list.get(subview_index as usize) + else { + continue; + }; + extracted_entities.extend(visible_mesh_entities.entities.iter().map( + |main_entity| { + let render_entity = match mapper.get(*main_entity) { + Ok(render_entity) => **render_entity, + Err(_) => Entity::PLACEHOLDER, + }; + (render_entity, MainEntity::from(*main_entity)) + }, + )); + } } } @@ -1526,7 +1533,7 @@ pub fn prepare_lights( // Point light shadow maps are shared across all cameras, // so the retained view entity must not include the camera. let retained_view_entity = - RetainedViewEntity::new(*light_main_entity, None, face_index as u32); + RetainedViewEntity::new(*light_main_entity, None, None, face_index as u32); commands.entity(view_light_entity).insert(( ShadowView { @@ -1584,7 +1591,7 @@ pub fn prepare_lights( { let view_light_entity = point_and_spot_light_view_entities.0[face_index]; let retained_view_entity = - RetainedViewEntity::new(*light_main_entity, None, face_index as u32); + RetainedViewEntity::new(*light_main_entity, None, None, face_index as u32); commands.entity(view_light_entity).insert(( ExtractedView { retained_view_entity, @@ -1610,7 +1617,7 @@ pub fn prepare_lights( // already created the views in order to clear out old data. for face_index in 0..6 { let retained_view_entity = - RetainedViewEntity::new(*light_main_entity, None, face_index); + RetainedViewEntity::new(*light_main_entity, None, None, face_index); shadow_render_phases.prepare_for_new_frame( retained_view_entity, gpu_preprocessing_support.max_supported_mode, @@ -1644,7 +1651,7 @@ pub fn prepare_lights( // Spot light shadow maps are shared across all cameras, // so the retained view entity must not include the camera. - let retained_view_entity = RetainedViewEntity::new(*light_main_entity, None, 0); + let retained_view_entity = RetainedViewEntity::new(*light_main_entity, None, None, 0); if point_and_spot_light_view_entities.0.is_empty() { let spot_world_from_view = spot_light_world_from_view(&light.transform); @@ -2021,6 +2028,7 @@ pub fn prepare_lights( let retained_view_entity = RetainedViewEntity::new( *light_main_entity, Some(camera_main_entity.into()), + Some(entity), cascade_index as u32, ); @@ -2854,6 +2862,7 @@ fn get_shadow_map_visible_entities<'w, 's: 'w>( let retained_view_entity = RetainedViewEntity { main_entity: extracted_view_light.retained_view_entity.main_entity, auxiliary_entity: MainEntity::from(Entity::PLACEHOLDER), + render_auxiliary_entity: Entity::PLACEHOLDER, subview_index: *face_index as u32, }; shadow_map_visible_entities_query @@ -2870,6 +2879,7 @@ fn get_shadow_map_visible_entities<'w, 's: 'w>( let retained_view_entity = RetainedViewEntity { main_entity: extracted_view_light.retained_view_entity.main_entity, auxiliary_entity: MainEntity::from(Entity::PLACEHOLDER), + render_auxiliary_entity: Entity::PLACEHOLDER, subview_index: 0, }; shadow_map_visible_entities_query diff --git a/crates/bevy_pbr/src/render/mesh_preprocess.wgsl b/crates/bevy_pbr/src/render/mesh_preprocess.wgsl index 496cd6b6f84ab..bece93f01e1c7 100644 --- a/crates/bevy_pbr/src/render/mesh_preprocess.wgsl +++ b/crates/bevy_pbr/src/render/mesh_preprocess.wgsl @@ -29,7 +29,9 @@ position_world_to_prev_ndc, position_world_to_prev_view, prev_view_z_to_depth_ndc } #import bevy_render::maths -#import bevy_render::view::View +#import bevy_render::view::{ + View, VIEW_FLAGS_HAS_USABLE_PRIMARY_WORLD_POSITION +} // Information about each mesh instance needed to cull it on GPU. // @@ -211,7 +213,9 @@ fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { let visibility_buffer_array_len = arrayLength(&visibility_ranges); let visibility_buffer_index = current_input[input_index].flags & MESH_FLAGS_VISIBILITY_RANGE_INDEX_BITS; - if (visibility_buffer_index < visibility_buffer_array_len) { + if (visibility_buffer_index < visibility_buffer_array_len + && (view.flags & VIEW_FLAGS_HAS_USABLE_PRIMARY_WORLD_POSITION) != 0u) + { let lod_range = visibility_ranges[visibility_buffer_index]; // If we're using the AABB as the mesh center, determine its world space position. @@ -224,7 +228,7 @@ fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { world_pos = world_from_local[3].xyz; } - let camera_distance = length(position_world_to_view(world_pos)); + let camera_distance = length(view.primary_world_position - world_pos); // `x` is the minimum range; `w` is the largest range. if (camera_distance < lod_range.x || camera_distance >= lod_range.w) { return; diff --git a/crates/bevy_pbr/src/transmission/phase.rs b/crates/bevy_pbr/src/transmission/phase.rs index 69bcb62c82be9..09efd535287ec 100644 --- a/crates/bevy_pbr/src/transmission/phase.rs +++ b/crates/bevy_pbr/src/transmission/phase.rs @@ -133,7 +133,7 @@ pub fn extract_transmissive_camera_phases( } // This is the main camera, so use the first subview index (0). - let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); + let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, None, 0); transmissive_3d_phases.prepare_for_new_frame(retained_view_entity); live_entities.insert(retained_view_entity); diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 3b8581abfbcf6..146f5c0dab7bf 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -1268,7 +1268,7 @@ fn extract_wireframe_3d_camera( GpuPreprocessingMode::PreprocessingOnly }); - let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); + let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, None, 0); wireframe_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); live_entities.insert(retained_view_entity); } diff --git a/crates/bevy_render/src/camera.rs b/crates/bevy_render/src/camera.rs index 047187e2a9c2a..3e2666fa38a0e 100644 --- a/crates/bevy_render/src/camera.rs +++ b/crates/bevy_render/src/camera.rs @@ -640,7 +640,12 @@ pub fn extract_cameras( compositing_space: compositing_space.copied(), }, ExtractedView { - retained_view_entity: RetainedViewEntity::new(main_entity.into(), None, 0), + retained_view_entity: RetainedViewEntity::new( + main_entity.into(), + None, + None, + 0, + ), clip_from_view: camera.clip_from_view(), world_from_view: *transform, clip_from_world: None, diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 6c55afaec7b80..90e8f06ac04b9 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -287,6 +287,12 @@ pub struct RetainedViewEntity { /// If not present, this will be `MainEntity(Entity::PLACEHOLDER)`. pub auxiliary_entity: MainEntity, + /// The render entity corresponding to the `auxiliary_entity`. + /// + /// This is used to get the auxiliary entity's world position + /// for visibility range culling calculations. + pub render_auxiliary_entity: Entity, + /// The index of the view corresponding to the entity. /// /// For example, for point lights that cast shadows, this is the index of @@ -297,18 +303,20 @@ pub struct RetainedViewEntity { impl RetainedViewEntity { /// Creates a new [`RetainedViewEntity`] from the given main world entity, - /// auxiliary main world entity, and subview index. + /// auxiliary main world entity, auxiliary render world entity, and subview index. /// /// See [`RetainedViewEntity::subview_index`] for an explanation of what - /// `auxiliary_entity` and `subview_index` are. + /// `auxiliary_entity`, `render_auxiliary_entity` and `subview_index` are. pub fn new( main_entity: MainEntity, auxiliary_entity: Option, + render_auxiliary_entity: Option, subview_index: u32, ) -> Self { Self { main_entity, auxiliary_entity: auxiliary_entity.unwrap_or(Entity::PLACEHOLDER.into()), + render_auxiliary_entity: render_auxiliary_entity.unwrap_or(Entity::PLACEHOLDER), subview_index, } } @@ -654,6 +662,27 @@ pub struct ViewUniform { pub color_grading: ColorGradingUniform, pub mip_bias: f32, pub frame_count: u32, + /// The world position of a camera view used for visibility range culling. + /// + /// This is useful for directional shadow views, where visibility range culling should + /// be executed in relation to its non-shadow camera's world position. + /// + /// If this `ViewUniform` already represents a camera view or has no associated camera view, + /// this field will be set to `world_position`. + pub primary_world_position: Vec3, + /// Flags associated with this View. + pub flags: u32, +} + +// NOTE: These must match the bit flags in bevy_pbr/src/view/view.wgsl! +bitflags::bitflags! { + /// Various flags and tightly-packed values on a View. + #[repr(transparent)] + pub struct ViewFlags: u32 { + /// Whether the `primary_world_position` of this `ViewUniform` can be used + /// for visibility range cullling. + const USABLE_PRIMARY_WORLD_POSITION = 1 << 0; + } } #[derive(Resource)] @@ -997,6 +1026,7 @@ pub fn prepare_view_uniforms( Option<&MipBias>, Option<&MainPassResolutionOverride>, )>, + primary_view: Query<&ExtractedView, With>, frame_count: Res, ) { let view_iter = views.iter(); @@ -1049,6 +1079,25 @@ pub fn prepare_view_uniforms( .map(|frustum| frustum.half_spaces.map(|h| h.normal_d())) .unwrap_or([Vec4::ZERO; 6]); + let mut view_flags = ViewFlags::empty(); + let primary_world_position = if let Ok(primary_extracted_view) = + primary_view.get(extracted_view.retained_view_entity.render_auxiliary_entity) + { + view_flags |= ViewFlags::USABLE_PRIMARY_WORLD_POSITION; + primary_extracted_view.world_from_view.translation() + } else { + // Point and Spot light shadows do not have a usable primary world position, + // because they are not associated with a camera. + // This means that they don't engage in visibility range culling. + // This is a problem if the original mesh also has the `NoCpuCulling` component, + // as this means that the shadow will never be culled via visibility range! + // TODO: How do we better handle this case? + if extracted_camera.is_some() { + view_flags |= ViewFlags::USABLE_PRIMARY_WORLD_POSITION; + } + extracted_view.world_from_view.translation() + }; + let view_uniforms = ViewUniformOffset { offset: writer.write(&ViewUniform { clip_from_world, @@ -1068,6 +1117,8 @@ pub fn prepare_view_uniforms( color_grading: extracted_view.color_grading.clone().into(), mip_bias: mip_bias.unwrap_or(&MipBias(0.0)).0, frame_count: frame_count.0, + primary_world_position, + flags: view_flags.bits(), }), }; diff --git a/crates/bevy_render/src/view/view.wgsl b/crates/bevy_render/src/view/view.wgsl index 23ded53f40f91..52c15c0397785 100644 --- a/crates/bevy_render/src/view/view.wgsl +++ b/crates/bevy_render/src/view/view.wgsl @@ -64,8 +64,12 @@ struct View { color_grading: ColorGrading, mip_bias: f32, frame_count: u32, + primary_world_position: vec3, + flags: u32, }; +const VIEW_FLAGS_HAS_USABLE_PRIMARY_WORLD_POSITION: u32 = 1u << 0u; + /// World space: /// +y is up diff --git a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs index ec844c40e4ae5..3d0fa0e246ff2 100644 --- a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs @@ -662,7 +662,7 @@ fn extract_wireframe_2d_camera( if !camera.is_active { continue; } - let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); + let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, None, 0); wireframe_2d_phases.prepare_for_new_frame(retained_view_entity, GpuPreprocessingMode::None); live_entities.insert(retained_view_entity); } diff --git a/crates/bevy_ui_render/src/lib.rs b/crates/bevy_ui_render/src/lib.rs index 21fa1f301da98..340ada3898ed8 100644 --- a/crates/bevy_ui_render/src/lib.rs +++ b/crates/bevy_ui_render/src/lib.rs @@ -803,7 +803,7 @@ pub fn extract_ui_camera_view( // We use `UI_CAMERA_SUBVIEW` here so as not to conflict with the // main 3D or 2D camera, which will have subview index 0. let retained_view_entity = - RetainedViewEntity::new(main_entity.into(), None, UI_CAMERA_SUBVIEW); + RetainedViewEntity::new(main_entity.into(), None, None, UI_CAMERA_SUBVIEW); // Creates the UI view. let ui_camera_view = commands .spawn(( diff --git a/examples/shader_advanced/custom_render_phase.rs b/examples/shader_advanced/custom_render_phase.rs index 73c3e64a27bca..9f373f2ecfa5c 100644 --- a/examples/shader_advanced/custom_render_phase.rs +++ b/examples/shader_advanced/custom_render_phase.rs @@ -508,7 +508,7 @@ fn extract_camera_phases( continue; } // This is the main camera, so we use the first subview index (0) - let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); + let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, None, 0); stencil_phases.prepare_for_new_frame(retained_view_entity); live_entities.insert(retained_view_entity);