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)
- 先通过NuGet或Unity Package Manager导入Assimp.NET
- 编写加载代码:
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
相关产品推荐
相关产品推荐

