From 41681300f4c4094ff8c4bf9c34ed5c92a7b57061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kurt=20K=C3=BChnert?= Date: Thu, 3 Nov 2022 07:09:51 +0000 Subject: [PATCH] Increase the `MAX_DIRECTIONAL_LIGHTS` from 1 to 10 (#6066) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective Currently we are limiting the amount of direction lights in a scene to one. ## Solution Increase the amount of direction lights from 1 to 10. This still is not a perfect solution, but should unblock many use cases. We could probably just store the directional lights similar to the point lights in an storage buffer, allowing for an variable amount of directional lights. Co-authored-by: Kurt Kühnert <51823519+Ku95@users.noreply.github.com> --- crates/bevy_pbr/src/light.rs | 13 ++ crates/bevy_pbr/src/render/light.rs | 199 +++++++++++------- .../bevy_pbr/src/render/mesh_view_types.wgsl | 2 +- 3 files changed, 132 insertions(+), 82 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 7b67e4bb00f571..39e8a6cf4a5c09 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -754,6 +754,19 @@ pub(crate) fn point_light_order( .then_with(|| entity_1.cmp(entity_2)) // stable } +// Sort lights by +// - those with shadows enabled first, so that the index can be used to render at most `directional_light_shadow_maps_count` +// directional light shadows +// - then by entity as a stable key to ensure that a consistent set of lights are chosen if the light count limit is exceeded. +pub(crate) fn directional_light_order( + (entity_1, shadows_enabled_1): (&Entity, &bool), + (entity_2, shadows_enabled_2): (&Entity, &bool), +) -> std::cmp::Ordering { + shadows_enabled_2 + .cmp(shadows_enabled_1) // shadow casters before non-casters + .then_with(|| entity_1.cmp(entity_2)) // stable +} + #[derive(Clone, Copy)] // data required for assigning lights to clusters pub(crate) struct PointLightAssignmentData { diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 7e9da57876154d..d314116c839527 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1,8 +1,8 @@ use crate::{ - point_light_order, AmbientLight, Clusters, CubemapVisibleEntities, DirectionalLight, - DirectionalLightShadowMap, DrawMesh, GlobalVisiblePointLights, MeshPipeline, NotShadowCaster, - PointLight, PointLightShadowMap, SetMeshBindGroup, SpotLight, VisiblePointLights, - SHADOW_SHADER_HANDLE, + directional_light_order, point_light_order, AmbientLight, Clusters, CubemapVisibleEntities, + DirectionalLight, DirectionalLightShadowMap, DrawMesh, GlobalVisiblePointLights, MeshPipeline, + NotShadowCaster, PointLight, PointLightShadowMap, SetMeshBindGroup, SpotLight, + VisiblePointLights, SHADOW_SHADER_HANDLE, }; use bevy_asset::Handle; use bevy_core_pipeline::core_3d::Transparent3d; @@ -211,7 +211,7 @@ pub struct GpuLights { // NOTE: this must be kept in sync with the same constants in pbr.frag pub const MAX_UNIFORM_BUFFER_POINT_LIGHTS: usize = 256; -pub const MAX_DIRECTIONAL_LIGHTS: usize = 1; +pub const MAX_DIRECTIONAL_LIGHTS: usize = 10; pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float; #[derive(Resource)] @@ -771,6 +771,7 @@ pub fn prepare_lights( ambient_light: Res, point_light_shadow_map: Res, directional_light_shadow_map: Res, + mut max_directional_lights_warning_emitted: Local, point_lights: Query<(Entity, &ExtractedPointLight)>, directional_lights: Query<(Entity, &ExtractedDirectionalLight)>, ) { @@ -787,6 +788,7 @@ pub fn prepare_lights( global_light_meta.entity_to_index.clear(); let mut point_lights: Vec<_> = point_lights.iter().collect::>(); + let mut directional_lights: Vec<_> = directional_lights.iter().collect::>(); #[cfg(not(feature = "webgl"))] let max_texture_array_layers = render_device.limits().max_texture_array_layers as usize; @@ -797,6 +799,16 @@ pub fn prepare_lights( #[cfg(feature = "webgl")] let max_texture_cubes = 1; + if !*max_directional_lights_warning_emitted && directional_lights.len() > MAX_DIRECTIONAL_LIGHTS + { + warn!( + "The amount of directional lights of {} is exceeding the supported limit of {}.", + directional_lights.len(), + MAX_DIRECTIONAL_LIGHTS + ); + *max_directional_lights_warning_emitted = true; + } + let point_light_count = point_lights .iter() .filter(|light| light.1.spot_light_angles.is_none()) @@ -810,6 +822,7 @@ pub fn prepare_lights( let directional_shadow_maps_count = directional_lights .iter() + .take(MAX_DIRECTIONAL_LIGHTS) .filter(|(_, light)| light.shadows_enabled) .count() .min(max_texture_array_layers); @@ -840,6 +853,17 @@ pub fn prepare_lights( ) }); + // Sort lights by + // - those with shadows enabled first, so that the index can be used to render at most `directional_light_shadow_maps_count` + // directional light shadows + // - then by entity as a stable key to ensure that a consistent set of lights are chosen if the light count limit is exceeded. + directional_lights.sort_by(|(entity_1, light_1), (entity_2, light_2)| { + directional_light_order( + (entity_1, &light_1.shadows_enabled), + (entity_2, &light_2.shadows_enabled), + ) + }); + if global_light_meta.entity_to_index.capacity() < point_lights.len() { global_light_meta .entity_to_index @@ -908,6 +932,54 @@ pub fn prepare_lights( global_light_meta.entity_to_index.insert(entity, index); } + let mut gpu_directional_lights = [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS]; + + for (index, (_light_entity, light)) in directional_lights + .iter() + .enumerate() + .take(MAX_DIRECTIONAL_LIGHTS) + { + let mut flags = DirectionalLightFlags::NONE; + + // Lights are sorted, shadow enabled lights are first + if light.shadows_enabled && (index < directional_shadow_maps_count) { + flags |= DirectionalLightFlags::SHADOWS_ENABLED; + } + + // direction is negated to be ready for N.L + let dir_to_light = -light.direction; + + // convert from illuminance (lux) to candelas + // + // exposure is hard coded at the moment but should be replaced + // by values coming from the camera + // see: https://google.github.io/filament/Filament.html#imagingpipeline/physicallybasedcamera/exposuresettings + const APERTURE: f32 = 4.0; + const SHUTTER_SPEED: f32 = 1.0 / 250.0; + const SENSITIVITY: f32 = 100.0; + let ev100 = f32::log2(APERTURE * APERTURE / SHUTTER_SPEED) - f32::log2(SENSITIVITY / 100.0); + let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2); + let intensity = light.illuminance * exposure; + + // NOTE: A directional light seems to have to have an eye position on the line along the direction of the light + // through the world origin. I (Rob Swain) do not yet understand why it cannot be translated away from this. + let view = Mat4::look_at_rh(Vec3::ZERO, light.direction, Vec3::Y); + // NOTE: This orthographic projection defines the volume within which shadows from a directional light can be cast + let projection = light.projection; + + gpu_directional_lights[index] = GpuDirectionalLight { + // premultiply color by intensity + // we don't use the alpha at all, so no reason to multiply only [0..3] + color: Vec4::from_slice(&light.color.as_linear_rgba_f32()) * intensity, + 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, + }; + } + global_light_meta.gpu_point_lights.set(gpu_point_lights); global_light_meta .gpu_point_lights @@ -962,8 +1034,8 @@ pub fn prepare_lights( ); let n_clusters = clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z; - let mut gpu_lights = GpuLights { - directional_lights: [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS], + let gpu_lights = GpuLights { + directional_lights: gpu_directional_lights, ambient_color: Vec4::from_slice(&ambient_light.color.as_linear_rgba_f32()) * ambient_light.brightness, cluster_factors: Vec4::new( @@ -1097,89 +1169,54 @@ pub fn prepare_lights( view_lights.push(view_light_entity); } - for (i, (light_entity, light)) in directional_lights + // directional lights + for (light_index, &(light_entity, light)) in directional_lights .iter() .enumerate() - .take(MAX_DIRECTIONAL_LIGHTS) + .take(directional_shadow_maps_count) { - // direction is negated to be ready for N.L - let dir_to_light = -light.direction; - - // convert from illuminance (lux) to candelas - // - // exposure is hard coded at the moment but should be replaced - // by values coming from the camera - // see: https://google.github.io/filament/Filament.html#imagingpipeline/physicallybasedcamera/exposuresettings - const APERTURE: f32 = 4.0; - const SHUTTER_SPEED: f32 = 1.0 / 250.0; - const SENSITIVITY: f32 = 100.0; - let ev100 = - f32::log2(APERTURE * APERTURE / SHUTTER_SPEED) - f32::log2(SENSITIVITY / 100.0); - let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2); - let intensity = light.illuminance * exposure; - // NOTE: A directional light seems to have to have an eye position on the line along the direction of the light // through the world origin. I (Rob Swain) do not yet understand why it cannot be translated away from this. let view = Mat4::look_at_rh(Vec3::ZERO, light.direction, Vec3::Y); - // 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] - color: Vec4::from_slice(&light.color.as_linear_rgba_f32()) * intensity, - 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, - }; - - 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 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: light_index as u32, + array_layer_count: NonZeroU32::new(1), + }); - let view_light_entity = commands - .spawn(( - ShadowView { - depth_texture_view, - pass_name: format!("shadow pass directional light {i}"), - }, - ExtractedView { - viewport: UVec4::new( - 0, - 0, - directional_light_shadow_map.size as u32, - directional_light_shadow_map.size as u32, - ), - transform: GlobalTransform::from(view.inverse()), - projection, - hdr: false, - }, - RenderPhase::::default(), - LightEntity::Directional { light_entity }, - )) - .id(); - view_lights.push(view_light_entity); - } + let view_light_entity = commands + .spawn(( + ShadowView { + depth_texture_view, + pass_name: format!("shadow pass directional light {}", light_index), + }, + ExtractedView { + viewport: UVec4::new( + 0, + 0, + directional_light_shadow_map.size as u32, + directional_light_shadow_map.size as u32, + ), + transform: GlobalTransform::from(view.inverse()), + projection: light.projection, + hdr: false, + }, + RenderPhase::::default(), + LightEntity::Directional { light_entity }, + )) + .id(); + view_lights.push(view_light_entity); } + let point_light_depth_texture_view = point_light_depth_texture .texture diff --git a/crates/bevy_pbr/src/render/mesh_view_types.wgsl b/crates/bevy_pbr/src/render/mesh_view_types.wgsl index 25dad2569c3825..7db9af72f64816 100644 --- a/crates/bevy_pbr/src/render/mesh_view_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_types.wgsl @@ -42,7 +42,7 @@ let DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; struct Lights { // NOTE: this array size must be kept in sync with the constants defined in bevy_pbr/src/render/light.rs - directional_lights: array, + directional_lights: array, ambient_color: vec4, // x/y/z dimensions and n_clusters in w cluster_dimensions: vec4,