three_d/renderer/material/
physical_material.rs

1use crate::core::*;
2use crate::renderer::*;
3
4///
5/// A physically-based material that renders a [Geometry] in an approximate correct physical manner based on Physically Based Rendering (PBR).
6/// This material is affected by lights.
7///
8#[derive(Clone)]
9pub struct PhysicalMaterial {
10    /// Name.
11    pub name: String,
12    /// Albedo base color, also called diffuse color.
13    pub albedo: Srgba,
14    /// Texture with albedo base colors, also called diffuse color.
15    /// The colors are assumed to be in linear sRGB (`RgbU8`), linear sRGB with an alpha channel (`RgbaU8`) or HDR color space.
16    pub albedo_texture: Option<Texture2DRef>,
17    /// A value in the range `[0..1]` specifying how metallic the surface is.
18    pub metallic: f32,
19    /// A value in the range `[0..1]` specifying how rough the surface is.
20    pub roughness: f32,
21    /// Texture containing the metallic and roughness parameters which are multiplied with the [Self::metallic] and [Self::roughness] values in the shader.
22    /// The metallic values are sampled from the blue channel and the roughness from the green channel.
23    pub metallic_roughness_texture: Option<Texture2DRef>,
24    /// A scalar multiplier controlling the amount of occlusion applied from the [Self::occlusion_texture]. A value of 0.0 means no occlusion. A value of 1.0 means full occlusion.
25    pub occlusion_strength: f32,
26    /// An occlusion map. Higher values indicate areas that should receive full indirect lighting and lower values indicate no indirect lighting.
27    /// The occlusion values are sampled from the red channel.
28    pub occlusion_texture: Option<Texture2DRef>,
29    /// A scalar multiplier applied to each normal vector of the [Self::normal_texture].
30    pub normal_scale: f32,
31    /// A tangent space normal map, also known as bump map.
32    pub normal_texture: Option<Texture2DRef>,
33    /// Render states.
34    pub render_states: RenderStates,
35    /// Whether this material should be treated as a transparent material (An object needs to be rendered differently depending on whether it is transparent or opaque).
36    pub is_transparent: bool,
37    /// Color of light shining from an object.
38    pub emissive: Srgba,
39    /// Texture with color of light shining from an object.
40    /// The colors are assumed to be in linear sRGB (`RgbU8`), linear sRGB with an alpha channel (`RgbaU8`) or HDR color space.
41    pub emissive_texture: Option<Texture2DRef>,
42    /// The lighting model used when rendering this material
43    pub lighting_model: LightingModel,
44}
45
46impl PhysicalMaterial {
47    ///
48    /// Constructs a new physical material from a [CpuMaterial].
49    /// If the input contains an [CpuMaterial::occlusion_metallic_roughness_texture], this texture is used for both
50    /// [PhysicalMaterial::metallic_roughness_texture] and [PhysicalMaterial::occlusion_texture] while any [CpuMaterial::metallic_roughness_texture] or [CpuMaterial::occlusion_texture] are ignored.
51    /// Tries to infer whether this material is transparent or opaque from the alpha value of the albedo color and the alpha values in the albedo texture.
52    /// Since this is not always correct, it is preferred to use [PhysicalMaterial::new_opaque] or [PhysicalMaterial::new_transparent].
53    ///
54    pub fn new(context: &Context, cpu_material: &CpuMaterial) -> Self {
55        Self::new_internal(context, cpu_material, super::is_transparent(cpu_material))
56    }
57
58    /// Constructs a new opaque physical material from a [CpuMaterial].
59    /// If the input contains an [CpuMaterial::occlusion_metallic_roughness_texture], this texture is used for both
60    /// [PhysicalMaterial::metallic_roughness_texture] and [PhysicalMaterial::occlusion_texture] while any [CpuMaterial::metallic_roughness_texture] or [CpuMaterial::occlusion_texture] are ignored.
61    pub fn new_opaque(context: &Context, cpu_material: &CpuMaterial) -> Self {
62        Self::new_internal(context, cpu_material, false)
63    }
64
65    /// Constructs a new transparent physical material from a [CpuMaterial].
66    /// If the input contains an [CpuMaterial::occlusion_metallic_roughness_texture], this texture is used for both
67    /// [PhysicalMaterial::metallic_roughness_texture] and [PhysicalMaterial::occlusion_texture] while any [CpuMaterial::metallic_roughness_texture] or [CpuMaterial::occlusion_texture] are ignored.
68    pub fn new_transparent(context: &Context, cpu_material: &CpuMaterial) -> Self {
69        Self::new_internal(context, cpu_material, true)
70    }
71
72    fn new_internal(context: &Context, cpu_material: &CpuMaterial, is_transparent: bool) -> Self {
73        let albedo_texture =
74            cpu_material
75                .albedo_texture
76                .as_ref()
77                .map(|cpu_texture| match &cpu_texture.data {
78                    TextureData::RgbU8(_) | TextureData::RgbaU8(_) => {
79                        let mut cpu_texture = cpu_texture.clone();
80                        cpu_texture.data.to_linear_srgb();
81                        Texture2DRef::from_cpu_texture(context, &cpu_texture)
82                    }
83                    _ => Texture2DRef::from_cpu_texture(context, cpu_texture),
84                });
85        let metallic_roughness_texture =
86            if let Some(ref cpu_texture) = cpu_material.occlusion_metallic_roughness_texture {
87                Some(Texture2DRef::from_cpu_texture(context, cpu_texture))
88            } else {
89                cpu_material
90                    .metallic_roughness_texture
91                    .as_ref()
92                    .map(|cpu_texture| Texture2DRef::from_cpu_texture(context, cpu_texture))
93            };
94        let occlusion_texture = if cpu_material.occlusion_metallic_roughness_texture.is_some() {
95            metallic_roughness_texture.clone()
96        } else {
97            cpu_material
98                .occlusion_texture
99                .as_ref()
100                .map(|cpu_texture| Texture2DRef::from_cpu_texture(context, cpu_texture))
101        };
102        let normal_texture = cpu_material
103            .normal_texture
104            .as_ref()
105            .map(|cpu_texture| Texture2DRef::from_cpu_texture(context, cpu_texture));
106        let emissive_texture =
107            cpu_material
108                .emissive_texture
109                .as_ref()
110                .map(|cpu_texture| match &cpu_texture.data {
111                    TextureData::RgbU8(_) | TextureData::RgbaU8(_) => {
112                        let mut cpu_texture = cpu_texture.clone();
113                        cpu_texture.data.to_linear_srgb();
114                        Texture2DRef::from_cpu_texture(context, &cpu_texture)
115                    }
116                    _ => Texture2DRef::from_cpu_texture(context, cpu_texture),
117                });
118        Self {
119            name: cpu_material.name.clone(),
120            albedo: cpu_material.albedo,
121            albedo_texture,
122            metallic: cpu_material.metallic,
123            roughness: cpu_material.roughness,
124            metallic_roughness_texture,
125            normal_texture,
126            normal_scale: cpu_material.normal_scale,
127            occlusion_texture,
128            occlusion_strength: cpu_material.occlusion_strength,
129            render_states: if is_transparent {
130                RenderStates {
131                    write_mask: WriteMask::COLOR,
132                    blend: Blend::TRANSPARENCY,
133                    ..Default::default()
134                }
135            } else {
136                RenderStates::default()
137            },
138            is_transparent,
139            emissive: cpu_material.emissive,
140            emissive_texture,
141            lighting_model: cpu_material.lighting_model,
142        }
143    }
144}
145
146impl FromCpuMaterial for PhysicalMaterial {
147    fn from_cpu_material(context: &Context, cpu_material: &CpuMaterial) -> Self {
148        Self::new(context, cpu_material)
149    }
150}
151
152impl Material for PhysicalMaterial {
153    fn id(&self) -> EffectMaterialId {
154        EffectMaterialId::PhysicalMaterial(
155            self.albedo_texture.is_some(),
156            self.metallic_roughness_texture.is_some(),
157            self.occlusion_texture.is_some(),
158            self.normal_texture.is_some(),
159            self.emissive_texture.is_some(),
160        )
161    }
162
163    fn fragment_shader_source(&self, lights: &[&dyn Light]) -> String {
164        let mut output = lights_shader_source(lights);
165        if self.albedo_texture.is_some()
166            || self.metallic_roughness_texture.is_some()
167            || self.normal_texture.is_some()
168            || self.occlusion_texture.is_some()
169            || self.emissive_texture.is_some()
170        {
171            output.push_str("in vec2 uvs;\n");
172            if self.albedo_texture.is_some() {
173                output.push_str("#define USE_ALBEDO_TEXTURE;\n");
174            }
175            if self.metallic_roughness_texture.is_some() {
176                output.push_str("#define USE_METALLIC_ROUGHNESS_TEXTURE;\n");
177            }
178            if self.occlusion_texture.is_some() {
179                output.push_str("#define USE_OCCLUSION_TEXTURE;\n");
180            }
181            if self.normal_texture.is_some() {
182                output.push_str("#define USE_NORMAL_TEXTURE;\nin vec3 tang;\nin vec3 bitang;\n");
183            }
184            if self.emissive_texture.is_some() {
185                output.push_str("#define USE_EMISSIVE_TEXTURE;\n");
186            }
187        }
188        output.push_str(ToneMapping::fragment_shader_source());
189        output.push_str(ColorMapping::fragment_shader_source());
190        output.push_str(include_str!("shaders/physical_material.frag"));
191        output
192    }
193
194    fn use_uniforms(&self, program: &Program, viewer: &dyn Viewer, lights: &[&dyn Light]) {
195        program.use_uniform_if_required("lightingModel", lighting_model_to_id(self.lighting_model));
196        viewer.tone_mapping().use_uniforms(program);
197        viewer.color_mapping().use_uniforms(program);
198        program.use_uniform_if_required("cameraPosition", viewer.position());
199        for (i, light) in lights.iter().enumerate() {
200            light.use_uniforms(program, i as u32);
201        }
202        program.use_uniform_if_required("metallic", self.metallic);
203        program.use_uniform_if_required("roughness", self.roughness);
204        if program.requires_uniform("albedoTexture") {
205            if let Some(ref texture) = self.albedo_texture {
206                program.use_uniform("albedoTexTransform", texture.transformation);
207                program.use_texture("albedoTexture", texture);
208            }
209        }
210        if program.requires_uniform("metallicRoughnessTexture") {
211            if let Some(ref texture) = self.metallic_roughness_texture {
212                program.use_uniform("metallicRoughnessTexTransform", texture.transformation);
213                program.use_texture("metallicRoughnessTexture", texture);
214            }
215        }
216        if program.requires_uniform("occlusionTexture") {
217            if let Some(ref texture) = self.occlusion_texture {
218                program.use_uniform("occlusionTexTransform", texture.transformation);
219                program.use_uniform("occlusionStrength", self.occlusion_strength);
220                program.use_texture("occlusionTexture", texture);
221            }
222        }
223        if program.requires_uniform("normalTexture") {
224            if let Some(ref texture) = self.normal_texture {
225                program.use_uniform("normalTexTransform", texture.transformation);
226                program.use_uniform("normalScale", self.normal_scale);
227                program.use_texture("normalTexture", texture);
228            }
229        }
230
231        program.use_uniform("albedo", self.albedo.to_linear_srgb());
232        program.use_uniform("emissive", self.emissive.to_linear_srgb());
233        if program.requires_uniform("emissiveTexture") {
234            if let Some(ref texture) = self.emissive_texture {
235                program.use_uniform("emissiveTexTransform", texture.transformation);
236                program.use_texture("emissiveTexture", texture);
237            }
238        }
239    }
240
241    fn render_states(&self) -> RenderStates {
242        self.render_states
243    }
244    fn material_type(&self) -> MaterialType {
245        if self.is_transparent {
246            MaterialType::Transparent
247        } else {
248            MaterialType::Opaque
249        }
250    }
251}
252
253impl Default for PhysicalMaterial {
254    fn default() -> Self {
255        Self {
256            name: "default".to_string(),
257            albedo: Srgba::WHITE,
258            albedo_texture: None,
259            metallic: 0.0,
260            roughness: 1.0,
261            metallic_roughness_texture: None,
262            normal_texture: None,
263            normal_scale: 1.0,
264            occlusion_texture: None,
265            occlusion_strength: 1.0,
266            render_states: RenderStates::default(),
267            is_transparent: false,
268            emissive: Srgba::BLACK,
269            emissive_texture: None,
270            lighting_model: LightingModel::Blinn,
271        }
272    }
273}