LOD: fix inverted normals on decimated meshes + rim protection
Root cause of the user-reported artifacts (stripes disappearing on the far LOD): normals were RECOMPUTED from the surviving faces after the collapse. uv_sphere is wound inward, so the recomputed normals pointed inward — the far LOD was back-face-lit (measured deviation 2.0 vs 0.0 on L0). Fix — normals are INHERITED, never recomputed: - welded() now also welds normals (first-encountered per cluster) - Collapse owns the normal table; collapse_edge λ-blends + renormalizes at the same λ as the position (no seam guard: the attribute-aware weld kept hard-edge vertices separate, so no edge crosses a shading break) - compaction reads the post-collapse table instead of recomputing - the outward reorientation added earlier is removed: the source winding + normals are preserved as-is, so every LOD level is shading-compatible with L0 whatever the source orientation Rim protection (attribute-aware weld leaves UV-seam slits / pole fans as boundary rims): a face touching such a rim is never removed while interior collapses remain — strict PQ mode (edges whose incident faces are fully interior, re-validated at pop) with a best-effort fallback when the interior alone cannot reach the target. Seam-free meshes (icosahedron) stay topologically closed; sewn meshes stay geometrically complete (no hole at the slit) — tests now assert seam-column survival. Docs: gpu-driven.md §LOD, ARCHI_CPU_GPU LOD note, ROADMAP 4.3 updated with the attribute-aware weld + rim protection + inherited normals. Gate: fmt ✓, check 0 warnings ✓, 104 tests ✓, demo runs ✓. Measured (uv_sphere 32×20): normal max deviation 0.0000 on L0–L2 (was 2.0000), UV max error 0.0084 vs analytical (was 0.5000).
This commit is contained in:
+13
-7
@@ -96,11 +96,17 @@ CPU):
|
||||
automatically from level 0 by **quadric edge collapse** (Garland–Heckbert,
|
||||
`Geometry::generate_lod_levels`): edges are ranked by quadric error and collapsed
|
||||
cheapest-first; an interior collapse merges both incident triangles (−2 faces) and remaps the
|
||||
neighbours — no new face, so a **closed mesh stays closed** (no holes, no non-manifold
|
||||
"books"), a boundary collapse removes one face; duplicate corners are welded (relative
|
||||
tolerance 1e-6); a survivor **moved** by a collapse gets its UV/color **interpolated**
|
||||
between the collapsed endpoints (same λ as its new position — the texture stays attached
|
||||
to the surface and coarsens smoothly across levels, never across a UV seam), and the
|
||||
neighbours — no new face, so a **seam-free mesh stays closed** (no holes, no non-manifold
|
||||
"books") and a boundary collapse removes one face; duplicate corners are welded **aware of
|
||||
their attributes** (relative position tolerance 1e-6, merged only when the UVs are within half
|
||||
a tile and the normals within ~25° — a seam or a hard edge stays a separate corner, so a
|
||||
collapse can never cross a texture chart or a shading break; on a mesh with a UV seam the
|
||||
seam's lips are then *protected* — their faces never collapse while interior ones remain — so
|
||||
the surface stays geometrically complete); a survivor **moved** by a collapse gets its
|
||||
UV/color/normal **interpolated** between the collapsed endpoints (same λ as its new position —
|
||||
texture and shading stay attached to the surface and coarsen smoothly across levels, never
|
||||
across a UV seam; normals are **inherited from the source, never recomputed**, so the lighting
|
||||
is identical to level 0 whatever the source's winding), and the
|
||||
levels are **packed into the mesh's single
|
||||
vertex/index buffers** (see the constraint below). Level 0 is always your exact geometry.
|
||||
2. **Per frame (CPU).** For each entity, the bounding sphere used by culling is projected to screen
|
||||
@@ -219,8 +225,8 @@ and verified fixed, see the D14 note in `docs/tech/ARCHI_CPU_GPU.md`.)
|
||||
- **Mesh bounding boxes are recomputed when meshes are added**; a scene whose mesh set changes
|
||||
at runtime simply re-uploads the small bbox table (a few bytes per mesh).
|
||||
- **LOD levels are packed into the mesh's own buffers**: u16 indices cap the *total* across all
|
||||
levels at 65 535 vertices, and there are at most 4 levels. The decimation (smallest-triangle
|
||||
removal + welding) is a setup-time cost only (a few ms for thousands of triangles); the
|
||||
levels at 65 535 vertices, and there are at most 4 levels. The decimation (quadric edge collapse
|
||||
+ attribute-aware welding) is a setup-time cost only (a few ms for thousands of triangles); the
|
||||
per-frame cost is one sphere projection per entity on the CPU.
|
||||
- **LOD detail loss is visible by design** — the hysteresis dead band makes the pop rare and
|
||||
one-directional (immediate when gaining detail, delayed when losing it), but a coarse level is
|
||||
|
||||
Reference in New Issue
Block a user