glTF Multiple UV Sets for Multi-Layered Materials

The glTF (GL Transmission Format) specification handles complex, multi-layered materials by separating texture coordinates at the vertex level and mapping them individually at the material level. By defining indexed UV channels in mesh primitives and referencing them through specific material property objects, glTF allows different texture layers—such as base color, ambient occlusion, lightmaps, and extension-driven coatings—to utilize independent UV channels and transformations. This decoupled architecture enables detailed, multi-layered surface rendering without duplicating underlying mesh geometry.

Mesh Primitive UV Definitions

In glTF, texture coordinates are stored as vertex attributes within the primitives array of a mesh. They follow a strict naming convention formatted as TEXCOORD_n, where n is an integer index starting from 0:

"attributes": {
  "POSITION": 0,
  "NORMAL": 1,
  "TEXCOORD_0": 2,
  "TEXCOORD_1": 3
}
  • TEXCOORD_0: Typically assigned to the primary UV unwrap, used for the main surface maps (base color, normal, metallic-roughness).
  • TEXCOORD_1 and beyond: Assigned to secondary layouts, often utilized for tiled detail textures, baking ambient occlusion, or decoupled lightmapping.

The glTF specification requires client implementations to support at least two UV sets (TEXCOORD_0 and TEXCOORD_1), though modern engines often support more depending on GPU hardware limits.

Material Property Mapping via texCoord

glTF links a texture to a specific UV set via the textureInfo object. Every texture slot in a glTF material—including base color, metallic-roughness, normal maps, occlusion maps, and emissive maps—contains an optional texCoord integer property.

If omitted, texCoord defaults to 0. Specifying an explicit integer tells the renderer which TEXCOORD_n attribute to read for that specific texture:

"materials": [
  {
    "name": "MultiUV_Material",
    "pbrMetallicRoughness": {
      "baseColorTexture": {
        "index": 0,
        "texCoord": 0
      }
    },
    "occlusionTexture": {
      "index": 1,
      "texCoord": 1
    }
  }
]

In this setup, the base color evaluates using the unique layout in TEXCOORD_0, while the baked ambient occlusion evaluates using the non-overlapping lightmap unwrap in TEXCOORD_1.

Multi-Layered Materials and glTF Extensions

Standard PBR in glTF core is often enhanced by extensions to represent multi-layered materials like car paint, textiles, or lacquered wood. Extensions such as KHR_materials_clearcoat, KHR_materials_sheen, and KHR_materials_transmission follow the exact same textureInfo pattern.

For example, a clearcoat layer can use a secondary UV coordinate to drive scratches or dust independently of the underlying base paint:

"extensions": {
  "KHR_materials_clearcoat": {
    "clearcoatFactor": 1.0,
    "clearcoatRoughnessTexture": {
      "index": 2,
      "texCoord": 1
    }
  }
}

Each material layer can sample whichever UV set best preserves its resolution and mapping needs.

Independent Tiling via KHR_texture_transform

To further enhance UV management without adding additional geometry data, glTF utilizes the KHR_texture_transform extension. This extension allows per-texture scaling, offset, and rotation.

When applied, it can also override the target UV channel using its own texCoord property. This allows two different texture layers to share TEXCOORD_0 at the vertex level, while sampling at completely different scales and orientations in the shader:

"baseColorTexture": {
  "index": 0,
  "texCoord": 0,
  "extensions": {
    "KHR_texture_transform": {
      "scale": [10.0, 10.0]
    }
  }
}

Technical Considerations and Best Practices

  • Attribute Overhead: Each additional TEXCOORD_n adds a VEC2 (typically 8 bytes per vertex using 32-bit floats, or 4 bytes using normalized 16-bit integers). Keep UV sets to the minimum necessary for runtime performance.
  • Fallback Behavior: If a material requests a texCoord index that does not exist in the mesh's attributes map, glTF conforming engines must default to TEXCOORD_0.
  • Shader Variants: Varying the texCoord indices across different textures can increase the number of shader permutations in some game engines, as shaders must compile dynamically to route inputs to specific UV channels.