three_d/renderer/light/
directional_light.rs

1use crate::core::*;
2use crate::renderer::light::*;
3use crate::renderer::*;
4
5///
6/// A light which shines in the given direction.
7/// The light will cast shadows if you [generate a shadow map](DirectionalLight::generate_shadow_map).
8///
9pub struct DirectionalLight {
10    context: Context,
11    shadow_texture: Option<DepthTexture2D>,
12    shadow_matrix: Mat4,
13    /// The intensity of the light. This allows for higher intensity than 1 which can be used to simulate high intensity light sources like the sun.
14    pub intensity: f32,
15    /// The base color of the light.
16    pub color: Srgba,
17    /// The direction the light shines.
18    pub direction: Vec3,
19}
20
21impl DirectionalLight {
22    /// Creates a new directional light.
23    pub fn new(
24        context: &Context,
25        intensity: f32,
26        color: Srgba,
27        direction: Vec3,
28    ) -> DirectionalLight {
29        DirectionalLight {
30            context: context.clone(),
31            shadow_matrix: Mat4::identity(),
32            shadow_texture: None,
33            intensity,
34            color,
35            direction,
36        }
37    }
38
39    ///
40    /// Clear the shadow map, effectively disable the shadow.
41    /// Only necessary if you want to disable the shadow, if you want to update the shadow, just use [DirectionalLight::generate_shadow_map].
42    ///
43    pub fn clear_shadow_map(&mut self) {
44        self.shadow_texture = None;
45        self.shadow_matrix = Mat4::identity();
46    }
47
48    ///
49    /// Generate a shadow map which is used to simulate shadows from the directional light onto the geometries given as input.
50    /// It is recomended that the texture size is power of 2.
51    /// If the shadows are too low resolution (the edges between shadow and non-shadow are pixelated) try to increase the texture size
52    /// and/or split the scene by creating another light source with same parameters and let the two light sources shines on different parts of the scene.
53    ///
54    pub fn generate_shadow_map(
55        &mut self,
56        texture_size: u32,
57        geometries: impl IntoIterator<Item = impl Geometry> + Clone,
58    ) {
59        let up = compute_up_direction(self.direction);
60
61        let viewport = Viewport::new_at_origo(texture_size, texture_size);
62        let mut aabb = AxisAlignedBoundingBox::EMPTY;
63        for geometry in geometries.clone() {
64            aabb.expand_with_aabb(geometry.aabb());
65        }
66        if aabb.is_empty() {
67            return;
68        }
69        let position = aabb.center();
70        let target = position + self.direction.normalize();
71        let z_far = aabb.distance_max(position);
72        let z_near = -z_far;
73        let frustum_height = aabb.max().distance(aabb.min()); // TODO: more tight fit
74        let shadow_camera = Camera::new_orthographic(
75            viewport,
76            position,
77            target,
78            up,
79            frustum_height,
80            z_near,
81            z_far,
82        );
83        let mut shadow_texture = DepthTexture2D::new::<f32>(
84            &self.context,
85            texture_size,
86            texture_size,
87            Wrapping::ClampToEdge,
88            Wrapping::ClampToEdge,
89        );
90        let depth_material = DepthMaterial {
91            render_states: RenderStates {
92                write_mask: WriteMask::DEPTH,
93                ..Default::default()
94            },
95            ..Default::default()
96        };
97        let frustum = shadow_camera.frustum();
98        shadow_texture
99            .as_depth_target()
100            .clear(ClearState::default())
101            .write::<RendererError>(|| {
102                for geometry in geometries
103                    .into_iter()
104                    .filter(|g| frustum.contains(g.aabb()))
105                {
106                    render_with_material(
107                        &self.context,
108                        &shadow_camera,
109                        &geometry,
110                        &depth_material,
111                        &[],
112                    );
113                }
114                Ok(())
115            })
116            .unwrap();
117        self.shadow_texture = Some(shadow_texture);
118        self.shadow_matrix = shadow_matrix(&shadow_camera);
119    }
120
121    ///
122    /// Returns a reference to the shadow map if it has been generated.
123    ///
124    pub fn shadow_map(&self) -> Option<&DepthTexture2D> {
125        self.shadow_texture.as_ref()
126    }
127}
128
129impl Light for DirectionalLight {
130    fn shader_source(&self, i: u32) -> String {
131        if self.shadow_texture.is_some() {
132            format!(
133                "
134                    uniform sampler2D shadowMap{};
135                    uniform mat4 shadowMVP{};
136
137                    uniform vec3 color{};
138                    uniform vec3 direction{};
139
140                    vec3 calculate_lighting{}(vec3 surface_color, vec3 position, vec3 normal, vec3 view_direction, float metallic, float roughness, float occlusion)
141                    {{
142                        return calculate_light(color{}, -direction{}, surface_color, view_direction, normal, metallic, roughness)
143                            * calculate_shadow(-direction{}, normal, shadowMap{}, shadowMVP{}, position);
144                    }}
145
146                ", i, i, i, i, i, i, i, i, i, i)
147        } else {
148            format!(
149                "
150                    uniform vec3 color{};
151                    uniform vec3 direction{};
152
153                    vec3 calculate_lighting{}(vec3 surface_color, vec3 position, vec3 normal, vec3 view_direction, float metallic, float roughness, float occlusion)
154                    {{
155                        return calculate_light(color{}, -direction{}, surface_color, view_direction, normal, metallic, roughness);
156                    }}
157
158                ", i, i, i, i, i)
159        }
160    }
161    fn use_uniforms(&self, program: &Program, i: u32) {
162        if let Some(ref tex) = self.shadow_texture {
163            program.use_depth_texture(&format!("shadowMap{}", i), tex);
164            program.use_uniform(&format!("shadowMVP{}", i), self.shadow_matrix);
165        }
166        program.use_uniform(
167            &format!("color{}", i),
168            self.color.to_linear_srgb().truncate() * self.intensity,
169        );
170        program.use_uniform(&format!("direction{}", i), self.direction.normalize());
171    }
172
173    fn id(&self) -> LightId {
174        LightId::DirectionalLight(self.shadow_texture.is_some())
175    }
176}