donmccurdy / glTF-Transform

glTF 2.0 SDK for JavaScript and TypeScript, on Web and Node.js.
https://gltf-transform.dev
MIT License
1.3k stars 145 forks source link

Simplification removes all vertices #1325

Closed marwie closed 3 months ago

marwie commented 3 months ago

Describe the bug Calling weldPrimitive followed by simplifyPrimitive via a nodejs script removes all vertices (-100.00%) from a mesh I wasn't able to reproduce it with the same settings and same gltf-transform version using the commandline

Expected behavior Simplification should keep one triangle

Versions:

Additional context files.zip

[needle_mesh_transform]: Welding primitive of mesh "BenchSmall" → tolerance: 0.0001
weld: Tolerance thresholds: POSITION=0.00022405464649200441, NORMAL=0.05, TANGENT=0.05, TEXCOORD_0=0.0001, TEXCOORD_1=0.0001, COLOR_0=0.01
weld: 80 → 80 (+0.00%) vertices.
[needle_mesh_transform]: Simplifying primitive of mesh "BenchSmall" → ratio: 0.015625, error: 0.4666666666666667, lockBorder: true
simplify: 80 → 0 (–100.00%) vertices, error: 0.3347.
donmccurdy commented 3 months ago

My guess would be that Meshopt's simplifier does something like:

  1. Look for an edge to collapse without exceeding the error metric
  2. Merge the two vertices on that edge
  3. Remove any resulting degenerate triangles

The bench turns into one very long triangle just before it collapses, so probably meshopt thinks it can safely collapse that small edge at the end of the triangle, makes the triangle degenerate, and there's no special case for making sure not to collapse the entire mesh down to nothing.

I can think of arguments for or against that approach ... if you're choosing error ratios carefully based on each object's size in a large s cene, maybe you want it to collapse to nothing at some distance? Or maybe you want to preserve, at minimum, a triangle or a tetrahedron or something like that.

In any case, I think a change in Meshoptimizer would be required to avoid this, or else (slower...) iteratively call simplifyPrimitive(...) until you get a result in the right ballpark.

Also note that an error ratio of just 0.05 is enough to reduce the bench to a 12-triangle box:

import { MeshoptSimplifier } from 'meshoptimizer';
import { weld, simplify } from '@gltf-transform/functions';

await document.transform(
    weld({tolerance: 0.01}),
    simplify({simplifier: MeshoptSimplifier, ratio: 0.01, error: .05, lockBorder: true})
);

I suspect that more aggressive welds, fewer vertex attributes, and lower error thresholds would get you more predictable results. Also note, #802 / #868 are somewhat intended to help with discarding UVs for LOD generation.