Visitar URL original
fix: Honour the clearcoat and sheen gloss invert flags in the glTF exporter by willeastcott · Pull Request #9837 · playcanvas/engine · GitHub
Skip to content

fix: Honour the clearcoat and sheen gloss invert flags in the glTF exporter - #9837

Merged
mvaligursky merged 2 commits into
mainfrom
claude/stoic-robinson-83cce7
Oct 27, 2026
Merged

mvaligursky merged 2 commits into
mainfrom
claude/stoic-robinson-83cce7

Conversation

@willeastcott

@willeastcott willeastcott commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Fix inverted clearcoat and sheen roughness in the glTF exporter

The glTF exporter wrote clearCoatGloss and sheenGloss unchanged as clearcoatRoughnessFactor and sheenRoughnessFactor. That is only correct for materials that enable clearCoatGlossInvert or sheenGlossInvert, as the glTF importer does. Materials authored with gloss exported the inverse of their roughness. The checks that omit the glTF default roughness of 0 also tested the gloss, not the roughness. So the default smooth clearcoat was exported as fully rough, and the default rough sheen as smooth. The base material's gloss was already converted correctly.

Changes

  • Clearcoat and sheen roughness are now invert ? gloss : 1 - gloss, as for the base material. The factor is omitted when that roughness is 0.
  • A black sheen color is no longer exported, because it disables the sheen layer. Every sheen the exporter writes now includes its color, so a round trip doesn't depend on how an importer defaults a missing one.
  • clearCoatGlossMap and sheenGlossMap are still exported unchanged as the roughness textures. The exporter converts each texture once and never rewrites texels per slot; the base glossMap isn't exported at all. The exporter now warns when it exports one of these maps from a material that treats it as gloss.
  • The GltfExporter#build docs describe this limitation.

Behavior change

Materials imported from glTF export as before, except for a black sheen. Materials authored with gloss, with the invert flags off, now export their actual roughness:

Material Before After
clearCoatGloss 0.8 clearcoatRoughnessFactor 0.8 clearcoatRoughnessFactor 0.2
clearCoatGloss 1 (default, smooth) clearcoatRoughnessFactor 1 omitted (glTF default 0)
sheenGloss 0 (default, rough) omitted (glTF default 0) sheenRoughnessFactor 1
sheen black, with a roughness or a sheen map KHR_materials_sheen without a color no KHR_materials_sheen

This is independent of #9836, which fixes the sheen shading, and consistent with it: both follow the documented meaning of sheenGloss, where 0 is rough.

Tests

New cases in test/extras/exporters/gltf-exporter.test.mjs, for both KHR_materials_clearcoat and KHR_materials_sheen:

  • A gloss of 0.8 with the invert flag off exports a roughness of 0.2, and 0.3 with the flag on exports 0.3.
  • The default roughness of 0 is omitted.
  • A gloss map is attached as the roughness texture, with a warning only when the flag is off.
  • Round trip: exporting and re-importing through GlbParser gives the same roughness for gloss 0.8, inverted 0.3, 1 and 0.

For the sheen color, a black sheen is omitted, and black, white and colored sheens re-import with the color they render with.

12 of the 22 new cases fail without the fix. The black sheen cases pass on main, which omitted that sheen, and guard the regression that converting the default gloss to roughness would otherwise cause. Full suites pass: 4487 on Node, 4486 on Dawn (test:webgpu). Lint and docs pass.

Not changed here

  • Texture channels aren't remapped. glTF reads clearcoat from R, clearcoat roughness from G, sheen roughness from A and metalness from B. The engine defaults clearCoatMapChannel, sheenGlossMapChannel and metalnessMapChannel to G, so those maps on materials authored in the engine export with data in the wrong channel.
  • The importer still defaults a missing sheenColorFactor to white, where glTF's default is black. That's a separate fix. Files from this exporter aren't affected, as every sheen it writes now includes its color.

🤖 Generated with Claude Code

…porter

The exporter wrote clearCoatGloss and sheenGloss unchanged as the
clearcoat and sheen roughness factors. That is only correct for
materials that enable clearCoatGlossInvert or sheenGlossInvert, as
materials imported from glTF do. Materials authored with gloss exported
the inverse of their roughness. The checks that omit the glTF default
roughness of 0 also tested the gloss, so the default smooth clearcoat
was exported as fully rough and the default rough sheen as smooth.

