Merge pull request #2457 from ousttrue/fix/springbone0x_runtime_interface

[0.x] IVrm0XSpringBoneRuntime の実装
This commit is contained in:
ousttrue 2024-10-08 17:37:26 +09:00 committed by GitHub
commit 83b63eaed1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 2494 additions and 57 deletions

View File

@ -5,7 +5,7 @@ namespace UniGLTF.SpringBoneJobs.Blittables
public struct BlittableModelLevel
{
/// <summary>
/// 風など。
/// World 座標系の追加の力。風など。
/// </summary>
public Vector3 ExternalForce;

View File

@ -1,5 +1,6 @@
using System.Threading.Tasks;
using UniGLTF;
using UniGLTF.SpringBoneJobs.Blittables;
using UnityEngine;
namespace VRM
@ -7,5 +8,27 @@ namespace VRM
public interface IVrm0XSpringBoneRuntime
{
public Task InitializeAsync(GameObject vrm, IAwaitCaller awaitCaller);
/// <summary>
/// SpringBone の構成変更を反映して再構築する。
/// </summary>
public void ReconstructSpringBone();
/// <summary>
/// initialTransform 状態に復帰。verlet の速度 も 0 に。
/// </summary>
public void RestoreInitialTransform();
/// <summary>
/// Joint レベルの可変情報をセットする
/// stiffness,
/// </summary>
public void SetJointLevel(Transform joint, BlittableJointMutable jointSettings);
/// <summary>
/// Model レベルの可変情報をセットする
/// 風, pause, scaling
/// </summary>
public void SetModelLevel(Transform modelRoot, BlittableModelLevel modelSettings);
}
}

View File

@ -5,24 +5,17 @@ using UniGLTF.SpringBoneJobs.Blittables;
using UniGLTF;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using UniGLTF.Utils;
namespace VRM.SpringBoneJobs
{
public static class FastSpringBoneReplacer
{
/// <summary>
/// - 指定された GameObject 内にある SpringBone を停止させる
/// - FastSpringBoneBuffer に変換する
/// </summary>
public static async Task<FastSpringBoneBuffer> MakeBufferAsync(GameObject root, IAwaitCaller awaitCaller = null, CancellationToken token = default)
{
var components = root.GetComponentsInChildren<VRMSpringBone>();
foreach (var sb in components)
{
// 停止させて FastSpringBoneService から実行させる
sb.m_updateType = VRMSpringBone.SpringBoneUpdateType.Manual;
}
var springs = new FastSpringBoneSpring[components.Length];
for (int i = 0; i < components.Length; ++i)
@ -57,6 +50,9 @@ namespace VRM.SpringBoneJobs
}
}
var runtime = root.GetComponent<RuntimeGltfInstance>();
var initMap = runtime != null ? runtime.InitialTransformStates : root.GetComponentsInChildren<Transform>().ToDictionary(x => x, x => new TransformState(x));
var joints = new List<FastSpringBoneJoint>();
foreach (var springRoot in component.RootBones)
{
@ -67,7 +63,7 @@ namespace VRM.SpringBoneJobs
token.ThrowIfCancellationRequested();
}
Traverse(joints, component, springRoot);
Traverse(joints, component, springRoot, initMap);
}
var spring = new FastSpringBoneSpring
@ -82,11 +78,11 @@ namespace VRM.SpringBoneJobs
return new FastSpringBoneBuffer(root.transform, springs);
}
static void Traverse(List<FastSpringBoneJoint> joints, VRMSpringBone spring, Transform joint)
static void Traverse(List<FastSpringBoneJoint> joints, VRMSpringBone spring, Transform joint, IReadOnlyDictionary<Transform, TransformState> initMap)
{
joints.Add(new FastSpringBoneJoint
{
Transform = joint.transform,
Transform = joint,
Joint = new BlittableJointMutable
{
radius = spring.m_hitRadius,
@ -95,11 +91,11 @@ namespace VRM.SpringBoneJobs
gravityPower = spring.m_gravityPower,
stiffnessForce = spring.m_stiffnessForce
},
DefaultLocalRotation = joint.transform.localRotation,
DefaultLocalRotation = initMap[joint.transform].LocalRotation,
});
foreach (Transform child in joint)
{
Traverse(joints, spring, child);
Traverse(joints, spring, child, initMap);
}
}
}

