Skip to content

Commit 73f4a9d

Browse files
committed
Directional light (#2112)
This PR adds a `DirectionalLight` component to bevy_pbr.
1 parent d1f4014 commit 73f4a9d

File tree

5 files changed

+248
-74
lines changed

5 files changed

+248
-74
lines changed

crates/bevy_pbr/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ pub use material::*;
1010

1111
pub mod prelude {
1212
#[doc(hidden)]
13-
pub use crate::{entity::*, light::PointLight, material::StandardMaterial};
13+
pub use crate::{
14+
entity::*,
15+
light::{DirectionalLight, PointLight},
16+
material::StandardMaterial,
17+
};
1418
}
1519

1620
use bevy_app::prelude::*;

crates/bevy_pbr/src/light.rs

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use bevy_core::Byteable;
22
use bevy_ecs::reflect::ReflectComponent;
3+
use bevy_math::Vec3;
34
use bevy_reflect::Reflect;
45
use bevy_render::color::Color;
56
use bevy_transform::components::GlobalTransform;
67

78
/// A point light
8-
#[derive(Debug, Reflect)]
9+
#[derive(Debug, Clone, Copy, Reflect)]
910
#[reflect(Component)]
1011
pub struct PointLight {
1112
pub color: Color,
@@ -37,7 +38,7 @@ pub(crate) struct PointLightUniform {
3738
unsafe impl Byteable for PointLightUniform {}
3839

3940
impl PointLightUniform {
40-
pub fn from(light: &PointLight, global_transform: &GlobalTransform) -> PointLightUniform {
41+
pub fn new(light: &PointLight, global_transform: &GlobalTransform) -> PointLightUniform {
4142
let (x, y, z) = global_transform.translation.into();
4243

4344
// premultiply color by intensity
@@ -52,6 +53,109 @@ impl PointLightUniform {
5253
}
5354
}
5455

56+
/// A Directional light.
57+
///
58+
/// Directional lights don't exist in reality but they are a good
59+
/// approximation for light sources VERY far away, like the sun or
60+
/// the moon.
61+
///
62+
/// Valid values for `illuminance` are:
63+
///
64+
/// | Illuminance (lux) | Surfaces illuminated by |
65+
/// |-------------------|------------------------------------------------|
66+
/// | 0.0001 | Moonless, overcast night sky (starlight) |
67+
/// | 0.002 | Moonless clear night sky with airglow |
68+
/// | 0.05–0.3 | Full moon on a clear night |
69+
/// | 3.4 | Dark limit of civil twilight under a clear sky |
70+
/// | 20–50 | Public areas with dark surroundings |
71+
/// | 50 | Family living room lights |
72+
/// | 80 | Office building hallway/toilet lighting |
73+
/// | 100 | Very dark overcast day |
74+
/// | 150 | Train station platforms |
75+
/// | 320–500 | Office lighting |
76+
/// | 400 | Sunrise or sunset on a clear day. |
77+
/// | 1000 | Overcast day; typical TV studio lighting |
78+
/// | 10,000–25,000 | Full daylight (not direct sun) |
79+
/// | 32,000–100,000 | Direct sunlight |
80+
///
81+
/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux)
82+
#[derive(Debug, Clone, Copy, Reflect)]
83+
#[reflect(Component)]
84+
pub struct DirectionalLight {
85+
pub color: Color,
86+
pub illuminance: f32,
87+
direction: Vec3,
88+
}
89+
90+
impl DirectionalLight {
91+
/// Create a new directional light component.
92+
pub fn new(color: Color, illuminance: f32, direction: Vec3) -> Self {
93+
DirectionalLight {
94+
color,
95+
illuminance,
96+
direction: direction.normalize(),
97+
}
98+
}
99+
100+
/// Set direction of light.
101+
pub fn set_direction(&mut self, direction: Vec3) {
102+
self.direction = direction.normalize();
103+
}
104+
105+
pub fn get_direction(&self) -> Vec3 {
106+
self.direction
107+
}
108+
}
109+
110+
impl Default for DirectionalLight {
111+
fn default() -> Self {
112+
DirectionalLight {
113+
color: Color::rgb(1.0, 1.0, 1.0),
114+
illuminance: 100000.0,
115+
direction: Vec3::new(0.0, -1.0, 0.0),
116+
}
117+
}
118+
}
119+
120+
#[repr(C)]
121+
#[derive(Debug, Clone, Copy)]
122+
pub(crate) struct DirectionalLightUniform {
123+
pub dir: [f32; 4],
124+
pub color: [f32; 4],
125+
}
126+
127+
unsafe impl Byteable for DirectionalLightUniform {}
128+
129+
impl DirectionalLightUniform {
130+
pub fn new(light: &DirectionalLight) -> DirectionalLightUniform {
131+
// direction is negated to be ready for N.L
132+
let dir: [f32; 4] = [
133+
-light.direction.x,
134+
-light.direction.y,
135+
-light.direction.z,
136+
0.0,
137+
];
138+
139+
// convert from illuminance (lux) to candelas
140+
//
141+
// exposure is hard coded at the moment but should be replaced
142+
// by values coming from the camera
143+
// see: https://google.github.io/filament/Filament.html#imagingpipeline/physicallybasedcamera/exposuresettings
144+
const APERTURE: f32 = 4.0;
145+
const SHUTTER_SPEED: f32 = 1.0 / 250.0;
146+
const SENSITIVITY: f32 = 100.0;
147+
let ev100 = f32::log2(APERTURE * APERTURE / SHUTTER_SPEED) - f32::log2(SENSITIVITY / 100.0);
148+
let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2);
149+
let intensity = light.illuminance * exposure;
150+
151+
// premultiply color by intensity
152+
// we don't use the alpha at all, so no reason to multiply only [0..3]
153+
let color: [f32; 4] = (light.color * intensity).into();
154+
155+
DirectionalLightUniform { dir, color }
156+
}
157+
}
158+
55159
// Ambient light color.
56160
#[derive(Debug)]
57161
pub struct AmbientLight {

crates/bevy_pbr/src/render_graph/lights_node.rs

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::{
2-
light::{AmbientLight, PointLight, PointLightUniform},
2+
light::{
3+
AmbientLight, DirectionalLight, DirectionalLightUniform, PointLight, PointLightUniform,
4+
},
35
render_graph::uniform,
46
};
57
use bevy_core::{AsBytes, Byteable};
@@ -21,12 +23,14 @@ use bevy_transform::prelude::*;
2123
pub struct LightsNode {
2224
command_queue: CommandQueue,
2325
max_point_lights: usize,
26+
max_dir_lights: usize,
2427
}
2528

2629
impl LightsNode {
27-
pub fn new(max_lights: usize) -> Self {
30+
pub fn new(max_point_lights: usize, max_dir_lights: usize) -> Self {
2831
LightsNode {
29-
max_point_lights: max_lights,
32+
max_point_lights,
33+
max_dir_lights,
3034
command_queue: CommandQueue::default(),
3135
}
3236
}
@@ -48,6 +52,8 @@ impl Node for LightsNode {
4852
#[derive(Debug, Clone, Copy)]
4953
struct LightCount {
5054
// storing as a `[u32; 4]` for memory alignement
55+
// Index 0 is for point lights,
56+
// Index 1 is for directional lights
5157
pub num_lights: [u32; 4],
5258
}
5359

@@ -59,6 +65,7 @@ impl SystemNode for LightsNode {
5965
config.0 = Some(LightsNodeSystemState {
6066
command_queue: self.command_queue.clone(),
6167
max_point_lights: self.max_point_lights,
68+
max_dir_lights: self.max_dir_lights,
6269
light_buffer: None,
6370
staging_buffer: None,
6471
})
@@ -74,6 +81,7 @@ pub struct LightsNodeSystemState {
7481
staging_buffer: Option<BufferId>,
7582
command_queue: CommandQueue,
7683
max_point_lights: usize,
84+
max_dir_lights: usize,
7785
}
7886

7987
pub fn lights_node_system(
@@ -83,7 +91,8 @@ pub fn lights_node_system(
8391
// TODO: this write on RenderResourceBindings will prevent this system from running in parallel
8492
// with other systems that do the same
8593
mut render_resource_bindings: ResMut<RenderResourceBindings>,
86-
query: Query<(&PointLight, &GlobalTransform)>,
94+
point_lights: Query<(&PointLight, &GlobalTransform)>,
95+
dir_lights: Query<&DirectionalLight>,
8796
) {
8897
let state = &mut state;
8998
let render_resource_context = &**render_resource_context;
@@ -92,16 +101,31 @@ pub fn lights_node_system(
92101
let ambient_light: [f32; 4] =
93102
(ambient_light_resource.color * ambient_light_resource.brightness).into();
94103
let ambient_light_size = std::mem::size_of::<[f32; 4]>();
95-
let point_light_count = query.iter().len().min(state.max_point_lights);
96-
let size = std::mem::size_of::<PointLightUniform>();
104+
105+
let point_light_count = point_lights.iter().len().min(state.max_point_lights);
106+
let point_light_size = std::mem::size_of::<PointLightUniform>();
107+
let point_light_array_size = point_light_size * point_light_count;
108+
let point_light_array_max_size = point_light_size * state.max_point_lights;
109+
110+
let dir_light_count = dir_lights.iter().len().min(state.max_dir_lights);
111+
let dir_light_size = std::mem::size_of::<DirectionalLightUniform>();
112+
let dir_light_array_size = dir_light_size * dir_light_count;
113+
let dir_light_array_max_size = dir_light_size * state.max_dir_lights;
114+
97115
let light_count_size = ambient_light_size + std::mem::size_of::<LightCount>();
98-
let point_light_array_size = size * point_light_count;
99-
let point_light_array_max_size = size * state.max_point_lights;
100-
let current_point_light_uniform_size = light_count_size + point_light_array_size;
101-
let max_light_uniform_size = light_count_size + point_light_array_max_size;
116+
117+
let point_light_uniform_start = light_count_size;
118+
let point_light_uniform_end = light_count_size + point_light_array_size;
119+
120+
let dir_light_uniform_start = light_count_size + point_light_array_max_size;
121+
let dir_light_uniform_end =
122+
light_count_size + point_light_array_max_size + dir_light_array_size;
123+
124+
let max_light_uniform_size =
125+
light_count_size + point_light_array_max_size + dir_light_array_max_size;
102126

103127
if let Some(staging_buffer) = state.staging_buffer {
104-
if point_light_count == 0 {
128+
if point_light_count == 0 && dir_light_count == 0 {
105129
return;
106130
}
107131

@@ -133,23 +157,33 @@ pub fn lights_node_system(
133157
let staging_buffer = state.staging_buffer.unwrap();
134158
render_resource_context.write_mapped_buffer(
135159
staging_buffer,
136-
0..current_point_light_uniform_size as u64,
160+
0..max_light_uniform_size as u64,
137161
&mut |data, _renderer| {
138162
// ambient light
139163
data[0..ambient_light_size].copy_from_slice(ambient_light.as_bytes());
140164

141165
// light count
142-
data[ambient_light_size..light_count_size]
143-
.copy_from_slice([point_light_count as u32, 0, 0, 0].as_bytes());
166+
data[ambient_light_size..light_count_size].copy_from_slice(
167+
[point_light_count as u32, dir_light_count as u32, 0, 0].as_bytes(),
168+
);
144169

145-
// light array
146-
for ((point_light, global_transform), slot) in query.iter().zip(
147-
data[light_count_size..current_point_light_uniform_size].chunks_exact_mut(size),
170+
// point light array
171+
for ((point_light, global_transform), slot) in point_lights.iter().zip(
172+
data[point_light_uniform_start..point_light_uniform_end]
173+
.chunks_exact_mut(point_light_size),
148174
) {
149175
slot.copy_from_slice(
150-
PointLightUniform::from(&point_light, &global_transform).as_bytes(),
176+
PointLightUniform::new(&point_light, &global_transform).as_bytes(),
151177
);
152178
}
179+
180+
// directional light array
181+
for (dir_light, slot) in dir_lights.iter().zip(
182+
data[dir_light_uniform_start..dir_light_uniform_end]
183+
.chunks_exact_mut(dir_light_size),
184+
) {
185+
slot.copy_from_slice(DirectionalLightUniform::new(&dir_light).as_bytes());
186+
}
153187
},
154188
);
155189
render_resource_context.unmap_buffer(staging_buffer);

crates/bevy_pbr/src/render_graph/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use bevy_render::{
2727
use bevy_transform::prelude::GlobalTransform;
2828

2929
pub const MAX_POINT_LIGHTS: usize = 10;
30+
pub const MAX_DIRECTIONAL_LIGHTS: usize = 1;
3031
pub(crate) fn add_pbr_graph(world: &mut World) {
3132
{
3233
let mut graph = world.get_resource_mut::<RenderGraph>().unwrap();
@@ -39,7 +40,10 @@ pub(crate) fn add_pbr_graph(world: &mut World) {
3940
AssetRenderResourcesNode::<StandardMaterial>::new(true),
4041
);
4142

42-
graph.add_system_node(node::LIGHTS, LightsNode::new(MAX_POINT_LIGHTS));
43+
graph.add_system_node(
44+
node::LIGHTS,
45+
LightsNode::new(MAX_POINT_LIGHTS, MAX_DIRECTIONAL_LIGHTS),
46+
);
4347

4448
// TODO: replace these with "autowire" groups
4549
graph

0 commit comments

Comments
 (0)