1use crate::core::*;
2use crate::renderer::light::*;
3use crate::renderer::*;
4
5pub struct SpotLight {
10 context: Context,
11 shadow_texture: Option<DepthTexture2D>,
12 shadow_matrix: Mat4,
13 pub intensity: f32,
15 pub color: Srgba,
17 pub position: Vec3,
19 pub direction: Vec3,
21 pub cutoff: Radians,
23 pub attenuation: Attenuation,
25}
26
27impl SpotLight {
28 pub fn new(
30 context: &Context,
31 intensity: f32,
32 color: Srgba,
33 position: Vec3,
34 direction: Vec3,
35 cutoff: impl Into<Radians>,
36 attenuation: Attenuation,
37 ) -> SpotLight {
38 SpotLight {
39 context: context.clone(),
40 shadow_texture: None,
41 intensity,
42 color,
43 position,
44 direction,
45 cutoff: cutoff.into(),
46 attenuation,
47 shadow_matrix: Mat4::identity(),
48 }
49 }
50
51 pub fn clear_shadow_map(&mut self) {
56 self.shadow_texture = None;
57 self.shadow_matrix = Mat4::identity();
58 }
59
60 pub fn generate_shadow_map(
66 &mut self,
67 texture_size: u32,
68 geometries: impl IntoIterator<Item = impl Geometry> + Clone,
69 ) {
70 let position = self.position;
71 let target = position + self.direction.normalize();
72 let up = compute_up_direction(self.direction);
73
74 let viewport = Viewport::new_at_origo(texture_size, texture_size);
75
76 let mut z_far = 0.0f32;
77 let mut z_near = f32::MAX;
78 for geometry in geometries.clone() {
79 let aabb = geometry.aabb();
80 if !aabb.is_empty() {
81 z_far = z_far.max(aabb.distance_max(self.position));
82 z_near = z_near.min(aabb.distance(self.position));
83 }
84 }
85
86 let shadow_camera = Camera::new_perspective(
87 viewport,
88 position,
89 target,
90 up,
91 self.cutoff,
92 z_near.max(0.01),
93 z_far,
94 );
95 self.shadow_matrix = shadow_matrix(&shadow_camera);
96
97 let mut shadow_texture = DepthTexture2D::new::<f32>(
98 &self.context,
99 texture_size,
100 texture_size,
101 Wrapping::ClampToEdge,
102 Wrapping::ClampToEdge,
103 );
104 let depth_material = DepthMaterial {
105 render_states: RenderStates {
106 write_mask: WriteMask::DEPTH,
107 ..Default::default()
108 },
109 ..Default::default()
110 };
111 let frustum = shadow_camera.frustum();
112 shadow_texture
113 .as_depth_target()
114 .clear(ClearState::default())
115 .write::<RendererError>(|| {
116 for geometry in geometries
117 .into_iter()
118 .filter(|g| frustum.contains(g.aabb()))
119 {
120 render_with_material(
121 &self.context,
122 &shadow_camera,
123 &geometry,
124 &depth_material,
125 &[],
126 );
127 }
128 Ok(())
129 })
130 .unwrap();
131 self.shadow_texture = Some(shadow_texture);
132 }
133
134 pub fn shadow_map(&self) -> Option<&DepthTexture2D> {
138 self.shadow_texture.as_ref()
139 }
140}
141
142impl Light for SpotLight {
143 fn shader_source(&self, i: u32) -> String {
144 if self.shadow_texture.is_some() {
145 format!(
146 "
147 uniform sampler2D shadowMap{};
148 uniform mat4 shadowMVP{};
149
150 uniform vec3 color{};
151 uniform vec3 attenuation{};
152 uniform vec3 position{};
153 uniform float cutoff{};
154 uniform vec3 direction{};
155 vec3 calculate_lighting{}(vec3 surface_color, vec3 position, vec3 normal, vec3 view_direction, float metallic, float roughness, float occlusion)
156 {{
157 vec3 light_direction = position{} - position;
158 float distance = length(light_direction);
159 light_direction = light_direction / distance;
160
161 float angle = acos(dot(-light_direction, normalize(direction{})));
162 float cutoff = cutoff{};
163
164 vec3 result = vec3(0.0);
165 if (angle < cutoff) {{
166 vec3 light_color = attenuate(color{}, attenuation{}, distance);
167 result = calculate_light(light_color, light_direction, surface_color, view_direction, normal,
168 metallic, roughness) * (1.0 - smoothstep(0.75 * cutoff, cutoff, angle));
169 result *= calculate_shadow(light_direction, normal, shadowMap{}, shadowMVP{}, position);
170 }}
171 return result;
172 }}
173
174 ", i, i, i, i, i, i, i, i, i, i, i, i, i, i, i)
175 } else {
176 format!(
177 "
178 uniform vec3 color{};
179 uniform vec3 attenuation{};
180 uniform vec3 position{};
181 uniform float cutoff{};
182 uniform vec3 direction{};
183 vec3 calculate_lighting{}(vec3 surface_color, vec3 position, vec3 normal, vec3 view_direction, float metallic, float roughness, float occlusion)
184 {{
185 vec3 light_direction = position{} - position;
186 float distance = length(light_direction);
187 light_direction = light_direction / distance;
188
189 float angle = acos(dot(-light_direction, normalize(direction{})));
190 float cutoff = cutoff{};
191
192 vec3 result = vec3(0.0);
193 if (angle < cutoff) {{
194 vec3 light_color = attenuate(color{}, attenuation{}, distance);
195 result = calculate_light(light_color, light_direction, surface_color, view_direction, normal,
196 metallic, roughness) * (1.0 - smoothstep(0.75 * cutoff, cutoff, angle));
197 }}
198 return result;
199 }}
200
201 ", i, i, i, i, i, i, i, i, i, i, i)
202 }
203 }
204 fn use_uniforms(&self, program: &Program, i: u32) {
205 if let Some(ref tex) = self.shadow_texture {
206 program.use_depth_texture(&format!("shadowMap{}", i), tex);
207 program.use_uniform(&format!("shadowMVP{}", i), self.shadow_matrix);
208 }
209 program.use_uniform(
210 &format!("color{}", i),
211 self.color.to_linear_srgb().truncate() * self.intensity,
212 );
213 program.use_uniform(
214 &format!("attenuation{}", i),
215 vec3(
216 self.attenuation.constant,
217 self.attenuation.linear,
218 self.attenuation.quadratic,
219 ),
220 );
221 program.use_uniform(&format!("position{}", i), self.position);
222 program.use_uniform(&format!("direction{}", i), self.direction.normalize());
223 program.use_uniform(&format!("cutoff{}", i), self.cutoff.0);
224 }
225
226 fn id(&self) -> LightId {
227 LightId::SpotLight(self.shadow_texture.is_some())
228 }
229}