Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1272,6 +1272,17 @@ description = "Illustrates various lighting options in a simple scene"
category = "3D Rendering"
wasm = true

[[example]]
name = "light_falloff"
path = "examples/3d/light_falloff.rs"
doc-scrape-examples = true

[package.metadata.example.light_falloff]
name = "Light Falloff"
description = "Compares inverse-square, linear, and exponential falloff for point and spot lights"
category = "3D Rendering"
wasm = true

[[example]]
name = "lines"
path = "examples/3d/lines.rs"
Expand Down
49 changes: 32 additions & 17 deletions crates/bevy_light/src/cluster/assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ use tracing::{error, warn};

use super::{ClusterConfig, ClusterFarZMode, ClusteredDecal, Clusters, GlobalClusterSettings};
use crate::{
cluster::ClusterableObjects, EnvironmentMapLight, LightProbe, PointLight, SpotLight,
cluster::ClusterableObjects, EnvironmentMapLight, LightFalloff, LightProbe, PointLight,
SpotLight,
VolumetricLight,
};

Expand Down Expand Up @@ -73,6 +74,9 @@ pub enum ClusterableObjectType {
///
/// This is used for sorting the light list.
volumetric: bool,

/// The distance falloff mode for this light.
falloff: LightFalloff,
},

/// Data needed to assign spot lights to clusters.
Expand All @@ -87,6 +91,9 @@ pub enum ClusterableObjectType {
/// This is used for sorting the light list.
volumetric: bool,

/// The distance falloff mode for this light.
falloff: LightFalloff,

/// The outer angle of the light cone in radians.
outer_angle: f32,
},
Expand Down Expand Up @@ -114,15 +121,21 @@ impl ClusterableObjectType {
ClusterableObjectType::PointLight {
shadow_maps_enabled,
volumetric,
} => (0, !shadow_maps_enabled, !volumetric),
falloff,
} => (falloff.bucket_index() as u8, !shadow_maps_enabled, !volumetric),
ClusterableObjectType::SpotLight {
shadow_maps_enabled,
volumetric,
falloff,
..
} => (1, !shadow_maps_enabled, !volumetric),
ClusterableObjectType::ReflectionProbe => (2, false, false),
ClusterableObjectType::IrradianceVolume => (3, false, false),
ClusterableObjectType::Decal => (4, false, false),
} => (
LightFalloff::VARIANT_COUNT as u8 + falloff.bucket_index() as u8,
!shadow_maps_enabled,
!volumetric,
),
ClusterableObjectType::ReflectionProbe => (6, false, false),
ClusterableObjectType::IrradianceVolume => (7, false, false),
ClusterableObjectType::Decal => (8, false, false),
}
}
}
Expand Down Expand Up @@ -189,6 +202,7 @@ pub(crate) fn assign_objects_to_clusters(
object_type: ClusterableObjectType::PointLight {
shadow_maps_enabled: point_light.shadow_maps_enabled,
volumetric: volumetric.is_some(),
falloff: point_light.falloff,
},
render_layers: maybe_layers.unwrap_or_default().clone(),
})
Expand All @@ -208,6 +222,7 @@ pub(crate) fn assign_objects_to_clusters(
outer_angle: spot_light.outer_angle,
shadow_maps_enabled: spot_light.shadow_maps_enabled,
volumetric: volumetric.is_some(),
falloff: spot_light.falloff,
},
render_layers: maybe_layers.unwrap_or_default().clone(),
})
Expand Down Expand Up @@ -264,17 +279,17 @@ pub(crate) fn assign_objects_to_clusters(
));
}

clusterable_objects.sort_by_cached_key(|clusterable_object| {
(
clusterable_object.object_type.ordering(),
clusterable_object.entity,
)
});

