找回密码
 立即注册
查看: 738|回复: 2

[笔记] Unity动画TA:在导入fbx时直接分解出Mesh文件及自动生成 ...

[复制链接]
发表于 2021-11-24 17:44 | 显示全部楼层 |阅读模式
感觉很多项目喜欢这么做……剥离fbx里的所有资源然后把fbx删掉。于是写了一个有待完善的个人代码块。
目前没有经过复杂测试,可能有很多bug。
依赖一些规则,如SkinnedMesh一定要和Root同级。
可能有很多写法幼稚的部分,如果大佬们实在忍不住,请评论区或私信留下优化方式。
to-do: 天色晚了,没有把材质处理保存进去,接下来更新了再说。
【只想参考代码的同学请直接翻到最后】
<hr/>更新于2021.11.17:
新增支持导出材质,如果导入了同名材质不会覆盖原有的材质(毕竟原有的材质可能是被调好的)
一个fbx中的每个SkinnedMesh会被视为一个装备,单独导出为一个Prefab。
新增支持导出静态网格。如果所有的Mesh都是静态网格,则只保存成一个Prefab。
新增拖动导入后,工程里完全看不见fbx文件,只有被分解后的mesh、material和perfab,不劳有强迫症的同学手动删除fbx矣。
测试1:导入单个骨骼网格体

声明:Mannequin模型来源于虚幻官方。

工具导入骨骼网格体
https://www.zhihu.com/video/1444453808764481536
fbx全程不出现,直接成了Prefab。
测试2:导入有多个submesh的静态网格体的FBX

dcc里随便拖四个box,然后导出fbx文件。

工具导入静态网格体
https://www.zhihu.com/video/1444455676902412288
于是成功导入四个mesh资源,外加一个保持原来形状的Prefab。
测试3:导入带有多个带蒙皮的子模型的FBX

其实就是看有项目组喜欢一套外装分开导出,于是目前生成多个预制体,每个蒙皮网格生成一个。对于不喜欢这个生成预制体方式的同学来说,让蒙皮子模型保持为一个prefab的代码会简单得多,目前建议自己两分钟写完 0.0
使用刚才测试静态网格的四个box,每个都加上蒙皮,再导出一次就成了含有多个带蒙皮的子模型的fbx文件。

导入多个蒙皮子网格
https://www.zhihu.com/video/1444459825879379968
to-do:
动画TA的练手工具现在就还剩下导入动画不支持了,下次再说吧……

<hr/>更新于2021.11.19:
新增导入时分解出动画片段的功能。具体代码可以在下边翻,或者看下一篇具体解释。
加了两个用于调试的没啥用的宏,修改了删fbx的位置。
https://zhuanlan.zhihu.com/p/435022514
<hr/>
#define USE_IMPORTOR
#define USE_NO_FBX

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



namespace ZxtTool
{
#if(USE_IMPORTOR)
    public class CharacterFbxImporter : AssetPostprocessor
    {
        void OnpreprocessModel() {
            ModelImporter modelImporter = assetImporter as ModelImporter;
            modelImporter.clipAnimations = modelImporter.defaultClipAnimations;

            ModelImporterClipAnimation[] clip_animations = modelImporter.clipAnimations;
            for (int i = 0; i < clip_animations.Length; i++) {
                //to-do:目前什么都没做,但肯定要做点什么,比如设置根运动类型之类的
            }

            modelImporter.animationType =  clip_animations.Length > 0 ? ModelImporterAnimationType.Generic: ModelImporterAnimationType.None;
            modelImporter.SaveAndReimport();
        }

