Skip to content

Commit

Permalink
Generate vertex tangents using mikktspace (#3872)
Browse files Browse the repository at this point in the history
# Objective

Models can be produced that do not have vertex tangents but do have normal map textures. The tangents can be generated. There is a way that the vertex tangents can be generated to be exactly invertible to avoid introducing error when recreating the normals in the fragment shader.

## Solution

- After attempts to get https://github.com/gltf-rs/mikktspace to integrate simple glam changes and version bumps, and releases of that crate taking weeks / not being made (no offense intended to the authors/maintainers, bevy just has its own timelines and needs to take care of) it was decided to fork that repository. The following steps were taken:
  - mikktspace was forked to https://github.com/bevyengine/mikktspace in order to preserve the repository's history in case the original is ever taken down
  - The README in that repo was edited to add a note stating from where the repository was forked and explaining why
  - The repo was locked for changes as its only purpose is historical
  - The repo was integrated into the bevy repo using `git subtree add --prefix crates/bevy_mikktspace git@github.com:bevyengine/mikktspace.git master`
  - In `bevy_mikktspace`:
    - The travis configuration was removed
    - `cargo fmt` was run
    - The `Cargo.toml` was conformed to bevy's (just adding bevy to the keywords, changing the homepage and repository, changing the version to 0.7.0-dev - importantly the license is exactly the same)
    - Remove the features, remove `nalgebra` entirely, only use `glam`, suppress clippy.
      - This was necessary because our CI runs clippy with `--all-features` and the `nalgebra` and `glam` features are mutually exclusive, plus I don't want to modify this highly numerically-sensitive code just to appease clippy and diverge even more from upstream.
- Rebase bevyengine/bevy#1795
  - @jakobhellermann said it was fine to copy and paste but it ended up being almost exactly the same with just a couple of adjustments when validating correctness so I decided to actually rebase it and then build on top of it.
- Use the exact same fragment shader code to ensure correct normal mapping.
- Tested with both https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/NormalTangentMirrorTest which has vertex tangents and https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/NormalTangentTest which requires vertex tangent generation

Co-authored-by: alteous <alteous@outlook.com>
  • Loading branch information
bwasty and alteous committed May 31, 2022
1 parent b66cfa1 commit 22e0613
Showing 1 changed file with 22 additions and 7 deletions.
29 changes: 22 additions & 7 deletions src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ pub enum GltfError {
AssetIoError(#[from] AssetIoError),
#[error("Missing sampler for animation {0}")]
MissingAnimationSampler(usize),
#[error("failed to generate tangents: {0}")]
GenerateTangentsError(#[from] bevy_render::mesh::GenerateTangentsError),
}

/// Loads glTF files with all of their data as their corresponding bevy representations.
Expand Down Expand Up @@ -250,13 +252,6 @@ async fn load_gltf<'a, 'b>(
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute);
}

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

if let Some(vertex_attribute) = reader
.read_tex_coords(0)
.map(|v| VertexAttributeValues::Float32x2(v.into_f32().collect()))
Expand Down Expand Up @@ -309,6 +304,25 @@ async fn load_gltf<'a, 'b>(
}
}

if let Some(vertex_attribute) = reader
.read_tangents()
.map(|v| VertexAttributeValues::Float32x4(v.collect()))
{
mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute);
} else if mesh.attribute(Mesh::ATTRIBUTE_NORMAL).is_some()
&& primitive.material().normal_texture().is_some()
{
bevy_log::debug!(
"Missing vertex tangents, computing them using the mikktspace algorithm"
);
if let Err(err) = mesh.generate_tangents() {
bevy_log::warn!(
"Failed to generate vertex tangents using the mikktspace algorithm: {:?}",
err
);
}
}

let mesh = load_context.set_labeled_asset(&primitive_label, LoadedAsset::new(mesh));
primitives.push(super::GltfPrimitive {
mesh,
Expand All @@ -318,6 +332,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

0 comments on commit 22e0613

Please sign in to comment.