generate_asset: meshgod-grade metal PBR lane — RAM-sized face cap, forced-opaque alpha, 4096 textures
- SAFETY_FACE_TARGET: flat 200k laptop mtlbvh guard -> RAM-sized (>=96GB Studios get 500k ~ fal-parity detail; TRELLIS2_MAX_BAKE_FACES override, 0 = uncapped) - _force_opaque: rewrite GLB materials to alphaMode OPAQUE after metal/ kdtree bakes — o_voxel's noisy baked alpha + min<250 BLEND auto-detect rendered solid hair as a transparent speckle veil (TRELLIS2_ALPHA_MODE=auto keeps baked alpha for glassy subjects) - --texture-size: allow 4096 m3ultra verified (seed 0, 1024_cascade, metal@500k): 156s total, 26.2GB peak, candidate_pbr 391,782 tris + 2048 PBR textures, raw master 2.19M tris — vs 94,535-face BLEND output before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0db816b55d
commit
ba4e2ad42b
@ -37,7 +37,23 @@ from trellis2.model_revisions import ( # noqa: E402
|
|||||||
TRELLIS_REVISION,
|
TRELLIS_REVISION,
|
||||||
)
|
)
|
||||||
|
|
||||||
SAFETY_FACE_TARGET = 200_000
|
def _default_safety_faces() -> int:
|
||||||
|
"""RAM-sized pre-bake face cap (MODELBEAST BENCHMARKS 2026-07-22): the
|
||||||
|
flat 200k laptop mtlbvh guard crushed 2.2M-tri decodes to ~95k-face GLBs
|
||||||
|
(fal ships ~495k from the same weights). 500k is verified crash-free on
|
||||||
|
the M3 Ultra Studio; small-memory boxes keep the safe cap. Env override:
|
||||||
|
TRELLIS2_MAX_BAKE_FACES (0 = uncapped)."""
|
||||||
|
env = os.environ.get("TRELLIS2_MAX_BAKE_FACES")
|
||||||
|
if env is not None:
|
||||||
|
return int(env) or 10_000_000
|
||||||
|
try:
|
||||||
|
mem_gb = os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES") / 1e9
|
||||||
|
except (ValueError, OSError):
|
||||||
|
mem_gb = 0
|
||||||
|
return 500_000 if mem_gb >= 96 else 200_000
|
||||||
|
|
||||||
|
|
||||||
|
SAFETY_FACE_TARGET = _default_safety_faces()
|
||||||
WATCHDOG_SIGNATURES = (
|
WATCHDOG_SIGNATURES = (
|
||||||
"non-zero size",
|
"non-zero size",
|
||||||
"BVH needs at least 8 triangles",
|
"BVH needs at least 8 triangles",
|
||||||
@ -165,6 +181,43 @@ def _export_raw(mesh, path: Path) -> dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _force_opaque(glb_path: Path) -> None:
|
||||||
|
"""Rewrite every material's alphaMode to OPAQUE in a GLB, in place.
|
||||||
|
|
||||||
|
The o_voxel Metal baker samples the decoder's alpha attr noisily (same
|
||||||
|
family as the dark-patch base-color bug): solid hair bakes with scattered
|
||||||
|
near-zero alpha texels, to_glb's min<250 auto-detect flips the material
|
||||||
|
to BLEND, and the model renders as a transparent speckle veil.
|
||||||
|
TRELLIS2_ALPHA_MODE=auto keeps the baked behaviour for genuinely
|
||||||
|
transparent subjects (glass etc.); the default assumes solid objects.
|
||||||
|
"""
|
||||||
|
import struct
|
||||||
|
|
||||||
|
data = glb_path.read_bytes()
|
||||||
|
magic, version, _length = struct.unpack_from("<III", data, 0)
|
||||||
|
if magic != 0x46546C67: # b'glTF'
|
||||||
|
return
|
||||||
|
json_len, json_type = struct.unpack_from("<II", data, 12)
|
||||||
|
if json_type != 0x4E4F534A: # b'JSON'
|
||||||
|
return
|
||||||
|
gltf = json.loads(data[20:20 + json_len])
|
||||||
|
changed = False
|
||||||
|
for material in gltf.get("materials") or []:
|
||||||
|
if material.get("alphaMode", "OPAQUE") != "OPAQUE":
|
||||||
|
material["alphaMode"] = "OPAQUE"
|
||||||
|
material.pop("alphaCutoff", None)
|
||||||
|
changed = True
|
||||||
|
if not changed:
|
||||||
|
return
|
||||||
|
payload = json.dumps(gltf, separators=(",", ":")).encode()
|
||||||
|
payload += b" " * (-len(payload) % 4)
|
||||||
|
rest = data[20 + json_len:]
|
||||||
|
out = struct.pack("<III", magic, version, 20 + len(payload) + len(rest))
|
||||||
|
out += struct.pack("<II", len(payload), json_type) + payload + rest
|
||||||
|
glb_path.write_bytes(out)
|
||||||
|
print("Forced alphaMode=OPAQUE (TRELLIS2_ALPHA_MODE=auto keeps baked alpha)")
|
||||||
|
|
||||||
|
|
||||||
def _metal_baker_available() -> tuple[bool, str]:
|
def _metal_baker_available() -> tuple[bool, str]:
|
||||||
try:
|
try:
|
||||||
from trellis2.backends import probe_metal_backends
|
from trellis2.backends import probe_metal_backends
|
||||||
@ -273,6 +326,8 @@ def _export_pbr(mesh, path: Path, *, baker: str, target: Optional[int], texture_
|
|||||||
verbose=True,
|
verbose=True,
|
||||||
)
|
)
|
||||||
result.export(path)
|
result.export(path)
|
||||||
|
if os.environ.get("TRELLIS2_ALPHA_MODE", "opaque") != "auto":
|
||||||
|
_force_opaque(Path(path))
|
||||||
return result, pre_simplified
|
return result, pre_simplified
|
||||||
|
|
||||||
|
|
||||||
@ -437,7 +492,7 @@ def _parse_args():
|
|||||||
parser.add_argument("--pipeline-type", choices=("512", "1024", "1024_cascade"), default="512")
|
parser.add_argument("--pipeline-type", choices=("512", "1024", "1024_cascade"), default="512")
|
||||||
parser.add_argument("--seed", type=int, default=42)
|
parser.add_argument("--seed", type=int, default=42)
|
||||||
parser.add_argument("--steps", type=int)
|
parser.add_argument("--steps", type=int)
|
||||||
parser.add_argument("--texture-size", type=int, choices=(512, 1024, 2048), default=1024)
|
parser.add_argument("--texture-size", type=int, choices=(512, 1024, 2048, 4096), default=1024)
|
||||||
parser.add_argument("--background", choices=("auto", "keep"), default="auto")
|
parser.add_argument("--background", choices=("auto", "keep"), default="auto")
|
||||||
parser.add_argument("--pbr-decimation-target", type=_parse_target, default=None, metavar="none|N")
|
parser.add_argument("--pbr-decimation-target", type=_parse_target, default=None, metavar="none|N")
|
||||||
parser.add_argument("--cache-dir", type=Path)
|
parser.add_argument("--cache-dir", type=Path)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user