View File

@ -1,6 +1,8 @@
using System.Threading.Tasks;
using UniGLTF;
using UniGLTF.SpringBoneJobs;
using UniGLTF.SpringBoneJobs.Blittables;
using UniGLTF.SpringBoneJobs.InputPorts;
using UnityEngine;
namespace VRM
@ -16,26 +18,78 @@ namespace VRM
/// </summary>
public class Vrm0XFastSpringboneRuntime : IVrm0XSpringBoneRuntime
{
GameObject m_vrm;
SpringBoneJobs.FastSpringBoneService m_service;
FastSpringBoneBuffer m_buffer;
public async Task InitializeAsync(GameObject vrm, IAwaitCaller awaitCaller)
{
m_vrm = vrm;
m_service = SpringBoneJobs.FastSpringBoneService.Instance;
// default update の停止
foreach (VRMSpringBone sb in vrm.GetComponentsInChildren<VRMSpringBone>())
{
sb.m_updateType = VRMSpringBone.SpringBoneUpdateType.Manual;
}
// create
var buffer = await SpringBoneJobs.FastSpringBoneReplacer.MakeBufferAsync(vrm, awaitCaller);
SpringBoneJobs.FastSpringBoneService.Instance.BufferCombiner.Register(buffer);
// disposer
var disposer = vrm.AddComponent<FastSpringBoneDisposer>()
var disposer = m_vrm.AddComponent<FastSpringBoneDisposer>()
.AddAction(() =>
{
SpringBoneJobs.FastSpringBoneService.Instance.BufferCombiner.Unregister(buffer);
buffer.Dispose();
Unregister();
})
;
// create
await RegisterAsync(awaitCaller);
}
void Unregister()
{
Debug.Log("Vrm0XFastSpringboneRuntime.Unregister");
if (m_buffer == null)
{
return;
}
m_service.BufferCombiner.Unregister(m_buffer);
m_buffer.Dispose();
m_buffer = null;
}
async Task RegisterAsync(IAwaitCaller awaitCaller)
{
Debug.Assert(m_buffer == null);
var buffer = await SpringBoneJobs.FastSpringBoneReplacer.MakeBufferAsync(m_vrm, awaitCaller);
m_buffer = buffer;
SpringBoneJobs.FastSpringBoneService.Instance.BufferCombiner.Register(buffer);
}
public void ReconstructSpringBone()
{
var disposer = m_vrm.gameObject.GetComponent<FastSpringBoneDisposer>();
Unregister();
var task = RegisterAsync(new ImmediateCaller());
}
public void RestoreInitialTransform()
{
if (m_buffer != null)
{
m_service.BufferCombiner.InitializeJointsLocalRotation(m_buffer);
}
}
public void SetJointLevel(Transform joint, BlittableJointMutable jointSettings)
{
m_service.BufferCombiner.Combined.SetJointLevel(joint, jointSettings);
}
public void SetModelLevel(Transform modelRoot, BlittableModelLevel modelSettings)
{
m_service.BufferCombiner.Combined?.SetModelLevel(modelRoot, modelSettings);
}
}
}

View File

@ -8,11 +8,14 @@ namespace VRM.SpringBone
public readonly IReadOnlyList<Transform> RootBones;
public readonly Transform Center;
public readonly VRMSpringBoneColliderGroup[] ColliderGroups;
public readonly Vector3 ExternalForce;
public SceneInfo(
IReadOnlyList<Transform> rootBones,
Transform center,
VRMSpringBoneColliderGroup[] colliderGroups) =>
(RootBones, Center, ColliderGroups) = (rootBones, center, colliderGroups);
VRMSpringBoneColliderGroup[] colliderGroups,
Vector3 externalForce) =>
(RootBones, Center, ColliderGroups, ExternalForce)
= (rootBones, center, colliderGroups, externalForce);
}
}

View File

