diff --git a/docs/vrm1/ControlRig.png b/docs/vrm1/ControlRig.png new file mode 100644 index 000000000..0bf414357 Binary files /dev/null and b/docs/vrm1/ControlRig.png differ diff --git a/docs/vrm1/vrm1_controlrig.md b/docs/vrm1/vrm1_controlrig.md index 39bf18258..5bab4ef38 100644 --- a/docs/vrm1/vrm1_controlrig.md +++ b/docs/vrm1/vrm1_controlrig.md @@ -5,42 +5,128 @@ VRM-1.0 は正規化が仕様から除かれました。 ```{admonition} 正規化 :class: info -ヒエラルキーからの 回転、スケールの除去。 +正規化とは、ヒエラルキーからの 回転、スケールの除去。 その状態での Binding 行列再生成。 です。 + すべてのノードの回転が 0 のときが初期姿勢(T-Pose)であるという仕様で、 プログラムから統一的にモデルを操作することが可能でした。 ``` -正規化されていないモデルも含めて統一的にポーズを付けるインターフェスとして、 `ControlRig` が新規に導入されました。 `v0.103` +`v0.103` 正規化されていないモデルも含めて統一的にポーズを付けるインターフェスとして、 `ControlRig` が新規に導入されました。 -基本的な使い方は下記のとおりです。 -`UnityEngine.Animator.GetBoneTransform` の代わりに使う想定です。 +> Vrm10RuntimeControlRig.GetBoneTransform が導入されました。`v0.104` で Animator.getBoneTransform が +使えるようになったので特に使う必要が無くなりました。 -```csharp -Vrm10RuntimeControlRig rig = instance.Runtime.ControlRig +`v0.104` `UnityEngine.Animator.getBoneTransform` が ControlRig のボーンを返すようになりました。 -// 使用例 -var transform = rig.GetBoneTransform(HumanBodyBones.Head); -transform.localRotation = rotation; +```{admonition} HumanoidAvatar の材料に ControlRig のボーンを使う +:class: info + +ControlRigGenerationOption.Generate の時は、AvatarBuilder.BuildHumanAvatar の引き数にオリジナルのヒエラルキーでは無く、 ControlRig のボーンを渡します。 ``` +## ControlRig は ランタイムロード時に生成されます + ```{admonition} ランタイムロード専用 -:class: warn +:class: warning `v0.103` 現在この機能は Editor で Asset 生成されたモデルでは動作しません。 -初期姿勢を確実に取得する方法を検討中です。 +VRMモデルをセットアップするときに邪魔になってしまうので、Editor では生成しないようにしています。 ``` -vrm-0.x でプログラムから HumanoidBone に回転クォータニオンを代入していた -場所を `Vrm10RuntimeControlRig.GetBoneTransform` で置きかえることで -同じポーズを当てることができます。 +デフォルトで ControlRig を生成 `ControlRigGenerationOption.Generate` します。 +`ControlRigGenerationOption.None` は ControlRig を生成しません。 -## 動作の説明 +```csharp +public static async Task 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) +``` -毎フレーム `Vrm10Instance` が `Vrm10RuntimeControlRig` からVRM-1.0のヒエラルキーにポーズを転送します。 +`ControlRigGenerationOption.Generate` でロードしたモデルは、 `Animator.getBoneTransform` が +ControlRig の該当ボーンを返します。 +オリジナルのボーンを取得する方法は後述します。 -`Vrm10RuntimeControlRig` が初期姿勢を記憶していて、 -`正規化ポーズ localRotation => 初期姿勢を加味 => 元のヒエラルキーの localRotation` に変換という処理をします。 +## ControlRig でないオリジナルのボーンを取得する方法 -初期姿勢は `T-Pose` ですが、 `VRM-0.X` のように正規化されている必要がありません。 +`Vrm10Instance.Humanoid.GetBoneTransform` を使ってください。 + +## ControlRig によるポーズ適用例 + +正規化済みの bvh ヒエラルキーの `Animator src` のポーズを、 +未正規化かもしれない `vrm-1.0` モデルにコピーする例です。 + +各ボーンの localRotation を代入するだけで動きます。 + +```csharp +// VRM10_Samples/VRM10Viewer(v0.104) からの抜粋 +/// +/// from v0.104 +/// +/// +public void UpdateControlRigImplicit(Animator src) +{ + var dst = m_controller.GetComponent(); + + foreach (HumanBodyBones bone in CachedEnum.GetValues()) + { + 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; + } + } +} +``` + +## 詳細 + +```{figure} ./ControlRig.png +ControlRig +``` + +毎フレーム `Vrm10Instance` が `Vrm10RuntimeControlRig` からVRM-1.0のヒエラルキーにポーズをコピーします。 +コピーするときに、各関節の回転を初期姿勢を加味したものに加工しています。 + +`GlobalInit.Inverse * localPose * GlobalInit` という式がロジックです。 +これにより、正規化済みのポーズ(localPose) を非正規化ポーズに変換できます。 + +`Vrm10ControlBone.cs` + +```csharp + internal void ProcessRecursively() + { + ControlTarget.localRotation = _initialTargetLocalRotation * Quaternion.Inverse(_initialTargetGlobalRotation) * ControlBone.localRotation * _initialTargetGlobalRotation; + foreach (var child in _children) + { + child.ProcessRecursively(); + } + } +```