Files
animation/docs/fbx-glb-converter-research-2026-07-30.md
T
jeremy 3ba86b2ea8 feat: clothing lane, character sources, and DCC bridges
Bulk import of the working lanes that were living untracked on the PC.

Content:
- characters/  Lena/male body lanes, bakes, texture work, run logs
- clothing/    garment pipeline, configs, gates, contract docs
- garments/    MD-authored garment sources (.zprj/.zpac)
- UAL-Lib/     Universal Animation Library 2 source (.blend/.fbx/.glb)
- tools/       blender_bridge, iclone_bridge, md_bridge, tailor, glm_agent
- docs/, plans/, dev/, .agents/plans/

Repo hygiene:
- .gitattributes: LFS now covers .blend, .zprj, .zpac, .obj, .npy and the
  Reallusion .iAvatar/.ccAvatar/.ccRestore containers. Without this the
  ~3.8 GB in this commit would land as raw blobs. .png/.jpg are left out
  on purpose — ~250 are already tracked raw and converting them would
  rewrite every one without shrinking history.
- .gitignore: exclude /accurig/ (~1 GB AccuRig program files, redistributable
  from Reallusion, nothing authored here) and /dev/null/ (git-lfs hook copies
  dropped by a `>/dev/null` redirect on Windows).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 15:55:43 -07:00

13 KiB
Raw Blame History

FBX→GLB Converter Research — what to learn from external tools & skills

Date: 2026-07-30 Scope: survey of established FBX→GLB converters, retargeting tools, and published Claude skills, cross-referenced against our pipeline (tools/cc_retarget.py, mixamo_retarget.py, kevin_retarget.py + loop_qc.py/loop_fix.py). Complements — does not replace — plans/fbx-pipeline-plan-2026-07-21.md, which this research largely validates.


TL;DR — ranked recommendations

