Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_light/src/point_light.rs
9395 views
1
use bevy_asset::Handle;
2
use bevy_camera::{
3
primitives::{CubeMapFace, CubemapFrusta, CubemapLayout, Frustum, Sphere, CUBE_MAP_FACES},
4
visibility::{self, CubemapVisibleEntities, ViewVisibility, Visibility, VisibilityClass},
5
};
6
use bevy_color::Color;
7
use bevy_ecs::prelude::*;
8
use bevy_image::Image;
9
use bevy_math::{primitives::ViewFrustum, Mat4};
10
use bevy_reflect::prelude::*;
11
use bevy_transform::components::{GlobalTransform, Transform};
12
13
use crate::{cluster::ClusterVisibilityClass, light_consts};
14
15
/// A light that emits light in all directions from a central point.
16
///
17
/// Real-world values for `intensity` (luminous power in lumens) based on the electrical power
18
/// consumption of the type of real-world light are:
19
///
20
/// | Luminous Power (lumen) (i.e. the intensity member) | Incandescent non-halogen (Watts) | Incandescent halogen (Watts) | Compact fluorescent (Watts) | LED (Watts) |
21
/// |------|-----|----|--------|-------|
22
/// | 200 | 25 | | 3-5 | 3 |
23
/// | 450 | 40 | 29 | 9-11 | 5-8 |
24
/// | 800 | 60 | | 13-15 | 8-12 |
25
/// | 1100 | 75 | 53 | 18-20 | 10-16 |
26
/// | 1600 | 100 | 72 | 24-28 | 14-17 |
27
/// | 2400 | 150 | | 30-52 | 24-30 |
28
/// | 3100 | 200 | | 49-75 | 32 |
29
/// | 4000 | 300 | | 75-100 | 40.5 |
30
///
31
/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lumen_(unit)#Lighting)
32
///
33
/// ## Shadows
34
///
35
/// To enable shadows, set the `shadow_maps_enabled` property to `true`.
36
///
37
/// To control the resolution of the shadow maps, use the [`PointLightShadowMap`] resource.
38
#[derive(Component, Debug, Clone, Copy, Reflect)]
39
#[reflect(Component, Default, Debug, Clone)]
40
#[require(
41
CubemapFrusta,
42
CubemapVisibleEntities,
43
Transform,
44
Visibility,
45
VisibilityClass
46
)]
47
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]
48
pub struct PointLight {
49
/// The color of this light source.
50
pub color: Color,
51
52
/// Luminous power in lumens, representing the amount of light emitted by this source in all directions.
53
pub intensity: f32,
54
55
/// Cut-off for the light's area-of-effect. Fragments outside this range will not be affected by
56
/// this light at all, so it's important to tune this together with `intensity` to prevent hard
57
/// lighting cut-offs.
58
pub range: f32,
59
60
/// Simulates a light source coming from a spherical volume with the given
61
/// radius.
62
///
63
/// This affects the size of specular highlights created by this light, as
64
/// well as the soft shadow penumbra size. Because of this, large values may
65
/// not produce the intended result -- for example, light radius does not
66
/// affect shadow softness or diffuse lighting.
67
pub radius: f32,
68
69
/// Whether this light casts shadows.
70
pub shadow_maps_enabled: bool,
71
72
/// Whether this light casts contact shadows.
73
pub contact_shadows_enabled: bool,
74
75
/// Whether soft shadows are enabled.
76
///
77
/// Soft shadows, also known as *percentage-closer soft shadows* or PCSS,
78
/// cause shadows to become blurrier (i.e. their penumbra increases in
79
/// radius) as they extend away from objects. The blurriness of the shadow
80
/// depends on the [`PointLight::radius`] of the light; larger lights result
81
/// in larger penumbras and therefore blurrier shadows.
82
///
83
/// Currently, soft shadows are rather noisy if not using the temporal mode.
84
/// If you enable soft shadows, consider choosing
85
/// [`ShadowFilteringMethod::Temporal`] and enabling temporal antialiasing
86
/// (TAA) to smooth the noise out over time.
87
///
88
/// Note that soft shadows are significantly more expensive to render than
89
/// hard shadows.
90
///
91
/// [`ShadowFilteringMethod::Temporal`]: crate::ShadowFilteringMethod::Temporal
92
#[cfg(feature = "experimental_pbr_pcss")]
93
pub soft_shadows_enabled: bool,
94
95
/// Whether this point light contributes diffuse lighting to meshes with
96
/// lightmaps.
97
///
98
/// Set this to false if your lightmap baking tool bakes the direct diffuse
99
/// light from this point light into the lightmaps in order to avoid
100
/// counting the radiance from this light twice. Note that the specular
101
/// portion of the light is always considered, because Bevy currently has no
102
/// means to bake specular light.
103
///
104
/// By default, this is set to true.
105
pub affects_lightmapped_mesh_diffuse: bool,
106
107
/// A bias used when sampling shadow maps to avoid "shadow-acne", or false shadow occlusions
108
/// that happen as a result of shadow-map fragments not mapping 1:1 to screen-space fragments.
109
/// Too high of a depth bias can lead to shadows detaching from their casters, or
110
/// "peter-panning". This bias can be tuned together with `shadow_normal_bias` to correct shadow
111
/// artifacts for a given scene.
112
pub shadow_depth_bias: f32,
113
114
/// A bias applied along the direction of the fragment's surface normal. It is scaled to the
115
/// shadow map's texel size so that it can be small close to the camera and gets larger further
116
/// away.
117
pub shadow_normal_bias: f32,
118
119
/// The distance from the light to near Z plane in the shadow map.
120
///
121
/// Objects closer than this distance to the light won't cast shadows.
122
/// Setting this higher increases the shadow map's precision.
123
///
124
/// This only has an effect if shadows are enabled.
125
pub shadow_map_near_z: f32,
126
}
127
128
impl Default for PointLight {
129
fn default() -> Self {
130
PointLight {
131
color: Color::WHITE,
132
intensity: light_consts::lumens::VERY_LARGE_CINEMA_LIGHT,
133
range: 20.0,
134
radius: 0.0,
135
shadow_maps_enabled: false,
136
contact_shadows_enabled: false,
137
affects_lightmapped_mesh_diffuse: true,
138
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
139
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
140
shadow_map_near_z: Self::DEFAULT_SHADOW_MAP_NEAR_Z,
141
#[cfg(feature = "experimental_pbr_pcss")]
142
soft_shadows_enabled: false,
143
}
144
}
145
}
146
147
impl PointLight {
148
/// The default value of [`PointLight::shadow_depth_bias`].
149
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.08;
150
/// The default value of [`PointLight::shadow_normal_bias`].
151
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6;
152
/// The default value of [`PointLight::shadow_map_near_z`].
153
pub const DEFAULT_SHADOW_MAP_NEAR_Z: f32 = 0.1;
154
}
155
156
/// Add to a [`PointLight`] to add a light texture effect.
157
/// A texture mask is applied to the light source to modulate its intensity,
158
/// simulating patterns like window shadows, gobo/cookie effects, or soft falloffs.
159
#[derive(Clone, Component, Debug, Reflect)]
160
#[reflect(Component, Debug)]
161
#[require(PointLight)]
162
pub struct PointLightTexture {
163
/// The texture image. Only the R channel is read.
164
pub image: Handle<Image>,
165
/// The cubemap layout. The image should be a packed cubemap in one of the formats described by the [`CubemapLayout`] enum.
166
pub cubemap_layout: CubemapLayout,
167
}
168
169
/// Controls the resolution of [`PointLight`] shadow maps.
170
///
171
/// ```
172
/// # use bevy_app::prelude::*;
173
/// # use bevy_light::PointLightShadowMap;
174
/// App::new()
175
/// .insert_resource(PointLightShadowMap { size: 2048 });
176
/// ```
177
#[derive(Resource, Clone, Debug, Reflect)]
178
#[reflect(Resource, Debug, Default, Clone)]
179
pub struct PointLightShadowMap {
180
/// The width and height of each of the 6 faces of the cubemap.
181
///
182
/// Defaults to `1024`.
183
pub size: usize,
184
}
185
186
impl Default for PointLightShadowMap {
187
fn default() -> Self {
188
Self { size: 1024 }
189
}
190
}
191
192
/// A system that updates the bounding [`Sphere`] for changed point lights.
193
///
194
/// The [`Sphere`] component is used for frustum culling.
195
pub fn update_point_light_bounding_spheres(
196
mut commands: Commands,
197
point_lights_query: Query<
198
(Entity, &PointLight, &GlobalTransform),
199
Or<(Changed<PointLight>, Changed<GlobalTransform>)>,
200
>,
201
) {
202
for (point_light_entity, point_light, global_transform) in &point_lights_query {
203
commands.entity(point_light_entity).insert(Sphere {
204
center: global_transform.translation_vec3a(),
205
radius: point_light.range,
206
});
207
}
208
}
209
210
// NOTE: Run this after assign_lights_to_clusters!
211
/// Updates the frusta for all visible shadow mapped [`PointLight`]s.
212
pub fn update_point_light_frusta(
213
mut views: Query<
214
(
215
&GlobalTransform,
216
&PointLight,
217
&mut CubemapFrusta,
218
&ViewVisibility,
219
),
220
Or<(
221
Changed<GlobalTransform>,
222
Changed<PointLight>,
223
Changed<ViewVisibility>,
224
)>,
225
>,
226
) {
227
let view_rotations = CUBE_MAP_FACES
228
.iter()
229
.map(|CubeMapFace { target, up }| Transform::IDENTITY.looking_at(*target, *up))
230
.collect::<Vec<_>>();
231
232
for (transform, point_light, mut cubemap_frusta, view_visibility) in &mut views {
233
// The frusta are used for culling meshes to the light for shadow mapping
234
// so if shadow mapping is disabled for this light, then the frusta are
235
// not needed.
236
// Also, if the light is not relevant for any cluster, it will not be in the
237
// global lights set and so there is no need to update its frusta.
238
if !point_light.shadow_maps_enabled || !view_visibility.get() {
239
continue;
240
}
241
242
let clip_from_view = Mat4::perspective_infinite_reverse_rh(
243
core::f32::consts::FRAC_PI_2,
244
1.0,
245
point_light.shadow_map_near_z,
246
);
247
248
// ignore scale because we don't want to effectively scale light radius and range
249
// by applying those as a view transform to shadow map rendering of objects
250
// and ignore rotation because we want the shadow map projections to align with the axes
251
let view_translation = Transform::from_translation(transform.translation());
252
let view_backward = transform.back();
253
254
for (view_rotation, frustum) in view_rotations.iter().zip(cubemap_frusta.iter_mut()) {
255
let world_from_view = view_translation * *view_rotation;
256
let clip_from_world = clip_from_view * world_from_view.compute_affine().inverse();
257
258
*frustum = Frustum(ViewFrustum::from_clip_from_world_custom_far(
259
&clip_from_world,
260
&transform.translation(),
261
&view_backward,
262
point_light.range,
263
));
264
}
265
}
266
}
267
268