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

196 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](https://github.com/godotengine/FBX2glTF/issues/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](https://github.com/assimp/assimp/issues/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](https://github.com/donmccurdy/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](https://github.com/KhronosGroup/glTF/issues/1395),
[#2073](https://github.com/KhronosGroup/glTF/issues/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](https://github.com/infosia/avatar-asset-pipeline)):
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.)
### Further afield (noted, not recommended now)
- 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](https://github.com/ksmithRenweb/everything-claude-code/blob/main/skills/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](https://github.com/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](https://github.com/elithril/blender-kiln)) — 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](https://github.com/rawwerks/VibeCAD)) — 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](https://github.com/KhronosGroup/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 resample``gltf-validator` gate → optional render-to-PNG
contact sheet.