Converting glTF PBR materials from spec/gloss to metal/rough



When the Khronos Group ratified glTF 2.0 in 2017, the format included a PBR shading model based on the metallic roughness (“metal/rough”) workflow, and an extension (KHR_materials_pbrSpecularGlossiness) for the specular glossiness (“spec/gloss”) workflow.[1] Differences between the metal/rough and spec/gloss workflows are mostly subtle.[2] Broadly, the metal/rough workflow tends to be slightly more memory-efficient and performance-friendly for realtime, and the spec/gloss workflow provides slightly more artistic control.

In the following years, new glTF extensions — KHR_materials_ior and KHR_materials_specular — bridged the gap between these PBR workflows. Bringing artistic control over dielectric F0 into metal/rough, allow artists gain flexibility. At the same time, engines can enable these features conditionally and keep the performance advantages of the metal/rough workflow by default.

The metal/rough workflow has been understandably more popular in the years since, even outside of the glTF ecosystem. glTF 2.0 has continued to evolve, launching an excellent series of advanced PBR features: clearcoat, thin-surface transmission, volumetric transmission, iridescence, sheen, and more. These extensions have been widely adopted by 3D viewers and engines in the years since, improving PBR capabilites on many platforms.

Bringing nascent PBR features into a single, well-defined standard is complex: complex to define clearly and fully, and complex to implement correctly and efficiently. Requiring that these features support two PBR workflows, and wrangling the entire ecosystem to implement everyting twice, would have been painful for everyone involved.

Thankfully, Khronos didn’t do that. Recent PBR features have been designed for the metal/rough workflow, and in late 2021 the KHR_materials_pbrSpecularGlossiness extension was archived with the statement below:

Archived extensions may be useful for reading older glTF files, but they are no longer recommended for creating new files.

Updates for three.js

With three.js r147, in November 2022, support for the extension KHR_materials_pbrSpecularGlossiness will be dropped. This allows important cleanup for THREE.GLTFLoader. three.js has always used metal/rough as its PBR shading model, and the loader has historically needed to build a custom ShaderMaterial to support spec/gloss materials.

Most glTF 2.0 files already use metal/rough, and in those cases no changes are needed. However, certain sources[3] may provide spec/gloss glTF downloads for some files. When that happens, three.js users will need to convert their models from the spec/gloss workflow to metal/rough.

Converting spec/gloss to metal/rough

Fortunately, conversion from spec/gloss to metal/rough is lossless in any engine that supports KHR_materials_specular and KHR_materials_ior extensions.[4] Using the glTF Transform library, here are three ways to convert existing assets from the spec/gloss workflow.


Go to and drag your model into the viewer. You’ll see a warning that the materials are about to be converted to metal/rough. After that, just download the result with the Export button on the right hand side. That’s it!


With Node.js ≥14 installed, open a terminal run the following commands:

# install
npm install --global @gltf-transform/cli

# convert
gltf-transform metalrough input.glb output.glb

These steps will convert any spec/gloss materials in the file to metal/rough.


If you’re using JavaScript or TypeScript to process glTF models as part of a Node.js server or frontend web application, then you can use glTF Transform’s scripting API to do this conversion. After choosing the appropriate platform I/O service, run:

import { WebIO, NodeIO } from '@gltf-transform/core';
import { KHRONOS_EXTENSIONS } from '@gltf-transform/extensions';
import { metalRough } from '@gltf-transform/functions';

// configure I/O
const io = new WebIO().registerExtensions(KHRONOS_EXTENSIONS); // web
const io = new NodeIO().registerExtensions(KHRONOS_EXTENSIONS); // node.js

// read
const document = await'path/to/model.glb');

// convert
await document.transform(metalRough());

// write
const glb = io.writeBinary(document); // → Uint8Array

Cleaning up

In some situations you may find that file size increases as a result of the conversion. Larger size isn’t a result of the metal/rough workflow (files originally authored for metal/rough will generally be smaller) but can be a side effect of conservative choices made during material conversion. If that happens, here are some ways to clean things up.

First, try removing the IOR and Specular extensions from the converted file by opening it in, going to the script tab in the sidebar, and running the following script:

import { prune } from '@gltf-transform/functions';

const DENYLIST = ['KHR_materials_ior', 'KHR_materials_specular'];

for (const extension of document.getRoot().listExtensionsUsed()) {
    if (DENYLIST.includes(extension.extensionName)) {

await document.transform(prune());

If your model still looks correct, then congrats! You didn’t need some of the spec/gloss features anyway, and your model will be smaller as a result. If things don’t look right anymore, then discard your changes. Either way, you can continue to the next step.

Next, we’ll try optimizing textures, using the glTF Transform CLI.

# install
npm install --global @gltf-transform/cli

# optimize
gltf-transform mozjpeg tmp.glb tmp.glb
gltf-transform oxipng tmp.glb output.glb

The CLI will attempt to optimize textures using the mozjpeg and oxipng utilities, bundled by squoosh. Customizing compression settings (use the --help flag) may allow you to go further. Finally, you may also want to convert textures to the KTX2 format. KTX2 offers major advantages over PNG and JPEG in 3D scenes, but also requires a bit more work to manage size/quality tradeoffs in compression.


For three.js users affected by the r147 release, and anyone else interested in converting your glTF 2.0 files to the metal/rough shading model, I hope this guide has been helpful. If not, please reach out on the issue trackers for any of the projects linked.

1 Later extensions also provided a shadeless (“unlit”) model, KHR_materials_unlit.

2 I recommend Allegorithmic’s PBR Guide if you’re interested in those details.

3 Notably, Sketchfab.

4 Conversion is often lossless even without those extensions, but you’ll need to test whether the results are acceptable for your content without them.