在unity中将Audio2Face导出的Json转AnimationClip
·
量大的时候要通过Blender中转有点麻烦,还是用代码批处理方便,也顺便一些调整和瘦身;
JSON是用BS_ARKIT导出的,如果是用BS_46,需要对应的PropertyName;
BS名首字母是大写的是为了对应项目里已有的资源;
眨眼和注视是通过代码控制的,所以过滤掉Blink和Look的BS;
话说Audio2Face这么久不更新了,现在开始搞这个是不是有点晚了,但是UNITY不争气啊;
//DODO
//将Audio2Face导出的权重Json转换为动画
using UnityEngine;
using Newtonsoft.Json;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
using System.Globalization;
public class Json2Anim : EditorWindow
{
public static DefaultAsset targetFolder;
static int DecimalAccuracy = 10;
public static int Accuracy = 1;
public static float KeyReductionError = 0.5f;
public static float totalFactor = 1f;
public static float browFactor = 1f;
public static float cheekFactor = 1f;
public static float eyeFactor = 1f;
public static float noseFactor = 1f;
public static float mouthFactor = 1f;
public static float jawFactor = 1f;
readonly string[] propertyType = { "Brow", "Cheek", "Eye", "Nose", "Mouth", "Jaw" };
//SkinnedMeshRenderer的层级
readonly string propPath = "Geo_LOD1_Grp/OYi_LOD1_Grp/OYi_Head_LOD1_Geo";
[MenuItem("DodoTools/Json2Anim")]
static void Init()
{
Json2Anim window = EditorWindow.GetWindow<Json2Anim>("Json2Anim");
window.Show();
}
void OnGUI()
{
targetFolder = EditorGUILayout.ObjectField("Json Folder", targetFolder, typeof(DefaultAsset), false) as DefaultAsset;
Accuracy = EditorGUILayout.IntField("Accuracy", Accuracy);
KeyReductionError = EditorGUILayout.FloatField("Threshold", KeyReductionError);
totalFactor = EditorGUILayout.FloatField("Multiplier", totalFactor);
browFactor = EditorGUILayout.Slider("Brow", browFactor, 0, 10);
cheekFactor = EditorGUILayout.Slider("Cheek", cheekFactor, 0, 10);
eyeFactor = EditorGUILayout.Slider("Eye", eyeFactor, 0, 10);
noseFactor = EditorGUILayout.Slider("Nose", noseFactor, 0, 10);
mouthFactor = EditorGUILayout.Slider("Mouth", mouthFactor, 0, 10);
jawFactor = EditorGUILayout.Slider("Jaw", jawFactor, 0, 10);
if (GUILayout.Button("Apply"))
{
Convert(targetFolder);
}
}
void Convert(DefaultAsset folder)
{
string[] jsonPath = Directory.GetFiles(AssetDatabase.GetAssetPath(folder), "*.json", SearchOption.AllDirectories);
if (jsonPath.Length > 0)
{
for (int i = 0; i < jsonPath.Length; i++)
{
string path = jsonPath[i];
string json = File.ReadAllText(path);
FaceAnimationData data = JsonConvert.DeserializeObject<FaceAnimationData>(json);
Debug.Log(data.facsNames.Count);
Debug.Log(data.weightMat.Count);
AnimationClip clip = new AnimationClip();
clip.name = Path.GetFileNameWithoutExtension(data.trackPath);
clip.frameRate = data.exportFps;
float keyInterval = 1f / data.exportFps;
// 2. 创建曲线容器
Dictionary<string, AnimationCurve> curves = new Dictionary<string, AnimationCurve>();
// 3. 添加示例混合形状关键帧
for (int j = 0; j < data.facsNames.Count; j++)
{
if (data.facsNames[j].ToLower().Contains("look") || data.facsNames[j].ToLower().Contains("blink"))
{
continue;
}
data.facsNames[j] = PropNameFix(data.facsNames[j]);
for (int k = 0; k < data.weightMat.Count; k++)
{
AddKeyframe(curves, data.facsNames[j], k*keyInterval, data.weightMat[k][j]*100);
}
}
// 4. 将曲线应用到动画剪辑
foreach (var curve in curves)
{
clip.SetCurve(propPath, typeof(SkinnedMeshRenderer), curve.Key, curve.Value);
}
//clip =AnimOptimize(clip);
ProcessCurves(clip, KeyReductionError);
EditorUtility.SetDirty(clip);
// 5. 保存到Assets
AssetDatabase.CreateAsset(clip, Path.GetDirectoryName(jsonPath[i]) + "/" + clip.name + ".anim");
AssetDatabase.SaveAssets();
}
}
}
string PropNameFix(string input)
{
return "blendShape."+char.ToUpper(input[0], CultureInfo.CurrentCulture) +
input.Substring(1);
}
void AddKeyframe(Dictionary<string, AnimationCurve> curves,
string propertyName, float time, float value)
{
if (!curves.ContainsKey(propertyName))
{
curves[propertyName] = new AnimationCurve();
}
curves[propertyName].AddKey(time, value);
}
void ProcessCurves(AnimationClip clip, float Threshold)
{
foreach (var binding in AnimationUtility.GetCurveBindings(clip))
{
AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding);
Keyframe[] optimizedKeys = OptimizeKeyframes(curve.keys, Threshold, GetPropFactor(binding.propertyName));
AnimationUtility.SetEditorCurve(clip, binding,
new AnimationCurve(optimizedKeys));
}
}
float GetPropFactor(string name)
{
float factor = 1f;
string nodeType = "";
for (int i = 0; i < propertyType.Length; i++)
{
if (name.Contains(propertyType[i]))
{
nodeType = propertyType[i];
break;
}
}
switch (nodeType)
{
case "Brow":
factor = browFactor;
break;
case "Cheek":
factor = cheekFactor;
break;
case "Eye":
factor = eyeFactor;
break;
case "Nose":
factor = noseFactor;
break;
case "Mouth":
factor = mouthFactor;
break;
case "Jaw":
factor = jawFactor;
break;
default:
break;
}
return factor * totalFactor;
}
Keyframe[] OptimizeKeyframes(Keyframe[] originalKeys, float threshold,float factor)
{
DecimalAccuracy = (int)Mathf.Pow(10, Accuracy);
List<Keyframe> newKeys = new List<Keyframe>();
for (int i = 0; i < originalKeys.Length; i++)
{
originalKeys[i].value *= factor;
// 首尾帧必保留
if (i == 0 || i == originalKeys.Length - 1)
{
newKeys.Add(originalKeys[i]);
continue;
}
// 值变化超过阈值时保留
if (Mathf.Abs(originalKeys[i].value - newKeys[newKeys.Count - 1].value) > threshold)
{
//Debug.Log(originalKeys[i].value + "__" + Mathf.Round(originalKeys[i].value * DecimalAccuracy) + "__" + Mathf.Round(originalKeys[i].value * DecimalAccuracy) / DecimalAccuracy);
newKeys.Add(new Keyframe(
originalKeys[i].time,
Mathf.Round(originalKeys[i].value * DecimalAccuracy) / DecimalAccuracy // 精度控制
));
}
}
return newKeys.ToArray();
}
[System.Serializable]
public class FaceAnimationData
{
public float exportFps;
public string trackPath;
public int numPoses;
public int numFrames;
public List<string> facsNames;
public List<List<float>> weightMat;
}
}
更多推荐




所有评论(0)