Summary
The MTL parser recognises map_bump / bump / norm as tokens but the mesh path consumes only Kd, Ke, d/Tr and map_Kd — so an authored normal map is loaded and ignored.
Why it matters
The engine already has per-pixel normal-map lighting, for 2D sprites: Light2d plus a normal map gives 3D-looking dynamic lighting, and the machinery (sampling a second texture, transforming into the lighting space, feeding the shading term) exists and is tested. The lit mesh path shades from interpolated vertex normals only.
Wiring MTL normal maps into it reuses that machinery rather than inventing a second one, and it is the single biggest visual return on a low-poly model: surface detail without geometry, which is exactly what OBJ/MTL assets ship with.
Depends on #1572 — a normal map needs a tangent basis, which needs authored (or generated) vertex normals to exist first.
Proposal
- Consume
map_bump / bump / norm into the material, alongside map_Kd.
- Generate per-vertex tangents at load (from positions + UVs, the standard accumulation), or accept them if a future format supplies them.
- Sample and apply in
mesh-lit.frag / mesh-lit.wgsl, sharing the shading structure the 2D normal-map path uses so the two stay consistent rather than diverging.
bump and norm are not the same thing — bump is a height map, norm a tangent-space normal map. Decide whether to treat bump as a normal map (common, wrong, and what most exporters mean in practice) or to derive from height, and document the choice rather than leaving it implicit.
Acceptance
- A normal-mapped OBJ shows lighting detail that vertex normals alone cannot produce, under a moving
Light3d, on both GPU backends.
- A mesh with no normal map renders byte-identically to today.
References
src/loader/parsers/mtl.js, src/video/webgl/shaders/mesh-lit.frag, src/video/webgpu/shaders/mesh-lit.wgsl, and the existing 2D normal-map path for the shading convention.
Summary
The MTL parser recognises
map_bump/bump/normas tokens but the mesh path consumes onlyKd,Ke,d/Trandmap_Kd— so an authored normal map is loaded and ignored.Why it matters
The engine already has per-pixel normal-map lighting, for 2D sprites:
Light2dplus a normal map gives 3D-looking dynamic lighting, and the machinery (sampling a second texture, transforming into the lighting space, feeding the shading term) exists and is tested. The lit mesh path shades from interpolated vertex normals only.Wiring MTL normal maps into it reuses that machinery rather than inventing a second one, and it is the single biggest visual return on a low-poly model: surface detail without geometry, which is exactly what OBJ/MTL assets ship with.
Depends on #1572 — a normal map needs a tangent basis, which needs authored (or generated) vertex normals to exist first.
Proposal
map_bump/bump/norminto the material, alongsidemap_Kd.mesh-lit.frag/mesh-lit.wgsl, sharing the shading structure the 2D normal-map path uses so the two stay consistent rather than diverging.bumpandnormare not the same thing —bumpis a height map,norma tangent-space normal map. Decide whether to treatbumpas a normal map (common, wrong, and what most exporters mean in practice) or to derive from height, and document the choice rather than leaving it implicit.Acceptance
Light3d, on both GPU backends.References
src/loader/parsers/mtl.js,src/video/webgl/shaders/mesh-lit.frag,src/video/webgpu/shaders/mesh-lit.wgsl, and the existing 2D normal-map path for the shading convention.