From 0dfe85be19f80bcddcdd7232713768693dcfd35a Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Thu, 15 Apr 2021 21:06:49 +0000 Subject: [PATCH] calculate flat normals for mesh if missing (#1808) If the gltf loader encounters a mesh without normal attributes, it will duplicate the vertex attributes and compute flat normals, as defined by https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes: > **Implementation Note**: When normals are not specified, client implementations should calculate flat normals. ![image](https://user-images.githubusercontent.com/22177966/113483243-bb204880-94a2-11eb-8fa1-c4828a4882c5.png) Helps with #1802 Co-authored-by: Carter Anderson --- crates/bevy_gltf/Cargo.toml | 1 + crates/bevy_gltf/src/loader.rs | 15 ++++ crates/bevy_render/src/mesh/mesh.rs | 112 ++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+) diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index b09b4a7d68de3..1a8576e00d88b 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -24,6 +24,7 @@ bevy_render = { path = "../bevy_render", version = "0.5.0" } bevy_transform = { path = "../bevy_transform", version = "0.5.0" } bevy_math = { path = "../bevy_math", version = "0.5.0" } bevy_scene = { path = "../bevy_scene", version = "0.5.0" } +bevy_log = { path = "../bevy_log", version = "0.5.0" } # other gltf = { version = "0.15.2", default-features = false, features = ["utils", "names", "KHR_materials_unlit"] } diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 5021eff00cac2..3f4947e63772f 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -153,6 +153,21 @@ async fn load_gltf<'a, 'b>( mesh.set_indices(Some(Indices::U32(indices.into_u32().collect()))); }; + if mesh.attribute(Mesh::ATTRIBUTE_NORMAL).is_none() { + let vertex_count_before = mesh.count_vertices(); + mesh.duplicate_vertices(); + mesh.compute_flat_normals(); + let vertex_count_after = mesh.count_vertices(); + + if vertex_count_before != vertex_count_after { + bevy_log::debug!("Missing vertex normals in indexed geometry, computing them as flat. Vertex count increased from {} to {}", vertex_count_before, vertex_count_after); + } else { + bevy_log::debug!( + "Missing vertex normals in indexed geometry, computing them as flat." + ); + } + } + let mesh = load_context.set_labeled_asset(&primitive_label, LoadedAsset::new(mesh)); primitives.push(super::GltfPrimitive { mesh, diff --git a/crates/bevy_render/src/mesh/mesh.rs b/crates/bevy_render/src/mesh/mesh.rs index fa334bdd2b3d1..8ce04a7ba9a60 100644 --- a/crates/bevy_render/src/mesh/mesh.rs +++ b/crates/bevy_render/src/mesh/mesh.rs @@ -95,6 +95,13 @@ impl VertexAttributeValues { self.len() == 0 } + fn as_float3(&self) -> Option<&[[f32; 3]]> { + match self { + VertexAttributeValues::Float3(values) => Some(values), + _ => None, + } + } + // TODO: add vertex format as parameter here and perform type conversions /// Flattens the VertexAttributeArray into a sequence of bytes. This is /// useful for serialization and sending to the GPU. @@ -254,6 +261,29 @@ pub enum Indices { U32(Vec), } +impl Indices { + fn iter(&self) -> impl Iterator + '_ { + match self { + Indices::U16(vec) => IndicesIter::U16(vec.iter()), + Indices::U32(vec) => IndicesIter::U32(vec.iter()), + } + } +} +enum IndicesIter<'a> { + U16(std::slice::Iter<'a, u16>), + U32(std::slice::Iter<'a, u32>), +} +impl Iterator for IndicesIter<'_> { + type Item = usize; + + fn next(&mut self) -> Option { + match self { + IndicesIter::U16(iter) => iter.next().map(|val| *val as usize), + IndicesIter::U32(iter) => iter.next().map(|val| *val as usize), + } + } +} + impl From<&Indices> for IndexFormat { fn from(indices: &Indices) -> Self { match indices { @@ -431,6 +461,88 @@ impl Mesh { attributes_interleaved_buffer } + + /// Duplicates the vertex attributes so that no vertices are shared. + /// + /// This can dramatically increase the vertex count, so make sure this is what you want. + /// Does nothing if no [Indices] are set. + pub fn duplicate_vertices(&mut self) { + fn duplicate(values: &[T], indices: impl Iterator) -> Vec { + indices.map(|i| values[i]).collect() + } + + assert!( + matches!(self.primitive_topology, PrimitiveTopology::TriangleList), + "can only duplicate vertices for `TriangleList`s" + ); + + let indices = match self.indices.take() { + Some(indices) => indices, + None => return, + }; + for (_, attributes) in self.attributes.iter_mut() { + let indices = indices.iter(); + match attributes { + VertexAttributeValues::Float(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Int(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Uint(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Float2(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Int2(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Uint2(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Float3(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Int3(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Uint3(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Int4(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Uint4(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Float4(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Short2(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Short2Norm(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Ushort2(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Ushort2Norm(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Short4(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Short4Norm(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Ushort4(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Ushort4Norm(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Char2(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Char2Norm(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Uchar2(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Uchar2Norm(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Char4(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Char4Norm(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Uchar4(vec) => *vec = duplicate(&vec, indices), + VertexAttributeValues::Uchar4Norm(vec) => *vec = duplicate(&vec, indices), + } + } + } + + /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. + /// + /// Panics if [`Indices`] are set. + /// Consider calling [Mesh::duplicate_vertices] or export your mesh with normal attributes. + pub fn compute_flat_normals(&mut self) { + if self.indices().is_some() { + panic!("`compute_flat_normals` can't work on indexed geometry. Consider calling `Mesh::duplicate_vertices`."); + } + + let positions = self + .attribute(Mesh::ATTRIBUTE_POSITION) + .unwrap() + .as_float3() + .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`"); + + let normals: Vec<_> = positions + .chunks_exact(3) + .map(|p| face_normal(p[0], p[1], p[2])) + .flat_map(|normal| std::array::IntoIter::new([normal, normal, normal])) + .collect(); + + self.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + } +} + +fn face_normal(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> [f32; 3] { + let (a, b, c) = (Vec3::from(a), Vec3::from(b), Vec3::from(c)); + (b - a).cross(c - a).normalize().into() } fn remove_resource_save(