Add flat shading support - #9191
Conversation
Adds Material#flatShading, which shades each fragment using the geometric normal of its triangle instead of the normal interpolated from the vertex normals, giving the surface a faceted look. It is implemented from the screen space derivatives of the world position in a new flatNormalPS chunk, and so it is exact, works on skinned and morphed geometry at no additional cost, and does not need the mesh to supply vertex normals. The normal is oriented to match the triangle winding, keeping flat shading orthogonal to cull, frontFace and two sided lighting. The property is backed by a FLAT_SHADING define, which ShaderMaterial shaders can handle themselves using the same chunk.
Public API reportThis PR changes the public API surface (+6 / −0), per the docs' rules (@ignore / @Private / undocumented are excluded). Show API diff+Material.get flatShading(): boolean
+Material.set flatShading(value: boolean)
+ShaderMaterial.get flatShading(): boolean
+ShaderMaterial.set flatShading(value: boolean)
+StandardMaterial.get flatShading(): boolean
+StandardMaterial.set flatShading(value: boolean)Informational only — this never fails the build. |
Build size reportThis PR changes the size of the minified bundles.
|
mvaligursky
left a comment
There was a problem hiding this comment.
Automated PR review by Codex (GPT-5).
I found one blocking WebGPU compatibility issue in the advertised normal-free mesh path. Beyond that finding, I reviewed the public API and serialization plumbing, material define propagation/copying, GLSL/WGSL chunk integration, front-face and render-target sign handling, ShaderMaterial usage, pass behavior, and hot-path/variant impact.
Validation performed: all current CI checks are green; the focused StandardMaterial suite passes (22 tests); the StandardMaterial and ShaderMaterial examples compile and render on WebGL2 and WebGPU; and I exercised a positions/indices-only mesh on both backends. The latter exposes the inline issue below.
The lit shader still declares the vertex_normal attribute for flat shaded variants, as the tangent, GGX and area light paths depend on it, so the documented guarantee was not delivered. Drop the claim from the property docs and both chunks, and note the limitation instead.
mvaligursky
left a comment
There was a problem hiding this comment.
Automated re-review by Codex (GPT-5) of changes through dfcb1234d1ea8f05bc5cd30fc40411daaf81ab1a.
I reviewed the response and the three-file follow-up diff. The clarification is correct: a lit shader requiring vertex_normal for a normal-free mesh is pre-existing behavior on main, not a regression introduced by flatShading. The narrower fix is clean: the public JSDoc no longer promises support for meshes without normals, and both shader chunks explicitly document the current limitation. The broader vertex-normal dependency refactor is reasonably left to a dedicated, cross-backend change. The previous P1 thread is therefore resolved.
One non-code item remains: please update the PR description before merging. It still says that flat shading “does not need the mesh to supply vertex normals at all,” which now contradicts both the implementation and the updated chunk comments.
No additional code findings. The follow-up diff passes git diff --check, and all current CI checks are green.
Adds flat shading, resolving #239. When enabled, each fragment is shaded using the geometric normal of its triangle instead of the normal interpolated from the vertex normals, giving the surface a faceted look.
The normal is taken from the screen space derivatives of the world position. This is exact rather than an approximation - both derivatives lie in the plane of the triangle, so their cross product is the normal of that plane - and it works on skinned and morphed geometry at no additional cost, as it operates on the final world space positions. It also does not need the mesh to supply vertex normals at all.
The returned normal is oriented to match the triangle winding, so it agrees with correctly authored vertex normals on both front and back faces. Flat shading therefore only changes the faceting:
cull,frontFaceandtwoSidedLightingall behave as they do for smooth shading.Changes:
flatNormalPSchunk (GLSL and WGSL) providinggetFlatNormal(worldPos)Material#flatShading, backed by aFLAT_SHADINGdefine. As all material types resolve their defines throughShaderUtils.getCoreDefines, this needs no lit option plumbing, and it reachesStandardMaterial,LitMaterialandShaderMaterialaliketbnBasisuniform declaration inTBN.jsis now behind an#ifndefguard so it can be shared with the new chunktbnBasiscompensates for the flipY projection flip and WebGPU's Y-down framebuffer space, thengl_FrontFacing/pcFrontFacingrestores the winding orientationAPI Changes:
Material#flatShading(boolean, defaults tofalse). As with other material properties, callMaterial#update()after changing itflatShadingto the standard material asset parametersflatNormalPSshader chunk, usable fromShaderMaterialshaders via#include "flatNormalPS"together with#ifdef FLAT_SHADINGExamples:
graphics/asset-viewer- flat shading toggle, exercising it across transmission, volume, IOR, iridescence, sheen and clearcoat materialsgraphics/lights- flat shading toggle on the statue and ground, showing that shadows and light cookies remain correctshaders/shader-hatch- flat shading toggle demonstrating theShaderMaterialroute. Its lighting moved from the vertex to the fragment shader, which derivatives require, so it is now evaluated per pixel