feat: add submesh support for mesh loading and rendering
Meshes now track per-primitive submesh ranges (index range + source material index) instead of one flat index range each. glTF meshes get one submesh per primitive; OBJ meshes get a single default submesh until .mtl loading exists. MeshRegistry rewrites these into offsets into its shared index buffer, and the renderer issues one draw call per submesh so each can later be bound to its own material. Also fixes app.rs to match the current MeshRegistry API (load_scene instead of the removed from_scene, and Option-wrapped buffers). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01986QNHM1cUsnRskKvBbbuo
This commit is contained in:
1 parent
fb64fa50e4
commit
9a93c0d189
6 files changed
+113
-21
No files matched your search
+29
-6
@@ -189,7 +189,9 @@ impl VulkanContext {
|
||||
);
|
||||
|
||||
let scene = Scene::from_file_json("./test_scene.scene");
|
||||
let mesh_registry = Arc::new(MeshRegistry::from_scene(&scene, mem_allocator.clone()));
|
||||
let mut mesh_registry = MeshRegistry::new(mem_allocator.clone());
|
||||
mesh_registry.load_scene(&scene, mem_allocator.clone());
|
||||
let mesh_registry = Arc::new(mesh_registry);
|
||||
let instance_registry = Arc::new(InstanceRegistry::from_scene(&scene));
|
||||
|
||||
Ok(Self {
|
||||
@@ -458,8 +460,23 @@ impl VulkanContext {
|
||||
camera_resources.descriptor_set().clone(),
|
||||
)?
|
||||
.bind_pipeline_graphics(pipeline.clone())?
|
||||
.bind_vertex_buffers(0, mesh_registry.vertex_buffer.as_ref().clone())?
|
||||
.bind_index_buffer(mesh_registry.index_buffer.as_ref().clone())?;
|
||||
.bind_vertex_buffers(
|
||||
0,
|
||||
mesh_registry
|
||||
.vertex_buffer
|
||||
.as_ref()
|
||||
.expect("Vertex buffer not allocated")
|
||||
.as_ref()
|
||||
.clone(),
|
||||
)?
|
||||
.bind_index_buffer(
|
||||
mesh_registry
|
||||
.index_buffer
|
||||
.as_ref()
|
||||
.expect("Index buffer not allocated")
|
||||
.as_ref()
|
||||
.clone(),
|
||||
)?;
|
||||
|
||||
unsafe {
|
||||
render_batches.iter().for_each(|batch| {
|
||||
@@ -470,15 +487,21 @@ impl VulkanContext {
|
||||
|
||||
builder
|
||||
.bind_vertex_buffers(1, batch.instance_buffer.clone())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// Issue one draw call per submesh so each can eventually be bound to its own
|
||||
// material. All submeshes share the mesh's instance buffer.
|
||||
for submesh in &mesh.submeshes {
|
||||
builder
|
||||
.draw_indexed(
|
||||
mesh.index_count as u32,
|
||||
submesh.index_count as u32,
|
||||
batch.instance_count,
|
||||
mesh.index_loc as u32,
|
||||
submesh.index_loc as u32,
|
||||
mesh.vertex_loc as i32,
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -39,15 +39,19 @@ impl AssetManager {
|
||||
///
|
||||
/// This method will attempt to auto-generate IDs for materials and textures if they do not have
|
||||
/// any preconfigured, using the ID of the mesh and the type of resource being stored.
|
||||
pub fn load_model<P: AsRef<Path>>(&mut self, mesh_id: String, path: P) -> Result<()> {
|
||||
pub fn load_model<P: AsRef<Path>>(&mut self, _mesh_id: String, path: P) -> Result<()> {
|
||||
// Load and register all materials first - submeshes will reference these materials.
|
||||
let materials = self.material_loader.load_materials(&path)?;
|
||||
let _materials = self.material_loader.load_materials(&path)?;
|
||||
|
||||
// Load and register the mesh using the materials
|
||||
let mesh = self.mesh_loader.load_mesh(&path)?;
|
||||
self.mesh_registry.register(mesh_id, mesh);
|
||||
// Load the mesh. Each of `mesh.submeshes` carries a `material_index` local to this file,
|
||||
// which should be resolved against `materials` (e.g. via a generated
|
||||
// "{mesh_id}_mat_{index}" id) once there's a way to register it.
|
||||
let _mesh = self.mesh_loader.load_mesh(&path)?;
|
||||
|
||||
Ok(())
|
||||
// `MeshRegistry` only knows how to allocate its shared vertex/index buffers in one batch
|
||||
// via `load_scene`; there's no way yet to register a single mesh's `MeshInfo` into
|
||||
// already-allocated buffers (or grow them), so this can't build a `MeshHandle` yet.
|
||||
todo!("MeshRegistry needs support for registering a single mesh after its buffers are already allocated")
|
||||
}
|
||||
|
||||
/// Loads all information from a scene, returning an empty `Result` indicating success or failure.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::assets::loaders::mesh_loader::{MeshFileLoader, MeshInfo};
|
||||
use crate::assets::loaders::mesh_loader::{MeshFileLoader, MeshInfo, Submesh};
|
||||
use anyhow::Result;
|
||||
use gltf::buffer::Data;
|
||||
use nalgebra::{Matrix4, Vector4};
|
||||
@@ -15,7 +15,8 @@ impl GltfLoader {
|
||||
vertices: &mut Vec<[f32; 3]>,
|
||||
normals: &mut Vec<[f32; 3]>,
|
||||
uvs: &mut Vec<[f32; 3]>,
|
||||
indices: &mut Vec<u32>
|
||||
indices: &mut Vec<u32>,
|
||||
submeshes: &mut Vec<Submesh>,
|
||||
) {
|
||||
let transform = node.transform().matrix()
|
||||
.iter()
|
||||
@@ -29,6 +30,7 @@ impl GltfLoader {
|
||||
// Read the mesh and apply the current transformation to every vertex
|
||||
for primitive in mesh.primitives() {
|
||||
let vert_offset = vertices.len() as u32;
|
||||
let index_start = indices.len();
|
||||
|
||||
let reader = primitive.reader(|buffer_index| Some(&buffers[buffer_index.index()]));
|
||||
|
||||
@@ -59,8 +61,11 @@ impl GltfLoader {
|
||||
}
|
||||
}
|
||||
|
||||
primitive.material().pbr_metallic_roughness()
|
||||
.metallic_factor();
|
||||
submeshes.push(Submesh {
|
||||
material_index: primitive.material().index(),
|
||||
index_start,
|
||||
index_count: indices.len() - index_start,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +78,8 @@ impl GltfLoader {
|
||||
vertices,
|
||||
normals,
|
||||
uvs,
|
||||
indices
|
||||
indices,
|
||||
submeshes,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -87,6 +93,7 @@ impl MeshFileLoader for GltfLoader {
|
||||
let mut normals = Vec::new();
|
||||
let mut uvs = Vec::new();
|
||||
let mut indices = Vec::<u32>::new();
|
||||
let mut submeshes = Vec::new();
|
||||
|
||||
let scenes = document.scenes();
|
||||
|
||||
@@ -104,7 +111,8 @@ impl MeshFileLoader for GltfLoader {
|
||||
&mut vertices,
|
||||
&mut normals,
|
||||
&mut uvs,
|
||||
&mut indices
|
||||
&mut indices,
|
||||
&mut submeshes,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -114,6 +122,7 @@ impl MeshFileLoader for GltfLoader {
|
||||
indices,
|
||||
normals,
|
||||
uvs,
|
||||
submeshes,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,23 @@ pub struct MeshInfo {
|
||||
pub indices: Vec<u32>,
|
||||
/// The texture coordinates. Note that these may also be used on 3D textures.
|
||||
pub uvs: Vec<[f32; 3]>,
|
||||
/// The contiguous ranges of `indices` that should be drawn with a single material, in the
|
||||
/// order they were encountered in the source file.
|
||||
pub submeshes: Vec<Submesh>,
|
||||
}
|
||||
|
||||
/// A contiguous range within a [`MeshInfo`]'s `indices`, along with the material it should be
|
||||
/// drawn with. Ranges are local to a single loaded file - a [`crate::common::mesh::MeshRegistry`]
|
||||
/// rewrites these into offsets within its own shared index buffer.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Submesh {
|
||||
/// The index of the material this submesh uses, as defined by the source file (e.g. a glTF
|
||||
/// material index). `None` if the file did not assign a material to this range.
|
||||
pub material_index: Option<usize>,
|
||||
/// The offset of this submesh's first index within `MeshInfo::indices`.
|
||||
pub index_start: usize,
|
||||
/// The number of indices that make up this submesh.
|
||||
pub index_count: usize,
|
||||
}
|
||||
|
||||
pub trait MeshFileLoader {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::assets::loaders::mesh_loader::{MeshFileLoader, MeshInfo};
|
||||
use crate::assets::loaders::mesh_loader::{MeshFileLoader, MeshInfo, Submesh};
|
||||
use std::path::Path;
|
||||
use wavefront::Obj;
|
||||
|
||||
@@ -17,11 +17,20 @@ impl MeshFileLoader for ObjLoader {
|
||||
})
|
||||
.collect::<Vec<u32>>();
|
||||
|
||||
// Wavefront `.mtl` material groups aren't loaded yet, so the whole mesh is treated as a
|
||||
// single submesh with no assigned material.
|
||||
let submeshes = vec![Submesh {
|
||||
material_index: None,
|
||||
index_start: 0,
|
||||
index_count: index_list.len(),
|
||||
}];
|
||||
|
||||
Ok(MeshInfo {
|
||||
vertices: obj_info.positions().to_vec(),
|
||||
normals: obj_info.normals().to_vec(),
|
||||
indices: index_list,
|
||||
uvs: obj_info.uvs().to_vec(),
|
||||
submeshes,
|
||||
})
|
||||
}
|
||||
}
|
||||
+32
-2
@@ -19,10 +19,29 @@ pub struct MeshHandle {
|
||||
pub vertex_count: usize,
|
||||
pub index_loc: usize,
|
||||
pub index_count: usize,
|
||||
/// The submeshes that make up this mesh, in the order they should be drawn. Each one covers
|
||||
/// a contiguous range of the registry's shared index buffer and (once material loading is
|
||||
/// wired up) should be drawn with its own material bound.
|
||||
pub submeshes: Vec<SubmeshHandle>,
|
||||
}
|
||||
|
||||
impl MeshHandle {}
|
||||
|
||||
/// A submesh's location within a [`MeshRegistry`]'s shared index buffer, along with a reference
|
||||
/// to the material it was assigned in its source file.
|
||||
///
|
||||
/// Unlike [`crate::assets::loaders::mesh_loader::Submesh`], `index_loc` here is an absolute
|
||||
/// offset into the registry's shared index buffer rather than an offset local to one loaded file.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubmeshHandle {
|
||||
/// The material index this submesh was assigned in its source file, if any. This is not yet
|
||||
/// resolved to a [`crate::common::material::Material`] - that mapping is the caller's
|
||||
/// responsibility until material loading is wired into mesh registration.
|
||||
pub material_index: Option<usize>,
|
||||
pub index_loc: usize,
|
||||
pub index_count: usize,
|
||||
}
|
||||
|
||||
type VertexBuffer = Arc<Subbuffer<[Vertex3]>>;
|
||||
type IndexBuffer = Arc<Subbuffer<[u32]>>;
|
||||
|
||||
@@ -102,9 +121,19 @@ impl MeshRegistry {
|
||||
let obj_num_idx = obj.indices.len();
|
||||
buf_indices.extend(obj.indices);
|
||||
|
||||
let submeshes = obj
|
||||
.submeshes
|
||||
.into_iter()
|
||||
.map(|submesh| SubmeshHandle {
|
||||
material_index: submesh.material_index,
|
||||
index_loc: index_start + submesh.index_start,
|
||||
index_count: submesh.index_count,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
println!(
|
||||
"Loaded mesh \"{}\" with {} vertices and {} indices",
|
||||
id, obj_num_verts, obj_num_idx
|
||||
"Loaded mesh \"{}\" with {} vertices, {} indices, and {} submesh(es)",
|
||||
id, obj_num_verts, obj_num_idx, submeshes.len()
|
||||
);
|
||||
|
||||
// TODO Create bounding boxes for each mesh and associate them with the struct.
|
||||
@@ -116,6 +145,7 @@ impl MeshRegistry {
|
||||
vertex_count: obj_num_verts,
|
||||
index_loc: index_start,
|
||||
index_count: obj_num_idx,
|
||||
submeshes,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user