简介面向Unity开发者和需要批量转换.unity3d格式资源的技术人员一套轻量级脚本方案可帮助实现从原始资源到.unity3d文件的自动化转换。压缩包内共3个文件包含两个JavaScript脚本用于自动化导出以及一个C#编辑器辅助脚本BeastHelper.cs用于在Unity Editor环境内操作资源压缩后仅5KB轻量易用。目前已有333人学习下载说明该方案在Unity资源处理场景中具备一定实践价值。脚本逻辑覆盖资源读取、序列化处理、输出路径指定等关键环节可根据项目需求调整模型多边形数、纹理压缩或资源合并从而降低启动加载时间与内存占用将辅助脚本置于Editor目录还可保证其仅在编辑器运行期执行不会混入最终发布包这一组织方式对搭建资源优化流水线很有参考意义适合具备基础Unity开发经验的读者直接采用或二次定制。1. 先纠正一个误会.unity 文件不是 3D 模型但转换脚本依然值得写搜「转换.unity.3D格式脚本」的人多半手里有一批 Unity 项目文件想转成能交给 Blender、Maya 或其他引擎处理的 3D 资源。这里要先说破一个常见误会.unity后缀是 Unity 的场景文件它只是把场景里所有对象的 GUID、组件、Transform、光照和引用关系用 YAML 序列化文本存了起来并不是一个可以像 FBX、OBJ 那样直接拖进建模软件的网格格式。真正能转的是场景引用到的那些.fbx、.obj、.gltf、.asset模型资源以及它们被导入 Unity 后生成的那套 Meta 配置。所以这篇文章要讲的方案是「用脚本完成 3D 模型与 Unity 之间的双向转换」一边把外部 DCC 工具导出的模型批量转成 Unity 原生资源另一边把 Unity 场景里的模型按需求导出成通用格式。脚本的价值不在单次转换而在批量、可复现、能挂进 CI 或右键菜单。适合谁用经常要从 Blender/SketchUp/3ds Max 往 Unity 灌模型的开发或者反方向做资产交付、需要把 Unity 里的模型抽回原始工具的人。新手能按步骤跑通一个最小脚本熟手可以拿走参数设置和踩坑清单直接改造。2. 转换脚本到底在转什么Unity 序列化格式与三种常见转换路径2.1 外部 3D 文件到 Unity 资源官方管线与脚本的分工Unity 本身就是一个巨大的「转换器」。你把 FBX 拖进 Assets 目录的那一刻Unity 的导入管线就已经在做转换解析文件内容、生成网格与材质引用、计算包围盒、按导入设置生成内部 Asset 数据。手动拖文件没问题问题出在当你要处理几十上百个文件或者要求每次导入使用统一参数时手点就完全不现实了。脚本的作用就是把这套导入动作自动化让 AssetImporter 按你写好的规则批量执行。常见做法是写一个 Editor 脚本挂在 Unity 编辑器环境里运行。它和运行时脚本最关键的区别是Editor 脚本可以调用AssetImporter、ModelImporter、TextureImporter这些只在编辑器模式下暴露的 API并且能在资源导入完成后立刻拿到ImportedObject进行二次处理比如自动生成碰撞体、套用材质预设、设置动画类型。如果你的需求只是把一批 Obj 文件转成 Unity 能用的资源甚至不需要写任何导入代码——把文件放进去、让AssetDatabase.Refresh扫描一遍就行。脚本的价值在于把扫描之后的「统一配置」这一步接管过来。2.2 .unity 场景文件和 .prefab 里的 3D 数据到底长什么样拿文本编辑器打开一个.unity文件你会发现它不是二进制而是 Unity 自己的一套 YAML 序列化格式。场景里每个对象对应一个GameObject块里面有m_Name、m_Component引用列表而网格、材质、动画这些真正的资源并不内嵌在场景里而是通过m_SourcePrefab、m_Mesh这样的 GUID 引用指向外部.asset、.fbx文件。所以如果你直接解析.unity文件去提取网格顶点坐标等于把简单问题复杂化——正确路径是顺着 GUID 找到被引用的原始模型文件再去处理那个文件。这也是「转换 .unity 3D 格式」在实际操作里最常见的两种形态。第一种把外部模型导入 Unity利用ModelImporter生成可用的内部资源第二种把 Unity 里已经摆放好、甚至做了组合和材质替换的模型用FBXExporter插件包导出为通用格式。前者的核心是 import 配置后者的核心是 export 范围和 Transform 还原。不管哪一头写脚本的价值都在于把「打开编辑器、手动选中、调整参数、点 Apply」这一串动作变成一行命令。2.3 FBX、OBJ、glTF 转 Unity 的选型对比格式网格骨骼/动画材质Unity 原生支持适合场景FBX二进制/ASCII 混合完整支持可含骨骼绑定与动画内嵌材质与贴图引用导入时可映射内置无需插件角色、动画、从 Maya/3ds Max 来的资产OBJ仅顶点/法线/UV无骨骼动画仅引用 MTL 文件不内嵌 PBR 参数内置静态建筑、机械件、Blender 练习模型glTF/glb支持网格与 Morph Target支持骨骼动画支持 PBR金属粗糙度映射完整较新版本内置部分支持建议装 glTFast 插件Web 资产、扫描模型、跨引擎互转3D Tiles分块 LOD 网格无自带纹理映射需第三方插件倾斜摄影、GIS 类大体量场景选型里有个很容易被忽略的点OBJ 转进 Unity 后ModelImporter默认会把导入的网格重新计算法线和切线如果你的 OBJ 文件里已经有法线但没切线结果完全依赖 Unity 的算法经常出现硬边变软边、高光异常。而 FBX 反而因为信息完整导入后几乎不需要二次修正。glTF 的坑在材质——Unity 标准着色器不直接认 glTF 的 metallic-roughness 工作流需要插件转成 URP/HDRP 的 Lit 材质。因此我的建议是静态几何用 OBJ 最省事要骨骼动画或跨引擎回改必选 FBXWeb/扫描资产选 glb 并装插件。2.4 先理解 AssetDatabase 与 AssetImporter 的分工在动笔写脚本之前要搞清楚两个 API 各自负责什么。AssetDatabase管理的是资产生命周期——刷新、创建、删除、移动、获取路径、保存修改AssetImporter及其子类ModelImporter、TextureImporter、AudioImporter管的是「这个文件导入后要被解释成什么」。转格式这件事本质上就是让某个导入器以特定参数运行。一个典型误区是新手会试图用File.Copy在磁盘层面把.blend改名成.fbx然后刷新 Unity。不会有任何效果因为 Unity 不按后缀解析按文件头识别格式。另一个误区是导入完成后立刻用Resources.Load去加载资源你会发现拿不到——因为资源还没进入内存索引。正确做法是用AssetDatabase.LoadAssetAtPath走编辑器加载路径或者等OnPostprocessAllAssets回调触发后再做下一步。理解了这两层分工后面的脚步就不会跑偏。3. 用 UnityEditor 脚本批量转换 3D 模型最小可用代码与 4 个必调参数3.1 环境准备Unity 版本和脚本存放位置这一步不需要装任何外部插件Unity 自带的编辑器 API 就够了。我建议在 Unity 2021.3 LTS 或更新的版本上操作ModelImporter的 API 在这之后稳定下来老版本动辄遇到的importMaterials参数异常在新版本里已经收敛。脚本要放在Assets/Editor目录下或者Assets里任意路径但必须放在名为Editor的文件夹内否则打包发布时 Unity 不允许 Editor 程序集打进玩家包。using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; public class BatchModelConverter { [MenuItem(Tools/批量转换/导入指定目录下的所有FBX和OBJ)] public static void ImportAllModels() { // 源文件目录可以是任何 Assets 下的路径 string sourceDir Assets/RawModels; // 目标资源目录导入脚本会自动创建 string targetDir Assets/ConvertedModels; Directory.CreateDirectory(targetDir); string[] files Directory.GetFiles(sourceDir, *, SearchOption.AllDirectories); Liststring modelPaths new Liststring(); foreach (string file in files) { string ext Path.GetExtension(file).ToLower(); if (ext .fbx || ext .obj) { modelPaths.Add(file); } } foreach (string path in modelPaths) { ImportSingleModel(path, targetDir); } AssetDatabase.Refresh(); Debug.Log($批量转换完成共处理 {modelPaths.Count} 个模型文件); } }这段是入口脚本不直接参与格式转换但负责把文件捞出来。注意GetFiles用的是操作系统的物理路径而 Unity 的AssetDatabase需要的是项目相对路径两者差一个开头。物理路径是C:/MyProject/Assets/RawModels/xxx.fbx项目路径是Assets/RawModels/xxx.fbx之间不能直接混用。所以下面进行转换时需要先做一个路径换算。Windows 下路径分隔符是反斜杠Unity 资产路径统一用正斜杠。建议养成习惯所有路径处理完先 Replace(\\, /)。3.2 核心代码批量导入并自动套用导入配置现在写真正的转换逻辑也就是对每个模型执行ModelImporter配置并重新导入。private static void ImportSingleModel(string physicalPath, string targetDir) { // 物理路径转 Unity 项目相对路径 string projectPath physicalPath.Replace(\\, /); int assetsIndex projectPath.IndexOf(Assets/); if (assetsIndex 0) return; projectPath projectPath.Substring(assetsIndex); // 按原目录结构把文件复制到目标目录 string relativePath physicalPath.Replace(\\, /); relativePath relativePath.Substring(relativePath.IndexOf(RawModels/) RawModels/.Length); string destPath Path.Combine(targetDir, relativePath).Replace(\\, /); Directory.CreateDirectory(Path.GetDirectoryName(destPath)); if (!File.Exists(destPath)) { File.Copy(physicalPath, destPath, false); } // 关键步骤让 Unity 扫描到新文件 AssetDatabase.Refresh(); // 拿到导入器并按统一参数配置 ModelImporter importer AssetImporter.GetAtPath(destPath) as ModelImporter; if (importer null) { Debug.LogWarning($无法获取导入器: {destPath}); return; } importer.materialLocation ModelImporterMaterialLocation.External; importer.materialName ModelImporterMaterialName.ModelName _ByScript; importer.importNormals ModelImporterNormals.Import; importer.importTangents ModelImporterTangents.CalculateLegacy; importer.importBlendShapeWeights ModelImporterBlendShapeWeights.None; importer.isReadable false; importer.generateColliders true; importer.optimizeMesh true; // 配置材质提取到独立目录 importer.SearchAndRemapMaterials(ModelImporterMaterialName.ModelName _ByScript); importer.SaveAndReimport(); }逻辑说明先强制刷新让 Unity 识别新文件再用GetAtPath拿到该文件的导入器。materialLocation设为 External 的意思是不要在 FBX 内部嵌材质而是把材质提取成独立的.mat文件这样你可以拿到目录下逐个替换材质。importNormals Import代表优先读取文件自带法线而不是重新计算但importTangents CalculateLegacy是让 Unity 用老算法计算切线因为 OBJ 基本不带切线FBX 带的也未必标准统一交给 Unity 更可控。最后SaveAndReimport()把配置写回文件系统这一步相当于你在 Inspector 里点 Apply。参数说明里有个常被忽略的isReadable false看起来是「不可读」它的真实含义是是否把网格数据保留在 CPU 内存里。对纯运行时渲染而言保留读权限是浪费内存但如果你后续需要用MeshCollider动态生成碰撞体或做网格变形就必须设成 true。这里默认关掉因为纯静态场景省内存优先级高。3.3 4 个必调参数详解从材质到碰撞体第一个必调参数是materialLocation。它有三个可选值External提取材质到独立文件、Embedded把材质内嵌到 FBX 里、UseExternalMaterials时完全沿用已有外部材质包。批量转换时统一用 External原因很直接——三个模型用同一张贴图Embedded 会让每个模型都生成一份贴图副本项目体积直接膨胀。External 模式下材质被定位后只存一份后续换材质也容易。第二个是importNormals。设成Import读文件自带法线是最保险的如果你手里的模型来自 SketchUp 这类软件法线方向经常反那就要设成Calculate让 Unity 按平滑角重新算。每个项目到底选哪个取决于源文件的制作规范。统一用 Import 会保留脏数据统一用 Calculate 会抹掉建模软件里精心调整的硬边所以这个参数必须由人工按资产来源确认一次不能无脑统一。第三个是generateColliders。默认的 Collider 是盒碰撞体对大多数静态场景够用但精度差。如果模型是用来做地形或复杂障碍物需要在导入后额外调ModelImporter的addCollider再配合MeshCollider使用。这里脚本里开了 true记住运行时仍需在 Prefab 上手动挂MeshCollider导入器只负责生成数据不负责组件挂载。第四个是optimizeMesh。开启后 Unity 会重新组织顶点缓冲以优化 GPU 读取效率静态模型建议开启但如果后续要按顶点做寻路网格或布点得关掉否则顶点索引会打乱你的逻辑。这个参数的名字有迷惑性——「优化」不总是好事。3.4 把脚本挂到菜单和命令行右键转换与批处理模式上面的代码里用了[MenuItem(Tools/批量转换/导入指定目录下的所有FBX和OBJ)]。Unity 编译通过后顶部菜单栏会出现 Tools → 批量转换 → 相应的按钮点击即可运行。调试时建议把源目录里的文件只放两个先跑通再看转换结果而不是一次倒几十个进去。// 开启命令行模式供 CI 或本机批处理调用 public static void BatchConvertFromCommandLine() { string sourceDirArg System.Environment.GetCommandLineArgs() .FirstOrDefault(arg arg.StartsWith(-sourceDir)); if (string.IsNullOrEmpty(sourceDirArg)) { Debug.LogError(请传入 -sourceDirAssets/RawModels 参数); return; } string dir sourceDirArg.Replace(-sourceDir, ); ImportAllModels(); EditorApplication.Exit(0); }命令行入口方便你在本地跑完 Blender 烘焙、顺手执行一句 Unity 批处理把所有模型导入进去。完整命令是先打开 Unity 项目在合适位置加这段入口然后让脚本执行Exit(0)退出编辑器——注意这里的Exit(0)是编辑器进程退出不是场景退出别放在非命令行模式下调用否则会直接关掉你的 Unity。4. 避坑与排查从材质丢失到坐标系翻转的 5 个真实翻车现场4.1 现象导入后的模型渲染成一片紫色这是所有用过 Unity 的人最熟悉的翻车现场也是批处理转换脚本最容易踩的坑——因为你批量导入了 50 个模型紫色能一次性铺满整个场景。原因无非两条模型自带材质文件没被正确复制到项目里或者材质引用的贴图路径失效。用脚本导入时File.Copy只复制了.fbx/.obj文件配套的.mtl文件和纹理贴图不在复制列表里。Unity 找不到材质自然退化成默认的洋红材质。解决方式批量转换的第一步先把整个目录复制过去而不仅仅复制模型文件。在ImportAllModels里遍历时把模型同目录下的.mtl、.jpg、.png、.tga一并复制。更稳妥的办法是只转换那些源文件目录本身就在 Assets 下的项目因为 Unity 扫描时已经完整建了索引不会出现引用断裂。不要试图在脚本里动态修复贴图引用那个工程量远大于重新组织目录。4.2 现象模型导入后朝向不对绕某个轴旋转了 90 度这是 cross-engine 转换里最经典的玄学问题。Blender 的坐标系是 Z 轴向上Unity 是 Y 轴向上Maya 又分 Y-up 和 Z-up 两种导出设置。FBX 文件头里带着一个UpAxis标记Unity 读到后会做一次坐标转换但如果你把 OBJ 文件直接丢进 UnityOBJ 格式本身不声明坐标系Unity 就按自己的规则猜测此时 Blender 导出的 OBJ 几乎必然出现俯仰角偏差。排查方法先看源 DCC 工具的导出设置。Blender 在「导出 FBX」面板里有一项「Apply Transform」勾上后会把所有旋转烘焙到顶点上导出结果就是一个干净的 Y-up 网格OBJ 没有这个选项需要在 Blender 里先把整个场景绕 X 轴旋转 -90 度再导出。脚本能做的补救是在导入后拿到模型主动绕 X 轴旋转修正但我建议别在脚本里做——问题出在导出的源头每修一次就多一层不确定性。血泪经验统一让美术按 Unity 规范导出而不是让导入端适配。4.3 现象批处理模式下脚本跑不起来或者没有报错但资源没生成用命令行模式跑 Unity 批量转换时有个很典型的坑EditorApplication.Exit(0)执行太早AssetDatabase.SaveAndReimport()还没落盘就被杀掉了。因为导入和序列化是异步批处理的Exit 指令不会等它们完成。解决方式是在退出前先同步保存一次并且把AssetDatabase.SaveAssets()和AssetDatabase.Refresh()都调用一遍再延时一帧退出。如果不做这个处理你看到的场景是「日志全绿、文件根本没更新」。还有一个更隐蔽的问题命令行批处理模式默认不带图形环境部分创建材质、创建 ScriptableObject 的 API 不执行完就返回null。这不是脚本写错而是编辑器没初始化完成。解决方式是在转换入口加一个等待编辑器初始化的循环判断EditorApplication.isCompiling为 false 且AssetDatabase.IsValidFolder已返回预期值再继续。转换这种东西多次验证比一次写完美重要。4.4 现象转换后的资源文件体积巨大构建包翻倍批量转换脚本跑完你会发现.asset文件比原始 FBX 大得多。这通常与两个设置有关。第一isReadable设为 true 会让每个网格在内存和序列化文件里都保留一份完整顶点数据副本第二贴图导入时默认的TextureImporter没做压缩。脚本里如果把每个导入模型都保留了 CPU 可读数据同时纹理格式又是 RGBA32 未压缩40 个中型模型就能吃掉 1GB 磁盘空间。解决方式在转换脚本里对AssetImporter做一次统一降级。isReadable设为 false贴图压缩格式按平台分别设置——Android 用 ASTC、iOS 用 ASTC、桌面用 BC7具体不在这里展开。还有纹理的 Max Size 按场景决定UI 用途 2048 上限足够场景大背景 4096。别给所有贴图一律开 4096那是资源失控的第一根源。4.5 现象Unity 安装后脚本直接报错提示找不到命名空间或 API 过时新装的 Unity 版本把ModelImporter的某些方法标记为过时或者移动了命名空间你的脚本引用旧 API 会编译失败。典型的像ModelImporter.normalImportMode被废弃替换成importNormals还有ModelImporterMaterialName.ModelName的枚举值改了名称。这不是玄学是 Unity 每个大版本都会做的清理。脚本在 2020 LTS 上跑得好好的换到 2022 LTS 就编译失败。排查方式先看Console面板的完整报错确认是哪个 API 报的。然后去 Unity 官方文档查当前版本的对应写法不要凭记忆改。我的习惯是转换脚本从不追求跨版本兼容直接按当前项目版本写死并在脚本头部注释标明「适配 Unity 2021.3 / 2022.3 LTS」。跨大版本维护统一转换脚本是个无底洞不值得。5. 进阶验证用 AssetPostprocessor 自动后处理与转换结果自检转换脚本跑完后不验证就投入使用等于裸奔。我自己会做三件事第一统计导入后的顶点数和材质数量与源文件比对第二随机抽三个模型放进场景里开线框模式看法线方向和 UV 朝向第三用一个自动化的后处理脚本在每次导入完成时统一设置碰撞体层、光照静态标记省掉每次手动调的工序。public class AutoPostProcess : AssetPostprocessor { private void OnPreprocessModel() { // 仅处理从 ConvertedModels 目录进来的模型 if (assetPath.StartsWith(Assets/ConvertedModels/)) { ModelImporter importer assetImporter as ModelImporter; importer.generateSecondaryUV true; importer.lightmapParametersName MediumQuality; } } }这段是后处理钩子OnPreprocessModel在模型导入之前触发对指定目录统一开启自动 UV2 生成。开启generateSecondaryUV会显著增加导入耗时但对于要跑光照烘焙的场景没有 UV2 就意味着烘焙黑斑和阴影漏光。你可以按场景目录来开关别全局开启。lightmapParametersName需要项目里预先创建好对应的LightmapParameters资源否则会静默失败。如果模型不做烘焙或使用混合光照模式这两个设置都不需要开了反而慢。验证的最终手段是把模型和源文件在场景里做叠影对比。把源 FBX 直接拖进一个空白场景再把转换后的模型放到同一个坐标点两个网格在 Game View 里如果完全重合说明坐标和缩放进来了如果不重合优先检查源文件是否有非均匀缩放。这比任何自动校验都可靠建议每次做批量转换后花十分钟抽查。最后说一个个人习惯转换脚本里永远留一个Debug.Log把每个模型的资源 GUID、导入耗时和生成的文件大小打出来。这堆日志在单次转换时显得啰嗦但等资源膨胀到几百个、需要追查某个模型为何 Failure 时它是唯一的线索。技术活做到最后拼的不是炫技是出问题时能找到后悔药。这套脚本的应用路径值得每个每天要跟外部 3D 资产打交道的 Unity 开发者从最小版跑起来用一次你就知道该往哪个方向加参数。希望帮到你。本文还有配套的精品资源点击获取