How glTF IBL Extensions Reference Specular Cubemaps

Image-based lighting (IBL) enhances real-time rendering in glTF by simulating realistic ambient light and reflections using environment maps. Because the core glTF 2.0 specification does not natively include environment lighting, extensions such as EXT_lights_image_based define how specular cubemaps and diffuse irradiance are stored, structured, and linked within the asset's JSON hierarchy. This article explains how specular cubemaps are defined, organized across mip levels, and assigned to scenes or nodes within glTF image-based lighting workflows.

The Role of Specular Cubemaps in glTF PBR

glTF uses a metallic-roughness Physically Based Rendering (PBR) model based on the split-sum approximation for specular indirect lighting. In this workflow, a specular cubemap stores the pre-filtered environment radiance.

Different levels of surface roughness correspond to different blur levels of the environment:

  • Mip level 0 stores the sharpest reflections (roughness = 0.0).
  • Higher mip levels store progressively blurred versions of the environment filtered with a GGX microfacet distribution (roughness approaching 1.0).

Declaration via EXT_lights_image_based

The primary extension defining IBL in the glTF ecosystem is EXT_lights_image_based. To use this extension, it must first be declared in the document root:

{
  "extensionsUsed": [
    "EXT_lights_image_based"
  ]
}

The extension places an array of light definitions inside the root extensions.EXT_lights_image_based.lights object. Each entry represents an environment light with both diffuse (usually spherical harmonics) and specular components.

Structuring Cubemap Faces and Mip Levels

Unlike standard 2D textures that map directly to single image files, pre-filtered specular cubemaps require six faces across multiple mip levels. In EXT_lights_image_based, these are referenced through the specularImages property, which is organized as a two-dimensional array of image indices:

  1. Outer Array (Mip Levels): Each entry in the outer array corresponds to a mipmap level, starting from mip 0 (full resolution) up to specularMipLevels - 1.
  2. Inner Array (Cubemap Faces): Each mip level contains an array of exactly six integers. These integers reference indices in the glTF top-level images array in the standard cubemap order:
    • +X (Right)
    • -X (Left)
    • +Y (Top)
    • -Y (Bottom)
    • +Z (Front)
    • -Z (Back)

An example structure inside a light object appears as follows:

{
  "extensions": {
    "EXT_lights_image_based": {
      "lights": [
        {
          "rotation": [0.0, 0.0, 0.0, 1.0],
          "intensity": 1.0,
          "irradianceCoefficients": [ /* Spherical harmonics */ ],
          "specularImageSize": 512,
          "specularImages": [
            [0, 1, 2, 3, 4, 5],     // Mip level 0 (512x512)
            [6, 7, 8, 9, 10, 11],   // Mip level 1 (256x256)
            [12, 13, 14, 15, 16, 17] // Mip level 2 (128x128)
          ]
        }
      ]
    }
  }
}

The specularImageSize property explicitly states the edge resolution of mip level 0 in pixels, ensuring runtimes allocate the appropriate cubemap texture dimensions.

Referencing the Cubemap in the Scene

Defining the cubemap does not activate it automatically. To apply the environment light to a scene, a reference is added under the scene definition pointing to the target light by its array index:

{
  "scenes": [
    {
      "name": "MainScene",
      "extensions": {
        "EXT_lights_image_based": {
          "light": 0
        }
      }
    }
  ]
}

Assigning the light to a scene establishes a global environment map. Alternatively, attaching the extension to specific nodes allows environment maps to move or rotate dynamically with the node hierarchy.

Modern Implementations and KTX2 Containers

While discrete image arrays defined in EXT_lights_image_based allow broad compatibility with standard PNG and JPEG loaders, modern engines frequently prefer compressed GPU textures.

When paired with KHR_texture_basisu, a specular cubemap can be bundled into a single .ktx2 container. In this setup, all six faces and complete mip chains reside in a single file or buffer view, streamlining memory layout and avoiding dozens of individual image references in the glTF JSON structure.