分解FBX文件

作者:追风剑情 发布于:2018-12-7 18:33 分类:Unity3d

一、导入一个FBX文件

11111.png

二、分解FBX文件

22222.png

33333.png

4444.png

示例代码

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

public class TestEditor {
    
    public const string PREFIX_BONE = "bone";
    public const string PREFIX_M2B = "smb";
    public const string PREFIX_MODEL = "model";

    [MenuItem("Assets/分解FBX", false, 100)]
    public static void DetachFBX()
    {
        GameObject go = Selection.activeGameObject;
        if (go == null){
            EditorUtility.DisplayDialog("提示", "请选择FBX文件", "OK");
            return;
        }
        
        string asset_path = AssetDatabase.GetAssetPath(go);
        string postfix = Path.GetExtension(asset_path).ToLower();
        if (postfix != ".fbx") {
            EditorUtility.DisplayDialog("错误", "选中的不是FBX文件", "OK");
            return;
        }

        //创建导出目录
        string asset_folder = Path.GetDirectoryName(asset_path);
        string export_folder = Path.Combine(asset_folder, go.name).Replace("\\","/");
        if (!Directory.Exists(export_folder)) {
            AssetDatabase.CreateFolder(asset_folder, go.name);
        }

        Object[] assets = AssetDatabase.LoadAllAssetsAtPath(asset_path);
        //遍历FBX内部所有资源
        for (int i = 0; i < assets.Length; i++)
        {
            if (!(assets[i] is GameObject))
                continue;

            GameObject asset = assets[i] as GameObject;
            asset_path = Path.Combine(export_folder, asset.name);

            //导出带动画的模型
            SkinnedMeshRenderer smr = asset.GetComponent<SkinnedMeshRenderer>();
            if (smr != null) {
                ExportSkinnedBone(smr, export_folder);
                ExportModel(asset, export_folder);
            }
            //导出无动画的模型
            MeshFilter mf  = asset.GetComponent<MeshFilter>();
            if (mf != null) {
                ExportModel(asset, export_folder);
            }
        }
        //导出骨架
        ExportSkeleton(go, export_folder);

        AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
    }

    [MenuItem("Assets/导出选中", false, 200)]
    public static void ExportSelected()
    {
        Object[] objects = Selection.objects;
        for(int i=0; i<objects.Length; i++)
        {
            Object obj = objects[i];
            if (obj == null)
                continue;
            string name = obj.name;
            if (name.StartsWith(PREFIX_BONE) || name.StartsWith(PREFIX_M2B) || name.StartsWith(PREFIX_MODEL))
                BuildAsset(obj);
        }
    }

    // 导出骨架
    public static void ExportSkeleton(GameObject asset, string export_folder)
    {
        GameObject go = GameObject.Instantiate(asset);

        //去掉模型资源
        foreach (var smr in go.GetComponentsInChildren<SkinnedMeshRenderer>())
            GameObject.DestroyImmediate(smr.gameObject);
        foreach (var mf in go.GetComponentsInChildren<MeshFilter>())
            GameObject.DestroyImmediate(mf.gameObject);

        string prefab_path = string.Format("{0}/{1}_{2}.prefab", export_folder, PREFIX_BONE, asset.name);
        CreatePrefab(prefab_path, go);

        //设置bundle信息
        string bundle_name = Path.GetFileNameWithoutExtension(prefab_path);
        SetAssetBundleNameAndVariant(prefab_path, bundle_name, PREFIX_BONE);

        //生成AssetBundle资源
        BuildAsset(prefab_path, export_folder, bundle_name, PREFIX_BONE);

        GameObject.DestroyImmediate(go);
    }

    // 导出蒙皮骨骼信息
    public static void ExportSkinnedBone(SkinnedMeshRenderer smr, string export_folder)
    {
        string asset_path = string.Format("{0}/{1}_{2}.asset", export_folder, PREFIX_M2B, smr.name);
        SkinnedMeshBone obj = SkinnedMeshBone.Create(smr);
        AssetDatabase.CreateAsset(obj, asset_path);

        //设置bundle信息
        string bundle_name = Path.GetFileNameWithoutExtension(asset_path);
        SetAssetBundleNameAndVariant(asset_path, bundle_name, PREFIX_M2B);

        //生成AssetBundle资源
        BuildAsset(asset_path, export_folder, bundle_name, PREFIX_M2B, false);
    }

