feat: implement material loading and rendering
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4d9a0c8b43
commit
a315cbc543
7 files changed
+137
-67
No files matched your search
+32
-2
@@ -4,6 +4,7 @@ use crate::common::instance::InstanceRegistry;
|
||||
use crate::common::render_batch::RenderBatch;
|
||||
use crate::common::scene::Scene;
|
||||
use crate::gpu::instance::GpuInstance;
|
||||
use crate::gpu::material::MaterialPushConstants;
|
||||
use crate::gpu::vertex3::Vertex3;
|
||||
use crate::triangle;
|
||||
use std::error::Error;
|
||||
@@ -48,6 +49,10 @@ use winit::event::WindowEvent;
|
||||
use winit::event_loop::ActiveEventLoop;
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
/// Used for a submesh whose material couldn't be resolved (no material assigned, or nothing
|
||||
/// registered under its material ID yet).
|
||||
const DEFAULT_BASE_COLOR: [f32; 4] = [0.5, 0.5, 0.5, 1.0];
|
||||
|
||||
struct VulkanContext {
|
||||
window: Arc<Window>,
|
||||
device: Arc<Device>,
|
||||
@@ -472,13 +477,38 @@ impl VulkanContext {
|
||||
.get_mesh(&batch.mesh_id)
|
||||
.expect("Mesh does not exist in registry");
|
||||
|
||||
let mesh_id = &mesh.id;
|
||||
|
||||
builder
|
||||
.bind_vertex_buffers(1, batch.instance_buffer.clone())
|
||||
.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.
|
||||
// Issue one draw call per submesh, pushing its material's base color first so
|
||||
// each submesh is shaded independently. All submeshes share the mesh's instance
|
||||
// buffer. Submeshes without a resolved material fall back to a flat gray.
|
||||
for submesh in &mesh.submeshes {
|
||||
let material_id = submesh.material_id.as_ref();
|
||||
let base_color = material_id
|
||||
.and_then(|id| asset_manager.get_material(id))
|
||||
.map_or_else(
|
||||
|| {
|
||||
println!("Couldn't find material {material_id:?} for {mesh_id:?}");
|
||||
DEFAULT_BASE_COLOR
|
||||
},
|
||||
|material| {
|
||||
println!("Found material {material_id:?} for {mesh_id:?}, with base color {:?}", material.base_color);
|
||||
material.base_color
|
||||
}
|
||||
);
|
||||
|
||||
builder
|
||||
.push_constants(
|
||||
pipeline.layout().clone(),
|
||||
0,
|
||||
MaterialPushConstants { base_color },
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
builder
|
||||
.draw_indexed(
|
||||
submesh.index_count as u32,
|
||||
|
||||
@@ -6,7 +6,7 @@ use vulkano::buffer::Subbuffer;
|
||||
use vulkano::memory::allocator::MemoryAllocator;
|
||||
use crate::assets::loaders::material::mat_loader::MaterialLoader;
|
||||
use crate::assets::loaders::mesh_loader::MeshLoader;
|
||||
use crate::common::material::MaterialRegistry;
|
||||
use crate::common::material::{Material, MaterialRegistry};
|
||||
use crate::common::mesh::{MeshHandle, MeshRegistry};
|
||||
use crate::common::scene::Scene;
|
||||
use crate::gpu::vertex3::Vertex3;
|
||||
@@ -50,20 +50,35 @@ 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<()> {
|
||||
// TODO: load this model's materials via `self.material_loader` and register them into
|
||||
// `self.material_registry` under "{mesh_id}_mat_{index}" ids. `MeshRegistry` already
|
||||
// derives each submesh's `material_id` using that exact format, so nothing else needs to
|
||||
// change here once a real material loader exists.
|
||||
pub fn load_model<P: AsRef<Path>>(&mut self, mesh_id: &String, path: P) -> Result<()> {
|
||||
// Submeshes reference materials as "{mesh_id}_mat_{index}" (see
|
||||
// `MeshRegistry::append_mesh`), matching the index materials are registered under below.
|
||||
// Not every mesh format has a material loader yet (e.g. `.obj`), so a failure here just
|
||||
// means no materials get registered for this mesh rather than failing the whole load.
|
||||
match self.material_loader.load_materials(&path) {
|
||||
Ok(materials) => {
|
||||
for (index, info) in materials.into_iter().enumerate() {
|
||||
let material_id = format!("{mesh_id}_mat_{index}");
|
||||
self.material_registry
|
||||
.register(material_id.clone(), info.into());
|
||||
println!("Loaded material {material_id}");
|
||||
}
|
||||
}
|
||||
Err(err) => println!("No materials loaded for mesh \"{mesh_id}\": {err}"),
|
||||
}
|
||||
|
||||
let mesh = self.mesh_loader.load_mesh(&path)?;
|
||||
self.mesh_registry.register_mesh(mesh_id, mesh);
|
||||
self.mesh_registry.register_mesh(mesh_id.clone(), mesh);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Loads every mesh referenced by a [`Scene`] and allocates the buffers needed to render them.
|
||||
pub fn load_scene(&mut self, scene: &Scene) -> Result<()> {
|
||||
self.mesh_registry.load_scene(scene, self.allocator.clone());
|
||||
for (id, path) in &scene.mesh_paths {
|
||||
self.load_model(id, path)
|
||||
.expect("Couldn't load mesh file");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -97,4 +112,10 @@ impl AssetManager {
|
||||
pub fn meshes(&self) -> &HashMap<String, MeshHandle> {
|
||||
&self.mesh_registry.meshes
|
||||
}
|
||||
|
||||
/// Looks up a registered material by its ID (e.g. a [`crate::common::mesh::SubmeshHandle`]'s
|
||||
/// `material_id`).
|
||||
pub fn get_material(&self, material_id: &String) -> Option<&Material> {
|
||||
self.material_registry.get(material_id)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,62 @@
|
||||
use crate::assets::loaders::material::mat_loader::{MaterialFileLoader, MaterialInfo};
|
||||
use crate::assets::loaders::material::mat_loader::{MaterialFileLoader, MaterialInfo, MaterialUniforms};
|
||||
use gltf::image::Source;
|
||||
use gltf::texture::Texture;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct GltfMaterialLoader;
|
||||
|
||||
impl MaterialFileLoader for GltfMaterialLoader {
|
||||
fn load_materials(&self, path: &Path) -> anyhow::Result<Vec<MaterialInfo>> {
|
||||
let (document, buffers, images) = gltf::import(path)?;
|
||||
|
||||
for material in document.materials() {
|
||||
|
||||
impl GltfMaterialLoader {
|
||||
/// Returns a stable identifier for a texture's image, meant to be resolved by a texture
|
||||
/// loader later. External images are identified by their URI; images embedded in the file
|
||||
/// itself (e.g. in a `.glb`) have no URI, so they're identified by their image index instead.
|
||||
fn texture_id(texture: &Texture) -> String {
|
||||
let image = texture.source();
|
||||
match image.source() {
|
||||
Source::Uri { uri, .. } => uri.to_string(),
|
||||
Source::View { .. } => format!("embedded_image_{}", image.index()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for scene in document.scenes() {
|
||||
for node in scene.nodes() {
|
||||
impl MaterialFileLoader for GltfMaterialLoader {
|
||||
fn load_materials(&self, path: &Path) -> anyhow::Result<Vec<MaterialInfo>> {
|
||||
let (document, _buffers, _images) = gltf::import(path)?;
|
||||
|
||||
}
|
||||
}
|
||||
// Collected in document order, so a material's position here matches the
|
||||
// `primitive.material().index()` that mesh loading recorded for its submeshes.
|
||||
Ok(document
|
||||
.materials()
|
||||
.enumerate()
|
||||
.map(|(index, material)| {
|
||||
let pbr = material.pbr_metallic_roughness();
|
||||
|
||||
// We just want to read through the information to see what kinds of materials are available.
|
||||
// I'm not sure how gltf defines materials, especially if they're applied to specific sets
|
||||
// of vertices.
|
||||
let name = material
|
||||
.name()
|
||||
.map(str::to_string)
|
||||
.unwrap_or_else(|| format!("material_{index}"));
|
||||
|
||||
todo!()
|
||||
let base_color_map = pbr
|
||||
.base_color_texture()
|
||||
.map(|info| Self::texture_id(&info.texture()));
|
||||
let roughness_map = pbr
|
||||
.metallic_roughness_texture()
|
||||
.map(|info| Self::texture_id(&info.texture()));
|
||||
let normal_map = material
|
||||
.normal_texture()
|
||||
.map(|info| Self::texture_id(&info.texture()));
|
||||
|
||||
MaterialInfo {
|
||||
name,
|
||||
uniforms: MaterialUniforms {
|
||||
base_color: pbr.base_color_factor(),
|
||||
metalness_factor: pbr.metallic_factor(),
|
||||
roughness_factor: pbr.roughness_factor(),
|
||||
},
|
||||
base_color_map,
|
||||
roughness_map,
|
||||
normal_map,
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
+16
-2
@@ -1,6 +1,5 @@
|
||||
use crate::assets::loaders::material::mat_loader::MaterialInfo;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use vulkano::memory::allocator::MemoryAllocator;
|
||||
|
||||
pub struct MaterialRegistry {
|
||||
materials: HashMap<String, Material>
|
||||
@@ -36,3 +35,18 @@ pub struct Material {
|
||||
/// The path of the base color's texture, if any.
|
||||
pub base_color_texture: Option<String>,
|
||||
}
|
||||
|
||||
impl From<MaterialInfo> for Material {
|
||||
/// Converts loader output into a storable `Material`. `MaterialInfo::roughness_map` has no
|
||||
/// equivalent here yet - glTF packs it with the metallic map, and there's no texture loading
|
||||
/// in place to make use of either.
|
||||
fn from(info: MaterialInfo) -> Self {
|
||||
Self {
|
||||
base_color: info.uniforms.base_color,
|
||||
metallic: info.uniforms.metalness_factor,
|
||||
roughness: info.uniforms.roughness_factor,
|
||||
normal_map: info.normal_map,
|
||||
base_color_texture: info.base_color_map,
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-38
@@ -1,5 +1,4 @@
|
||||
use crate::assets::loaders::mesh_loader::{MeshInfo, MeshLoader};
|
||||
use crate::common::scene::Scene;
|
||||
use crate::assets::loaders::mesh_loader::MeshInfo;
|
||||
use crate::gpu::vertex3::Vertex3;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@@ -148,42 +147,6 @@ impl MeshRegistry {
|
||||
self.rebuild_buffers();
|
||||
}
|
||||
|
||||
/// Creates a `MeshRegistry` from the given [`Scene`].
|
||||
///
|
||||
/// This method will load all the meshes indicated by the scene and allocate the required
|
||||
/// buffers. This significantly simplifies the allocation process, as the buffers only need to
|
||||
/// be allocated once.
|
||||
pub fn load_scene(&mut self, scene: &Scene, allocator: Arc<dyn MemoryAllocator>) {
|
||||
// Load all the meshes into a single map.
|
||||
println!("Loading meshes from scene");
|
||||
self.allocator = allocator;
|
||||
|
||||
let mesh_loader = MeshLoader::new();
|
||||
let meshes = scene
|
||||
.mesh_paths
|
||||
.iter()
|
||||
.map(|(id, path)| {
|
||||
(
|
||||
id.clone(),
|
||||
mesh_loader
|
||||
.load_mesh(path)
|
||||
.expect("Couldn't read wavefront file"),
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<String, MeshInfo>>();
|
||||
|
||||
self.raw_vertices.clear();
|
||||
self.raw_indices.clear();
|
||||
let mut mesh_list = HashMap::new();
|
||||
meshes.into_iter().for_each(|(id, info)| {
|
||||
let handle = Self::append_mesh(&id, info, &mut self.raw_vertices, &mut self.raw_indices);
|
||||
mesh_list.insert(id, handle);
|
||||
});
|
||||
|
||||
self.meshes = mesh_list;
|
||||
self.rebuild_buffers();
|
||||
}
|
||||
|
||||
/// (Re)allocates `vertex_buffer`/`index_buffer` from the currently accumulated
|
||||
/// `raw_vertices`/`raw_indices`.
|
||||
fn rebuild_buffers(&mut self) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod camera;
|
||||
pub mod instance;
|
||||
pub mod material;
|
||||
pub mod vertex3;
|
||||
@@ -12,11 +12,16 @@ layout(set = 0, binding = 0) uniform CameraUniform {
|
||||
vec3 position;
|
||||
} camera;
|
||||
|
||||
// Per-submesh information - MaterialPushConstants struct
|
||||
layout(push_constant) uniform MaterialPushConstants {
|
||||
vec4 base_color;
|
||||
} material;
|
||||
|
||||
layout(location = 0) out vec4 f_color;
|
||||
|
||||
void main() {
|
||||
// Default mesh color - no textures for now.
|
||||
vec3 objectColor = vec3(0.5, 0.5, 0.5);
|
||||
// No texture sampling yet - just the material's flat base color.
|
||||
vec3 objectColor = material.base_color.rgb;
|
||||
// Default light information - we aren't going to edit light information until we know more about it.
|
||||
vec3 lightPosition = vec3(-100.0, 200.0, 200.0);
|
||||
vec3 lightColor = vec3(1.0, 1.0, 1.0);
|
||||
|
||||
Reference in new issue
Block a user