Implement loading material textures from gltf and rendering them

This commit is contained in:
SeanOMik 2023-10-17 22:04:25 -04:00
parent 02a0eea7b3
commit fd9f4bee2a
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
22 changed files with 891 additions and 74 deletions

14
Cargo.lock generated
View File

@ -1091,6 +1091,12 @@ dependencies = [
"hashbrown 0.14.0", "hashbrown 0.14.0",
] ]
[[package]]
name = "infer"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199"
[[package]] [[package]]
name = "inflections" name = "inflections"
version = "1.1.1" version = "1.1.1"
@ -1302,6 +1308,8 @@ dependencies = [
"glam", "glam",
"gltf", "gltf",
"image", "image",
"infer",
"mime",
"percent-encoding", "percent-encoding",
"thiserror", "thiserror",
"tracing", "tracing",
@ -1373,6 +1381,12 @@ dependencies = [
"objc", "objc",
] ]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"

View File

@ -1124,6 +1124,12 @@ dependencies = [
"hashbrown 0.14.1", "hashbrown 0.14.1",
] ]
[[package]]
name = "infer"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199"
[[package]] [[package]]
name = "inflections" name = "inflections"
version = "1.1.1" version = "1.1.1"
@ -1341,6 +1347,8 @@ dependencies = [
"glam", "glam",
"gltf", "gltf",
"image", "image",
"infer",
"mime",
"percent-encoding", "percent-encoding",
"thiserror", "thiserror",
"tracing", "tracing",
@ -1412,6 +1420,12 @@ dependencies = [
"objc", "objc",
] ]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"

File diff suppressed because one or more lines are too long

View File

@ -75,7 +75,8 @@ async fn main() {
let mut resman = world.get_resource_mut::<ResourceManager>().unwrap(); let mut resman = world.get_resource_mut::<ResourceManager>().unwrap();
let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap(); let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap();
let cube_model = resman.request::<Model>("assets/cube-embedded.gltf").unwrap(); //let cube_model = resman.request::<Model>("assets/cube-embedded.gltf").unwrap();
let cube_model = resman.request::<Model>("assets/cube-texture-embedded.gltf").unwrap();
drop(resman); drop(resman);
/* world.spawn((MeshComponent::new( /* world.spawn((MeshComponent::new(
@ -132,18 +133,18 @@ async fn main() {
}; };
let jiggle_system = |world: &mut World| -> anyhow::Result<()> { let jiggle_system = |world: &mut World| -> anyhow::Result<()> {
let keys = world.get_resource(); let keys = world.get_resource::<InputButtons<KeyCode>>();
if keys.is_none() { if keys.is_none() {
return Ok(()); return Ok(());
} }
let keys = keys.unwrap();
let keys: Ref<InputButtons<KeyCode>> = keys.unwrap(); let speed = 0.01;
let speed = 0.001;
let rot_speed = 1.0; let rot_speed = 1.0;
let mut dir_x = 0.0; let mut dir_x = 0.0;
let mut dir_y = 0.0; let mut dir_y = 0.0;
let mut dir_z = 0.0;
let mut rot_x = 0.0; let mut rot_x = 0.0;
let mut rot_y = 0.0; let mut rot_y = 0.0;
@ -164,6 +165,14 @@ async fn main() {
dir_y -= speed; dir_y -= speed;
} }
if keys.is_pressed(KeyCode::E) {
dir_z += speed;
}
if keys.is_pressed(KeyCode::Q) {
dir_z -= speed;
}
if keys.is_pressed(KeyCode::Left) { if keys.is_pressed(KeyCode::Left) {
rot_y -= rot_speed; rot_y -= rot_speed;
} }
@ -182,7 +191,7 @@ async fn main() {
drop(keys); drop(keys);
if dir_x == 0.0 && dir_y == 0.0 && rot_x == 0.0 && rot_y == 0.0 { if dir_x == 0.0 && dir_y == 0.0 && dir_z == 0.0 && rot_x == 0.0 && rot_y == 0.0 {
return Ok(()); return Ok(());
} }
@ -197,6 +206,7 @@ async fn main() {
t.translation.x *= -1.0; */ t.translation.x *= -1.0; */
t.translation.x += dir_x; t.translation.x += dir_x;
t.translation.y += dir_y; t.translation.y += dir_y;
t.translation.z += dir_z;
t.rotate_x(math::Angle::Degrees(rot_x)); t.rotate_x(math::Angle::Degrees(rot_x));
t.rotate_y(math::Angle::Degrees(rot_y)); t.rotate_y(math::Angle::Degrees(rot_y));
} }

