1use std::collections::HashMap;
12use std::fmt;
13
14use super::{clampf, lerpf, saturate, Mat4};
15
16#[derive(Debug, Clone)]
22pub struct PbrMaterial {
23 pub name: String,
25 pub albedo: [f32; 3],
27 pub alpha: f32,
29 pub metallic: f32,
31 pub roughness: f32,
33 pub emission: [f32; 3],
35 pub emission_intensity: f32,
37 pub normal_map_index: i32,
39 pub normal_map_strength: f32,
41 pub ior: f32,
43 pub anisotropy: f32,
45 pub anisotropy_rotation: f32,
47 pub clearcoat: f32,
49 pub clearcoat_roughness: f32,
51 pub subsurface: f32,
53 pub subsurface_color: [f32; 3],
55 pub subsurface_radius: f32,
57 pub sheen: f32,
59 pub sheen_tint: f32,
61 pub specular: f32,
63 pub specular_tint: f32,
65 pub transmission: f32,
67 pub absorption_color: [f32; 3],
69 pub absorption_distance: f32,
71 pub albedo_texture: u64,
73 pub roughness_texture: u64,
74 pub metallic_texture: u64,
75 pub normal_texture: u64,
76 pub emission_texture: u64,
77 pub ao_texture: u64,
78 pub material_id: u8,
80 pub is_transparent: bool,
82 pub alpha_test: bool,
84 pub alpha_threshold: f32,
86 pub double_sided: bool,
88 pub uv_scale: [f32; 2],
90 pub uv_offset: [f32; 2],
92 pub receive_shadows: bool,
94 pub cast_shadows: bool,
96 pub priority: i32,
98}
99
100impl PbrMaterial {
101 pub fn new(name: impl Into<String>) -> Self {
103 Self {
104 name: name.into(),
105 albedo: [0.8, 0.8, 0.8],
106 alpha: 1.0,
107 metallic: 0.0,
108 roughness: 0.5,
109 emission: [0.0, 0.0, 0.0],
110 emission_intensity: 1.0,
111 normal_map_index: -1,
112 normal_map_strength: 1.0,
113 ior: 1.5,
114 anisotropy: 0.0,
115 anisotropy_rotation: 0.0,
116 clearcoat: 0.0,
117 clearcoat_roughness: 0.1,
118 subsurface: 0.0,
119 subsurface_color: [1.0, 0.2, 0.1],
120 subsurface_radius: 1.0,
121 sheen: 0.0,
122 sheen_tint: 0.5,
123 specular: 0.5,
124 specular_tint: 0.0,
125 transmission: 0.0,
126 absorption_color: [1.0, 1.0, 1.0],
127 absorption_distance: 1.0,
128 albedo_texture: 0,
129 roughness_texture: 0,
130 metallic_texture: 0,
131 normal_texture: 0,
132 emission_texture: 0,
133 ao_texture: 0,
134 material_id: 0,
135 is_transparent: false,
136 alpha_test: false,
137 alpha_threshold: 0.5,
138 double_sided: false,
139 uv_scale: [1.0, 1.0],
140 uv_offset: [0.0, 0.0],
141 receive_shadows: true,
142 cast_shadows: true,
143 priority: 0,
144 }
145 }
146
147 pub fn with_albedo(mut self, r: f32, g: f32, b: f32) -> Self {
149 self.albedo = [r, g, b];
150 self
151 }
152
153 pub fn with_metallic(mut self, m: f32) -> Self {
154 self.metallic = clampf(m, 0.0, 1.0);
155 self
156 }
157
158 pub fn with_roughness(mut self, r: f32) -> Self {
159 self.roughness = clampf(r, 0.0, 1.0);
160 self
161 }
162
163 pub fn with_emission(mut self, r: f32, g: f32, b: f32) -> Self {
164 self.emission = [r, g, b];
165 self
166 }
167
168 pub fn with_emission_intensity(mut self, i: f32) -> Self {
169 self.emission_intensity = i.max(0.0);
170 self
171 }
172
173 pub fn with_ior(mut self, ior: f32) -> Self {
174 self.ior = ior.max(1.0);
175 self
176 }
177
178 pub fn with_anisotropy(mut self, a: f32) -> Self {
179 self.anisotropy = clampf(a, -1.0, 1.0);
180 self
181 }
182
183 pub fn with_clearcoat(mut self, strength: f32, roughness: f32) -> Self {
184 self.clearcoat = clampf(strength, 0.0, 1.0);
185 self.clearcoat_roughness = clampf(roughness, 0.0, 1.0);
186 self
187 }
188
189 pub fn with_subsurface(mut self, strength: f32, color: [f32; 3]) -> Self {
190 self.subsurface = clampf(strength, 0.0, 1.0);
191 self.subsurface_color = color;
192 self
193 }
194
195 pub fn with_sheen(mut self, strength: f32, tint: f32) -> Self {
196 self.sheen = clampf(strength, 0.0, 1.0);
197 self.sheen_tint = clampf(tint, 0.0, 1.0);
198 self
199 }
200
201 pub fn with_transmission(mut self, t: f32) -> Self {
202 self.transmission = clampf(t, 0.0, 1.0);
203 if t > 0.0 {
204 self.is_transparent = true;
205 }
206 self
207 }
208
209 pub fn with_alpha(mut self, a: f32) -> Self {
210 self.alpha = clampf(a, 0.0, 1.0);
211 if a < 1.0 {
212 self.is_transparent = true;
213 }
214 self
215 }
216
217 pub fn with_alpha_test(mut self, threshold: f32) -> Self {
218 self.alpha_test = true;
219 self.alpha_threshold = clampf(threshold, 0.0, 1.0);
220 self
221 }
222
223 pub fn with_material_id(mut self, id: u8) -> Self {
224 self.material_id = id;
225 self
226 }
227
228 pub fn with_uv_transform(mut self, scale: [f32; 2], offset: [f32; 2]) -> Self {
229 self.uv_scale = scale;
230 self.uv_offset = offset;
231 self
232 }
233
234 pub fn with_double_sided(mut self, ds: bool) -> Self {
235 self.double_sided = ds;
236 self
237 }
238
239 pub fn f0(&self) -> [f32; 3] {
241 let dielectric_f0 = ((self.ior - 1.0) / (self.ior + 1.0)).powi(2);
242 [
243 lerpf(dielectric_f0, self.albedo[0], self.metallic),
244 lerpf(dielectric_f0, self.albedo[1], self.metallic),
245 lerpf(dielectric_f0, self.albedo[2], self.metallic),
246 ]
247 }
248
249 pub fn diffuse_color(&self) -> [f32; 3] {
251 let factor = 1.0 - self.metallic;
252 [
253 self.albedo[0] * factor,
254 self.albedo[1] * factor,
255 self.albedo[2] * factor,
256 ]
257 }
258
259 pub fn total_emission(&self) -> [f32; 3] {
261 [
262 self.emission[0] * self.emission_intensity,
263 self.emission[1] * self.emission_intensity,
264 self.emission[2] * self.emission_intensity,
265 ]
266 }
267
268 pub fn sort_key(&self) -> MaterialSortKey {
270 MaterialSortKey::from_material(self)
271 }
272
273 pub fn has_textures(&self) -> bool {
275 self.albedo_texture != 0
276 || self.roughness_texture != 0
277 || self.metallic_texture != 0
278 || self.normal_texture != 0
279 || self.emission_texture != 0
280 || self.ao_texture != 0
281 }
282
283 pub fn albedo_luminance(&self) -> f32 {
285 0.2126 * self.albedo[0] + 0.7152 * self.albedo[1] + 0.0722 * self.albedo[2]
286 }
287
288 pub fn glsl_uniforms() -> &'static str {
290 r#"uniform vec3 u_albedo;
291uniform float u_alpha;
292uniform float u_metallic;
293uniform float u_roughness;
294uniform vec3 u_emission;
295uniform float u_emission_intensity;
296uniform float u_ior;
297uniform float u_anisotropy;
298uniform float u_clearcoat;
299uniform float u_clearcoat_roughness;
300uniform float u_subsurface;
301uniform vec3 u_subsurface_color;
302uniform float u_sheen;
303uniform float u_sheen_tint;
304uniform float u_specular;
305uniform float u_specular_tint;
306uniform float u_transmission;
307uniform float u_normal_map_strength;
308uniform vec2 u_uv_scale;
309uniform vec2 u_uv_offset;
310uniform float u_material_id;
311"#
312 }
313}
314
315impl Default for PbrMaterial {
316 fn default() -> Self {
317 Self::new("default")
318 }
319}
320
321impl fmt::Display for PbrMaterial {
322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 write!(
324 f,
325 "PbrMaterial '{}' (albedo=[{:.2},{:.2},{:.2}], metallic={:.2}, roughness={:.2})",
326 self.name, self.albedo[0], self.albedo[1], self.albedo[2],
327 self.metallic, self.roughness
328 )
329 }
330}
331
332#[derive(Debug, Clone, Default)]
339pub struct MaterialInstance {
340 pub base_material_index: u32,
342 pub albedo_override: Option<[f32; 3]>,
344 pub alpha_override: Option<f32>,
346 pub metallic_override: Option<f32>,
348 pub roughness_override: Option<f32>,
350 pub emission_override: Option<[f32; 3]>,
352 pub emission_intensity_override: Option<f32>,
354 pub uv_scale_override: Option<[f32; 2]>,
356 pub uv_offset_override: Option<[f32; 2]>,
358 pub tint: Option<[f32; 4]>,
360 pub priority_override: Option<i32>,
362 pub custom_floats: HashMap<String, f32>,
364 pub custom_vec3s: HashMap<String, [f32; 3]>,
366}
367
368impl MaterialInstance {
369 pub fn new(base_material_index: u32) -> Self {
370 Self {
371 base_material_index,
372 ..Default::default()
373 }
374 }
375
376 pub fn with_albedo(mut self, albedo: [f32; 3]) -> Self {
377 self.albedo_override = Some(albedo);
378 self
379 }
380
381 pub fn with_alpha(mut self, alpha: f32) -> Self {
382 self.alpha_override = Some(alpha);
383 self
384 }
385
386 pub fn with_metallic(mut self, m: f32) -> Self {
387 self.metallic_override = Some(m);
388 self
389 }
390
391 pub fn with_roughness(mut self, r: f32) -> Self {
392 self.roughness_override = Some(r);
393 self
394 }
395
396 pub fn with_emission(mut self, e: [f32; 3]) -> Self {
397 self.emission_override = Some(e);
398 self
399 }
400
401 pub fn with_tint(mut self, tint: [f32; 4]) -> Self {
402 self.tint = Some(tint);
403 self
404 }
405
406 pub fn set_custom_float(&mut self, name: impl Into<String>, value: f32) {
407 self.custom_floats.insert(name.into(), value);
408 }
409
410 pub fn set_custom_vec3(&mut self, name: impl Into<String>, value: [f32; 3]) {
411 self.custom_vec3s.insert(name.into(), value);
412 }
413
414 pub fn resolve(&self, base: &PbrMaterial) -> PbrMaterial {
416 let mut mat = base.clone();
417
418 if let Some(a) = self.albedo_override {
419 mat.albedo = a;
420 }
421 if let Some(a) = self.alpha_override {
422 mat.alpha = a;
423 if a < 1.0 {
424 mat.is_transparent = true;
425 }
426 }
427 if let Some(m) = self.metallic_override {
428 mat.metallic = m;
429 }
430 if let Some(r) = self.roughness_override {
431 mat.roughness = r;
432 }
433 if let Some(e) = self.emission_override {
434 mat.emission = e;
435 }
436 if let Some(ei) = self.emission_intensity_override {
437 mat.emission_intensity = ei;
438 }
439 if let Some(uv) = self.uv_scale_override {
440 mat.uv_scale = uv;
441 }
442 if let Some(uvo) = self.uv_offset_override {
443 mat.uv_offset = uvo;
444 }
445 if let Some(p) = self.priority_override {
446 mat.priority = p;
447 }
448
449 if let Some(tint) = self.tint {
451 mat.albedo[0] *= tint[0];
452 mat.albedo[1] *= tint[1];
453 mat.albedo[2] *= tint[2];
454 mat.alpha *= tint[3];
455 }
456
457 mat
458 }
459
460 pub fn override_count(&self) -> u32 {
462 let mut count = 0u32;
463 if self.albedo_override.is_some() { count += 1; }
464 if self.alpha_override.is_some() { count += 1; }
465 if self.metallic_override.is_some() { count += 1; }
466 if self.roughness_override.is_some() { count += 1; }
467 if self.emission_override.is_some() { count += 1; }
468 if self.emission_intensity_override.is_some() { count += 1; }
469 if self.uv_scale_override.is_some() { count += 1; }
470 if self.uv_offset_override.is_some() { count += 1; }
471 if self.tint.is_some() { count += 1; }
472 if self.priority_override.is_some() { count += 1; }
473 count += self.custom_floats.len() as u32;
474 count += self.custom_vec3s.len() as u32;
475 count
476 }
477}
478
479#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
493pub struct MaterialSortKey(pub u64);
494
495impl MaterialSortKey {
496 pub fn new(key: u64) -> Self {
497 Self(key)
498 }
499
500 pub fn from_material(mat: &PbrMaterial) -> Self {
502 let mut key: u64 = 0;
503
504 if mat.is_transparent {
506 key |= 1u64 << 63;
507 }
508
509 key |= (mat.material_id as u64) << 48;
511
512 key |= (mat.albedo_texture & 0xFFFF) << 16;
514
515 key |= mat.normal_texture & 0xFFFF;
517
518 Self(key)
519 }
520
521 pub fn is_transparent(&self) -> bool {
523 (self.0 >> 63) & 1 != 0
524 }
525
526 pub fn material_id(&self) -> u8 {
528 ((self.0 >> 48) & 0xFF) as u8
529 }
530}
531
532impl Default for MaterialSortKey {
533 fn default() -> Self {
534 Self(0)
535 }
536}
537
538#[derive(Debug, Clone, Copy)]
545#[repr(C)]
546pub struct InstanceData {
547 pub model_matrix: [[f32; 4]; 4],
549 pub material_params: [[f32; 4]; 4],
555}
556
557impl InstanceData {
558 pub fn new(transform: &Mat4, material: &PbrMaterial) -> Self {
560 Self {
561 model_matrix: transform.cols,
562 material_params: [
563 [material.albedo[0], material.albedo[1], material.albedo[2], material.alpha],
564 [material.metallic, material.roughness, material.emission_intensity,
565 material.material_id as f32],
566 [material.emission[0], material.emission[1], material.emission[2], 0.0],
567 [material.uv_scale[0], material.uv_scale[1],
568 material.uv_offset[0], material.uv_offset[1]],
569 ],
570 }
571 }
572
573 pub fn from_transform(transform: &Mat4) -> Self {
575 Self {
576 model_matrix: transform.cols,
577 material_params: [
578 [0.8, 0.8, 0.8, 1.0],
579 [0.0, 0.5, 0.0, 0.0],
580 [0.0, 0.0, 0.0, 0.0],
581 [1.0, 1.0, 0.0, 0.0],
582 ],
583 }
584 }
585
586 pub fn set_albedo(&mut self, r: f32, g: f32, b: f32) {
588 self.material_params[0][0] = r;
589 self.material_params[0][1] = g;
590 self.material_params[0][2] = b;
591 }
592
593 pub fn set_alpha(&mut self, a: f32) {
595 self.material_params[0][3] = a;
596 }
597
598 pub fn set_metallic_roughness(&mut self, metallic: f32, roughness: f32) {
600 self.material_params[1][0] = metallic;
601 self.material_params[1][1] = roughness;
602 }
603
604 pub fn set_material_id(&mut self, id: u8) {
606 self.material_params[1][3] = id as f32;
607 }
608
609 pub fn set_emission(&mut self, r: f32, g: f32, b: f32, intensity: f32) {
611 self.material_params[2][0] = r;
612 self.material_params[2][1] = g;
613 self.material_params[2][2] = b;
614 self.material_params[1][2] = intensity;
615 }
616
617 pub fn stride() -> usize {
619 std::mem::size_of::<Self>()
620 }
621
622 pub fn glsl_instance_attributes() -> &'static str {
624 r#"// Instance attributes (occupies locations 4-11)
625layout(location = 4) in vec4 i_model_col0;
626layout(location = 5) in vec4 i_model_col1;
627layout(location = 6) in vec4 i_model_col2;
628layout(location = 7) in vec4 i_model_col3;
629layout(location = 8) in vec4 i_mat_params0; // albedo.rgb, alpha
630layout(location = 9) in vec4 i_mat_params1; // metallic, roughness, emission_intensity, matid
631layout(location = 10) in vec4 i_mat_params2; // emission.rgb, unused
632layout(location = 11) in vec4 i_mat_params3; // uv_scale, uv_offset
633"#
634 }
635}
636
637impl Default for InstanceData {
638 fn default() -> Self {
639 Self::from_transform(&Mat4::IDENTITY)
640 }
641}
642
643#[derive(Debug)]
649pub struct InstanceBuffer {
650 pub data: Vec<InstanceData>,
652 pub gpu_handle: u64,
654 pub capacity: usize,
656 pub dirty: bool,
658 pub generation: u32,
660}
661
662impl InstanceBuffer {
663 pub fn new(capacity: usize) -> Self {
664 Self {
665 data: Vec::with_capacity(capacity),
666 gpu_handle: 0,
667 capacity,
668 dirty: true,
669 generation: 0,
670 }
671 }
672
673 pub fn push(&mut self, instance: InstanceData) {
675 self.data.push(instance);
676 self.dirty = true;
677 }
678
679 pub fn clear(&mut self) {
681 self.data.clear();
682 self.dirty = true;
683 }
684
685 pub fn len(&self) -> usize {
687 self.data.len()
688 }
689
690 pub fn is_empty(&self) -> bool {
692 self.data.is_empty()
693 }
694
695 pub fn upload(&mut self) {
697 if !self.dirty {
698 return;
699 }
700 self.dirty = false;
702 self.generation += 1;
703 }
704
705 pub fn memory_bytes(&self) -> usize {
707 self.data.len() * InstanceData::stride()
708 }
709
710 pub fn sort_by_material(&mut self) {
712 self.data.sort_by(|a, b| {
713 let a_id = a.material_params[1][3] as u32;
714 let b_id = b.material_params[1][3] as u32;
715 a_id.cmp(&b_id)
716 });
717 self.dirty = true;
718 }
719}
720
721impl Default for InstanceBuffer {
722 fn default() -> Self {
723 Self::new(1024)
724 }
725}
726
727#[derive(Debug)]
733pub struct MaterialLibrary {
734 pub materials: Vec<PbrMaterial>,
736 pub name_map: HashMap<String, usize>,
738 next_id: u8,
740}
741
742impl MaterialLibrary {
743 pub fn new() -> Self {
744 Self {
745 materials: Vec::new(),
746 name_map: HashMap::new(),
747 next_id: 0,
748 }
749 }
750
751 pub fn with_presets() -> Self {
753 let mut lib = Self::new();
754 let presets = MaterialPresets::all();
755 for (name, mat) in presets {
756 lib.add(name, mat);
757 }
758 lib
759 }
760
761 pub fn add(&mut self, name: impl Into<String>, mut material: PbrMaterial) -> usize {
763 let name = name.into();
764 material.material_id = self.next_id;
765 self.next_id = self.next_id.wrapping_add(1);
766 material.name = name.clone();
767
768 let index = self.materials.len();
769 self.materials.push(material);
770 self.name_map.insert(name, index);
771 index
772 }
773
774 pub fn get(&self, name: &str) -> Option<&PbrMaterial> {
776 self.name_map.get(name).map(|&i| &self.materials[i])
777 }
778
779 pub fn get_mut(&mut self, name: &str) -> Option<&mut PbrMaterial> {
781 if let Some(&i) = self.name_map.get(name) {
782 Some(&mut self.materials[i])
783 } else {
784 None
785 }
786 }
787
788 pub fn get_by_index(&self, index: usize) -> Option<&PbrMaterial> {
790 self.materials.get(index)
791 }
792
793 pub fn get_by_index_mut(&mut self, index: usize) -> Option<&mut PbrMaterial> {
795 self.materials.get_mut(index)
796 }
797
798 pub fn index_of(&self, name: &str) -> Option<usize> {
800 self.name_map.get(name).copied()
801 }
802
803 pub fn remove(&mut self, name: &str) -> Option<PbrMaterial> {
805 if let Some(index) = self.name_map.remove(name) {
806 Some(self.materials.remove(index))
809 } else {
810 None
811 }
812 }
813
814 pub fn len(&self) -> usize {
816 self.materials.len()
817 }
818
819 pub fn is_empty(&self) -> bool {
821 self.materials.is_empty()
822 }
823
824 pub fn names(&self) -> Vec<&str> {
826 self.name_map.keys().map(|s| s.as_str()).collect()
827 }
828
829 pub fn resolve_instance(&self, instance: &MaterialInstance) -> Option<PbrMaterial> {
831 self.materials
832 .get(instance.base_material_index as usize)
833 .map(|base| instance.resolve(base))
834 }
835
836 pub fn describe(&self) -> String {
838 let mut s = format!("Material Library ({} materials):\n", self.materials.len());
839 for (i, mat) in self.materials.iter().enumerate() {
840 s.push_str(&format!(
841 " [{}] {} (id={}, metallic={:.2}, roughness={:.2})\n",
842 i, mat.name, mat.material_id, mat.metallic, mat.roughness
843 ));
844 }
845 s
846 }
847}
848
849impl Default for MaterialLibrary {
850 fn default() -> Self {
851 Self::new()
852 }
853}
854
855#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
861pub enum MaterialPreset {
862 Metal,
863 Plastic,
864 Glass,
865 Wood,
866 Stone,
867 Skin,
868 Fabric,
869 Water,
870 Crystal,
871 Lava,
872 Gold,
873 Silver,
874 Copper,
875 Iron,
876 Rubber,
877 Concrete,
878 Marble,
879 Ice,
880 Leather,
881 Ceramic,
882}
883
884impl MaterialPreset {
885 pub fn material(&self) -> PbrMaterial {
887 match self {
888 Self::Metal => PbrMaterial::new("metal")
889 .with_albedo(0.7, 0.7, 0.72)
890 .with_metallic(1.0)
891 .with_roughness(0.3),
892
893 Self::Plastic => PbrMaterial::new("plastic")
894 .with_albedo(0.8, 0.2, 0.2)
895 .with_metallic(0.0)
896 .with_roughness(0.4),
897
898 Self::Glass => PbrMaterial::new("glass")
899 .with_albedo(0.95, 0.95, 0.97)
900 .with_metallic(0.0)
901 .with_roughness(0.05)
902 .with_ior(1.52)
903 .with_transmission(0.9)
904 .with_alpha(0.3),
905
906 Self::Wood => PbrMaterial::new("wood")
907 .with_albedo(0.55, 0.35, 0.18)
908 .with_metallic(0.0)
909 .with_roughness(0.7),
910
911 Self::Stone => PbrMaterial::new("stone")
912 .with_albedo(0.5, 0.48, 0.45)
913 .with_metallic(0.0)
914 .with_roughness(0.85),
915
916 Self::Skin => PbrMaterial::new("skin")
917 .with_albedo(0.85, 0.65, 0.52)
918 .with_metallic(0.0)
919 .with_roughness(0.6)
920 .with_subsurface(0.5, [1.0, 0.4, 0.25]),
921
922 Self::Fabric => PbrMaterial::new("fabric")
923 .with_albedo(0.3, 0.3, 0.6)
924 .with_metallic(0.0)
925 .with_roughness(0.9)
926 .with_sheen(0.5, 0.5),
927
928 Self::Water => PbrMaterial::new("water")
929 .with_albedo(0.02, 0.05, 0.08)
930 .with_metallic(0.0)
931 .with_roughness(0.05)
932 .with_ior(1.33)
933 .with_transmission(0.95)
934 .with_alpha(0.6),
935
936 Self::Crystal => PbrMaterial::new("crystal")
937 .with_albedo(0.8, 0.85, 0.95)
938 .with_metallic(0.0)
939 .with_roughness(0.02)
940 .with_ior(2.42) .with_transmission(0.8)
942 .with_clearcoat(1.0, 0.01),
943
944 Self::Lava => PbrMaterial::new("lava")
945 .with_albedo(0.1, 0.02, 0.01)
946 .with_metallic(0.0)
947 .with_roughness(0.95)
948 .with_emission(3.0, 0.8, 0.1)
949 .with_emission_intensity(5.0)
950 .with_subsurface(0.3, [1.0, 0.3, 0.05]),
951
952 Self::Gold => PbrMaterial::new("gold")
953 .with_albedo(1.0, 0.766, 0.336)
954 .with_metallic(1.0)
955 .with_roughness(0.2),
956
957 Self::Silver => PbrMaterial::new("silver")
958 .with_albedo(0.972, 0.960, 0.915)
959 .with_metallic(1.0)
960 .with_roughness(0.15),
961
962 Self::Copper => PbrMaterial::new("copper")
963 .with_albedo(0.955, 0.638, 0.538)
964 .with_metallic(1.0)
965 .with_roughness(0.25),
966
967 Self::Iron => PbrMaterial::new("iron")
968 .with_albedo(0.56, 0.57, 0.58)
969 .with_metallic(1.0)
970 .with_roughness(0.4),
971
972 Self::Rubber => PbrMaterial::new("rubber")
973 .with_albedo(0.15, 0.15, 0.15)
974 .with_metallic(0.0)
975 .with_roughness(0.95),
976
977 Self::Concrete => PbrMaterial::new("concrete")
978 .with_albedo(0.55, 0.55, 0.52)
979 .with_metallic(0.0)
980 .with_roughness(0.9),
981
982 Self::Marble => PbrMaterial::new("marble")
983 .with_albedo(0.9, 0.88, 0.85)
984 .with_metallic(0.0)
985 .with_roughness(0.3)
986 .with_subsurface(0.2, [1.0, 0.95, 0.9]),
987
988 Self::Ice => PbrMaterial::new("ice")
989 .with_albedo(0.85, 0.92, 0.97)
990 .with_metallic(0.0)
991 .with_roughness(0.1)
992 .with_ior(1.31)
993 .with_transmission(0.6)
994 .with_subsurface(0.3, [0.6, 0.8, 1.0]),
995
996 Self::Leather => PbrMaterial::new("leather")
997 .with_albedo(0.35, 0.22, 0.12)
998 .with_metallic(0.0)
999 .with_roughness(0.75),
1000
1001 Self::Ceramic => PbrMaterial::new("ceramic")
1002 .with_albedo(0.9, 0.9, 0.88)
1003 .with_metallic(0.0)
1004 .with_roughness(0.25)
1005 .with_clearcoat(0.8, 0.05),
1006 }
1007 }
1008}
1009
1010impl fmt::Display for MaterialPreset {
1011 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1012 let name = match self {
1013 Self::Metal => "Metal",
1014 Self::Plastic => "Plastic",
1015 Self::Glass => "Glass",
1016 Self::Wood => "Wood",
1017 Self::Stone => "Stone",
1018 Self::Skin => "Skin",
1019 Self::Fabric => "Fabric",
1020 Self::Water => "Water",
1021 Self::Crystal => "Crystal",
1022 Self::Lava => "Lava",
1023 Self::Gold => "Gold",
1024 Self::Silver => "Silver",
1025 Self::Copper => "Copper",
1026 Self::Iron => "Iron",
1027 Self::Rubber => "Rubber",
1028 Self::Concrete => "Concrete",
1029 Self::Marble => "Marble",
1030 Self::Ice => "Ice",
1031 Self::Leather => "Leather",
1032 Self::Ceramic => "Ceramic",
1033 };
1034 write!(f, "{}", name)
1035 }
1036}
1037
1038pub struct MaterialPresets;
1040
1041impl MaterialPresets {
1042 pub fn all() -> Vec<(String, PbrMaterial)> {
1044 let presets = [
1045 MaterialPreset::Metal,
1046 MaterialPreset::Plastic,
1047 MaterialPreset::Glass,
1048 MaterialPreset::Wood,
1049 MaterialPreset::Stone,
1050 MaterialPreset::Skin,
1051 MaterialPreset::Fabric,
1052 MaterialPreset::Water,
1053 MaterialPreset::Crystal,
1054 MaterialPreset::Lava,
1055 MaterialPreset::Gold,
1056 MaterialPreset::Silver,
1057 MaterialPreset::Copper,
1058 MaterialPreset::Iron,
1059 MaterialPreset::Rubber,
1060 MaterialPreset::Concrete,
1061 MaterialPreset::Marble,
1062 MaterialPreset::Ice,
1063 MaterialPreset::Leather,
1064 MaterialPreset::Ceramic,
1065 ];
1066 presets.iter().map(|p| (p.to_string().to_lowercase(), p.material())).collect()
1067 }
1068
1069 pub fn get(preset: MaterialPreset) -> PbrMaterial {
1071 preset.material()
1072 }
1073
1074 pub fn metal() -> PbrMaterial { MaterialPreset::Metal.material() }
1076 pub fn plastic() -> PbrMaterial { MaterialPreset::Plastic.material() }
1078 pub fn glass() -> PbrMaterial { MaterialPreset::Glass.material() }
1080 pub fn wood() -> PbrMaterial { MaterialPreset::Wood.material() }
1082 pub fn stone() -> PbrMaterial { MaterialPreset::Stone.material() }
1084 pub fn skin() -> PbrMaterial { MaterialPreset::Skin.material() }
1086 pub fn fabric() -> PbrMaterial { MaterialPreset::Fabric.material() }
1088 pub fn water() -> PbrMaterial { MaterialPreset::Water.material() }
1090 pub fn crystal() -> PbrMaterial { MaterialPreset::Crystal.material() }
1092 pub fn lava() -> PbrMaterial { MaterialPreset::Lava.material() }
1094
1095 pub fn lerp(a: &PbrMaterial, b: &PbrMaterial, t: f32) -> PbrMaterial {
1097 let t = saturate(t);
1098 let mut result = a.clone();
1099 result.name = format!("{}_{}_blend", a.name, b.name);
1100 result.albedo = [
1101 lerpf(a.albedo[0], b.albedo[0], t),
1102 lerpf(a.albedo[1], b.albedo[1], t),
1103 lerpf(a.albedo[2], b.albedo[2], t),
1104 ];
1105 result.alpha = lerpf(a.alpha, b.alpha, t);
1106 result.metallic = lerpf(a.metallic, b.metallic, t);
1107 result.roughness = lerpf(a.roughness, b.roughness, t);
1108 result.emission = [
1109 lerpf(a.emission[0], b.emission[0], t),
1110 lerpf(a.emission[1], b.emission[1], t),
1111 lerpf(a.emission[2], b.emission[2], t),
1112 ];
1113 result.emission_intensity = lerpf(a.emission_intensity, b.emission_intensity, t);
1114 result.ior = lerpf(a.ior, b.ior, t);
1115 result.anisotropy = lerpf(a.anisotropy, b.anisotropy, t);
1116 result.clearcoat = lerpf(a.clearcoat, b.clearcoat, t);
1117 result.clearcoat_roughness = lerpf(a.clearcoat_roughness, b.clearcoat_roughness, t);
1118 result.subsurface = lerpf(a.subsurface, b.subsurface, t);
1119 result.subsurface_color = [
1120 lerpf(a.subsurface_color[0], b.subsurface_color[0], t),
1121 lerpf(a.subsurface_color[1], b.subsurface_color[1], t),
1122 lerpf(a.subsurface_color[2], b.subsurface_color[2], t),
1123 ];
1124 result.sheen = lerpf(a.sheen, b.sheen, t);
1125 result.transmission = lerpf(a.transmission, b.transmission, t);
1126 result
1127 }
1128
1129 pub fn randomized(base: MaterialPreset, seed: u32) -> PbrMaterial {
1131 let mut mat = base.material();
1132 let hash = |s: u32, channel: u32| -> f32 {
1134 let x = s.wrapping_mul(2654435761).wrapping_add(channel.wrapping_mul(40503));
1135 let bits = (x >> 9) | 0x3F800000;
1136 let f = f32::from_bits(bits) - 1.0;
1137 f * 0.2 - 0.1 };
1139
1140 mat.albedo[0] = clampf(mat.albedo[0] + hash(seed, 0), 0.0, 1.0);
1141 mat.albedo[1] = clampf(mat.albedo[1] + hash(seed, 1), 0.0, 1.0);
1142 mat.albedo[2] = clampf(mat.albedo[2] + hash(seed, 2), 0.0, 1.0);
1143 mat.roughness = clampf(mat.roughness + hash(seed, 3) * 0.5, 0.01, 1.0);
1144 mat.name = format!("{}_var{}", mat.name, seed);
1145 mat
1146 }
1147}
1148
1149#[cfg(test)]
1154mod tests {
1155 use super::*;
1156
1157 #[test]
1158 fn test_material_defaults() {
1159 let mat = PbrMaterial::default();
1160 assert_eq!(mat.metallic, 0.0);
1161 assert_eq!(mat.roughness, 0.5);
1162 assert_eq!(mat.alpha, 1.0);
1163 assert!(!mat.is_transparent);
1164 }
1165
1166 #[test]
1167 fn test_material_builder() {
1168 let mat = PbrMaterial::new("test")
1169 .with_albedo(1.0, 0.0, 0.0)
1170 .with_metallic(0.8)
1171 .with_roughness(0.2)
1172 .with_emission(0.5, 0.5, 0.0)
1173 .with_clearcoat(0.5, 0.1);
1174
1175 assert_eq!(mat.albedo, [1.0, 0.0, 0.0]);
1176 assert_eq!(mat.metallic, 0.8);
1177 assert_eq!(mat.roughness, 0.2);
1178 assert_eq!(mat.clearcoat, 0.5);
1179 }
1180
1181 #[test]
1182 fn test_material_f0() {
1183 let dielectric = PbrMaterial::new("test").with_metallic(0.0).with_ior(1.5);
1184 let f0 = dielectric.f0();
1185 assert!((f0[0] - 0.04).abs() < 0.01);
1186
1187 let metal = PbrMaterial::new("test")
1188 .with_metallic(1.0)
1189 .with_albedo(1.0, 0.766, 0.336); let f0 = metal.f0();
1191 assert!((f0[0] - 1.0).abs() < 0.01);
1192 }
1193
1194 #[test]
1195 fn test_material_instance_resolve() {
1196 let base = PbrMaterial::new("base")
1197 .with_albedo(0.5, 0.5, 0.5)
1198 .with_roughness(0.5);
1199
1200 let instance = MaterialInstance::new(0)
1201 .with_albedo([1.0, 0.0, 0.0])
1202 .with_roughness(0.2);
1203
1204 let resolved = instance.resolve(&base);
1205 assert_eq!(resolved.albedo, [1.0, 0.0, 0.0]);
1206 assert_eq!(resolved.roughness, 0.2);
1207 assert_eq!(resolved.metallic, 0.0); }
1209
1210 #[test]
1211 fn test_material_instance_tint() {
1212 let base = PbrMaterial::new("base").with_albedo(1.0, 1.0, 1.0);
1213 let instance = MaterialInstance::new(0)
1214 .with_tint([0.5, 0.0, 1.0, 0.8]);
1215 let resolved = instance.resolve(&base);
1216 assert!((resolved.albedo[0] - 0.5).abs() < 0.001);
1217 assert!((resolved.albedo[1] - 0.0).abs() < 0.001);
1218 assert!((resolved.albedo[2] - 1.0).abs() < 0.001);
1219 assert!((resolved.alpha - 0.8).abs() < 0.001);
1220 }
1221
1222 #[test]
1223 fn test_material_library() {
1224 let mut lib = MaterialLibrary::new();
1225 let idx = lib.add("iron", MaterialPresets::metal());
1226 assert_eq!(idx, 0);
1227 assert_eq!(lib.len(), 1);
1228
1229 let mat = lib.get("iron").unwrap();
1230 assert_eq!(mat.metallic, 1.0);
1231
1232 assert!(lib.get("nonexistent").is_none());
1233 }
1234
1235 #[test]
1236 fn test_material_library_presets() {
1237 let lib = MaterialLibrary::with_presets();
1238 assert!(lib.len() >= 10);
1239 assert!(lib.get("gold").is_some());
1240 assert!(lib.get("glass").is_some());
1241 assert!(lib.get("lava").is_some());
1242 }
1243
1244 #[test]
1245 fn test_sort_key() {
1246 let opaque = PbrMaterial::new("opaque").with_metallic(0.0);
1247 let transparent = PbrMaterial::new("transparent").with_alpha(0.5);
1248
1249 let key_opaque = opaque.sort_key();
1250 let key_transparent = transparent.sort_key();
1251
1252 assert!(!key_opaque.is_transparent());
1253 assert!(key_transparent.is_transparent());
1254 assert!(key_opaque < key_transparent);
1255 }
1256
1257 #[test]
1258 fn test_instance_data() {
1259 let mat = MaterialPreset::Gold.material();
1260 let transform = Mat4::IDENTITY;
1261 let instance = InstanceData::new(&transform, &mat);
1262
1263 assert!((instance.material_params[0][0] - 1.0).abs() < 0.01); assert!((instance.material_params[1][0] - 1.0).abs() < 0.01); }
1266
1267 #[test]
1268 fn test_instance_buffer() {
1269 let mut buf = InstanceBuffer::new(64);
1270 assert!(buf.is_empty());
1271 buf.push(InstanceData::default());
1272 buf.push(InstanceData::default());
1273 assert_eq!(buf.len(), 2);
1274 assert!(buf.dirty);
1275 buf.upload();
1276 assert!(!buf.dirty);
1277 }
1278
1279 #[test]
1280 fn test_material_lerp() {
1281 let a = MaterialPresets::metal();
1282 let b = MaterialPresets::plastic();
1283 let mid = MaterialPresets::lerp(&a, &b, 0.5);
1284 assert!((mid.metallic - 0.5).abs() < 0.01);
1285 assert!((mid.roughness - 0.35).abs() < 0.01); }
1287
1288 #[test]
1289 fn test_presets_all() {
1290 let all = MaterialPresets::all();
1291 assert!(all.len() >= 10);
1292 for (name, mat) in &all {
1293 assert!(!name.is_empty());
1294 assert!(mat.roughness >= 0.0 && mat.roughness <= 1.0);
1295 assert!(mat.metallic >= 0.0 && mat.metallic <= 1.0);
1296 }
1297 }
1298
1299 #[test]
1300 fn test_randomized_preset() {
1301 let m1 = MaterialPresets::randomized(MaterialPreset::Metal, 42);
1302 let m2 = MaterialPresets::randomized(MaterialPreset::Metal, 43);
1303 assert_ne!(m1.albedo[0], m2.albedo[0]);
1305 }
1306}