Skip to content

Commit

Permalink
Increase the MAX_DIRECTIONAL_LIGHTS from 1 to 10 (#6066)
Browse files Browse the repository at this point in the history
# 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>
  • Loading branch information
kurtkuehnert and kurtkuehnert committed Nov 3, 2022
1 parent 54a1e51 commit 701ed8c
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 82 deletions.
13 changes: 13 additions & 0 deletions crates/bevy_pbr/src/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
199 changes: 118 additions & 81 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -771,6 +771,7 @@ pub fn prepare_lights(
ambient_light: Res<AmbientLight>,
point_light_shadow_map: Res<PointLightShadowMap>,
directional_light_shadow_map: Res<DirectionalLightShadowMap>,
mut max_directional_lights_warning_emitted: Local<bool>,
point_lights: Query<(Entity, &ExtractedPointLight)>,
directional_lights: Query<(Entity, &ExtractedDirectionalLight)>,
) {
Expand All @@ -787,6 +788,7 @@ pub fn prepare_lights(
global_light_meta.entity_to_index.clear();

let mut point_lights: Vec<_> = point_lights.iter().collect::<Vec<_>>();
let mut directional_lights: Vec<_> = directional_lights.iter().collect::<Vec<_>>();

#[cfg(not(feature = "webgl"))]
let max_texture_array_layers = render_device.limits().max_texture_array_layers as usize;
Expand All @@ -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())
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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::<Shadow>::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::<Shadow>::default(),
LightEntity::Directional { light_entity },
))
.id();
view_lights.push(view_light_entity);
}

let point_light_depth_texture_view =
point_light_depth_texture
.texture
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/render/mesh_view_types.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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<DirectionalLight, 1u>,
directional_lights: array<DirectionalLight, 10u>,
ambient_color: vec4<f32>,
// x/y/z dimensions and n_clusters in w
cluster_dimensions: vec4<u32>,
Expand Down

0 comments on commit 701ed8c

Please sign in to comment.