【Unity】用于BlendShape的LookAt效果控制器

【Unity】用于BlendShape的LookAt效果控制器

为了控制游戏中角色眼球的朝向,通常会使用 LookAtIK 。但LookAtIK需要有眼球骨骼,如果动画师使用BlendShape制作了面部表情,可能会省略掉眼球骨骼。当然,更多的情况是,动画师为角色单独制作了一个高精度的带BlendShape的面部模型,这时,即使角色主体带有眼球骨骼,也无法直接控制到这个独立的面部模型。解决上述问题的关键点是为眼球分配一个骨骼,并将LookAtIK的计算结果换算成BlendShape权重。

如果角色不带有眼球骨骼,可以在角色的 Head 骨骼下建立一个空GameObject,将其位置对齐到两眼中间,不要有旋转,用来模拟眼球骨骼,然后将这个假的骨骼设置到LookAtIK的 Eyes 骨骼参数上;如果已有眼球骨骼,可以省略上一步。

有了眼球骨骼后,就可以在LookAtIK计算完成后(注册LookAtIK的 OnPostUpdate 回调),将眼球骨骼的旋转角度换算成BlendShape权重。最简单的换算就是线性地将骨骼旋转角度乘以一个系数得到BlendShape权重。如果需要更复杂的控制,可以增加一个 AnimationCurve 来动态调整换算系数。

如果没有使用LookAtIK,也可以利用眼睛的位置和眼睛直视目标的位置,通过三角函数手动计算眼球旋转,再换算成BlendShape参数。

下面给出一个利用FinalIK实现的线性换算控制器:

using RootMotion.FinalIK;
using UnityEngine;

/// <summary>
/// 用于BlendShape的LookAt效果控制器。
/// <br/>
/// 在Animator完成眼睛的BlendShape控制后,
/// 通过LookAtIK的计算结果重新计算并更新BlendShape参数,
/// 实现BlendShape的LookAt效果。
/// <br/>
/// 此组件需要配合BlendShape、LookAtIK和眼睛骨骼使用。
/// </summary>
public class BlendShapeLookAtController : MonoBehaviour
{
    
    
    [Tooltip("带有眼球BlendShape的蒙皮组件。")]
    [SerializeField]
    private SkinnedMeshRenderer _skin;

    [Tooltip("FinalIK的LookAtIK组件。")]
    [SerializeField]
    private LookAtIK _lookAtIK;

    [SerializeField]
    [Tooltip("眼球骨骼。")]
    private Transform _eyeBone;

    [Tooltip("视线每偏转1°时眼球BlendShape权重增加的百分点数。")]
    [SerializeField]
    [Range(-20f, 20f)]
    private float _eyeBlendShapeWeightPerDegree = 2;

    [Tooltip("眼球向上的BlendShape权重最大值。")]
    [SerializeField]
    [Range(0f, 100f)]
    private float _maxEyeUpBlendShapeWeight = 60;

    [Tooltip("眼球向下的BlendShape权重最大值。")]
    [SerializeField]
    [Range(0f, 100f)]
    private float _maxEyeDownBlendShapeWeight = 60;

    [Tooltip("眼球向左和向右的BlendShape权重最大值。")]
    [SerializeField]
    [Range(0f, 100f)]
    private float _maxEyeLeftRightBlendShapeWeight = 60;

    [SerializeField]
    [Tooltip("眼球向上的BlendShape权重索引(索引从0开始)。")]
    private ushort _eyeUpBlendShapeIndex = 10;

    [SerializeField]
    [Tooltip("眼球向下的BlendShape权重索引(索引从0开始)。")]
    private ushort _eyeDownBlendShapeIndex = 11;

    [SerializeField]
    [Tooltip("眼球向左的BlendShape权重索引(索引从0开始)。")]
    private ushort _eyeLeftBlendShapeIndex = 12;

    [SerializeField]
    [Tooltip("眼球向右的BlendShape权重索引(索引从0开始)。")]
    private ushort _eyeRightBlendShapeIndex = 13;


    private void Reset()
    {
    
    
        // SkinnedMeshRenderer
        var skins = GetComponentsInChildren<SkinnedMeshRenderer>();
        foreach (var skin in skins)
        {
    
    
            if (skin.name.ToUpper().Contains("FACE"))
            {
    
    
                _skin = skin;
                break;
            }
        }

        // LookAtIK
        _lookAtIK = GetComponentInParent<LookAtIK>();

        // Transform(Eye Bone)
        if (_lookAtIK && _lookAtIK.solver.eyes.Length > 0)
        {
    
    
            _eyeBone = _lookAtIK.solver.eyes[0].transform;
        }
    }

    private void Awake()
    {
    
    
        // IK 在 LateUpdate 中进行更新,此脚本在 IK 更新后再更新
        _lookAtIK.solver.OnPostUpdate += UpdateEyeLookAtBlendShapes;
    }

    /// <summary>
    /// 计算并更新眼球的BlendShape权重。
    /// </summary>
    private void UpdateEyeLookAtBlendShapes()
    {
    
    
        // 通过眼睛骨骼的本地空间旋转角计算BlendShape值
        var horizontal = _eyeBone.localEulerAngles.x;
        var vertical = _eyeBone.localEulerAngles.z;

        var eyeLeftBlendShapeWeight = 0f;
        var eyeRightBlendShapeWeight = 0f;
        var eyeUpBlendShapeWeight = 0f;
        var eyeDownBlendShapeWeight = 0f;

        // 计算眼球BlendShape权重
        if (horizontal > 180)
        {
    
    
            eyeRightBlendShapeWeight = Mathf.Min(_maxEyeLeftRightBlendShapeWeight, (360 - horizontal) * _eyeBlendShapeWeightPerDegree);
        }
        else
        {
    
    
            eyeLeftBlendShapeWeight = Mathf.Min(_maxEyeLeftRightBlendShapeWeight, horizontal * _eyeBlendShapeWeightPerDegree);
        }

        if (vertical > 180)
        {
    
    
            eyeDownBlendShapeWeight = Mathf.Min(_maxEyeDownBlendShapeWeight, (360 - vertical) * _eyeBlendShapeWeightPerDegree);
        }
        else
        {
    
    
            eyeUpBlendShapeWeight = Mathf.Min(_maxEyeUpBlendShapeWeight, vertical * _eyeBlendShapeWeightPerDegree);
        }

        // 设置眼球BlendShape权重
        _skin.SetBlendShapeWeight(_eyeUpBlendShapeIndex, eyeUpBlendShapeWeight);
        _skin.SetBlendShapeWeight(_eyeDownBlendShapeIndex, eyeDownBlendShapeWeight);
        _skin.SetBlendShapeWeight(_eyeLeftBlendShapeIndex, eyeLeftBlendShapeWeight);
        _skin.SetBlendShapeWeight(_eyeRightBlendShapeIndex, eyeRightBlendShapeWeight);
    }
}

猜你喜欢

转载自blog.csdn.net/qq_21397217/article/details/120966714