Skip to content

Flat shade glTF primitives without vertex normals - #9193

Merged
mvaligursky merged 1 commit into
mainfrom
mv-gltf-flat-normals
Aug 19, 2026
Merged

Flat shade glTF primitives without vertex normals#9193
mvaligursky merged 1 commit into
mainfrom
mv-gltf-flat-normals

Conversation

@mvaligursky

@mvaligursky mvaligursky commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

The glTF spec requires that a primitive which omits the NORMAL attribute is rendered with flat normals (§3.7.2.1, Meshes → Overview). The engine instead generated smooth normals for it, averaging each face normal across the shared vertices, so such meshes rendered smooth.

Those primitives now get Material#flatShading enabled, which resolves the non-compliance while building on the flat shading added in #9191.

Note this changes how existing content renders: a mesh which relies on the engine's generated normals becomes faceted. The generated smooth normals are deliberately still produced, so the previous rendering is fully recoverable by setting flatShading back to false - either at runtime, or at load time through the existing material.postprocess container asset option.

Changes:

  • Triangle-mode primitives which do not supply NORMAL now render flat shaded
  • Smooth normals are still generated for the vertex buffer, so the change is reversible
  • Flat shading is applied to a clone of the material, as a material is shared between primitives and only some of them may be missing normals. Clones are shared by primitives which share a material, and cover KHR_materials_variants mappings as well as the default material
  • A primitive with no material of its own gets a flat shaded copy of the glTF default material, which is shared by the container and so must not be modified
  • Non-triangle modes are left alone, as points and lines have no face normal to evaluate
  • Both parser paths are covered by the same check. The Draco worker is unchanged, as its own normal generation is also kept

Examples:

  • test/primitive-mode - the description was a copy of the clear coat example's and has been replaced with one describing what the example actually shows
  • PrimitiveModeNormalsTest.glb regenerated from the current Khronos sample. The previous conversion was lossy: every LINE_STRIP mesh had lost its COLOR_0 and NORMAL attributes, and triangle COLOR_0 had been de-quantized to float. The sample's labels and vertex colours now render

Performance:

  • The regenerated sample asset is 1.88 MB, down from 8.46 MB

This is what we get:
Screenshot 2026-08-19 at 10 16 46

And this is what the gltf expects:
image

The glTF spec requires flat normals to be used when a primitive omits the
NORMAL attribute, but the engine generated smooth ones instead, averaging each
face normal across the shared vertices. Such primitives now get
Material#flatShading enabled, on a clone of their material so that primitives
which do supply normals are unaffected.

The smooth normals are still generated for the vertex buffer, so the previous
rendering can be restored by setting flatShading back to false.

Also regenerates the PrimitiveModeNormalsTest sample asset, whose previous
conversion had lost the COLOR_0 and NORMAL attributes of its line strips, and
replaces the example's copy-pasted clear coat description.
@github-actions

Copy link
Copy Markdown

Build size report

This PR changes the size of the minified bundles.

Bundle Minified Gzip Brotli
playcanvas.min.js 2369.2 KB (+0.5 KB, +0.02%) 608.6 KB (+0.1 KB, +0.02%) 472.7 KB (+0.1 KB, +0.02%)
playcanvas.min.mjs 2366.6 KB (+0.5 KB, +0.02%) 607.6 KB (+0.2 KB, +0.03%) 472.1 KB (+0.4 KB, +0.09%)

@mvaligursky
mvaligursky merged commit 02b847b into main Aug 19, 2026
10 checks passed
@mvaligursky
mvaligursky deleted the mv-gltf-flat-normals branch August 19, 2026 08:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant