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:

  1. Position Deltas: The engine computes the spatial derivatives of the world-space or view-space fragment position: dp1 = dFdx(position) and dp2 = dFdy(position).
  2. UV Deltas: The engine computes the texture coordinate derivatives: duv1 = dFdx(uv) and duv2 = dFdy(uv).
  3. 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)\)
  4. 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 w sign 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