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

../_images/ControlRig.png

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();
            }
        }