        void OnPostprocessModel(GameObject g)
        {
            ModelImporter modelImporter = assetImporter as ModelImporter;
            if (modelImporter == null) return;

            string dir = Path.GetDirectoryName(assetPath);



#if (USE_NO_FBX)

            if (g) {
                //得到骨骼(?)


                SkinnedMeshRenderer[] smrs = g.GetComponentsInChildren<SkinnedMeshRenderer>();
                MeshRenderer[] mrs = g.GetComponentsInChildren<MeshRenderer>();

                if (smrs.Length > 0)
                {
                    Mesh[] meshes = new Mesh[smrs.Length];
                    for (int i = 0; i < smrs.Length; i++)
                    {
                        GameObject skeleton = SkeletonTool.FormSkeleton(g);
                        GameObject skinObj = new GameObject();
                        SkinnedMeshRenderer smr = skinObj.AddComponent<SkinnedMeshRenderer>();
                        //to-do:在资源极端不规范的情况下,有可能出现local scale和lossy scale相差很大的情况,比如Root存在非等比缩放或者蒙皮在Root下边的层级,这种情况没考虑到
                        skinObj.transform.parent = skeleton.transform;

                        meshes = smrs.sharedMesh;

                        string mesh_path = Path.Combine(dir, meshes.name + ".asset");
                        AssetDatabase.CreateAsset(meshes, mesh_path);

                        Material[] mats = smrs.sharedMaterials;
                        string[] mat_paths = new string[mats.Length];

                        for (int j = 0; j < mats.Length; j++)
                        {
                            mat_paths[j] = Path.Combine(dir, mats[j].name + ".mat");
                            string full_path = Path.Combine(Directory.GetParent(Application.dataPath).FullName, mat_paths[j]);
                            if (File.Exists(full_path))
                            {
                                //不替换已经存在的同名资源
                                continue;
                            }
                            AssetDatabase.CreateAsset(mats[j], mat_paths[j]);
                        }

                        Mesh mesh = AssetDatabase.LoadAssetAtPath<Mesh>(mesh_path);
                        //清空mat,虽然也许没必要
                        mats = new Material[mats.Length];
                        for (int j = 0; j < mats.Length; j++)
                        {
                            mats[j] = AssetDatabase.LoadAssetAtPath<Material>(mat_paths[j]);
                        }

                        GameObject skinObjOriginal = smrs.gameObject;
                        skinObj.transform.position = skinObjOriginal.transform.position;
                        skinObj.transform.rotation = skinObjOriginal.transform.rotation;
                        skinObj.transform.localScale = skinObjOriginal.transform.localScale;
                        skinObj.name = skinObjOriginal.name;

                        smr.sharedMesh = mesh;
                        smr.bones = SkeletonTool.MatchBonesByName(smrs.bones, skeleton);
                        smr.rootBone = SkeletonTool.MatchBoneByName(smrs.rootBone, skeleton);
                        smr.sharedMaterials = mats;

                        string prefab_path = Path.Combine(dir, meshes.name + ".prefab");

                        //prefab_path = AssetDatabase.GenerateUniqueAssetPath(prefab_path);
                        PrefabUtility.SaveAsPrefabAssetAndConnect(skeleton, prefab_path, InteractionMode.AutomatedAction);
                        Object.DestroyImmediate(skeleton);
                    }

                    //AssetDatabase.DeleteAsset(assetPath);
                    //meta已经被删除,但还是会弹一个“存在meta file”的警告
                    //AssetDatabase.DeleteAsset(Path.Combine(assetPath, ".meta"));
                    AssetDatabase.Refresh();
                }
                else if (mrs.Length > 0) {
                    Mesh[] meshes = new Mesh[mrs.Length];
                    string[] mesh_paths = new string[mrs.Length];
                    for (int i = 0; i < mrs.Length; i++) {
                        MeshFilter mesh_filter = mrs.GetComponent<MeshFilter>();
                        Mesh mesh = mesh_filter.sharedMesh;
                        mesh_paths = Path.Combine(dir, mesh.name + ".asset");
                        Material[] mats = mrs.sharedMaterials;
                        string[] mat_paths = new string[mats.Length];
                        for (int j = 0; j < mats.Length; j++) {
                            string name = mats[j].name;
                            mat_paths[j] = Path.Combine(dir, name + ".mat");
                            string full_path = Path.Combine(Directory.GetParent(Application.dataPath).FullName, mat_paths[j]);
                            if (!File.Exists(full_path))
                            {
                                //不替换已经存在的同名资源
                                AssetDatabase.CreateAsset(mats[j], mat_paths[j]);
                            }
                        }
                        for (int j = 0; j < mats.Length; j++) {
                            mats[j] = AssetDatabase.LoadAssetAtPath<Material>(mat_paths[j]);
                        }
                        AssetDatabase.CreateAsset(mesh, mesh_paths);
                        meshes = AssetDatabase.LoadAssetAtPath<Mesh>(mesh_paths);
                        mesh_filter.sharedMesh = meshes;
                        mrs.sharedMaterials = mats;
                    }
                    string prefab_path = Path.Combine(dir, g.name + ".prefab");
                    PrefabUtility.SaveAsPrefabAssetAndConnect(g, prefab_path, InteractionMode.AutomatedAction);
                    //AssetDatabase.DeleteAsset(assetPath);
                    //meta已经被删除,但还是会弹一个“存在meta file”的警告
                    //AssetDatabase.DeleteAsset(Path.Combine(assetPath, ".meta"));
                    AssetDatabase.Refresh();
                }
            }
#endif
        }

        
        public static void OnPostprocessAllAssets(string[] importedAsset, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) {

            bool processed_flag = false;

            foreach (string asset_path in importedAsset) {
                string dir = Path.GetDirectoryName(asset_path);
                Object main_asset = AssetDatabase.LoadMainAssetAtPath(asset_path);
                if (main_asset.GetType() == typeof(GameObject) && Path.GetExtension(asset_path).ToLower() == ".fbx") {
                    Object[] assets = AssetDatabase.LoadAllAssetsAtPath(asset_path);
                    List<Object> animation_clip_list = new List<Object>();
                    foreach (Object asset in assets)
                    {
                        if (asset.GetType() == typeof(AnimationClip))
                        {
                            //这种preview的clip完全没法拷贝序列化
                            if (!asset.name.StartsWith("__preview__")){
                                animation_clip_list.Add(asset);
                            }
                        }
                    }
                    foreach (AnimationClip animation_clip in animation_clip_list)
                    {
                        Object new_animation_clip = new AnimationClip();

                        //Debug.LogFormat("{0},{1}", animation_clip.GetType(), new_animation_clip.GetType());
                        EditorUtility.CopySerialized(animation_clip, new_animation_clip);
                        //感谢虚幻引擎官方提供了测试模型
                        if (new_animation_clip.name == "Unreal Take" || new_animation_clip.name == "")
                        {
                            new_animation_clip.name = Path.GetFileNameWithoutExtension(asset_path);
                        }
                        string animation_path = Path.Combine(dir, new_animation_clip.name + ".anim");
                        AssetDatabase.CreateAsset(new_animation_clip, animation_path);
                    }
                    AssetDatabase.DeleteAsset(asset_path);
                    processed_flag = true;
                }
            }
            if (processed_flag) {
                AssetDatabase.Refresh();
            }
        }
    }
#endif
        public static class SkeletonTool
    {
        public static GameObject FormSkeleton(GameObject g) {
            //剔除所有的SkinnedMeshRenderer,剩下的看作都是骨骼
            if (g.GetComponent<SkinnedMeshRenderer>() == null)
            {
                GameObject skeleton_bone = new GameObject(g.name);
                skeleton_bone.transform.position = g.transform.position;
                skeleton_bone.transform.rotation = g.transform.rotation;
                skeleton_bone.transform.localScale = g.transform.localScale;

                for (int i = 0; i < g.transform.childCount; i++) {
                    GameObject childBone = FormSkeleton(g.transform.GetChild(i).gameObject);
                    if (childBone) {
                        childBone.transform.parent = skeleton_bone.transform;
                    }
                }
                return skeleton_bone;
            }
            return null;
        }