# Change Fixes Effort Source of technique
1 Enforce quaternion neighborhood in the baker (negate q when dot(qᵢ,qᵢ₊₁)<0) latent rotation-flip pops at runtime ~10 lines Khronos glTF #1395/#2073, Magnum
2 Post-export gltf-transform resample pass (slerp-aware, tol 1e-4, lossless) dense 1800-frame bakes, GLB size 1 script hook (Node stack already in repo) glTF-Transform / keyframe-resample-wasm
3 gltf-validator JSON output as a machine gate after export silent malformed output small Khronos glTF-Validator (no published skill does this — we'd be first)
4 Unmapped-bone diagnostics + mapping-quality score before retarget silently degraded clips small blender-toolkit skill (Excellent/Good/Fair/Poor gate)
5 Explicit rest-pose alignment step (A-pose→T-pose correction) the open shoulders-~45°-off QA item medium avatar-asset-pipeline pose configs, ARP "Redefine Rest Pose", Godot Rest Fixer
6 Bake twist-bone rotation into parent instead of dropping it candy-wrapper forearms/thighs medium ufbx helper-node insight, standard practice
7 Unified retargeter + JSON rig maps (already planned as P2/FR-3) 3 divergent forks planned validated by avatar-asset-pipeline's declarative component design
8 Motion QC thresholds beyond the loop seam (ground penetration, scale drift, heading jumps) "green-verify-is-not-correct" medium blender-motion-state-inspection skill
9 Evaluate soupday cc_blender_tools importer for CC FBX stock-importer risk we already documented evaluation spike soupday docs: "do not use the standard Blender FBX importer"
10 If GLB size ever matters: meshopt (not Draco) + gltfpack-style quantization later gltfpack (12-bit rot / 16-bit trans / 30 Hz defaults)

Items 14 are cheap, independent, and zero-risk to output quality. Item 5 closes our single biggest open correctness question.


1. What the established converters teach

FBX2glTF (Facebook, archived; Godot fork)

  • Pattern worth keeping (we already follow it): correctness by evaluation, not translation — never convert FBX curves directly; sample the evaluated transform per frame. FBX2glTF bakes everything at a fixed rate exactly like our depsgraph-evaluate loop.
  • Its failure modes (GeometricTransformation pivots mishandled — fork #56; skeleton defects that made Godot abandon it) confirm Blender-headless was the right lane.
  • Blend-shape lesson: morph normals/tangents in FBX are "rarely correctly present" — if we ever carry facial morphs, don't export morph normals unless verified.

glTF-Blender-IO (what our export actually runs through)

  • Armature export is always sampled; rest pose is the baseline for the relative TRS it writes. If our armature rest ≠ the game skeleton rest, the export silently differs — reinforces the plan's QC-1 verify_target.py (pinned rig vs live game rig, tol 1e-5).
  • Known issues we should pin settings against: sampling keys all bones (#1432/#2657), stepped keys become linear (#842). Action: set export_force_sampling, export_frame_step, export_optimize_animation_size and the keep_anim_* flags explicitly per Blender version instead of trusting defaults (we already set the first and last — add the rest).

ufbx — the best-documented catalog of FBX semantics

  • Rest pose ≠ bind pose is legal FBX. Correct converters trust skin-cluster matrices (geometry_to_bone), never the node hierarchy at time 0. (assimp gets this wrong — assimp #4015 — which is why it's disqualified for rigged characters.) Relevant to our A-pose question: iClone's "T-pose export" only prepends a T-pose frame; the skin bind stays A-pose.
  • Scale-inheritance and geometric-transform mismatches with glTF are solved by inserting helper nodes — the explanation to reach for if iClone props/twist bones ever explode.
  • Baking niceties: don't re-resample tracks that are already dense (minimum_sample_rate guard); reduce keys after baking, not by blind resampling.

glTF-Transform (directly usable — Node + gltf-transform already in repo via bake_run_punch.mjs)

  • resample() is lossless keyframe dedup: drop interior keys exactly reproduced by interpolating neighbors, using the track's real mode — slerp-aware for rotations, default tolerance 1e-4 (algorithm: keyframe-resample-wasm). Perfect complement to our bake-every-frame approach; free size win, zero quality risk.
  • Compression fact: Draco compresses mesh primitives only. meshopt (EXT_meshopt_compression) also compresses animation samplers — meshopt is the only correct choice for our animation-only GLBs, if size ever matters (needs gzip/brotli outer layer to pay off; Godot must support the extension — verify before adopting).
  • Community warning (blender-kiln skill): never run gltf-transform optimize as one blob — its simplify pass can destroy assets; apply individual steps (resample, prune, dedup) sequentially.

Keyframe reduction done right — ozz-animation

  • ozz's AnimationOptimizer measures error as world-space distance at the end of the joint's child hierarchy (default tolerance 1 mm, probe distance 0.1 m, per-joint overrides). Far better than per-channel epsilons: tighten fingers/feet, loosen spine. The metric to copy if we ever add lossy reduction on top of resample().
  • gltfpack's shipping defaults — 30 Hz resample, 12-bit rotation / 16-bit translation quantization — calibrate "what players don't notice."

Quaternion continuity — a latent bug we likely have

  • glTF runtimes disagree on >180° rotation steps (lerp+normalize vs slerp, shortest-path ambiguity — Khronos #1395, #2073). Blender's matrix decomposition does not guarantee quaternion sign continuity frame-to-frame.
  • Fix at bake time in retarget_one(): keep the previous frame's quaternion per bone; if dot(prev, cur) < 0, negate cur before keyframe_insert. ~10 lines, applies to all three retargeters (or the future unified one).
  • Also: never use CUBICSPLINE samplers for rotations (overshoot — Khronos #2008). We export sampled/linear, so we're fine there.

2. Retargeting techniques

Godot's fixed-skeleton retargeting (closest published design to our Quaternius lane)

  • SkeletonProfileHumanoid + BoneMap + Rest Fixer with:
    • Overwrite Axis — rewrite all bone rests to canonical profile rests (their version of our "rig onto the clip rest" move).
    • Fix Silhouette — A-pose sources corrected toward the T-pose profile (documented limitation: can't fully fix bone roll).
    • Remove Unimportant Positions — strip position tracks from everything except root/hips. We already comply (rotation-only except pelvis/root) — validated.
    • Normalize Position Tracks — scale hip translation by source/target hips-height ratio (motion_scale). Our tscale is the same idea — validated, keep it.
  • Docs: https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/retargeting_3d_skeletons.html

Rest-pose alignment (our open weakness #1)

Every serious retargeter has an explicit rest-alignment step; we have none:

  • Auto-Rig Pro Remap: map → Redefine Rest Pose (snapshot a corrected source pose as the retarget basis) → retarget → per-bone offsets → bake. The proven order of operations.
  • avatar-asset-pipeline (infosia): declarative JSON pipelines with an A-pose↔T-pose component driven by a pose config file (quaternion per bone). Closest architectural analog to our planned pipeline/retarget/rigs/*.json — extend the rig-map JSON schema with an optional rest_correction block of per-bone quaternions applied to the source rest before computing C, and the A-pose problem becomes data, not code.
  • soupday cc_blender_tools maps skin bones by iClone ID rather than name and explicitly warns against Blender's stock FBX importer ("Automatic Bone Orientation" alters joint rotations and breaks round-tripping). It retargets armature and shape-key animation. Worth a spike: import one known-problem CC FBX both ways and diff the baked curves.

Twist bones (our weakness #2)

We currently drop *Twist01/02 rotation entirely → candy-wrapper forearms/thighs. Standard fix: compose the twist bone's captured rotation into its mapped parent (upperarm/lowerarm/thigh) before writing the target key — the twist joints exist to distribute roll, so discarding them loses real roll. (ufbx's helper-node machinery is the deep version; composing into the parent is the cheap correct-enough one for a rig with no twist joints on the target.)

  • Unity Mecanim: retarget in normalized "muscle space," then IK-correct hands/feet — the principled fix for foot slide, heavyweight.
  • ossos (Ubisoft IK-Rig style): motion as normalized IK targets re-solved on the target — the modern answer when proportions differ wildly. Prototype-quality.
  • Root Motionist add-on: hip→root channel migration pattern, if we ever need root-motion variants (currently banned in-game).

3. Published Claude skills — what exists, what to borrow

Nothing published covers our exact lane (FBX skeletal retarget → game GLB with numeric animation QC); our loop-qc agent is already ahead of public art. Reusable pieces:

  • blender-motion-state-inspection (SKILL.md) — "measure, don't eyeball" QC playbook with thresholds worth adopting into a broader motion-QC gate alongside loop-qc: ground penetration >12 cm visible; scale drift >5% = rig problem; root heading jump >30°/frame suspicious. Sample frames likely to expose errors (contact, airborne, extremes), check source integrity before blaming the retarget, report facts (frame numbers, coordinates) before verdicts.
  • blender-toolkit (kevinbadi/blender-skills) — only published skill with a real retargeting workflow. Borrow: named bone-map presets as skill assets, and a mapping-quality score (Excellent/Good/Fair/Poor by critical-bone coverage) that gates whether the pipeline may proceed autonomously — direct fit for the plan's FR-2 (reject with unmapped-bone list).
  • blender-kiln (elithril) — best-structured 3D skill: thin SKILL.md dispatcher + deep references/ files, phase-gated pipeline with mandatory state-read before each phase, scale sanity via reference dimensions (character ≈1.75 m — same trick as our 1.700 m AccuRig rule), batch manifest pattern.
  • VibeCAD render-glb (rawwerks) — GLB→PNG so the agent can see its output. Rendering 34 keyframes of a retargeted clip to PNG would partially automate the mandatory human-eyeball QC-3 step (catches upside-down/T-posed/ exploded output; still not a substitute for motion judgment).
  • anthropics/skills docx pattern — the canonical file-conversion skill structure our animation skill could adopt more fully: decision table routing by source type (Mixamo / CC / Kevin → script + bone map), explicit Gotchas section (AccuRig 4 cm offset, Dummy export, External Motion skating), pinned script invocations, and an explicit SHIP / NO-SHIP verdict at the end.
  • gltf-validator gap: no published skill or agent workflow wires Khronos glTF-Validator in as a machine-checkable gate — everyone stops at gltf-transform inspect + eyeballing. Adding gltf-validator -o json after export is cheap and genuinely novel.

4. What this validates in the existing plan

plans/fbx-pipeline-plan-2026-07-21.md is independently confirmed by this research:

  • Unified retargeter + JSON rig maps (P2/FR-3) = avatar-asset-pipeline's architecture.
  • Reject-with-unmapped-list (FR-2) = blender-toolkit's mapping-quality gate.
  • verify_target.py pinned-rig check (QC-1) = the glTF-Blender-IO rest-pose-baseline risk.
  • Intake T-pose WARN (IC-3) = every retargeter's rest-alignment prerequisite.
  • Fail-loud + atomic writes + provenance (NFR-1/2) = table stakes in every surveyed tool.

Two additions this research argues for beyond the plan:

  1. A rest_correction per-bone quaternion block in the rig-map JSON schema (turns the A-pose fix into data).
  2. A post-export normalize step in FR-1's single command: quaternion-neighborhood fix (in-baker) → gltf-transform resamplegltf-validator gate → optional render-to-PNG contact sheet.