mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-08-25 20:16:05 -05:00
Implements expression overriding https://github.com/vrm-c/vrm-specification/pull/196
This commit is contained in:
@@ -136,6 +136,9 @@ namespace UniVRM10
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
// Setup runtime function.
|
||||
m_target.Setup();
|
||||
|
||||
// base.OnInspectorGUI();
|
||||
switch (_tab)
|
||||
@@ -176,14 +179,6 @@ namespace UniVRM10
|
||||
void ExpressionGUI()
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Override rates", EditorStyles.boldLabel);
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
{
|
||||
EditorGUILayout.Slider("Blink override rate", m_target.Expression.BlinkOverrideRate, 0f, 1f);
|
||||
EditorGUILayout.Slider("LookAt override rate", m_target.Expression.LookAtOverrideRate, 0f, 1f);
|
||||
EditorGUILayout.Slider("Mouth override rate", m_target.Expression.MouthOverrideRate, 0f, 1f);
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
@@ -197,6 +192,9 @@ namespace UniVRM10
|
||||
|
||||
if (m_sliders != null)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Expression Weights", EditorStyles.boldLabel);
|
||||
|
||||
var sliders = m_sliders.Select(x => x.Slider());
|
||||
foreach (var slider in sliders)
|
||||
{
|
||||
@@ -204,6 +202,16 @@ namespace UniVRM10
|
||||
}
|
||||
m_target.Expression.SetWeights(m_expressionKeyWeights);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Override rates", EditorStyles.boldLabel);
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
{
|
||||
EditorGUILayout.Slider("Blink override rate", m_target.Expression.BlinkOverrideRate, 0f, 1f);
|
||||
EditorGUILayout.Slider("LookAt override rate", m_target.Expression.LookAtOverrideRate, 0f, 1f);
|
||||
EditorGUILayout.Slider("Mouth override rate", m_target.Expression.MouthOverrideRate, 0f, 1f);
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
|
||||
void OnSceneGUI()
|
||||
|
||||
@@ -1,37 +1,101 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using VrmLib;
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
public sealed class DefaultExpressionValidator : IExpressionValidator
|
||||
{
|
||||
private readonly Dictionary<ExpressionKey, VRM10Expression> _expressions;
|
||||
|
||||
private DefaultExpressionValidator(VRM10ExpressionAvatar expressionAvatar)
|
||||
{
|
||||
|
||||
_expressions = expressionAvatar.Clips.ToDictionary(ExpressionKey.CreateFromClip, x => x);
|
||||
}
|
||||
|
||||
public void Validate(IReadOnlyDictionary<ExpressionKey, float> inputWeights, IDictionary<ExpressionKey, float> actualWeights,
|
||||
LookAtEyeDirection inputEyeDirection, out LookAtEyeDirection actualEyeDirection,
|
||||
out float blinkOverrideRate, out float lookAtOverrideRate, out float mouthOverrideRate)
|
||||
{
|
||||
// weights
|
||||
// override rate
|
||||
blinkOverrideRate = 0f;
|
||||
lookAtOverrideRate = 0f;
|
||||
mouthOverrideRate = 0f;
|
||||
|
||||
// 1. Set weights and Accumulate override rates.
|
||||
foreach (var (key, weight) in inputWeights)
|
||||
{
|
||||
if (!actualWeights.ContainsKey(key))
|
||||
{
|
||||
actualWeights.Add(key, weight);
|
||||
}
|
||||
|
||||
// Set weight.
|
||||
actualWeights[key] = weight;
|
||||
|
||||
// Get expression.
|
||||
if (!_expressions.ContainsKey(key)) continue;
|
||||
var expression = _expressions[key];
|
||||
|
||||
// Override rate without targeting myself.
|
||||
if (!key.IsBlink)
|
||||
{
|
||||
blinkOverrideRate = Mathf.Max(blinkOverrideRate, GetOverrideRate(expression.OverrideBlink, weight));
|
||||
}
|
||||
if (!key.IsLookAt)
|
||||
{
|
||||
lookAtOverrideRate = Mathf.Max(lookAtOverrideRate, GetOverrideRate(expression.OverrideLookAt, weight));
|
||||
}
|
||||
if (!key.IsMouth)
|
||||
{
|
||||
mouthOverrideRate = Mathf.Max(mouthOverrideRate, GetOverrideRate(expression.OverrideMouth, weight));
|
||||
}
|
||||
}
|
||||
|
||||
// eye direction
|
||||
actualEyeDirection = inputEyeDirection;
|
||||
// 2. Saturate rate.
|
||||
blinkOverrideRate = Mathf.Clamp01(blinkOverrideRate);
|
||||
lookAtOverrideRate = Mathf.Clamp01(lookAtOverrideRate);
|
||||
mouthOverrideRate = Mathf.Clamp01(mouthOverrideRate);
|
||||
|
||||
// override rate
|
||||
blinkOverrideRate = 0f;
|
||||
lookAtOverrideRate = 0f;
|
||||
mouthOverrideRate = 0f;
|
||||
var blinkMultiplier = 1f - blinkOverrideRate;
|
||||
var lookAtMultiplier = 1f - lookAtOverrideRate;
|
||||
var mouthMultiplier = 1f - mouthOverrideRate;
|
||||
|
||||
// 3. Set procedural key's weights.
|
||||
foreach (var (key, weight) in inputWeights)
|
||||
{
|
||||
if (key.IsBlink)
|
||||
{
|
||||
actualWeights[key] = weight * blinkMultiplier;
|
||||
}
|
||||
else if (key.IsLookAt)
|
||||
{
|
||||
actualWeights[key] = weight * lookAtMultiplier;
|
||||
}
|
||||
else if (key.IsMouth)
|
||||
{
|
||||
actualWeights[key] = weight * mouthMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. eye direction
|
||||
actualEyeDirection = LookAtEyeDirection.Multiply(inputEyeDirection, 1f - lookAtOverrideRate);
|
||||
}
|
||||
|
||||
private float GetOverrideRate(ExpressionOverrideType type, float weight)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ExpressionOverrideType.None:
|
||||
return 0f;
|
||||
case ExpressionOverrideType.Block:
|
||||
return 1f;
|
||||
case ExpressionOverrideType.Blend:
|
||||
return weight;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Factory : IExpressionValidatorFactory
|
||||
|
||||
@@ -29,5 +29,15 @@
|
||||
RightYaw = rightYaw;
|
||||
RightPitch = rightPitch;
|
||||
}
|
||||
|
||||
public static LookAtEyeDirection Multiply(LookAtEyeDirection a, float b)
|
||||
{
|
||||
return new LookAtEyeDirection(
|
||||
a.LeftYaw * b,
|
||||
a.LeftPitch * b,
|
||||
a.RightYaw * b,
|
||||
a.RightPitch * b
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,10 @@ namespace UniVRM10
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
Expression.Dispose();
|
||||
if (Expression != null)
|
||||
{
|
||||
Expression.Restore();
|
||||
}
|
||||
|
||||
if (ModelAsset != null)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ using VrmLib;
|
||||
namespace UniVRM10
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class VRM10ControllerExpression : IDisposable
|
||||
public sealed class VRM10ControllerExpression
|
||||
{
|
||||
public static IExpressionValidatorFactory ExpressionValidatorFactory = new DefaultExpressionValidator.Factory();
|
||||
|
||||
@@ -31,12 +31,6 @@ namespace UniVRM10
|
||||
public float LookAtOverrideRate { get; private set; }
|
||||
public float MouthOverrideRate { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_merger?.RestoreMaterialInitialValues();
|
||||
_eyeDirectionApplicable?.Restore();
|
||||
}
|
||||
|
||||
internal void Setup(Transform transform, ILookAtEyeDirectionProvider eyeDirectionProvider, ILookAtEyeDirectionApplicable eyeDirectionApplicable)
|
||||
{
|
||||
if (ExpressionAvatar == null)
|
||||
@@ -45,14 +39,31 @@ namespace UniVRM10
|
||||
return;
|
||||
}
|
||||
|
||||
Restore();
|
||||
|
||||
_merger = new ExpressionMerger(ExpressionAvatar.Clips, transform);
|
||||
_keys = ExpressionAvatar.Clips.Select(ExpressionKey.CreateFromClip).ToList();
|
||||
var oldInputWeights = _inputWeights;
|
||||
_inputWeights = _keys.ToDictionary(x => x, x => 0f);
|
||||
foreach (var key in _keys)
|
||||
{
|
||||
// remain user input weights.
|
||||
if (oldInputWeights.ContainsKey(key)) _inputWeights[key] = oldInputWeights[key];
|
||||
}
|
||||
_actualWeights = _keys.ToDictionary(x => x, x => 0f);
|
||||
_validator = ExpressionValidatorFactory.Create(ExpressionAvatar);
|
||||
_eyeDirectionProvider = eyeDirectionProvider;
|
||||
_eyeDirectionApplicable = eyeDirectionApplicable;
|
||||
}
|
||||
|
||||
internal void Restore()
|
||||
{
|
||||
_merger?.RestoreMaterialInitialValues();
|
||||
_merger = null;
|
||||
|
||||
_eyeDirectionApplicable?.Restore();
|
||||
_eyeDirectionApplicable = null;
|
||||
}
|
||||
|
||||
public void Process()
|
||||
{
|
||||
@@ -107,7 +118,7 @@ namespace UniVRM10
|
||||
private void Apply()
|
||||
{
|
||||
// 1. Get eye direction from provider.
|
||||
_inputEyeDirection = _eyeDirectionProvider.EyeDirection;
|
||||
_inputEyeDirection = _eyeDirectionProvider?.EyeDirection ?? default;
|
||||
|
||||
// 2. Validate user input, and Output as actual weights.
|
||||
_validator.Validate(_inputWeights, _actualWeights,
|
||||
|
||||
@@ -136,7 +136,12 @@ namespace UniVRM10
|
||||
m_head = head;
|
||||
m_leftEye = animator.GetBoneTransform(HumanBodyBones.LeftEye);
|
||||
m_rightEye = animator.GetBoneTransform(HumanBodyBones.RightEye);
|
||||
if (Gaze == null)
|
||||
|
||||
var isRuntimeAsset = true;
|
||||
#if UNITY_EDITOR
|
||||
isRuntimeAsset = Application.isPlaying && !PrefabUtility.IsPartOfAnyPrefab(m_head);
|
||||
#endif
|
||||
if (isRuntimeAsset && LookAtTargetType == LookAtTargetTypes.CalcYawPitchToGaze && Gaze == null)
|
||||
{
|
||||
Gaze = new GameObject().transform;
|
||||
Gaze.name = "__LOOKAT_GAZE__";
|
||||
|
||||
Reference in New Issue
Block a user