use VRM10Retarget.Retarget

This commit is contained in:
ousttrue 2023-02-21 15:43:15 +09:00
parent 5235a9b837
commit 0502f30588
4 changed files with 226 additions and 127 deletions

View File

@ -9,6 +9,7 @@ namespace UniVRM10.VRM10Viewer
{
RuntimeGltfInstance m_instance;
Vrm10Instance m_controller;
public Vrm10RuntimeControlRig ControlRig => m_controller.Runtime.ControlRig;
VRM10AIUEO m_lipSync;
bool m_enableLipSyncValue;
@ -88,110 +89,6 @@ namespace UniVRM10.VRM10Viewer
GameObject.Destroy(m_instance.gameObject);
}
/// <summary>
/// from v0.103
/// </summary>
/// <param name="src"></param>
public void UpdateControlRigExplicit(Animator src)
{
var controlRig = m_controller.Runtime.ControlRig;
foreach (HumanBodyBones bone in CachedEnum.GetValues<HumanBodyBones>())
{
if (bone == HumanBodyBones.LastBone)
{
continue;
}
var controlRigBone = controlRig.GetBoneTransform(bone);
if (controlRigBone == null)
{
continue;
}
var bvhBone = src.GetBoneTransform(bone);
if (bvhBone != null)
{
// set normalized pose
controlRigBone.localRotation = bvhBone.localRotation;
}
if (bone == HumanBodyBones.Hips)
{
controlRigBone.localPosition = bvhBone.localPosition * controlRig.InitialHipsHeight;
}
}
}
/// <summary>
/// from v0.104
/// </summary>
public void UpdateControlRigImplicit(Animator 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;
}
}
}
/// <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;

View File

@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UniGLTF;
using UniHumanoid;
using UniJSON;
using UnityEngine;
using VRMShaders;
namespace UniVRM10.VRM10Viewer
{
public class VRM10Motion
{
public (INormalizedPoseProvider, ITPoseProvider) ControlRig;
UniHumanoid.BvhImporterContext m_context;
UniGLTF.RuntimeGltfInstance m_instance;
public Transform Root => m_context?.Root.transform;
public VRM10Motion(UniHumanoid.BvhImporterContext context)
{
m_context = context;
var provider = new NormalizedPoseProvider(m_context.Root.transform, m_context.Root.GetComponent<Animator>());
ControlRig = (provider, provider);
}
public VRM10Motion(UniGLTF.RuntimeGltfInstance instance)
{
m_instance = instance;
if (instance.GetComponent<Animation>() is Animation animation)
{
animation.Play();
}
}
public void ShowBoxMan(bool showBoxMan)
{
if (m_context != null)
{
m_context.Root.GetComponent<SkinnedMeshRenderer>().enabled = showBoxMan;
}
}
public static VRM10Motion LoadBvhFromText(string source, string path = "tmp.bvh")
{
var context = new UniHumanoid.BvhImporterContext();
context.Parse(path, source);
context.Load();
return new VRM10Motion(context);
}
public static VRM10Motion LoadBvhFromPath(string path)
{
return LoadBvhFromText(File.ReadAllText(path), path);
}
static IEnumerable<Transform> Traverse(Transform t)
{
yield return t;
foreach (Transform child in t)
{
foreach (var x in Traverse(child))
{
yield return x;
}
}
}
#region experimental vrm-animation deserializer
//
// vrm-animation の簡易実装
//
// https://github.com/vrm-c/vrm-animation
//
static float ForceMeterScale(Dictionary<HumanBodyBones, Transform> map)
{
var positionMap = map.ToDictionary(kv => kv.Key, kv => kv.Value.position);
var hipsHeight = positionMap[HumanBodyBones.Hips].y;
float scaling = 1.0f;
if (hipsHeight > 80)
{
// cm スケールであると見做す
scaling = 0.01f;
}
return scaling;
}
static bool TryGet(JsonNode obj, string key, out JsonNode found)
{
foreach (var kv in obj.ObjectItems())
{
if (kv.Key.GetString() == key)
{
found = kv.Value;
return true;
}
}
found = default;
return false;
}
static (HumanBodyBones, int) ToTuple(KeyValuePair<JsonNode, JsonNode> kv)
{
if (TryGet(kv.Value, "node", out var value))
{
var name = kv.Key.GetString();
switch (name)
{
case "rightThumbMetacarpal":
return (HumanBodyBones.RightThumbProximal, value.GetInt32());
case "leftThumbMetacarpal":
return (HumanBodyBones.LeftThumbProximal, value.GetInt32());
case "rightThumbProximal":
return (HumanBodyBones.RightThumbIntermediate, value.GetInt32());
case "leftThumbProximal":
return (HumanBodyBones.LeftThumbIntermediate, value.GetInt32());
default:
return ((HumanBodyBones)Enum.Parse(typeof(HumanBodyBones), name, true), value.GetInt32());
}
}
throw new Exception();
}
static Dictionary<HumanBodyBones, Transform> GetHumanMap(GltfData data, IReadOnlyList<Transform> nodes)
{
var humanMap = new Dictionary<HumanBodyBones, Transform>();
if (data.GLTF.extensions is UniGLTF.glTFExtensionImport extensions)
{
foreach (var kv in extensions.ObjectItems())
{
if (kv.Key.GetString() == "VRMC_vrm_animation")
{
var animation = kv.Value;
if (TryGet(animation, "humanoid", out var animation_humanoid))
{
if (TryGet(animation_humanoid, "humanBones", out var bones))
{
foreach (var kkvv in bones.ObjectItems())
{
var (bone, index) = ToTuple(kkvv);
// Debug.Log($"{bone} => {index}");
humanMap.Add(bone, nodes[index]);
}
}
}
}
}
}
return humanMap;
}
#endregion
public static async Task<VRM10Motion> LoadVrmAnimationFromPathAsync(string path)
{
//
// GetHumanoid Mapping
//
using (GltfData data = new AutoGltfFileParser(path).Parse())
using (var loader = new UniGLTF.ImporterContext(data))
{
loader.InvertAxis = Axes.X;
// loader.PositionScaling = 0.01f;
var instance = await loader.LoadAsync(new ImmediateCaller());
var humanMap = GetHumanMap(data, loader.Nodes);
if (humanMap.Count == 0)
{
throw new ArgumentException("fail to load VRMC_vrm_animation");
}
var scaling = ForceMeterScale(humanMap);
// instance.transform.localScale = new Vector3(scaling, scaling, scaling);
var description = AvatarDescription.Create(humanMap);
//
// avatar
//
var avatar = description.CreateAvatar(instance.Root.transform);
avatar.name = "Avatar";
// AvatarDescription = description;
var animator = instance.gameObject.AddComponent<Animator>();
animator.avatar = avatar;
// create SkinnedMesh for bone visualize
var renderer = SkeletonMeshUtility.CreateRenderer(animator);
var material = new Material(Shader.Find("Standard"));
renderer.sharedMaterial = material;
var mesh = renderer.sharedMesh;
mesh.name = "box-man";
var humanoid = instance.gameObject.AddComponent<Humanoid>();
humanoid.AssignBonesFromAnimator();
var motion = new VRM10Motion(instance);
var poseProvider = new InitRotationPoseProvider(instance.transform, humanoid);
motion.ControlRig = (poseProvider, poseProvider);
return motion;
}
}
public void Retarget(Vrm10RuntimeControlRig dst)
{
VRM10Retarget.Retarget(ControlRig, (dst, dst));
}
}
}