@ -35,14 +35,14 @@ namespace VRM.SpringBone
/// Verlet積分で次の位置を計算する
/// </summary>
public Vector3 VerletIntegration(float deltaTime, Transform center, Quaternion parentRotation,
SpringBoneSettings settings, SpringBoneJointState _state, float scalingFactor)
SpringBoneSettings settings, SpringBoneJointState _state, float scalingFactor, Vector3 externalForce)
{
var state = _state.ToWorld(center);
var nextTail = state.CurrentTail
+ (state.CurrentTail - state.PrevTail) * (1.0f - settings.DragForce) // 前フレームの移動を継続する(減衰もあるよ)
+ parentRotation * LocalRotation * BoneAxis * settings.StiffnessForce * deltaTime * scalingFactor // 親の回転による子ボーンの移動目標
+ settings.GravityDir * (settings.GravityPower * deltaTime) * scalingFactor; // 外力による移動量
+ (settings.GravityDir * settings.GravityPower + externalForce) * deltaTime * scalingFactor; // 外力による移動量
return nextTail;
}

View File

@ -9,6 +9,7 @@ namespace VRM.SpringBone
{
Dictionary<Transform, Quaternion> m_initialLocalRotationMap;
List<(Transform, SpringBoneJointInit, SpringBoneJointState)> m_joints = new();
Dictionary<Transform, int> m_jointIndexMap = new();
List<SphereCollider> m_colliders = new();
public void Setup(SceneInfo scene, bool force)
@ -23,6 +24,7 @@ namespace VRM.SpringBone
m_initialLocalRotationMap.Clear();
}
m_joints.Clear();
m_jointIndexMap.Clear();
foreach (var go in scene.RootBones)
{
@ -33,6 +35,55 @@ namespace VRM.SpringBone
SetupRecursive(scene.Center, go);
}
}
for (int i = 0; i < m_joints.Count; ++i)
{
m_jointIndexMap.Add(m_joints[i].Item1, i);
}
}
public void ReinitializeRotation(SceneInfo scene)
{
foreach (var go in scene.RootBones)
{
if (go != null)
{
ReinitializeRotationRecursive(scene.Center, go);
}
}
}
public void ReinitializeRotationRecursive(Transform center, Transform parent)
{
var index = m_jointIndexMap[parent];
var (t, init, state) = m_joints[index];
t.localRotation = m_initialLocalRotationMap[t];
Vector3 localPosition;
Vector3 scale;
if (parent.childCount == 0)
{
// 子ードが無い。7cm 固定
var delta = parent.position - parent.parent.position;
var childPosition = parent.position + delta.normalized * 0.07f * parent.UniformedLossyScale();
localPosition = parent.worldToLocalMatrix.MultiplyPoint(childPosition); // cancel scale
scale = parent.lossyScale;
}
else
{
var firstChild = GetChildren(parent).First();
localPosition = firstChild.localPosition;
scale = firstChild.lossyScale;
}
var localChildPosition = new Vector3(
localPosition.x * scale.x,
localPosition.y * scale.y,
localPosition.z * scale.z
);
m_joints[index] = (t, init, new SpringBoneJointState(localChildPosition, localChildPosition));
foreach (Transform child in parent) SetupRecursive(center, child);
}
private static IEnumerable<Transform> GetChildren(Transform parent)
@ -115,7 +166,8 @@ namespace VRM.SpringBone
// false の場合
// 拡大すると移動速度はだいたい同じ => SpringBone の角速度が遅くなる
var scalingFactor = settings.UseRuntimeScalingSupport ? transform.UniformedLossyScale() : 1.0f;
var nextTail = init.VerletIntegration(deltaTime, scene.Center, parentRotation, settings, state, scalingFactor);
var nextTail = init.VerletIntegration(deltaTime, scene.Center, parentRotation, settings, state,
scalingFactor, scene.ExternalForce);
// 長さをboneLengthに強制
nextTail = transform.position + (nextTail - transform.position).normalized * init.Length;

View File

