rend3_pbr/
material.rs

1//! Types which make up `rend3-pbr`'s material [`PbrMaterial`]
2
3use std::mem;
4
5use glam::{Mat3, Mat3A, Vec3, Vec4};
6use rend3::types::{Material, TextureHandle};
7
8bitflags::bitflags! {
9    /// Flags which shaders use to determine properties of a material
10    pub struct MaterialFlags : u32 {
11        const ALBEDO_ACTIVE =       0b0000_0000_0000_0001;
12        const ALBEDO_BLEND =        0b0000_0000_0000_0010;
13        const ALBEDO_VERTEX_SRGB =  0b0000_0000_0000_0100;
14        const BICOMPONENT_NORMAL =  0b0000_0000_0000_1000;
15        const SWIZZLED_NORMAL =     0b0000_0000_0001_0000;
16        const AOMR_COMBINED =       0b0000_0000_0010_0000;
17        const AOMR_SWIZZLED_SPLIT = 0b0000_0000_0100_0000;
18        const AOMR_SPLIT =          0b0000_0000_1000_0000;
19        const AOMR_BW_SPLIT =       0b0000_0001_0000_0000;
20        const CC_GLTF_COMBINED =    0b0000_0010_0000_0000;
21        const CC_GLTF_SPLIT =       0b0000_0100_0000_0000;
22        const CC_BW_SPLIT =         0b0000_1000_0000_0000;
23        const UNLIT =               0b0001_0000_0000_0000;
24        const NEAREST =             0b0010_0000_0000_0000;
25    }
26}
27
28/// How the albedo color should be determined.
29#[derive(Debug, Clone)]
30pub enum AlbedoComponent {
31    /// No albedo color.
32    None,
33    /// Albedo color is the vertex value.
34    Vertex {
35        /// Vertex should be converted from srgb -> linear before multiplication.
36        srgb: bool,
37    },
38    /// Albedo color is the given value.
39    Value(Vec4),
40    /// Albedo color is the given value multiplied by the vertex color.
41    ValueVertex {
42        value: Vec4,
43        /// Vertex should be converted from srgb -> linear before multiplication.
44        srgb: bool,
45    },
46    /// Albedo color is loaded from the given texture.
47    Texture(TextureHandle),
48    /// Albedo color is loaded from the given texture, then multiplied
49    /// by the vertex color.
50    TextureVertex {
51        texture: TextureHandle,
52        /// Vertex should be converted from srgb -> linear before multiplication.
53        srgb: bool,
54    },
55    /// Albedo color is loaded from given texture, then multiplied
56    /// by the given value.
57    TextureValue { texture: TextureHandle, value: Vec4 },
58    /// Albedo color is loaded from the given texture, then multiplied
59    /// by the vertex color and the given value.
60    TextureVertexValue {
61        texture: TextureHandle,
62        /// Vertex should be converted from srgb -> linear before multiplication.
63        srgb: bool,
64        value: Vec4,
65    },
66}
67
68impl Default for AlbedoComponent {
69    fn default() -> Self {
70        Self::None
71    }
72}
73
74impl AlbedoComponent {
75    pub fn to_value(&self) -> Vec4 {
76        match *self {
77            Self::Value(value) => value,
78            Self::ValueVertex { value, .. } => value,
79            Self::TextureValue { value, .. } => value,
80            _ => Vec4::splat(1.0),
81        }
82    }
83
84    pub fn to_flags(&self) -> MaterialFlags {
85        match *self {
86            Self::None => MaterialFlags::empty(),
87            Self::Value(_) | Self::Texture(_) | Self::TextureValue { .. } => MaterialFlags::ALBEDO_ACTIVE,
88            Self::Vertex { srgb: false }
89            | Self::ValueVertex { srgb: false, .. }
90            | Self::TextureVertex { srgb: false, .. }
91            | Self::TextureVertexValue { srgb: false, .. } => {
92                MaterialFlags::ALBEDO_ACTIVE | MaterialFlags::ALBEDO_BLEND
93            }
94            Self::Vertex { srgb: true }
95            | Self::ValueVertex { srgb: true, .. }
96            | Self::TextureVertex { srgb: true, .. }
97            | Self::TextureVertexValue { srgb: true, .. } => {
98                MaterialFlags::ALBEDO_ACTIVE | MaterialFlags::ALBEDO_BLEND | MaterialFlags::ALBEDO_VERTEX_SRGB
99            }
100        }
101    }
102
103    pub fn is_texture(&self) -> bool {
104        matches!(
105            *self,
106            Self::Texture(..)
107                | Self::TextureVertex { .. }
108                | Self::TextureValue { .. }
109                | Self::TextureVertexValue { .. }
110        )
111    }
112
113    pub fn to_texture<Func, Out>(&self, func: Func) -> Option<Out>
114    where
115        Func: FnOnce(&TextureHandle) -> Out,
116    {
117        match *self {
118            Self::None | Self::Vertex { .. } | Self::Value(_) | Self::ValueVertex { .. } => None,
119            Self::Texture(ref texture)
120            | Self::TextureVertex { ref texture, .. }
121            | Self::TextureValue { ref texture, .. }
122            | Self::TextureVertexValue { ref texture, .. } => Some(func(texture)),
123        }
124    }
125}
126
127/// Generic container for a component of a material that could either be from a texture or a fixed value.
128#[derive(Debug, Clone)]
129pub enum MaterialComponent<T> {
130    None,
131    Value(T),
132    Texture(TextureHandle),
133    TextureValue { texture: TextureHandle, value: T },
134}
135
136impl<T> Default for MaterialComponent<T> {
137    fn default() -> Self {
138        Self::None
139    }
140}
141
142impl<T: Copy> MaterialComponent<T> {
143    pub fn to_value(&self, default: T) -> T {
144        match *self {
145            Self::Value(value) | Self::TextureValue { value, .. } => value,
146            Self::None | Self::Texture(_) => default,
147        }
148    }
149
150    pub fn is_texture(&self) -> bool {
151        matches!(*self, Self::Texture(..) | Self::TextureValue { .. })
152    }
153
154    pub fn to_texture<Func, Out>(&self, func: Func) -> Option<Out>
155    where
156        Func: FnOnce(&TextureHandle) -> Out,
157    {
158        match *self {
159            Self::None | Self::Value(_) => None,
160            Self::Texture(ref texture) | Self::TextureValue { ref texture, .. } => Some(func(texture)),
161        }
162    }
163}
164
165/// How normals should be derived
166#[derive(Debug, Clone)]
167pub enum NormalTexture {
168    /// No normal texture.
169    None,
170    /// Normal stored in RGB values.
171    Tricomponent(TextureHandle),
172    /// Normal stored in RG values, third value should be reconstructed.
173    Bicomponent(TextureHandle),
174    /// Normal stored in Green and Alpha values, third value should be reconstructed.
175    /// This is useful for storing in BC3 or BC7 compressed textures.
176    BicomponentSwizzled(TextureHandle),
177}
178impl Default for NormalTexture {
179    fn default() -> Self {
180        Self::None
181    }
182}
183
184impl NormalTexture {
185    pub fn to_texture<Func, Out>(&self, func: Func) -> Option<Out>
186    where
187        Func: FnOnce(&TextureHandle) -> Out,
188    {
189        match *self {
190            Self::None => None,
191            Self::Tricomponent(ref texture)
192            | Self::Bicomponent(ref texture)
193            | Self::BicomponentSwizzled(ref texture) => Some(func(texture)),
194        }
195    }
196
197    pub fn to_flags(&self) -> MaterialFlags {
198        match self {
199            Self::None => MaterialFlags::empty(),
200            Self::Tricomponent(..) => MaterialFlags::empty(),
201            Self::Bicomponent(..) => MaterialFlags::BICOMPONENT_NORMAL,
202            Self::BicomponentSwizzled(..) => MaterialFlags::BICOMPONENT_NORMAL | MaterialFlags::SWIZZLED_NORMAL,
203        }
204    }
205}
206
207/// How the Ambient Occlusion, Metalic, and Roughness values should be determined.
208#[derive(Debug, Clone)]
209pub enum AoMRTextures {
210    None,
211    Combined {
212        /// Texture with Ambient Occlusion in R, Metallic in G, and Roughness in B
213        texture: Option<TextureHandle>,
214    },
215    SwizzledSplit {
216        /// Texture with Ambient Occlusion in R
217        ao_texture: Option<TextureHandle>,
218        /// Texture with Metallic in G, and Roughness in B
219        mr_texture: Option<TextureHandle>,
220    },
221    Split {
222        /// Texture with Ambient Occlusion in R
223        ao_texture: Option<TextureHandle>,
224        /// Texture with Metallic in R, and Roughness in G
225        mr_texture: Option<TextureHandle>,
226    },
227    BWSplit {
228        /// Texture with Ambient Occlusion in R
229        ao_texture: Option<TextureHandle>,
230        /// Texture with Metallic in R
231        m_texture: Option<TextureHandle>,
232        /// Texture with Roughness in R
233        r_texture: Option<TextureHandle>,
234    },
235}
236
237impl AoMRTextures {
238    pub fn to_roughness_texture<Func, Out>(&self, func: Func) -> Option<Out>
239    where
240        Func: FnOnce(&TextureHandle) -> Out,
241    {
242        match *self {
243            Self::Combined {
244                texture: Some(ref texture),
245            } => Some(func(texture)),
246            Self::SwizzledSplit {
247                mr_texture: Some(ref texture),
248                ..
249            } => Some(func(texture)),
250            Self::Split {
251                mr_texture: Some(ref texture),
252                ..
253            } => Some(func(texture)),
254            Self::BWSplit {
255                r_texture: Some(ref texture),
256                ..
257            } => Some(func(texture)),
258            _ => None,
259        }
260    }
261
262    pub fn to_metallic_texture<Func, Out>(&self, func: Func) -> Option<Out>
263    where
264        Func: FnOnce(&TextureHandle) -> Out,
265    {
266        match *self {
267            Self::Combined { .. } => None,
268            Self::SwizzledSplit { .. } => None,
269            Self::Split { .. } => None,
270            Self::BWSplit {
271                m_texture: Some(ref texture),
272                ..
273            } => Some(func(texture)),
274            _ => None,
275        }
276    }
277
278    pub fn to_ao_texture<Func, Out>(&self, func: Func) -> Option<Out>
279    where
280        Func: FnOnce(&TextureHandle) -> Out,
281    {
282        match *self {
283            Self::Combined { .. } => None,
284            Self::SwizzledSplit {
285                ao_texture: Some(ref texture),
286                ..
287            } => Some(func(texture)),
288            Self::Split {
289                ao_texture: Some(ref texture),
290                ..
291            } => Some(func(texture)),
292            Self::BWSplit {
293                ao_texture: Some(ref texture),
294                ..
295            } => Some(func(texture)),
296            _ => None,
297        }
298    }
299
300    pub fn to_flags(&self) -> MaterialFlags {
301        match self {
302            Self::Combined { .. } => MaterialFlags::AOMR_COMBINED,
303            Self::SwizzledSplit { .. } => MaterialFlags::AOMR_SWIZZLED_SPLIT,
304            Self::Split { .. } => MaterialFlags::AOMR_SPLIT,
305            Self::BWSplit { .. } => MaterialFlags::AOMR_BW_SPLIT,
306            // Use AOMR_COMBINED so shader only checks roughness texture, then bails
307            Self::None => MaterialFlags::AOMR_COMBINED,
308        }
309    }
310}
311impl Default for AoMRTextures {
312    fn default() -> Self {
313        Self::None
314    }
315}
316
317/// How clearcoat values should be derived.
318#[derive(Debug, Clone)]
319pub enum ClearcoatTextures {
320    GltfCombined {
321        /// Texture with Clearcoat in R, and Clearcoat Roughness in G
322        texture: Option<TextureHandle>,
323    },
324    GltfSplit {
325        /// Texture with Clearcoat in R
326        clearcoat_texture: Option<TextureHandle>,
327        /// Texture with Clearcoat Roughness in G
328        clearcoat_roughness_texture: Option<TextureHandle>,
329    },
330    BWSplit {
331        /// Texture with Clearcoat in R
332        clearcoat_texture: Option<TextureHandle>,
333        /// Texture with Clearcoat Roughness in R
334        clearcoat_roughness_texture: Option<TextureHandle>,
335    },
336    None,
337}
338
339impl ClearcoatTextures {
340    pub fn to_clearcoat_texture<Func, Out>(&self, func: Func) -> Option<Out>
341    where
342        Func: FnOnce(&TextureHandle) -> Out,
343    {
344        match *self {
345            Self::GltfCombined {
346                texture: Some(ref texture),
347            } => Some(func(texture)),
348            Self::GltfSplit {
349                clearcoat_texture: Some(ref texture),
350                ..
351            } => Some(func(texture)),
352            Self::BWSplit {
353                clearcoat_texture: Some(ref texture),
354                ..
355            } => Some(func(texture)),
356            _ => None,
357        }
358    }
359
360    pub fn to_clearcoat_roughness_texture<Func, Out>(&self, func: Func) -> Option<Out>
361    where
362        Func: FnOnce(&TextureHandle) -> Out,
363    {
364        match *self {
365            Self::GltfCombined { .. } => None,
366            Self::GltfSplit {
367                clearcoat_roughness_texture: Some(ref texture),
368                ..
369            } => Some(func(texture)),
370            Self::BWSplit {
371                clearcoat_roughness_texture: Some(ref texture),
372                ..
373            } => Some(func(texture)),
374            _ => None,
375        }
376    }
377
378    pub fn to_flags(&self) -> MaterialFlags {
379        match self {
380            Self::GltfCombined { .. } => MaterialFlags::CC_GLTF_COMBINED,
381            Self::GltfSplit { .. } => MaterialFlags::CC_GLTF_SPLIT,
382            Self::BWSplit { .. } => MaterialFlags::CC_BW_SPLIT,
383            // Use CC_GLTF_COMBINED so shader only checks clear coat texture, then bails
384            Self::None => MaterialFlags::CC_GLTF_COMBINED,
385        }
386    }
387}
388impl Default for ClearcoatTextures {
389    fn default() -> Self {
390        Self::None
391    }
392}
393
394/// How textures should be sampled.
395#[derive(Debug, Copy, Clone, PartialEq, Eq)]
396pub enum SampleType {
397    Nearest,
398    Linear,
399}
400impl Default for SampleType {
401    fn default() -> Self {
402        Self::Linear
403    }
404}
405
406/// The type of transparency in a material.
407#[repr(u8)]
408#[derive(Debug, Copy, Clone, PartialEq)]
409pub enum TransparencyType {
410    /// Alpha is completely ignored.
411    Opaque,
412    /// Alpha less than a specified value is discorded.
413    Cutout,
414    /// Alpha is blended.
415    Blend,
416}
417impl From<Transparency> for TransparencyType {
418    fn from(t: Transparency) -> Self {
419        match t {
420            Transparency::Opaque => Self::Opaque,
421            Transparency::Cutout { .. } => Self::Cutout,
422            Transparency::Blend => Self::Blend,
423        }
424    }
425}
426impl TransparencyType {
427    pub fn to_debug_str(self) -> &'static str {
428        match self {
429            TransparencyType::Opaque => "opaque",
430            TransparencyType::Cutout => "cutout",
431            TransparencyType::Blend => "blend",
432        }
433    }
434}
435
436#[allow(clippy::cmp_owned)] // This thinks making a temporary TransparencyType is the end of the world
437impl PartialEq<Transparency> for TransparencyType {
438    fn eq(&self, other: &Transparency) -> bool {
439        *self == Self::from(*other)
440    }
441}
442
443#[allow(clippy::cmp_owned)]
444impl PartialEq<TransparencyType> for Transparency {
445    fn eq(&self, other: &TransparencyType) -> bool {
446        TransparencyType::from(*self) == *other
447    }
448}
449
450/// How transparency should be handled in a material.
451#[derive(Debug, Copy, Clone, PartialEq)]
452pub enum Transparency {
453    /// Alpha is completely ignored.
454    Opaque,
455    /// Pixels with alpha less than `cutout` is discorded.
456    Cutout { cutout: f32 },
457    /// Alpha is blended.
458    Blend,
459}
460impl Default for Transparency {
461    fn default() -> Self {
462        Self::Opaque
463    }
464}
465
466// Consider:
467//
468// - Green screen value
469/// A set of textures and values that determine the how an object interacts with light.
470#[derive(Default)]
471pub struct PbrMaterial {
472    pub albedo: AlbedoComponent,
473    pub transparency: Transparency,
474    pub normal: NormalTexture,
475    pub aomr_textures: AoMRTextures,
476    pub ao_factor: Option<f32>,
477    pub metallic_factor: Option<f32>,
478    pub roughness_factor: Option<f32>,
479    pub clearcoat_textures: ClearcoatTextures,
480    pub clearcoat_factor: Option<f32>,
481    pub clearcoat_roughness_factor: Option<f32>,
482    pub emissive: MaterialComponent<Vec3>,
483    pub reflectance: MaterialComponent<f32>,
484    pub anisotropy: MaterialComponent<f32>,
485    pub uv_transform0: Mat3,
486    pub uv_transform1: Mat3,
487    // TODO: Determine how to make this a clearer part of the type system, esp. with the changable_struct macro.
488    pub unlit: bool,
489    pub sample_type: SampleType,
490}
491
492impl Material for PbrMaterial {
493    const TEXTURE_COUNT: u32 = 10;
494    const DATA_SIZE: u32 = mem::size_of::<ShaderMaterial>() as _;
495
496    fn object_key(&self) -> u64 {
497        TransparencyType::from(self.transparency) as u64
498    }
499
500    fn to_textures(
501        &self,
502        slice: &mut [Option<std::num::NonZeroU32>],
503        translation_fn: &mut (dyn FnMut(&TextureHandle) -> std::num::NonZeroU32 + '_),
504    ) {
505        slice[0] = self.albedo.to_texture(&mut *translation_fn);
506        slice[1] = self.normal.to_texture(&mut *translation_fn);
507        slice[2] = self.aomr_textures.to_roughness_texture(&mut *translation_fn);
508        slice[3] = self.aomr_textures.to_metallic_texture(&mut *translation_fn);
509        slice[4] = self.reflectance.to_texture(&mut *translation_fn);
510        slice[5] = self.clearcoat_textures.to_clearcoat_texture(&mut *translation_fn);
511        slice[6] = self
512            .clearcoat_textures
513            .to_clearcoat_roughness_texture(&mut *translation_fn);
514        slice[7] = self.emissive.to_texture(&mut *translation_fn);
515        slice[8] = self.anisotropy.to_texture(&mut *translation_fn);
516        slice[9] = self.aomr_textures.to_ao_texture(&mut *translation_fn);
517    }
518
519    fn to_data(&self, slice: &mut [u8]) {
520        slice.copy_from_slice(bytemuck::bytes_of(&ShaderMaterial::from_material(self)));
521    }
522}
523
524#[repr(C)]
525#[derive(Debug, Copy, Clone)]
526struct ShaderMaterial {
527    uv_transform0: Mat3A,
528    uv_transform1: Mat3A,
529
530    albedo: Vec4,
531    emissive: Vec3,
532    roughness: f32,
533    metallic: f32,
534    reflectance: f32,
535    clear_coat: f32,
536    clear_coat_roughness: f32,
537    anisotropy: f32,
538    ambient_occlusion: f32,
539    alpha_cutout: f32,
540
541    material_flags: MaterialFlags,
542}
543
544unsafe impl bytemuck::Zeroable for ShaderMaterial {}
545unsafe impl bytemuck::Pod for ShaderMaterial {}
546
547impl ShaderMaterial {
548    fn from_material(material: &PbrMaterial) -> Self {
549        Self {
550            uv_transform0: material.uv_transform0.into(),
551            uv_transform1: material.uv_transform1.into(),
552            albedo: material.albedo.to_value(),
553            roughness: material.roughness_factor.unwrap_or(0.0),
554            metallic: material.metallic_factor.unwrap_or(0.0),
555            reflectance: material.reflectance.to_value(0.5),
556            clear_coat: material.clearcoat_factor.unwrap_or(0.0),
557            clear_coat_roughness: material.clearcoat_roughness_factor.unwrap_or(0.0),
558            emissive: material.emissive.to_value(Vec3::ZERO),
559            anisotropy: material.anisotropy.to_value(0.0),
560            ambient_occlusion: material.ao_factor.unwrap_or(1.0),
561            alpha_cutout: match material.transparency {
562                Transparency::Cutout { cutout } => cutout,
563                _ => 0.0,
564            },
565            material_flags: {
566                let mut flags = material.albedo.to_flags();
567                flags |= material.normal.to_flags();
568                flags |= material.aomr_textures.to_flags();
569                flags |= material.clearcoat_textures.to_flags();
570                flags.set(MaterialFlags::UNLIT, material.unlit);
571                flags.set(
572                    MaterialFlags::NEAREST,
573                    match material.sample_type {
574                        SampleType::Nearest => true,
575                        SampleType::Linear => false,
576                    },
577                );
578                flags
579            },
580        }
581    }
582}