View File

@ -12,6 +12,9 @@ edict = "0.5.0"
glam = "0.24.1" glam = "0.24.1"
gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness"] } gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness"] }
image = "0.24.7" image = "0.24.7"
# not using custom matcher, or file type from file path
infer = { version = "0.15.0", default-features = false }
mime = "0.3.17"
percent-encoding = "2.3.0" percent-encoding = "2.3.0"
thiserror = "1.0.48" thiserror = "1.0.48"
tracing = "0.1.37" tracing = "0.1.37"

View File

@ -15,3 +15,5 @@ pub use model::*;
pub mod material; pub mod material;
pub use material::*; pub use material::*;
pub(crate) mod util;

View File

@ -2,7 +2,7 @@ use std::{fs::File, sync::Arc, io::Read};
use image::ImageError; use image::ImageError;
use crate::{resource_manager::ResourceStorage, texture::Texture, resource::Resource}; use crate::{resource_manager::ResourceStorage, texture::Texture, resource::Resource, ResourceManager};
use super::{LoaderError, ResourceLoader}; use super::{LoaderError, ResourceLoader};
@ -14,9 +14,9 @@ impl From<ImageError> for LoaderError {
/// A struct that implements the `ResourceLoader` trait used for loading textures. /// A struct that implements the `ResourceLoader` trait used for loading textures.
#[derive(Default)] #[derive(Default)]
pub struct TextureLoader; pub struct ImageLoader;
impl ResourceLoader for TextureLoader { impl ResourceLoader for ImageLoader {
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {
&[ &[
// the extensions of these are the names of the formats // the extensions of these are the names of the formats
@ -26,11 +26,23 @@ impl ResourceLoader for TextureLoader {
"ff", "ff",
// pnm // pnm
"pnm", "pbm", "pgm", "ppm", "pam", "pnm", "pbm", "pgm", "ppm",
] ]
} }
fn load(&self, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError> { fn mime_types(&self) -> &[&str] {
&[
"image/bmp", "image/vnd.ms-dds", "image/gif", "image/x-icon", "image/jpeg",
"image/png", "image/qoi", "image/tga", "image/tiff", "image/webp",
// no known mime for farbfeld
// pnm, pbm, pgm, ppm
"image/x-portable-anymap", "image/x-portable-bitmap", "image/x-portable-graymap", "image/x-portable-pixmap",
]
}
fn load(&self, _resource_manager: &mut ResourceManager, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
// check if the file is supported by this loader // check if the file is supported by this loader
if !self.does_support_file(path) { if !self.does_support_file(path) {
return Err(LoaderError::UnsupportedExtension(path.to_string())); return Err(LoaderError::UnsupportedExtension(path.to_string()));
@ -54,6 +66,20 @@ impl ResourceLoader for TextureLoader {
Ok(Arc::new(res)) Ok(Arc::new(res))
} }
fn load_bytes(&self, _resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
let image = image::load_from_memory(&bytes[offset..(length-offset)])
.map_err(|e| match e {
ImageError::IoError(e) => LoaderError::IoError(e),
_ => LoaderError::DecodingError(e.into()),
})?;
let texture = Texture {
image,
};
let res = Resource::with_data(&uuid::Uuid::new_v4().to_string(), texture);
Ok(Arc::new(res))
}
} }
#[cfg(test)] #[cfg(test)]
@ -68,20 +94,22 @@ mod tests {
#[test] #[test]
fn check_unsupport() { fn check_unsupport() {
let loader = TextureLoader::default(); let loader = ImageLoader::default();
assert_eq!(loader.does_support_file("test.gltf"), false); assert_eq!(loader.does_support_file("test.gltf"), false);
} }
/// Tests loading an image /// Tests loading an image
#[test] #[test]
fn image_load() { fn image_load() {
let loader = TextureLoader::default(); let mut manager = ResourceManager::new();
loader.load(&get_image("squiggles.png")).unwrap(); let loader = ImageLoader::default();
loader.load(&mut manager, &get_image("squiggles.png")).unwrap();
} }
#[test] #[test]
fn image_load_unsupported() { fn image_load_unsupported() {
let loader = TextureLoader::default(); let mut manager = ResourceManager::new();
assert!(loader.load(&get_image("squiggles.gltf")).is_err()); let loader = ImageLoader::default();
assert!(loader.load(&mut manager, &get_image("squiggles.gltf")).is_err());
} }
} }

View File

@ -1,11 +1,11 @@
pub mod texture; pub mod image;
pub mod model; pub mod model;
use std::{io, sync::Arc, path::Path, ffi::OsStr}; use std::{io, sync::Arc, path::Path, ffi::OsStr};
use thiserror::Error; use thiserror::Error;
use crate::resource_manager::ResourceStorage; use crate::{resource_manager::ResourceStorage, ResourceManager};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum LoaderError { pub enum LoaderError {
@ -30,8 +30,12 @@ impl From<io::Error> for LoaderError {
} }
pub trait ResourceLoader: Send + Sync { pub trait ResourceLoader: Send + Sync {
/// Returns the extensions that this loader supports.
fn extensions(&self) -> &[&str]; fn extensions(&self) -> &[&str];
/// Returns the mime types that this loader supports.
fn mime_types(&self) -> &[&str];
/// Returns true if this loader supports the file.
fn does_support_file(&self, path: &str) -> bool { fn does_support_file(&self, path: &str) -> bool {
match Path::new(path).extension().and_then(OsStr::to_str) { match Path::new(path).extension().and_then(OsStr::to_str) {
Some(ext) => { Some(ext) => {
@ -41,18 +45,26 @@ pub trait ResourceLoader: Send + Sync {
} }
} }
fn load(&self, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError>; /// Returns true if this loader supports the mime type.
fn does_support_mime(&self, mime: &str) -> bool {
self.mime_types().contains(&mime)
}
/// Load a resource from a path.
fn load(&self, resource_manager: &mut ResourceManager, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
/// Load a resource from bytes.
fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{*, texture::TextureLoader}; use super::{*, image::ImageLoader};
/// Ensure that `does_support_file` works /// Ensure that `does_support_file` works
#[test] #[test]
fn check_support() { fn check_support() {
let loader = TextureLoader::default(); let loader = ImageLoader::default();
let extensions = loader.extensions(); let extensions = loader.extensions();
let fake_paths: Vec<String> = extensions.iter().map(|e| format!("a.{}", e)).collect(); let fake_paths: Vec<String> = extensions.iter().map(|e| format!("a.{}", e)).collect();
for path in fake_paths.iter() { for path in fake_paths.iter() {

View File

@ -1,8 +1,9 @@
use std::sync::Arc; use std::{sync::Arc, path::{Path, PathBuf}};
use base64::Engine; use base64::Engine;
use thiserror::Error;
use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource, Material, MeshIndices}; use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource, Material, MeshIndices, ResourceManager, util};
use tracing::debug; use tracing::debug;
@ -12,11 +13,25 @@ impl From<gltf::Error> for LoaderError {
} }
} }
#[derive(Error, Debug)]
enum ModelLoaderError {
#[error("The model ({0}) is missing the BIN section in the gltf file")]
MissingBin(String),
#[error("There was an error with decoding a uri defined in the model: '{0}'")]
UriDecodingError(util::UriReadError),
}
impl From<ModelLoaderError> for LoaderError {
fn from(value: ModelLoaderError) -> Self {
LoaderError::DecodingError(value.into())
}
}
#[derive(Default)] #[derive(Default)]
pub struct ModelLoader; pub struct ModelLoader;
impl ModelLoader { impl ModelLoader {
fn parse_uri(uri: &str) -> Option<Vec<u8>> { /* fn parse_uri(containing_path: &str, uri: &str) -> Option<Vec<u8>> {
let uri = uri.strip_prefix("data")?; let uri = uri.strip_prefix("data")?;
let (mime, data) = uri.split_once(",")?; let (mime, data) = uri.split_once(",")?;
@ -26,11 +41,13 @@ impl ModelLoader {
}; };
if is_base64 { if is_base64 {
base64::engine::general_purpose::STANDARD.decode(data).ok() Some(base64::engine::general_purpose::STANDARD.decode(data).unwrap())
} else { } else {
Some(data.as_bytes().to_vec()) let full_path = format!("{containing_path}/{data}");
let buf = std::fs::read(&full_path).unwrap();
Some(buf)
} }
} } */
fn process_node(&self, buffers: &Vec<Vec<u8>>, materials: &Vec<Material>, node: gltf::Node<'_>) -> Vec<Mesh> { fn process_node(&self, buffers: &Vec<Vec<u8>>, materials: &Vec<Material>, node: gltf::Node<'_>) -> Vec<Mesh> {
let mut meshes = vec![]; let mut meshes = vec![];
@ -61,7 +78,7 @@ impl ModelLoader {
// read tex coords // read tex coords
if let Some(tex_coords) = reader.read_tex_coords(0) { if let Some(tex_coords) = reader.read_tex_coords(0) {
let tex_coords: Vec<glam::Vec2> = tex_coords.into_f32().map(|t| t.into()).collect(); // TODO: u16, u8 let tex_coords: Vec<glam::Vec2> = tex_coords.into_f32().map(|t| t.into()).collect();
new_mesh.add_attribute(MeshVertexAttribute::TexCoords, VertexAttributeData::Vec2(tex_coords)); new_mesh.add_attribute(MeshVertexAttribute::TexCoords, VertexAttributeData::Vec2(tex_coords));
} }
@ -101,17 +118,27 @@ impl ResourceLoader for ModelLoader {
] ]
} }
fn load(&self, path: &str) -> Result<std::sync::Arc<dyn crate::ResourceStorage>, crate::LoaderError> { fn mime_types(&self) -> &[&str] {
&[]
}
fn load(&self, resource_manager: &mut ResourceManager, path: &str) -> Result<std::sync::Arc<dyn crate::ResourceStorage>, crate::LoaderError> {
// check if the file is supported by this loader // check if the file is supported by this loader
if !self.does_support_file(path) { if !self.does_support_file(path) {
return Err(LoaderError::UnsupportedExtension(path.to_string())); return Err(LoaderError::UnsupportedExtension(path.to_string()));
} }
let mut parent_path = PathBuf::from(path);
parent_path.pop();
let parent_path = parent_path.display().to_string();
let gltf = gltf::Gltf::open(path)?; let gltf = gltf::Gltf::open(path)?;
let buffers: Vec<Vec<u8>> = gltf.buffers().map(|b| match b.source() { let buffers: Vec<Vec<u8>> = gltf.buffers().map(|b| match b.source() {
gltf::buffer::Source::Bin => gltf.blob.as_deref().map(|v| v.to_vec()), gltf::buffer::Source::Bin => gltf.blob.as_deref().map(|v| v.to_vec())
gltf::buffer::Source::Uri(uri) => ModelLoader::parse_uri(uri), .ok_or(ModelLoaderError::MissingBin(path.to_string())),
gltf::buffer::Source::Uri(uri) => util::gltf_read_buffer_uri(&parent_path, uri)
.map_err(|e| ModelLoaderError::UriDecodingError(e)),
}).flatten().collect(); }).flatten().collect();
// TODO: Read in multiple scenes // TODO: Read in multiple scenes
@ -119,7 +146,7 @@ impl ResourceLoader for ModelLoader {
// Load the materials // Load the materials
let materials: Vec<Material> = gltf.materials() let materials: Vec<Material> = gltf.materials()
.map(|mat| Material::from(mat)).collect(); .map(|mat| Material::from_gltf(resource_manager, &parent_path, mat)).collect();
let meshes: Vec<Mesh> = scene.nodes() let meshes: Vec<Mesh> = scene.nodes()
.map(|node| self.process_node(&buffers, &materials, node)) .map(|node| self.process_node(&buffers, &materials, node))
@ -127,6 +154,10 @@ impl ResourceLoader for ModelLoader {
Ok(Arc::new(Resource::with_data(path, Model::new(meshes)))) Ok(Arc::new(Resource::with_data(path, Model::new(meshes))))
} }
fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn crate::ResourceStorage>, LoaderError> {
todo!()
}
} }
#[cfg(test)] #[cfg(test)]
@ -142,10 +173,11 @@ mod tests {
#[test] #[test]
fn test_loading() { fn test_loading() {
let path = test_file_path("test-embedded.gltf"); let path = test_file_path("texture-embedded.gltf");
let mut manager = ResourceManager::new();
let loader = ModelLoader::default(); let loader = ModelLoader::default();
let model = loader.load(&path).unwrap(); let model = loader.load(&mut manager, &path).unwrap();
let model = Arc::downcast::<Resource<Model>>(model.as_arc_any()).unwrap(); let model = Arc::downcast::<Resource<Model>>(model.as_arc_any()).unwrap();
let model = model.data.as_ref().unwrap(); let model = model.data.as_ref().unwrap();
assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh
@ -154,6 +186,7 @@ mod tests {
assert!(mesh.normals().unwrap().len() > 0); assert!(mesh.normals().unwrap().len() > 0);
assert!(mesh.tex_coords().unwrap().len() > 0); assert!(mesh.tex_coords().unwrap().len() > 0);
assert!(mesh.indices.clone().unwrap().len() > 0); assert!(mesh.indices.clone().unwrap().len() > 0);
assert!(mesh.material().base_color_texture.is_some());
let _mesh_mat = mesh.material(); // inner panic if material was not loaded let _mesh_mat = mesh.material(); // inner panic if material was not loaded
} }
} }

View File

@ -1,4 +1,6 @@
use crate::{Texture, ResHandle}; use std::{fs::File, io::{BufReader, Read}, collections::hash_map::DefaultHasher, hash::{Hash, Hasher}};
use crate::{Texture, ResHandle, ResourceManager, util};
/// PBR metallic roughness /// PBR metallic roughness
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
@ -27,10 +29,10 @@ impl From<gltf::material::PbrMetallicRoughness<'_>> for PbrRoughness {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct PbrGlossiness { pub struct PbrGlossiness {
/// The rgba diffuse color of the material /// The rgba diffuse color of the material
pub diffuse_color: [f32; 4], pub diffuse_color: glam::Vec4,
// The base color texture // The base color texture
// pub diffuse_texture // TODO // pub diffuse_texture // TODO
pub specular: [f32; 3], pub specular: glam::Vec3,
/// The glossiness factor of the material. /// The glossiness factor of the material.
/// From 0.0 (no glossiness) to 1.0 (full glossiness) /// From 0.0 (no glossiness) to 1.0 (full glossiness)
pub glossiness: f32, pub glossiness: f32,
@ -40,39 +42,201 @@ pub struct PbrGlossiness {
impl From<gltf::material::PbrSpecularGlossiness<'_>> for PbrGlossiness { impl From<gltf::material::PbrSpecularGlossiness<'_>> for PbrGlossiness {
fn from(value: gltf::material::PbrSpecularGlossiness) -> Self { fn from(value: gltf::material::PbrSpecularGlossiness) -> Self {
PbrGlossiness { PbrGlossiness {
diffuse_color: value.diffuse_factor(), diffuse_color: value.diffuse_factor().into(),
specular: value.specular_factor(), specular: value.specular_factor().into(),
glossiness: value.glossiness_factor() 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)] #[derive(Clone, Default)]
pub struct Material { pub struct Material {
pub shader_uuid: Option<u64>, pub shader_uuid: Option<u64>,
pub name: Option<String>, pub name: Option<String>,
pub double_sided: bool, pub double_sided: bool,
pub pbr_roughness: PbrRoughness,
pub pbr_glossiness: Option<PbrGlossiness>,
pub alpha_cutoff: Option<f32>,
pub alpha_mode: gltf::material::AlphaMode,
pub texture: Option<ResHandle<Texture>>, //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>>,
} }
impl From<gltf::Material<'_>> for Material { impl Material {
fn from(value: gltf::Material) -> Self { /// 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 }; */
Material { Material {
name: value.name() name: gltf_mat.name()
.map(|s| s.to_string()), .map(|s| s.to_string()),
double_sided: value.double_sided(), double_sided: gltf_mat.double_sided(),
pbr_roughness: value.pbr_metallic_roughness().into(), base_color,
pbr_glossiness: value.pbr_specular_glossiness() metallic,
roughness,
pbr_glossiness: gltf_mat.pbr_specular_glossiness()
.map(|o| o.into()), .map(|o| o.into()),
alpha_cutoff: value.alpha_cutoff(), alpha_cutoff: gltf_mat.alpha_cutoff(),
alpha_mode: value.alpha_mode(), alpha_mode: gltf_mat.alpha_mode().into(),
shader_uuid: None, shader_uuid: None,
texture: None,
// TODO
base_color_texture,
metallic_roughness_texture,
} }
} }
} }

View File

@ -10,6 +10,15 @@ pub enum MeshIndices {
U32(Vec<u32>), U32(Vec<u32>),
} }
impl MeshIndices {
pub fn len(&self) -> usize {
match self {
MeshIndices::U16(v) => v.len(),
MeshIndices::U32(v) => v.len(),
}
}
}
/* impl From<Vec<u8>> for MeshIndices { /* impl From<Vec<u8>> for MeshIndices {
fn from(value: Vec<u8>) -> Self { fn from(value: Vec<u8>) -> Self {
MeshIndices::U8(value) MeshIndices::U8(value)

View File

@ -2,7 +2,7 @@ use std::{sync::Arc, collections::{HashMap, hash_map::DefaultHasher}, hash::{Has
use thiserror::Error; use thiserror::Error;
use crate::{resource::Resource, loader::{ResourceLoader, LoaderError, texture::TextureLoader, model::ModelLoader}}; use crate::{resource::Resource, loader::{ResourceLoader, LoaderError, image::ImageLoader, model::ModelLoader}};
pub trait ResourceStorage: Send + Sync + Any + 'static { pub trait ResourceStorage: Send + Sync + Any + 'static {
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
@ -31,6 +31,10 @@ pub enum RequestError {
Loader(LoaderError), Loader(LoaderError),
#[error("The file extension is unsupported: '{0}'")] #[error("The file extension is unsupported: '{0}'")]
UnsupportedFileExtension(String), UnsupportedFileExtension(String),
#[error("The mimetype is unsupported: '{0}'")]
UnsupportedMime(String),
#[error("The identifier is not found: '{0}'")]
IdentNotFound(String),
} }
impl From<LoaderError> for RequestError { impl From<LoaderError> for RequestError {
@ -39,16 +43,19 @@ impl From<LoaderError> for RequestError {
} }
} }
/// A struct that stores all Manager data. This is requried for sending
//struct ManagerStorage
pub struct ResourceManager { pub struct ResourceManager {
resources: HashMap<String, Arc<dyn ResourceStorage>>, resources: HashMap<String, Arc<dyn ResourceStorage>>,
loaders: Vec<Box<dyn ResourceLoader>>, loaders: Vec<Arc<dyn ResourceLoader>>,
} }
impl ResourceManager { impl ResourceManager {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
resources: HashMap::new(), resources: HashMap::new(),
loaders: vec![ Box::new(TextureLoader::default()), Box::new(ModelLoader::default()) ], loaders: vec![ Arc::new(ImageLoader::default()), Arc::new(ModelLoader::default()) ],
} }
} }
@ -65,10 +72,11 @@ impl ResourceManager {
.find(|l| l.does_support_file(path)) { .find(|l| l.does_support_file(path)) {
// Load the resource and store it // Load the resource and store it
let res = loader.load(path)?; let loader = Arc::clone(&loader); // stop borrowing from self
let res = loader.load(self, path)?;
self.resources.insert(path.to_string(), res.clone()); self.resources.insert(path.to_string(), res.clone());
// convert Arc<dyn ResourceStorage> to Arc<Resource<T> // cast Arc<dyn ResourceStorage> to Arc<Resource<T>
let res = res.as_arc_any(); let res = res.as_arc_any();
let res = res.downcast::<Resource<T>>() let res = res.downcast::<Resource<T>>()
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?"); .expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
@ -80,6 +88,49 @@ impl ResourceManager {
} }
} }
} }
/// Store bytes in the manager. If there is already an entry with the same identifier it will be updated.
///
/// Panics: If there is already an entry with the same `ident`, and the entry is not bytes, this function will panic.
///
/// Parameters:
/// * `ident` - The identifier to store along with these bytes. Make sure its unique to avoid overriding something.
/// * `bytes` - The bytes to store.
///
/// Returns: The `Arc` to the now stored resource
pub fn load_bytes<T: Send + Sync + Any + 'static>(&mut self, ident: &str, mime_type: &str, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<Resource<T>>, RequestError> {
if let Some(loader) = self.loaders.iter()
.find(|l| l.does_support_mime(mime_type)) {
let loader = loader.clone();
let res = loader.load_bytes(self, bytes, offset, length)?;
self.resources.insert(ident.to_string(), res.clone());
// code here...
// cast Arc<dyn ResourceStorage> to Arc<Resource<T>
let res = res.as_arc_any();
let res = res.downcast::<Resource<T>>()
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
Ok(res)
} else {
Err(RequestError::UnsupportedMime(mime_type.to_string()))
}
}
/// Requests bytes from the manager.
pub fn request_loaded_bytes<T: Send + Sync + Any + 'static>(&mut self, ident: &str) -> Result<Arc<Resource<T>>, RequestError> {
match self.resources.get(&ident.to_string()) {
Some(res) => {
let res = res.clone().as_arc_any();
let res = res.downcast::<Resource<T>>().expect("Failure to downcast resource");
Ok(res)
},
None => {
Err(RequestError::IdentNotFound(ident.to_string()))
}
}
}
} }
#[cfg(test)] #[cfg(test)]

38
lyra-resource/src/util.rs Normal file
View File

@ -0,0 +1,38 @@
use base64::Engine;
use thiserror::Error;
use std::io;
#[derive(Error, Debug)]
pub enum UriReadError {
#[error("IOError: '{0}'")]
IoError(io::Error),
// From is implemented for this field in each loader module
#[error("Base64 decoding error: '{0}'")]
Base64Decode(base64::DecodeError),
#[error("Some data was missing from the uri")]
None
}
/// Read a buffer's uri string into a byte buffer.
///
/// * `containing_path`: The path of the containing folder of the buffers "parent",
/// the parent being where this buffer is defined in,
/// i.e. parent="resources/models/player.gltf", containing="reousrce/models"
pub(crate) fn gltf_read_buffer_uri(containing_path: &str, uri: &str) -> Result<Vec<u8>, UriReadError> {
let uri = uri.strip_prefix("data").ok_or(UriReadError::None)?;
let (mime, data) = uri.split_once(",").ok_or(UriReadError::None)?;
let (_mime, is_base64) = match mime.strip_suffix(";base64") {
Some(mime) => (mime, true),
None => (mime, false),
};
if is_base64 {
base64::engine::general_purpose::STANDARD.decode(data).map_err(|e| UriReadError::Base64Decode(e))
} else {
let full_path = format!("{containing_path}/{data}");
std::fs::read(&full_path).map_err(|e| UriReadError::IoError(e))
}
}

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,137 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v3.6.6",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"Cube"
}
],
"materials":[
{
"doubleSided":true,
"name":"Material",
"pbrMetallicRoughness":{
"baseColorTexture":{
"index":0
},
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"name":"Cube",
"primitives":[
{
"attributes":{
"POSITION":0,
"TEXCOORD_0":1,
"NORMAL":2
},
"indices":3,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/png",
"name":"uvgrid",
"uri":"uvgrid.png"
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":24,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":2,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":288,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":288,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":480,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":768,
"target":34963
}
],
"samplers":[
{
"magFilter":9729,
"minFilter":9987
}
],
"buffers":[
{
"byteLength":840,
"uri":"texture-sep.bin"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -21,6 +21,7 @@ use crate::ecs::components::transform::TransformComponent;
use super::camera::RenderCamera; use super::camera::RenderCamera;
use super::desc_buf_lay::DescVertexBufferLayout; use super::desc_buf_lay::DescVertexBufferLayout;
use super::texture::RenderTexture; use super::texture::RenderTexture;
use super::vertex::Vertex;
use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob}; use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob};
use lyra_resource::Mesh; use lyra_resource::Mesh;
@ -443,15 +444,26 @@ impl BasicRenderer {
} }
fn create_vertex_index_buffers(&mut self, mesh: &Mesh) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) { fn create_vertex_index_buffers(&mut self, mesh: &Mesh) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) {
let vertices = mesh.position().unwrap(); let positions = mesh.position().unwrap();
let tex_coords: Vec<glam::Vec2> = mesh.tex_coords().cloned()
.unwrap_or_else(|| vec![glam::Vec2::new(0.0, 0.0); positions.len()]);
debug!("Pos count: {}, tex coords count: {}", positions.len(), tex_coords.len());
assert!(positions.len() == tex_coords.len());
let vertex_inputs: Vec<Vertex> = std::iter::zip(positions, tex_coords.into_iter())
.map(|(v, t)| Vertex::new(v.clone(), t))
.collect();
println!("Vertex inputs: {:?}", vertex_inputs.as_slice());
let vertex_buffer = self.device.create_buffer_init( let vertex_buffer = self.device.create_buffer_init(
&wgpu::util::BufferInitDescriptor { &wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"), label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(vertices.as_slice()), contents: bytemuck::cast_slice(vertex_inputs.as_slice()),//vertex_combined.as_slice(),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages:: COPY_DST, usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages:: COPY_DST,
} }
); );
let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertices.len()); let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertex_inputs.len());
let indices = match mesh.indices.as_ref() { let indices = match mesh.indices.as_ref() {
Some(indices) => { Some(indices) => {
@ -483,7 +495,7 @@ impl BasicRenderer {
fn create_mesh_buffers(&mut self, mesh: &Mesh, transform_indices: TransformBufferIndices) -> RenderBufferStorage { fn create_mesh_buffers(&mut self, mesh: &Mesh, transform_indices: TransformBufferIndices) -> RenderBufferStorage {
let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh); let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh);
let diffuse_bindgroup = if let Some(model_texture) = &mesh.material().texture { let diffuse_bindgroup = if let Some(model_texture) = &mesh.material().base_color_texture {
let image = &model_texture.data.as_ref().unwrap().image; let image = &model_texture.data.as_ref().unwrap().image;
let diffuse_texture = RenderTexture::from_image(&self.device, &self.queue, image, None).unwrap(); let diffuse_texture = RenderTexture::from_image(&self.device, &self.queue, image, None).unwrap();

View File

@ -2,7 +2,7 @@
struct VertexInput { struct VertexInput {
@location(0) position: vec3<f32>, @location(0) position: vec3<f32>,
//@location(1) tex_coords: vec2<f32>, @location(1) tex_coords: vec2<f32>,
} }
struct VertexOutput { struct VertexOutput {
@ -25,7 +25,7 @@ fn vs_main(
model: VertexInput, model: VertexInput,
) -> VertexOutput { ) -> VertexOutput {
var out: VertexOutput; var out: VertexOutput;
out.tex_coords = vec2<f32>(1.0, 1.0); out.tex_coords = model.tex_coords;
out.clip_position = camera.view_proj * u_model_transform * vec4<f32>(model.position, 1.0); out.clip_position = camera.view_proj * u_model_transform * vec4<f32>(model.position, 1.0);
return out; return out;
} }

View File

@ -1,31 +1,37 @@
use glam::Vec3;
use super::desc_buf_lay::DescVertexBufferLayout; use super::desc_buf_lay::DescVertexBufferLayout;
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex { pub struct Vertex {
pub position: [f32; 3], pub position: glam::Vec3,
pub tex_coords: glam::Vec2
//pub color: [f32; 3], // TODO: add color again //pub color: [f32; 3], // TODO: add color again
//pub tex_coords: [f32; 2] }
impl Vertex {
pub fn new(position: glam::Vec3, tex_coords: glam::Vec2) -> Self {
Self {
position, tex_coords
}
}
} }
impl DescVertexBufferLayout for Vertex { impl DescVertexBufferLayout for Vertex {
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
wgpu::VertexBufferLayout { wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vec3>() as wgpu::BufferAddress, array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex, step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[ attributes: &[
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: 0, offset: 0,
shader_location: 0, shader_location: 0,
format: wgpu::VertexFormat::Float32x3, format: wgpu::VertexFormat::Float32x3, // Vec3
}, },
/* wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1, shader_location: 1,
format: wgpu::VertexFormat::Float32x2, format: wgpu::VertexFormat::Float32x2, // Vec2
} */ }
] ]
} }
} }