Skip to content

Commit

Permalink
Spotlights (bevyengine#4715)
Browse files Browse the repository at this point in the history
# Objective

add spotlight support

## Solution / Changelog

- add spotlight angles (inner, outer) to ``PointLight`` struct. emitted light is linearly attenuated from 100% to 0% as angle tends from inner to outer. Direction is taken from the existing transform rotation.
- add spotlight direction (vec3) and angles (f32,f32) to ``GpuPointLight`` struct (60 bytes -> 80 bytes) in ``pbr/render/lights.rs`` and ``mesh_view_bind_group.wgsl``
- reduce no-buffer-support max point light count to 204 due to above
- use spotlight data to attenuate light in ``pbr.wgsl``
- do additional cluster culling on spotlights to minimise cost in ``assign_lights_to_clusters``
- changed one of the lights in the lighting demo to a spotlight
- also added a ``spotlight`` demo - probably not justified but so reviewers can see it more easily

## notes

increasing the size of the GpuPointLight struct on my machine reduces the FPS of ``many_lights -- sphere`` from ~150fps to 140fps. 

i thought this was a reasonable tradeoff, and felt better than handling spotlights separately which is possible but would mean introducing a new bind group, refactoring light-assignment code and adding new spotlight-specific code in pbr.wgsl. the FPS impact for smaller numbers of lights should be very small.

the cluster culling strategy reintroduces the cluster aabb code which was recently removed... sorry. the aabb is used to get a cluster bounding sphere, which can then be tested fairly efficiently using the strategy described at the end of https://bartwronski.com/2017/04/13/cull-that-cone/. this works well with roughly cubic clusters (where the cluster z size is close to the same as x/y size), less well for other cases like single Z slice / tiled forward rendering. In the worst case we will end up just keeping the culling of the equivalent point light.

Co-authored-by: François <mockersf@gmail.com>
  • Loading branch information
2 people authored and james7132 committed Oct 28, 2022
1 parent 9758be6 commit 38f9ccd
Show file tree
Hide file tree
Showing 15 changed files with 1,027 additions and 126 deletions.
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,16 @@ description = "Illustrates various lighting options in a simple scene"
category = "3D Rendering"
wasm = true

[[example]]
name = "spotlight"
path = "examples/3d/spotlight.rs"

[package.metadata.example.spotlight]
name = "Spotlight"
description = "Illustrates spot lights"
category = "3D Rendering"
wasm = true

[[example]]
name = "load_gltf"
path = "examples/3d/load_gltf.rs"
Expand Down
32 changes: 28 additions & 4 deletions crates/bevy_gltf/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use bevy_log::warn;
use bevy_math::{Mat4, Vec3};
use bevy_pbr::{
AlphaMode, DirectionalLight, DirectionalLightBundle, PbrBundle, PointLight, PointLightBundle,
StandardMaterial,
SpotLight, SpotLightBundle, StandardMaterial,
};
use bevy_render::{
camera::{
Expand Down Expand Up @@ -862,9 +862,33 @@ fn load_node(
}
}
gltf::khr_lights_punctual::Kind::Spot {
inner_cone_angle: _inner_cone_angle,
outer_cone_angle: _outer_cone_angle,
} => warn!("Spot lights are not yet supported."),
inner_cone_angle,
outer_cone_angle,
} => {
let mut entity = parent.spawn_bundle(SpotLightBundle {
spot_light: SpotLight {
color: Color::from(light.color()),
// NOTE: KHR_punctual_lights defines the intensity units for spot lights in
// candela (lm/sr) which is luminous intensity and we need luminous power.
// For a spot light, we map luminous power = 4 * pi * luminous intensity
intensity: light.intensity() * std::f32::consts::PI * 4.0,
range: light.range().unwrap_or(20.0),
radius: light.range().unwrap_or(0.0),
inner_angle: inner_cone_angle,
outer_angle: outer_cone_angle,
..Default::default()
},
..Default::default()
});
if let Some(name) = light.name() {
entity.insert(Name::new(name.to_string()));
}
if let Some(extras) = light.extras() {
entity.insert(super::GltfExtras {
value: extras.get().to_string(),
});
}
}
}
}

Expand Down
14 changes: 13 additions & 1 deletion crates/bevy_pbr/src/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{DirectionalLight, Material, PointLight, StandardMaterial};
use crate::{DirectionalLight, Material, PointLight, SpotLight, StandardMaterial};
use bevy_asset::Handle;
use bevy_ecs::{bundle::Bundle, component::Component, reflect::ReflectComponent};
use bevy_reflect::Reflect;
Expand Down Expand Up @@ -75,6 +75,18 @@ pub struct PointLightBundle {
pub visibility: Visibility,
}

/// A component bundle for spot light entities
#[derive(Debug, Bundle, Default)]
pub struct SpotLightBundle {
pub spot_light: SpotLight,
pub visible_entities: VisibleEntities,
pub frustum: Frustum,
pub transform: Transform,
pub global_transform: GlobalTransform,
/// Enables or disables the light
pub visibility: Visibility,
}

/// A component bundle for [`DirectionalLight`] entities.
#[derive(Debug, Bundle, Default)]
pub struct DirectionalLightBundle {
Expand Down
22 changes: 16 additions & 6 deletions crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ pub mod prelude {
#[doc(hidden)]
pub use crate::{
alpha::AlphaMode,
bundle::{DirectionalLightBundle, MaterialMeshBundle, PbrBundle, PointLightBundle},
light::{AmbientLight, DirectionalLight, PointLight},
bundle::{
DirectionalLightBundle, MaterialMeshBundle, PbrBundle, PointLightBundle,
SpotLightBundle,
},
light::{AmbientLight, DirectionalLight, PointLight, SpotLight},
material::{Material, MaterialPlugin},
pbr_material::StandardMaterial,
};
Expand Down Expand Up @@ -123,6 +126,7 @@ impl Plugin for PbrPlugin {
app.register_type::<CubemapVisibleEntities>()
.register_type::<DirectionalLight>()
.register_type::<PointLight>()
.register_type::<SpotLight>()
.add_plugin(MeshRenderPlugin)
.add_plugin(MaterialPlugin::<StandardMaterial>::default())
.register_type::<AmbientLight>()
Expand Down Expand Up @@ -152,13 +156,20 @@ impl Plugin for PbrPlugin {
.add_system_to_stage(
CoreStage::PostUpdate,
update_directional_light_frusta
.label(SimulationLightSystems::UpdateDirectionalLightFrusta)
.label(SimulationLightSystems::UpdateLightFrusta)
.after(TransformSystem::TransformPropagate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_point_light_frusta
.label(SimulationLightSystems::UpdatePointLightFrusta)
.label(SimulationLightSystems::UpdateLightFrusta)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::AssignLightsToClusters),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_spot_light_frusta
.label(SimulationLightSystems::UpdateLightFrusta)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::AssignLightsToClusters),
)
Expand All @@ -168,8 +179,7 @@ impl Plugin for PbrPlugin {
.label(SimulationLightSystems::CheckLightVisibility)
.after(TransformSystem::TransformPropagate)
.after(VisibilitySystems::CalculateBounds)
.after(SimulationLightSystems::UpdateDirectionalLightFrusta)
.after(SimulationLightSystems::UpdatePointLightFrusta)
.after(SimulationLightSystems::UpdateLightFrusta)
// NOTE: This MUST be scheduled AFTER the core renderer visibility check
// because that resets entity ComputedVisibility for the first view
// which would override any results from this otherwise
Expand Down
Loading

0 comments on commit 38f9ccd

Please sign in to comment.