Skip to content

Commit 827fc56

Browse files
feat: add spot light shadow mapping
1 parent 8aead2f commit 827fc56

13 files changed

Lines changed: 934 additions & 159 deletions

File tree

apps/shadows/src/main.rs

Lines changed: 254 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ freecs::ecs! {
1717
ShadowsDemoResources {
1818
time: f32,
1919
light_entity: Option<Entity>,
20+
light_cone_entity: Option<Entity>,
21+
spot_light_entity: Option<Entity>,
22+
flashlight_entity: Option<Entity>,
23+
flashlight_enabled: bool,
2024
spheres: Vec<(Entity, f32, f32)>,
2125
}
2226
}
@@ -27,6 +31,10 @@ impl State for ShadowsDemo {
2731

2832
self.resources.time = 0.0;
2933
self.resources.light_entity = None;
34+
self.resources.light_cone_entity = None;
35+
self.resources.spot_light_entity = None;
36+
self.resources.flashlight_entity = None;
37+
self.resources.flashlight_enabled = true;
3038
self.resources.spheres = Vec::new();
3139

3240
let floor = world.spawn_entities(
@@ -46,6 +54,8 @@ impl State for ShadowsDemo {
4654
scale: Vec3::new(30.0, 0.1, 20.0),
4755
},
4856
);
57+
world.set_global_transform(floor, GlobalTransform(nalgebra_glm::Mat4::identity()));
58+
world.set_local_transform_dirty(floor, LocalTransformDirty);
4959
world.set_render_mesh(
5060
floor,
5161
RenderMesh {
@@ -63,6 +73,42 @@ impl State for ShadowsDemo {
6373
);
6474
world.set_casts_shadow(floor, CastsShadow);
6575

76+
let wall = world.spawn_entities(
77+
LOCAL_TRANSFORM
78+
| LOCAL_TRANSFORM_DIRTY
79+
| GLOBAL_TRANSFORM
80+
| RENDER_MESH
81+
| MATERIAL
82+
| CASTS_SHADOW,
83+
1,
84+
)[0];
85+
world.set_local_transform(
86+
wall,
87+
LocalTransform {
88+
translation: Vec3::new(0.0, -3.0, -10.0),
89+
rotation: Quat::identity(),
90+
scale: Vec3::new(30.0, 20.0, 0.1),
91+
},
92+
);
93+
world.set_global_transform(wall, GlobalTransform(nalgebra_glm::Mat4::identity()));
94+
world.set_local_transform_dirty(wall, LocalTransformDirty);
95+
world.set_render_mesh(
96+
wall,
97+
RenderMesh {
98+
name: "Cube".to_string(),
99+
},
100+
);
101+
world.set_material(
102+
wall,
103+
Material {
104+
base_color: [0.7, 0.6, 0.5, 1.0],
105+
roughness: 0.9,
106+
metallic: 0.0,
107+
..Default::default()
108+
},
109+
);
110+
world.set_casts_shadow(wall, CastsShadow);
111+
66112
let torus = world.spawn_entities(
67113
LOCAL_TRANSFORM
68114
| LOCAL_TRANSFORM_DIRTY
@@ -84,6 +130,8 @@ impl State for ShadowsDemo {
84130
scale: Vec3::new(4.0, 4.0, 4.0),
85131
},
86132
);
133+
world.set_global_transform(torus, GlobalTransform(nalgebra_glm::Mat4::identity()));
134+
world.set_local_transform_dirty(torus, LocalTransformDirty);
87135
world.set_render_mesh(
88136
torus,
89137
RenderMesh {
@@ -109,6 +157,72 @@ impl State for ShadowsDemo {
109157
);
110158
world.set_casts_shadow(torus, CastsShadow);
111159

160+
let torus_light_cube = world.spawn_entities(
161+
LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM | RENDER_MESH | MATERIAL,
162+
1,
163+
)[0];
164+
world.set_local_transform(
165+
torus_light_cube,
166+
LocalTransform {
167+
translation: Vec3::new(0.0, -4.7, 0.0),
168+
rotation: Quat::identity(),
169+
scale: Vec3::new(0.5, 0.5, 0.5),
170+
},
171+
);
172+
world.set_global_transform(
173+
torus_light_cube,
174+
GlobalTransform(nalgebra_glm::Mat4::identity()),
175+
);
176+
world.set_local_transform_dirty(torus_light_cube, LocalTransformDirty);
177+
world.set_render_mesh(
178+
torus_light_cube,
179+
RenderMesh {
180+
name: "Cube".to_string(),
181+
},
182+
);
183+
world.set_material(
184+
torus_light_cube,
185+
Material {
186+
base_color: [1.0, 0.8, 0.6, 1.0],
187+
emissive_factor: [2.0, 1.6, 1.2],
188+
roughness: 0.0,
189+
metallic: 0.0,
190+
unlit: true,
191+
..Default::default()
192+
},
193+
);
194+
195+
let torus_point_light = world.spawn_entities(
196+
LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM | LIGHT,
197+
1,
198+
)[0];
199+
world.set_local_transform(
200+
torus_point_light,
201+
LocalTransform {
202+
translation: Vec3::new(0.0, -4.7, 0.0),
203+
rotation: Quat::identity(),
204+
scale: Vec3::new(1.0, 1.0, 1.0),
205+
},
206+
);
207+
world.set_global_transform(
208+
torus_point_light,
209+
GlobalTransform(nalgebra_glm::Mat4::identity()),
210+
);
211+
world.set_local_transform_dirty(torus_point_light, LocalTransformDirty);
212+
world.set_light(
213+
torus_point_light,
214+
Light {
215+
light_type: LightType::Point,
216+
color: Vec3::new(1.0, 0.8, 0.6),
217+
intensity: 10.0,
218+
range: 20.0,
219+
inner_cone_angle: 0.0,
220+
outer_cone_angle: 0.0,
221+
cast_shadows: true,
222+
shadow_bias: 0.007,
223+
},
224+
);
225+
112226
for _ in 0..18 {
113227
let x = rng.random_range(-15.0..15.0);
114228
let y = rng.random_range(-11.0..11.0);
@@ -134,6 +248,7 @@ impl State for ShadowsDemo {
134248
scale: Vec3::new(scale, scale, scale),
135249
},
136250
);
251+
world.set_local_transform_dirty(sphere, LocalTransformDirty);
137252
world.set_render_mesh(
138253
sphere,
139254
RenderMesh {
@@ -160,23 +275,20 @@ impl State for ShadowsDemo {
160275
LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM | LIGHT,
161276
1,
162277
)[0];
163-
world.set_local_transform(
164-
light,
165-
LocalTransform {
166-
translation: Vec3::new(0.0, 100.0, 0.0),
167-
rotation: nalgebra_glm::quat_angle_axis(
168-
-std::f32::consts::FRAC_PI_2,
169-
&Vec3::x_axis(),
170-
),
171-
scale: Vec3::new(1.0, 1.0, 1.0),
172-
},
173-
);
278+
let light_transform = LocalTransform {
279+
translation: Vec3::new(0.0, 100.0, 0.0),
280+
rotation: nalgebra_glm::quat_angle_axis(-std::f32::consts::FRAC_PI_2, &Vec3::x_axis()),
281+
scale: Vec3::new(1.0, 1.0, 1.0),
282+
};
283+
world.set_local_transform(light, light_transform);
284+
world.set_global_transform(light, GlobalTransform(light_transform.as_matrix()));
285+
world.set_local_transform_dirty(light, LocalTransformDirty);
174286
world.set_light(
175287
light,
176288
Light {
177289
light_type: LightType::Directional,
178290
color: Vec3::new(1.0, 1.0, 1.0),
179-
intensity: 3.0,
291+
intensity: 1.0,
180292
range: 100.0,
181293
inner_cone_angle: 0.0,
182294
outer_cone_angle: 0.0,
@@ -187,35 +299,85 @@ impl State for ShadowsDemo {
187299

188300
self.resources.light_entity = Some(light);
189301

302+
let light_cone = world.spawn_entities(
303+
LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM | RENDER_MESH | MATERIAL,
304+
1,
305+
)[0];
306+
world.set_local_transform(
307+
light_cone,
308+
LocalTransform {
309+
translation: Vec3::new(0.0, 100.0, 0.0),
310+
rotation: nalgebra_glm::quat_angle_axis(
311+
-std::f32::consts::FRAC_PI_2,
312+
&Vec3::x_axis(),
313+
),
314+
scale: Vec3::new(3.0, 3.0, 3.0),
315+
},
316+
);
317+
world.set_global_transform(light_cone, GlobalTransform(nalgebra_glm::Mat4::identity()));
318+
world.set_local_transform_dirty(light_cone, LocalTransformDirty);
319+
world.set_render_mesh(
320+
light_cone,
321+
RenderMesh {
322+
name: "Cone".to_string(),
323+
},
324+
);
325+
world.set_material(
326+
light_cone,
327+
Material {
328+
base_color: [1.0, 1.0, 0.8, 1.0],
329+
emissive_factor: [1.0, 1.0, 0.8],
330+
roughness: 0.0,
331+
metallic: 0.0,
332+
unlit: true,
333+
..Default::default()
334+
},
335+
);
336+
337+
self.resources.light_cone_entity = Some(light_cone);
338+
190339
let camera_position = Vec3::new(0.0, 10.0, 20.0);
191340
let camera = spawn_camera(world, camera_position, "Main Camera".to_string());
341+
if let Some(camera_transform) = world.get_local_transform(camera) {
342+
world.set_global_transform(camera, GlobalTransform(camera_transform.as_matrix()));
343+
}
192344
world.resources.active_camera = Some(camera);
193-
}
194345

195-
fn run_systems(&mut self, world: &mut World) {
196-
let delta = world.resources.window.timing.delta_time;
197-
self.resources.time += delta;
198-
199-
if let Some(light_entity) = self.resources.light_entity {
200-
if let Some(mut transform) = world.get_local_transform(light_entity).cloned() {
201-
let x = 50.0 * self.resources.time.sin();
202-
let z = 50.0 * self.resources.time.cos();
203-
transform.translation.x = x;
204-
transform.translation.z = z;
346+
let flashlight = world.spawn_entities(
347+
LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM | LIGHT | PARENT,
348+
1,
349+
)[0];
205350

206-
let target = Vec3::zeros();
207-
let direction = (target - transform.translation).normalize();
351+
let flashlight_transform = LocalTransform {
352+
translation: Vec3::new(0.5, -1.2, 0.3),
353+
rotation: Quat::identity(),
354+
scale: Vec3::new(1.0, 1.0, 1.0),
355+
};
356+
world.set_local_transform(flashlight, flashlight_transform);
357+
world.set_parent(flashlight, Parent(Some(camera)));
358+
world.set_local_transform_dirty(flashlight, LocalTransformDirty);
359+
world.set_light(
360+
flashlight,
361+
Light {
362+
light_type: LightType::Spot,
363+
color: Vec3::new(1.0, 1.0, 0.9),
364+
intensity: 15.0,
365+
range: 100.0,
366+
inner_cone_angle: 0.15,
367+
outer_cone_angle: 0.4,
368+
cast_shadows: true,
369+
shadow_bias: 0.007,
370+
},
371+
);
208372

209-
let pitch = direction.y.asin();
210-
let yaw = direction.z.atan2(direction.x);
373+
self.resources.flashlight_entity = Some(flashlight);
211374

212-
transform.rotation = nalgebra_glm::quat_angle_axis(yaw, &Vec3::y())
213-
* nalgebra_glm::quat_angle_axis(pitch, &Vec3::x());
375+
nightshade::ecs::transform::systems::run_systems(world);
376+
}
214377

215-
world.set_local_transform(light_entity, transform);
216-
world.set_local_transform_dirty(light_entity, LocalTransformDirty);
217-
}
218-
}
378+
fn run_systems(&mut self, world: &mut World) {
379+
let delta = world.resources.window.timing.delta_time;
380+
self.resources.time += delta;
219381

220382
for (sphere_entity, _initial_y, velocity) in &mut self.resources.spheres {
221383
if let Some(mut transform) = world.get_local_transform(*sphere_entity).cloned() {
@@ -252,7 +414,66 @@ impl State for ShadowsDemo {
252414
}
253415
}
254416

417+
if let Some(light_entity) = self.resources.light_entity {
418+
let cycle_speed = 0.2;
419+
let angle = self.resources.time * cycle_speed;
420+
let radius = 25.0;
421+
422+
let light_x = angle.sin() * radius;
423+
let light_y = angle.cos() * radius;
424+
let light_position = Vec3::new(light_x, light_y, 0.0);
425+
426+
let scene_center = Vec3::new(0.0, 0.0, 0.0);
427+
let light_direction_toward_scene = (scene_center - light_position).normalize();
428+
429+
let up = if light_direction_toward_scene.y.abs() > 0.99 {
430+
Vec3::new(1.0, 0.0, 0.0)
431+
} else {
432+
Vec3::new(0.0, 1.0, 0.0)
433+
};
434+
435+
let light_rotation = nalgebra_glm::quat_look_at_rh(&light_direction_toward_scene, &up);
436+
437+
let transform = LocalTransform {
438+
translation: light_position,
439+
rotation: light_rotation,
440+
scale: Vec3::new(1.0, 1.0, 1.0),
441+
};
442+
443+
world.set_local_transform(light_entity, transform);
444+
world.set_local_transform_dirty(light_entity, LocalTransformDirty);
445+
446+
if let Some(light_cone_entity) = self.resources.light_cone_entity {
447+
let cone_rotation = light_rotation
448+
* nalgebra_glm::quat_angle_axis(std::f32::consts::FRAC_PI_2, &Vec3::x_axis());
449+
let cone_transform = LocalTransform {
450+
translation: light_position,
451+
rotation: cone_rotation,
452+
scale: Vec3::new(3.0, 3.0, 3.0),
453+
};
454+
world.set_local_transform(light_cone_entity, cone_transform);
455+
world.set_local_transform_dirty(light_cone_entity, LocalTransformDirty);
456+
}
457+
}
458+
255459
escape_key_exit_system(world);
256460
fly_camera_system(world);
461+
nightshade::ecs::transform::systems::run_systems(world);
462+
}
463+
464+
fn on_keyboard_input(&mut self, world: &mut World, key_code: KeyCode, key_state: KeyState) {
465+
if matches!((key_code, key_state), (KeyCode::KeyF, KeyState::Pressed)) {
466+
self.resources.flashlight_enabled = !self.resources.flashlight_enabled;
467+
if let Some(flashlight_entity) = self.resources.flashlight_entity {
468+
if let Some(mut light) = world.get_light(flashlight_entity).cloned() {
469+
light.intensity = if self.resources.flashlight_enabled {
470+
15.0
471+
} else {
472+
0.0
473+
};
474+
world.set_light(flashlight_entity, light);
475+
}
476+
}
477+
}
257478
}
258479
}

crates/nightshade/src/ecs/camera/components/orthographic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ impl OrthographicCamera {
2626
self.x_mag,
2727
-self.y_mag,
2828
self.y_mag,
29-
self.z_near,
3029
self.z_far,
30+
self.z_near,
3131
)
3232
}
3333
}

0 commit comments

Comments
 (0)