Per-light toggleable shadow mapping (#3126)

# Objective

Allow shadow mapping to be enabled/disabled per-light.

## Solution

- NOTE: This PR is on top of https://github.com/bevyengine/bevy/pull/3072
- Add `shadows_enabled` boolean property to `PointLight` and `DirectionalLight` components.
- Do not update the frusta for the light if shadows are disabled.
- Do not check for visible entities for the light if shadows are disabled.
- Do not fetch shadows for lights with shadows disabled.
- I reworked a few types for clarity: `ViewLight` -> `ShadowView`, the bulk of `ViewLights` members -> `ViewShadowBindings`, the entities Vec in `ViewLights` -> `ViewLightEntities`, the uniform offset in `ViewLights` for `GpuLights` -> `ViewLightsUniformOffset`

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Robert Swain 2021-11-19 21:16:58 +00:00
parent 2e79951659
commit a7729319cc
5 changed files with 204 additions and 103 deletions

View file

@ -33,6 +33,7 @@ pub struct PointLight {
pub intensity: f32, pub intensity: f32,
pub range: f32, pub range: f32,
pub radius: f32, pub radius: f32,
pub shadows_enabled: bool,
pub shadow_depth_bias: f32, pub shadow_depth_bias: f32,
/// A bias applied along the direction of the fragment's surface normal. It is scaled to the /// 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 /// 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 intensity: 800.0, // Roughly a 60W non-halogen incandescent bulb
range: 20.0, range: 20.0,
radius: 0.0, radius: 0.0,
shadows_enabled: false,
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS, shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS, shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
} }
@ -101,6 +103,7 @@ pub struct DirectionalLight {
pub color: Color, pub color: Color,
/// Illuminance in lux /// Illuminance in lux
pub illuminance: f32, pub illuminance: f32,
pub shadows_enabled: bool,
pub shadow_projection: OrthographicProjection, pub shadow_projection: OrthographicProjection,
pub shadow_depth_bias: f32, pub shadow_depth_bias: f32,
/// A bias applied along the direction of the fragment's surface normal. It is scaled to the /// 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 { DirectionalLight {
color: Color::rgb(1.0, 1.0, 1.0), color: Color::rgb(1.0, 1.0, 1.0),
illuminance: 100000.0, illuminance: 100000.0,
shadows_enabled: false,
shadow_projection: OrthographicProjection { shadow_projection: OrthographicProjection {
left: -size, left: -size,
right: size, right: size,
@ -178,6 +182,13 @@ pub fn update_directional_light_frusta(
mut views: Query<(&GlobalTransform, &DirectionalLight, &mut Frustum)>, mut views: Query<(&GlobalTransform, &DirectionalLight, &mut Frustum)>,
) { ) {
for (transform, directional_light, mut frustum) in views.iter_mut() { 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() let view_projection = directional_light.shadow_projection.get_projection_matrix()
* transform.compute_matrix().inverse(); * transform.compute_matrix().inverse();
*frustum = Frustum::from_view_projection( *frustum = Frustum::from_view_projection(
@ -199,6 +210,13 @@ pub fn update_point_light_frusta(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for (transform, point_light, mut cubemap_frusta) in views.iter_mut() { 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 // 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 // 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 // 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, &mut CubemapVisibleEntities,
Option<&RenderLayers>, Option<&RenderLayers>,
)>, )>,
mut directional_lights: Query< mut directional_lights: Query<(
(&Frustum, &mut VisibleEntities, Option<&RenderLayers>), &DirectionalLight,
With<DirectionalLight>, &Frustum,
>, &mut VisibleEntities,
Option<&RenderLayers>,
)>,
mut visible_entity_query: Query< mut visible_entity_query: Query<
( (
Entity, Entity,
@ -244,8 +264,16 @@ pub fn check_light_visibility(
>, >,
) { ) {
// Directonal lights // 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(); 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(); let view_mask = maybe_view_mask.copied().unwrap_or_default();
for ( for (
@ -288,6 +316,12 @@ pub fn check_light_visibility(
for visible_entities in cubemap_visible_entities.iter_mut() { for visible_entities in cubemap_visible_entities.iter_mut() {
visible_entities.entities.clear(); 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 view_mask = maybe_view_mask.copied().unwrap_or_default();
let light_sphere = Sphere { let light_sphere = Sphere {
center: transform.translation, center: transform.translation,

View file

@ -50,6 +50,7 @@ pub struct ExtractedPointLight {
range: f32, range: f32,
radius: f32, radius: f32,
transform: GlobalTransform, transform: GlobalTransform,
shadows_enabled: bool,
shadow_depth_bias: f32, shadow_depth_bias: f32,
shadow_normal_bias: f32, shadow_normal_bias: f32,
} }
@ -61,6 +62,7 @@ pub struct ExtractedDirectionalLight {
illuminance: f32, illuminance: f32,
direction: Vec3, direction: Vec3,
projection: Mat4, projection: Mat4,
shadows_enabled: bool,
shadow_depth_bias: f32, shadow_depth_bias: f32,
shadow_normal_bias: f32, shadow_normal_bias: f32,
} }
@ -77,20 +79,42 @@ pub struct GpuPointLight {
radius: f32, radius: f32,
near: f32, near: f32,
far: f32, far: f32,
flags: u32,
shadow_depth_bias: f32, shadow_depth_bias: f32,
shadow_normal_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)] #[repr(C)]
#[derive(Copy, Clone, AsStd140, Default, Debug)] #[derive(Copy, Clone, AsStd140, Default, Debug)]
pub struct GpuDirectionalLight { pub struct GpuDirectionalLight {
view_projection: Mat4, view_projection: Mat4,
color: Vec4, color: Vec4,
dir_to_light: Vec3, dir_to_light: Vec3,
flags: u32,
shadow_depth_bias: f32, shadow_depth_bias: f32,
shadow_normal_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)] #[repr(C)]
#[derive(Copy, Clone, Debug, AsStd140)] #[derive(Copy, Clone, Debug, AsStd140)]
pub struct GpuLights { pub struct GpuLights {
@ -328,6 +352,7 @@ pub fn extract_lights(
range: point_light.range, range: point_light.range,
radius: point_light.radius, radius: point_light.radius,
transform: *transform, transform: *transform,
shadows_enabled: point_light.shadows_enabled,
shadow_depth_bias: point_light.shadow_depth_bias, shadow_depth_bias: point_light.shadow_depth_bias,
// The factor of SQRT_2 is for the worst-case diagonal offset // The factor of SQRT_2 is for the worst-case diagonal offset
shadow_normal_bias: point_light.shadow_normal_bias shadow_normal_bias: point_light.shadow_normal_bias
@ -357,6 +382,7 @@ pub fn extract_lights(
illuminance: directional_light.illuminance, illuminance: directional_light.illuminance,
direction: transform.forward(), direction: transform.forward(),
projection: directional_light.shadow_projection.get_projection_matrix(), projection: directional_light.shadow_projection.get_projection_matrix(),
shadows_enabled: directional_light.shadows_enabled,
shadow_depth_bias: directional_light.shadow_depth_bias, shadow_depth_bias: directional_light.shadow_depth_bias,
// The factor of SQRT_2 is for the worst-case diagonal offset // The factor of SQRT_2 is for the worst-case diagonal offset
shadow_normal_bias: directional_light.shadow_normal_bias 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 depth_texture_view: TextureView,
pub pass_name: String, pub pass_name: String,
} }
pub struct ViewLights { pub struct ViewShadowBindings {
pub point_light_depth_texture: Texture, pub point_light_depth_texture: Texture,
pub point_light_depth_texture_view: TextureView, pub point_light_depth_texture_view: TextureView,
pub directional_light_depth_texture: Texture, pub directional_light_depth_texture: Texture,
pub directional_light_depth_texture_view: TextureView, pub directional_light_depth_texture_view: TextureView,
}
pub struct ViewLightEntities {
pub lights: Vec<Entity>, pub lights: Vec<Entity>,
pub gpu_light_binding_index: u32, }
pub struct ViewLightsUniformOffset {
pub offset: u32,
} }
#[derive(Default)] #[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 // 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 for (light_index, (light_entity, light)) in point_lights.iter().enumerate() {
point_lights.iter().enumerate().take(MAX_POINT_LIGHTS)
{
// ignore scale because we don't want to effectively scale light radius and range // 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 // 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 // and ignore rotation because we want the shadow map projections to align with the axes
let view_translation = GlobalTransform::from_translation(light.transform.translation); let view_translation = GlobalTransform::from_translation(light.transform.translation);
for (face_index, view_rotation) in cube_face_rotations.iter().enumerate() { if light.shadows_enabled {
let depth_texture_view = for (face_index, view_rotation) in cube_face_rotations.iter().enumerate() {
point_light_depth_texture let depth_texture_view =
.texture point_light_depth_texture
.create_view(&TextureViewDescriptor { .texture
label: Some("point_light_shadow_map_texture_view"), .create_view(&TextureViewDescriptor {
format: None, label: Some("point_light_shadow_map_texture_view"),
dimension: Some(TextureViewDimension::D2), format: None,
aspect: TextureAspect::All, dimension: Some(TextureViewDimension::D2),
base_mip_level: 0, aspect: TextureAspect::All,
mip_level_count: None, base_mip_level: 0,
base_array_layer: (light_index * 6 + face_index) as u32, mip_level_count: None,
array_layer_count: NonZeroU32::new(1), base_array_layer: (light_index * 6 + face_index) as u32,
}); array_layer_count: NonZeroU32::new(1),
});
let view_light_entity = commands let view_light_entity = commands
.spawn() .spawn()
.insert_bundle(( .insert_bundle((
ViewLight { ShadowView {
depth_texture_view, depth_texture_view,
pass_name: format!( pass_name: format!(
"shadow pass point light {} {}", "shadow pass point light {} {}",
light_index, light_index,
face_index_to_name(face_index) face_index_to_name(face_index)
), ),
}, },
ExtractedView { ExtractedView {
width: point_light_shadow_map.size as u32, width: point_light_shadow_map.size as u32,
height: point_light_shadow_map.size as u32, height: point_light_shadow_map.size as u32,
transform: view_translation * *view_rotation, transform: view_translation * *view_rotation,
projection: cube_face_projection, projection: cube_face_projection,
}, },
RenderPhase::<Shadow>::default(), RenderPhase::<Shadow>::default(),
LightEntity::Point { LightEntity::Point {
light_entity, light_entity,
face_index, face_index,
}, },
)) ))
.id(); .id();
view_lights.push(view_light_entity); 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 { gpu_lights.point_lights[light_index] = GpuPointLight {
@ -584,6 +621,7 @@ pub fn prepare_lights(
inverse_square_range: 1.0 / (light.range * light.range), inverse_square_range: 1.0 / (light.range * light.range),
near: 0.1, near: 0.1,
far: light.range, far: light.range,
flags: flags.bits,
shadow_depth_bias: light.shadow_depth_bias, shadow_depth_bias: light.shadow_depth_bias,
shadow_normal_bias: light.shadow_normal_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 // NOTE: This orthographic projection defines the volume within which shadows from a directional light can be cast
let projection = light.projection; let projection = light.projection;
let mut flags = DirectionalLightFlags::NONE;
if light.shadows_enabled {
flags |= DirectionalLightFlags::SHADOWS_ENABLED;
}
gpu_lights.directional_lights[i] = GpuDirectionalLight { gpu_lights.directional_lights[i] = GpuDirectionalLight {
// premultiply color by intensity // premultiply color by intensity
// we don't use the alpha at all, so no reason to multiply only [0..3] // 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, dir_to_light,
// NOTE: * view is correct, it should not be view.inverse() here // NOTE: * view is correct, it should not be view.inverse() here
view_projection: projection * view, view_projection: projection * view,
flags: flags.bits,
shadow_depth_bias: light.shadow_depth_bias, shadow_depth_bias: light.shadow_depth_bias,
shadow_normal_bias: light.shadow_normal_bias, shadow_normal_bias: light.shadow_normal_bias,
}; };
let depth_texture_view = if light.shadows_enabled {
directional_light_depth_texture let depth_texture_view =
.texture directional_light_depth_texture
.create_view(&TextureViewDescriptor { .texture
label: Some("directional_light_shadow_map_texture_view"), .create_view(&TextureViewDescriptor {
format: None, label: Some("directional_light_shadow_map_texture_view"),
dimension: Some(TextureViewDimension::D2), format: None,
aspect: TextureAspect::All, dimension: Some(TextureViewDimension::D2),
base_mip_level: 0, aspect: TextureAspect::All,
mip_level_count: None, base_mip_level: 0,
base_array_layer: i as u32, mip_level_count: None,
array_layer_count: NonZeroU32::new(1), base_array_layer: i as u32,
}); array_layer_count: NonZeroU32::new(1),
});
let view_light_entity = commands let view_light_entity = commands
.spawn() .spawn()
.insert_bundle(( .insert_bundle((
ViewLight { ShadowView {
depth_texture_view, depth_texture_view,
pass_name: format!("shadow pass directional light {}", i), pass_name: format!("shadow pass directional light {}", i),
}, },
ExtractedView { ExtractedView {
width: directional_light_shadow_map.size as u32, width: directional_light_shadow_map.size as u32,
height: directional_light_shadow_map.size as u32, height: directional_light_shadow_map.size as u32,
transform: GlobalTransform::from_matrix(view.inverse()), transform: GlobalTransform::from_matrix(view.inverse()),
projection, projection,
}, },
RenderPhase::<Shadow>::default(), RenderPhase::<Shadow>::default(),
LightEntity::Directional { light_entity }, LightEntity::Directional { light_entity },
)) ))
.id(); .id();
view_lights.push(view_light_entity); view_lights.push(view_light_entity);
}
} }
let point_light_depth_texture_view = let point_light_depth_texture_view =
point_light_depth_texture point_light_depth_texture
@ -686,14 +732,20 @@ pub fn prepare_lights(
array_layer_count: None, array_layer_count: None,
}); });
commands.entity(entity).insert(ViewLights { commands.entity(entity).insert_bundle((
point_light_depth_texture: point_light_depth_texture.texture, ViewShadowBindings {
point_light_depth_texture_view, point_light_depth_texture: point_light_depth_texture.texture,
directional_light_depth_texture: directional_light_depth_texture.texture, point_light_depth_texture_view,
directional_light_depth_texture_view, directional_light_depth_texture: directional_light_depth_texture.texture,
lights: view_lights, directional_light_depth_texture_view,
gpu_light_binding_index: light_meta.view_gpu_lights.push(gpu_lights), },
}); ViewLightEntities {
lights: view_lights,
},
ViewLightsUniformOffset {
offset: light_meta.view_gpu_lights.push(gpu_lights),
},
));
} }
light_meta light_meta
@ -728,12 +780,12 @@ pub fn queue_shadows(
render_meshes: Res<RenderAssets<Mesh>>, render_meshes: Res<RenderAssets<Mesh>>,
mut pipelines: ResMut<SpecializedPipelines<ShadowPipeline>>, mut pipelines: ResMut<SpecializedPipelines<ShadowPipeline>>,
mut pipeline_cache: ResMut<RenderPipelineCache>, mut pipeline_cache: ResMut<RenderPipelineCache>,
mut view_lights: Query<&ViewLights>, view_lights: Query<&ViewLightEntities>,
mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase<Shadow>)>, mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase<Shadow>)>,
point_light_entities: Query<&CubemapVisibleEntities, With<ExtractedPointLight>>, point_light_entities: Query<&CubemapVisibleEntities, With<ExtractedPointLight>>,
directional_light_entities: Query<&VisibleEntities, With<ExtractedDirectionalLight>>, directional_light_entities: Query<&VisibleEntities, With<ExtractedDirectionalLight>>,
) { ) {
for view_lights in view_lights.iter_mut() { for view_lights in view_lights.iter() {
let draw_shadow_mesh = shadow_draw_functions let draw_shadow_mesh = shadow_draw_functions
.read() .read()
.get_id::<DrawShadowMesh>() .get_id::<DrawShadowMesh>()
@ -753,6 +805,8 @@ pub fn queue_shadows(
.expect("Failed to get point light visible entities") .expect("Failed to get point light visible entities")
.get(*face_index), .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() { for VisibleEntity { entity, .. } in visible_entities.iter() {
let mut key = ShadowPipelineKey::empty(); let mut key = ShadowPipelineKey::empty();
if let Ok(mesh_handle) = casting_meshes.get(*entity) { if let Ok(mesh_handle) = casting_meshes.get(*entity) {
@ -811,8 +865,8 @@ impl CachedPipelinePhaseItem for Shadow {
} }
pub struct ShadowPassNode { pub struct ShadowPassNode {
main_view_query: QueryState<&'static ViewLights>, main_view_query: QueryState<&'static ViewLightEntities>,
view_light_query: QueryState<(&'static ViewLight, &'static RenderPhase<Shadow>)>, view_light_query: QueryState<(&'static ShadowView, &'static RenderPhase<Shadow>)>,
} }
impl ShadowPassNode { impl ShadowPassNode {

View file

@ -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_app::Plugin;
use bevy_asset::{Assets, Handle, HandleUntyped}; use bevy_asset::{Assets, Handle, HandleUntyped};
use bevy_ecs::{ use bevy_ecs::{
@ -523,13 +526,13 @@ pub fn queue_mesh_view_bind_groups(
shadow_pipeline: Res<ShadowPipeline>, shadow_pipeline: Res<ShadowPipeline>,
light_meta: Res<LightMeta>, light_meta: Res<LightMeta>,
view_uniforms: Res<ViewUniforms>, view_uniforms: Res<ViewUniforms>,
mut views: Query<(Entity, &ViewLights)>, mut views: Query<(Entity, &ViewShadowBindings)>,
) { ) {
if let (Some(view_binding), Some(light_binding)) = ( if let (Some(view_binding), Some(light_binding)) = (
view_uniforms.uniforms.binding(), view_uniforms.uniforms.binding(),
light_meta.view_gpu_lights.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 { let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[ entries: &[
BindGroupEntry { BindGroupEntry {
@ -543,7 +546,7 @@ pub fn queue_mesh_view_bind_groups(
BindGroupEntry { BindGroupEntry {
binding: 2, binding: 2,
resource: BindingResource::TextureView( resource: BindingResource::TextureView(
&view_lights.point_light_depth_texture_view, &view_shadow_bindings.point_light_depth_texture_view,
), ),
}, },
BindGroupEntry { BindGroupEntry {
@ -553,7 +556,7 @@ pub fn queue_mesh_view_bind_groups(
BindGroupEntry { BindGroupEntry {
binding: 4, binding: 4,
resource: BindingResource::TextureView( resource: BindingResource::TextureView(
&view_lights.directional_light_depth_texture_view, &view_shadow_bindings.directional_light_depth_texture_view,
), ),
}, },
BindGroupEntry { BindGroupEntry {
@ -578,7 +581,7 @@ pub struct SetMeshViewBindGroup<const I: usize>;
impl<const I: usize> EntityRenderCommand for SetMeshViewBindGroup<I> { impl<const I: usize> EntityRenderCommand for SetMeshViewBindGroup<I> {
type Param = SQuery<( type Param = SQuery<(
Read<ViewUniformOffset>, Read<ViewUniformOffset>,
Read<ViewLights>, Read<ViewLightsUniformOffset>,
Read<MeshViewBindGroup>, Read<MeshViewBindGroup>,
)>; )>;
#[inline] #[inline]
@ -592,7 +595,7 @@ impl<const I: usize> EntityRenderCommand for SetMeshViewBindGroup<I> {
pass.set_bind_group( pass.set_bind_group(
I, I,
&mesh_view_bind_group.value, &mesh_view_bind_group.value,
&[view_uniform.offset, view_lights.gpu_light_binding_index], &[view_uniform.offset, view_lights.offset],
); );
RenderCommandResult::Success RenderCommandResult::Success

View file

@ -13,18 +13,26 @@ struct PointLight {
radius: f32; radius: f32;
near: f32; near: f32;
far: 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_depth_bias: f32;
shadow_normal_bias: f32; shadow_normal_bias: f32;
}; };
let POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u;
struct DirectionalLight { struct DirectionalLight {
view_projection: mat4x4<f32>; view_projection: mat4x4<f32>;
color: vec4<f32>; color: vec4<f32>;
direction_to_light: vec3<f32>; direction_to_light: vec3<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_depth_bias: f32;
shadow_normal_bias: f32; shadow_normal_bias: f32;
}; };
let DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u;
[[block]] [[block]]
struct Lights { struct Lights {
// NOTE: this array size must be kept in sync with the constants defined bevy_pbr2/src/render/light.rs // 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)]] [[group(0), binding(4)]]
var directional_shadow_textures: texture_depth_2d_array; var directional_shadow_textures: texture_depth_2d_array;
[[group(0), binding(5)]] [[group(0), binding(5)]]
var directional_shadow_textures_sampler: sampler_comparison; var directional_shadow_textures_sampler: sampler_comparison;

View file

@ -500,7 +500,8 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
for (var i: i32 = 0; i < n_point_lights; i = i + 1) { for (var i: i32 = 0; i < n_point_lights; i = i + 1) {
let light = lights.point_lights[i]; let light = lights.point_lights[i];
var shadow: f32; 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); shadow = fetch_point_shadow(i, in.world_position, in.world_normal);
} else { } else {
shadow = 1.0; shadow = 1.0;
@ -511,7 +512,8 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
for (var i: i32 = 0; i < n_directional_lights; i = i + 1) { for (var i: i32 = 0; i < n_directional_lights; i = i + 1) {
let light = lights.directional_lights[i]; let light = lights.directional_lights[i];
var shadow: f32; 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); shadow = fetch_directional_shadow(i, in.world_position, in.world_normal);
} else { } else {
shadow = 1.0; shadow = 1.0;