    // 导出模型
    public static void ExportModel(GameObject asset, string export_folder)
    {
        GameObject go = GameObject.Instantiate(asset);

        string prefab_path = string.Format("{0}/{1}_{2}.prefab", export_folder, PREFIX_MODEL, asset.name);
        CreatePrefab(prefab_path, go);

        //设置bundle信息
        string bundle_name = Path.GetFileNameWithoutExtension(prefab_path);
        SetAssetBundleNameAndVariant(prefab_path, bundle_name, PREFIX_MODEL);

        //生成AssetBundle资源
        BuildAsset(prefab_path, export_folder, bundle_name, PREFIX_MODEL);

        GameObject.DestroyImmediate(go);
    }

    // 创建Prefab
    public static void CreatePrefab(string asset_path, GameObject go)
    {
        Object prefab = PrefabUtility.CreateEmptyPrefab(asset_path);
        PrefabUtility.ReplacePrefab(go, prefab);
    }

    // 设置资源的AssetBundle信息
    static void SetAssetBundleNameAndVariant(Object asset, string bundle_name, string bundle_variant) {
        AssetImporter importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(asset));
        if (importer == null)
            return;
        importer.SetAssetBundleNameAndVariant(bundle_name, bundle_variant);
    }

    // 设置资源的AssetBundle信息
    static void SetAssetBundleNameAndVariant(string asset_path, string bundle_name, string bundle_variant) {
        AssetImporter importer = AssetImporter.GetAtPath(asset_path);
        if (importer == null)
            return;
        importer.SetAssetBundleNameAndVariant(bundle_name, bundle_variant);
    }

    //打包AssetBundle资源
    public static void BuildAsset(Object asset, string export_folder=null, string bundle_name=null, string bundle_variant=null, bool includeDependencies=true, bool compress=true)
    {
        if (asset is ScriptableObject) {
            includeDependencies = false;
        }
            
        string asset_path = AssetDatabase.GetAssetPath(asset);
        BuildAsset(asset_path, export_folder, bundle_name, bundle_variant, includeDependencies, compress);
    }

    public static void BuildAsset(string asset_path, string export_folder=null, string bundle_name=null, string bundle_variant=null, bool includeDependencies=true, bool compress=true)
    {
        if (string.IsNullOrEmpty(export_folder)) {
            export_folder = Path.GetDirectoryName(asset_path);
        }

        if (string.IsNullOrEmpty(bundle_name)) {
            AssetImporter importer = AssetImporter.GetAtPath(asset_path);
            if (!string.IsNullOrEmpty(importer.assetBundleName)) {
                bundle_name = importer.assetBundleName;
            }
        }

        if (string.IsNullOrEmpty(bundle_variant)) {
            AssetImporter importer = AssetImporter.GetAtPath(asset_path);
            if (!string.IsNullOrEmpty(importer.assetBundleVariant)) {
                bundle_variant = importer.assetBundleVariant;
            }
        }

        //-----检查bundle信息-----
        if (string.IsNullOrEmpty(bundle_name)) {
            string msg = string.Format("未设置BundleName\n{0}", Path.GetFileName(asset_path));
            EditorUtility.DisplayDialog("Error", msg, "OK");
            return;
        }

        if (string.IsNullOrEmpty(bundle_variant)) {
            string msg = string.Format("未设置BundleVariant\n{0}", Path.GetFileName(asset_path));
            EditorUtility.DisplayDialog("Error", msg, "OK");
            return;
        }
        //------------------------

        string[] assetNames;
        if (includeDependencies){
            /*************************************************************
            这里返回的依赖资源包含了自己
            注意:
            如果依赖资源未设置bundle信息,会全部打进ab文件中,
            这里必须把依赖资源的path也放进assetNames数组中,否则无法从AssetBundle中读取依赖资源

            如果未设置addressableNames,用资源名从AssetBundle从读取资源
            示例: ab.LoadAsset("资源名")

            如果设置了addressableNames,则只能用addressableName读取资源
            示例: ab.LoadAsset("addressableName")

            addressableNames数组长度必须和assetNames数组长度相同
            **************************************************************/
            assetNames = AssetDatabase.GetDependencies(asset_path);
        }else{
            assetNames = new string[] { asset_path };
        }

        AssetBundleBuild abb = new AssetBundleBuild();
        abb.assetBundleName = bundle_name;
        abb.assetBundleVariant = bundle_variant;
        abb.assetNames = assetNames;
        //abb.addressableNames = addressableNames;//自定义资源名称
        BuildAssetBundleOptions options = BuildAssetBundleOptions.ChunkBasedCompression;
        if (!compress) options = BuildAssetBundleOptions.UncompressedAssetBundle;
        options |= BuildAssetBundleOptions.StrictMode;
        BuildPipeline.BuildAssetBundles(export_folder, new AssetBundleBuild[] { abb }, options, EditorUserBuildSettings.activeBuildTarget);
        AssetDatabase.Refresh();

        //删除生成的manifest文件
        AssetDatabase.DeleteAsset(string.Format("{0}/{1}.{2}.manifest", export_folder, bundle_name, bundle_variant));
        string folder_name = export_folder.Substring(export_folder.LastIndexOf("/") + 1);
        AssetDatabase.DeleteAsset(string.Format("{0}/{1}.manifest", export_folder, folder_name));
        AssetDatabase.DeleteAsset(string.Format("{0}/{1}", export_folder, folder_name));
    }
}