        public static Transform[] MatchBonesByName(Transform[] sourceBones, GameObject targetSkeleton) {
            Transform[] match = new Transform[sourceBones.Length];
            Transform[] target_transforms = targetSkeleton.GetComponentsInChildren<Transform>();
            for (int i = 0; i < sourceBones.Length; i++) {
                for (int j = 0; j < target_transforms.Length; j++) {
                    if(sourceBones.name == target_transforms[j].name){
                        match = target_transforms[j];
                        break;
                    }
                }
            }
            return match;
        }

        public static Transform MatchBoneByName(Transform sourceBone, GameObject targetSkeleton) {
            Transform[] target_transforms = targetSkeleton.GetComponentsInChildren<Transform>();
            foreach (Transform target_bone in target_transforms) {
                if (target_bone.name == sourceBone.name) {
                    return target_bone;
                }
            }
            return null;
        }
    }
}

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-11-24 17:47 | 显示全部楼层
搞不懂这样做的意义是什么?要知道modelimport可是包含很多设置项的但是,我也曾经这样干过,给fbx增加第二第三套uv,在unity里编辑后另存为mesh,主要给gpuskin用
发表于 2021-11-24 17:52 | 显示全部楼层
设置项的问题我最倾向的解决方式是,在部分文件夹下的Editor文件夹下存一个config文件,选文件夹的时候加给右键菜单弹出对话框编辑设置项,真正可用的importer在导入时逐层向上查找最近的那个config  。这样一边解决手动导入工作量大容易出错的问题,另一边可以加一些例如“自动分配文件夹里已有的Avatar”“根据名字决定是否提取y方向rootmotion”之类的trick型设置……
不过这里懒得加了,再加就跑题了 0.0
我本人其实不喜欢把fbx拆出来,留着fbx方便美术迭代挺香的,而且导出得够规范就能规避大部分的垃圾asset容量……留给control freak或者真优化大神用吧就是。万一技术负责人拍脑袋决定了“全给我拆了”,我能回答Yes we can就不错
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-12-22 20:41 , Processed in 0.099543 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表