DEV Community

snake lil
snake lil

Posted on

High Roughness, Sharp Highlights: Auditing a glTF Material in Three.js

An imported car-body model still showed sharp highlights on its hood and rear fender after switching to a preset labeled Matte. The overall reflection became weaker, but some bright patches remained.

That observation prompted a material audit: what did the preset actually change, and what properties had arrived with the imported asset?

Affiliation: This case comes from WrapPrism, a project I work on.

The browser capture session and file checks described below took place on October 27, 2026.

Establishing a comparable capture sequence

The comparison used one BMW M4 G82 ADRO-kit body study in the same Chrome tab and the same initial front three-quarter view. Between captures, there was no rotation, zoom, camera reset, vehicle switch, navigation, or window resizing.

Recorded item Capture conditions
Renderer marker Three.js r186
Viewport 1470 × 775 CSS pixels; device pixel ratio 2
3D canvas 881 × 565 CSS pixels; drawing buffer 1762 × 1130
Reported camera distance 7.8000 in every capture
Environment Same RoomEnvironment / PMREM setup
Lighting configuration Hemisphere light intensity 1.2; directional light intensity 2.5 at (4, 8, 3)
Exposure 0.9
Base-color inputs #AB645B and #343B3A
Finish presets Gloss, Satin, Matte

The lighting settings were checked against the inspected application source snapshot and the script loaded by the browser. GPU uniforms were not inspected directly. Camera azimuth, elevation, and the complete Chrome version were not recorded either, so this is an application-level comparison with repeatability evidence, rather than a claim of pixel-identical reproduction across devices.

Two colors across three finishes produced six conditions. Two return-to-baseline captures brought the total to eight unmodified screenshots.

Color changes were visible on the hood, doors, and fenders, while the glass, wheels, and dark carbon components retained their appearance. Both colors showed changes between Gloss and Matte, although some sharp highlights remained under Matte.

The dark-gray sequence was Satin → Gloss → Matte → Satin. A second sequence switched away from the copper-colored Satin condition and then restored it. In both cases, the returning screenshot had the same whole-file SHA-256 hash as its corresponding baseline. That supports the absence of visible framing or scene drift within these sequences.

The original screenshots and model binary are not distributed with this article because their source-license provenance still needs clarification. The tables below summarize retained internal records; the exact asset-specific images cannot be independently reproduced from this article alone.

One preset changed more than one parameter

The finish mapping was verified in the script loaded by the browser:

Preset Base roughness Metalness
Gloss 0.16 0.24
Satin 0.42 0.24
Matte 0.86 0.02

Of these two parameters, only roughness changed between Gloss and Satin. Switching to Matte also changed metalness.

Consequently, the full three-preset comparison does not isolate roughness. Three.js defines roughness and metalness as separate material properties; metalness is not a general-purpose gloss control. MeshStandardMaterial documentation

The original rationale for these values was not established, and no physical film samples were used for calibration. They are application presets, not universal physical definitions of gloss, satin, or matte.

To check that the changes extended beyond the selected UI control, the screenshots were compared using mean absolute differences in decoded RGB values:

Comparison Mean absolute difference, 0–255
Copper Satin / dark-gray Satin 6.996
Dark-gray Satin / dark-gray Gloss 1.047
Dark-gray Gloss / dark-gray Matte 1.765
Copper Satin / copper Gloss 0.700
Copper Gloss / copper Matte 1.542
Each of the two baseline-return comparisons 0

The comparison region was the same screenshot rectangle: x = 100…959, y = 130…684. It includes background pixels, and the originals are JPEGs. These values establish image changes and returns to baseline within this capture session. They are not ΔE measurements, physical gloss readings, or material-accuracy scores.

The imported material had another reflective layer

The paint material in the actual GLB contained these extension fields:

{
  "extensions": {
    "KHR_materials_clearcoat": {
      "clearcoatFactor": 1,
      "clearcoatRoughnessFactor": 0.04
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The Khronos extension defines a clearcoat layer over the base material, with its own intensity and roughness. Setting clearcoatFactor to zero disables that layer. KHR_materials_clearcoat specification

Three.js exposes the corresponding clearcoat and clearcoatRoughness properties. A rough base can therefore have a much smoother coating above it. MeshPhysicalMaterial documentation

The r186 loader provides a concrete explanation of how those properties enter the scene. When the clearcoat extension is present, GLTFLoader selects MeshPhysicalMaterial and maps the glTF factors to its clearcoat properties. Three.js r186 GLTFLoader source

The inspected application path then clones the imported material and replaces its base roughness, metalness, and color map without resetting clearcoat. The r186 material-copy implementation preserves the clearcoat properties and associated maps. Three.js r186 material-copy source

Along that code path, a base roughness of 0.86 can coexist with a clearcoat roughness of 0.04. The base roughness value alone does not describe the complete material.

This is a plausible explanation for the remaining highlights, not an experimentally established cause. The capture session did not include a clearcoat-disabled comparison or direct inspection of the final material uniforms. Surface normals, bright regions in the environment, and other material settings can also affect the image. A preset named Matte does not imply zero reflection.

A follow-up comparison that could distinguish the causes

The next diagnostic step would keep the model, camera, color, base roughness, metalness, and environment fixed in an isolated scene:

Proposed condition Base roughness Metalness Clearcoat Clearcoat roughness
A: retain the coating 0.86 0.02 1 0.04
B: disable only the coating 0.86 0.02 0 0.04
C: increase only coating roughness 0.86 0.02 1 0.86

A/B would examine the coating's contribution. A/C would examine the effect of its roughness. These comparisons have not been performed, and no results are claimed here. The values are diagnostic choices, not calibrated settings for a real wrap film.

For a similar investigation:

  1. Record the actual material type, asset version, and glTF extensions.
  2. List every property changed by the UI action.
  3. Inspect the base and coating layers separately, including their maps and normals.
  4. Keep the camera, environment, exposure, and output pipeline fixed while changing one diagnostic variable.
  5. Return to the starting condition and capture it again.

Keep the color pipeline consistent as well. Three.js distinguishes color inputs, its linear working space, and display output. A base-color input is not a promise that the illuminated body will have the same pixel values. Color textures and roughness-data textures also require different color-space treatment. Three.js Color Management manual

The eight captures establish recoverable image changes between color and finish presets. The asset fields, loader, and copy implementation establish a documented route by which an imported coating can survive those preset changes. Whether that coating caused the particular sharp highlights remains a question for the next controlled comparison.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.