三、将生成的AssetBundle文件放到StreamingAssets目录下

22222.png

测试代码 Test.cs


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

/// <summary>
/// 测试: 重新将骨架、模型、蒙皮骨骼配置信息组装成角色
/// </summary>
public class Test : MonoBehaviour 
{
    CRoleObject ro;

    void Start()
    {
        ModelConfig cfg = new ModelConfig();
        cfg.skeletonUrl = string.Format("file://{0}/bone_lady01.bone", Application.streamingAssetsPath);
        cfg.smbUrl = string.Format("file://{0}/smb_lady01.smb", Application.streamingAssetsPath);
        cfg.modelUrl = string.Format("file://{0}/model_lady01.model", Application.streamingAssetsPath);

        ro = new CRoleObject(cfg);
    }

    void Update()
    {
        if (ro != null)
            ro.Update();   
    }
}

public abstract class IRenderObject
{
    public bool completed { get; protected set; }

    public void Update() {
        OnUpdate();
    }

    public void Destroy() {
        OnDestroy();
    }

    protected virtual void OnUpdate() { }
    protected virtual void OnDestroy() { }
}

public class CRoleObject : IRenderObject
{
    protected CSkeleton mSkeleton = null;
    protected CSkinnedMeshBone mSkinnedMeshBone = null;
    protected CSkinnedMesh mSkinnedMesh = null;

    //构建状态
    public enum  BuildState
    {
        None,
        SkeletonLoading,
        MeshToBoneMapLoading,
        SkinnedMeshLoading,
        LoadCompleted,
        BuildCompleted
    }

    public BuildState buildState { get; protected set; }

    public CRoleObject() { }

    public CRoleObject(ModelConfig cfg)
    {
        Load(cfg);
    }

    public void Load(ModelConfig cfg)
    {
        this.completed = false;

        mSkeleton = new CSkeleton();
        mSkeleton.Load(cfg);

        mSkinnedMeshBone = new CSkinnedMeshBone();
        mSkinnedMeshBone.Load(cfg);

        mSkinnedMesh = new CSkinnedMesh();
        mSkinnedMesh.Load(cfg);

        buildState = BuildState.SkeletonLoading;
    }

    protected override void OnUpdate() 
    {
        if (mSkeleton != null)
            mSkeleton.Update();

        if (mSkinnedMeshBone != null)
            mSkinnedMeshBone.Update();

        if (mSkinnedMesh != null)
            mSkinnedMesh.Update();

        switch(buildState)
        {
            case BuildState.SkeletonLoading:
                if (mSkeleton.completed)
                    buildState = BuildState.MeshToBoneMapLoading;
                break;
            case BuildState.MeshToBoneMapLoading:
                if (mSkinnedMeshBone.completed)
                    buildState = BuildState.SkinnedMeshLoading;
                break;
            case BuildState.SkinnedMeshLoading:
                if (mSkinnedMesh.completed)
                    buildState = BuildState.LoadCompleted;
                break;
            case BuildState.LoadCompleted:
                mSkinnedMesh.SetBones(mSkinnedMeshBone.rootBone, mSkinnedMeshBone.bones);
                mSkinnedMesh.AttachSkeleton(mSkeleton);
                buildState = BuildState.BuildCompleted;
                this.completed = true;
                Debug.Log("Role build completed.");
                break;
        }
        Debug.Log("build state: " + buildState);
    }

