Merge pull request #1933 from ousttrue/feature10/openxr_ext_hand_tracking

[1.0] XR_EXT_hand_tracking の Rotation に対応した ControlRig の生成
This commit is contained in:
ousttrue
2022-11-28 17:22:45 +09:00
committed by GitHub
19 changed files with 294 additions and 50 deletions

View File

@@ -40,7 +40,7 @@ namespace UniVRM10
var materialGenerator = GetMaterialDescriptorGenerator(renderPipeline);
using (var loader = new Vrm10Importer(result, extractedObjects, materialGenerator: materialGenerator))
using (var loader = new Vrm10Importer(result, externalObjectMap: extractedObjects, materialGenerator: materialGenerator))
{
// settings TextureImporters
foreach (var textureInfo in loader.TextureDescriptorGenerator.Get().GetEnumerable())

View File

@@ -1,4 +1,5 @@
using UnityEngine;
using System.Collections.Generic;
using UnityEngine;
namespace UniVRM10
@@ -52,7 +53,15 @@ namespace UniVRM10
private UniHumanoid.Humanoid m_humanoid;
private Vrm10Runtime m_runtime;
private ControlRigGenerationOption m_controlRigGenerationOption = ControlRigGenerationOption.None;
/// <summary>
/// ControlRig の生成オプション
///
/// null: ControlRigGenerationOption.None
/// empty: ControlRigGenerationOption.Generate = Vrm0XCompatibleRig
/// other: ControlRigGenerationOption.Vrm0XCompatibleWithXR_EXT_hand_tracking など
/// </summary>
private IReadOnlyDictionary<HumanBodyBones, Quaternion> m_controlRigInitialRotations;
/// <summary>
/// VRM ファイルに記録された Humanoid ボーンに対応します。
@@ -79,15 +88,15 @@ namespace UniVRM10
{
if (m_runtime == null)
{
m_runtime = new Vrm10Runtime(this, m_controlRigGenerationOption);
m_runtime = new Vrm10Runtime(this, m_controlRigInitialRotations);
}
return m_runtime;
}
}
internal void InitializeAtRuntime(ControlRigGenerationOption controlRigGenerationOption)
internal void InitializeAtRuntime(IReadOnlyDictionary<HumanBodyBones, Quaternion> controlRigInitialRotations)
{
m_controlRigGenerationOption = controlRigGenerationOption;
m_controlRigInitialRotations = controlRigInitialRotations;
}
void Start()

View File

