three_d/renderer/material/
deferred_physical_material.rs

1use crate::core::*;
2use crate::renderer::*;
3
4///
5/// Similar to [PhysicalMaterial] except that rendering happens in two stages which produces the same result, but is more efficient for complex scenes.
6/// This material does not support transparency but does support [alpha cutout](DeferredPhysicalMaterial::alpha_cutout).
7///
8/// The first stage renders geometry information to a [RenderTarget] and the second stage uses this render target to apply lighting based on the geometry information which means the expensive lighting calculations are only done once per pixel.
9/// The [RenderTarget::render], [ColorTarget::render] or [DepthTarget::render] methods all support the two stages required by this material, so just pass the [Object] with this material applied into one of these methods.
10/// However, it is not possible to use the [Object::render] method to render a [Geometry] with this material directly to the screen.
11/// Instead render the object into a [RenderTarget] consisting of a [Texture2DArray] with three RGBA u8 layers as color target and a [DepthTexture2D] as depth target.
12/// Then call the [DeferredPhysicalMaterial::lighting_pass] method with these textures to render to the screen.
13///
14#[derive(Clone)]
15pub struct DeferredPhysicalMaterial {
16    /// Name.
17    pub name: String,
18    /// Albedo base color, also called diffuse color.
19    pub albedo: Srgba,
20    /// Texture with albedo base colors, also called diffuse color.
21    /// The colors are assumed to be in linear sRGB (`RgbU8`), linear sRGB with an alpha channel (`RgbaU8`) or HDR color space.
22    pub albedo_texture: Option<Texture2DRef>,
23    /// A value in the range `[0..1]` specifying how metallic the material is.
24    pub metallic: f32,
25    /// A value in the range `[0..1]` specifying how rough the material surface is.
26    pub roughness: f32,
27    /// Texture containing the metallic and roughness parameters which are multiplied with the [Self::metallic] and [Self::roughness] values in the shader.
28    /// The metallic values are sampled from the blue channel and the roughness from the green channel.
29    pub metallic_roughness_texture: Option<Texture2DRef>,
30    /// 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.
31    pub occlusion_strength: f32,
32    /// An occlusion map. Higher values indicate areas that should receive full indirect lighting and lower values indicate no indirect lighting.
33    /// The occlusion values are sampled from the red channel.
34    pub occlusion_texture: Option<Texture2DRef>,
35    /// A scalar multiplier applied to each normal vector of the [Self::normal_texture].
36    pub normal_scale: f32,
37    /// A tangent space normal map, also known as bump map.
38    pub normal_texture: Option<Texture2DRef>,
39    /// Render states
40    pub render_states: RenderStates,
41    /// Color of light shining from an object.
42    pub emissive: Srgba,
43    /// Texture with color of light shining from an object.
44    /// The colors are assumed to be in linear sRGB (`RgbU8`), linear sRGB with an alpha channel (`RgbaU8`) or HDR color space.
45    pub emissive_texture: Option<Texture2DRef>,
46    /// A threshold on the alpha value of the color as a workaround for transparency.
47    /// If the alpha value of a pixel touched by an object with this material is less than the threshold, then that object is not contributing to the color of that pixel.
48    /// On the other hand, if the alpha value is more than the threshold, then it is contributing fully to that pixel and thereby blocks out everything behind.
49    pub alpha_cutout: Option<f32>,
50}
51
52impl DeferredPhysicalMaterial {
53    ///
54    /// Constructs a new deferred physical material from a [CpuMaterial].
55    /// If the input contains an [CpuMaterial::occlusion_metallic_roughness_texture], this texture is used for both
56    /// [DeferredPhysicalMaterial::metallic_roughness_texture] and [DeferredPhysicalMaterial::occlusion_texture] while any [CpuMaterial::metallic_roughness_texture] or [CpuMaterial::occlusion_texture] are ignored.
57    ///
58    pub fn new(context: &Context, cpu_material: &CpuMaterial) -> Self {
59        let albedo_texture =
60            cpu_material
61                .albedo_texture
62                .as_ref()
63                .map(|cpu_texture| match &cpu_texture.data {
64                    TextureData::RgbU8(_) | TextureData::RgbaU8(_) => {
65                        let mut cpu_texture = cpu_texture.clone();
66                        cpu_texture.data.to_linear_srgb();
67                        Texture2DRef::from_cpu_texture(context, &cpu_texture)
68                    }
69                    _ => Texture2DRef::from_cpu_texture(context, cpu_texture),
70                });
71        let metallic_roughness_texture =
72            if let Some(ref cpu_texture) = cpu_material.occlusion_metallic_roughness_texture {
73                Some(Texture2DRef::from_cpu_texture(context, cpu_texture))
74            } else {
75                cpu_material
76                    .metallic_roughness_texture
77                    .as_ref()
78                    .map(|cpu_texture| Texture2DRef::from_cpu_texture(context, cpu_texture))
79            };
80        let occlusion_texture = if cpu_material.occlusion_metallic_roughness_texture.is_some() {
81            metallic_roughness_texture.clone()
82        } else {
83            cpu_material
84                .occlusion_texture
85                .as_ref()
86                .map(|cpu_texture| Texture2DRef::from_cpu_texture(context, cpu_texture))
87        };
88        let normal_texture = cpu_material
89            .normal_texture
90            .as_ref()
91            .map(|cpu_texture| Texture2DRef::from_cpu_texture(context, cpu_texture));
92        let emissive_texture =
93            cpu_material
94                .emissive_texture
95                .as_ref()
96                .map(|cpu_texture| match &cpu_texture.data {
97                    TextureData::RgbU8(_) | TextureData::RgbaU8(_) => {
98                        let mut cpu_texture = cpu_texture.clone();
99                        cpu_texture.data.to_linear_srgb();
100                        Texture2DRef::from_cpu_texture(context, &cpu_texture)
101                    }
102                    _ => Texture2DRef::from_cpu_texture(context, cpu_texture),
103                });
104        Self {
105            name: cpu_material.name.clone(),
106            albedo: cpu_material.albedo,
107            albedo_texture,
108            metallic: cpu_material.metallic,
109            roughness: cpu_material.roughness,
110            metallic_roughness_texture,
111            normal_texture,
112            normal_scale: cpu_material.normal_scale,
113            occlusion_texture,
114            occlusion_strength: cpu_material.occlusion_strength,
115            render_states: RenderStates::default(),
116            alpha_cutout: cpu_material.alpha_cutout,
117            emissive: cpu_material.emissive,
118            emissive_texture,
119        }
120    }
121
122    ///
123    /// Constructs a deferred physical material from a physical material.
124    ///
125    pub fn from_physical_material(physical_material: &PhysicalMaterial) -> Self {
126        Self {
127            name: physical_material.name.clone(),
128            albedo: physical_material.albedo,
129            albedo_texture: physical_material.albedo_texture.clone(),
130            metallic: physical_material.metallic,
131            roughness: physical_material.roughness,
132            metallic_roughness_texture: physical_material.metallic_roughness_texture.clone(),
133            normal_texture: physical_material.normal_texture.clone(),
134            normal_scale: physical_material.normal_scale,
135            occlusion_texture: physical_material.occlusion_texture.clone(),
136            occlusion_strength: physical_material.occlusion_strength,
137            render_states: RenderStates {
138                write_mask: WriteMask::default(),
139                blend: Blend::Disabled,
140                ..physical_material.render_states
141            },
142            emissive: physical_material.emissive,
143            emissive_texture: physical_material.emissive_texture.clone(),
144            alpha_cutout: if physical_material.is_transparent {
145                Some(0.5)
146            } else {
147                None
148            },
149        }
150    }
151    ///
152    /// The second stage of a deferred render call.
153    /// Use the [Object::render] method to render the objects with this material into a [RenderTarget] and then call this method with these textures to render to the screen.
154    /// See [DeferredPhysicalMaterial] for more information.
155    ///
156    pub fn lighting_pass(
157        context: &Context,
158        viewer: &dyn Viewer,
159        geometry_pass_color_texture: ColorTexture,
160        geometry_pass_depth_texture: DepthTexture,
161        lights: &[&dyn Light],
162    ) {
163        apply_screen_effect(
164            context,
165            lighting_pass::LightingPassEffect {},
166            viewer,
167            lights,
168            Some(geometry_pass_color_texture),
169            Some(geometry_pass_depth_texture),
170        );
171    }
172}
173
174impl FromCpuMaterial for DeferredPhysicalMaterial {
175    fn from_cpu_material(context: &Context, cpu_material: &CpuMaterial) -> Self {
176        Self::new(context, cpu_material)
177    }
178}
179
180impl Material for DeferredPhysicalMaterial {
181    fn id(&self) -> EffectMaterialId {
182        EffectMaterialId::DeferredPhysicalMaterial(
183            self.albedo_texture.is_some(),
184            self.metallic_roughness_texture.is_some(),
185            self.occlusion_texture.is_some(),
186            self.normal_texture.is_some(),
187            self.emissive_texture.is_some(),
188            self.alpha_cutout.is_some(),
189        )
190    }
191
192    fn fragment_shader_source(&self, _lights: &[&dyn Light]) -> String {
193        let mut output = include_str!("../../core/shared.frag").to_string();
194        if self.albedo_texture.is_some()
195            || self.metallic_roughness_texture.is_some()
196            || self.normal_texture.is_some()
197            || self.occlusion_texture.is_some()
198            || self.emissive_texture.is_some()
199            || self.alpha_cutout.is_some()
200        {
201            output.push_str("in vec2 uvs;\n");
202            if self.albedo_texture.is_some() {
203                output.push_str("#define USE_ALBEDO_TEXTURE;\n");
204            }
205            if self.metallic_roughness_texture.is_some() {
206                output.push_str("#define USE_METALLIC_ROUGHNESS_TEXTURE;\n");
207            }
208            if self.occlusion_texture.is_some() {
209                output.push_str("#define USE_OCCLUSION_TEXTURE;\n");
210            }
211            if self.normal_texture.is_some() {
212                output.push_str("#define USE_NORMAL_TEXTURE;\nin vec3 tang;\nin vec3 bitang;\n");
213            }
214            if self.emissive_texture.is_some() {
215                output.push_str("#define USE_EMISSIVE_TEXTURE;\n");
216            }
217            if self.alpha_cutout.is_some() {
218                output.push_str(
219                    format!(
220                        "#define ALPHACUT;\nfloat acut = {};",
221                        self.alpha_cutout.unwrap()
222                    )
223                    .as_str(),
224                );
225            }
226        }
227        output.push_str(include_str!("shaders/deferred_physical_material.frag"));
228        output
229    }
230
231    fn use_uniforms(&self, program: &Program, _viewer: &dyn Viewer, _lights: &[&dyn Light]) {
232        program.use_uniform("metallic", self.metallic);
233        program.use_uniform("roughness", self.roughness);
234        program.use_uniform("albedo", self.albedo.to_linear_srgb());
235        program.use_uniform("emissive", self.emissive.to_linear_srgb());
236        if let Some(ref texture) = self.albedo_texture {
237            program.use_texture("albedoTexture", texture);
238            program.use_uniform("albedoTexTransform", texture.transformation);
239        }
240        if let Some(ref texture) = self.metallic_roughness_texture {
241            program.use_texture("metallicRoughnessTexture", texture);
242            program.use_uniform("metallicRoughnessTexTransform", texture.transformation);
243        }
244        if let Some(ref texture) = self.occlusion_texture {
245            program.use_uniform("occlusionStrength", self.occlusion_strength);
246            program.use_uniform("occlusionTexTransform", texture.transformation);
247            program.use_texture("occlusionTexture", texture);
248        }
249        if let Some(ref texture) = self.normal_texture {
250            program.use_uniform("normalScale", self.normal_scale);
251            program.use_uniform("normalTexTransform", texture.transformation);
252            program.use_texture("normalTexture", texture);
253        }
254        if program.requires_uniform("emissiveTexture") {
255            if let Some(ref texture) = self.emissive_texture {
256                program.use_uniform("emissiveTexTransform", texture.transformation);
257                program.use_texture("emissiveTexture", texture);
258            }
259        }
260    }
261
262    fn render_states(&self) -> RenderStates {
263        self.render_states
264    }
265
266    fn material_type(&self) -> MaterialType {
267        MaterialType::Deferred
268    }
269}
270
271impl Default for DeferredPhysicalMaterial {
272    fn default() -> Self {
273        Self {
274            name: "default".to_string(),
275            albedo: Srgba::WHITE,
276            albedo_texture: None,
277            metallic: 0.0,
278            roughness: 1.0,
279            metallic_roughness_texture: None,
280            normal_texture: None,
281            normal_scale: 1.0,
282            occlusion_texture: None,
283            occlusion_strength: 1.0,
284            render_states: RenderStates::default(),
285            alpha_cutout: None,
286            emissive: Srgba::BLACK,
287            emissive_texture: None,
288        }
289    }
290}