diff --git a/scripts/generate_asset.py b/scripts/generate_asset.py index a8c6287..d668a67 100755 --- a/scripts/generate_asset.py +++ b/scripts/generate_asset.py @@ -212,6 +212,40 @@ def _export_pbr(mesh, path: Path, *, baker: str, target: Optional[int], texture_ faces = torch.from_numpy(simplified_faces).to(dtype=mesh.faces.dtype) pre_simplified = True + if baker == "vertex": + # Dark-patch fix (MODELBEAST BENCHMARKS 2026-07-19): the Metal texel + # sampler mangles clean decoder attrs; sample the voxel grid at mesh + # vertices (cKDTree IDW, ~0.3s) and ship linear vertex colors. + import numpy as np + import trimesh + from scipy.spatial import cKDTree + + v_np = vertices.numpy() + f_np = faces.numpy() + coords_np = mesh.coords.cpu().numpy().astype(np.float32) + if coords_np.shape[1] == 4: + coords_np = coords_np[:, 1:4] + attrs_np = mesh.attrs.float().cpu().numpy() + vsz = float(mesh.voxel_size) + origin = np.array([-0.5, -0.5, -0.5], np.float32) + tree = cKDTree(coords_np * vsz + origin + vsz * 0.5) + d, idx = tree.query(v_np, k=4, workers=-1) + w = 1.0 / (d + vsz * 0.05) ** 2 + w[d > vsz * 1.5] = 0.0 + ws = w.sum(1, keepdims=True) + has = (ws > 0).squeeze() + ws[ws == 0] = 1.0 + rgb = np.clip(((attrs_np[idx] * (w / ws)[..., None]).sum(1))[:, 0:3], 0, 1) + rgb[~has] = np.clip(attrs_np[idx[~has, 0], 0:3], 0, 1) + v_gltf = np.stack([v_np[:, 0], v_np[:, 2], -v_np[:, 1]], axis=1) + out = trimesh.Trimesh(vertices=v_gltf, faces=f_np, process=False) + rgba = np.concatenate([rgb, np.ones((len(v_gltf), 1))], 1) + out.visual = trimesh.visual.ColorVisuals( + out, vertex_colors=(rgba * 255).astype(np.uint8)) # LINEAR + out.export(str(path)) + return {"baker": "vertex", "pre_simplified": pre_simplified, + "triangles": int(len(f_np)), "vertices": int(len(v_np))} + if baker == "metal": available, reason = _metal_baker_available() if not available: @@ -394,7 +428,7 @@ def _parse_args(): parser.add_argument("image", type=Path) parser.add_argument("--output-dir", type=Path, required=True) parser.add_argument("--backend", choices=("auto", "mps", "mlx-experimental"), default="auto") - parser.add_argument("--baker", choices=("auto", "metal", "kdtree"), default="auto") + parser.add_argument("--baker", choices=("auto", "metal", "kdtree", "vertex"), default="auto") parser.add_argument("--pipeline-type", choices=("512", "1024", "1024_cascade"), default="512") parser.add_argument("--seed", type=int, default=42) parser.add_argument("--steps", type=int)