@@ -1,16 +0,0 @@
namespace UniVRM10
{
public enum ControlRigGenerationOption
{
/// <summary>
/// コントロールリグを生成しません。
/// </summary>
None,
/// <summary>
/// 推奨されるオプションです。
/// コントロールリグのボーン Transform を生成し、Root の Animator はコントロールリグのボーンを制御するようになります。
/// </summary>
Generate,
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 9723f89e671a460188d12cbcceccaa4e
timeCreated: 1663314138

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ebc9416609f7b734f87e487dfa0ae076
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UniVRM10
{
public enum ControlRigGenerationOption
{
/// <summary>
/// コントロールリグを生成しません。
/// </summary>
None = 0,
/// <summary>
/// 推奨されるオプションです。
/// コントロールリグのボーン Transform を生成し、Root の Animator はコントロールリグのボーンを制御するようになります。
/// </summary>
Generate = 1,
Vrm0XCompatibleRig = 1,
/// <summary>
/// コントロールリグのボーン Transform を生成し、Root の Animator はコントロールリグのボーンを制御するようになります。
/// 手と指に関して、XR_EXT_hand_tracking の初期回転を持ちます。
/// https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_hand_tracking
/// </summary>
Vrm0XCompatibleWithXR_EXT_hand_tracking = 2,
}
internal static class ControlRigGenerationOptionExtensions
{
internal static IReadOnlyDictionary<HumanBodyBones, Quaternion> ToInitialRotations(this ControlRigGenerationOption option)
{
switch (option)
{
case ControlRigGenerationOption.None:
return null;
case ControlRigGenerationOption.Generate:
// case ControlRigGenerationOption.Vrm0XCompatibleRig:
return Vrm0XCompatibleRig.InitialRotations;
case ControlRigGenerationOption.Vrm0XCompatibleWithXR_EXT_hand_tracking:
return XR_EXT_hand_tracking.InitialRotations;
default:
throw new ArgumentException();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 254b61afe523dd74b9579ff1e971bee7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
using System.Collections.Generic;
using UnityEngine;
namespace UniVRM10
{
public static class Vrm0XCompatibleRig
{
/// <summary>
/// 空の Dictionary を返します。
/// HumanBodyBones に対応する Quaternion が無い場合は Quaternion.Identity を採用する仕様です。
/// VRM-0.X では T-Pose のときにすべてのボーンが Quaternion.Identity です。
/// </summary>
public static IReadOnlyDictionary<HumanBodyBones, Quaternion> InitialRotations => new Dictionary<HumanBodyBones, Quaternion>();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d1e0f212571bf3f41809f19c2d8f3211
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,85 @@
using System.Collections.Generic;
using UnityEngine;
namespace UniVRM10
{
/// <summary>
/// XR_EXT_hand_tracking の joint を VRM-1.0 の TPose に当てはめたときの方向を定義します。
///
/// * https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#_conventions_of_hand_joints
/// * https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0/tpose.ja.md
///
/// </summary>
public static class XR_EXT_hand_tracking
{
/// <summary>
/// up vector と forward vector の外積により空間を算出して、回転を得ます。
///
/// OpenXR は右手系なのに対して Unityは左手系です。
/// 結果として、X軸が反転することに注意してください。
/// </summary>
/// <param name="up"></param>
/// <param name="forward"></param>
/// <returns></returns>
public static Quaternion GetRotation(Vector3 up, Vector3 forward)
{
var xAxis = Vector3.Cross(up, forward).normalized;
var m = new Matrix4x4(xAxis, up, forward, new Vector4(0, 0, 0, 1));
return m.rotation;
}
public static Quaternion LeftHand = GetRotation(Vector3.up, Vector3.right);
public static Quaternion RightHand = GetRotation(Vector3.up, Vector3.left);
/// <summary>
/// 親指は XZ 平面45度です。
/// </summary>
public static Quaternion LeftThumb = GetRotation((Vector3.forward + Vector3.left).normalized, (Vector3.right + Vector3.forward).normalized);
/// <summary>
/// 親指は XZ 平面45度です。
/// </summary>
public static Quaternion RightThumb = GetRotation((Vector3.forward + Vector3.right).normalized, (Vector3.left + Vector3.forward).normalized);
/// <summary>
/// VRM-1.0 の T-Pose の定義から各指はX軸と並行です。親指はXZ平面に45度です。
/// </summary>
public static IReadOnlyDictionary<HumanBodyBones, Quaternion> InitialRotations => new Dictionary<HumanBodyBones, Quaternion>()
{
// Left
{HumanBodyBones.LeftHand, LeftHand},
{HumanBodyBones.LeftThumbProximal, LeftThumb},
{HumanBodyBones.LeftThumbIntermediate, LeftThumb},
{HumanBodyBones.LeftThumbDistal, LeftThumb},
{HumanBodyBones.LeftIndexProximal, LeftHand},
{HumanBodyBones.LeftIndexIntermediate, LeftHand},
{HumanBodyBones.LeftIndexDistal, LeftHand},
{HumanBodyBones.LeftMiddleProximal, LeftHand},
{HumanBodyBones.LeftMiddleIntermediate, LeftHand},
{HumanBodyBones.LeftMiddleDistal, LeftHand},
{HumanBodyBones.LeftRingProximal, LeftHand},
{HumanBodyBones.LeftRingIntermediate, LeftHand},
{HumanBodyBones.LeftRingDistal, LeftHand},
{HumanBodyBones.LeftLittleProximal, LeftHand},
{HumanBodyBones.LeftLittleIntermediate, LeftHand},
{HumanBodyBones.LeftLittleDistal, LeftHand},
// Right
{HumanBodyBones.RightHand, RightHand},
{HumanBodyBones.RightThumbProximal, RightThumb},
{HumanBodyBones.RightThumbIntermediate, RightThumb},
{HumanBodyBones.RightThumbDistal, RightThumb},
{HumanBodyBones.RightIndexProximal, RightHand},
{HumanBodyBones.RightIndexIntermediate, RightHand},
{HumanBodyBones.RightIndexDistal, RightHand},
{HumanBodyBones.RightMiddleProximal, RightHand},
{HumanBodyBones.RightMiddleIntermediate, RightHand},
{HumanBodyBones.RightMiddleDistal, RightHand},
{HumanBodyBones.RightRingProximal, RightHand},
{HumanBodyBones.RightRingIntermediate, RightHand},
{HumanBodyBones.RightRingDistal, RightHand},
{HumanBodyBones.RightLittleProximal, RightHand},
{HumanBodyBones.RightLittleIntermediate, RightHand},
{HumanBodyBones.RightLittleDistal, RightHand},
};
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 551eba367636e4b45b222ce636a6adcf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -41,11 +41,12 @@ namespace UniVRM10
/// </summary>
public Quaternion InitialControlBoneLocalRotation { get; }
private readonly Quaternion _initialControlBoneGlobalRotation;
private readonly Quaternion _initialTargetLocalRotation;
private readonly Quaternion _initialTargetGlobalRotation;
private readonly List<Vrm10ControlBone> _children = new List<Vrm10ControlBone>();
private Vrm10ControlBone(Transform controlTarget, HumanBodyBones boneType, Vrm10ControlBone parent)
private Vrm10ControlBone(Transform controlTarget, HumanBodyBones boneType, Vrm10ControlBone parent, IReadOnlyDictionary<HumanBodyBones, Quaternion> controlRigInitialRotations)
{
if (boneType == HumanBodyBones.LastBone)
{
@@ -60,6 +61,13 @@ namespace UniVRM10
ControlTarget = controlTarget;
// NOTE: bone name must be unique in the vrm instance.
ControlBone = new GameObject($"{nameof(Vrm10ControlBone)}:{boneType.ToString()}").transform;
if (controlRigInitialRotations != null)
{
if (controlRigInitialRotations.TryGetValue(boneType, out var rotation))
{
ControlBone.rotation = rotation;
}
}
ControlBone.position = controlTarget.position;
if (parent != null)
@@ -70,48 +78,63 @@ namespace UniVRM10
InitialControlBoneLocalPosition = ControlBone.localPosition;
InitialControlBoneLocalRotation = ControlBone.localRotation;
_initialControlBoneGlobalRotation = ControlBone.rotation;
_initialTargetLocalRotation = controlTarget.localRotation;
_initialTargetGlobalRotation = controlTarget.rotation;
}
/// <summary>
/// 初期姿勢からの相対的な回転。
///
/// VRM-0.X 互換リグでは localRotation と同じ値を示す。
/// </summary>
Quaternion NormalizedLocalRotation
{
get
{
var delta = Quaternion.Inverse(_initialControlBoneGlobalRotation) * ControlBone.localRotation * _initialControlBoneGlobalRotation;
return InitialControlBoneLocalRotation * delta;
}
}
/// <summary>
/// 親から再帰的にNormalized の ローカル回転を初期回転を加味して Target に適用する。
/// </summary>
internal void ProcessRecursively()
{
ControlTarget.localRotation = _initialTargetLocalRotation * Quaternion.Inverse(_initialTargetGlobalRotation) * ControlBone.localRotation * _initialTargetGlobalRotation;
ControlTarget.localRotation = _initialTargetLocalRotation * (Quaternion.Inverse(_initialTargetGlobalRotation) * NormalizedLocalRotation * _initialTargetGlobalRotation);
foreach (var child in _children)
{
child.ProcessRecursively();
}
}
public static Vrm10ControlBone Build(UniHumanoid.Humanoid humanoid, out Dictionary<HumanBodyBones, Vrm10ControlBone> boneMap)
public static Vrm10ControlBone Build(UniHumanoid.Humanoid humanoid, IReadOnlyDictionary<HumanBodyBones, Quaternion> controlRigInitialRotations, out Dictionary<HumanBodyBones, Vrm10ControlBone> boneMap)
{
var hips = new Vrm10ControlBone(humanoid.Hips, HumanBodyBones.Hips, null);
var hips = new Vrm10ControlBone(humanoid.Hips, HumanBodyBones.Hips, null, controlRigInitialRotations);
boneMap = new Dictionary<HumanBodyBones, Vrm10ControlBone>();
boneMap.Add(HumanBodyBones.Hips, hips);
foreach (Transform child in humanoid.Hips)
{
BuildRecursively(humanoid, child, hips, boneMap);
BuildRecursively(humanoid, child, hips, controlRigInitialRotations, boneMap);
}
return hips;
}
private static void BuildRecursively(UniHumanoid.Humanoid humanoid, Transform current, Vrm10ControlBone parent, Dictionary<HumanBodyBones, Vrm10ControlBone> boneMap)
private static void BuildRecursively(UniHumanoid.Humanoid humanoid, Transform current, Vrm10ControlBone parent, IReadOnlyDictionary<HumanBodyBones, Quaternion> controlRigInitialRotations, Dictionary<HumanBodyBones, Vrm10ControlBone> boneMap)
{
if (humanoid.TryGetBoneForTransform(current, out var bone))
{
var newBone = new Vrm10ControlBone(current, bone, parent);
var newBone = new Vrm10ControlBone(current, bone, parent, controlRigInitialRotations);
parent = newBone;
boneMap.Add(bone, newBone);
}
foreach (Transform child in current)
{
BuildRecursively(humanoid, child, parent, boneMap);
BuildRecursively(humanoid, child, parent, controlRigInitialRotations, boneMap);
}
}
}

View File

@@ -49,7 +49,7 @@ namespace UniVRM10
}
}
public Vrm10Runtime(Vrm10Instance target, ControlRigGenerationOption controlRigGenerationOption)
public Vrm10Runtime(Vrm10Instance target, IReadOnlyDictionary<HumanBodyBones, Quaternion> controlRigInitialRotations)
{
m_target = target;
@@ -58,9 +58,9 @@ namespace UniVRM10
throw new Exception();
}
if (controlRigGenerationOption != ControlRigGenerationOption.None)
if (controlRigInitialRotations != null)
{
ControlRig = new Vrm10RuntimeControlRig(target.Humanoid, m_target.transform, controlRigGenerationOption);
ControlRig = new Vrm10RuntimeControlRig(target.Humanoid, m_target.transform, controlRigInitialRotations);
}
Constraints = target.GetComponentsInChildren<IVrm10Constraint>();
LookAt = new Vrm10RuntimeLookAt(target.Vrm.LookAt, target.Humanoid, m_head, target.LookAtTargetType, target.Gaze);

View File

@@ -25,17 +25,22 @@ namespace UniVRM10
public float InitialHipsHeight { get; }
/// <summary>
/// コンストラクタ。
/// humanoid は VRM T-Pose でなければならない。
/// humanoid に対して ControlRig を生成します
/// </summary>
public Vrm10RuntimeControlRig(UniHumanoid.Humanoid humanoid, Transform vrmRoot, ControlRigGenerationOption option)
/// <param name="humanoid">T-Pose である必要があります</param>
/// <param name="vrmRoot"></param>
/// <param name="controlRigInitialRotations">ControlRigの各ボーンの初期回転を表します</param>
public Vrm10RuntimeControlRig(UniHumanoid.Humanoid humanoid, Transform vrmRoot, IReadOnlyDictionary<HumanBodyBones, Quaternion> controlRigInitialRotations)
{
if (option == ControlRigGenerationOption.None) return;
if (controlRigInitialRotations == null)
{
throw new ArgumentNullException();
}
_controlRigRoot = new GameObject("Runtime Control Rig").transform;
_controlRigRoot.SetParent(vrmRoot);
_hipBone = Vrm10ControlBone.Build(humanoid, out _bones);
_hipBone = Vrm10ControlBone.Build(humanoid, controlRigInitialRotations, out _bones);
_hipBone.ControlBone.SetParent(_controlRigRoot);
InitialHipsHeight = _hipBone.ControlTarget.position.y;

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UniGLTF;
@@ -274,7 +275,7 @@ namespace UniVRM10
throw new ArgumentNullException(nameof(vrm10Data));
}
using (var loader = new Vrm10Importer(vrm10Data, controlRigGenerationOption: controlRigGenerationOption, materialGenerator: materialGenerator))
using (var loader = new Vrm10Importer(vrm10Data, materialGenerator: materialGenerator, controlRigInitialRotations: controlRigGenerationOption.ToInitialRotations()))
{
// 1. Load meta information if callback was available.
if (vrmMetaInformationCallback != null)

View File

@@ -18,7 +18,7 @@ namespace UniVRM10
private readonly Vrm10Data m_vrm;
/// VrmLib.Model の オブジェクトと UnityEngine.Object のマッピングを記録する
private readonly ModelMap m_map = new ModelMap();
private readonly ControlRigGenerationOption m_controlRigGenerationOption;
private readonly IReadOnlyDictionary<HumanBodyBones, Quaternion> m_controlRigInitialRotations;
private VrmLib.Model m_model;
private IReadOnlyDictionary<SubAssetKey, UnityEngine.Object> m_externalMap;
@@ -31,7 +31,8 @@ namespace UniVRM10
IReadOnlyDictionary<SubAssetKey, UnityEngine.Object> externalObjectMap = null,
ITextureDeserializer textureDeserializer = null,
IMaterialDescriptorGenerator materialGenerator = null,
ControlRigGenerationOption controlRigGenerationOption = ControlRigGenerationOption.None)
IReadOnlyDictionary<HumanBodyBones, Quaternion> controlRigInitialRotations = null
)
: base(vrm.Data, externalObjectMap, textureDeserializer)
{
if (vrm == null)
@@ -39,7 +40,7 @@ namespace UniVRM10
throw new ArgumentNullException("vrm");
}
m_vrm = vrm;
m_controlRigGenerationOption = controlRigGenerationOption;
m_controlRigInitialRotations = controlRigInitialRotations;
TextureDescriptorGenerator = new Vrm10TextureDescriptorGenerator(Data);
MaterialDescriptorGenerator = materialGenerator ?? new BuiltInVrm10MaterialDescriptorGenerator();
@@ -246,7 +247,7 @@ namespace UniVRM10
// VrmController
var controller = Root.AddComponent<Vrm10Instance>();
controller.InitializeAtRuntime(m_controlRigGenerationOption);
controller.InitializeAtRuntime(m_controlRigInitialRotations);
controller.enabled = false;
// vrm

View File

@@ -21,7 +21,7 @@ namespace UniVRM10.Test
GameObject BuildGameObject(Vrm10Data data, bool showMesh)
{
using (var loader = new Vrm10Importer(data))
using (var loader = new Vrm10Importer(data, null))
{
var loaded = loader.Load();
if (showMesh)

View File

@@ -32,7 +32,7 @@ namespace UniVRM10.Test
controller.Vrm.Expression.Aa.MaterialColorBindings = src.ToArray();
// ok if no exception
var r = new Vrm10Runtime(controller, ControlRigGenerationOption.None);
var r = new Vrm10Runtime(controller, null);
}
[Test]
@@ -56,7 +56,7 @@ namespace UniVRM10.Test
controller.Vrm.Expression.Aa.MaterialUVBindings = src.ToArray();
// ok if no exception
var r = new Vrm10Runtime(controller, ControlRigGenerationOption.None);
var r = new Vrm10Runtime(controller, null);
}
}
}

View File

@@ -126,7 +126,6 @@ namespace UniVRM10.VRM10Viewer
/// <summary>
/// from v0.104
/// </summary>
/// <param name="src"></param>
public void UpdateControlRigImplicit(Animator src)
{
var dst = m_controller.GetComponent<Animator>();
@@ -159,6 +158,40 @@ namespace UniVRM10.VRM10Viewer
}
}
/// <summary>
/// from v0.108
/// </summary>
public void UpdateControlRigImplicit(UniHumanoid.Humanoid src)
{
var dst = m_controller.GetComponent<Animator>();
foreach (HumanBodyBones bone in CachedEnum.GetValues<HumanBodyBones>())
{
if (bone == HumanBodyBones.LastBone)
{
continue;
}
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;
}
}
}
}
public void TPoseControlRig()
{
var controlRig = m_controller.Runtime.ControlRig;