From 64f5a05bc3844ab559d8be12384f98f8214adf6d Mon Sep 17 00:00:00 2001 From: ousttrue Date: Tue, 23 Aug 2022 15:06:19 +0900 Subject: [PATCH 1/2] Vrm10FkRetarget --- .../UniGLTF/Runtime/UniHumanoid/Humanoid.cs | 14 +++ .../Vrm10Runtime/Vrm10BoneWithAxis.cs | 108 ++++++++++++++++++ .../Vrm10Runtime/Vrm10BoneWithAxis.cs.meta | 11 ++ .../Vrm10Runtime/Vrm10FkRetarget.cs | 38 ++++++ .../Vrm10Runtime/Vrm10FkRetarget.cs.meta | 11 ++ .../Components/Vrm10Runtime/Vrm10Runtime.cs | 16 +++ 6 files changed, 198 insertions(+) create mode 100644 Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs create mode 100644 Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs.meta create mode 100644 Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs create mode 100644 Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs.meta diff --git a/Assets/UniGLTF/Runtime/UniHumanoid/Humanoid.cs b/Assets/UniGLTF/Runtime/UniHumanoid/Humanoid.cs index 3e70358be..2b099488e 100644 --- a/Assets/UniGLTF/Runtime/UniHumanoid/Humanoid.cs +++ b/Assets/UniGLTF/Runtime/UniHumanoid/Humanoid.cs @@ -437,5 +437,19 @@ namespace UniHumanoid return true; } + + public bool TryGetBoneForTransform(Transform t, out HumanBodyBones bone) + { + foreach (var (v, k) in BoneMap) + { + if (v == t) + { + bone = k; + return true; + } + } + bone = default; + return false; + } } } diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs new file mode 100644 index 000000000..e131d2d1a --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// このクラスのヒエラルキーが 正規化された TPose を表している。 + /// 同時に、元のヒエラルキーの初期回転を保持する。 + /// Apply 関数で、再帰的に正規化済みのローカル回転から初期回転を加味したローカル回転を作って適用する。 + /// + public class Vrm10BoneWithAxis + { + public readonly HumanBodyBones Bone; + + /// + /// 元のヒエラルキーの対応ボーン + /// + public readonly Transform Target; + + /// + /// 回転と拡大縮小を除去した(正規化された)ボーン。 + /// このボーンに対して localRotation を代入する。 + /// + public readonly Transform Normalized; + + /// + /// 元のボーンの初期回転。 + /// + public readonly Quaternion InitialLocalRotation; + + public readonly Quaternion ToLocal; + + public List Children = new List(); + + public Vrm10BoneWithAxis(Transform current, Quaternion parentInverse, HumanBodyBones bone) + { + if (bone == HumanBodyBones.LastBone) + { + throw new ArgumentNullException(); + } + if (current == null) + { + throw new ArgumentNullException(); + } + Bone = bone; + Target = current; + Normalized = new GameObject(bone.ToString()).transform; + Normalized.position = current.position; + // InitialLocalRotation = parentInverse * current.rotation; + InitialLocalRotation = current.localRotation; + // InitialLocalRotation = current.rotation; + ToLocal = current.rotation; + } + + public static Vrm10BoneWithAxis Build(UniHumanoid.Humanoid humanoid, Dictionary boneMap) + { + var hips = new Vrm10BoneWithAxis(humanoid.Hips, Quaternion.identity, HumanBodyBones.Hips); + + foreach (Transform child in humanoid.Hips) + { + Traverse(humanoid, child, hips, boneMap); + } + + return hips; + } + + private static void Traverse(UniHumanoid.Humanoid humanoid, Transform current, Vrm10BoneWithAxis parent, Dictionary boneMap) + { + if (humanoid.TryGetBoneForTransform(current, out var bone)) + { + + // ヒューマンボーンだけを対象にするので、 + // parent が current の直接の親でない場合がある。 + // ワールド回転 parent^-1 * current からローカル回転を算出する。 + var parentInverse = Quaternion.Inverse(parent.Target.rotation); + + var newBone = new Vrm10BoneWithAxis(current, parentInverse, bone); + newBone.Normalized.SetParent(parent.Normalized, true); + parent.Children.Add(newBone); + parent = newBone; + boneMap.Add(bone, newBone); + } + + foreach (Transform child in current) + { + Traverse(humanoid, child, parent, boneMap); + } + } + + /// + /// 親から再帰的にNormalized の ローカル回転を初期回転を加味して Target に適用する。 + /// + public void ApplyRecursive(Quaternion worldParentRotation) + { + // var pose = InitialLocalRotation * Normalized.localRotation * Quaternion.Inverse(InitialLocalRotation); + // var pose = Quaternion.Inverse(InitialLocalRotation) * Normalized.localRotation * InitialLocalRotation; + Target.localRotation = InitialLocalRotation * Quaternion.Inverse(ToLocal) * Normalized.localRotation * ToLocal; + // Target.localRotation = InitialLocalRotation * Normalized.localRotation; // * Quaternion.Inverse(InitialLocalRotation); + // Target.localRotation = InitialLocalRotation; + + foreach (var child in Children) + { + child.ApplyRecursive(Normalized.rotation); + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs.meta b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs.meta new file mode 100644 index 000000000..35e73e07c --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eac40a89a1de8bc4a8d97b3cc2aea5fb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs new file mode 100644 index 000000000..1b08b21b5 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + public class Vrm10FkRetarget + { + Vrm10BoneWithAxis m_root; + + Dictionary m_boneMap = new Dictionary(); + + Vector3 m_initialHipsPosition; + + public Vrm10FkRetarget(UniHumanoid.Humanoid humanoid) + { + m_root = Vrm10BoneWithAxis.Build(humanoid, m_boneMap); + m_initialHipsPosition = m_root.Target.position; + } + + public void Apply() + { + m_root.Target.position = m_root.Normalized.position; + m_root.ApplyRecursive(Quaternion.identity); + } + + public Transform GetBoneTransform(HumanBodyBones bone) + { + if (m_boneMap.TryGetValue(bone, out var value)) + { + return value.Normalized; + } + else + { + return null; + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs.meta b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs.meta new file mode 100644 index 000000000..e48351021 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c0af836ff386dd469cfedbd238ab2a5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs index 15f504d54..ddace679e 100644 --- a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs @@ -14,6 +14,17 @@ namespace UniVRM10 public class Vrm10Runtime : IDisposable { private readonly Vrm10Instance m_target; + + Vrm10FkRetarget m_fkRetarget; + public Vrm10FkRetarget GetOrCreateFkRetarget() + { + if (m_fkRetarget == null) + { + m_fkRetarget = new Vrm10FkRetarget(m_target.Humanoid); + } + return m_fkRetarget; + } + private readonly IVrm10Constraint[] m_constraints; private readonly Transform m_head; private readonly FastSpringBoneService m_fastSpringBoneService; @@ -152,6 +163,11 @@ namespace UniVRM10 /// public void Process() { + if (m_fkRetarget != null) + { + m_fkRetarget.Apply(); + } + // // constraint // From 2eb64b5f24571706dfef35d96b06ea59fffbd461 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Tue, 23 Aug 2022 15:53:21 +0900 Subject: [PATCH 2/2] =?UTF-8?q?FkRetarget=20=E3=81=AE=E5=88=9D=E6=9C=9F?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=E3=80=82vrm-1.0=20=E3=81=A7=20=E6=AD=A3?= =?UTF-8?q?=E8=A6=8F=E5=8C=96=E3=82=92=E3=82=84=E3=82=81=E3=82=8B=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E3=80=81=E5=BE=93=E6=9D=A5=E3=81=AE=E6=AD=A3=E8=A6=8F?= =?UTF-8?q?=E5=8C=96=E6=B8=88=E3=81=BFTPose=E3=81=AB=E5=AF=BE=E3=81=99?= =?UTF-8?q?=E3=82=8B=20localRotation=20=E3=82=92=E5=A4=89=E6=8F=9B?= =?UTF-8?q?=E3=81=97=E3=81=A6=E9=81=A9=E7=94=A8=E3=81=99=E3=82=8B=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=81=A7=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Vrm10Runtime/Vrm10BoneWithAxis.cs | 6 +- .../Vrm10Runtime/Vrm10FkRetarget.cs | 5 +- .../Vrm10Runtime/Vrm10RuntimeLookAt.cs | 17 ++-- .../VRM10Viewer/VRM10ViewerUI.cs | 84 +++++++++++++------ 4 files changed, 71 insertions(+), 41 deletions(-) diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs index e131d2d1a..da931757a 100644 --- a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs @@ -56,6 +56,7 @@ namespace UniVRM10 public static Vrm10BoneWithAxis Build(UniHumanoid.Humanoid humanoid, Dictionary boneMap) { var hips = new Vrm10BoneWithAxis(humanoid.Hips, Quaternion.identity, HumanBodyBones.Hips); + boneMap.Add(HumanBodyBones.Hips, hips); foreach (Transform child in humanoid.Hips) { @@ -93,12 +94,7 @@ namespace UniVRM10 /// public void ApplyRecursive(Quaternion worldParentRotation) { - // var pose = InitialLocalRotation * Normalized.localRotation * Quaternion.Inverse(InitialLocalRotation); - // var pose = Quaternion.Inverse(InitialLocalRotation) * Normalized.localRotation * InitialLocalRotation; Target.localRotation = InitialLocalRotation * Quaternion.Inverse(ToLocal) * Normalized.localRotation * ToLocal; - // Target.localRotation = InitialLocalRotation * Normalized.localRotation; // * Quaternion.Inverse(InitialLocalRotation); - // Target.localRotation = InitialLocalRotation; - foreach (var child in Children) { child.ApplyRecursive(Normalized.rotation); diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs index 1b08b21b5..f91d4bcda 100644 --- a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs @@ -9,12 +9,13 @@ namespace UniVRM10 Dictionary m_boneMap = new Dictionary(); - Vector3 m_initialHipsPosition; + public readonly float InitialHipsHeight; public Vrm10FkRetarget(UniHumanoid.Humanoid humanoid) { m_root = Vrm10BoneWithAxis.Build(humanoid, m_boneMap); - m_initialHipsPosition = m_root.Target.position; + InitialHipsHeight = m_root.Target.position.y; + Debug.Log($"InitialHipsHeight: {InitialHipsHeight}"); } public void Apply() diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeLookAt.cs b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeLookAt.cs index 7aa88d374..36ba102f8 100644 --- a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeLookAt.cs +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeLookAt.cs @@ -107,16 +107,15 @@ namespace UniVRM10 gaze.SetParent(m_head); gaze.localPosition = Vector3.forward; } - switch (m_lookat.LookAtType) + + // bone が無いときのエラー防止。マイグレーション失敗? + if (m_lookat.LookAtType == LookAtType.bone && m_leftEye != null && m_rightEye != null) { - case LookAtType.bone: - _eyeDirectionApplicable = new LookAtEyeDirectionApplicableToBone(m_leftEye, m_rightEye, m_lookat.HorizontalOuter, m_lookat.HorizontalInner, m_lookat.VerticalDown, m_lookat.VerticalUp); - break; - case LookAtType.expression: - _eyeDirectionApplicable = new LookAtEyeDirectionApplicableToExpression(m_lookat.HorizontalOuter, m_lookat.HorizontalInner, m_lookat.VerticalDown, m_lookat.VerticalUp); - break; - default: - throw new ArgumentOutOfRangeException(); + _eyeDirectionApplicable = new LookAtEyeDirectionApplicableToBone(m_leftEye, m_rightEye, m_lookat.HorizontalOuter, m_lookat.HorizontalInner, m_lookat.VerticalDown, m_lookat.VerticalUp); + } + else + { + _eyeDirectionApplicable = new LookAtEyeDirectionApplicableToExpression(m_lookat.HorizontalOuter, m_lookat.HorizontalInner, m_lookat.VerticalDown, m_lookat.VerticalUp); } } diff --git a/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs b/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs index 7b56b4676..00ca86a68 100644 --- a/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs +++ b/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs @@ -39,7 +39,7 @@ namespace UniVRM10.VRM10Viewer [Header("Runtime")] [SerializeField] - HumanPoseTransfer m_src = default; + Animator m_src = default; [SerializeField] GameObject m_target = default; @@ -200,7 +200,7 @@ namespace UniVRM10.VRM10Viewer var texts = GameObject.FindObjectsOfType(); m_version = texts.First(x => x.name == "Version"); - m_src = GameObject.FindObjectOfType(); + m_src = GameObject.FindObjectOfType(); m_target = GameObject.FindObjectOfType().gameObject; } @@ -208,7 +208,7 @@ namespace UniVRM10.VRM10Viewer class Loaded : IDisposable { RuntimeGltfInstance m_instance; - HumanPoseTransfer m_pose; + bool m_useBvh; Vrm10Instance m_controller; VRM10AIUEO m_lipSync; @@ -256,7 +256,7 @@ namespace UniVRM10.VRM10Viewer } } - public Loaded(RuntimeGltfInstance instance, HumanPoseTransfer src, Transform lookAtTarget) + public Loaded(RuntimeGltfInstance instance, Transform lookAtTarget) { m_instance = instance; @@ -266,10 +266,6 @@ namespace UniVRM10.VRM10Viewer // VRM m_controller.UpdateType = Vrm10Instance.UpdateTypes.LateUpdate; // after HumanPoseTransfer's setPose { - m_pose = instance.gameObject.AddComponent(); - m_pose.Source = src; - m_pose.SourceType = HumanPoseTransfer.HumanPoseTransferSourceType.HumanPoseTransfer; - m_lipSync = instance.gameObject.AddComponent(); m_blink = instance.gameObject.AddComponent(); m_autoExpression = instance.gameObject.AddComponent(); @@ -293,22 +289,57 @@ namespace UniVRM10.VRM10Viewer GameObject.Destroy(m_instance.gameObject); } - public void EnableBvh(HumanPoseTransfer src) + public void EnableBvh() { - if (m_pose != null) - { - m_pose.Source = src; - m_pose.SourceType = HumanPoseTransfer.HumanPoseTransferSourceType.HumanPoseTransfer; - } + m_useBvh = true; } - public void EnableTPose(HumanPoseClip pose) + public void EnableTPose() { - if (m_pose != null) + m_useBvh = false; + } + + public void UpdatePose(Animator pose) + { + var fkRetarget = m_controller.Runtime.GetOrCreateFkRetarget(); + foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones))) { - m_pose.PoseClip = pose; - m_pose.SourceType = HumanPoseTransfer.HumanPoseTransferSourceType.HumanPoseClip; + if (bone == HumanBodyBones.LastBone) + { + continue; + } + + var dst = fkRetarget.GetBoneTransform(bone); + if (dst == null) + { + continue; + } + + if (m_useBvh) + { + var src = pose.GetBoneTransform(bone); + if (src != null) + { + // set normalized pose + dst.localRotation = src.localRotation; + } + + if (bone == HumanBodyBones.Hips) + { + dst.position = src.position * fkRetarget.InitialHipsHeight; + } + } + else + { + // set TPose + dst.localRotation = Quaternion.identity; + if (bone == HumanBodyBones.Hips) + { + dst.position = Vector3.up * fkRetarget.InitialHipsHeight; + } + } } + fkRetarget.Apply(); } } Loaded m_loaded; @@ -344,7 +375,7 @@ namespace UniVRM10.VRM10Viewer var context = new UniHumanoid.BvhImporterContext(); context.Parse("tmp.bvh", source); context.Load(); - SetMotion(context.Root.GetComponent()); + SetMotion(context.Root.GetComponent()); } private void Update() @@ -369,7 +400,12 @@ namespace UniVRM10.VRM10Viewer } } - m_ui.UpdateToggle(() => m_loaded?.EnableBvh(m_src), () => m_loaded?.EnableTPose(m_pose)); + m_ui.UpdateToggle(() => m_loaded?.EnableBvh(), () => m_loaded?.EnableTPose()); + + if (m_loaded != null) + { + m_loaded.UpdatePose(m_src); + } } void OnOpenClicked() @@ -506,17 +542,15 @@ namespace UniVRM10.VRM10Viewer instance.ShowMeshes(); instance.EnableUpdateWhenOffscreen(); - m_loaded = new Loaded(instance, m_src, m_target.transform); + m_loaded = new Loaded(instance, m_target.transform); } - void SetMotion(HumanPoseTransfer src) + void SetMotion(Animator src) { m_src = src; - src.GetComponent().enabled = false; - if (m_loaded != null) { - m_loaded.EnableBvh(m_src); + m_loaded.EnableBvh(); } } }