use crate::core::*;
use crate::renderer::light::*;
use crate::renderer::*;
pub struct SpotLight {
context: Context,
shadow_texture: Option<DepthTexture2D>,
shadow_matrix: Mat4,
pub intensity: f32,
pub color: Srgba,
pub position: Vec3,
pub direction: Vec3,
pub cutoff: Radians,
pub attenuation: Attenuation,
}
impl SpotLight {
pub fn new(
context: &Context,
intensity: f32,
color: Srgba,
position: &Vec3,
direction: &Vec3,
cutoff: impl Into<Radians>,
attenuation: Attenuation,
) -> SpotLight {
SpotLight {
context: context.clone(),
shadow_texture: None,
intensity,
color,
position: *position,
direction: *direction,
cutoff: cutoff.into(),
attenuation,
shadow_matrix: Mat4::identity(),
}
}
pub fn clear_shadow_map(&mut self) {
self.shadow_texture = None;
self.shadow_matrix = Mat4::identity();
}
pub fn generate_shadow_map(
&mut self,
texture_size: u32,
geometries: impl IntoIterator<Item = impl Geometry> + Clone,
) {
let position = self.position;
let direction = self.direction;
let up = compute_up_direction(self.direction);
let viewport = Viewport::new_at_origo(texture_size, texture_size);
let mut z_far = 0.0f32;
let mut z_near = f32::MAX;
for geometry in geometries.clone() {
let aabb = geometry.aabb();
if !aabb.is_empty() {
z_far = z_far.max(aabb.distance_max(&self.position));
z_near = z_near.min(aabb.distance(&self.position));
}
}
let shadow_camera = Camera::new_perspective(
viewport,
position,
position + direction,
up,
self.cutoff,
z_near.max(0.01),
z_far,
);
self.shadow_matrix = shadow_matrix(&shadow_camera);
let mut shadow_texture = DepthTexture2D::new::<f32>(
&self.context,
texture_size,
texture_size,
Wrapping::ClampToEdge,
Wrapping::ClampToEdge,
);
let depth_material = DepthMaterial {
render_states: RenderStates {
write_mask: WriteMask::DEPTH,
..Default::default()
},
..Default::default()
};
shadow_texture
.as_depth_target()
.clear(ClearState::default())
.write(|| {
for geometry in geometries
.into_iter()
.filter(|g| shadow_camera.in_frustum(&g.aabb()))
{
render_with_material(
&self.context,
&shadow_camera,
&geometry,
&depth_material,
&[],
);
}
});
self.shadow_texture = Some(shadow_texture);
}
pub fn shadow_map(&self) -> Option<&DepthTexture2D> {
self.shadow_texture.as_ref()
}
}
impl Light for SpotLight {
fn shader_source(&self, i: u32) -> String {
if self.shadow_texture.is_some() {
format!(
"
uniform sampler2D shadowMap{};
uniform mat4 shadowMVP{};
uniform vec3 color{};
uniform vec3 attenuation{};
uniform vec3 position{};
uniform float cutoff{};
uniform vec3 direction{};
vec3 calculate_lighting{}(vec3 surface_color, vec3 position, vec3 normal, vec3 view_direction, float metallic, float roughness, float occlusion)
{{
vec3 light_direction = position{} - position;
float distance = length(light_direction);
light_direction = light_direction / distance;
float angle = acos(dot(-light_direction, normalize(direction{})));
float cutoff = cutoff{};
vec3 result = vec3(0.0);
if (angle < cutoff) {{
vec3 light_color = attenuate(color{}, attenuation{}, distance);
result = calculate_light(light_color, light_direction, surface_color, view_direction, normal,
metallic, roughness) * (1.0 - smoothstep(0.75 * cutoff, cutoff, angle));
result *= calculate_shadow(light_direction, normal, shadowMap{}, shadowMVP{}, position);
}}
return result;
}}
", i, i, i, i, i, i, i, i, i, i, i, i, i, i, i)
} else {
format!(
"
uniform vec3 color{};
uniform vec3 attenuation{};
uniform vec3 position{};
uniform float cutoff{};
uniform vec3 direction{};
vec3 calculate_lighting{}(vec3 surface_color, vec3 position, vec3 normal, vec3 view_direction, float metallic, float roughness, float occlusion)
{{
vec3 light_direction = position{} - position;
float distance = length(light_direction);
light_direction = light_direction / distance;
float angle = acos(dot(-light_direction, normalize(direction{})));
float cutoff = cutoff{};
vec3 result = vec3(0.0);
if (angle < cutoff) {{
vec3 light_color = attenuate(color{}, attenuation{}, distance);
result = calculate_light(light_color, light_direction, surface_color, view_direction, normal,
metallic, roughness) * (1.0 - smoothstep(0.75 * cutoff, cutoff, angle));
}}
return result;
}}
", i, i, i, i, i, i, i, i, i, i, i)
}
}
fn use_uniforms(&self, program: &Program, i: u32) {
if let Some(ref tex) = self.shadow_texture {
program.use_depth_texture(&format!("shadowMap{}", i), tex);
program.use_uniform(&format!("shadowMVP{}", i), self.shadow_matrix);
}
program.use_uniform(
&format!("color{}", i),
self.color.to_linear_srgb().truncate() * self.intensity,
);
program.use_uniform(
&format!("attenuation{}", i),
vec3(
self.attenuation.constant,
self.attenuation.linear,
self.attenuation.quadratic,
),
);
program.use_uniform(&format!("position{}", i), self.position);
program.use_uniform(&format!("direction{}", i), self.direction.normalize());
program.use_uniform(&format!("cutoff{}", i), self.cutoff.0);
}
fn id(&self) -> u8 {
if self.shadow_texture.is_some() {
0b1u8 << 7 | 0b101u8
} else {
0b1u8 << 7 | 0b110u8
}
}
}