Expand description
Provides a byte-identical implementation of mikktspace
in entirely safe
and idiomatic Rust.
This is used to generate TangentSpace
values for 3D geometry.
Like the original implementation, this crate has no dependencies.
With only the default features enabled, this should produce identical
results to mikktspace
on x86 architectures.
Other architectures may produce differing results due to the original
implementation’s reliance on undefined behavior.
§Usage
As a preliminary step for no_std
users, you must provide an implementation
for Ops
.
When the std
feature is enabled, one is provided and automatically selected
as the default.
First, implement Geometry
for your geometry.
impl Geometry for MyGeometry { /* ... */ }
The interface is how this crate reads geometric information and writes back the generated tangent space information.
Finally, use generate_tangents
to calculate and write back all
TangentSpace
values.
§Description
The code is designed to consistently generate the same tangent spaces, for a given mesh, in any tool in which it is used. This is done by performing an internal welding step and subsequently an order-independent evaluation of tangent space for meshes consisting of triangles and quads. This means faces can be received in any order and the same is true for the order of vertices of each face. The generated result will not be affected by such reordering. Additionally, whether degenerate (vertices or texture coordinates) primitives are present or not will not affect the generated results either.
Once tangent space calculation is done the vertices of degenerate primitives will simply inherit tangent space from neighboring non degenerate primitives. The analysis behind this implementation can be found in Morten S. Mikkelsen’s master’s thesis.
Note that though the tangent spaces at the vertices are generated in an order-independent way, by this implementation, the interpolated tangent space is still affected by which diagonal is chosen to split each quad. A sensible solution is to have your tools pipeline always split quads by the shortest diagonal. This choice is order-independent and works with mirroring. If these have the same length then compare the diagonals defined by the texture coordinates. XNormal, which is a tool for baking normal maps, allows you to write your own tangent space plugin and also quad triangulator plugin.
§Features
§std
(default)
Provides access to the standard library, allowing a default implementation
of Ops
to be provided.
If you disable this feature, you will need to provide a type implementing
Ops
as the O
parameter in the Geometry
trait.
impl Ops for MyOps {
fn sqrt(x: f32) -> f32 {
unimplemented!()
}
fn acos(x: f32) -> f32 {
unimplemented!()
}
}
impl Geometry<MyOps> for MyGeometry { /* ... */ }
A common backend for implementing Ops
is libm
:
impl Ops for LibmOps {
fn sqrt(x: f32) -> f32 {
libm::sqrtf(x)
}
fn acos(x: f32) -> f32 {
libm::acos(x as f64) as f32
}
}
Note that alternate backends may break byte-compatibility with the original C implementation. It also should go without saying that improper implementations could give entirely incorrect results.
§corrected-edge-sorting
Fixes a known bug in the original C implementation which can affect the generated values. The bug can cause edges to be improperly sorted, leading neighboring faces to be ungrouped. If you do not need byte compatibility with the C implementation, it is recommended to enable this feature for improved performance, compile times, and correctness.
§corrected-vertex-welding
Fixes a known bug in the original C implementation which can affect the generated values. This bug causes vertices to be combined in such a way that the lowest vertex index isn’t reliably selected. If you do not need byte compatibility with the C implementation, it is recommended to enable this feature for improved performance, compile times, and correctness.
§Copyright
This code is a Rust reimplementation of https://github.com/mmikk/MikkTSpace. The copyright notice below reflects that history, and should not be removed.
Copyright (C) 2011 by Morten S. Mikkelsen
This software is provided ‘as-is’, without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
- This notice may not be removed or altered from any source distribution.
The above notice will also be included in any derivative source files.
§Advice
To avoid visual errors (distortions/unwanted hard edges in lighting), when
using sampled normal maps, the normal map sampler must use the exact inverse
of the pixel shader transformation.
The most efficient transformation we can possibly do in the pixel shader is
achieved by using, directly, the “unnormalized” interpolated tangent, bitangent
and vertex normal: vT
, vB
and vN
.
// pixel shader (fast transform out)
vNout = normalize(vNt.x * vT + vNt.y * vB + vNt.z * vN);
where vNt
is the tangent space normal.
The normal map sampler must likewise use the interpolated and “unnormalized” tangent, bitangent and vertex normal to be compliant with the pixel shader.
// sampler does (exact inverse of pixel shader):
float3 row0 = cross(vB, vN);
float3 row1 = cross(vN, vT);
float3 row2 = cross(vT, vB);
float fSign = dot(vT, row0)<0 ? -1 : 1;
vNt = normalize(fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)));
where vNout
is the sampled normal in some chosen 3D space.
Should you choose to reconstruct the bitangent in the pixel shader instead of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also.
Finally, beware of quad triangulations. If the normal map sampler doesn’t use the same triangulation of quads as your renderer then problems will occur since the interpolated tangent spaces will differ even though the vertex level tangent spaces match. This can be solved either by triangulating before sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. However, this must be used both by the sampler and your tools/rendering pipeline.
Structs§
- StdOps
- Implements
Ops
using the standard library. This is the recommended default when thestd
feature is enabled, as it it designed to identically match the results provided by the original mikktspace C library. - Tangent
Space - Wraps the relevant results generated when calculating the tangent space for a particular vertex on a particular face.
Enums§
- Generate
Tangent Space Error - Error returned when failing to generate tangent spaces for a geometry.
Traits§
- Geometry
- Provides an interface for reading vertex information from geometry, and writing back out the calculated tangent space information.
- Ops
- Provides the math operations required by the tangent space algorithm but which
aren’t included in Rust’s
core
crate. With thestd
feature enabled, a (default) implementation is provided.
Functions§
- generate_
tangents - Generates
TangentSpace
s for the provided geometry with the grouping threshold disabled. - generate_
tangents_ with_ threshold - Generates
TangentSpace
s for the provided geometry with a providedthreshold
for vertex grouping.