glTF Tangent Space Without Tangent Vectors
When a 3D model in the glTF format utilizes normal maps, it relies on tangent space to orient surface details correctly. However, glTF assets frequently omit precomputed tangent vectors from their vertex attribute buffers to reduce file size and transmission bandwidth. When tangent attributes are absent, the glTF specification expects the client runtime to dynamically reconstruct the tangent-bitangent-normal (TBN) frame. This is primarily achieved through screen-space partial derivatives computed directly within the fragment shader or via CPU-side generation using standard algorithms like MikkTSpace before geometry submission.
The Role of Tangents in glTF
Normal mapping stores high-frequency directional data relative to the surface's local coordinate system, known as tangent space. This space is defined by three orthogonal vectors: the surface normal (N), the tangent (T), and the bitangent (B).
While normal vectors are mandatory for lit materials, explicit
tangent attributes (TANGENT) are optional in glTF. When
supplied, glTF tangents are represented as 4-component vectors
(vec4), where the first three components define the
directional vector and the fourth component (w) defines the
sign of handedness (+1.0 or -1.0) required to compute the bitangent via
the cross product:
\[B = (N \times T) \times T.w\]
When TANGENT data is omitted, engines must reconstruct
this coordinate system using the existing vertex normal and texture
coordinates (UVs).
Dynamic Generation via Screen-Space Derivatives
The standard real-time approach to handling missing tangents in glTF viewers is Christian Schüler’s method, which derives tangent vectors on-the-fly in the fragment shader using hardware derivatives.
In APIs like WebGL, OpenGL, and Vulkan, functions such as
dFdx and dFdy (or ddx and
ddy in DirectX/HLSL) measure how surface properties change
across adjacent screen pixels:
- Position Deltas: The engine computes the spatial
derivatives of the world-space or view-space fragment position:
dp1 = dFdx(position)anddp2 = dFdy(position). - UV Deltas: The engine computes the texture
coordinate derivatives:
duv1 = dFdx(uv)andduv2 = dFdy(uv). - Cross Products: Using the determinants of the UV
gradients, the unnormalized tangent and bitangent vectors are computed:
- \(T = (dp1 \cdot duv2.y - dp2 \cdot duv1.y)\)
- \(B = (dp2 \cdot duv1.x - dp1 \cdot duv2.x)\)
- Gram-Schmidt Orthogonalization: The derived tangent is orthogonalized against the interpolated vertex normal and normalized to produce a stable TBN matrix.
This technique removes the need to store or upload tangent buffers to the GPU entirely, enabling models to render normal maps immediately upon loading.
CPU Precomputation with MikkTSpace
Alternatively, runtimes can generate missing tangents on the CPU during model parsing before uploading mesh buffers to the GPU. The glTF 2.0 specification specifically recommends the MikkTSpace algorithm for tangent space calculations.
Because tools like Blender, Substance 3D, and Unreal Engine bake normal maps using MikkTSpace rules, precomputing missing tangents using this exact algorithm prevents visual artifacts such as seam visibility, inverted lighting, or incorrect shading across hard edges. The algorithm analyzes triangle connectivity, UV discontinuities, and smoothing groups to generate standard 4-component tangent vectors that match what would have been baked into the texture originally.
UV Orientation and Handedness Considerations
When recovering tangent space without explicit tangents, engines must respect glTF's coordinate conventions:
- The glTF texture coordinate origin
(0, 0)is located at the upper-left corner of the texture. - Handedness must account for mirrored texture mappings. In
screen-space derivation, mirrored UVs naturally invert the determinant
sign calculated from the UV partial derivatives, allowing the shader to
mirror the bitangent automatically without requiring an explicit
wsign component.
Trade-offs: Screen-Space Derivatives vs. Precomputation
| Feature | Screen-Space Derivatives (Fragment Shader) | MikkTSpace Precomputation (CPU/Compute) |
|---|---|---|
| Memory Footprint | Low (no tangent buffer stored or uploaded) | Higher (allocates an additional
vec4 per vertex) |
| Load Time | Instant | Requires CPU processing during asset ingest |
| Runtime Cost | Slight GPU fragment overhead per pixel | Free at runtime (standard vertex TBN pipeline) |
| Visual Accuracy | High, but minor variance near geometric seams | Exact match with authoring tools |