View File

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

View File

@ -3,7 +3,6 @@ using System.IO;
using System.Linq;
using System.Threading;
using UniGLTF;
using UniHumanoid;
using UnityEngine;
using UnityEngine.UI;
using VRMShaders;
@ -34,18 +33,15 @@ namespace UniVRM10.VRM10Viewer
[SerializeField]
Toggle m_useAsync = default;
[Header("Runtime")]
[SerializeField]
Animator m_src = default;
[SerializeField]
GameObject m_target = default;
[SerializeField]
TextAsset m_motion;
GameObject Root = default;
[SerializeField]
TextAsset m_motion;
VRM10Motion m_src = default;
private CancellationTokenSource _cancellationTokenSource;
@ -181,8 +177,6 @@ namespace UniVRM10.VRM10Viewer
var texts = GameObject.FindObjectsOfType<Text>();
m_version = texts.First(x => x.name == "Version");
m_src = GameObject.FindObjectOfType<Animator>();
m_target = GameObject.FindObjectOfType<VRM10TargetMover>().gameObject;
}
@ -197,7 +191,7 @@ namespace UniVRM10.VRM10Viewer
// load initial bvh
if (m_motion != null)
{
LoadMotion(m_motion.text);
m_src = VRM10Motion.LoadBvhFromText(m_motion.text);
}
string[] cmds = System.Environment.GetCommandLineArgs();
@ -217,17 +211,6 @@ namespace UniVRM10.VRM10Viewer
_cancellationTokenSource?.Dispose();
}
private void LoadMotion(string source)
{
var context = new UniHumanoid.BvhImporterContext();
context.Parse("tmp.bvh", File.ReadAllText(source));
context.Load();
m_src = context.Root.GetComponent<Animator>();
m_ui.IsBvhEnabled = true;
// hide box man
context.Root.GetComponent<SkinnedMeshRenderer>().enabled = false;
}
private void Update()
{
if (m_loaded != null)
@ -254,7 +237,7 @@ namespace UniVRM10.VRM10Viewer
{
if (m_ui.IsBvhEnabled && m_src != null)
{
m_loaded.UpdateControlRigImplicit(m_src);
m_src.Retarget(m_loaded.ControlRig);
}
else
{
@ -280,7 +263,7 @@ namespace UniVRM10.VRM10Viewer
var ext = Path.GetExtension(path).ToLower();
if (ext == ".bvh")
{
LoadMotion(path);
m_src = VRM10Motion.LoadBvhFromPath(path);
return;
}