@ -31,6 +31,14 @@ namespace VRM
/// </summary>
public bool UseRuntimeScalingSupport { get; set; }
/// <summary>
/// VRM-1.0 からのバックポート。
/// - Runtime 制御用のパラメタである
/// - シリアライズ対象でない
/// - World座標系
/// </summary>
public Vector3 ExternalForce { get; set; }
public enum SpringBoneUpdateType
{
LateUpdate,
@ -49,7 +57,9 @@ namespace VRM
SpringBone.SceneInfo Scene => new(
rootBones: RootBones,
center: m_center,
colliderGroups: ColliderGroups);
colliderGroups: ColliderGroups,
externalForce: ExternalForce);
SpringBone.SpringBoneSettings Settings => new
(
stiffnessForce: m_stiffnessForce,
@ -68,6 +78,17 @@ namespace VRM
}
}
public void ReinitializeRotation()
{
m_system.ReinitializeRotation(Scene);
}
public void SetModelLevel(UniGLTF.SpringBoneJobs.Blittables.BlittableModelLevel modelSettings)
{
UseRuntimeScalingSupport = modelSettings.SupportsScalingAtRuntime;
ExternalForce = modelSettings.ExternalForce;
}
void LateUpdate()
{
if (m_updateType == SpringBoneUpdateType.LateUpdate)
@ -95,7 +116,7 @@ namespace VRM
private void OnDrawGizmosSelected()
{
if (Application.isPlaying && m_updateType!=SpringBoneUpdateType.Manual)
if (Application.isPlaying && m_updateType != SpringBoneUpdateType.Manual)
{
m_system.PlayingGizmo(m_center, Settings, m_gizmoColor);
}

View File

@ -1,5 +1,6 @@
using System.Threading.Tasks;
using UniGLTF;
using UniGLTF.SpringBoneJobs.Blittables;
using UnityEngine;
namespace VRM
@ -13,13 +14,46 @@ namespace VRM
/// </summary>
public class Vrm0XSpringBoneDefaultRuntime : IVrm0XSpringBoneRuntime
{
GameObject m_vrm;
public async Task InitializeAsync(GameObject vrm, IAwaitCaller awaitCaller)
{
m_vrm = vrm;
foreach (VRMSpringBone sb in vrm.GetComponentsInChildren<VRMSpringBone>())
{
sb.m_updateType = VRMSpringBone.SpringBoneUpdateType.LateUpdate;
}
await awaitCaller.NextFrame();
}
public void ReconstructSpringBone()
{
foreach (VRMSpringBone sb in m_vrm.GetComponentsInChildren<VRMSpringBone>())
{
sb.Setup();
}
}
public void RestoreInitialTransform()
{
foreach (VRMSpringBone sb in m_vrm.GetComponentsInChildren<VRMSpringBone>())
{
sb.ReinitializeRotation();
}
}
public void SetJointLevel(Transform joint, BlittableJointMutable jointSettings)
{
// no impl
}
public void SetModelLevel(Transform modelRoot, BlittableModelLevel modelSettings)
{
foreach (VRMSpringBone sb in m_vrm.GetComponentsInChildren<VRMSpringBone>())
{
sb.SetModelLevel(modelSettings);
sb.m_updateType = modelSettings.StopSpringBoneWriteback ? VRMSpringBone.SpringBoneUpdateType.Manual : VRMSpringBone.SpringBoneUpdateType.LateUpdate;
}
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using UniGLTF;
using UniGLTF.SpringBoneJobs.Blittables;
using UniHumanoid;
using UnityEngine;
@ -40,9 +41,12 @@ namespace VRM.SimpleViewer
}
}
public Loaded(RuntimeGltfInstance instance, HumanPoseTransfer src, Transform lookAtTarget)
IVrm0XSpringBoneRuntime _springbone;
public Loaded(RuntimeGltfInstance instance, HumanPoseTransfer src, Transform lookAtTarget, IVrm0XSpringBoneRuntime springbone)
{
_instance = instance;
_springbone = springbone;
var lookAt = instance.GetComponent<VRMLookAtHead>();
if (lookAt != null)
@ -104,15 +108,19 @@ namespace VRM.SimpleViewer
}
}
public void ResetSpring()
public void ResetSpringbone()
{
if (_pose != null)
{
foreach (var spring in _pose.GetComponentsInChildren<VRMSpringBone>())
{
spring.Setup();
}
}
_springbone.RestoreInitialTransform();
}
public void ReconstructSpringbone()
{
_springbone.ReconstructSpringBone();
}
public void SetSpringboneModelLevel(BlittableModelLevel modelSettings)
{
_springbone.SetModelLevel(_instance.transform, modelSettings);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -39,8 +39,28 @@ namespace VRM.SimpleViewer
[SerializeField]
Toggle m_loadAnimation = default;
// SpringBone
[SerializeField]
Toggle m_useFastSpringBone = default;
[SerializeField]
Toggle m_springBonePause = default;
[SerializeField]
Toggle m_springBoneScaling = default;
[SerializeField]
Slider m_springExternalX = default;
[SerializeField]
Slider m_springExternalY = default;
[SerializeField]
Slider m_springExternalZ = default;
[SerializeField]
Button m_reset = default;
[SerializeField]
Button m_reconstruct = default;
#endregion
[SerializeField]
@ -52,9 +72,6 @@ namespace VRM.SimpleViewer
[SerializeField]
GameObject Root = default;
[SerializeField]
Button m_reset = default;
[SerializeField]
TextAsset m_motion;
@ -280,10 +297,9 @@ namespace VRM.SimpleViewer
m_version.text = string.Format("VRMViewer {0}.{1}",
PackageVersion.MAJOR, PackageVersion.MINOR);
m_open.onClick.AddListener(OnOpenClicked);
m_useFastSpringBone.onValueChanged.AddListener(OnUseFastSpringBoneValueChanged);
OnUseFastSpringBoneValueChanged(m_useFastSpringBone.isOn);
m_reset.onClick.AddListener(() => m_loaded?.ResetSpring());
m_reset.onClick.AddListener(() => m_loaded?.ResetSpringbone());
m_reconstruct.onClick.AddListener(() => m_loaded?.ReconstructSpringbone());
// load initial bvh
if (m_motion != null)
@ -327,6 +343,16 @@ namespace VRM.SimpleViewer
{
m_loaded.EnableLipSyncValue = m_enableLipSync.isOn;
m_loaded.EnableBlinkValue = m_enableAutoBlink.isOn;
m_loaded.SetSpringboneModelLevel(new UniGLTF.SpringBoneJobs.Blittables.BlittableModelLevel
{
ExternalForce = new Vector3(
m_springExternalX.value,
m_springExternalY.value,
m_springExternalZ.value
),
StopSpringBoneWriteback = m_springBonePause.isOn,
SupportsScalingAtRuntime = m_springBoneScaling.isOn,
});
m_loaded.Update();
}
}
@ -392,21 +418,17 @@ namespace VRM.SimpleViewer
// vrm
VrmUtility.MaterialGeneratorCallback materialCallback = (glTF_VRM_extensions vrm) => GetVrmMaterialGenerator(m_useUrpMaterial.isOn, vrm);
VrmUtility.MetaCallback metaCallback = m_texts.UpdateMeta;
IVrm0XSpringBoneRuntime springboneRuntime = m_useFastSpringBone.isOn ? new Vrm0XFastSpringboneRuntime() : new Vrm0XSpringBoneDefaultRuntime();
var instance = await VrmUtility.LoadBytesAsync(path, bytes, GetIAwaitCaller(m_useAsync.isOn),
materialCallback, metaCallback,
loadAnimation: m_loadAnimation.isOn,
springboneRuntime: m_useFastSpringBone.isOn ? new Vrm0XFastSpringboneRuntime() : new Vrm0XSpringBoneDefaultRuntime()
springboneRuntime: springboneRuntime
);
instance.EnableUpdateWhenOffscreen();
instance.ShowMeshes();
m_loaded = new Loaded(instance, m_src, m_target.transform);
}
void OnUseFastSpringBoneValueChanged(bool flag)
{
m_reset.gameObject.SetActive(!flag);
m_loaded = new Loaded(instance, m_src, m_target.transform, springboneRuntime);
}
static IMaterialDescriptorGenerator GetGltfMaterialGenerator(bool useUrp)