From 1baf2e5e7d4fe9c0840151fb8d3f2559da266fbe Mon Sep 17 00:00:00 2001 From: ousttrue Date: Tue, 8 Aug 2023 18:53:32 +0900 Subject: [PATCH] =?UTF-8?q?VrmAnimation=20=E3=81=AE=20Expression=20?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=E3=82=92=E3=80=81=E9=80=8F=E6=98=8E=E3=81=AA?= =?UTF-8?q?=20transform=20=E3=81=AE=20x=20=E5=BA=A7=E6=A8=99=E3=82=92?= =?UTF-8?q?=E8=BB=A2=E7=94=A8=E3=81=99=E3=82=8B=E3=81=AE=E3=82=92=E3=82=84?= =?UTF-8?q?=E3=82=81=E3=81=A6=E3=80=81=20AnimationClip=20=E3=81=AB?= =?UTF-8?q?=E6=99=AE=E9=80=9A=E3=81=AE=20float=20curve=20=E3=81=A8?= =?UTF-8?q?=E3=81=97=E3=81=A6=E6=A0=BC=E7=B4=8D=E3=81=99=E3=82=8B=E6=A7=98?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4=E3=81=97=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VrmAnimationInstance の public field と連動する。 --- .../IO/AnimationIO/AnimationImporterUtil.cs | 10 +- .../VrmAnimationInstance.cs | 48 ++++- .../VRM10/Runtime/IO/VrmAnimationImporter.cs | 199 +++++++++++++----- .../VRM10Viewer/Motions/BvhMotion.cs | 3 +- .../VRM10Viewer/Motions/IMotion.cs | 2 +- .../VRM10Viewer/Motions/VrmAnimation.cs | 3 +- .../VRM10Viewer/VRM10ViewerUI.cs | 2 +- 7 files changed, 198 insertions(+), 69 deletions(-) diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/AnimationIO/AnimationImporterUtil.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/AnimationIO/AnimationImporterUtil.cs index a1bcb52ec..b6f0bab04 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/AnimationIO/AnimationImporterUtil.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/AnimationIO/AnimationImporterUtil.cs @@ -9,14 +9,14 @@ namespace UniGLTF { public static class AnimationImporterUtil { - private enum TangentMode + public enum TangentMode { Linear, Constant, Cubicspline } - private static TangentMode GetTangentMode(string interpolation) + public static TangentMode GetTangentMode(string interpolation) { if (string.IsNullOrEmpty(interpolation) || interpolation == glTFAnimationTarget.Interpolations.LINEAR.ToString()) { @@ -36,7 +36,7 @@ namespace UniGLTF } } - private static void CalculateTanget(List keyframes, int current) + public static void CalculateTangent(List keyframes, int current) { int back = current - 1; if (back < 0) @@ -140,7 +140,7 @@ namespace UniGLTF keyframes[i].Add(new Keyframe(time, reversed[i], 0, 0)); if (keyframes[i].Count > 0) { - CalculateTanget(keyframes[i], keyframes[i].Count - 1); + CalculateTangent(keyframes[i], keyframes[i].Count - 1); } } else if (tangentMode == TangentMode.Constant) @@ -235,7 +235,7 @@ namespace UniGLTF var input = data.GetArrayFromAccessor(sampler.input); var output = data.FlatternFloatArrayFromAccessor(sampler.output); - AnimationImporterUtil.SetAnimationCurve( + SetAnimationCurve( clip, relativePath, new string[] { "localPosition.x", "localPosition.y", "localPosition.z" }, diff --git a/Assets/VRM10/Runtime/Components/VrmAnimationInstance/VrmAnimationInstance.cs b/Assets/VRM10/Runtime/Components/VrmAnimationInstance/VrmAnimationInstance.cs index cc63e0962..6ba133d15 100644 --- a/Assets/VRM10/Runtime/Components/VrmAnimationInstance/VrmAnimationInstance.cs +++ b/Assets/VRM10/Runtime/Components/VrmAnimationInstance/VrmAnimationInstance.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using UniHumanoid; using UnityEngine; @@ -8,9 +9,28 @@ namespace UniVRM10 { public SkinnedMeshRenderer BoxMan; public (INormalizedPoseProvider, ITPoseProvider) ControlRig; - public Dictionary ExpressionMap = new Dictionary(); + public Dictionary> ExpressionMap = new(); - public void Initialize(IReadOnlyList<(ExpressionKey, Transform)> expressions) + public float preset_happy; + public float preset_angry; + public float preset_sad; + public float preset_relaxed; + public float preset_surprised; + public float preset_aa; + public float preset_ih; + public float preset_ou; + public float preset_ee; + public float preset_oh; + public float preset_blink; + public float preset_blinkleft; + public float preset_blinkright; + // public float preset_lookup; + // public float preset_lookdown; + // public float preset_lookleft; + // public float preset_lookright; + public float preset_neutral; + + public void Initialize(IEnumerable keys) { var humanoid = gameObject.AddComponent(); if (humanoid.AssignBonesFromAnimator()) @@ -28,9 +48,29 @@ namespace UniVRM10 mesh.name = "box-man"; } - foreach (var (preset, transform) in expressions) + foreach (var key in keys) { - ExpressionMap.Add(preset, transform); + switch (key.Preset) + { + case ExpressionPreset.happy: ExpressionMap.Add(key, () => preset_happy); break; + case ExpressionPreset.angry: ExpressionMap.Add(key, () => preset_angry); break; + case ExpressionPreset.sad: ExpressionMap.Add(key, () => preset_sad); break; + case ExpressionPreset.relaxed: ExpressionMap.Add(key, () => preset_relaxed); break; + case ExpressionPreset.surprised: ExpressionMap.Add(key, () => preset_surprised); break; + case ExpressionPreset.aa: ExpressionMap.Add(key, () => preset_aa); break; + case ExpressionPreset.ih: ExpressionMap.Add(key, () => preset_ih); break; + case ExpressionPreset.ou: ExpressionMap.Add(key, () => preset_ou); break; + case ExpressionPreset.ee: ExpressionMap.Add(key, () => preset_ee); break; + case ExpressionPreset.oh: ExpressionMap.Add(key, () => preset_oh); break; + case ExpressionPreset.blink: ExpressionMap.Add(key, () => preset_blink); break; + case ExpressionPreset.blinkLeft: ExpressionMap.Add(key, () => preset_blinkleft); break; + case ExpressionPreset.blinkRight: ExpressionMap.Add(key, () => preset_blinkright); break; + // case ExpressionPreset.lookUp: ExpressionMap.Add(key, () => preset_lookUp); break; + // case ExpressionPreset.lookDown: ExpressionMap.Add(key, () => preset_lookDown); break; + // case ExpressionPreset.lookLeft: ExpressionMap.Add(key, () => preset_lookLeft); break; + // case ExpressionPreset.lookRight: ExpressionMap.Add(key, () => preset_lookRight); break; + case ExpressionPreset.neutral: ExpressionMap.Add(key, () => preset_neutral); break; + } } } } diff --git a/Assets/VRM10/Runtime/IO/VrmAnimationImporter.cs b/Assets/VRM10/Runtime/IO/VrmAnimationImporter.cs index 5ba5bf037..da97ded14 100644 --- a/Assets/VRM10/Runtime/IO/VrmAnimationImporter.cs +++ b/Assets/VRM10/Runtime/IO/VrmAnimationImporter.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using UniGLTF; +using UniGLTF.Extensions.VRMC_vrm_animation; using UniHumanoid; using UniJSON; using UnityEngine; @@ -10,9 +12,10 @@ using VRMShaders; namespace UniVRM10 { - public class VrmAnimationImporter : UniGLTF.ImporterContext { + VRMC_vrm_animation m_vrma; + public VrmAnimationImporter(GltfData data, IReadOnlyDictionary externalObjectMap = null, ITextureDeserializer textureDeserializer = null, @@ -20,6 +23,23 @@ namespace UniVRM10 : base(data, externalObjectMap, textureDeserializer, materialGenerator) { InvertAxis = Axes.X; + + m_vrma = GetExtension(Data); + } + + private static VRMC_vrm_animation GetExtension(GltfData data) + { + if (data.GLTF.extensions is UniGLTF.glTFExtensionImport extensions) + { + foreach (var kv in extensions.ObjectItems()) + { + if (kv.Key.GetString() == "VRMC_vrm_animation") + { + return UniGLTF.Extensions.VRMC_vrm_animation.GltfDeserializer.Deserialize(kv.Value); + } + } + } + return null; } private static int? GetNodeIndex(UniGLTF.Extensions.VRMC_vrm_animation.Humanoid humanoid, HumanBodyBones bone) @@ -88,86 +108,132 @@ namespace UniVRM10 public Dictionary GetHumanMap() { var humanMap = new Dictionary(); - if (Data.GLTF.extensions is UniGLTF.glTFExtensionImport extensions) + if (m_vrma is UniGLTF.Extensions.VRMC_vrm_animation.VRMC_vrm_animation animation && animation.Humanoid != null) { - foreach (var kv in extensions.ObjectItems()) + foreach (HumanBodyBones bone in UniGLTF.Utils.CachedEnum.GetValues()) { - if (kv.Key.GetString() == "VRMC_vrm_animation") + // Debug.Log($"{bone} => {index}"); + var node = GetNodeIndex(animation.Humanoid, bone); + if (node.HasValue) { - var animation = UniGLTF.Extensions.VRMC_vrm_animation.GltfDeserializer.Deserialize(kv.Value); - if (animation.Humanoid != null) - { - foreach (HumanBodyBones bone in UniGLTF.Utils.CachedEnum.GetValues()) - { - // Debug.Log($"{bone} => {index}"); - var node = GetNodeIndex(animation.Humanoid, bone); - if (node.HasValue) - { - humanMap.Add(bone, Nodes[node.Value]); - } - } - } + humanMap.Add(bone, Nodes[node.Value]); } } } return humanMap; } - public List<(ExpressionKey, Transform)> GetExpressions() + class ExpressionInfo { - var expressions = new List<(ExpressionKey, Transform)>(); - if (Data.GLTF.extensions is UniGLTF.glTFExtensionImport extensions) + public ExpressionKey Key; + public int ChannelIndex; + public string PropertyName; + public glTFAnimationChannel Channel; + } + + ExpressionInfo GetExpression(glTFAnimation animation, ExpressionKey key, string propertyName, Expression expression) + { + if (expression.Node.HasValue) { - foreach (var kv in extensions.ObjectItems()) + for (int i = 0; i < animation.channels.Count; ++i) { - if (kv.Key.GetString() == "VRMC_vrm_animation") + var channel = animation.channels[i]; + if (channel.target.node == expression.Node.Value) { - var animation = UniGLTF.Extensions.VRMC_vrm_animation.GltfDeserializer.Deserialize(kv.Value); - if (animation.Expressions != null) + return new ExpressionInfo { - foreach (ExpressionPreset preset in UniGLTF.Utils.CachedEnum.GetValues()) - { - var node = GetNodeIndex(animation.Expressions, preset); - if (node.HasValue) - { - expressions.Add((ExpressionKey.CreateFromPreset(preset), Nodes[node.Value])); - } - } - } + Key = key, + ChannelIndex = i, + PropertyName = propertyName, + Channel = channel, + }; } } } - return expressions; + return null; } - static int? GetNodeIndex(UniGLTF.Extensions.VRMC_vrm_animation.Expressions expressions, ExpressionPreset preset) + IEnumerable IterateExpressions() { - switch (preset) + if (m_vrma is UniGLTF.Extensions.VRMC_vrm_animation.VRMC_vrm_animation animation && animation.Expressions != null) { - case ExpressionPreset.happy: return expressions.Preset?.Happy?.Node; - case ExpressionPreset.angry: return expressions.Preset?.Angry?.Node; - case ExpressionPreset.sad: return expressions.Preset?.Sad?.Node; - case ExpressionPreset.relaxed: return expressions.Preset?.Relaxed?.Node; - case ExpressionPreset.surprised: return expressions.Preset?.Surprised?.Node; - case ExpressionPreset.aa: return expressions.Preset?.Aa?.Node; - case ExpressionPreset.ih: return expressions.Preset?.Ih?.Node; - case ExpressionPreset.ou: return expressions.Preset?.Ou?.Node; - case ExpressionPreset.ee: return expressions.Preset?.Ee?.Node; - case ExpressionPreset.oh: return expressions.Preset?.Oh?.Node; - case ExpressionPreset.blink: return expressions.Preset?.Blink?.Node; - case ExpressionPreset.blinkLeft: return expressions.Preset?.BlinkLeft?.Node; - case ExpressionPreset.blinkRight: return expressions.Preset?.BlinkRight?.Node; - case ExpressionPreset.lookUp: return expressions.Preset?.LookUp?.Node; - case ExpressionPreset.lookDown: return expressions.Preset?.LookDown?.Node; - case ExpressionPreset.lookLeft: return expressions.Preset?.LookLeft?.Node; - case ExpressionPreset.lookRight: return expressions.Preset?.LookRight?.Node; - case ExpressionPreset.neutral: return expressions.Preset?.Neutral?.Node; + var gltfAnimation = Data.GLTF.animations[0]; + if (animation.Expressions.Preset is Preset preset) + { + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.happy), "preset_happy", preset.Happy) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.angry), "preset_angry", preset.Angry) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.sad), "preset_sad", preset.Sad) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.relaxed), "preset_relaxed", preset.Relaxed) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.surprised), "preset_surprised", preset.Surprised) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.aa), "preset_aa", preset.Aa) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.ih), "preset_ih", preset.Ih) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.ou), "preset_ou", preset.Ou) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.ee), "preset_ee", preset.Ee) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.oh), "preset_oh", preset.Oh) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.blink), "preset_blink", preset.Blink) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.blinkLeft), "preset_blinkLeft", preset.BlinkLeft) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.blinkRight), "preset_blinkRight", preset.BlinkRight) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.lookUp), "preset_lookUp", preset.LookUp) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.lookDown), "preset_lookDown", preset.LookDown) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.lookLeft), "preset_lookLeft", preset.LookLeft) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.lookRight), "preset_lookRight", preset.LookRight) is ExpressionInfo info) yield return info; } + { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.neutral), "preset_neutral", preset.Neutral) is ExpressionInfo info) yield return info; } + } + if (animation.Expressions.Custom != null) + { + + } } - return default; + } + + /// + /// xyz translation カーブから x だけの カーブを生成する + /// + /// + /// + /// + static AnimationCurve CreateCurve( + float[] input, + float[] output) + { + var keyframes = new List(); + for (var inputIndex = 0; inputIndex < input.Length; ++inputIndex) + { + var time = input[inputIndex]; + // translation + var value = output[inputIndex * 3]; + keyframes.Add(new Keyframe(time, value, 0, 0)); + if (keyframes.Count > 0) + { + AnimationImporterUtil.CalculateTangent(keyframes, keyframes.Count - 1); + } + } + + var curve = new AnimationCurve(); + foreach (var keyFrame in keyframes) + { + curve.AddKey(keyFrame); + } + return curve; } public override async Task LoadAsync(IAwaitCaller awaitCaller, Func measureTime = null) { + // Expression は AnimationClip を分ける。 + // glTFData から関連 Animation を取り除いて、取っておく。 + var expressions = IterateExpressions().ToArray(); + foreach (var channelIndex in expressions.Select(x => x.ChannelIndex).OrderByDescending(x => x)) + { + var nodeIndex = Data.GLTF.animations[0].channels[channelIndex].target.node; + Data.GLTF.nodes.RemoveAt(nodeIndex); + // 後ろから順に channel を除去 + Data.GLTF.animations[0].channels.RemoveAt(channelIndex); + + Debug.Log($"remove: {channelIndex}"); + } + Data.GLTF.scenes[0].nodes = Data.GLTF.scenes[0].nodes.Take(1).ToArray(); + + // Humanoid Animation が Gltf アニメーションとしてロードされる var instance = await base.LoadAsync(awaitCaller, measureTime); // setup humanoid @@ -185,9 +251,30 @@ namespace UniVRM10 animator.avatar = avatar; } + if (expressions.Length > 0) + { + var animation = instance.GetComponent(); + var clip = animation.clip; + + // Expression の float カーブを追加する + // VrmAnimationInstance の "preset_xx" field に連動する + var gltfAnimation = Data.GLTF.animations[0]; + foreach (var expression in expressions) + { + var channel = expression.Channel; + var sampler = gltfAnimation.samplers[channel.sampler]; + var input = Data.GetArrayFromAccessor(sampler.input); + var output = Data.FlatternFloatArrayFromAccessor(sampler.output); + var curve = CreateCurve( + input.ToArray(), + output.ToArray()); + clip.SetCurve("", typeof(VrmAnimationInstance), expression.PropertyName, curve); + } + } + // VRMA-animation solver var animationInstance = instance.gameObject.AddComponent(); - animationInstance.Initialize(GetExpressions()); + animationInstance.Initialize(expressions.Select(x => x.Key)); return instance; } diff --git a/Assets/VRM10_Samples/VRM10Viewer/Motions/BvhMotion.cs b/Assets/VRM10_Samples/VRM10Viewer/Motions/BvhMotion.cs index 0a801d555..e3993d08c 100644 --- a/Assets/VRM10_Samples/VRM10Viewer/Motions/BvhMotion.cs +++ b/Assets/VRM10_Samples/VRM10Viewer/Motions/BvhMotion.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using UniHumanoid; @@ -13,7 +14,7 @@ namespace UniVRM10.VRM10Viewer public SkinnedMeshRenderer BoxMan => m_boxMan; (INormalizedPoseProvider, ITPoseProvider) m_controlRig; (INormalizedPoseProvider, ITPoseProvider) IMotion.ControlRig => m_controlRig; - public IDictionary ExpressionMap { get; } = new Dictionary(); + public IDictionary> ExpressionMap { get; } = new Dictionary>(); public BvhMotion(UniHumanoid.BvhImporterContext context) { m_context = context; diff --git a/Assets/VRM10_Samples/VRM10Viewer/Motions/IMotion.cs b/Assets/VRM10_Samples/VRM10Viewer/Motions/IMotion.cs index 656548b24..5c74619ed 100644 --- a/Assets/VRM10_Samples/VRM10Viewer/Motions/IMotion.cs +++ b/Assets/VRM10_Samples/VRM10Viewer/Motions/IMotion.cs @@ -7,7 +7,7 @@ namespace UniVRM10.VRM10Viewer public interface IMotion : IDisposable { (INormalizedPoseProvider, ITPoseProvider) ControlRig { get; } - IDictionary ExpressionMap { get; } + IDictionary> ExpressionMap { get; } void ShowBoxMan(bool enable); } diff --git a/Assets/VRM10_Samples/VRM10Viewer/Motions/VrmAnimation.cs b/Assets/VRM10_Samples/VRM10Viewer/Motions/VrmAnimation.cs index 2954251b5..0d801c0a1 100644 --- a/Assets/VRM10_Samples/VRM10Viewer/Motions/VrmAnimation.cs +++ b/Assets/VRM10_Samples/VRM10Viewer/Motions/VrmAnimation.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading.Tasks; using UniGLTF; @@ -12,7 +13,7 @@ namespace UniVRM10.VRM10Viewer private readonly VrmAnimationInstance m_instance; (INormalizedPoseProvider, ITPoseProvider) IMotion.ControlRig => m_instance.ControlRig; - IDictionary IMotion.ExpressionMap => m_instance.ExpressionMap; + IDictionary> IMotion.ExpressionMap => m_instance.ExpressionMap; public VrmAnimation(VrmAnimationInstance instance, Vector3 hips = default, Dictionary map = null) diff --git a/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs b/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs index 88969baa9..2b7410322 100644 --- a/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs +++ b/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs @@ -370,7 +370,7 @@ namespace UniVRM10.VRM10Viewer foreach (var (k, v) in Motion.ExpressionMap) { // VRMA-expression use localPosition.x as expression weight - m_loaded.Runtime.Expression.SetWeight(k, -v.transform.localPosition.x); + m_loaded.Runtime.Expression.SetWeight(k, v()); } } else