Implement loading material textures from gltf and rendering them
This commit is contained in:
parent
02a0eea7b3
commit
fd9f4bee2a
|
@ -1091,6 +1091,12 @@ dependencies = [
|
|||
"hashbrown 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "infer"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199"
|
||||
|
||||
[[package]]
|
||||
name = "inflections"
|
||||
version = "1.1.1"
|
||||
|
@ -1302,6 +1308,8 @@ dependencies = [
|
|||
"glam",
|
||||
"gltf",
|
||||
"image",
|
||||
"infer",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
|
@ -1373,6 +1381,12 @@ dependencies = [
|
|||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
|
|
|
@ -1124,6 +1124,12 @@ dependencies = [
|
|||
"hashbrown 0.14.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "infer"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199"
|
||||
|
||||
[[package]]
|
||||
name = "inflections"
|
||||
version = "1.1.1"
|
||||
|
@ -1341,6 +1347,8 @@ dependencies = [
|
|||
"glam",
|
||||
"gltf",
|
||||
"image",
|
||||
"infer",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
|
@ -1412,6 +1420,12 @@ dependencies = [
|
|||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -75,7 +75,8 @@ async fn main() {
|
|||
|
||||
let mut resman = world.get_resource_mut::<ResourceManager>().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);
|
||||
|
||||
/* world.spawn((MeshComponent::new(
|
||||
|
@ -132,18 +133,18 @@ async fn main() {
|
|||
};
|
||||
|
||||
let jiggle_system = |world: &mut World| -> anyhow::Result<()> {
|
||||
let keys = world.get_resource();
|
||||
let keys = world.get_resource::<InputButtons<KeyCode>>();
|
||||
if keys.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
let keys = keys.unwrap();
|
||||
|
||||
let keys: Ref<InputButtons<KeyCode>> = keys.unwrap();
|
||||
|
||||
let speed = 0.001;
|
||||
let speed = 0.01;
|
||||
let rot_speed = 1.0;
|
||||
|
||||
let mut dir_x = 0.0;
|
||||
let mut dir_y = 0.0;
|
||||
let mut dir_z = 0.0;
|
||||
|
||||
let mut rot_x = 0.0;
|
||||
let mut rot_y = 0.0;
|
||||
|
@ -164,6 +165,14 @@ async fn main() {
|
|||
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) {
|
||||
rot_y -= rot_speed;
|
||||
}
|
||||
|
@ -182,7 +191,7 @@ async fn main() {
|
|||
|
||||
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(());
|
||||
}
|
||||
|
||||
|
@ -197,6 +206,7 @@ async fn main() {
|
|||
t.translation.x *= -1.0; */
|
||||
t.translation.x += dir_x;
|
||||
t.translation.y += dir_y;
|
||||
t.translation.z += dir_z;
|
||||
t.rotate_x(math::Angle::Degrees(rot_x));
|
||||
t.rotate_y(math::Angle::Degrees(rot_y));
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ edict = "0.5.0"
|
|||
glam = "0.24.1"
|
||||
gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness"] }
|
||||
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"
|
||||
thiserror = "1.0.48"
|
||||
tracing = "0.1.37"
|
||||
|
|
|
@ -15,3 +15,5 @@ pub use model::*;
|
|||
|
||||
pub mod material;
|
||||
pub use material::*;
|
||||
|
||||
pub(crate) mod util;
|
|
@ -2,7 +2,7 @@ use std::{fs::File, sync::Arc, io::Read};
|
|||
|
||||
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};
|
||||
|
||||
|
@ -14,9 +14,9 @@ impl From<ImageError> for LoaderError {
|
|||
|
||||
/// A struct that implements the `ResourceLoader` trait used for loading textures.
|
||||
#[derive(Default)]
|
||||
pub struct TextureLoader;
|
||||
pub struct ImageLoader;
|
||||
|
||||
impl ResourceLoader for TextureLoader {
|
||||
impl ResourceLoader for ImageLoader {
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&[
|
||||
// the extensions of these are the names of the formats
|
||||
|
@ -26,11 +26,23 @@ impl ResourceLoader for TextureLoader {
|
|||
"ff",
|
||||
|
||||
// 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
|
||||
if !self.does_support_file(path) {
|
||||
return Err(LoaderError::UnsupportedExtension(path.to_string()));
|
||||
|
@ -54,6 +66,20 @@ impl ResourceLoader for TextureLoader {
|
|||
|
||||
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)]
|
||||
|
@ -68,20 +94,22 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn check_unsupport() {
|
||||
let loader = TextureLoader::default();
|
||||
let loader = ImageLoader::default();
|
||||
assert_eq!(loader.does_support_file("test.gltf"), false);
|
||||
}
|
||||
|
||||
/// Tests loading an image
|
||||
#[test]
|
||||
fn image_load() {
|
||||
let loader = TextureLoader::default();
|
||||
loader.load(&get_image("squiggles.png")).unwrap();
|
||||
let mut manager = ResourceManager::new();
|
||||
let loader = ImageLoader::default();
|
||||
loader.load(&mut manager, &get_image("squiggles.png")).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_load_unsupported() {
|
||||
let loader = TextureLoader::default();
|
||||
assert!(loader.load(&get_image("squiggles.gltf")).is_err());
|
||||
let mut manager = ResourceManager::new();
|
||||
let loader = ImageLoader::default();
|
||||
assert!(loader.load(&mut manager, &get_image("squiggles.gltf")).is_err());
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
pub mod texture;
|
||||
pub mod image;
|
||||
pub mod model;
|
||||
|
||||
use std::{io, sync::Arc, path::Path, ffi::OsStr};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::resource_manager::ResourceStorage;
|
||||
use crate::{resource_manager::ResourceStorage, ResourceManager};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum LoaderError {
|
||||
|
@ -30,8 +30,12 @@ impl From<io::Error> for LoaderError {
|
|||
}
|
||||
|
||||
pub trait ResourceLoader: Send + Sync {
|
||||
/// Returns the extensions that this loader supports.
|
||||
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 {
|
||||
match Path::new(path).extension().and_then(OsStr::to_str) {
|
||||
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)]
|
||||
mod tests {
|
||||
use super::{*, texture::TextureLoader};
|
||||
use super::{*, image::ImageLoader};
|
||||
|
||||
/// Ensure that `does_support_file` works
|
||||
#[test]
|
||||
fn check_support() {
|
||||
let loader = TextureLoader::default();
|
||||
let loader = ImageLoader::default();
|
||||
let extensions = loader.extensions();
|
||||
let fake_paths: Vec<String> = extensions.iter().map(|e| format!("a.{}", e)).collect();
|
||||
for path in fake_paths.iter() {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use std::sync::Arc;
|
||||
use std::{sync::Arc, path::{Path, PathBuf}};
|
||||
|
||||
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;
|
||||
|
||||
|
@ -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)]
|
||||
pub struct 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 (mime, data) = uri.split_once(",")?;
|
||||
|
||||
|
@ -26,11 +41,13 @@ impl ModelLoader {
|
|||
};
|
||||
|
||||
if is_base64 {
|
||||
base64::engine::general_purpose::STANDARD.decode(data).ok()
|
||||
Some(base64::engine::general_purpose::STANDARD.decode(data).unwrap())
|
||||
} 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> {
|
||||
let mut meshes = vec![];
|
||||
|
@ -61,7 +78,7 @@ impl ModelLoader {
|
|||
|
||||
// read tex coords
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
if !self.does_support_file(path) {
|
||||
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 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::Uri(uri) => ModelLoader::parse_uri(uri),
|
||||
gltf::buffer::Source::Bin => gltf.blob.as_deref().map(|v| v.to_vec())
|
||||
.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();
|
||||
|
||||
// TODO: Read in multiple scenes
|
||||
|
@ -119,7 +146,7 @@ impl ResourceLoader for ModelLoader {
|
|||
|
||||
// Load the 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()
|
||||
.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))))
|
||||
}
|
||||
|
||||
fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn crate::ResourceStorage>, LoaderError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -142,10 +173,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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 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 = model.data.as_ref().unwrap();
|
||||
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.tex_coords().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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
@ -27,10 +29,10 @@ impl From<gltf::material::PbrMetallicRoughness<'_>> for PbrRoughness {
|
|||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PbrGlossiness {
|
||||
/// The rgba diffuse color of the material
|
||||
pub diffuse_color: [f32; 4],
|
||||
pub diffuse_color: glam::Vec4,
|
||||
// The base color texture
|
||||
// pub diffuse_texture // TODO
|
||||
pub specular: [f32; 3],
|
||||
pub specular: glam::Vec3,
|
||||
/// The glossiness factor of the material.
|
||||
/// From 0.0 (no glossiness) to 1.0 (full glossiness)
|
||||
pub glossiness: f32,
|
||||
|
@ -40,39 +42,201 @@ pub struct PbrGlossiness {
|
|||
impl From<gltf::material::PbrSpecularGlossiness<'_>> for PbrGlossiness {
|
||||
fn from(value: gltf::material::PbrSpecularGlossiness) -> Self {
|
||||
PbrGlossiness {
|
||||
diffuse_color: value.diffuse_factor(),
|
||||
specular: value.specular_factor(),
|
||||
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<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,
|
||||
|
||||
//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>,
|
||||
pub alpha_mode: gltf::material::AlphaMode,
|
||||
|
||||
pub texture: Option<ResHandle<Texture>>,
|
||||
/// 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 {
|
||||
fn from(value: gltf::Material) -> Self {
|
||||
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 }; */
|
||||
|
||||
Material {
|
||||
name: value.name()
|
||||
name: gltf_mat.name()
|
||||
.map(|s| s.to_string()),
|
||||
double_sided: value.double_sided(),
|
||||
pbr_roughness: value.pbr_metallic_roughness().into(),
|
||||
pbr_glossiness: value.pbr_specular_glossiness()
|
||||
double_sided: gltf_mat.double_sided(),
|
||||
base_color,
|
||||
metallic,
|
||||
roughness,
|
||||
pbr_glossiness: gltf_mat.pbr_specular_glossiness()
|
||||
.map(|o| o.into()),
|
||||
alpha_cutoff: value.alpha_cutoff(),
|
||||
alpha_mode: value.alpha_mode(),
|
||||
alpha_cutoff: gltf_mat.alpha_cutoff(),
|
||||
alpha_mode: gltf_mat.alpha_mode().into(),
|
||||
shader_uuid: None,
|
||||
texture: None,
|
||||
|
||||
// TODO
|
||||
base_color_texture,
|
||||
metallic_roughness_texture,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,15 @@ pub enum MeshIndices {
|
|||
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 {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
MeshIndices::U8(value)
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::{sync::Arc, collections::{HashMap, hash_map::DefaultHasher}, hash::{Has
|
|||
|
||||
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 {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
@ -31,6 +31,10 @@ pub enum RequestError {
|
|||
Loader(LoaderError),
|
||||
#[error("The file extension is unsupported: '{0}'")]
|
||||
UnsupportedFileExtension(String),
|
||||
#[error("The mimetype is unsupported: '{0}'")]
|
||||
UnsupportedMime(String),
|
||||
#[error("The identifier is not found: '{0}'")]
|
||||
IdentNotFound(String),
|
||||
}
|
||||
|
||||
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 {
|
||||
resources: HashMap<String, Arc<dyn ResourceStorage>>,
|
||||
loaders: Vec<Box<dyn ResourceLoader>>,
|
||||
loaders: Vec<Arc<dyn ResourceLoader>>,
|
||||
}
|
||||
|
||||
impl ResourceManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
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)) {
|
||||
|
||||
// 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());
|
||||
|
||||
// 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.downcast::<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)]
|
||||
|
|
|
@ -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 |
Binary file not shown.
|
@ -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 |
|
@ -21,6 +21,7 @@ use crate::ecs::components::transform::TransformComponent;
|
|||
use super::camera::RenderCamera;
|
||||
use super::desc_buf_lay::DescVertexBufferLayout;
|
||||
use super::texture::RenderTexture;
|
||||
use super::vertex::Vertex;
|
||||
use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob};
|
||||
|
||||
use lyra_resource::Mesh;
|
||||
|
@ -443,15 +444,26 @@ impl BasicRenderer {
|
|||
}
|
||||
|
||||
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(
|
||||
&wgpu::util::BufferInitDescriptor {
|
||||
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,
|
||||
}
|
||||
);
|
||||
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() {
|
||||
Some(indices) => {
|
||||
|
@ -483,7 +495,7 @@ impl BasicRenderer {
|
|||
fn create_mesh_buffers(&mut self, mesh: &Mesh, transform_indices: TransformBufferIndices) -> RenderBufferStorage {
|
||||
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 diffuse_texture = RenderTexture::from_image(&self.device, &self.queue, image, None).unwrap();
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
//@location(1) tex_coords: vec2<f32>,
|
||||
@location(1) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
|
@ -25,7 +25,7 @@ fn vs_main(
|
|||
model: VertexInput,
|
||||
) -> 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);
|
||||
return out;
|
||||
}
|
||||
|
|
|
@ -1,31 +1,37 @@
|
|||
use glam::Vec3;
|
||||
|
||||
use super::desc_buf_lay::DescVertexBufferLayout;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
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 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 {
|
||||
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
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,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
offset: 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,
|
||||
shader_location: 1,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
} */
|
||||
format: wgpu::VertexFormat::Float32x2, // Vec2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue