ControlRig: Working with Unnormalized Models¶
VRM-1.0 removed normalization from the specification.
Normalization
Normalization is removing the rotation and scale from the hierarchy.Regenerate the combined matrix in that state.
With the specification that the initial pose (T-Pose) is when the rotation of all nodes is 0, we were able to uniformly manipulate the model from the program.
v0.103
ControlRig
is newly introduced as an interface for uniformly posing even non-normalized models.
Vrm10RuntimeControlRig.GetBoneTransform was introduced. in
v0.104
Animator.getBoneTransform is now available so you no longer need to use it.
v0.104
UnityEngine.Animator.getBoneTransform
is ControlRi
Using ControlRig bone as material for HumanoidAvatar
For ControlRigGenerationOption.Generate, AvatarBuilder.BuildHumanAvatar Pass the ControlRig’s bones to the arguments instead of the original hierarchies.
ControlRig is generated at runtime load¶
Runtime load only
As of v0.103
, this feature does not work with in-editor asset generation models.I don’t want to generate it in the Editor as it gets in the way when setting up the VRM model.
ControlRigGenerationOption.Generate
to generate a ControlRig by default.
public static async Task<Vrm10Instance> Vrm10.LoadPathAsync(
string path,
bool canLoadVrm0X = true,
ControlRigGenerationOption controlRigGenerationOption = ControlRigGenerationOption.Generate, // 👈
bool showMeshes = true,
IAwaitCaller awaitCaller = null,
IMaterialDescriptorGenerator materialGenerator = null,
VrmMetaInformationCallback vrmMetaInformationCallback = null,
CancellationToken ct = default)
Model loaded with ControlRigGenerationOption.Generate``Animator.getBoneTransform
returns the corresponding bone of the ControlRig.How to get the original bones is described later.
How to get bones other than the original ControlRig¶
Use Vrm10Instance.Humanoid.GetBoneTransform.
Example of applying pose with ControlRig¶
Animator src
pose in normalized bvh hierarchy, possibly non-normalized `vrm-1.0
Just replace the localRotation for each bone you move.
// VRM10_Samples/VRM10Viewer(v0.104) からの抜粋
/// <summary>
/// from v0.104
/// </summary>
/// <param name="src"></param>
public void UpdateControlRigImplicit(Animator src)
{
var dst = m_controller.GetComponent<Animator>();
foreach (HumanBodyBones bone in CachedEnum.GetValues<HumanBodyBones>())
{
if (bone == HumanBodyBones.LastBone)
{
continue;
}
// v0.104 から Animator.GetBoneTransform が
// ControlRig のボーンを返します。
var boneTransform = dst.GetBoneTransform(bone);
if (boneTransform == null)
{
continue;
}
var bvhBone = src.GetBoneTransform(bone);
if (bvhBone != null)
{
// set normalized pose
boneTransform.localRotation = bvhBone.localRotation;
}
if (bone == HumanBodyBones.Hips)
{
// TODO: hips position scaling ?
boneTransform.localPosition = bvhBone.localPosition;
}
}
}
detail¶

ControlRig¶
Every frame Vrm10Instance
is Vrm10RuntimeControlRig
Copy the pose into the VRM-1.0 hierarchy. When copying, the rotation of each joint is processed to reflect the initial pose.
The logic is the expression GlobalInit.Inverse * localPose * GlobalInit
.This allows you to convert a normalized pose (localPose) to a non-normalized pose.
Vrm10ControlBone.cs
internal void ProcessRecursively()
{
ControlTarget.localRotation = _initialTargetLocalRotation * Quaternion.Inverse(_initialTargetGlobalRotation) * ControlBone.localRotation * _initialTargetGlobalRotation;
foreach (var child in _children)
{
child.ProcessRecursively();
}
}