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