You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Record a video of yourself — or point a webcam at yourself live — and reproduce the performance on a mesh in the editor:
Face: per-frame facial expression → 52 ARKit-style blendshape weights → morph-target weight keyframes on the entity (the exact (time, target, weight) stream that MorphAnimationManager::setMorphWeightKeyframe already consumes — Anim: Slice B — Mesh / vertex animation (Alembic + Ogre VAT_POSE clips) #519 shipped the whole downstream pipeline: authoring, dope-sheet diamonds, timeline playback, glTF morph-weight-animation export).
Head: per-frame head pose (rotation + translation) → keyframes on the Head/Neck bone (skinned mesh) or node TRS clip (static mesh, via NodeAnimationManager).
Body: per-frame full-body pose → per-joint rotations → retargeted onto the user's rig through the existing 22-joint canonical mapping + world-frame retarget (MotionInbetween::canonicalIndexForBone + the AnimationMerger::applyMotionClip conjugation math — AI: Animation in-betweening (ONNX) #409/AI: Text-to-motion via MDM (ONNX) — research/ambitious #411 built this for exactly this shape of problem).
Live mode: webcam preview driving the mesh in real time (morph weights + manually-controlled bones), with Record writing the take into an ordinary animation clip that plays on the existing timeline and exports through the existing exporters.
This is "markerless performance capture" — the VTuber / Rokoko-Video / Move.ai feature class — built on infrastructure this codebase already has: ONNX Runtime (8 consumers today), ModelDownloader + HF hosting, canonical-skeleton retargeting, morph weight clips, worker-thread AI controllers.
Model selection & licensing (the deciding factor, as always)
Face — MediaPipe Face Landmarker (Google) — Apache-2.0 code AND models ✅
3-stage pipeline: BlazeFace face detector → 3D face landmark model (478 landmarks) → blendshape head (small MLP-Mixer) emitting 52 ARKit-compatible blendshape coefficients (jawOpen, mouthSmileLeft, eyeBlinkRight, browInnerUp, …) + a facial transformation matrix (head pose for free).
Ships as TFLite inside a .task bundle — not ONNX. The offline conversion (tf2onnx / tflite2onnx; PINTO model zoo has proven these models convert) is a first-class spike deliverable, exactly like scripts/export-rmib-onnx.py / export-triposr-onnx.py. Models are small (detector + landmarks + blendshapes ≈ a few MB total) — trivial hosting/download.
This is what the entire VTuber ecosystem runs on; quality is proven for this use case.
Rejected: DECA / EMOCA / SPECTRE (all regress the FLAME 3DMM — FLAME is research-only; the LAFAN1/AMASS/ShapeNet wall again), ARKit (iOS-only), OpenSeeFace (MIT code but weaker blendshape story).
Body — two backends, quality path + always-available permissive fallback (the SkinTokens/GeodesicVoxel house pattern)
SAM 3D Body + MHR (Meta, Nov 2025) — the quality path. Regresses full-body pose as per-joint rotations on the 127-joint Momentum Human Rig from a single image. MHR itself is Apache-2.0 (explicitly commercially licensed — Meta's SMPL replacement); the SAM 3D Body model checkpoints are under the "SAM License" (permissive with conditions + acceptable-use policy). Slice A must do the license due-diligence read (redistribution/rehosting terms for our HF models repo) and record the decision in THIRD_PARTY_AI_MODELS.md — the PBRify/UniRig precedent. A community pure-C++ ONNX port exists (SAM3DBody-cpp) proving the export + runtime path is viable. Per-image model → video = per-frame inference + temporal smoothing.
MediaPipe Pose Landmarker (Apache-2.0) + analytic IK — the fallback, always compiled. 33 3D world landmarks per frame; we solve bone rotations from landmark segment directions vs the rig's bind directions (the same change-of-basis conjugation applyMotionClip already does). Softer quality, tiny models, zero license risk. Used automatically when the SAM model is unavailable / un-downloadable / fails — algorithmUsed + fallbackReason in the report, house pattern.
Rejected: WHAM / GVHMR / TRAM / 4D-Humans (all regress SMPL, whose weights are non-commercial — Meshcapade sells the commercial license; the recurring wall), OpenPose (CMU non-commercial), FreeMoCap (AGPL).
Related but out of scope here
NVIDIA Audio2Face (open-sourced 2025) — audio → blendshapes, no camera. Would ride the same morph-weight-keyframe plumbing; belongs in its own follow-up epic.
The real risks (read before scheduling)
Qt Multimedia is a new dependency (camera + video decode). Qt 6.5+ ships an FFmpeg backend in the official binaries, but CI (aqt module list), the Debian package depends, the macOS bundle, and MinGW all need verification. Mitigate: everything lives behind a new ENABLE_MOCAP CMake flag (default OFF; ON for release builds once CI is green on all three platforms) — the ENABLE_ALEMBIC / ENABLE_ONNX precedent. A non-mocap build must print a clear "rebuild with -DENABLE_MOCAP" on every surface.
TFLite → ONNX conversion parity for the MediaPipe models is assumed, not proven — Slice A validates numerically against the Python mediapipe reference before anything else is built.
SAM License may or may not permit rehosting the converted ONNX on our HF repo — Slice A decides; if it fails the bar, the epic still ships with MediaPipe-Pose-IK as the only body backend (feature degrades, doesn't die).
Live-drive of a skinned mesh requires Bone::setManuallyControlled(true) during preview and a clean restore after — get this wrong and the entity's existing animations break. The preview path must snapshot + restore like UvUnwrap's GUI-safe entry point does.
macOS camera permission: NSCameraUsageDescription in Info.plist.in + graceful denied-permission UX.
Worker-thread AI controller: src/ImageTo3D/MeshGenController — QML_SINGLETON, inference on a worker thread, results marshalled to the main thread, progress + cancel.
Canonical skeleton mapping + retarget: MotionInbetween::canonicalIndexForBone() (22 CMU roles, Mixamo/generic/CMU name handling) and AnimationMerger::applyMotionClip (world-frame conjugation local(f) = parentWorld⁻¹ · (W · clip(f) · W⁻¹) · parentWorld · bind, standing-pose bind harvest, locked root, rotation-only keys). Body capture output must flow through this, not a new retargeter.
Morph weight keyframes: MorphAnimationManager::setMorphWeightKeyframe(name, time, weight) + the multi-clip morph weight support (Anim: Slice B — Mesh / vertex animation (Alembic + Ogre VAT_POSE clips) #519). The dope sheet, timeline, and glTF morph-weight-anim export all already work downstream of this call.
Undoable bulk edit: ComputeSkinWeightsCommand snapshot pattern — a recorded take pushes ONE undo command.
Recommended order: A → B → C → D (face ships end-to-end) → E (body) → F (live) → G (hardening). D is the first user-visible milestone; F is the headline feature.
Acceptance criteria
qtmesh mocap talk.mp4 --face --mesh face_with_arkit_targets.glb -o out.glb produces a glTF whose morph-weight animation visibly reproduces the recorded speech/expressions (verify via the existing timeline playback + glTF reimport).
qtmesh mocap dance.mp4 --body --mesh rigged.fbx -o out.glb produces a skeletal clip on the user's rig; --algo pose-ik and --no-model force the fallback; report carries algorithmUsed/fallbackReason.
GUI live mode: webcam preview drives the selected entity's morphs + head in real time at ≥15 fps on an M-series laptop; Record produces a clip that plays on the existing timeline, is undoable (single Ctrl+Z), and exports.
Head pose lands on the Head bone for skinned meshes and as node TRS for static meshes.
Non-ENABLE_MOCAP builds show a clear "rebuild with -DENABLE_MOCAP" on every surface (no crash); ENABLE_MOCAP without ONNX models degrades per-surface with clear messages; camera-permission denial is handled gracefully.
Unmatched blendshape channels are REPORTED (count + names), never silently dropped; a JSON mapping override file is honoured.
All pure-data pieces (mapper, One-Euro, head-pose solve, Pose-IK, letterbox math) have headless unit tests; ONNX-dependent tests skip gracefully without models (UniRig/PBR test pattern).
Licensing decisions recorded in THIRD_PARTY_AI_MODELS.md; models hosted on the HF repo (or documented "not yet hosted" state with graceful degradation).
Overview
Record a video of yourself — or point a webcam at yourself live — and reproduce the performance on a mesh in the editor:
(time, target, weight)stream thatMorphAnimationManager::setMorphWeightKeyframealready consumes — Anim: Slice B — Mesh / vertex animation (Alembic + Ogre VAT_POSE clips) #519 shipped the whole downstream pipeline: authoring, dope-sheet diamonds, timeline playback, glTF morph-weight-animation export).NodeAnimationManager).MotionInbetween::canonicalIndexForBone+ theAnimationMerger::applyMotionClipconjugation math — AI: Animation in-betweening (ONNX) #409/AI: Text-to-motion via MDM (ONNX) — research/ambitious #411 built this for exactly this shape of problem).This is "markerless performance capture" — the VTuber / Rokoko-Video / Move.ai feature class — built on infrastructure this codebase already has: ONNX Runtime (8 consumers today),
ModelDownloader+ HF hosting, canonical-skeleton retargeting, morph weight clips, worker-thread AI controllers.Model selection & licensing (the deciding factor, as always)
Face — MediaPipe Face Landmarker (Google) — Apache-2.0 code AND models ✅
jawOpen,mouthSmileLeft,eyeBlinkRight,browInnerUp, …) + a facial transformation matrix (head pose for free)..taskbundle — not ONNX. The offline conversion (tf2onnx / tflite2onnx; PINTO model zoo has proven these models convert) is a first-class spike deliverable, exactly likescripts/export-rmib-onnx.py/export-triposr-onnx.py. Models are small (detector + landmarks + blendshapes ≈ a few MB total) — trivial hosting/download.Body — two backends, quality path + always-available permissive fallback (the SkinTokens/GeodesicVoxel house pattern)
THIRD_PARTY_AI_MODELS.md— the PBRify/UniRig precedent. A community pure-C++ ONNX port exists (SAM3DBody-cpp) proving the export + runtime path is viable. Per-image model → video = per-frame inference + temporal smoothing.applyMotionClipalready does). Softer quality, tiny models, zero license risk. Used automatically when the SAM model is unavailable / un-downloadable / fails —algorithmUsed+fallbackReasonin the report, house pattern.Related but out of scope here
NVIDIA Audio2Face (open-sourced 2025) — audio → blendshapes, no camera. Would ride the same morph-weight-keyframe plumbing; belongs in its own follow-up epic.
The real risks (read before scheduling)
ENABLE_MOCAPCMake flag (default OFF; ON for release builds once CI is green on all three platforms) — theENABLE_ALEMBIC/ENABLE_ONNXprecedent. A non-mocap build must print a clear "rebuild with -DENABLE_MOCAP" on every surface.mediapipereference before anything else is built.Bone::setManuallyControlled(true)during preview and a clean restore after — get this wrong and the entity's existing animations break. The preview path must snapshot + restore likeUvUnwrap's GUI-safe entry point does.NSCameraUsageDescriptioninInfo.plist.in+ graceful denied-permission UX.Existing patterns to clone (do NOT rewrite)
src/ImageTo3D/MeshGenPredictor/src/SkinTokensPredictor—ENABLE_ONNXguards,Ort::Sessionsetup (CoreML EP on macOS), runtime I/O-name/shape discovery (never hardcode tensor names),ensureModelBlocking()+QTMESH_*_MODEL_BASE_URL/QTMESH_*_NO_DOWNLOADenv overrides, models underAppData/ai_models/<feature>/.src/ImageTo3D/MeshGenController— QML_SINGLETON, inference on a worker thread, results marshalled to the main thread, progress + cancel.MotionInbetween::canonicalIndexForBone()(22 CMU roles, Mixamo/generic/CMU name handling) andAnimationMerger::applyMotionClip(world-frame conjugationlocal(f) = parentWorld⁻¹ · (W · clip(f) · W⁻¹) · parentWorld · bind, standing-pose bind harvest, locked root, rotation-only keys). Body capture output must flow through this, not a new retargeter.MorphAnimationManager::setMorphWeightKeyframe(name, time, weight)+ the multi-clip morph weight support (Anim: Slice B — Mesh / vertex animation (Alembic + Ogre VAT_POSE clips) #519). The dope sheet, timeline, and glTF morph-weight-anim export all already work downstream of this call.ComputeSkinWeightsCommandsnapshot pattern — a recorded take pushes ONE undo command.CLIPipeline::rundispatcher +cmdAnim/cmdSegmentas reference subcommand implementations;MCPServer::callToolmap + heavy-tool registration.scripts/export-rmib-onnx.py,scripts/export-triposr-onnx.py,scripts/upload-triposr-models.sh— one-time dev tools, NOT shipped.Proposed architecture (new feature folder
src/Mocap/)Everything except
MocapController/MocapRecorderis Ogre-free and unit-testable headless (thePbrMapSynth/MotionLibrarybar).Surfaces
qtmesh mocap <video> --face [--body] [--mesh <file>] [--clip-name NAME] [--fps N] [--smooth-cutoff HZ] [--algo sam3dbody|pose-ik] [--no-model] [--json] -o out.glbcapture_face_from_video,capture_body_from_video(both heavy, file-in/scene-or-file-out),list_capture_devices,start_live_capture/stop_live_capture(GUI-attached sessions only).Child issues (slices)
ENABLE_MOCAPbuild infra +VideoFrameSource(Qt Multimedia)FaceCapPredictor+ pure-data core (mapper, head pose, smoothing)Recommended order: A → B → C → D (face ships end-to-end) → E (body) → F (live) → G (hardening). D is the first user-visible milestone; F is the headline feature.
Acceptance criteria
qtmesh mocap talk.mp4 --face --mesh face_with_arkit_targets.glb -o out.glbproduces a glTF whose morph-weight animation visibly reproduces the recorded speech/expressions (verify via the existing timeline playback + glTF reimport).qtmesh mocap dance.mp4 --body --mesh rigged.fbx -o out.glbproduces a skeletal clip on the user's rig;--algo pose-ikand--no-modelforce the fallback; report carriesalgorithmUsed/fallbackReason.ENABLE_MOCAPbuilds show a clear "rebuild with -DENABLE_MOCAP" on every surface (no crash);ENABLE_MOCAPwithout ONNX models degrades per-surface with clear messages; camera-permission denial is handled gracefully.THIRD_PARTY_AI_MODELS.md; models hosted on the HF repo (or documented "not yet hosted" state with graceful degradation).ai.assist.mocap_face,ai.assist.mocap_body,ui.actionon panel controls) + gamificationnoteFeature/noteOperationinstrumentation.Out of scope