diff --git a/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs index 0c6a7feddb..f324193983 100644 --- a/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs @@ -14,7 +14,7 @@ use bevy_ecs::{ component::Component, entity::Entity, prelude::{resource_exists, Without}, - query::{QueryItem, With}, + query::{Or, QueryState, With}, resource::Resource, schedule::IntoSystemConfigs as _, system::{lifetimeless::Read, Commands, Local, Query, Res, ResMut}, @@ -22,8 +22,10 @@ use bevy_ecs::{ }; use bevy_math::{uvec2, UVec2, Vec4Swizzles as _}; use bevy_render::{ - experimental::occlusion_culling::OcclusionCulling, - render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner}, + experimental::occlusion_culling::{ + OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities, + }, + render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext}, render_resource::{ binding_types::{sampler, texture_2d, texture_2d_multisampled, texture_storage_2d}, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, @@ -41,12 +43,9 @@ use bevy_render::{ }; use bitflags::bitflags; -use crate::{ - core_3d::{ - graph::{Core3d, Node3d}, - prepare_core_3d_depth_textures, - }, - prepass::DepthPrepass, +use crate::core_3d::{ + graph::{Core3d, Node3d}, + prepare_core_3d_depth_textures, }; /// Identifies the `downsample_depth.wgsl` shader. @@ -81,14 +80,8 @@ impl Plugin for MipGenerationPlugin { render_app .init_resource::>() - .add_render_graph_node::>( - Core3d, - Node3d::EarlyDownsampleDepth, - ) - .add_render_graph_node::>( - Core3d, - Node3d::LateDownsampleDepth, - ) + .add_render_graph_node::(Core3d, Node3d::EarlyDownsampleDepth) + .add_render_graph_node::(Core3d, Node3d::LateDownsampleDepth) .add_render_graph_edges( Core3d, ( @@ -137,7 +130,7 @@ impl Plugin for MipGenerationPlugin { /// /// This runs the single-pass downsampling (SPD) shader with the *min* filter in /// order to generate a series of mipmaps for the Z buffer. The resulting -/// hierarchical Z buffer can be used for occlusion culling. +/// hierarchical Z-buffer can be used for occlusion culling. /// /// There are two instances of this node. The *early* downsample depth pass is /// the first hierarchical Z-buffer stage, which runs after the early prepass @@ -148,79 +141,150 @@ impl Plugin for MipGenerationPlugin { /// of the *next* frame will perform. /// /// This node won't do anything if occlusion culling isn't on. -#[derive(Default)] -pub struct DownsampleDepthNode; - -impl ViewNode for DownsampleDepthNode { - type ViewQuery = ( +pub struct DownsampleDepthNode { + /// The query that we use to find views that need occlusion culling for + /// their Z-buffer. + main_view_query: QueryState<( Read, Read, Read, - ); + Option>, + )>, + /// The query that we use to find shadow maps that need occlusion culling. + shadow_view_query: QueryState<( + Read, + Read, + Read, + )>, +} + +impl FromWorld for DownsampleDepthNode { + fn from_world(world: &mut World) -> Self { + Self { + main_view_query: QueryState::new(world), + shadow_view_query: QueryState::new(world), + } + } +} + +impl Node for DownsampleDepthNode { + fn update(&mut self, world: &mut World) { + self.main_view_query.update_archetypes(world); + self.shadow_view_query.update_archetypes(world); + } fn run<'w>( &self, render_graph_context: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (view_depth_pyramid, view_downsample_depth_bind_group, view_depth_texture): QueryItem< - 'w, - Self::ViewQuery, - >, world: &'w World, ) -> Result<(), NodeRunError> { - // Produce a depth pyramid from the current depth buffer for a single - // view. The resulting depth pyramid can be used for occlusion testing. - - let downsample_depth_pipelines = world.resource::(); - let pipeline_cache = world.resource::(); - - // Despite the name "single-pass downsampling", we actually need two - // passes because of the lack of `coherent` buffers in WGPU/WGSL. - // Between each pass, there's an implicit synchronization barrier. - - // Fetch the appropriate pipeline ID, depending on whether the depth - // buffer is multisampled or not. - let (Some(first_downsample_depth_pipeline_id), Some(second_downsample_depth_pipeline_id)) = - (if view_depth_texture.texture.sample_count() > 1 { - ( - downsample_depth_pipelines.first_multisample.pipeline_id, - downsample_depth_pipelines.second_multisample.pipeline_id, - ) - } else { - ( - downsample_depth_pipelines.first.pipeline_id, - downsample_depth_pipelines.second.pipeline_id, - ) - }) + let Ok(( + view_depth_pyramid, + view_downsample_depth_bind_group, + view_depth_texture, + maybe_view_light_entities, + )) = self + .main_view_query + .get_manual(world, render_graph_context.view_entity()) else { return Ok(()); }; - // Fetch the pipelines for the two passes. - let (Some(first_downsample_depth_pipeline), Some(second_downsample_depth_pipeline)) = ( - pipeline_cache.get_compute_pipeline(first_downsample_depth_pipeline_id), - pipeline_cache.get_compute_pipeline(second_downsample_depth_pipeline_id), - ) else { - return Ok(()); - }; - - // Run the depth downsampling. - let view_size = uvec2( - view_depth_texture.texture.width(), - view_depth_texture.texture.height(), - ); - view_depth_pyramid.downsample_depth( - &format!("{:?}", render_graph_context.label()), + // Downsample depth for the main Z-buffer. + downsample_depth( + render_graph_context, render_context, - view_size, + world, + view_depth_pyramid, view_downsample_depth_bind_group, - first_downsample_depth_pipeline, - second_downsample_depth_pipeline, - ); + uvec2( + view_depth_texture.texture.width(), + view_depth_texture.texture.height(), + ), + view_depth_texture.texture.sample_count(), + )?; + + // Downsample depth for shadow maps that have occlusion culling enabled. + if let Some(view_light_entities) = maybe_view_light_entities { + for &view_light_entity in &view_light_entities.0 { + let Ok((view_depth_pyramid, view_downsample_depth_bind_group, occlusion_culling)) = + self.shadow_view_query.get_manual(world, view_light_entity) + else { + continue; + }; + downsample_depth( + render_graph_context, + render_context, + world, + view_depth_pyramid, + view_downsample_depth_bind_group, + UVec2::splat(occlusion_culling.depth_texture_size), + 1, + )?; + } + } + Ok(()) } } +/// Produces a depth pyramid from the current depth buffer for a single view. +/// The resulting depth pyramid can be used for occlusion testing. +fn downsample_depth<'w>( + render_graph_context: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + world: &'w World, + view_depth_pyramid: &ViewDepthPyramid, + view_downsample_depth_bind_group: &ViewDownsampleDepthBindGroup, + view_size: UVec2, + sample_count: u32, +) -> Result<(), NodeRunError> { + let downsample_depth_pipelines = world.resource::(); + let pipeline_cache = world.resource::(); + + // Despite the name "single-pass downsampling", we actually need two + // passes because of the lack of `coherent` buffers in WGPU/WGSL. + // Between each pass, there's an implicit synchronization barrier. + + // Fetch the appropriate pipeline ID, depending on whether the depth + // buffer is multisampled or not. + let (Some(first_downsample_depth_pipeline_id), Some(second_downsample_depth_pipeline_id)) = + (if sample_count > 1 { + ( + downsample_depth_pipelines.first_multisample.pipeline_id, + downsample_depth_pipelines.second_multisample.pipeline_id, + ) + } else { + ( + downsample_depth_pipelines.first.pipeline_id, + downsample_depth_pipelines.second.pipeline_id, + ) + }) + else { + return Ok(()); + }; + + // Fetch the pipelines for the two passes. + let (Some(first_downsample_depth_pipeline), Some(second_downsample_depth_pipeline)) = ( + pipeline_cache.get_compute_pipeline(first_downsample_depth_pipeline_id), + pipeline_cache.get_compute_pipeline(second_downsample_depth_pipeline_id), + ) else { + return Ok(()); + }; + + // Run the depth downsampling. + view_depth_pyramid.downsample_depth( + &format!("{:?}", render_graph_context.label()), + render_context, + view_size, + view_downsample_depth_bind_group, + first_downsample_depth_pipeline, + second_downsample_depth_pipeline, + ); + Ok(()) +} + /// A single depth downsample pipeline. #[derive(Resource)] pub struct DownsampleDepthPipeline { @@ -641,19 +705,12 @@ impl ViewDepthPyramid { } /// Creates depth pyramids for views that have occlusion culling enabled. -fn prepare_view_depth_pyramids( +pub fn prepare_view_depth_pyramids( mut commands: Commands, render_device: Res, mut texture_cache: ResMut, depth_pyramid_dummy_texture: Res, - views: Query< - (Entity, &ExtractedView), - ( - With, - Without, - With, - ), - >, + views: Query<(Entity, &ExtractedView), (With, Without)>, ) { for (view_entity, view) in &views { commands.entity(view_entity).insert(ViewDepthPyramid::new( @@ -680,10 +737,21 @@ fn prepare_downsample_depth_view_bind_groups( mut commands: Commands, render_device: Res, downsample_depth_pipelines: Res, - view_depth_textures: Query<(Entity, &ViewDepthPyramid, &ViewDepthTexture)>, + view_depth_textures: Query< + ( + Entity, + &ViewDepthPyramid, + Option<&ViewDepthTexture>, + Option<&OcclusionCullingSubview>, + ), + Or<(With, With)>, + >, ) { - for (view_entity, view_depth_pyramid, view_depth_texture) in &view_depth_textures { - let is_multisampled = view_depth_texture.texture.sample_count() > 1; + for (view_entity, view_depth_pyramid, view_depth_texture, shadow_occlusion_culling) in + &view_depth_textures + { + let is_multisampled = view_depth_texture + .is_some_and(|view_depth_texture| view_depth_texture.texture.sample_count() > 1); commands .entity(view_entity) .insert(ViewDownsampleDepthBindGroup( @@ -701,7 +769,13 @@ fn prepare_downsample_depth_view_bind_groups( } else { &downsample_depth_pipelines.first.bind_group_layout }, - view_depth_texture.view(), + match (view_depth_texture, shadow_occlusion_culling) { + (Some(view_depth_texture), _) => view_depth_texture.view(), + (None, Some(shadow_occlusion_culling)) => { + &shadow_occlusion_culling.depth_texture_view + } + (None, None) => panic!("Should never happen"), + }, &downsample_depth_pipelines.sampler, ), )); diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 4914060d08..543a4665fc 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -87,22 +87,36 @@ pub mod prelude { pub mod graph { use bevy_render::render_graph::RenderLabel; + /// Render graph nodes specific to 3D PBR rendering. #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] pub enum NodePbr { - /// Label for the shadow pass node. - ShadowPass, + /// Label for the shadow pass node that draws meshes that were visible + /// from the light last frame. + EarlyShadowPass, + /// Label for the shadow pass node that draws meshes that became visible + /// from the light this frame. + LateShadowPass, /// Label for the screen space ambient occlusion render node. ScreenSpaceAmbientOcclusion, DeferredLightingPass, /// Label for the volumetric lighting pass. VolumetricFog, - /// Label for the compute shader instance data building pass. + /// Label for the shader that transforms and culls meshes that were + /// visible last frame. EarlyGpuPreprocess, + /// Label for the shader that transforms and culls meshes that became + /// visible this frame. LateGpuPreprocess, /// Label for the screen space reflections pass. ScreenSpaceReflections, + /// Label for the node that builds indirect draw parameters for meshes + /// that were visible last frame. EarlyPrepassBuildIndirectParameters, + /// Label for the node that builds indirect draw parameters for meshes + /// that became visible this frame. LatePrepassBuildIndirectParameters, + /// Label for the node that builds indirect draw parameters for the main + /// rendering pass, containing all meshes that are visible this frame. MainBuildIndirectParameters, ClearIndirectParametersMetadata, } @@ -473,11 +487,17 @@ impl Plugin for PbrPlugin { .add_observer(remove_light_view_entities); render_app.world_mut().add_observer(extracted_light_removed); - let shadow_pass_node = ShadowPassNode::new(render_app.world_mut()); + let early_shadow_pass_node = EarlyShadowPassNode::from_world(render_app.world_mut()); + let late_shadow_pass_node = LateShadowPassNode::from_world(render_app.world_mut()); let mut graph = render_app.world_mut().resource_mut::(); let draw_3d_graph = graph.get_sub_graph_mut(Core3d).unwrap(); - draw_3d_graph.add_node(NodePbr::ShadowPass, shadow_pass_node); - draw_3d_graph.add_node_edge(NodePbr::ShadowPass, Node3d::StartMainPass); + draw_3d_graph.add_node(NodePbr::EarlyShadowPass, early_shadow_pass_node); + draw_3d_graph.add_node(NodePbr::LateShadowPass, late_shadow_pass_node); + draw_3d_graph.add_node_edges(( + NodePbr::EarlyShadowPass, + NodePbr::LateShadowPass, + Node3d::StartMainPass, + )); } fn finish(&self, app: &mut App) { diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index 0333be74fe..4057f29e39 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -253,7 +253,7 @@ impl Plugin for MeshletPlugin { Core3d, ( NodeMeshlet::VisibilityBufferRasterPass, - NodePbr::ShadowPass, + NodePbr::EarlyShadowPass, // NodeMeshlet::Prepass, // diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 80035a31cc..fbb54cb1e2 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -20,7 +20,7 @@ use bevy_ecs::{ component::Component, entity::Entity, prelude::resource_exists, - query::{Has, QueryState, With, Without}, + query::{Has, Or, QueryState, With, Without}, resource::Resource, schedule::IntoSystemConfigs as _, system::{lifetimeless::Read, Commands, Query, Res, ResMut}, @@ -61,7 +61,7 @@ use crate::{ graph::NodePbr, MeshCullingData, MeshCullingDataBuffer, MeshInputUniform, MeshUniform, }; -use super::ViewLightEntities; +use super::{ShadowView, ViewLightEntities}; /// The handle to the `mesh_preprocess.wgsl` compute shader. pub const MESH_PREPROCESS_SHADER_HANDLE: Handle = @@ -154,7 +154,7 @@ pub struct EarlyPrepassBuildIndirectParametersNode { ( Without, Without, - With, + Or<(With, With)>, ), >, } @@ -173,7 +173,7 @@ pub struct LatePrepassBuildIndirectParametersNode { ( Without, Without, - With, + Or<(With, With)>, With, ), >, @@ -528,11 +528,22 @@ impl Plugin for GpuMeshPreprocessPlugin { Node3d::LatePrepass, Node3d::LateDeferredPrepass, NodePbr::MainBuildIndirectParameters, - // Shadows don't currently support occlusion culling, so we - // treat shadows as effectively the main phase for our - // purposes. - NodePbr::ShadowPass, + Node3d::StartMainPass, ), + ).add_render_graph_edges( + Core3d, + ( + NodePbr::EarlyPrepassBuildIndirectParameters, + NodePbr::EarlyShadowPass, + Node3d::EarlyDownsampleDepth, + ) + ).add_render_graph_edges( + Core3d, + ( + NodePbr::LatePrepassBuildIndirectParameters, + NodePbr::LateShadowPass, + NodePbr::MainBuildIndirectParameters, + ) ); } } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 6b00636523..5dc225d466 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -15,6 +15,9 @@ use bevy_ecs::{ use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_platform_support::collections::{HashMap, HashSet}; use bevy_platform_support::hash::FixedHasher; +use bevy_render::experimental::occlusion_culling::{ + OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities, +}; use bevy_render::sync_world::MainEntityHashMap; use bevy_render::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, @@ -82,6 +85,8 @@ pub struct ExtractedDirectionalLight { pub frusta: EntityHashMap>, pub render_layers: RenderLayers, pub soft_shadow_size: Option, + /// True if this light is using two-phase occlusion culling. + pub occlusion_culling: bool, } // NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl! @@ -254,6 +259,7 @@ pub fn extract_lights( &ViewVisibility, Option<&RenderLayers>, Option<&VolumetricLight>, + Has, ), Without, >, @@ -418,6 +424,7 @@ pub fn extract_lights( view_visibility, maybe_layers, volumetric_light, + occlusion_culling, ) in &directional_lights { if !view_visibility.get() { @@ -483,6 +490,7 @@ pub fn extract_lights( cascades: extracted_cascades, frusta: extracted_frusta, render_layers: maybe_layers.unwrap_or_default().clone(), + occlusion_culling, }, RenderCascadesVisibleEntities { entities: cascade_visible_entities, @@ -1148,7 +1156,9 @@ pub fn prepare_lights( .filter_map(|sorted_camera| views.get(sorted_camera.entity).ok()) { live_views.insert(entity); + let mut view_lights = Vec::new(); + let mut view_occlusion_culling_lights = Vec::new(); let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing { GpuPreprocessingMode::Culling @@ -1494,7 +1504,7 @@ pub fn prepare_lights( // NOTE: For point and spotlights, we reuse the same depth attachment for all views. // However, for directional lights, we want a new depth attachment for each view, // so that the view is cleared for each view. - let depth_attachment = DepthAttachment::new(depth_texture_view, Some(0.0)); + let depth_attachment = DepthAttachment::new(depth_texture_view.clone(), Some(0.0)); directional_depth_texture_array_index += 1; @@ -1543,6 +1553,18 @@ pub fn prepare_lights( view_lights.push(view_light_entity); + // If this light is using occlusion culling, add the appropriate components. + if light.occlusion_culling { + commands.entity(view_light_entity).insert(( + OcclusionCulling, + OcclusionCullingSubview { + depth_texture_view, + depth_texture_size: directional_light_shadow_map.size as u32, + }, + )); + view_occlusion_culling_lights.push(view_light_entity); + } + // Subsequent views with the same light entity will **NOT** reuse the same shadow map // (Because the cascades are unique to each view) // TODO: Implement GPU culling for shadow passes. @@ -1566,6 +1588,16 @@ pub fn prepare_lights( offset: view_gpu_lights_writer.write(&gpu_lights), }, )); + + // Make a link from the camera to all shadow cascades with occlusion + // culling enabled. + if !view_occlusion_culling_lights.is_empty() { + commands + .entity(entity) + .insert(OcclusionCullingSubviewEntities( + view_occlusion_culling_lights, + )); + } } // Despawn light-view entities for views that no longer exist @@ -2115,13 +2147,44 @@ impl CachedRenderPipelinePhaseItem for Shadow { } } +/// The rendering node that renders meshes that were "visible" (so to speak) +/// from a light last frame. +/// +/// If occlusion culling for a light is disabled, then this node simply renders +/// all meshes in range of the light. +#[derive(Deref, DerefMut)] +pub struct EarlyShadowPassNode(ShadowPassNode); + +/// The rendering node that renders meshes that became newly "visible" (so to +/// speak) from a light this frame. +/// +/// If occlusion culling for a light is disabled, then this node does nothing. +#[derive(Deref, DerefMut)] +pub struct LateShadowPassNode(ShadowPassNode); + +/// Encapsulates rendering logic shared between the early and late shadow pass +/// nodes. pub struct ShadowPassNode { + /// The query that finds cameras in which shadows are visible. main_view_query: QueryState>, - view_light_query: QueryState<(Read, Read)>, + /// The query that finds shadow cascades. + view_light_query: QueryState<(Read, Read, Has)>, } -impl ShadowPassNode { - pub fn new(world: &mut World) -> Self { +impl FromWorld for EarlyShadowPassNode { + fn from_world(world: &mut World) -> Self { + Self(ShadowPassNode::from_world(world)) + } +} + +impl FromWorld for LateShadowPassNode { + fn from_world(world: &mut World) -> Self { + Self(ShadowPassNode::from_world(world)) + } +} + +impl FromWorld for ShadowPassNode { + fn from_world(world: &mut World) -> Self { Self { main_view_query: QueryState::new(world), view_light_query: QueryState::new(world), @@ -2129,10 +2192,9 @@ impl ShadowPassNode { } } -impl Node for ShadowPassNode { +impl Node for EarlyShadowPassNode { fn update(&mut self, world: &mut World) { - self.main_view_query.update_archetypes(world); - self.view_light_query.update_archetypes(world); + self.0.update(world); } fn run<'w>( @@ -2140,6 +2202,42 @@ impl Node for ShadowPassNode { graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, world: &'w World, + ) -> Result<(), NodeRunError> { + self.0.run(graph, render_context, world, false) + } +} + +impl Node for LateShadowPassNode { + fn update(&mut self, world: &mut World) { + self.0.update(world); + } + + fn run<'w>( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + world: &'w World, + ) -> Result<(), NodeRunError> { + self.0.run(graph, render_context, world, true) + } +} + +impl ShadowPassNode { + fn update(&mut self, world: &mut World) { + self.main_view_query.update_archetypes(world); + self.view_light_query.update_archetypes(world); + } + + /// Runs the node logic. + /// + /// `is_late` is true if this is the late shadow pass or false if this is + /// the early shadow pass. + fn run<'w>( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + world: &'w World, + is_late: bool, ) -> Result<(), NodeRunError> { let diagnostics = render_context.diagnostic_recorder(); @@ -2154,12 +2252,18 @@ impl Node for ShadowPassNode { if let Ok(view_lights) = self.main_view_query.get_manual(world, view_entity) { for view_light_entity in view_lights.lights.iter().copied() { - let Ok((view_light, extracted_light_view)) = + let Ok((view_light, extracted_light_view, occlusion_culling)) = self.view_light_query.get_manual(world, view_light_entity) else { continue; }; + // There's no need for a late shadow pass if the light isn't + // using occlusion culling. + if is_late && !occlusion_culling { + continue; + } + let Some(shadow_phase) = shadow_render_phases.get(&extracted_light_view.retained_view_entity) else { diff --git a/crates/bevy_render/src/experimental/occlusion_culling/mod.rs b/crates/bevy_render/src/experimental/occlusion_culling/mod.rs index 82f299d3a0..6bdfb4a102 100644 --- a/crates/bevy_render/src/experimental/occlusion_culling/mod.rs +++ b/crates/bevy_render/src/experimental/occlusion_culling/mod.rs @@ -5,10 +5,13 @@ use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, weak_handle, Handle}; -use bevy_ecs::{component::Component, prelude::ReflectComponent}; +use bevy_ecs::{component::Component, entity::Entity, prelude::ReflectComponent}; use bevy_reflect::{prelude::ReflectDefault, Reflect}; -use crate::{extract_component::ExtractComponent, render_resource::Shader}; +use crate::{ + extract_component::ExtractComponent, + render_resource::{Shader, TextureView}, +}; /// The handle to the `mesh_preprocess_types.wgsl` compute shader. pub const MESH_PREPROCESS_TYPES_SHADER_HANDLE: Handle = @@ -85,3 +88,29 @@ impl Plugin for OcclusionCullingPlugin { #[derive(Component, ExtractComponent, Clone, Copy, Default, Reflect)] #[reflect(Component, Default)] pub struct OcclusionCulling; + +/// A render-world component that contains resources necessary to perform +/// occlusion culling on any view other than a camera. +/// +/// Bevy automatically places this component on views created for shadow +/// mapping. You don't ordinarily need to add this component yourself. +#[derive(Clone, Component)] +pub struct OcclusionCullingSubview { + /// A texture view of the Z-buffer. + pub depth_texture_view: TextureView, + /// The size of the texture along both dimensions. + /// + /// Because [`OcclusionCullingSubview`] is only currently used for shadow + /// maps, they're guaranteed to have sizes equal to a power of two, so we + /// don't have to store the two dimensions individually here. + pub depth_texture_size: u32, +} + +/// A render-world component placed on each camera that stores references to all +/// entities other than cameras that need occlusion culling. +/// +/// Bevy automatically places this component on cameras that are drawing +/// shadows, when those shadows come from lights with occlusion culling enabled. +/// You don't ordinarily need to add this component yourself. +#[derive(Clone, Component)] +pub struct OcclusionCullingSubviewEntities(pub Vec); diff --git a/examples/tools/scene_viewer/main.rs b/examples/tools/scene_viewer/main.rs index 37ef81dbc6..0266a22ee0 100644 --- a/examples/tools/scene_viewer/main.rs +++ b/examples/tools/scene_viewer/main.rs @@ -209,10 +209,13 @@ fn setup_scene_after_load( // Spawn a default light if the scene does not have one if !scene_handle.has_light || args.add_light == Some(true) { info!("Spawning a directional light"); - commands.spawn(( + let mut light = commands.spawn(( DirectionalLight::default(), Transform::from_xyz(1.0, 1.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y), )); + if args.occlusion_culling == Some(true) { + light.insert(OcclusionCulling); + } scene_handle.has_light = true; }