    protected override void OnDestroy()
    {
        if (mSkeleton != null) {
            mSkeleton.Destroy();
            mSkeleton = null;
        }
           
        if (mSkinnedMeshBone != null) {
            mSkinnedMeshBone.Destroy();
            mSkinnedMeshBone = null;
        }

        if (mSkinnedMesh != null) {
            mSkinnedMesh.Destroy();
            mSkinnedMesh = null;
        }
    }
}

public class CSkeleton : IRenderObject
{
    protected Dictionary<string, Transform> mBoneDic = null;
    protected WWW www = null;
    protected Animation animation = null;

    protected override void OnUpdate() {
        if (this.www == null)
            return;

        if (!string.IsNullOrEmpty(this.www.error)) {
            Debug.LogErrorFormat("load skeleton failed. url={0}", this.www.url);
            return;
        }

        if (!this.www.isDone)
            return;

        AssetBundle ab = this.www.assetBundle;
        Object[] assets = ab.LoadAllAssets();
        if (assets == null || assets.Length <= 0) {
            Debug.LogErrorFormat("load skeleton asset failed. url={0}", this.www.url);
            return;
        }

        GameObject mainAsset = null;
        //找到骨架资源作为主资源
        for(int i=0; i < assets.Length; i++) {
            if (assets[i].name.StartsWith("bone_")) {
                mainAsset = assets[i] as GameObject;
                break;
            }
        }

        if (mainAsset == null) {
            Debug.LogErrorFormat("not skeleton asset. url={0}", this.www.url);
            return;
        }

        GameObject skeleton = GameObject.Instantiate(mainAsset);
        skeleton.name = mainAsset.name;

        //是否挂有动画组件
        animation = skeleton.GetComponent<Animation>();

        //建立 骨骼名-Transform 映射表
        mBoneDic = new Dictionary<string, Transform>();
        Transform[] transforms = skeleton.GetComponentsInChildren<Transform>();
        int length = transforms.Length;
        for (int i = 0; i < length; ++i) {
            string bone_name = transforms[i].name;
            if (!this.mBoneDic.ContainsKey(bone_name)) {
                this.mBoneDic[bone_name] = transforms[i];
            } else {
                Debug.LogErrorFormat("bone name '{0}' repeated", bone_name);
            }
        }

        ab.Unload(false);
        this.www.Dispose();
        this.www = null;

        this.completed = true;
        Debug.Log("load skeleton completed.");
    }

    public void Load(ModelConfig cfg)
    {
        this.Dispose();
        this.completed = false;
        this.www = WWW.LoadFromCacheOrDownload(cfg.skeletonUrl, cfg.version);
    }

    public Transform FindBoneByName(string boneName)
    {
        if (this.mBoneDic == null || string.IsNullOrEmpty(boneName) || !this.mBoneDic.ContainsKey(boneName))
            return null;
        return this.mBoneDic[boneName];
    }

    public Transform[] FindBonesByName(string[] boneNames)
    {
        if (this.mBoneDic == null || boneNames == null)
            return null;

        List<Transform> list = new List<Transform>();
        string bone_name;
        for(int i=0; i<boneNames.Length; i++){
            bone_name = boneNames[i];
            if (string.IsNullOrEmpty(bone_name))
                continue;
            if (this.mBoneDic.ContainsKey(bone_name)) {
                list.Add(this.mBoneDic[bone_name]);
            }else{
                Debug.LogWarningFormat("can't find bone transform:{0}", bone_name);
            }
        }
        return list.ToArray();
    }

    public void Dispose()
    {
        if (www != null) {
            www.Dispose();
            www = null;
        }
    }

    protected override void OnDestroy()
    {
        Dispose();
        if (mBoneDic != null) {
            mBoneDic.Clear();
            mBoneDic = null;
        }
    }
}

