Skip to main content

three_d/renderer/light/
environment.rs

1use crate::core::*;
2use crate::renderer::*;
3
4///
5/// Options used when generating an [Environment], ie. when precalculating lighting from an environment map.
6///
7#[derive(Debug, Copy, Clone, PartialEq)]
8pub struct EnvironmentOptions {
9    /// The lighting model used for the precalculation.
10    pub lighting_model: LightingModel,
11    /// The texture size of the irradiance map.
12    pub irradiance_map_size: u32,
13    /// The number of samples used when generating the irradiance map.
14    pub irradiance_sample_count: u32,
15    /// The texture size of the prefilter map.
16    pub prefilter_map_size: u32,
17    /// The number of mip levels used when generating the prefilter map.
18    pub prefilter_map_max_mip_levels: u32,
19    /// The number of samples used when generating the prefilter map.
20    pub prefilter_sample_count: u32,
21    /// The texture size of the BRDF map.
22    pub brdf_map_size: u32,
23    /// The number of samples used when generating the BRDF map.
24    pub brdf_sample_count: u32,
25}
26
27impl Default for EnvironmentOptions {
28    fn default() -> Self {
29        Self {
30            lighting_model: LightingModel::Cook(
31                NormalDistributionFunction::TrowbridgeReitzGGX,
32                GeometryFunction::SmithSchlickGGX,
33            ),
34            irradiance_map_size: 32,
35            irradiance_sample_count: 256,
36            prefilter_map_size: 128,
37            prefilter_map_max_mip_levels: 5,
38            prefilter_sample_count: 256,
39            brdf_map_size: 512,
40            brdf_sample_count: 128,
41        }
42    }
43}
44
45///
46/// Precalculations of light shining from an environment map (known as image based lighting - IBL).
47/// This allows for real-time rendering of ambient light from the environment (see [AmbientLight](crate::AmbientLight)).
48///
49pub struct Environment {
50    /// A cube map used to calculate the diffuse contribution from the environment.
51    pub irradiance_map: TextureCubeMap,
52    /// A cube map used to calculate the specular contribution from the environment.
53    /// Each mip-map level contain the prefiltered color for a certain surface roughness.
54    pub prefilter_map: TextureCubeMap,
55    /// A 2D texture that contain the BRDF lookup tables (LUT).
56    pub brdf_map: Texture2D,
57}
58
59impl Environment {
60    ///
61    /// Computes the maps needed for image based lighting with lighting coming
62    /// from the given environment map and using the default Cook-Torrance lighting model.
63    ///
64    pub fn new(context: &Context, environment_map: &TextureCubeMap) -> Self {
65        Self::new_with_options(context, environment_map, EnvironmentOptions::default())
66    }
67
68    ///
69    /// Computes the maps needed for image based lighting with lighting coming
70    /// from the given environment map and using the specified lighting model.
71    ///
72    pub fn new_with_lighting_model(
73        context: &Context,
74        environment_map: &TextureCubeMap,
75        lighting_model: LightingModel,
76    ) -> Self {
77        Self::new_with_options(
78            context,
79            environment_map,
80            EnvironmentOptions {
81                lighting_model,
82                ..Default::default()
83            },
84        )
85    }
86
87    ///
88    /// Computes the maps needed for image based lighting with lighting coming
89    /// from the given environment map and using the specified [EnvironmentOptions].
90    ///
91    pub fn new_with_options(
92        context: &Context,
93        environment_map: &TextureCubeMap,
94        options: EnvironmentOptions,
95    ) -> Self {
96        // Diffuse
97        let irradiance_map = TextureCubeMap::new_empty::<[f16; 4]>(
98            context,
99            options.irradiance_map_size,
100            options.irradiance_map_size,
101            Interpolation::Linear,
102            Interpolation::Linear,
103            Some(Mipmap::default()),
104            Wrapping::ClampToEdge,
105            Wrapping::ClampToEdge,
106            Wrapping::ClampToEdge,
107        );
108        {
109            let viewport = Viewport::new_at_origo(irradiance_map.width(), irradiance_map.height());
110            for side in CubeMapSide::iter() {
111                irradiance_map
112                    .as_color_target(&[side], None)
113                    .clear(ClearState::default())
114                    .apply_screen_material(
115                        &IrradianceMaterial {
116                            environment_map,
117                            side,
118                            sample_count: options.irradiance_sample_count,
119                        },
120                        Camera::new_2d(viewport),
121                        &[],
122                    );
123            }
124        }
125
126        // Prefilter
127        let prefilter_map = TextureCubeMap::new_empty::<[f16; 4]>(
128            context,
129            options.prefilter_map_size,
130            options.prefilter_map_size,
131            Interpolation::Linear,
132            Interpolation::Linear,
133            Some(Mipmap::default()),
134            Wrapping::ClampToEdge,
135            Wrapping::ClampToEdge,
136            Wrapping::ClampToEdge,
137        );
138        {
139            for mip in 0..options.prefilter_map_max_mip_levels {
140                for side in CubeMapSide::iter() {
141                    let sides = [side];
142                    let color_target = prefilter_map.as_color_target(&sides, Some(mip));
143                    let viewport =
144                        Viewport::new_at_origo(color_target.width(), color_target.height());
145                    color_target
146                        .clear(ClearState::default())
147                        .apply_screen_material(
148                            &PrefilterMaterial {
149                                lighting_model: options.lighting_model,
150                                environment_map,
151                                side,
152                                mip,
153                                max_mip_levels: options.prefilter_map_max_mip_levels,
154                                sample_count: options.prefilter_sample_count,
155                            },
156                            Camera::new_2d(viewport),
157                            &[],
158                        );
159                }
160            }
161        }
162
163        // BRDF
164        let brdf_map = Texture2D::new_empty::<[f32; 2]>(
165            context,
166            options.brdf_map_size,
167            options.brdf_map_size,
168            Interpolation::Linear,
169            Interpolation::Linear,
170            None,
171            Wrapping::ClampToEdge,
172            Wrapping::ClampToEdge,
173        );
174        let viewport = Viewport::new_at_origo(brdf_map.width(), brdf_map.height());
175        brdf_map
176            .as_color_target(None)
177            .clear(ClearState::default())
178            .apply_screen_material(
179                &BrdfMaterial {
180                    lighting_model: options.lighting_model,
181                    sample_count: options.brdf_sample_count,
182                },
183                Camera::new_2d(viewport),
184                &[],
185            );
186
187        Self {
188            irradiance_map,
189            prefilter_map,
190            brdf_map,
191        }
192    }
193}
194
195struct PrefilterMaterial<'a> {
196    lighting_model: LightingModel,
197    environment_map: &'a TextureCubeMap,
198    side: CubeMapSide,
199    mip: u32,
200    max_mip_levels: u32,
201    sample_count: u32,
202}
203
204impl Material for PrefilterMaterial<'_> {
205    fn fragment_shader_source(&self, _lights: &[&dyn Light]) -> String {
206        format!(
207            "{}{}{}",
208            include_str!("../../core/shared.frag"),
209            include_str!("shaders/light_shared.frag"),
210            include_str!("shaders/prefilter.frag")
211        )
212    }
213
214    fn id(&self) -> EffectMaterialId {
215        EffectMaterialId::PrefilterMaterial
216    }
217
218    fn use_uniforms(&self, program: &Program, _viewer: &dyn Viewer, _lights: &[&dyn Light]) {
219        program.use_uniform_if_required("lightingModel", lighting_model_to_id(self.lighting_model));
220        program.use_texture_cube("environmentMap", self.environment_map);
221        program.use_uniform(
222            "roughness",
223            self.mip as f32 / (self.max_mip_levels as f32 - 1.0),
224        );
225        program.use_uniform("resolution", self.environment_map.width() as f32);
226        program.use_uniform("direction", self.side.direction());
227        program.use_uniform("up", self.side.up());
228        program.use_uniform("sampleCount", self.sample_count);
229    }
230
231    fn render_states(&self) -> RenderStates {
232        RenderStates::default()
233    }
234
235    fn material_type(&self) -> MaterialType {
236        MaterialType::Opaque
237    }
238}
239
240struct BrdfMaterial {
241    lighting_model: LightingModel,
242    sample_count: u32,
243}
244
245impl Material for BrdfMaterial {
246    fn fragment_shader_source(&self, _lights: &[&dyn Light]) -> String {
247        format!(
248            "{}{}{}",
249            include_str!("../../core/shared.frag"),
250            include_str!("shaders/light_shared.frag"),
251            include_str!("shaders/brdf.frag")
252        )
253    }
254
255    fn id(&self) -> EffectMaterialId {
256        EffectMaterialId::BrdfMaterial
257    }
258
259    fn use_uniforms(&self, program: &Program, _viewer: &dyn Viewer, _lights: &[&dyn Light]) {
260        program.use_uniform_if_required("lightingModel", lighting_model_to_id(self.lighting_model));
261        program.use_uniform("sampleCount", self.sample_count);
262    }
263
264    fn render_states(&self) -> RenderStates {
265        RenderStates::default()
266    }
267
268    fn material_type(&self) -> MaterialType {
269        MaterialType::Opaque
270    }
271}
272
273struct IrradianceMaterial<'a> {
274    environment_map: &'a TextureCubeMap,
275    side: CubeMapSide,
276    sample_count: u32,
277}
278
279impl Material for IrradianceMaterial<'_> {
280    fn fragment_shader_source(&self, _lights: &[&dyn Light]) -> String {
281        format!(
282            "{}{}",
283            include_str!("../../core/shared.frag"),
284            include_str!("shaders/irradiance.frag")
285        )
286    }
287
288    fn id(&self) -> EffectMaterialId {
289        EffectMaterialId::IrradianceMaterial
290    }
291
292    fn use_uniforms(&self, program: &Program, _viewer: &dyn Viewer, _lights: &[&dyn Light]) {
293        program.use_texture_cube("environmentMap", self.environment_map);
294        program.use_uniform("direction", self.side.direction());
295        program.use_uniform("up", self.side.up());
296        program.use_uniform("sampleCount", self.sample_count);
297    }
298
299    fn render_states(&self) -> RenderStates {
300        RenderStates::default()
301    }
302
303    fn material_type(&self) -> MaterialType {
304        MaterialType::Opaque
305    }
306}