Skip to content

animu-sphere/usd-vrm-plugins

Repository files navigation

USD VRM Plugins

OpenUSD plugins for VRM avatars.

This repository is an OpenUSD plugin workspace: it separates schema definitions, file-format import, package resolution, and shared GLB container parsing into independently buildable, independently testable components. The v0.5.0 release ships four plugin bundles, four shared libraries, and two CLIs.

The importer reads VRM 0.x and 1.0, normalizes the differences away, and authors a static USD stage. It never evaluates or simulates — that boundary is the project's central design decision, and it is described below.

Built with OpenStrata. usd-vrm-plugins is OpenStrata's first external adopter, and the ost CLI is how this workspace is built, tested, packaged, and released. The record of adopting it — every version from pre-0.3 to 0.19.0, including what broke — is published in docs/reports/ost/. The repo is dual-mode: everything also builds with plain CMake against any OpenUSD install, with no ost involved.

Workspace components

Component Type Role Status
vrmSchema USD schema bundle (usd-schema) VRM typed API schemas + the schema contract Shipped
usdVrmFileFormat SdfFileFormat bundle (usd-fileformat) .vrm parsing, canonicalization, USD authoring Shipped
usdVrmPackageResolver ArPackageResolver bundle (usd-package-resolver) Embedded resource resolution from .vrm Shipped
vrmContainer Plain CMake library GLB parsing + byte-range validation Shipped
usdVrmaFileFormat SdfFileFormat bundle (usd-fileformat) .vrma motion clips → canonical UsdSkelAnimation v0.3.0
motionCore Plain static CMake library Vendor-neutral humanoid pose / animation / root-motion / constraint types v0.3.0
motionRuntime Plain static CMake library Timestamped pose buffer, interpolation, resample, filter, blend; live-capture intake, recorded traces, replay v0.4.0 · v0.5.0
vrmRetarget Plain static CMake library Humanoid mapping, rest-pose correction, root-motion policy, pose retargeter v0.4.0
motion_retarget CLI executable Bakes a semantic clip onto a target rig as UsdSkelAnimation v0.4.0
motion_capture CLI executable Replays a recorded capture session into a semantic clip the above consumes unchanged v0.5.0
usdVrm Aggregate product name Composed distribution of the workspace Shipped via ost plugin package --workspace --product

usdVrm is not a bundle id — it names the product as a whole. It was the file-format bundle's name until the workspace split; documentation and artifacts that predate that rename use it in the old sense.

The motion layer

motionCore and usdVrmaFileFormat were the v0.3.0 foundation; v0.4.0 added motionRuntime, vrmRetarget, and the motion_retarget CLI, which together make a .vrma clip play back on a real avatar. v0.5.0 adds the observation side — a vendor-neutral LiveCaptureSource, a recorded-trace format, and the motion_capture CLI — which produces the same semantic clip, so a live session is baked by the retarget tool unchanged. The fixed contract is docs/design/MOTION_CONTRACT.md. The exec* identities remain reserved; runtime evaluation is not part of this release.

Component Type Role
usdVrmaFileFormat SdfFileFormat bundle .vrma motion clips → UsdSkelAnimation on a canonical semantic humanoid skeleton
motionCore Plain static CMake library Vendor-neutral pose / animation / root-motion / constraint types
motionRuntime Plain static CMake library Timestamped pose buffer, interpolation, resample, filter, blend
vrmRetarget Plain static CMake library Humanoid mapping, rest-pose correction, root-motion policy, pose retargeter
motion_retarget CLI executable The stage half: reads the rig and the clip, bakes the retargeted UsdSkelAnimation, binds skel:animationSource
execMotion OpenExec bundle Vendor-neutral motion nodes
execVrm OpenExec bundle VRM semantics: retarget, root motion, expression, look-at, avatar apply
adapters/ Optional bundles The only place product names are permitted (e.g. Mocopi, ARDY)

.vrm and .vrma are deliberately separate file-format plugins with symmetric structure, and they compose by reference, not subLayer — a subLayer stack cannot express which skeleton a clip applies to. A third binding/assembly layer relates them.

Dependencies

usdVrmFileFormat ───────> vrmSchema
        │
        └───────────────> vrmContainer

usdVrmPackageResolver ──> vrmContainer

