use std::{collections::hash_map::DefaultHasher, hash::{Hash, Hasher}}; use crate::{Texture, ResHandle, util, loader::model::GltfLoadContext}; /// PBR metallic roughness #[derive(Clone, Debug, Default)] pub struct PbrRoughness { /// The rgba base color of the PBR material pub base_color: [f32; 4], /// The metalness of the material /// From 0.0 (non-metal) to 1.0 (metal) pub metallic: f32, /// The roughness of the material /// From 0.0 (smooth) to 1.0 (rough) pub roughness: f32, // TODO: base_color_texture and metallic_roughness_texture } impl From> for PbrRoughness { fn from(value: gltf::material::PbrMetallicRoughness) -> Self { PbrRoughness { base_color: value.base_color_factor(), metallic: value.metallic_factor(), roughness: value.roughness_factor(), } } } #[derive(Clone, Debug, Default)] pub struct PbrGlossiness { /// The rgba diffuse color of the material pub diffuse_color: glam::Vec4, // The base color texture // pub diffuse_texture // TODO pub specular: glam::Vec3, /// The glossiness factor of the material. /// From 0.0 (no glossiness) to 1.0 (full glossiness) pub glossiness: f32, // pub glossiness_texture // TODO } impl From> for PbrGlossiness { fn from(value: gltf::material::PbrSpecularGlossiness) -> Self { PbrGlossiness { diffuse_color: value.diffuse_factor().into(), specular: value.specular_factor().into(), glossiness: value.glossiness_factor() } } } /// The alpha rendering mode of a material. /// This is essentially a re-export of gltf::material::AlphaMode #[derive(Clone, Copy, Eq, PartialEq, Debug, Default)] pub enum AlphaMode { /// The alpha value is ignored and the rendered output is fully opaque. #[default] Opaque = 1, /// The rendered output is either fully opaque or fully transparent depending on /// the alpha value and the specified alpha cutoff value. Mask, /// The alpha value is used, to determine the transparency of the rendered output. /// The alpha cutoff value is ignored. Blend, } impl From for AlphaMode { fn from(value: gltf::material::AlphaMode) -> Self { match value { gltf::material::AlphaMode::Opaque => AlphaMode::Opaque, gltf::material::AlphaMode::Mask => AlphaMode::Mask, gltf::material::AlphaMode::Blend => AlphaMode::Blend, } } } #[derive(Clone, Default)] pub struct Material { pub shader_uuid: Option, pub name: Option, pub double_sided: bool, //pub pbr_roughness: PbrRoughness, /// The RGBA base color of the model. If a texture is supplied with `base_color_texture`, this value /// will tint the texture. If a texture is not provided, this value would be the color of the Material. pub base_color: glam::Vec4, /// The metalness of the material /// From 0.0 (non-metal) to 1.0 (metal) pub metallic: f32, /// The roughness of the material /// From 0.0 (smooth) to 1.0 (rough) pub roughness: f32, /// The base color texture of the model. pub base_color_texture: Option>, /// The metallic-roughness texture. /// /// The metalness values are sampled from the B channel. The roughness values are sampled from /// the G channel. These values are linear. If other channels are present (R or A), they are /// ignored for metallic-roughness calculations. pub metallic_roughness_texture: Option>, /// A set of parameter values that are used to define the specular-glossiness material model /// from Physically-Based Rendering (PBR) methodology. /// GLTF extension: [KHR_materials_pbrSpecularGlossiness](https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness) pub pbr_glossiness: Option, /// The optional alpha cutoff value of the material. pub alpha_cutoff: Option, /// The alpha rendering mode of the material. The material's alpha rendering /// mode enumeration specifying the interpretation of the alpha value of the main /// factor and texture. /// /// * In `Opaque` mode (default) the alpha value is ignored /// and the rendered output is fully opaque. /// * In `Mask` mode, the rendered /// output is either fully opaque or fully transparent depending on the alpha /// value and the specified alpha cutoff value. /// * In `Blend` mode, the alpha value is used to composite the source and /// destination areas and the rendered output is combined with the background /// using the normal painting operation (i.e. the Porter and Duff over /// operator). pub alpha_mode: AlphaMode, //pub texture: Option>, } #[allow(dead_code)] impl Material { /// Get a uri's identifier /// /// I'm not actually sure how identifiable this would be fn uri_ident(gltf_rel_path: &str, uri: &str) -> String { let mut hasher = DefaultHasher::new(); uri.hash(&mut hasher); let hash = hasher.finish(); format!("{gltf_rel_path};{hash}") } fn source_ident(gltf_rel_path: &str, src: &gltf::image::Source) -> Option { match src { gltf::image::Source::View { view, mime_type: _ } => { let buf = view.buffer(); let src = buf.source(); match src { gltf::buffer::Source::Bin => None, gltf::buffer::Source::Uri(uri) => { Some(Material::uri_ident(gltf_rel_path, uri)) } } }, gltf::image::Source::Uri { uri, mime_type: _ } => { Some(Material::uri_ident(gltf_rel_path, uri)) }, } } fn read_source(context: &mut GltfLoadContext, src: gltf::image::Source) -> Result, util::UriReadError> { let gltf_rel_path = context.gltf_parent_path; // TODO: Don't copy sources match src { gltf::image::Source::View { view, mime_type: _ } => { let buf = view.buffer(); let src = buf.source(); let offset = view.offset(); let len = view.length(); match src { gltf::buffer::Source::Bin => { let mut b = context.gltf.blob.clone().unwrap(); b.drain(0..offset); b.truncate(len); Ok(b) }, gltf::buffer::Source::Uri(uri) => { util::gltf_read_buffer_uri(gltf_rel_path, uri) .map(|mut buf| { buf.drain(0..offset); buf.truncate(len); buf }) } } }, gltf::image::Source::Uri { uri, mime_type: _ } => { util::gltf_read_buffer_uri(gltf_rel_path, uri) }, } } fn load_texture(context: &mut GltfLoadContext, texture_info: gltf::texture::Info<'_>) -> ResHandle { // TODO: texture_info.tex_coord() let tex = texture_info.texture(); let img = tex.source(); let src = img.source(); let buf = Material::read_source(context, src).unwrap(); let buflen = buf.len(); let mime_type = infer::get(&buf).expect("Failed to get file type").mime_type(); context.resource_manager.load_bytes::(&uuid::Uuid::new_v4().to_string(), mime_type, buf, 0, buflen).unwrap() } /// Load the Material from a gltf::Material. /// /// `gltf_rel_path`: The relative path of the gltf file, /// e.g. gltf model path is "resource/models/player.gltf", the relative path would be "resource/models/" pub(crate) fn from_gltf(context: &mut GltfLoadContext, gltf_mat: gltf::Material) -> Self { let pbr_rough = gltf_mat.pbr_metallic_roughness(); let base_color = pbr_rough.base_color_factor().into(); let metallic = pbr_rough.metallic_factor(); let roughness = pbr_rough.roughness_factor(); let base_color_texture = pbr_rough.base_color_texture() .map(|info| Material::load_texture(context, info)); let metallic_roughness_texture = pbr_rough.metallic_roughness_texture() .map(|info| Material::load_texture(context, info)); Material { name: gltf_mat.name() .map(|s| s.to_string()), double_sided: gltf_mat.double_sided(), base_color, metallic, roughness, pbr_glossiness: gltf_mat.pbr_specular_glossiness() .map(|o| o.into()), alpha_cutoff: gltf_mat.alpha_cutoff(), alpha_mode: gltf_mat.alpha_mode().into(), shader_uuid: None, // TODO base_color_texture, metallic_roughness_texture, } } }