if clusterable_objects.len()
> global_cluster_settings.max_uniform_buffer_clusterable_objects
&& !global_cluster_settings.supports_storage_buffers
{
clusterable_objects.sort_by_cached_key(|clusterable_object| {
(
clusterable_object.object_type.ordering(),
clusterable_object.entity,
)
});

if clusterable_objects.len()
> global_cluster_settings.max_uniform_buffer_clusterable_objects
&& !*max_clusterable_objects_warning_emitted
Expand Down Expand Up @@ -647,7 +662,7 @@ pub(crate) fn assign_objects_to_clusters(
+ z) as usize;

match clusterable_object.object_type {
ClusterableObjectType::SpotLight { .. } => {
ClusterableObjectType::SpotLight { falloff, .. } => {
let (view_light_direction, angle_sin, angle_cos) =
spot_light_dir_sin_cos.unwrap();
for x in min_x..=max_x {
Expand Down Expand Up @@ -699,18 +714,18 @@ pub(crate) fn assign_objects_to_clusters(
if !angle_cull && !front_cull && !back_cull {
// this cluster is affected by the spot light
clusterable_objects[cluster_index]
.add_spot_light(clusterable_object.entity);
.add_spot_light(clusterable_object.entity, falloff);
total_cluster_index_count += 1;
}
cluster_index += clusters.dimensions.z as usize;
}
}

ClusterableObjectType::PointLight { .. } => {
ClusterableObjectType::PointLight { falloff, .. } => {
for _ in min_x..=max_x {
// all clusters within range are affected by point lights
clusterable_objects[cluster_index]
.add_point_light(clusterable_object.entity);
.add_point_light(clusterable_object.entity, falloff);
cluster_index += clusters.dimensions.z as usize;
}
total_cluster_index_count += (max_x - min_x + 1) as usize;
Expand Down
50 changes: 41 additions & 9 deletions crates/bevy_light/src/cluster/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_transform::components::Transform;
use tracing::warn;

use crate::LightProbe;
use crate::{LightFalloff, LightProbe};

pub mod assign;

Expand Down Expand Up @@ -198,10 +198,18 @@ pub struct ObjectsInClusterCpu {
/// fewer than 3 SSBOs are available, which usually means on WebGL 2.
#[derive(Clone, Copy, Default, Debug)]
pub struct ClusterableObjectCounts {
/// The number of point lights in the cluster.
pub point_lights: u32,
/// The number of spot lights in the cluster.
pub spot_lights: u32,
/// The number of inverse-square point lights in the cluster.
pub point_lights_inverse_square: u32,
/// The number of linear point lights in the cluster.
pub point_lights_linear: u32,
/// The number of exponential point lights in the cluster.
pub point_lights_exponential: u32,
/// The number of inverse-square spot lights in the cluster.
pub spot_lights_inverse_square: u32,
/// The number of linear spot lights in the cluster.
pub spot_lights_linear: u32,
/// The number of exponential spot lights in the cluster.
pub spot_lights_exponential: u32,
/// The number of reflection probes in the cluster.
pub reflection_probes: u32,
/// The number of irradiance volumes in the cluster.
Expand All @@ -210,6 +218,22 @@ pub struct ClusterableObjectCounts {
pub decals: u32,
}

impl ClusterableObjectCounts {
/// Returns the total number of point lights in the cluster.
pub fn total_point_lights(self) -> u32 {
self.point_lights_inverse_square
+ self.point_lights_linear
+ self.point_lights_exponential
}

/// Returns the total number of spot lights in the cluster.
pub fn total_spot_lights(self) -> u32 {
self.spot_lights_inverse_square
+ self.spot_lights_linear
+ self.spot_lights_exponential
}
}

/// An object that projects a decal onto surfaces within its bounds.
///
/// Conceptually, a clustered decal is a 1×1×1 cube centered on its origin. It
Expand Down Expand Up @@ -474,15 +498,23 @@ impl ObjectsInClusterCpu {
}

/// Adds a spot light to the list.
pub fn add_spot_light(&mut self, entity: Entity) {
pub fn add_spot_light(&mut self, entity: Entity, falloff: LightFalloff) {
self.clusterables.push(entity);
self.counts.spot_lights += 1;
match falloff {
LightFalloff::InverseSquare => self.counts.spot_lights_inverse_square += 1,
LightFalloff::Linear => self.counts.spot_lights_linear += 1,
LightFalloff::Exponential => self.counts.spot_lights_exponential += 1,
}
}

/// Adds a point light to the list.
pub fn add_point_light(&mut self, entity: Entity) {
pub fn add_point_light(&mut self, entity: Entity, falloff: LightFalloff) {
self.clusterables.push(entity);
self.counts.point_lights += 1;
match falloff {
LightFalloff::InverseSquare => self.counts.point_lights_inverse_square += 1,
LightFalloff::Linear => self.counts.point_lights_linear += 1,
LightFalloff::Exponential => self.counts.point_lights_exponential += 1,
}
}

/// Adds a reflection probe to the list.
Expand Down
5 changes: 4 additions & 1 deletion crates/bevy_light/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub use cluster::ClusteredDecal;
mod ambient_light;
pub use ambient_light::{AmbientLight, GlobalAmbientLight};
use bevy_camera::visibility::SetViewVisibility;
mod light_falloff;
pub use light_falloff::LightFalloff;

mod probe;
pub use probe::{
Expand Down Expand Up @@ -70,7 +72,8 @@ pub mod prelude {
#[doc(hidden)]
pub use crate::{
light_consts, AmbientLight, DirectionalLight, EnvironmentMapLight,
GeneratedEnvironmentMapLight, GlobalAmbientLight, LightProbe, PointLight, SpotLight,
GeneratedEnvironmentMapLight, GlobalAmbientLight, LightFalloff, LightProbe, PointLight,
SpotLight,
};

#[doc(hidden)]
Expand Down
35 changes: 35 additions & 0 deletions crates/bevy_light/src/light_falloff.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use bevy_reflect::prelude::*;

/// Controls how a punctual light's intensity falls off over distance.
///
/// All modes are clamped to zero at the configured light range.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect)]
#[reflect(Default, Debug, Clone, PartialEq, Hash)]
pub enum LightFalloff {
/// Uses Bevy's existing physically-motivated inverse-square attenuation.
#[default]
InverseSquare,
/// Decreases intensity linearly from the light origin to its range.
Linear,
/// Uses an exponential falloff curve over the light's range.
Exponential,
}

impl LightFalloff {
/// The number of built-in falloff modes.
pub const VARIANT_COUNT: usize = 3;

/// Returns the value encoded into GPU-side flag bits for this mode.
pub const fn shader_index(self) -> u32 {
match self {
Self::InverseSquare => 0,
Self::Linear => 1,
Self::Exponential => 2,
}
}

/// Returns the stable ordering bucket used for clustered light sorting.
pub const fn bucket_index(self) -> usize {
self.shader_index() as usize
}
}
6 changes: 5 additions & 1 deletion crates/bevy_light/src/point_light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use bevy_math::{primitives::ViewFrustum, Mat4};
use bevy_reflect::prelude::*;
use bevy_transform::components::{GlobalTransform, Transform};

use crate::{cluster::ClusterVisibilityClass, light_consts};
use crate::{cluster::ClusterVisibilityClass, light_consts, LightFalloff};

/// A light that emits light in all directions from a central point.
///
Expand Down Expand Up @@ -57,6 +57,9 @@ pub struct PointLight {
/// lighting cut-offs.
pub range: f32,

/// Controls how this light attenuates over distance within its range.
pub falloff: LightFalloff,

/// Simulates a light source coming from a spherical volume with the given
/// radius.
///
Expand Down Expand Up @@ -131,6 +134,7 @@ impl Default for PointLight {
color: Color::WHITE,
intensity: light_consts::lumens::VERY_LARGE_CINEMA_LIGHT,
range: 20.0,
falloff: LightFalloff::InverseSquare,
radius: 0.0,
shadow_maps_enabled: false,
contact_shadows_enabled: false,
Expand Down
6 changes: 5 additions & 1 deletion crates/bevy_light/src/spot_light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use bevy_math::{primitives::ViewFrustum, Affine3A, Dir3, Mat3, Mat4, Vec3};
use bevy_reflect::prelude::*;
use bevy_transform::components::{GlobalTransform, Transform};

use crate::cluster::ClusterVisibilityClass;
use crate::{cluster::ClusterVisibilityClass, LightFalloff};

/// A light that emits light in a given direction from a central point.
///
Expand Down Expand Up @@ -39,6 +39,9 @@ pub struct SpotLight {
/// Consequently, you should set this value to be only the size that you need.
pub range: f32,

/// Controls how this light attenuates over distance within its range.
pub falloff: LightFalloff,

/// Simulates a light source coming from a spherical volume with the given
/// radius.
///
Expand Down Expand Up @@ -148,6 +151,7 @@ impl Default for SpotLight {
// this would be way too bright.
intensity: 1_000_000.0,
range: 20.0,
falloff: LightFalloff::InverseSquare,
radius: 0.0,
shadow_maps_enabled: false,
contact_shadows_enabled: false,
Expand Down
7 changes: 6 additions & 1 deletion crates/bevy_pbr/src/cluster/cluster_allocate.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ fn allocate_local_main(
// of the rasterizer.
scratchpad_offsets_and_counts.data[global_id.x][0u] = vec4(0u);
scratchpad_offsets_and_counts.data[global_id.x][1u] = vec4(0u);
scratchpad_offsets_and_counts.data[global_id.x][2u] = vec4(0u);
}
}

Expand Down Expand Up @@ -123,5 +124,9 @@ fn cluster_object_count(cluster_index: u32) -> u32 {
offsets_and_counts.data[cluster_index][0].z +
offsets_and_counts.data[cluster_index][0].w +
offsets_and_counts.data[cluster_index][1].x +
offsets_and_counts.data[cluster_index][1].y;
offsets_and_counts.data[cluster_index][1].y +
offsets_and_counts.data[cluster_index][1].z +
offsets_and_counts.data[cluster_index][1].w +
offsets_and_counts.data[cluster_index][2].x +
offsets_and_counts.data[cluster_index][2].y;
}
Loading
Loading