lyra-engine/lyra-resource/src/material.rs

242 lines
9.3 KiB
Rust
Raw Normal View History

use std::{fs::File, io::{BufReader, Read}, collections::hash_map::DefaultHasher, hash::{Hash, Hasher}};
use crate::{Texture, ResHandle, ResourceManager, util};
/// 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<gltf::material::PbrMetallicRoughness<'_>> 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
}
2023-09-29 18:20:28 +00:00
impl From<gltf::material::PbrSpecularGlossiness<'_>> for PbrGlossiness {
fn from(value: gltf::material::PbrSpecularGlossiness) -> Self {
PbrGlossiness {
diffuse_color: value.diffuse_factor().into(),
specular: value.specular_factor().into(),
2023-09-29 18:20:28 +00:00
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<gltf::material::AlphaMode> 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<u64>,
pub name: Option<String>,
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<ResHandle<Texture>>,
/// 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<ResHandle<Texture>>,
/// 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<PbrGlossiness>,
/// The optional alpha cutoff value of the material.
pub alpha_cutoff: Option<f32>,
/// 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<ResHandle<Texture>>,
2023-09-29 18:20:28 +00:00
}
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<String> {
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(resource_manager: &mut ResourceManager, gltf_rel_path: &str, src: gltf::image::Source) -> Result<Vec<u8>, util::UriReadError> {
// 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 => todo!("Read material source from gltf Bin"),
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(resource_manager: &mut ResourceManager, gltf_rel_path: &str, texture_info: gltf::texture::Info<'_>) -> ResHandle<Texture> {
// TODO: texture_info.tex_coord()
let tex = texture_info.texture();
let img = tex.source();
let src = img.source();
let buf = Material::read_source(resource_manager, gltf_rel_path, src).unwrap();
let buflen = buf.len();
let mime_type = infer::get(&buf).expect("Failed to get file type").mime_type();
resource_manager.load_bytes::<Texture>(&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 fn from_gltf(resource_manager: &mut ResourceManager, gltf_rel_path: &str, 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(resource_manager, gltf_rel_path, info));
let metallic_roughness_texture = pbr_rough.metallic_roughness_texture()
.map(|info| Material::load_texture(resource_manager, gltf_rel_path, info));
/* let base_color_texture = if let Some(base_tex_info) = pbr_rough.base_color_texture() {
Some(Material::load_texture(resource_manager, gltf_rel_path, base_tex_info))
} else { None }; */
2023-09-29 18:20:28 +00:00
Material {
name: gltf_mat.name()
2023-09-29 18:20:28 +00:00
.map(|s| s.to_string()),
double_sided: gltf_mat.double_sided(),
base_color,
metallic,
roughness,
pbr_glossiness: gltf_mat.pbr_specular_glossiness()
2023-09-29 18:20:28 +00:00
.map(|o| o.into()),
alpha_cutoff: gltf_mat.alpha_cutoff(),
alpha_mode: gltf_mat.alpha_mode().into(),
2023-09-29 18:20:28 +00:00
shader_uuid: None,
// TODO
base_color_texture,
metallic_roughness_texture,
2023-09-29 18:20:28 +00:00
}
}
}