diff --git a/pipelined/bevy_pbr2/src/light.rs b/pipelined/bevy_pbr2/src/light.rs index 65917d2aa5..5ff7e295bd 100644 --- a/pipelined/bevy_pbr2/src/light.rs +++ b/pipelined/bevy_pbr2/src/light.rs @@ -33,6 +33,7 @@ pub struct PointLight { pub intensity: f32, pub range: f32, pub radius: f32, + pub shadows_enabled: bool, pub shadow_depth_bias: f32, /// A bias applied along the direction of the fragment's surface normal. It is scaled to the /// shadow map's texel size so that it can be small close to the camera and gets larger further @@ -48,6 +49,7 @@ impl Default for PointLight { intensity: 800.0, // Roughly a 60W non-halogen incandescent bulb range: 20.0, radius: 0.0, + shadows_enabled: false, shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS, shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS, } @@ -101,6 +103,7 @@ pub struct DirectionalLight { pub color: Color, /// Illuminance in lux pub illuminance: f32, + pub shadows_enabled: bool, pub shadow_projection: OrthographicProjection, pub shadow_depth_bias: f32, /// A bias applied along the direction of the fragment's surface normal. It is scaled to the @@ -114,6 +117,7 @@ impl Default for DirectionalLight { DirectionalLight { color: Color::rgb(1.0, 1.0, 1.0), illuminance: 100000.0, + shadows_enabled: false, shadow_projection: OrthographicProjection { left: -size, right: size, @@ -178,6 +182,13 @@ pub fn update_directional_light_frusta( mut views: Query<(&GlobalTransform, &DirectionalLight, &mut Frustum)>, ) { for (transform, directional_light, mut frustum) in views.iter_mut() { + // The frustum is used for culling meshes to the light for shadow mapping + // so if shadow mapping is disabled for this light, then the frustum is + // not needed. + if !directional_light.shadows_enabled { + continue; + } + let view_projection = directional_light.shadow_projection.get_projection_matrix() * transform.compute_matrix().inverse(); *frustum = Frustum::from_view_projection( @@ -199,6 +210,13 @@ pub fn update_point_light_frusta( .collect::>(); for (transform, point_light, mut cubemap_frusta) in views.iter_mut() { + // The frusta are used for culling meshes to the light for shadow mapping + // so if shadow mapping is disabled for this light, then the frusta are + // not needed. + if !point_light.shadows_enabled { + continue; + } + // ignore scale because we don't want to effectively scale light radius and range // by applying those as a view transform to shadow map rendering of objects // and ignore rotation because we want the shadow map projections to align with the axes @@ -227,10 +245,12 @@ pub fn check_light_visibility( &mut CubemapVisibleEntities, Option<&RenderLayers>, )>, - mut directional_lights: Query< - (&Frustum, &mut VisibleEntities, Option<&RenderLayers>), - With, - >, + mut directional_lights: Query<( + &DirectionalLight, + &Frustum, + &mut VisibleEntities, + Option<&RenderLayers>, + )>, mut visible_entity_query: Query< ( Entity, @@ -244,8 +264,16 @@ pub fn check_light_visibility( >, ) { // Directonal lights - for (frustum, mut visible_entities, maybe_view_mask) in directional_lights.iter_mut() { + for (directional_light, frustum, mut visible_entities, maybe_view_mask) in + directional_lights.iter_mut() + { visible_entities.entities.clear(); + + // NOTE: If shadow mapping is disabled for the light then it must have no visible entities + if !directional_light.shadows_enabled { + continue; + } + let view_mask = maybe_view_mask.copied().unwrap_or_default(); for ( @@ -288,6 +316,12 @@ pub fn check_light_visibility( for visible_entities in cubemap_visible_entities.iter_mut() { visible_entities.entities.clear(); } + + // NOTE: If shadow mapping is disabled for the light then it must have no visible entities + if !point_light.shadows_enabled { + continue; + } + let view_mask = maybe_view_mask.copied().unwrap_or_default(); let light_sphere = Sphere { center: transform.translation, diff --git a/pipelined/bevy_pbr2/src/render/light.rs b/pipelined/bevy_pbr2/src/render/light.rs index 211efb8df1..c145563a1e 100644 --- a/pipelined/bevy_pbr2/src/render/light.rs +++ b/pipelined/bevy_pbr2/src/render/light.rs @@ -50,6 +50,7 @@ pub struct ExtractedPointLight { range: f32, radius: f32, transform: GlobalTransform, + shadows_enabled: bool, shadow_depth_bias: f32, shadow_normal_bias: f32, } @@ -61,6 +62,7 @@ pub struct ExtractedDirectionalLight { illuminance: f32, direction: Vec3, projection: Mat4, + shadows_enabled: bool, shadow_depth_bias: f32, shadow_normal_bias: f32, } @@ -77,20 +79,42 @@ pub struct GpuPointLight { radius: f32, near: f32, far: f32, + flags: u32, shadow_depth_bias: f32, shadow_normal_bias: f32, } +// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.frag! +bitflags::bitflags! { + #[repr(transparent)] + struct PointLightFlags: u32 { + const SHADOWS_ENABLED = (1 << 0); + const NONE = 0; + const UNINITIALIZED = 0xFFFF; + } +} + #[repr(C)] #[derive(Copy, Clone, AsStd140, Default, Debug)] pub struct GpuDirectionalLight { view_projection: Mat4, color: Vec4, dir_to_light: Vec3, + flags: u32, shadow_depth_bias: f32, shadow_normal_bias: f32, } +// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.frag! +bitflags::bitflags! { + #[repr(transparent)] + struct DirectionalLightFlags: u32 { + const SHADOWS_ENABLED = (1 << 0); + const NONE = 0; + const UNINITIALIZED = 0xFFFF; + } +} + #[repr(C)] #[derive(Copy, Clone, Debug, AsStd140)] pub struct GpuLights { @@ -328,6 +352,7 @@ pub fn extract_lights( range: point_light.range, radius: point_light.radius, transform: *transform, + shadows_enabled: point_light.shadows_enabled, shadow_depth_bias: point_light.shadow_depth_bias, // The factor of SQRT_2 is for the worst-case diagonal offset shadow_normal_bias: point_light.shadow_normal_bias @@ -357,6 +382,7 @@ pub fn extract_lights( illuminance: directional_light.illuminance, direction: transform.forward(), projection: directional_light.shadow_projection.get_projection_matrix(), + shadows_enabled: directional_light.shadows_enabled, shadow_depth_bias: directional_light.shadow_depth_bias, // The factor of SQRT_2 is for the worst-case diagonal offset shadow_normal_bias: directional_light.shadow_normal_bias @@ -424,18 +450,24 @@ fn face_index_to_name(face_index: usize) -> &'static str { } } -pub struct ViewLight { +pub struct ShadowView { pub depth_texture_view: TextureView, pub pass_name: String, } -pub struct ViewLights { +pub struct ViewShadowBindings { pub point_light_depth_texture: Texture, pub point_light_depth_texture_view: TextureView, pub directional_light_depth_texture: Texture, pub directional_light_depth_texture_view: TextureView, +} + +pub struct ViewLightEntities { pub lights: Vec, - pub gpu_light_binding_index: u32, +} + +pub struct ViewLightsUniformOffset { + pub offset: u32, } #[derive(Default)] @@ -524,54 +556,59 @@ pub fn prepare_lights( }; // TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query - for (light_index, (light_entity, light)) in - point_lights.iter().enumerate().take(MAX_POINT_LIGHTS) - { + for (light_index, (light_entity, light)) in point_lights.iter().enumerate() { // ignore scale because we don't want to effectively scale light radius and range // by applying those as a view transform to shadow map rendering of objects // and ignore rotation because we want the shadow map projections to align with the axes let view_translation = GlobalTransform::from_translation(light.transform.translation); - for (face_index, view_rotation) in cube_face_rotations.iter().enumerate() { - let depth_texture_view = - point_light_depth_texture - .texture - .create_view(&TextureViewDescriptor { - label: Some("point_light_shadow_map_texture_view"), - format: None, - dimension: Some(TextureViewDimension::D2), - aspect: TextureAspect::All, - base_mip_level: 0, - mip_level_count: None, - base_array_layer: (light_index * 6 + face_index) as u32, - array_layer_count: NonZeroU32::new(1), - }); + if light.shadows_enabled { + for (face_index, view_rotation) in cube_face_rotations.iter().enumerate() { + let depth_texture_view = + point_light_depth_texture + .texture + .create_view(&TextureViewDescriptor { + label: Some("point_light_shadow_map_texture_view"), + format: None, + dimension: Some(TextureViewDimension::D2), + aspect: TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: (light_index * 6 + face_index) as u32, + array_layer_count: NonZeroU32::new(1), + }); - let view_light_entity = commands - .spawn() - .insert_bundle(( - ViewLight { - depth_texture_view, - pass_name: format!( - "shadow pass point light {} {}", - light_index, - face_index_to_name(face_index) - ), - }, - ExtractedView { - width: point_light_shadow_map.size as u32, - height: point_light_shadow_map.size as u32, - transform: view_translation * *view_rotation, - projection: cube_face_projection, - }, - RenderPhase::::default(), - LightEntity::Point { - light_entity, - face_index, - }, - )) - .id(); - view_lights.push(view_light_entity); + let view_light_entity = commands + .spawn() + .insert_bundle(( + ShadowView { + depth_texture_view, + pass_name: format!( + "shadow pass point light {} {}", + light_index, + face_index_to_name(face_index) + ), + }, + ExtractedView { + width: point_light_shadow_map.size as u32, + height: point_light_shadow_map.size as u32, + transform: view_translation * *view_rotation, + projection: cube_face_projection, + }, + RenderPhase::::default(), + LightEntity::Point { + light_entity, + face_index, + }, + )) + .id(); + view_lights.push(view_light_entity); + } + } + + let mut flags = PointLightFlags::NONE; + if light.shadows_enabled { + flags |= PointLightFlags::SHADOWS_ENABLED; } gpu_lights.point_lights[light_index] = GpuPointLight { @@ -584,6 +621,7 @@ pub fn prepare_lights( inverse_square_range: 1.0 / (light.range * light.range), near: 0.1, far: light.range, + flags: flags.bits, shadow_depth_bias: light.shadow_depth_bias, shadow_normal_bias: light.shadow_normal_bias, }; @@ -616,6 +654,11 @@ pub fn prepare_lights( // NOTE: This orthographic projection defines the volume within which shadows from a directional light can be cast let projection = light.projection; + let mut flags = DirectionalLightFlags::NONE; + if light.shadows_enabled { + flags |= DirectionalLightFlags::SHADOWS_ENABLED; + } + gpu_lights.directional_lights[i] = GpuDirectionalLight { // premultiply color by intensity // we don't use the alpha at all, so no reason to multiply only [0..3] @@ -623,42 +666,45 @@ pub fn prepare_lights( dir_to_light, // NOTE: * view is correct, it should not be view.inverse() here view_projection: projection * view, + flags: flags.bits, shadow_depth_bias: light.shadow_depth_bias, shadow_normal_bias: light.shadow_normal_bias, }; - let depth_texture_view = - directional_light_depth_texture - .texture - .create_view(&TextureViewDescriptor { - label: Some("directional_light_shadow_map_texture_view"), - format: None, - dimension: Some(TextureViewDimension::D2), - aspect: TextureAspect::All, - base_mip_level: 0, - mip_level_count: None, - base_array_layer: i as u32, - array_layer_count: NonZeroU32::new(1), - }); + if light.shadows_enabled { + let depth_texture_view = + directional_light_depth_texture + .texture + .create_view(&TextureViewDescriptor { + label: Some("directional_light_shadow_map_texture_view"), + format: None, + dimension: Some(TextureViewDimension::D2), + aspect: TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: i as u32, + array_layer_count: NonZeroU32::new(1), + }); - let view_light_entity = commands - .spawn() - .insert_bundle(( - ViewLight { - depth_texture_view, - pass_name: format!("shadow pass directional light {}", i), - }, - ExtractedView { - width: directional_light_shadow_map.size as u32, - height: directional_light_shadow_map.size as u32, - transform: GlobalTransform::from_matrix(view.inverse()), - projection, - }, - RenderPhase::::default(), - LightEntity::Directional { light_entity }, - )) - .id(); - view_lights.push(view_light_entity); + let view_light_entity = commands + .spawn() + .insert_bundle(( + ShadowView { + depth_texture_view, + pass_name: format!("shadow pass directional light {}", i), + }, + ExtractedView { + width: directional_light_shadow_map.size as u32, + height: directional_light_shadow_map.size as u32, + transform: GlobalTransform::from_matrix(view.inverse()), + projection, + }, + RenderPhase::::default(), + LightEntity::Directional { light_entity }, + )) + .id(); + view_lights.push(view_light_entity); + } } let point_light_depth_texture_view = point_light_depth_texture @@ -686,14 +732,20 @@ pub fn prepare_lights( array_layer_count: None, }); - commands.entity(entity).insert(ViewLights { - point_light_depth_texture: point_light_depth_texture.texture, - point_light_depth_texture_view, - directional_light_depth_texture: directional_light_depth_texture.texture, - directional_light_depth_texture_view, - lights: view_lights, - gpu_light_binding_index: light_meta.view_gpu_lights.push(gpu_lights), - }); + commands.entity(entity).insert_bundle(( + ViewShadowBindings { + point_light_depth_texture: point_light_depth_texture.texture, + point_light_depth_texture_view, + directional_light_depth_texture: directional_light_depth_texture.texture, + directional_light_depth_texture_view, + }, + ViewLightEntities { + lights: view_lights, + }, + ViewLightsUniformOffset { + offset: light_meta.view_gpu_lights.push(gpu_lights), + }, + )); } light_meta @@ -728,12 +780,12 @@ pub fn queue_shadows( render_meshes: Res>, mut pipelines: ResMut>, mut pipeline_cache: ResMut, - mut view_lights: Query<&ViewLights>, + view_lights: Query<&ViewLightEntities>, mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase)>, point_light_entities: Query<&CubemapVisibleEntities, With>, directional_light_entities: Query<&VisibleEntities, With>, ) { - for view_lights in view_lights.iter_mut() { + for view_lights in view_lights.iter() { let draw_shadow_mesh = shadow_draw_functions .read() .get_id::() @@ -753,6 +805,8 @@ pub fn queue_shadows( .expect("Failed to get point light visible entities") .get(*face_index), }; + // NOTE: Lights with shadow mapping disabled will have no visible entities + // so no meshes will be queued for VisibleEntity { entity, .. } in visible_entities.iter() { let mut key = ShadowPipelineKey::empty(); if let Ok(mesh_handle) = casting_meshes.get(*entity) { @@ -811,8 +865,8 @@ impl CachedPipelinePhaseItem for Shadow { } pub struct ShadowPassNode { - main_view_query: QueryState<&'static ViewLights>, - view_light_query: QueryState<(&'static ViewLight, &'static RenderPhase)>, + main_view_query: QueryState<&'static ViewLightEntities>, + view_light_query: QueryState<(&'static ShadowView, &'static RenderPhase)>, } impl ShadowPassNode { diff --git a/pipelined/bevy_pbr2/src/render/mesh.rs b/pipelined/bevy_pbr2/src/render/mesh.rs index 8ea39af793..b684ae4932 100644 --- a/pipelined/bevy_pbr2/src/render/mesh.rs +++ b/pipelined/bevy_pbr2/src/render/mesh.rs @@ -1,4 +1,7 @@ -use crate::{LightMeta, NotShadowCaster, NotShadowReceiver, ShadowPipeline, ViewLights}; +use crate::{ + LightMeta, NotShadowCaster, NotShadowReceiver, ShadowPipeline, ViewLightsUniformOffset, + ViewShadowBindings, +}; use bevy_app::Plugin; use bevy_asset::{Assets, Handle, HandleUntyped}; use bevy_ecs::{ @@ -523,13 +526,13 @@ pub fn queue_mesh_view_bind_groups( shadow_pipeline: Res, light_meta: Res, view_uniforms: Res, - mut views: Query<(Entity, &ViewLights)>, + mut views: Query<(Entity, &ViewShadowBindings)>, ) { if let (Some(view_binding), Some(light_binding)) = ( view_uniforms.uniforms.binding(), light_meta.view_gpu_lights.binding(), ) { - for (entity, view_lights) in views.iter_mut() { + for (entity, view_shadow_bindings) in views.iter_mut() { let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor { entries: &[ BindGroupEntry { @@ -543,7 +546,7 @@ pub fn queue_mesh_view_bind_groups( BindGroupEntry { binding: 2, resource: BindingResource::TextureView( - &view_lights.point_light_depth_texture_view, + &view_shadow_bindings.point_light_depth_texture_view, ), }, BindGroupEntry { @@ -553,7 +556,7 @@ pub fn queue_mesh_view_bind_groups( BindGroupEntry { binding: 4, resource: BindingResource::TextureView( - &view_lights.directional_light_depth_texture_view, + &view_shadow_bindings.directional_light_depth_texture_view, ), }, BindGroupEntry { @@ -578,7 +581,7 @@ pub struct SetMeshViewBindGroup; impl EntityRenderCommand for SetMeshViewBindGroup { type Param = SQuery<( Read, - Read, + Read, Read, )>; #[inline] @@ -592,7 +595,7 @@ impl EntityRenderCommand for SetMeshViewBindGroup { pass.set_bind_group( I, &mesh_view_bind_group.value, - &[view_uniform.offset, view_lights.gpu_light_binding_index], + &[view_uniform.offset, view_lights.offset], ); RenderCommandResult::Success diff --git a/pipelined/bevy_pbr2/src/render/mesh_view_bind_group.wgsl b/pipelined/bevy_pbr2/src/render/mesh_view_bind_group.wgsl index f96dabd115..a9eb0f60d8 100644 --- a/pipelined/bevy_pbr2/src/render/mesh_view_bind_group.wgsl +++ b/pipelined/bevy_pbr2/src/render/mesh_view_bind_group.wgsl @@ -13,18 +13,26 @@ struct PointLight { radius: f32; near: f32; far: f32; + // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. + flags: u32; shadow_depth_bias: f32; shadow_normal_bias: f32; }; +let POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; + struct DirectionalLight { view_projection: mat4x4; color: vec4; direction_to_light: vec3; + // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. + flags: u32; shadow_depth_bias: f32; shadow_normal_bias: f32; }; +let DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; + [[block]] struct Lights { // NOTE: this array size must be kept in sync with the constants defined bevy_pbr2/src/render/light.rs @@ -47,4 +55,4 @@ var point_shadow_textures_sampler: sampler_comparison; [[group(0), binding(4)]] var directional_shadow_textures: texture_depth_2d_array; [[group(0), binding(5)]] -var directional_shadow_textures_sampler: sampler_comparison; \ No newline at end of file +var directional_shadow_textures_sampler: sampler_comparison; diff --git a/pipelined/bevy_pbr2/src/render/pbr.wgsl b/pipelined/bevy_pbr2/src/render/pbr.wgsl index eee5cbe2b4..bb5515657a 100644 --- a/pipelined/bevy_pbr2/src/render/pbr.wgsl +++ b/pipelined/bevy_pbr2/src/render/pbr.wgsl @@ -500,7 +500,8 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { for (var i: i32 = 0; i < n_point_lights; i = i + 1) { let light = lights.point_lights[i]; var shadow: f32; - if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u) { + if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u + || (light.flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { shadow = fetch_point_shadow(i, in.world_position, in.world_normal); } else { shadow = 1.0; @@ -511,7 +512,8 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { for (var i: i32 = 0; i < n_directional_lights; i = i + 1) { let light = lights.directional_lights[i]; var shadow: f32; - if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u) { + if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u + || (light.flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { shadow = fetch_directional_shadow(i, in.world_position, in.world_normal); } else { shadow = 1.0;