VRChat Avatar Transfer Conversion Details
A list of conversions that VRChatAvatarTransfer performs on a VRChat avatar prefab.
This page describes what happens internally when you press the Convert button on Use VRChat Avatars.
Overview
Drag the prefab you want to convert into the VRChat Avatar Transfer window, which you can open from Tools > VRChat Avatar Transfer > Transfer. This prefab is not modified.
- Convert VRChat dynamic bones (PhysBone) to VRM10 SpringBone
- Convert VRChat Constraint components to Unity standard Constraints
- Duplicate the FX layer AnimatorController of
VRCAvatarDescriptorand apply it to the rootAnimator- Replace
VRCAvatarParameterDriverin the duplicated FX withAvatarParameterDriver - Replace
VRCAnimatorTrackingControlin the duplicated FX withAvatarAnimatorTrackingControl
- Replace
- Add a component to the root depending on the avatar type
- VRCFT v2-compatible avatar (FX has
FT/v2/parameters) → addVRCFTAvatar - Otherwise → add
VRCAvatarand port viseme lip sync / blink / the Expressions menu
- VRCFT v2-compatible avatar (FX has
- Strip VRChat-only components (
VRCAvatarDescriptor,PipelineManager) and Missing Scripts
1. PhysBone → VRM10 SpringBone
| Source (VRChat) | Target (VRM10) |
|---|---|
VRCPhysBone | VRM10SpringBoneJoint (attached to each bone along the chain) |
VRCPhysBoneCollider | VRM10SpringBoneCollider |
| (none) | Adds Vrm10Instance to the root and registers entries in SpringBone.Springs / ColliderGroups |
Key characteristics:
- Because the leaf joint falls outside the VRM10 SpringBone simulation range, a
<bone-name>_endTransform is generated as a child of the leaf and registered as the tail. - Branching bone structures are supported. Branch points are included as the terminal joint of the parent chain, and each child starts a new Spring from the head.
- Colliders are grouped under a dedicated
ColliderGroupGameObject placed directly underVrm10Instance. Vrm10Instance.UpdateTypeis set toUpdate(withLateUpdate, results would lag by one frame after the animation).
Parameter Mapping
| VRC PhysBone | VRM10 SpringBone Joint | Conversion |
|---|---|---|
pull × spring | m_stiffnessForce | pull * (1 - spring) * 4 |
pull × spring | m_dragForce | clamp01(1 - pull * spring) |
gravity | m_gravityPower / m_gravityDir | abs(gravity) * 20, direction from sign |
radius (× radiusCurve) | m_jointRadius | As-is |
limitType | m_anglelimitType | Angle→Cone / Hinge→Hinge / Polar→Spherical |
limitRotation (× each Curve) | m_limitSpaceOffset | Euler angles converted to Quaternion |
maxAngleX | m_pitch | Degrees→Radians |
maxAngleZ | m_yaw | Applied only when Spherical |
Limitations
- Colliders: Colliders with shape
PlaneandinsideBounds=trueare skipped because VRM10 SpringBone has no matching type. immobile: Ignored because there is no equivalent concept in VRM10 SpringBone.- VRC
stiffness(Advanced): Not included in the mapping above.
2. VRC Constraint → Unity Constraint
| Source (VRChat) | Target (Unity standard) |
|---|---|
VRCParentConstraint | ParentConstraint |
VRCPositionConstraint | PositionConstraint |
VRCRotationConstraint | RotationConstraint |
VRCScaleConstraint | ScaleConstraint |
VRCAimConstraint | AimConstraint |
VRCLookAtConstraint | LookAtConstraint |
weight / constraintActive / locked, plus each source's transform and weight, per-axis affect flags (such as AffectsPositionX), *AtRest / *Offset, and Aim-family fields (aimVector / upVector / worldUpType) are carried over to the extent possible.
Limitations
FreezeToWorld: Skipped because there is no equivalent in Unity Constraints.- Non-self
TargetTransform: Skipped because Unity Constraints always control the host GameObject itself.
3. Applying the FX AnimatorController
The converter looks up the FX layer in VRCAvatarDescriptor.baseAnimationLayers, duplicates the assigned AnimatorController into the output folder, and then assigns it to Animator.runtimeAnimatorController on the root. The original FX controller asset is not modified.
This allows expression and toggle-style animations that worked on VRChat to play back from VirgoMotionStudio as well.
If the FX layer is empty, nothing is applied. If there is no Animator on the root, the application step is also skipped.
The duplicated FX controller is saved to the output folder as <avatar-name>.FX.controller.
3-1. VRCAvatarParameterDriver → AvatarParameterDriver
The VRCAvatarParameterDriver (StateMachineBehaviour) attached to each State / StateMachine of the duplicated FX controller is replaced with AvatarParameterDriver, which does not depend on the VRChat SDK.
- The
Set/Add/Random/CopyChangeTypevalues are carried over as-is. - Parameters such as
value/valueMin/valueMax/chance/sourceMin/sourceMax/destMin/destMax/convertRangeare also copied with the same meaning. - The
localOnlyfield is kept only for compatibility; since this package has no concept of network sync, it is always applied. - When
Copy'sconvertRangeistrue,[sourceMin, sourceMax]is linearly mapped to[destMin, destMax]. - Parameters are applied on
OnStateEnter, so the firing timing matches VRChat.
3-2. VRCAnimatorTrackingControl → AvatarAnimatorTrackingControl
Likewise, VRCAnimatorTrackingControl on States / StateMachines is replaced with AvatarAnimatorTrackingControl.
| Carried-over targets |
|---|
trackingHead / trackingHip |
trackingLeftHand / trackingRightHand |
trackingLeftFoot / trackingRightFoot |
trackingLeftFingers / trackingRightFingers |
trackingEyes / trackingMouth |
The TrackingType (NoChange / Tracking / Animation) of each target is mapped as-is.
4. Adding Avatar Components
The converter inspects the Animator parameters of the duplicated FX controller and switches which component is added to the root depending on the avatar type.
4-1. Detecting VRCFT v2-Compatible Avatars
If even one parameter name in the FX controller starts with FT/v2/, the avatar is treated as one built with Adjerry91's VRC Face Tracking Template v2.
- VRCFT v2-compatible: Adds
VRCFTAvatarto the root. It converts ARKit's 52 blendshape values intoFT/v2/...parameters and writes them directly to the Animator. Because the FT template drives expressions within the Animator, the subsequent viseme / blink / Expressions porting is not applied. - Otherwise: Adds
VRCAvatarto the root.VRCAvataris a bridge that drives the FX controller'sViseme/Voiceparameters from ARKit, and it ports the following information beforeVRCAvatarDescriptoris removed.
4-2. Viseme Lip Sync (VRCAvatar)
Only when VRCAvatarDescriptor.lipSync is VisemeBlendShape, the following vowel blendshapes are ported from VisemeSkinnedMesh and VisemeBlendShapes to VRCAvatar.
- The 5 vowels
aa/E/ih/oh/ou silhas no dedicated blendshape, so it is treated as -1 (mouth closed)- Consonant visemes are out of scope (only the 5 vowels estimated from ARKit are supported)
If none of the vowels are found, porting is skipped and VRCAvatar falls back to driving the Viseme/Voice parameters via the Animator.
4-3. Blink Blendshape (VRCAvatar)
When VRCAvatarDescriptor.enableEyeLook is enabled and customEyeLookSettings.eyelidType is Blendshapes, the blink blendshape is ported to VRCAvatar.
- Only the index of
eyelidsBlendshapes[0](the Blink slot) is used - "Looking Up" / "Looking Down" are out of scope (gaze is driven by the Eye bones)
5. Stripping VRChat-Only Components
Because these components cannot be referenced outside VRChat, they are removed from the entire hierarchy:
VRCAvatarDescriptorPipelineManager
In addition, third-party Missing Scripts left on the original VRChat avatar (components that do not exist in this project) are also removed, because SaveAsPrefabAsset refuses to save a prefab that contains Missing Scripts.
Related Links
- Use VRChat Avatars — How to operate the Transfer
- About VRC FaceTracking — How face tracking is handled