You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中如何从外部本地文件夹加载FBX格式3D对象?

解决Unity加载外部同级uploads文件夹中FBX的问题

首先明确:UnityWebRequest能加载图片但无法直接处理FBX,是因为FBX需要经过Unity的导入流程(生成Mesh、材质、动画等Asset),而图片是原生支持的纹理格式,可直接解码为Texture2D。以下是针对本地测试环境的几种可行方案:

方案1:编辑器模式下复制导入(适合开发阶段)

直接将外部uploads文件夹的FBX复制到Unity项目的Assets目录,通过AssetDatabase.Refresh()触发Unity的导入流程,之后即可加载实例化。

代码示例

using UnityEngine;
using UnityEditor;
using System.IO;

public class ExternalFBXImporter : MonoBehaviour
{
    [MenuItem("Tools/Import External FBX")]
    public static void ImportFBXFromUploads()
    {
        // 拼接外部uploads文件夹路径(与项目同级)
        string externalUploadsPath = Path.Combine(Application.dataPath, "../uploads");
        // 筛选文件夹内所有FBX文件
        string[] fbxFiles = Directory.GetFiles(externalUploadsPath, "*.fbx", SearchOption.TopDirectoryOnly);

        if (fbxFiles.Length == 0)
        {
            Debug.Log("uploads文件夹中未找到FBX文件");
            return;
        }

        // 项目内的目标导入目录,不存在则创建
        string targetAssetPath = "Assets/ImportedExternalFBX";
        if (!Directory.Exists(targetAssetPath))
        {
            Directory.CreateDirectory(targetAssetPath);
        }

        foreach (string fbxFilePath in fbxFiles)
        {
            string fileName = Path.GetFileName(fbxFilePath);
            string destPath = Path.Combine(targetAssetPath, fileName);

            // 复制文件到Assets目录
            File.Copy(fbxFilePath, destPath, overwrite: true);
            // 刷新AssetDatabase,触发Unity导入
            AssetDatabase.Refresh();

            // 加载导入后的模型并实例化
            GameObject importedModel = AssetDatabase.LoadAssetAtPath<GameObject>(destPath);
            if (importedModel != null)
            {
                Instantiate(importedModel, Vector3.zero, Quaternion.identity);
                Debug.Log($"成功导入并实例化:{fileName}");
            }
            else
            {
                Debug.LogError($"导入后加载模型失败:{fileName}");
            }
        }
    }
}

使用方式:在Unity编辑器顶部菜单点击Tools/Import External FBX即可执行导入。

方案2:Runtime环境下使用第三方库解析FBX(适合打包后程序)

Unity原生不支持Runtime直接加载未导入的FBX,需借助第三方库解析FBX文件,手动构建Mesh、GameObject等。常用库如Assimp.NET(跨平台的3D模型解析库)。

代码示例(Assimp.NET)

  1. 先通过NuGet或Unity Package Manager导入Assimp.NET
  2. 编写加载代码:
using UnityEngine;
using Assimp;
using System.IO;

public class RuntimeFBXLoader : MonoBehaviour
{
    void Start()
    {
        string targetFBXPath = Path.Combine(Application.dataPath, "../uploads/test.fbx");
        if (!File.Exists(targetFBXPath))
        {
            Debug.LogError("目标FBX文件不存在");
            return;
        }

        // 读取FBX文件字节
        byte[] fbxBytes = File.ReadAllBytes(targetFBXPath);

        // 初始化Assimp上下文并导入模型
        using (var assimpContext = new AssimpContext())
        {
            // 启用三角化、生成法线等后处理步骤
            Scene importedScene = assimpContext.ImportFromMemory(fbxBytes, 
                PostProcessSteps.Triangulate | 
                PostProcessSteps.GenerateNormals | 
                PostProcessSteps.FlipUVs);

            if (importedScene == null || importedScene.RootNode == null)
            {
                Debug.LogError("解析FBX场景失败");
                return;
            }

            // 递归处理节点,生成Unity GameObject
            ProcessAssimpNode(importedScene.RootNode, importedScene, transform);
        }
    }