usdVrmaFileFormat ──────> vrmContainer, motionCore

motionRuntime ──────────> motionCore
vrmRetarget ────────────> motionCore, motionRuntime
motion_retarget (CLI) ──> vrmRetarget + OpenUSD stage APIs

                          (planned)
execMotion ─────────────> motionCore, motionRuntime
execVrm ────────────────> vrmSchema, vrmRetarget
adapters/* ─────────────> motionCore, motionRuntime

Five rules keep those edges honest:

  • vrmSchema depends on no other bundle or library.
  • usdVrmPackageResolver never links the file-format bundle; the importer's dependency on the resolver is runtime-only, never link-time.
  • execVrm reads the schema contract from the stage — never the importer's private API or canonical model.
  • vrmRetarget does not depend on OpenExec. The retarget core is finished and testable before any OpenExec node exists; the nodes are thin wrappers.
  • Adapters depend on the core. The core never depends on an adapter, and motionCore never sees a vendor SDK, a network protocol, or a product name.

The bundle graph is validated by ost plugin test --workspace, and each consumer adds a binary link check proving what it does and does not import. Full contract: docs/architecture/WORKSPACE.md.

What the importer produces

.vrm is read as a GLB container (via vendored cgltf v1.15) and normalized — VRM 0.x and 1.0 differences are absorbed into a canonical model before any USD is authored — into:

/Asset                     SkelRoot (or Xform when there is no skeleton), kind=component
  customData.vrm.*         sourceFormat / sourceVersion / specVersion / meta / rawExtension
  geo/                     Scope of UsdGeomMesh (one per glTF primitive)
    <Mesh>                 points/normals/st, material binding; skel binding when
                           skinned, else the glTF node transform as xformOp
  mtl/                     Scope of UsdShadeMaterial (UsdPreviewSurface)
  skel/Skeleton            single UsdSkelSkeleton unified across all glTF skins
                           (bind transforms from the inverse bind matrices)
  rig/Humanoid             vrm:humanBones:<bone> joint tokens, typed VrmHumanoidAPI

Every /Asset/rig/* control prim carries typed schema data. The schema types themselves are provided by the vrmSchema bundle; usdVrmFileFormat depends on schema contract version 1 and authors against it. Raw VRM blocks stay in customData as the lossless fallback.

Runtime boundary

Import:   VRM bytes ──> canonical model ──> USD stage
Runtime:  USD stage + vrmSchema ──> OpenExec / DCC / renderer runtime

The importer authors data only:

  • Import is deterministic. The same bytes produce the same stage.
  • LookAt, node constraints, and spring bones are written as typed schema data, never executed.
  • Evaluation and simulation belong to execVrm (planned) or an external runtime.
  • No physics runs at import time.

This keeps import pure, so a runtime can be swapped without touching the importer.

Feature support

VRM 0.x / 1.0 detection and canonicalization, geometry, UsdPreviewSurface materials with the full texture set, MToon source preservation (vrm:mtoon:raw; renderer-specific realization is not implemented), unified skeleton + skinning from inverse bind matrices, skeletal animation, humanoid mapping, front-direction normalization, and a coded diagnostic taxonomy.

Per-feature status is in docs/reference/CAPABILITY_MATRIX.md. Supported platforms, OpenUSD versions, and build requirements are in docs/reference/SUPPORTED_CONFIGURATIONS.md. The schema contract is in plugins/vrmSchema/docs/SCHEMA_CONTRACT.md.

Install

See docs/guides/INSTALL.md for release-artifact, OpenStrata, and from-source installation, verification, and troubleshooting.

Install the components you use from the release artifacts. Each release publishes four member bundles and one aggregate product archive. The three VRM bundles are installed together; usdVrmaFileFormat is independently installable because it has no plugin-bundle dependency. The member bundles are separately addressable, while the aggregate archive keeps the exact workspace closure together. See the install guide for extraction and verification.

Build and test

Whole workspace, with OpenStrata (ost)

Requires ost 0.19+, so requires.bundles and requires.libraries are composed automatically.

# One-time: adopt an OpenUSD install as the cy2026 runtime.
ost runtime pull cy2026 --profile usd --from-usd /path/to/openusd-install

# Validate the bundle graph, then test every bundle in dependency order.
ost plugin test --workspace

A single bundle

ost plugin build plugins/usdVrmFileFormat
ost plugin test  plugins/usdVrmFileFormat            # L0-L5 verification pyramid

ost plugin build plugins/usdVrmaFileFormat
ost plugin test  plugins/usdVrmaFileFormat           # L0-L5 + VRMA golden

# Inspect a real avatar. build/test/run/package compose the manifest's
# requires.bundles closure automatically:
ost plugin run plugins/usdVrmFileFormat \
    -- python plugins/usdVrmFileFormat/tools/inspect_vrm.py avatar.vrm

# `view` / `test-view` are the exception: they load only what --with names, so
# the runtime siblings must be spelled out or the schema apply fails.
ost plugin view plugins/usdVrmFileFormat avatar.vrm \
    --with plugins/vrmSchema --with plugins/usdVrmPackageResolver

With plain CMake (no OpenStrata)

The workspace root composes every bundle:

cmake -S . -B build -DCMAKE_PREFIX_PATH=/path/to/openusd-install
cmake --build build --config Release
ctest --test-dir build -C Release

Each bundle also builds standalone against installed sibling packages (find_package(vrmSchema CONFIG REQUIRED)), which is what CI proves; a bundle never reaches sideways into a sibling's source tree.

The built libUsdVrmFileFormat.{dll,so,dylib} lands in plugins/usdVrmFileFormat/lib/; add plugins/usdVrmFileFormat/plugin/resources/usdVrmFileFormat to PXR_PLUGINPATH_NAME and the lib/ dir to your dynamic-loader path to use it.

Clean-install smoke

Verifies the packaged bundles have no build-tree dependency:

python scripts/clean_install_smoke.py               # build + package + extract + smoke
python scripts/clean_install_smoke.py --skip-build   # reuse the current build

It packages the three VRM bundles with ost, extracts them into a fresh directory outside the repo, and runs the assertions in plugins/usdVrmFileFormat/tests/clean_install_smoke.py against that extracted tree: .vrm discovery served from the package, a textured fixture and a corpus avatar open and validate, and an embedded texture resolves straight from the .vrm container. Needs ost + a validated cy2026 runtime.

CI

CI is generated from the support matrix in openstrata.ci.yaml (ost ci generate github). The PR lane (.github/workflows/ost-source-ci.yml) runs twelve cells — each of the four bundles on hosted Windows / macOS arm64 / Linux against digest-pinned cy2026 runtimes — building, testing (--up-to 5; Windows is capped at 4), and packaging each. A weekly scheduled lane (ost-support-matrix.yml) re-validates pinned runtime × plugin artifact cells on a self-hosted real runtime.

Release artifacts

Pushing a tag vX.Y.Z (matching VERSION, with that version's CHANGELOG.md section finalized) runs .github/workflows/release.yml: it builds on all three OS cells, proves the packaged artifact (packaged-artifact verification, clean-install smoke, digest-reproducible packaging), and assembles a draft GitHub release — per-target lean + debug bundles, a source archive, SHA256SUMS, and notes rendered from CHANGELOG.md via docs/contributing/RELEASE_NOTES_TEMPLATE.md. Publishing the draft is a human decision. Run the workflow manually (workflow_dispatch) for a dry run that creates no release.

Each bundle carries a buildInfo.json stamp (commit / toolchain / OpenUSD / build type / schema contract version), surfaced by tools/vrm_report.py.

Documentation

docs/ is organized by responsibility — the same layout open-strata and hydra-merlin use:

docs/architecture/ The binding workspace contract: identities, dependency directions, artifact naming
docs/guides/ How to install
docs/reference/ What is supported, on what
docs/roadmap/ What is planned next (incomplete work only)
docs/releases/ Per-version release records
docs/design/ Why the significant decisions were made
docs/reports/ Evidence from real runs: the ost dogfooding series + the delivery log

Release history is in the CHANGELOG; the release version lives in the single-source VERSION file.

License

Original source and documentation: Apache-2.0 (see LICENSE). Third-party components keep their own licenses; see THIRD_PARTY_NOTICES.md. cgltf v1.15 is vendored under third_party/cgltf with its MIT license.

Local test VRM avatars used during development are not part of this repository and are not redistributed here; mind their individual licenses.