通过Blender中转的教程

量大的时候要通过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;
    }
}

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