目录
Unity 工具类 之 BlendShape 捏脸的实现
一、简单介绍
二、实现原理
三、注意事项
四、效果预览
五、实现步骤
六、代码
七、参考工程
Blender 是一款开源的跨平台全能三维动画制作软件,提供从建模、动画、材质、渲染、到音频处理、视频剪辑等一系列动画短片制作解决方案。
在Unity程序开发中,使用 Blender 制作的模型,进行 BlendShap 捏脸的实现;
1、通过控制改变 Skinned Mesh Renderer 的 BlendShapes 下的参数数值,来实现捏脸的效果
1、Blender 中编辑模式下设置的值是实现好的捏脸效果的关键;
1、打开Unity,新建一个工程,并且导入模型,添加到场景中,如下图
2、在场景中,添加几个Slider UI 组件,控制捏脸的数值,如下图
3、编写脚本,控制BlendShapes 对应数值的变化,把对应脚本挂载到对应组件上,如下图
4、运行场景,效果如下
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
参考工程,点击下载