easy_gltf/scene/model/material/
mod.rs

1mod emissive;
2mod normal;
3mod occlusion;
4mod pbr;
5
6use crate::utils::*;
7use cgmath::*;
8use core::ops::Deref;
9use image::{ImageBuffer, Pixel};
10use std::sync::Arc;
11
12pub use emissive::Emissive;
13pub use normal::NormalMap;
14pub use occlusion::Occlusion;
15pub use pbr::PbrMaterial;
16
17/// Contains material properties of models.
18#[derive(Clone, Debug, Default)]
19pub struct Material {
20    #[cfg(feature = "names")]
21    /// Material name. Requires the `names` feature.
22    pub name: Option<String>,
23
24    #[cfg(feature = "extras")]
25    /// Material extra data. Requires the `extras` feature.
26    pub extras: gltf::json::extras::Extras,
27
28    /// Parameter values that define the metallic-roughness material model from
29    /// Physically-Based Rendering (PBR) methodology.
30    pub pbr: PbrMaterial,
31
32    /// Defines the normal texture of a material.
33    pub normal: Option<NormalMap>,
34
35    /// Defines the occlusion texture of a material.
36    pub occlusion: Option<Occlusion>,
37
38    /// The emissive color of the material.
39    pub emissive: Emissive,
40}
41
42impl Material {
43    /// Get the color base Rgb(A) (in RGB-color space) of the material given a
44    /// texture coordinate. If no `base_color_texture` is available then the
45    /// `base_color_factor` is returned.
46    ///
47    /// **Important**: `tex_coords` must contain values between `[0., 1.]`
48    /// otherwise the function will fail.
49    pub fn get_base_color_alpha(&self, tex_coords: Vector2<f32>) -> Vector4<f32> {
50        let mut res = self.pbr.base_color_factor;
51        if let Some(texture) = &self.pbr.base_color_texture {
52            let px_u = Self::get_pixel(tex_coords, texture);
53            // Transform to float
54            let mut px_f = Vector4::new(0., 0., 0., 0.);
55            for i in 0..4 {
56                px_f[i] = (px_u[i] as f32) / 255.;
57            }
58            // Convert sRGB to RGB
59            let pixel = Vector4::new(px_f.x.powf(2.2), px_f.y.powf(2.2), px_f.z.powf(2.2), px_f.w);
60            // Multiply to the scale factor
61            for i in 0..4 {
62                res[i] *= pixel[i];
63            }
64        }
65        res
66    }
67
68    /// Get the color base Rgb (in RGB-color space) of the material given a
69    /// texture coordinate. If no `base_color_texture` is available then the
70    /// `base_color_factor` is returned.
71    ///
72    /// **Important**: `tex_coords` must contain values between `[0., 1.]`
73    /// otherwise the function will fail.
74    pub fn get_base_color(&self, tex_coords: Vector2<f32>) -> Vector3<f32> {
75        self.get_base_color_alpha(tex_coords).truncate()
76    }
77
78    /// Get the metallic value of the material given a texture coordinate. If no
79    /// `metallic_texture` is available then the `metallic_factor` is returned.
80    ///
81    /// **Important**: `tex_coords` must contain values between `[0., 1.]`
82    /// otherwise the function will fail.
83    pub fn get_metallic(&self, tex_coords: Vector2<f32>) -> f32 {
84        self.pbr.metallic_factor
85            * if let Some(texture) = &self.pbr.metallic_texture {
86                Self::get_pixel(tex_coords, texture)[0] as f32 / 255.
87            } else {
88                1.
89            }
90    }
91
92    /// Get the roughness value of the material given a texture coordinate. If no
93    /// `roughness_texture` is available then the `roughness_factor` is returned.
94    ///
95    /// **Important**: `tex_coords` must contain values between `[0., 1.]`
96    /// otherwise the function will fail.
97    pub fn get_roughness(&self, tex_coords: Vector2<f32>) -> f32 {
98        self.pbr.roughness_factor
99            * if let Some(texture) = &self.pbr.roughness_texture {
100                Self::get_pixel(tex_coords, texture)[0] as f32 / 255.
101            } else {
102                1.
103            }
104    }
105
106    /// Get the normal vector of the material given a texture coordinate. If no
107    /// `normal_texture` is available then `None` is returned.
108    ///
109    /// **Important**: `tex_coords` must contain values between `[0., 1.]`
110    /// otherwise the function will fail.
111    pub fn get_normal(&self, tex_coords: Vector2<f32>) -> Option<Vector3<f32>> {
112        let normal = self.normal.as_ref()?;
113        let pixel = Self::get_pixel(tex_coords, &normal.texture);
114        Some(
115            normal.factor
116                * Vector3::new(
117                    (pixel[0] as f32) / 127.5 - 1.,
118                    (pixel[1] as f32) / 127.5 - 1.,
119                    (pixel[2] as f32) / 127.5 - 1.,
120                ),
121        )
122    }
123
124    /// Get the occlusion value of the material given a texture coordinate. If no
125    /// `occlusion_texture` is available then `None` is returned.
126    ///
127    /// **Important**: `tex_coords` must contain values between `[0., 1.]`
128    /// otherwise the function will fail.
129    pub fn get_occlusion(&self, tex_coords: Vector2<f32>) -> Option<f32> {
130        let occlusion = self.occlusion.as_ref()?;
131        Some(occlusion.factor * (Self::get_pixel(tex_coords, &occlusion.texture)[0] as f32 / 255.))
132    }
133
134    /// Get the emissive color Rgb of the material given a texture coordinate.
135    /// If no `emissive_texture` is available then the `emissive_factor` is
136    /// returned.
137    ///
138    /// **Important**: `tex_coords` must contain values between `[0., 1.]`
139    /// otherwise the function will fail.
140    pub fn get_emissive(&self, tex_coords: Vector2<f32>) -> Vector3<f32> {
141        let mut res = self.emissive.factor;
142        if let Some(texture) = &self.emissive.texture {
143            let pixel = Self::get_pixel(tex_coords, texture);
144            for i in 0..3 {
145                res[i] *= (pixel[i] as f32) / 255.;
146            }
147        }
148        res
149    }
150
151    fn get_pixel<P, Container>(tex_coords: Vector2<f32>, texture: &ImageBuffer<P, Container>) -> P
152    where
153        P: Pixel + 'static,
154        P::Subpixel: 'static,
155        Container: Deref<Target = [P::Subpixel]>,
156    {
157        let coords = tex_coords.mul_element_wise(Vector2::new(
158            texture.width() as f32,
159            texture.height() as f32,
160        ));
161
162        texture[(
163            (coords.x as i64).rem_euclid(texture.width() as i64) as u32,
164            (coords.y as i64).rem_euclid(texture.height() as i64) as u32,
165        )]
166    }
167
168    pub(crate) fn load(gltf_mat: gltf::Material, data: &mut GltfData) -> Arc<Self> {
169        if let Some(material) = data.materials.get(&gltf_mat.index()) {
170            return material.clone();
171        }
172
173        let material = Arc::new(Material {
174            #[cfg(feature = "names")]
175            name: gltf_mat.name().map(String::from),
176            #[cfg(feature = "extras")]
177            extras: gltf_mat.extras().clone(),
178
179            pbr: PbrMaterial::load(gltf_mat.pbr_metallic_roughness(), data),
180            normal: NormalMap::load(&gltf_mat, data),
181            occlusion: Occlusion::load(&gltf_mat, data),
182            emissive: Emissive::load(&gltf_mat, data),
183        });
184
185        // Add to the collection
186        data.materials.insert(gltf_mat.index(), material.clone());
187        material
188    }
189}