mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
light renderlayers (#10742)
# Objective add `RenderLayers` awareness to lights. lights default to `RenderLayers::layer(0)`, and must intersect the camera entity's `RenderLayers` in order to affect the camera's output. note that lights already use renderlayers to filter meshes for shadow casting. this adds filtering lights per view based on intersection of camera layers and light layers. fixes #3462 ## Solution PointLights and SpotLights are assigned to individual views in `assign_lights_to_clusters`, so we simply cull the lights which don't match the view layers in that function. DirectionalLights are global, so we - add the light layers to the `DirectionalLight` struct - add the view layers to the `ViewUniform` struct - check for intersection before processing the light in `apply_pbr_lighting` potential issue: when mesh/light layers are smaller than the view layers weird results can occur. e.g: camera = layers 1+2 light = layers 1 mesh = layers 2 the mesh does not cast shadows wrt the light as (1 & 2) == 0. the light affects the view as (1+2 & 1) != 0. the view renders the mesh as (1+2 & 2) != 0. so the mesh is rendered and lit, but does not cast a shadow. this could be fixed (so that the light would not affect the mesh in that view) by adding the light layers to the point and spot light structs, but i think the setup is pretty unusual, and space is at a premium in those structs (adding 4 bytes more would reduce the webgl point+spot light max count to 240 from 256). I think typical usage is for cameras to have a single layer, and meshes/lights to maybe have multiple layers to render to e.g. minimaps as well as primary views. if there is a good use case for the above setup and we should support it, please let me know. --- ## Migration Guide Lights no longer affect all `RenderLayers` by default, now like cameras and meshes they default to `RenderLayers::layer(0)`. To recover the previous behaviour and have all lights affect all views, add a `RenderLayers::all()` component to the light entity.
This commit is contained in:
parent
55402bdf2e
commit
67d92e9b85
8 changed files with 86 additions and 24 deletions
|
@ -1154,6 +1154,7 @@ pub(crate) struct PointLightAssignmentData {
|
|||
range: f32,
|
||||
shadows_enabled: bool,
|
||||
spot_light_angle: Option<f32>,
|
||||
render_layers: RenderLayers,
|
||||
}
|
||||
|
||||
impl PointLightAssignmentData {
|
||||
|
@ -1194,10 +1195,23 @@ pub(crate) fn assign_lights_to_clusters(
|
|||
&Frustum,
|
||||
&ClusterConfig,
|
||||
&mut Clusters,
|
||||
Option<&RenderLayers>,
|
||||
Option<&mut VisiblePointLights>,
|
||||
)>,
|
||||
point_lights_query: Query<(Entity, &GlobalTransform, &PointLight, &ViewVisibility)>,
|
||||
spot_lights_query: Query<(Entity, &GlobalTransform, &SpotLight, &ViewVisibility)>,
|
||||
point_lights_query: Query<(
|
||||
Entity,
|
||||
&GlobalTransform,
|
||||
&PointLight,
|
||||
Option<&RenderLayers>,
|
||||
&ViewVisibility,
|
||||
)>,
|
||||
spot_lights_query: Query<(
|
||||
Entity,
|
||||
&GlobalTransform,
|
||||
&SpotLight,
|
||||
Option<&RenderLayers>,
|
||||
&ViewVisibility,
|
||||
)>,
|
||||
mut lights: Local<Vec<PointLightAssignmentData>>,
|
||||
mut cluster_aabb_spheres: Local<Vec<Option<Sphere>>>,
|
||||
mut max_point_lights_warning_emitted: Local<bool>,
|
||||
|
@ -1215,12 +1229,15 @@ pub(crate) fn assign_lights_to_clusters(
|
|||
.iter()
|
||||
.filter(|(.., visibility)| visibility.get())
|
||||
.map(
|
||||
|(entity, transform, point_light, _visibility)| PointLightAssignmentData {
|
||||
entity,
|
||||
transform: GlobalTransform::from_translation(transform.translation()),
|
||||
shadows_enabled: point_light.shadows_enabled,
|
||||
range: point_light.range,
|
||||
spot_light_angle: None,
|
||||
|(entity, transform, point_light, maybe_layers, _visibility)| {
|
||||
PointLightAssignmentData {
|
||||
entity,
|
||||
transform: GlobalTransform::from_translation(transform.translation()),
|
||||
shadows_enabled: point_light.shadows_enabled,
|
||||
range: point_light.range,
|
||||
spot_light_angle: None,
|
||||
render_layers: maybe_layers.copied().unwrap_or_default(),
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -1229,12 +1246,15 @@ pub(crate) fn assign_lights_to_clusters(
|
|||
.iter()
|
||||
.filter(|(.., visibility)| visibility.get())
|
||||
.map(
|
||||
|(entity, transform, spot_light, _visibility)| PointLightAssignmentData {
|
||||
entity,
|
||||
transform: *transform,
|
||||
shadows_enabled: spot_light.shadows_enabled,
|
||||
range: spot_light.range,
|
||||
spot_light_angle: Some(spot_light.outer_angle),
|
||||
|(entity, transform, spot_light, maybe_layers, _visibility)| {
|
||||
PointLightAssignmentData {
|
||||
entity,
|
||||
transform: *transform,
|
||||
shadows_enabled: spot_light.shadows_enabled,
|
||||
range: spot_light.range,
|
||||
spot_light_angle: Some(spot_light.outer_angle),
|
||||
render_layers: maybe_layers.copied().unwrap_or_default(),
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -1264,7 +1284,7 @@ pub(crate) fn assign_lights_to_clusters(
|
|||
// check each light against each view's frustum, keep only those that affect at least one of our views
|
||||
let frusta: Vec<_> = views
|
||||
.iter()
|
||||
.map(|(_, _, _, frustum, _, _, _)| *frustum)
|
||||
.map(|(_, _, _, frustum, _, _, _, _)| *frustum)
|
||||
.collect();
|
||||
let mut lights_in_view_count = 0;
|
||||
lights.retain(|light| {
|
||||
|
@ -1296,9 +1316,18 @@ pub(crate) fn assign_lights_to_clusters(
|
|||
lights.truncate(MAX_UNIFORM_BUFFER_POINT_LIGHTS);
|
||||
}
|
||||
|
||||
for (view_entity, camera_transform, camera, frustum, config, clusters, mut visible_lights) in
|
||||
&mut views
|
||||
for (
|
||||
view_entity,
|
||||
camera_transform,
|
||||
camera,
|
||||
frustum,
|
||||
config,
|
||||
clusters,
|
||||
maybe_layers,
|
||||
mut visible_lights,
|
||||
) in &mut views
|
||||
{
|
||||
let view_layers = maybe_layers.copied().unwrap_or_default();
|
||||
let clusters = clusters.into_inner();
|
||||
|
||||
if matches!(config, ClusterConfig::None) {
|
||||
|
@ -1520,6 +1549,11 @@ pub(crate) fn assign_lights_to_clusters(
|
|||
|
||||
let mut update_from_light_intersections = |visible_lights: &mut Vec<Entity>| {
|
||||
for light in &lights {
|
||||
// check if the light layers overlap the view layers
|
||||
if !view_layers.intersects(&light.render_layers) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let light_sphere = light.sphere();
|
||||
|
||||
// Check if the light is within the view frustum
|
||||
|
|
|
@ -11,7 +11,7 @@ use bevy_render::{
|
|||
render_resource::*,
|
||||
renderer::{RenderContext, RenderDevice, RenderQueue},
|
||||
texture::*,
|
||||
view::{ExtractedView, ViewVisibility, VisibleEntities},
|
||||
view::{ExtractedView, RenderLayers, ViewVisibility, VisibleEntities},
|
||||
Extract,
|
||||
};
|
||||
use bevy_transform::{components::GlobalTransform, prelude::Transform};
|
||||
|
@ -48,6 +48,7 @@ pub struct ExtractedDirectionalLight {
|
|||
shadow_normal_bias: f32,
|
||||
cascade_shadow_config: CascadeShadowConfig,
|
||||
cascades: HashMap<Entity, Vec<Cascade>>,
|
||||
render_layers: RenderLayers,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, ShaderType, Default, Debug)]
|
||||
|
@ -169,6 +170,7 @@ pub struct GpuDirectionalLight {
|
|||
num_cascades: u32,
|
||||
cascades_overlap_proportion: f32,
|
||||
depth_texture_base_index: u32,
|
||||
render_layers: u32,
|
||||
}
|
||||
|
||||
// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl!
|
||||
|
@ -315,6 +317,7 @@ pub fn extract_lights(
|
|||
&CascadeShadowConfig,
|
||||
&GlobalTransform,
|
||||
&ViewVisibility,
|
||||
Option<&RenderLayers>,
|
||||
),
|
||||
Without<SpotLight>,
|
||||
>,
|
||||
|
@ -430,6 +433,7 @@ pub fn extract_lights(
|
|||
cascade_config,
|
||||
transform,
|
||||
view_visibility,
|
||||
maybe_layers,
|
||||
) in &directional_lights
|
||||
{
|
||||
if !view_visibility.get() {
|
||||
|
@ -449,6 +453,7 @@ pub fn extract_lights(
|
|||
shadow_normal_bias: directional_light.shadow_normal_bias * std::f32::consts::SQRT_2,
|
||||
cascade_shadow_config: cascade_config.clone(),
|
||||
cascades: cascades.cascades.clone(),
|
||||
render_layers: maybe_layers.copied().unwrap_or_default(),
|
||||
},
|
||||
render_visible_entities,
|
||||
));
|
||||
|
@ -883,6 +888,7 @@ pub fn prepare_lights(
|
|||
num_cascades: num_cascades as u32,
|
||||
cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion,
|
||||
depth_texture_base_index: num_directional_cascades_enabled as u32,
|
||||
render_layers: light.render_layers.bits(),
|
||||
};
|
||||
if index < directional_shadow_enabled_count {
|
||||
num_directional_cascades_enabled += num_cascades;
|
||||
|
|
|
@ -33,6 +33,7 @@ struct DirectionalLight {
|
|||
num_cascades: u32,
|
||||
cascades_overlap_proportion: f32,
|
||||
depth_texture_base_index: u32,
|
||||
render_layers: u32,
|
||||
};
|
||||
|
||||
const DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u;
|
||||
|
|
|
@ -267,6 +267,13 @@ fn apply_pbr_lighting(
|
|||
// directional lights (direct)
|
||||
let n_directional_lights = view_bindings::lights.n_directional_lights;
|
||||
for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) {
|
||||
// check the directional light render layers intersect the view render layers
|
||||
// note this is not necessary for point and spot lights, as the relevant lights are filtered in `assign_lights_to_clusters`
|
||||
let light = &view_bindings::lights.directional_lights[i];
|
||||
if ((*light).render_layers & view_bindings::view.render_layers) == 0u {
|
||||
continue;
|
||||
}
|
||||
|
||||
var shadow: f32 = 1.0;
|
||||
if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
|
||||
&& (view_bindings::lights.directional_lights[i].flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
|
|
|
@ -172,6 +172,7 @@ pub struct ViewUniform {
|
|||
frustum: [Vec4; 6],
|
||||
color_grading: ColorGrading,
|
||||
mip_bias: f32,
|
||||
render_layers: u32,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
|
@ -357,6 +358,7 @@ pub fn prepare_view_uniforms(
|
|||
Option<&Frustum>,
|
||||
Option<&TemporalJitter>,
|
||||
Option<&MipBias>,
|
||||
Option<&RenderLayers>,
|
||||
)>,
|
||||
) {
|
||||
let view_iter = views.iter();
|
||||
|
@ -368,7 +370,7 @@ pub fn prepare_view_uniforms(
|
|||
else {
|
||||
return;
|
||||
};
|
||||
for (entity, camera, frustum, temporal_jitter, mip_bias) in &views {
|
||||
for (entity, camera, frustum, temporal_jitter, mip_bias, maybe_layers) in &views {
|
||||
let viewport = camera.viewport.as_vec4();
|
||||
let unjittered_projection = camera.projection;
|
||||
let mut projection = unjittered_projection;
|
||||
|
@ -408,6 +410,7 @@ pub fn prepare_view_uniforms(
|
|||
frustum,
|
||||
color_grading: camera.color_grading,
|
||||
mip_bias: mip_bias.unwrap_or(&MipBias(0.0)).0,
|
||||
render_layers: maybe_layers.copied().unwrap_or_default().bits(),
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
@ -21,4 +21,5 @@ struct View {
|
|||
frustum: array<vec4<f32>, 6>,
|
||||
color_grading: ColorGrading,
|
||||
mip_bias: f32,
|
||||
render_layers: u32,
|
||||
};
|
||||
|
|
|
@ -110,6 +110,11 @@ impl RenderLayers {
|
|||
pub fn intersects(&self, other: &RenderLayers) -> bool {
|
||||
(self.0 & other.0) > 0
|
||||
}
|
||||
|
||||
/// get the bitmask representation of the contained layers
|
||||
pub fn bits(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -87,11 +87,16 @@ fn setup(
|
|||
));
|
||||
|
||||
// Light
|
||||
// NOTE: Currently lights are shared between passes - see https://github.com/bevyengine/bevy/issues/3462
|
||||
commands.spawn(PointLightBundle {
|
||||
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)),
|
||||
..default()
|
||||
});
|
||||
// NOTE: we add the light to all layers so it affects both the rendered-to-texture cube, and the cube on which we display the texture
|
||||
// Setting the layer to RenderLayers::layer(0) would cause the main view to be lit, but the rendered-to-texture cube to be unlit.
|
||||
// Setting the layer to RenderLayers::layer(1) would cause the rendered-to-texture cube to be lit, but the main view to be unlit.
|
||||
commands.spawn((
|
||||
PointLightBundle {
|
||||
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)),
|
||||
..default()
|
||||
},
|
||||
RenderLayers::all(),
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
|
|
Loading…
Reference in a new issue