Crate bevy_mikktspace

Crate bevy_mikktspace 

Source
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.

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:

  1. 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.
  2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
  3. 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 the std feature is enabled, as it it designed to identically match the results provided by the original mikktspace C library.
TangentSpace
Wraps the relevant results generated when calculating the tangent space for a particular vertex on a particular face.

Enums§

GenerateTangentSpaceError
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 the std feature enabled, a (default) implementation is provided.

Functions§

generate_tangents
Generates TangentSpaces for the provided geometry with the grouping threshold disabled.
generate_tangents_with_threshold
Generates TangentSpaces for the provided geometry with a provided threshold for vertex grouping.