1use std::mem;
4
5use glam::{Mat3, Mat3A, Vec3, Vec4};
6use rend3::types::{Material, TextureHandle};
7
8use crate::{
9 common::Sorting,
10 depth::{AlphaCutoutSpec, DepthRenderableMaterial},
11};
12
13bitflags::bitflags! {
14 #[derive(Default)]
16 pub struct MaterialFlags : u32 {
17 const ALBEDO_ACTIVE = 0b0000_0000_0000_0001;
18 const ALBEDO_BLEND = 0b0000_0000_0000_0010;
19 const ALBEDO_VERTEX_SRGB = 0b0000_0000_0000_0100;
20 const BICOMPONENT_NORMAL = 0b0000_0000_0000_1000;
21 const SWIZZLED_NORMAL = 0b0000_0000_0001_0000;
22 const YDOWN_NORMAL = 0b0000_0000_0010_0000;
23 const AOMR_COMBINED = 0b0000_0000_0100_0000;
24 const AOMR_SWIZZLED_SPLIT = 0b0000_0000_1000_0000;
25 const AOMR_SPLIT = 0b0000_0001_0000_0000;
26 const AOMR_BW_SPLIT = 0b0000_0010_0000_0000;
27 const CC_GLTF_COMBINED = 0b0000_0100_0000_0000;
28 const CC_GLTF_SPLIT = 0b0000_1000_0000_0000;
29 const CC_BW_SPLIT = 0b0001_0000_0000_0000;
30 const UNLIT = 0b0010_0000_0000_0000;
31 const NEAREST = 0b0100_0000_0000_0000;
32 }
33}
34
35#[derive(Debug, Clone)]
37pub enum AlbedoComponent {
38 None,
40 Vertex {
42 srgb: bool,
45 },
46 Value(Vec4),
48 ValueVertex {
50 value: Vec4,
51 srgb: bool,
54 },
55 Texture(TextureHandle),
57 TextureVertex {
60 texture: TextureHandle,
61 srgb: bool,
64 },
65 TextureValue { texture: TextureHandle, value: Vec4 },
68 TextureVertexValue {
71 texture: TextureHandle,
72 srgb: bool,
75 value: Vec4,
76 },
77}
78
79impl Default for AlbedoComponent {
80 fn default() -> Self {
81 Self::None
82 }
83}
84
85impl AlbedoComponent {
86 pub fn to_value(&self) -> Vec4 {
87 match *self {
88 Self::Value(value) => value,
89 Self::ValueVertex { value, .. } => value,
90 Self::TextureValue { value, .. } => value,
91 _ => Vec4::splat(1.0),
92 }
93 }
94
95 pub fn to_flags(&self) -> MaterialFlags {
96 match *self {
97 Self::None => MaterialFlags::empty(),
98 Self::Value(_) | Self::Texture(_) | Self::TextureValue { .. } => MaterialFlags::ALBEDO_ACTIVE,
99 Self::Vertex { srgb: false }
100 | Self::ValueVertex { srgb: false, .. }
101 | Self::TextureVertex { srgb: false, .. }
102 | Self::TextureVertexValue { srgb: false, .. } => {
103 MaterialFlags::ALBEDO_ACTIVE | MaterialFlags::ALBEDO_BLEND
104 }
105 Self::Vertex { srgb: true }
106 | Self::ValueVertex { srgb: true, .. }
107 | Self::TextureVertex { srgb: true, .. }
108 | Self::TextureVertexValue { srgb: true, .. } => {
109 MaterialFlags::ALBEDO_ACTIVE | MaterialFlags::ALBEDO_BLEND | MaterialFlags::ALBEDO_VERTEX_SRGB
110 }
111 }
112 }
113
114 pub fn is_texture(&self) -> bool {
115 matches!(
116 *self,
117 Self::Texture(..)
118 | Self::TextureVertex { .. }
119 | Self::TextureValue { .. }
120 | Self::TextureVertexValue { .. }
121 )
122 }
123
124 pub fn to_texture(&self) -> Option<&TextureHandle> {
125 match *self {
126 Self::None | Self::Vertex { .. } | Self::Value(_) | Self::ValueVertex { .. } => None,
127 Self::Texture(ref texture)
128 | Self::TextureVertex { ref texture, .. }
129 | Self::TextureValue { ref texture, .. }
130 | Self::TextureVertexValue { ref texture, .. } => Some(texture),
131 }
132 }
133}
134
135#[derive(Debug, Clone)]
138pub enum MaterialComponent<T> {
139 None,
140 Value(T),
141 Texture(TextureHandle),
142 TextureValue { texture: TextureHandle, value: T },
143}
144
145impl<T> Default for MaterialComponent<T> {
146 fn default() -> Self {
147 Self::None
148 }
149}
150
151impl<T: Copy> MaterialComponent<T> {
152 pub fn to_value(&self, default: T) -> T {
153 match *self {
154 Self::Value(value) | Self::TextureValue { value, .. } => value,
155 Self::None | Self::Texture(_) => default,
156 }
157 }
158
159 pub fn is_texture(&self) -> bool {
160 matches!(*self, Self::Texture(..) | Self::TextureValue { .. })
161 }
162
163 pub fn to_texture(&self) -> Option<&TextureHandle> {
164 match *self {
165 Self::None | Self::Value(_) => None,
166 Self::Texture(ref texture) | Self::TextureValue { ref texture, .. } => Some(texture),
167 }
168 }
169}
170
171#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
173pub enum NormalTextureYDirection {
174 Up,
176 Down,
178}
179
180impl Default for NormalTextureYDirection {
181 fn default() -> Self {
182 Self::Up
183 }
184}
185
186#[derive(Debug, Clone)]
188pub enum NormalTexture {
189 None,
191 Tricomponent(TextureHandle, NormalTextureYDirection),
193 Bicomponent(TextureHandle, NormalTextureYDirection),
195 BicomponentSwizzled(TextureHandle, NormalTextureYDirection),
199}
200impl Default for NormalTexture {
201 fn default() -> Self {
202 Self::None
203 }
204}
205
206impl NormalTexture {
207 pub fn to_texture(&self) -> Option<&TextureHandle> {
208 match *self {
209 Self::None => None,
210 Self::Tricomponent(ref texture, _)
211 | Self::Bicomponent(ref texture, _)
212 | Self::BicomponentSwizzled(ref texture, _) => Some(texture),
213 }
214 }
215
216 pub fn to_flags(&self) -> MaterialFlags {
217 let base = match self {
219 Self::None => MaterialFlags::empty(),
220 Self::Tricomponent(..) => MaterialFlags::empty(),
221 Self::Bicomponent(..) => MaterialFlags::BICOMPONENT_NORMAL,
222 Self::BicomponentSwizzled(..) => MaterialFlags::BICOMPONENT_NORMAL | MaterialFlags::SWIZZLED_NORMAL,
223 };
224
225 match self {
227 Self::Tricomponent(_, NormalTextureYDirection::Down)
228 | Self::Bicomponent(_, NormalTextureYDirection::Down)
229 | Self::BicomponentSwizzled(_, NormalTextureYDirection::Down) => base | MaterialFlags::YDOWN_NORMAL,
230 _ => base,
231 }
232 }
233}
234
235#[derive(Debug, Clone)]
238pub enum AoMRTextures {
239 None,
240 Combined {
241 texture: Option<TextureHandle>,
244 },
245 SwizzledSplit {
246 ao_texture: Option<TextureHandle>,
248 mr_texture: Option<TextureHandle>,
250 },
251 Split {
252 ao_texture: Option<TextureHandle>,
254 mr_texture: Option<TextureHandle>,
256 },
257 BWSplit {
258 ao_texture: Option<TextureHandle>,
260 m_texture: Option<TextureHandle>,
262 r_texture: Option<TextureHandle>,
264 },
265}
266
267impl AoMRTextures {
268 pub fn to_roughness_texture(&self) -> Option<&TextureHandle> {
269 match *self {
270 Self::Combined {
271 texture: Some(ref texture),
272 } => Some(texture),
273 Self::SwizzledSplit {
274 mr_texture: Some(ref texture),
275 ..
276 } => Some(texture),
277 Self::Split {
278 mr_texture: Some(ref texture),
279 ..
280 } => Some(texture),
281 Self::BWSplit {
282 r_texture: Some(ref texture),
283 ..
284 } => Some(texture),
285 _ => None,
286 }
287 }
288
289 pub fn to_metallic_texture(&self) -> Option<&TextureHandle> {
290 match *self {
291 Self::Combined { .. } => None,
292 Self::SwizzledSplit { .. } => None,
293 Self::Split { .. } => None,
294 Self::BWSplit {
295 m_texture: Some(ref texture),
296 ..
297 } => Some(texture),
298 _ => None,
299 }
300 }
301
302 pub fn to_ao_texture(&self) -> Option<&TextureHandle> {
303 match *self {
304 Self::Combined { .. } => None,
305 Self::SwizzledSplit {
306 ao_texture: Some(ref texture),
307 ..
308 } => Some(texture),
309 Self::Split {
310 ao_texture: Some(ref texture),
311 ..
312 } => Some(texture),
313 Self::BWSplit {
314 ao_texture: Some(ref texture),
315 ..
316 } => Some(texture),
317 _ => None,
318 }
319 }
320
321 pub fn to_flags(&self) -> MaterialFlags {
322 match self {
323 Self::Combined { .. } => MaterialFlags::AOMR_COMBINED,
324 Self::SwizzledSplit { .. } => MaterialFlags::AOMR_SWIZZLED_SPLIT,
325 Self::Split { .. } => MaterialFlags::AOMR_SPLIT,
326 Self::BWSplit { .. } => MaterialFlags::AOMR_BW_SPLIT,
327 Self::None => MaterialFlags::AOMR_COMBINED,
329 }
330 }
331}
332impl Default for AoMRTextures {
333 fn default() -> Self {
334 Self::None
335 }
336}
337
338#[derive(Debug, Clone)]
340pub enum ClearcoatTextures {
341 GltfCombined {
342 texture: Option<TextureHandle>,
344 },
345 GltfSplit {
346 clearcoat_texture: Option<TextureHandle>,
348 clearcoat_roughness_texture: Option<TextureHandle>,
350 },
351 BWSplit {
352 clearcoat_texture: Option<TextureHandle>,
354 clearcoat_roughness_texture: Option<TextureHandle>,
356 },
357 None,
358}
359
360impl ClearcoatTextures {
361 pub fn to_clearcoat_texture(&self) -> Option<&TextureHandle> {
362 match *self {
363 Self::GltfCombined {
364 texture: Some(ref texture),
365 } => Some(texture),
366 Self::GltfSplit {
367 clearcoat_texture: Some(ref texture),
368 ..
369 } => Some(texture),
370 Self::BWSplit {
371 clearcoat_texture: Some(ref texture),
372 ..
373 } => Some(texture),
374 _ => None,
375 }
376 }
377
378 pub fn to_clearcoat_roughness_texture(&self) -> Option<&TextureHandle> {
379 match *self {
380 Self::GltfCombined { .. } => None,
381 Self::GltfSplit {
382 clearcoat_roughness_texture: Some(ref texture),
383 ..
384 } => Some(texture),
385 Self::BWSplit {
386 clearcoat_roughness_texture: Some(ref texture),
387 ..
388 } => Some(texture),
389 _ => None,
390 }
391 }
392
393 pub fn to_flags(&self) -> MaterialFlags {
394 match self {
395 Self::GltfCombined { .. } => MaterialFlags::CC_GLTF_COMBINED,
396 Self::GltfSplit { .. } => MaterialFlags::CC_GLTF_SPLIT,
397 Self::BWSplit { .. } => MaterialFlags::CC_BW_SPLIT,
398 Self::None => MaterialFlags::CC_GLTF_COMBINED,
400 }
401 }
402}
403impl Default for ClearcoatTextures {
404 fn default() -> Self {
405 Self::None
406 }
407}
408
409#[derive(Debug, Copy, Clone, PartialEq, Eq)]
411pub enum SampleType {
412 Nearest,
413 Linear,
414}
415impl Default for SampleType {
416 fn default() -> Self {
417 Self::Linear
418 }
419}
420
421#[repr(u8)]
423#[derive(Debug, Copy, Clone, PartialEq)]
424pub enum TransparencyType {
425 Opaque,
427 Cutout,
429 Blend,
431}
432impl From<Transparency> for TransparencyType {
433 fn from(t: Transparency) -> Self {
434 match t {
435 Transparency::Opaque => Self::Opaque,
436 Transparency::Cutout { .. } => Self::Cutout,
437 Transparency::Blend => Self::Blend,
438 }
439 }
440}
441impl TransparencyType {
442 pub fn to_debug_str(self) -> &'static str {
443 match self {
444 TransparencyType::Opaque => "opaque",
445 TransparencyType::Cutout => "cutout",
446 TransparencyType::Blend => "blend",
447 }
448 }
449
450 pub fn to_sorting(self) -> Option<Sorting> {
451 match self {
452 Self::Opaque => None,
453 Self::Cutout => None,
454 Self::Blend => Some(Sorting::BackToFront),
455 }
456 }
457}
458
459#[allow(clippy::cmp_owned)] impl PartialEq<Transparency> for TransparencyType {
461 fn eq(&self, other: &Transparency) -> bool {
462 *self == Self::from(*other)
463 }
464}
465
466#[allow(clippy::cmp_owned)]
467impl PartialEq<TransparencyType> for Transparency {
468 fn eq(&self, other: &TransparencyType) -> bool {
469 TransparencyType::from(*self) == *other
470 }
471}
472
473#[derive(Debug, Copy, Clone, PartialEq)]
475pub enum Transparency {
476 Opaque,
478 Cutout { cutout: f32 },
480 Blend,
482}
483impl Default for Transparency {
484 fn default() -> Self {
485 Self::Opaque
486 }
487}
488
489#[derive(Default)]
495pub struct PbrMaterial {
496 pub albedo: AlbedoComponent,
497 pub transparency: Transparency,
498 pub normal: NormalTexture,
499 pub aomr_textures: AoMRTextures,
500 pub ao_factor: Option<f32>,
501 pub metallic_factor: Option<f32>,
502 pub roughness_factor: Option<f32>,
503 pub clearcoat_textures: ClearcoatTextures,
504 pub clearcoat_factor: Option<f32>,
505 pub clearcoat_roughness_factor: Option<f32>,
506 pub emissive: MaterialComponent<Vec3>,
507 pub reflectance: MaterialComponent<f32>,
508 pub anisotropy: MaterialComponent<f32>,
509 pub uv_transform0: Mat3,
510 pub uv_transform1: Mat3,
511 pub unlit: bool,
513 pub sample_type: SampleType,
514}
515
516impl Material for PbrMaterial {
517 const TEXTURE_COUNT: u32 = 10;
518 const DATA_SIZE: u32 = mem::size_of::<ShaderMaterial>() as _;
519
520 fn object_key(&self) -> u64 {
521 TransparencyType::from(self.transparency) as u64
522 }
523
524 fn to_textures<'a>(&'a self, slice: &mut [Option<&'a TextureHandle>]) {
525 slice[0] = self.albedo.to_texture();
526 slice[1] = self.normal.to_texture();
527 slice[2] = self.aomr_textures.to_roughness_texture();
528 slice[3] = self.aomr_textures.to_metallic_texture();
529 slice[4] = self.reflectance.to_texture();
530 slice[5] = self.clearcoat_textures.to_clearcoat_texture();
531 slice[6] = self.clearcoat_textures.to_clearcoat_roughness_texture();
532 slice[7] = self.emissive.to_texture();
533 slice[8] = self.anisotropy.to_texture();
534 slice[9] = self.aomr_textures.to_ao_texture();
535 }
536
537 fn to_data(&self, slice: &mut [u8]) {
538 slice.copy_from_slice(bytemuck::bytes_of(&ShaderMaterial::from_material(self)));
539 }
540}
541
542impl DepthRenderableMaterial for PbrMaterial {
543 const ALPHA_CUTOUT: Option<AlphaCutoutSpec> = Some(AlphaCutoutSpec {
544 index: 0,
545 cutoff_offset: 152,
546 uv_transform_offset: Some(0),
547 });
548}
549
550#[test]
551fn cutout_offset() {
552 assert_eq!(bytemuck::offset_of!(ShaderMaterial, alpha_cutout), 152);
553 assert_eq!(bytemuck::offset_of!(ShaderMaterial, uv_transform0), 0);
554}
555
556#[repr(C)]
557#[derive(Debug, Default, Copy, Clone)]
558struct ShaderMaterial {
559 uv_transform0: Mat3A,
560 uv_transform1: Mat3A,
561
562 albedo: Vec4,
563 emissive: Vec3,
564 roughness: f32,
565 metallic: f32,
566 reflectance: f32,
567 clear_coat: f32,
568 clear_coat_roughness: f32,
569 anisotropy: f32,
570 ambient_occlusion: f32,
571 alpha_cutout: f32,
572
573 material_flags: MaterialFlags,
574}
575
576unsafe impl bytemuck::Zeroable for ShaderMaterial {}
577unsafe impl bytemuck::Pod for ShaderMaterial {}
578
579impl ShaderMaterial {
580 fn from_material(material: &PbrMaterial) -> Self {
581 Self {
582 uv_transform0: material.uv_transform0.into(),
583 uv_transform1: material.uv_transform1.into(),
584 albedo: material.albedo.to_value(),
585 roughness: material.roughness_factor.unwrap_or(0.0),
586 metallic: material.metallic_factor.unwrap_or(0.0),
587 reflectance: material.reflectance.to_value(0.5),
588 clear_coat: material.clearcoat_factor.unwrap_or(0.0),
589 clear_coat_roughness: material.clearcoat_roughness_factor.unwrap_or(0.0),
590 emissive: material.emissive.to_value(Vec3::ZERO),
591 anisotropy: material.anisotropy.to_value(0.0),
592 ambient_occlusion: material.ao_factor.unwrap_or(1.0),
593 alpha_cutout: match material.transparency {
594 Transparency::Cutout { cutout } => cutout,
595 _ => 0.0,
596 },
597 material_flags: {
598 let mut flags = material.albedo.to_flags();
599 flags |= material.normal.to_flags();
600 flags |= material.aomr_textures.to_flags();
601 flags |= material.clearcoat_textures.to_flags();
602 flags.set(MaterialFlags::UNLIT, material.unlit);
603 flags.set(
604 MaterialFlags::NEAREST,
605 match material.sample_type {
606 SampleType::Nearest => true,
607 SampleType::Linear => false,
608 },
609 );
610 flags
611 },
612 }
613 }
614}