three_d/renderer/
light.rs

1//!
2//! A collection of lights implementing the [Light] trait.
3//!
4//! Lights shines onto objects in the scene, note however that some materials are affected by lights, others are not.
5//!
6
7macro_rules! impl_light_body {
8    ($inner:ident) => {
9        fn shader_source(&self, i: u32) -> String {
10            self.$inner().shader_source(i)
11        }
12        fn use_uniforms(&self, program: &Program, i: u32) {
13            self.$inner().use_uniforms(program, i)
14        }
15        fn id(&self) -> LightId {
16            self.$inner().id()
17        }
18    };
19}
20
21mod directional_light;
22use std::ops::Deref;
23
24#[doc(inline)]
25pub use directional_light::*;
26
27mod spot_light;
28#[doc(inline)]
29pub use spot_light::*;
30
31mod point_light;
32#[doc(inline)]
33pub use point_light::*;
34
35mod ambient_light;
36#[doc(inline)]
37pub use ambient_light::*;
38
39mod environment;
40#[doc(inline)]
41pub use environment::*;
42
43use crate::core::*;
44use crate::renderer::viewer::*;
45use crate::renderer::LightId;
46
47///
48/// Specifies how the intensity of a light fades over distance.
49/// The light intensity is scaled by ``` 1 / max(1, constant + distance * linear + distance * distance * quadratic) ```.
50///
51#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
52pub struct Attenuation {
53    /// Constant attenuation factor.
54    pub constant: f32,
55    /// Linear attenuation factor.
56    pub linear: f32,
57    /// Quadratic attenuation factor.
58    pub quadratic: f32,
59}
60
61impl Default for Attenuation {
62    fn default() -> Self {
63        Self {
64            constant: 1.0,
65            linear: 0.0,
66            quadratic: 0.0,
67        }
68    }
69}
70
71/// Represents a light source.
72pub trait Light {
73    /// The fragment shader source for calculating this lights contribution to the color in a fragment.
74    /// It should contain a function with this signature
75    /// `vec3 calculate_lighting{}(vec3 surface_color, vec3 position, vec3 normal, vec3 view_direction, float metallic, float roughness, float occlusion)`
76    /// Where `{}` is replaced with the number i given as input.
77    /// This function should return the color contribution for this light on the surface with the given surface parameters.
78    fn shader_source(&self, i: u32) -> String;
79
80    /// Should bind the uniforms that is needed for calculating this lights contribution to the color in [Light::shader_source].
81    fn use_uniforms(&self, program: &Program, i: u32);
82
83    ///
84    /// Returns a unique ID for each variation of the shader source returned from `Light::shader_source`.
85    ///
86    /// **Note:** The last bit is reserved to internally implemented materials, so if implementing the `Light` trait
87    /// outside of this crate, always return an id in the public use range as defined by [LightId].
88    ///
89    fn id(&self) -> LightId;
90}
91
92impl<T: Light + ?Sized> Light for &T {
93    impl_light_body!(deref);
94}
95
96impl<T: Light + ?Sized> Light for &mut T {
97    impl_light_body!(deref);
98}
99
100impl<T: Light> Light for Box<T> {
101    impl_light_body!(as_ref);
102}
103
104impl<T: Light> Light for std::rc::Rc<T> {
105    impl_light_body!(as_ref);
106}
107
108impl<T: Light> Light for std::sync::Arc<T> {
109    impl_light_body!(as_ref);
110}
111
112impl<T: Light> Light for std::cell::RefCell<T> {
113    impl_light_body!(borrow);
114}
115
116impl<T: Light> Light for std::sync::Arc<std::sync::RwLock<T>> {
117    fn shader_source(&self, i: u32) -> String {
118        self.read().unwrap().shader_source(i)
119    }
120    fn use_uniforms(&self, program: &Program, i: u32) {
121        self.read().unwrap().use_uniforms(program, i)
122    }
123    fn id(&self) -> LightId {
124        self.read().unwrap().id()
125    }
126}
127
128///
129/// Returns shader source code with the function `calculate_lighting` which calculate the lighting contribution for the given lights and the given [LightingModel].
130/// Use this if you want to implement a custom [Material](crate::renderer::Material) but use the default lighting calculations.
131///
132/// The shader function has the following signature:
133/// ```no_rust
134/// vec3 calculate_lighting(vec3 camera_position, vec3 surface_color, vec3 position, vec3 normal, float metallic, float roughness, float occlusion)
135/// ```
136///
137pub fn lights_shader_source(lights: &[&dyn Light]) -> String {
138    let mut shader_source = include_str!("../core/shared.frag").to_string();
139    shader_source.push_str(include_str!("light/shaders/light_shared.frag"));
140    let mut dir_fun = String::new();
141    for (i, light) in lights.iter().enumerate() {
142        shader_source.push_str(&light.shader_source(i as u32));
143        dir_fun.push_str(&format!("color += calculate_lighting{}(surface_color, position, normal, view_direction, metallic, roughness, occlusion);\n", i))
144    }
145    shader_source.push_str(&format!(
146        "
147            vec3 calculate_lighting(vec3 camera_position, vec3 surface_color, vec3 position, vec3 normal, float metallic, float roughness, float occlusion)
148            {{
149                vec3 color = vec3(0.0, 0.0, 0.0);
150                vec3 view_direction = normalize(camera_position - position);
151                {}
152                return color;
153            }}
154            ",
155        &dir_fun
156    ));
157    shader_source
158}
159
160fn shadow_matrix(camera: &Camera) -> Mat4 {
161    let bias_matrix = crate::Mat4::new(
162        0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0,
163    );
164    bias_matrix * camera.projection() * camera.view()
165}
166
167fn compute_up_direction(direction: Vec3) -> Vec3 {
168    if vec3(1.0, 0.0, 0.0).dot(direction).abs() > 0.9 {
169        (vec3(0.0, 1.0, 0.0).cross(direction)).normalize()
170    } else {
171        (vec3(1.0, 0.0, 0.0).cross(direction)).normalize()
172    }
173}
174
175use crate::renderer::{GeometryFunction, LightingModel, NormalDistributionFunction};
176pub(crate) fn lighting_model_to_id(model: LightingModel) -> u32 {
177    match model {
178        LightingModel::Phong => 1,
179        LightingModel::Blinn => 2,
180        LightingModel::Cook(
181            NormalDistributionFunction::Blinn,
182            GeometryFunction::SmithSchlickGGX,
183        ) => 3,
184        LightingModel::Cook(
185            NormalDistributionFunction::Beckmann,
186            GeometryFunction::SmithSchlickGGX,
187        ) => 4,
188        LightingModel::Cook(
189            NormalDistributionFunction::TrowbridgeReitzGGX,
190            GeometryFunction::SmithSchlickGGX,
191        ) => 5,
192    }
193}