Skip to content

Commit

Permalink
generate tangents if missing and a normal map exists
Browse files Browse the repository at this point in the history
  • Loading branch information
jakobhellermann committed Apr 1, 2021
1 parent f520a34 commit afbe3d5
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 7 deletions.
21 changes: 14 additions & 7 deletions crates/bevy_gltf/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub enum GltfError {
ImageError(#[from] TextureError),
#[error("failed to load an asset path")]
AssetIoError(#[from] AssetIoError),
#[error("failed to generate tangents: {0}")]
GenerateTangentsError(#[from] bevy_render::mesh::GenerateTangentsError),
}

/// Loads meshes from GLTF files into Mesh assets
Expand Down Expand Up @@ -128,13 +130,6 @@ async fn load_gltf<'a, 'b>(
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute);
}

if let Some(vertex_attribute) = reader
.read_tangents()
.map(|v| VertexAttributeValues::Float4(v.collect()))
{
mesh.set_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute);
}

if let Some(vertex_attribute) = reader
.read_tex_coords(0)
.map(|v| VertexAttributeValues::Float2(v.into_f32().collect()))
Expand All @@ -146,6 +141,17 @@ async fn load_gltf<'a, 'b>(
mesh.set_indices(Some(Indices::U32(indices.into_u32().collect())));
};

if let Some(vertex_attribute) = reader
.read_tangents()
.map(|v| VertexAttributeValues::Float4(v.collect()))
{
mesh.set_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute);
} else {
if primitive.material().normal_texture().is_some() {
mesh.generate_tangents()?;
}
}

let mesh = load_context.set_labeled_asset(&primitive_label, LoadedAsset::new(mesh));
primitives.push(super::GltfPrimitive {
mesh,
Expand All @@ -155,6 +161,7 @@ async fn load_gltf<'a, 'b>(
.and_then(|i| materials.get(i).cloned()),
});
}

let handle = load_context.set_labeled_asset(
&mesh_label(&mesh),
LoadedAsset::new(super::GltfMesh { primitives }),
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_render/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ anyhow = "1.0"
hex = "0.4.2"
hexasphere = "3.2"
parking_lot = "0.11.0"
mikktspace = { git = "https://github.com/jakobhellermann/mikktspace-glam", package = "mikktspace-glam" }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
spirv-reflect = "0.2.3"
Expand Down
138 changes: 138 additions & 0 deletions crates/bevy_render/src/mesh/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,16 @@ pub enum Indices {
U32(Vec<u32>),
}

impl Indices {
/// Returns the number of indices.,
pub fn len(&self) -> usize {
match self {
Indices::U16(vec) => vec.len(),
Indices::U32(vec) => vec.len(),
}
}
}

impl From<&Indices> for IndexFormat {
fn from(indices: &Indices) -> Self {
match indices {
Expand Down Expand Up @@ -369,6 +379,16 @@ impl Mesh {

attributes_interleaved_buffer
}

/// Generate tangents for the mesh using the `mikktspace` algorithm.
///
/// Sets the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful.
/// Requires a [`PrimitiveTopology::TriangleList`] topology and the [`Mesh::ATTRIBUTE_POSITION`], [`Mesh::ATTRIBUTE_NORMAL`] and [`Mesh::ATTRIBUTE_UV_0`] attributes set.
pub fn generate_tangents(&mut self) -> Result<(), GenerateTangentsError> {
let tangents = generate_tangents_for_mesh(self)?;
self.set_attribute(Mesh::ATTRIBUTE_TANGENT, tangents);
Ok(())
}
}

fn remove_resource_save(
Expand Down Expand Up @@ -527,3 +547,121 @@ fn update_entity_mesh(
render_pipelines.bindings.vertex_attribute_buffer = Some(vertex_attribute_buffer_resource);
}
}

struct MikktspaceGeometryHelper<'a> {
indices: &'a Indices,
positions: &'a Vec<[f32; 3]>,
normals: &'a Vec<[f32; 3]>,
uvs: &'a Vec<[f32; 2]>,
tangents: Vec<[f32; 4]>,
}
impl MikktspaceGeometryHelper<'_> {
fn index(&self, face: usize, vert: usize) -> usize {
let index_index = face * 3 + vert;

match self.indices {
Indices::U16(indices) => indices[index_index] as usize,
Indices::U32(indices) => indices[index_index] as usize,
}
}
}
impl mikktspace::Geometry for MikktspaceGeometryHelper<'_> {
fn num_faces(&self) -> usize {
self.indices.len() / 3
}

fn num_vertices_of_face(&self, _: usize) -> usize {
3
}

fn position(&self, face: usize, vert: usize) -> [f32; 3] {
self.positions[self.index(face, vert)]
}

fn normal(&self, face: usize, vert: usize) -> [f32; 3] {
self.normals[self.index(face, vert)]
}

fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] {
self.uvs[self.index(face, vert)]
}

fn set_tangent_encoded(&mut self, tangent: [f32; 4], face: usize, vert: usize) {
let idx = self.index(face, vert);
self.tangents[idx] = tangent;
}
}

#[derive(thiserror::Error, Debug)]
/// Failed to generate tangents for the mesh.
pub enum GenerateTangentsError {
#[error("cannot generate tangents for {0:?}")]
UnsupportedTopology(PrimitiveTopology),
#[error("missing indices")]
MissingIndices,
#[error("missing vertex attributes '{0}'")]
MissingVertexAttribute(&'static str),
#[error("the '{0}' vertex attribute should have {1:?} format")]
InvalidVertexAttributeFormat(&'static str, VertexFormat),
#[error("mesh not suitable for tangent generation")]
MikktspaceError,
}
fn generate_tangents_for_mesh(mesh: &Mesh) -> Result<Vec<[f32; 4]>, GenerateTangentsError> {
match mesh.primitive_topology() {
PrimitiveTopology::TriangleList => {}
other => return Err(GenerateTangentsError::UnsupportedTopology(other)),
};

let positions = match mesh.attribute(Mesh::ATTRIBUTE_POSITION).ok_or(
GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION),
)? {
VertexAttributeValues::Float3(vertices) => vertices,
_ => {
return Err(GenerateTangentsError::InvalidVertexAttributeFormat(
Mesh::ATTRIBUTE_POSITION,
VertexFormat::Float3,
))
}
};
let normals = match mesh.attribute(Mesh::ATTRIBUTE_NORMAL).ok_or(
GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_NORMAL),
)? {
VertexAttributeValues::Float3(vertices) => vertices,
_ => {
return Err(GenerateTangentsError::InvalidVertexAttributeFormat(
Mesh::ATTRIBUTE_NORMAL,
VertexFormat::Float3,
))
}
};
let uvs = match mesh.attribute(Mesh::ATTRIBUTE_UV_0).ok_or(
GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_UV_0),
)? {
VertexAttributeValues::Float2(vertices) => vertices,
_ => {
return Err(GenerateTangentsError::InvalidVertexAttributeFormat(
Mesh::ATTRIBUTE_UV_0,
VertexFormat::Float2,
))
}
};
let indices = mesh
.indices()
.ok_or(GenerateTangentsError::MissingIndices)?;

let len = positions.len();
let tangents = vec![[0., 0., 0., 0.]; len];
let mut mikktspace_mesh = MikktspaceGeometryHelper {
indices,
positions,
normals,
uvs,
tangents,
};
let success = mikktspace::generate_tangents(&mut mikktspace_mesh);
if !success {
return Err(GenerateTangentsError::MikktspaceError);
}

Ok(mikktspace_mesh.tangents)
}

0 comments on commit afbe3d5

Please sign in to comment.