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:
- 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. - Inner Array (Cubemap Faces): Each mip level
contains an array of exactly six integers. These integers reference
indices in the glTF top-level
imagesarray 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.