public class CSkinnedMesh : IRenderObject
{
    protected WWW www;
    protected SkinnedMeshRenderer smr;
    protected string rootBone;
    protected string[] bones;

    protected override void OnUpdate() {
        if (this.www == null)
            return;

        if (!string.IsNullOrEmpty(this.www.error)) {
            Debug.LogErrorFormat("load model failed. url={0}", this.www.url);
            return;
        }

        if (!this.www.isDone)
            return;

        AssetBundle ab = this.www.assetBundle;
        Object[] assets = ab.LoadAllAssets();
        if (assets == null || assets.Length <= 0) {
            Debug.LogErrorFormat("load model asset failed. url={0}", this.www.url);
            return;
        }

        GameObject mainAsset = null;
        //找到模型资源作为主资源
        for(int i=0; i < assets.Length; i++) {
            if (assets[i].name.StartsWith("model_")) {
                mainAsset = assets[i] as GameObject;
                break;
            }
        }

        if (mainAsset == null) {
            Debug.LogErrorFormat("not model asset. url={0}", this.www.url);
            return;
        }

        GameObject model = GameObject.Instantiate(mainAsset);
        model.name = mainAsset.name;
        this.smr = model.GetComponent<SkinnedMeshRenderer>();

        ab.Unload(false);
        this.www.Dispose();
        this.www = null;

        this.completed = true;
        Debug.Log("load model completed.");
    }

    public void Load(ModelConfig cfg)
    {
        Dispose();
        this.completed = false;
        this.www = WWW.LoadFromCacheOrDownload(cfg.modelUrl, cfg.version);
    }

    public void SetBones(string rootBone, string[] bones)
    {
        this.rootBone = rootBone;
        this.bones = bones;
    }

    public void AttachSkeleton(CSkeleton skeleton)
    {
        if (skeleton == null || smr == null)
            return;
        Transform rootTran = skeleton.FindBoneByName(rootBone);
        smr.transform.parent = rootTran.parent != null ? rootTran.parent : rootTran;
        smr.rootBone = rootTran;
        smr.bones = skeleton.FindBonesByName(bones);
        smr.enabled = true;
    }

    public void Dispose()
    {
        if (www != null) {
            www.Dispose();
            www = null;
        }
    }

    protected override void OnDestroy()
    {
        Dispose();
        if (smr != null) {
            smr.rootBone = null;
            smr.bones = null;
            smr = null;
        }
        bones = null;
    }
}

public class CSkinnedMeshBone : IRenderObject
{
    public string rootBone;
    public string[] bones;

    protected WWW www;

    protected override void OnUpdate() {
        if (this.www == null)
            return;

        if (!string.IsNullOrEmpty(this.www.error)) {
            Debug.LogErrorFormat("load smb failed. url={0}", this.www.url);
            return;
        }

        if (!this.www.isDone)
            return;

        AssetBundle ab = this.www.assetBundle;
        Object[] assets = ab.LoadAllAssets();
        if (assets == null || assets.Length <= 0) {
            Debug.LogErrorFormat("load smb asset failed. url={0}", this.www.url);
            return;
        }

        SkinnedMeshBone smb = assets[0] as SkinnedMeshBone;
        if (smb == null) {
            Debug.LogErrorFormat("not smb asset. url={0}", this.www.url);
            return;
        }

        this.rootBone = smb.rootBone;
        this.bones = smb.bones.Clone() as string[];

        ab.Unload(false);
        this.www.Dispose();
        this.www = null;

        this.completed = true;
        Debug.Log("load smb completed.");
    }

    public void Load(ModelConfig cfg)
    {
        this.Dispose();
        this.completed = false;
        this.www = WWW.LoadFromCacheOrDownload(cfg.smbUrl, cfg.version);
    }

    public void Dispose()
    {
        if (www != null) {
            www.Dispose();
            www = null;
        }
    }

    protected override void OnDestroy()
    {
        Dispose();
        bones = null;
    }
}

public class ModelConfig
{
    public int version = 6;
    public string skeletonUrl;
    public string modelUrl;
    public string smbUrl;
}


运行效果

Hierarchy窗口

1111.pnggg.gif

Console日志

444.png

标签: Unity3d

Powered by emlog  蜀ICP备18021003号-1   sitemap

川公网安备 51019002001593号