Convert the gloss to roughness unless the flag is set, as the exporter
already does for the base gloss, and omit the factor when the converted
roughness is 0.

The clearcoat and sheen gloss maps are still exported unchanged as
roughness textures, because the exporter doesn't convert texels. It now
warns when it exports a map from a material that treats it as gloss,
and the build() documentation describes this limitation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Build size report

This PR changes the size of the minified bundles.

Bundle Minified Gzip Brotli
playcanvas.min.js 2753.3 KB (+131 B, +0.00%) 715.8 KB (+66 B, +0.01%) 555.6 KB (+455 B, +0.08%)
playcanvas.min.mjs 2750.0 KB (+131 B, +0.00%) 714.5 KB (+76 B, +0.01%) 555.3 KB (+215 B, +0.04%)

@mvaligursky mvaligursky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review by Codex (OpenAI AI agent), posted through the account holder's authorized automated review process.

Reviewed 06e3d3a46 against 8c40ec6c6. One P2 round-trip regression is detailed inline. The scalar roughness conversions and zero-default omission are otherwise correct. I treated the explicitly documented texel conversion/channel limitations as existing scope limits, not additional findings.

The black-sheen importer defect is also documented, but the changed extension-emission condition newly exposes it for black sheen with the default gloss. A full GltfExporter.build → GlbParser.parse probe confirms that this case previously re-imported with sheen disabled and now re-imports with white sheen enabled.

Validation: 4,483 unit tests passing (43 pending), 4,482 WebGPU tests passing (39 pending), build/type checks, all 81 bundle tests, changed-file ESLint, and diff whitespace checks passed. Running the exporter tests against the base exporter reproduced the reported 12 failures. The full GLB round trip also confirmed the intended clearcoat 0.8 gloss → 0.2 roughness and default white-sheen gloss → roughness 1 conversions.

Comment thread src/extras/exporters/gltf-exporter.js
A black sheen color disables the sheen layer, in glTF as in the engine.
The exporter still wrote KHR_materials_sheen for a black sheen with a
nonzero roughness or a sheen map, leaving out the color because black is
its glTF default. With the gloss now converted to roughness, that
includes the default sheen gloss. The glTF parser defaults a missing
sheen color to white, so such a material came back from an export and
import with a white sheen.

Skip the extension for a black sheen. Every sheen the exporter writes
now includes its color, so a round trip no longer depends on how the
importer defaults a missing one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@mvaligursky mvaligursky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review by Codex (OpenAI AI agent), posted through the account holder's authorized automated review process.

Re-reviewed the updated PR at 0002c6643, including the response to the previous P2. That finding is addressed: a black sheen now omits the extension, while every exported non-black sheen includes its color. This avoids the importer's acknowledged missing-color default without requiring that separate importer fix. The shader multiplies the sheen color by its texture/vertex color, so omitting a black sheen is consistent with its disabled contribution.

Verified the complete GLB build/parse round trip: black sheen now re-imports with useSheen: false; white sheen remains enabled with the expected roughness. Both new black-sheen regression tests fail against the previous exporter commit (06e3d3a46) and pass on the updated head. The white/colored sheen and clearcoat/sheen roughness round-trip tests also pass.

Validation: 4,487 unit tests, 4,486 real-WebGPU tests, and 81 bundle tests passed; build, declaration/type tests, changed-file ESLint, and diff whitespace checks passed. No new actionable findings. The documented texture inversion/channel-remapping limitations remain outside this fix.

@mvaligursky
mvaligursky merged commit 3316ff7 into main Oct 27, 2026
11 checks passed
@mvaligursky
mvaligursky deleted the claude/stoic-robinson-83cce7 branch October 27, 2026 11:00
willeastcott added a commit that referenced this pull request Oct 27, 2026
…sion

Main's clearcoat and sheen gloss export fix (#9837) appended tests to
the end of the exporter suite, where the diffuse transmission tests
are. Both sets are kept, with the imports of both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

2 active deployments
Preview – engine — 0002c664 Deployed Oct 27, 2026 by vercel[bot]
Preview – engine-api-docs — 0002c664 Deployed Oct 27, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants