Unity 工具类 之 BlendShape 捏脸的实现

Unity 工具类 之 BlendShape 捏脸的实现

 

目录

Unity 工具类 之 BlendShape 捏脸的实现

一、简单介绍

二、实现原理

三、注意事项

四、效果预览

五、实现步骤

六、代码

七、参考工程


 

 

一、简单介绍

Blender 是一款开源的跨平台全能三维动画制作软件,提供从建模、动画、材质、渲染、到音频处理、视频剪辑等一系列动画短片制作解决方案。

在Unity程序开发中,使用 Blender 制作的模型,进行 BlendShap 捏脸的实现;

 

二、实现原理

1、通过控制改变 Skinned Mesh Renderer 的 BlendShapes 下的参数数值,来实现捏脸的效果

 

三、注意事项

1、Blender 中编辑模式下设置的值是实现好的捏脸效果的关键;

 

四、效果预览

Unity 工具类 之 BlendShape 捏脸的实现_第1张图片

 

五、实现步骤

1、打开Unity,新建一个工程,并且导入模型,添加到场景中,如下图

Unity 工具类 之 BlendShape 捏脸的实现_第2张图片

2、在场景中,添加几个Slider UI 组件,控制捏脸的数值,如下图

Unity 工具类 之 BlendShape 捏脸的实现_第3张图片

3、编写脚本,控制BlendShapes 对应数值的变化,把对应脚本挂载到对应组件上,如下图

Unity 工具类 之 BlendShape 捏脸的实现_第4张图片

Unity 工具类 之 BlendShape 捏脸的实现_第5张图片

4、运行场景,效果如下

Unity 工具类 之 BlendShape 捏脸的实现_第6张图片

 

六、代码

1、Singleton

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Singleton : MonoBehaviour
where T:MonoBehaviour
{
    private static T m_Instance;

    public static T Instance
    {
        get
        {
            return m_Instance;
        }

    }

    protected virtual void Awake()
    {
        m_Instance = this as T;
    }

}

2、BlendShape

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


/// 
/// 存储索引
/// 
public class BlendShape
{

    public int postiveIndex { get; set; }
    public int negativeIndex { get; set; }

    public BlendShape(int postiveIndex,int negativeIndex)
    {
        this.postiveIndex = postiveIndex;
        this.negativeIndex = negativeIndex;
    }
}

3、CharacterCustomization

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class CharacterCustomization : Singleton{

    public string suffixMin = "Min";
    public string suffixMax = "Max";
    public SkinnedMeshRenderer target;
    SkinnedMeshRenderer skm;
    Mesh mesh;
    Dictionary BlendShapeDatabase = new Dictionary();

    private void Start()
    {
        Initialize();
    }

   public  void Initialize()
    {
        skm = target;
        mesh = skm.sharedMesh;
        SaveBlendShapeDatabase();
    }

    #region 存储数据
    
    void SaveBlendShapeDatabase()
    {
        List BlendShapeNames = Enumerable.Range(0, mesh.blendShapeCount).Select(x => mesh.GetBlendShapeName(x)).ToList();
        for(int i = 0; BlendShapeNames.Count > 0;)
        {
            string noSuffix, altSuffix;
            noSuffix = BlendShapeNames[i].TrimEnd(suffixMax.ToCharArray()).TrimEnd(suffixMin.ToCharArray()).Trim();
            string positiveName = string.Empty;
            string negativeName = string.Empty;
            int postiveIndex = -1;
            int negativeIndex = -1;
            bool exist = false;

            //后缀是max
            if (BlendShapeNames[i].EndsWith(suffixMax))
            {
                positiveName = BlendShapeNames[i];
                altSuffix = noSuffix + " " + suffixMin;
                
                if (BlendShapeNames.Contains(altSuffix))
                    exist = true;

                postiveIndex = mesh.GetBlendShapeIndex(positiveName);

                if (exist)
                {
                    negativeName = altSuffix;
                    negativeIndex = mesh.GetBlendShapeIndex(negativeName);
                }
                   

                //后缀是min结尾
            }else if (BlendShapeNames[i].EndsWith(suffixMin))
                {
                    negativeName = BlendShapeNames[i];
                    altSuffix = noSuffix + " " + suffixMax;
                    
                    if (BlendShapeNames.Contains(altSuffix))
                        exist = true;

                    negativeIndex = mesh.GetBlendShapeIndex(negativeName);

                    if (exist)
                    {
                        positiveName = altSuffix;
                        postiveIndex = mesh.GetBlendShapeIndex(positiveName);
                    }


            }

            if (BlendShapeDatabase.ContainsKey(noSuffix))
                Debug.LogError(noSuffix +"已经存在");

            BlendShapeDatabase.Add(noSuffix, new BlendShape(postiveIndex, negativeIndex));

            //移除操作
            if (positiveName != string.Empty)
                BlendShapeNames.Remove(positiveName);
            if (negativeName != string.Empty)
                BlendShapeNames.Remove(negativeName);
        }

    }

    #endregion

    public void ChangeBlendShapeValue(string blendShapeName,float value)
    {
        if (!BlendShapeDatabase.ContainsKey(blendShapeName))
        {
            Debug.LogError(blendShapeName + "不存在");
            return;
        }

        BlendShape blendshape = BlendShapeDatabase[blendShapeName];
        value = Mathf.Clamp(value, -100, 100);
        if (value > 0)
        {
            if (blendshape.postiveIndex == -1)
                return;
            skm.SetBlendShapeWeight(blendshape.postiveIndex, value);
            if (blendshape.negativeIndex == -1)
                return;
            skm.SetBlendShapeWeight(blendshape.negativeIndex, 0);
        }
        else
        {
            if (blendshape.negativeIndex == -1)
                return;
            skm.SetBlendShapeWeight(blendshape.negativeIndex, -value);
            if (blendshape.postiveIndex == -1)
                return;
            skm.SetBlendShapeWeight(blendshape.postiveIndex, 0);
  
       
        }



    }


    public bool DoesTargetMatchSkm()
    {
        return (target == skm) ? true : false;
    }

    public void ClearDatabase()
    {
        BlendShapeDatabase.Clear();
    }

    public string[] GetBlendshapeNames()
    {
        return BlendShapeDatabase.Keys.ToArray();
    }

    public int GetNumber()
    {
        return BlendShapeDatabase.Count;
    }

    public BlendShape GetBlendShape(string name)
    {
        return BlendShapeDatabase[name];
    }
}


4、BlendShape

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BlendShapeSlider : MonoBehaviour {

    Slider slider;
    [Header("别加后缀!!!")]
    public string BlendShapeName;

    private void Start()
    {
        slider = GetComponent();
        slider.onValueChanged.AddListener(value => CharacterCustomization.Instance.ChangeBlendShapeValue(BlendShapeName, value));
    }
}

5、CharacterCustomizationEditor

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;

[CustomEditor(typeof(CharacterCustomization))]
public class CharacterCustomizationEditor : Editor
{
    int selectIndex;
    Canvas canvas;

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        EditorGUILayout.Space();

        var characterCustomization = (CharacterCustomization)target;

        if(characterCustomization.target == null)
        {
            EditorGUILayout.LabelField("请给target赋值!");
            return;
        }

        //是否换了新的skm
        if (!characterCustomization.DoesTargetMatchSkm())   
        {
            characterCustomization.ClearDatabase();
        }

        if(characterCustomization.GetNumber() <= 0)
        {
            characterCustomization.Initialize();
        }
        
        string[] blendshapeNames = characterCustomization.GetBlendshapeNames();

        if(blendshapeNames.Length <= 0)
        {
            EditorGUILayout.LabelField("taget没有blendshape");
            characterCustomization.ClearDatabase(); 
            return;
        }

        EditorGUILayout.LabelField("请创建一个滑动条~", EditorStyles.boldLabel);

        selectIndex = EditorGUILayout.Popup("blendShapeName", selectIndex, blendshapeNames);

        if (GUILayout.Button("创建滑动条"))
        {
            if(canvas == null)
            {
                canvas = GameObject.FindObjectOfType();
            }

            if(canvas == null)
            {
                throw new System.Exception("场景中没有canvas ,请创建!");
                
            }

            GameObject sliderGo = Instantiate(Resources.Load("slider", typeof(GameObject))) as GameObject;

            var BShapeSlider = sliderGo.GetComponent();
            //改名字
            //改父物体
            //大小
            BShapeSlider.BlendShapeName = blendshapeNames[selectIndex];
            BShapeSlider.name = blendshapeNames[selectIndex];
            BShapeSlider.transform.parent = canvas.transform;
            BShapeSlider.GetComponent().sizeDelta = new Vector2(140f, 25f);
            BShapeSlider.GetComponentInChildren().text = blendshapeNames[selectIndex];

            //获取BlendShape
            BlendShape blendShape = characterCustomization.GetBlendShape(blendshapeNames[selectIndex]);

            //获取slider
            Slider slider = sliderGo.GetComponent();

            if (blendShape.negativeIndex == -1)
                slider.minValue = 0;

            if (blendShape.postiveIndex == -1)
                slider.maxValue = 0;

            slider.value = 0;

            Debug.Log(blendshapeNames[selectIndex] + "slider 创建完成!");
        }

    }

}

 

七、参考工程

参考工程,点击下载

你可能感兴趣的:(基础,Unity,实用工具,Unity,BlendShape,捏脸)