    void ProcessAssimpNode(Node assimpNode, Scene assimpScene, Transform parentTransform)
    {
        // 创建节点对应的GameObject
        GameObject nodeGO = new GameObject(assimpNode.Name);
        nodeGO.transform.SetParent(parentTransform);

        // 应用节点的变换信息
        nodeGO.transform.localPosition = new Vector3(
            assimpNode.Transform.Column3.X, 
            assimpNode.Transform.Column3.Y, 
            assimpNode.Transform.Column3.Z);
        nodeGO.transform.localRotation = new Quaternion(
            assimpNode.Transform.Rotation.X, 
            assimpNode.Transform.Rotation.Y, 
            assimpNode.Transform.Rotation.Z, 
            assimpNode.Transform.Rotation.W);
        nodeGO.transform.localScale = new Vector3(
            assimpNode.Transform.Scale.X, 
            assimpNode.Transform.Scale.Y, 
            assimpNode.Transform.Scale.Z);

        // 处理节点下的网格
        foreach (int meshIndex in assimpNode.MeshIndices)
        {
            Assimp.Mesh assimpMesh = assimpScene.Meshes[meshIndex];
            Mesh unityMesh = new Mesh();

            // 填充顶点数据
            Vector3[] vertices = new Vector3[assimpMesh.Vertices.Count];
            for (int i = 0; i < assimpMesh.Vertices.Count; i++)
            {
                vertices[i] = new Vector3(assimpMesh.Vertices[i].X, assimpMesh.Vertices[i].Y, assimpMesh.Vertices[i].Z);
            }
            unityMesh.vertices = vertices;

            // 填充三角形索引
            int[] triangles = new int[assimpMesh.Faces.Sum(f => f.IndexCount)];
            int triIdx = 0;
            foreach (var face in assimpMesh.Faces)
            {
                foreach (int idx in face.Indices)
                {
                    triangles[triIdx++] = idx;
                }
            }
            unityMesh.triangles = triangles;

            // 填充法线数据
            if (assimpMesh.HasNormals)
            {
                Vector3[] normals = new Vector3[assimpMesh.Normals.Count];
                for (int i = 0; i < assimpMesh.Normals.Count; i++)
                {
                    normals[i] = new Vector3(assimpMesh.Normals[i].X, assimpMesh.Normals[i].Y, assimpMesh.Normals[i].Z);
                }
                unityMesh.normals = normals;
            }

            // 添加MeshFilter和MeshRenderer组件
            MeshFilter meshFilter = nodeGO.AddComponent<MeshFilter>();
            meshFilter.mesh = unityMesh;
            MeshRenderer meshRenderer = nodeGO.AddComponent<MeshRenderer>();
            meshRenderer.material = new Material(Shader.Find("Standard"));
        }

        // 递归处理子节点
        foreach (Node childNode in assimpNode.Children)
        {
            ProcessAssimpNode(childNode, assimpScene, nodeGO.transform);
        }
    }
}

方案3:预转换为Runtime友好格式(推荐生产环境)

提前将FBX转换为Unity支持的Runtime格式(如AssetBundle或GLTF/GLB),上传时直接传这些格式,之后通过UnityWebRequest或AssetBundle加载即可实例化。

示例:转换为AssetBundle(编辑器阶段)

using UnityEngine;
using UnityEditor;
using System.IO;

public class FBXToAssetBundleBuilder : MonoBehaviour
{
    [MenuItem("Tools/Build FBX to AssetBundle")]
    public static void BuildFBXBundle()
    {
        // 假设已将FBX导入到Assets目录
        string fbxAssetPath = "Assets/ImportedFBX/test.fbx";
        AssetImporter importer = AssetImporter.GetAtPath(fbxAssetPath);
        if (importer == null)
        {
            Debug.LogError("目标FBX资源不存在");
            return;
        }

        // 设置AssetBundle名称
        importer.SetAssetBundleNameAndVariant("external_fbx_bundle", "");
        AssetDatabase.Refresh();

        // 打包到uploads文件夹
        string outputDir = Path.Combine(Application.dataPath, "../uploads");
        BuildPipeline.BuildAssetBundles(outputDir, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
        Debug.Log("AssetBundle打包完成,已输出到uploads文件夹");
    }
}

Runtime加载AssetBundle

using UnityEngine;
using System.IO;

public class AssetBundleLoader : MonoBehaviour
{
    void Start()
    {
        string bundlePath = Path.Combine(Application.dataPath, "../uploads/external_fbx_bundle");
        AssetBundle bundle = AssetBundle.LoadFromFile(bundlePath);
        if (bundle == null)
        {
            Debug.LogError("加载AssetBundle失败");
            return;
        }

        // 加载并实例化模型
        GameObject model = bundle.LoadAsset<GameObject>("test");
        if (model != null)
        {
            Instantiate(model, Vector3.zero, Quaternion.identity);
        }

        // 释放AssetBundle资源(按需选择是否卸载)
        bundle.Unload(false);
    }
}

内容的提问来源于stack exchange,提问作者Claudio Vasquez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 22:31:01