Unity 3D模型格式转换实战:从Blender命令行到AssetPostprocessor自动流水线
发布时间:2026/9/29 17:30:35来源:尧图网络
简介一套用于将资源转换为Unity3D格式的轻量脚本工具主要面向Unity开发者、技术美术以及需要批量处理模型、纹理等资源的进阶用户。压缩包共3个文件包含1个C#脚本和2个JavaScript脚本分别负责编辑器辅助逻辑与自动化导出流程整体仅5KB轻量易用适合直接集成到现有工程。已有333人学习下载。借助其中的自动化导出脚本与编辑器扩展脚本用户可快速实现资源批处理、格式整理与打包输出还能在编辑器中调用AssetDatabase完成资源序列化相关操作适合作为学习Unity资源管线或制作自定义转换工具的参考也为不熟悉Unity资源序列化机制的开发者提供了现成的处理思路。所有脚本均针对Editor环境设计与Unity资源管线无缝衔接能有效减少重复劳动、提升资源管理效率尤其适合资源量大、需频繁更新的开发场景。1. 先把标题说破.unity 不是模型格式你要转的是 FBX / OBJ / GLTF看到「转换 .unity 3D 格式脚本」这个标题老手会先皱眉.unity 是 Unity 的场景序列化文件存的是场景里的层级关系、组件参数、引用关系不是通用 3D 模型格式。你从网上下载一个 .unity 文件想直接拖进别的引擎或者想把它转成 FBX/OBJ 给 Blender、3ds Max 用这条路本身就走不通。更多人实际遇到的情况是手里有一批 FBX/OBJ/GLTF 模型要批量整理进 Unity或者从 Unity 旧项目里把模型导出来给别的管线用需要一套能反复跑的转换脚本。这篇文章就把「转换 .unity 3D 格式」这个模糊诉求拆成真正能落地的方案。我会按三条实际路径来讲一是用 Blender 命令行做无头格式转换二是用 Unity Editor 脚本批量清理和导入模型三是从 .unity 场景文件里提取 mesh 的思路。适合的人群是Unity 开发者、做 3D 资产管线的 TA、以及被老板丢来一堆模型要转个格式放进项目里的倒霉蛋。读完你能照着搭一条最小可用的转换流水线也知道哪些环节是白费力气。2. 格式选型为什么绝大多数情况你都该转成 FBX2.1 FBX、OBJ、GLTF 到底怎么选先解决转成什么的问题。三个格式各有脾气FBX 是 Unity 和 3ds Max、Maya 之间的事实标准支持骨骼动画、Blend Shape、顶点色、多 UV、材质引用缺点是二进制版本兼容性差Autodesk 官方格式经常一升级就翻脸。OBJ 是最老实的通用格式什么工具都认但它只存网格和基础 UV动画、骨骼一概不支持材质靠配套的 .mtl 文件名字带中文就容易断链。GLTF 是新时代的3D 界 JPEG开源工具链齐全支持 PBR 材质、骨骼、动画Unity 用 glTFast 或官方导入器都能吃但遇上老项目里的标准材质还是会映射不准。我的默认选择是只要目标平台是 Unity优先 FBX。原因很实际——FBX 导入 Unity 后网格、材质球、动画剪辑的映射关系最直接刚体、碰撞体、LOD 这些组件挂载也更顺不需要额外写适配层。OBJ 只在给 CAD 软件、测绘软件做中间交换时用GLTF 只在 Web 端或跨引擎协作时用。2.2 FBX 的版本坑Unity 认什么Blender 导出什么FBX 虽然叫一个格式内部却有 2011、2013、2014、2016、2019 等多个版本。Unity 的 FBX 导入器对 2011 和 2013 支持最好Blender 默认导出的却是较新的版本有时候导入 Unity 会碰到网格错乱或动画曲线丢失就是版本不匹配闹的。在 Blender 导出面板里记得把 FBX 版本手动改成 7.4对应 2013ASCII 还是 Binary 选 Binary 就行。我见过有人为了排查问题导出 ASCII 版文件体积大好几倍而且 Unity 同样支持但没有任何必要非调试场景不要用。2.3 Smoothing 和法线新手最容易忽略的导入差异同一个模型在 Blender 里看着圆滑进 Unity 就变成一块块硬边90% 是法线没带过去。FBX 里法线有两种存法显式法线Normal和通过平滑组Smoothing Groups计算。Unity 的默认导入设置是计算法线Calculate Normals角度阈值默认 60 度超过就会产生硬边。转换脚本里我一般会强制让 Blender 导出时勾选 Apply Modifiers 和 Export Normals同时把 Unity 导入器里的 Normals 选项设成 Import而不是 Calculate。前者保证 Blender 端的修改器结果进到 FBX后者保证 Unity 不改你原来的法线。这一步写进脚本能给你省掉整整一轮模型进项目就花脸的排障。3. 用 Blender 命令行做无头转换机把 FBX/OBJ/GLTF 批量洗成 Unity 友好格式3.1 为什么是 Blender 而不是独立转换工具市面上的转换工具鱼龙混杂有些在线转换站传上去一个模型转完下载发现贴图路径全丢更别提资产保密问题。本地用 Blender 做转换机的好处一是免费开源、脚本接口稳定二是 Blender 的 Python API 能读能写 FBX/OBJ/GLTF/PLY/STL 几乎所有格式三是可以完全脱离 GUI 跑命令行适合批量处理。常见做法是写一个 Blender Python 脚本用bpy模块完成导入、清理、导出全流程再通过blender --background --python从终端调用。这套机制在 Windows、macOS、Linux 上行为一致进 CI 流水线也没问题比装插件逐个人工导出可靠得多。3.2 最小可用的无头转换脚本# Windows 命令示例假设 Blender 在默认安装路径 C:\Program Files\Blender Foundation\Blender 4.1\blender.exe ^ --background --python convert_model.py -- ^ --src D:\raw_models\robot.obj --dst D:\unity_models\robot.fbx ^ --scale 0.01 --rotate-x 90这个命令干了三件事让 Blender 不启动任何窗口--background、执行我们的 Python 脚本--python convert_model.py、把--后面的参数传给脚本--src/--dst/--scale/--rotate-x。--scale 0.01是把厘米转成米很多 OBJ 模型单位是 cmUnity 常用单位是 m不缩放导入后物体会放大 100 倍。--rotate-x 90是把模型从 Blender 的 Z-up 轴向转到 Unity 的 Y-up后面专章细讲。再看脚本本体convert_model.pyimport bpy import argparse import os # 解析命令行参数 parser argparse.ArgumentParser() parser.add_argument(--src, requiredTrue, help源模型路径) parser.add_argument(--dst, requiredTrue, help目标 FBX 路径) parser.add_argument(--scale, typefloat, default1.0, help缩放系数) parser.add_argument(--rotate-x, typefloat, default0.0, help绕 X 轴旋转角度) args parser.parse_args() # 清空 Blender 默认场景 bpy.ops.object.select_all(actionSELECT) bpy.ops.object.delete(use_globalFalse, confirmFalse) # 按扩展名分派导入器 ext os.path.splitext(args.src)[1].lower() if ext in (.obj, .fbx, .gltf, .glb): bpy.ops.wm.obj_import(filepathargs.src) if ext .obj else \ bpy.ops.import_scene.fbx(filepathargs.src) if ext .fbx else \ bpy.ops.import_scene.gltf(filepathargs.src) else: raise ValueError(f不支持的格式: {ext}) # 执行轴向修正和缩放 if args.rotate_x: bpy.ops.object.select_all(actionSELECT) bpy.ops.transform.rotate(valueargs.rotate_x, orient_axisX) if args.scale ! 1.0: bpy.ops.object.select_all(actionSELECT) bpy.ops.transform.resize(value(args.scale, args.scale, args.scale)) # 做一次全场景选中导出 FBX bpy.ops.object.select_all(actionSELECT) bpy.ops.export_scene.fbx(filepathargs.dst, use_selectionTrue, apply_scaleFBX_SCALE) print(f[OK] {args.src} - {args.dst})脚本清空默认场景后按扩展名分派到不同的导入器别小看这个if-elseBlender 的 OBJ 新导入器wm.obj_import和老导入器import_scene.obj返回参数不一样不区分就会报类型错误。apply_scaleFBX_SCALE是把缩放系数应用进 FBX 而不是套用父级 transform否则导入 Unity 后物体 Transform 里会带着诡异的小数缩放运行时做物理计算容易碰到浮点精度问题。3.3 批量跑目录一条 for 循环解决模型阵列# 在 PowerShell 里批量处理目录下所有 OBJ $srcDir D:\raw_models $dstDir D:\unity_models Get-ChildItem -Path $srcDir -Filter *.obj | ForEach-Object { $dst Join-Path $dstDir ($_.BaseName .fbx) C:\Program Files\Blender Foundation\Blender 4.1\blender.exe --background --python convert_model.py -- --src $_.FullName --dst $dst --scale 0.01 }Get-ChildItem拿到目录里所有 OBJ 文件ForEach-Object逐个调用前文的 Blender 命令。注意每个文件都会新起一个 Blender 进程模型数量几十个没事上百个就会明显变慢。这时候的优化方向是改造脚本让 Blender 在一个进程里循环导入导出而不是每次重启解释器能省掉一半以上的时间。3.4 为什么脚本里不顺便处理贴图路径初学者最容易把手伸得太长想着转换的时候顺便改贴图路径。这是坑。FBX 里存的材质引用是相对路径但贴图素材在转换时并不一定拷贝到位。正确姿势是转换脚本只管几何和材质槽贴图拷贝留到 Unity 端用 AssetPostprocessor 处理两边职责分开。你要真想在 Blender 里搞贴图还得先检查贴图节点是不是 Principled BSDF遇上老旧uvmap节点还得重建节点树工作量直接翻倍不值当。4. Unity 侧收尾用 Editor 脚本把模型导入变成一键操作4.1 为什么转完的 FBX 进 Unity 还要再洗一遍直接从 Blender 导出的 FBX 丢进 Unity只是第一步。实际项目里你会发现网格名字带_suffix脏后缀、材质球的名称是Material.001这种乱码、模型有几百个零散的 GameObjects 层级没法合并更重要的是模型的导入设置Scale Factor、Mesh Compression、Generate Colliders不统一美术每次手动调一遍再忘一遍。这些收尾工作全部可以写进 Unity 的AssetPostprocessor里自动完成——它是一个编辑器脚本类任何模型导入时都会被调用。这样就能做到佛系拖模型进 Assets 文件夹一切自动洗好。4.2 一个能洗模型的 AssetPostprocessor 脚本using UnityEditor; using UnityEngine; public class ModelImportPipeline : AssetPostprocessor { // 在模型导入之前修改导入器的设置 private void OnPreprocessModel() { // 只有 FBX 走这套流程避免误伤其他格式 if (!assetPath.EndsWith(.fbx)) return; ModelImporter importer (ModelImporter)assetImporter; // 单位统一成米Blender 默认导出就是米这里做个保险 importer.globalScale 1.0f; // 法线走导入不允许 Unity 重新计算 importer.importNormals ModelImporterNormals.Import; // 不生成碰撞体碰撞体到场景里按需加 importer.generateColliders false; // 网格压缩开启移动端包体能省不少体积 importer.meshCompression ModelImporterMeshCompression.Medium; // 材质创建模式用嵌入 FBX 的材质不自动生成新材质 importer.materialImportMode ModelImporterMaterialImportMode.ImportViaMaterialDescription; } // 模型导入完成后对根物体做后处理 private void OnPostprocessModel(GameObject root) { if (!assetPath.EndsWith(.fbx)) return; // 清理名字里的常见脏后缀 root.name root.name.Replace(_mod, ).Replace((Clone), ); // 动态加一个标记组件方便场景里批量识别这个模型 if (root.GetComponentAutoGenerated() null) { root.AddComponentAutoGenerated(); } } }OnPreprocessModel在导入前改参数OnPostprocessModel在导入完成后拿根物体做处理。这里面最值得注意的坑是globalScaleFBX 导入时 Unity 会根据文件里记录的单位自动换算但如果 Blender 导出时 Unity 的默认缩放系数已经是 1你再手动设一次反而可能造成双重缩放。安全做法是 Blender 导出时不动 ScaleUnity 这边也保持默认两边单位都统一成米脚本里这行纯属双保险。4.3 一键刷新导入缓存的时机改完导入设置不生效是常见翻车现场。Unity 对模型有导入缓存改完AssetPostprocessor脚本后必须强制重新导入。老手一般会顺手在脚本里加一段菜单命令[MenuItem(Tools/Reimport All Models)] public static void ReimportAllModels() { string[] guids AssetDatabase.FindAssets(t:Model, new[] { Assets/Models }); foreach (string guid in guids) { string path AssetDatabase.GUIDToAssetPath(guid); AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate); } Debug.Log($已重新导入 {guids.Length} 个模型); }AssetDatabase.FindAssets在指定目录下找所有模型ImportAssetOptions.ForceUpdate强制绕过增量检查。Windows 上如果模型文件本身没变但你就是想重置导入设置记得在导入前先手动删掉Library/ArtifactDBUnity 的导入缓存数据库这是真正的后悔药。4.4 从 .unity 文件里抢救模型素材的场景标题里提到的 .unity 场景文件确实有一个真实用途如果旧项目丢失了原始模型工程文件但场景文件里还留着网格引用你可以通过脚本把场景里所有模型导出成 FBX。实际操作是用AssetDatabase.GetAssetPath拿到模型资源的路径再调AssetDatabase.ExportPackage或直接把.fbx源文件复制出来。这里有个残酷的现实场景文件里存的是网格资源的引用和实例的 Transform不是网格本身。如果原始 FBX 文件已经删除场景里的模型就显示成 MissingUnity 编辑器会自动用一张untitled网格占位这时候从场景里无论如何也导不出高模网格。所以别再花时间去解析 .unity 文件找你想要的网格数据了先检查原始资源还在不在——这是很多人白忙活一整天才发现的事。5. 避坑手册我在这条管线里踩过的 6 个真实问题5.1 模型进 Unity 侧躺了Y 轴朝上怎么修都不对现象Blender 里好好的模型导出 FBX 进 Unity 后横躺在地上。原因Blender 默认 Z-up右手坐标系、Y 轴指向屏幕外Unity 是 Y-up左手坐标系。绝大多数建模软件3ds Max、Maya也都是 Y-up但 Blender 独树一帜用 Z-up。如果 FBX 导出时没做轴转换必须手动旋转。解决Blender 导出 FBX 时在导出面板里有个 Forward 和 Up 设置。经验值是 Forward 设 -ZUp 设 Y。在命令行脚本里对应bpy.ops.export_scene.fbx(axis_forward-Z, axis_upY)我前文的脚本偷懒没加这两个参数你应该自己补上。5.2 模型放大 100 倍场景里像个巨型城市现象从某 CAD 平台下载的模型导入后巨大肉眼看到的比例完全不正常。原因行业惯例问题。CAD 软件里单位默认 mm毫米Unity 默认单位是 m米。模型长 200 毫米导入 Unity 会被当成 200 米。另外有些游戏引擎导入器会自动除以 100Unity 的 FBX 导入器在某些版本里 scale factor 默认 0.01两套叠加就更乱。解决Blender 导入阶段统一成米。--scale 0.01是最粗暴的写法严谨做法是检查 FBX 嵌入的单位元数据bpy.data.scenes[0].unit_settings.system METRIC如果源文件单位是厘米就缩 0.01毫米就缩 0.001。写死在脚本里早晚坑下一批人。5.3 材质显示紫色贴图路径全断现象FBX 导入后材质球紫得发亮纹理全丢。原因FBX 里存的贴图路径是制作机器上的绝对路径比如C:\Users\ArtTeam\Assets\textures\metal.png。跨机器时这个路径直接失效Unity 不会沿绝对路径去找贴图。解决第一种方案是在 Blender 导出前用bpy.ops.file.find_missing_files()把材质重新指到相对路径然后勾选 Embed Textures 导出嵌入材质。第二种是 Unity 端写OnPostprocessAllAssets把丢失材质的物体按命名规则重新匹配贴图。如果你有规范的贴图命名modelname_matname_albedo.png第二种更可靠。5.4 动画模型转完只剩下 T-Pose现象模型带骨骼动画转完 FBX 后进 Unity 只有静态 T-Pose动画全无。原因Blender 导出 FBX 时默认只导当前帧。你没设置导出范围bpy.context.scene.frame_start/frame_end动画没烘焙进去或者骨骼的动画 Action 没选中时即便有帧数据也导不出。更隐蔽的情况是骨架Armature和网格蒙皮Mesh没有父子绑定关系导出时被拆成了两个独立 FBX 节点。解决导出前检查骨架对象bpy.context.selected_objects是否同时选中了 Armature 和 Mesh用bpy.ops.object.parent_set(typeARMATURE_NAME)重建绑定然后确保frame_start和frame_end覆盖完整动画区间。我在脚本里一般直接设frame_start0, frame_end1000解决 99% 动画丢失问题。5.5 大量导入时 Unity 直接卡死现象一次性拖入 500 个 FBXUnity 转菊花半小时甚至闪退。原因每个 FBX 导入都会跑一次完整管线——反序列化、网格处理、材质映射、碰撞体生成。如果每个模型都生成碰撞体、都开网格压缩处理器直接过载。还有人在模型里放了超大 4K 贴图导入时 Unity 要做完 mipmap 又不是增量编译不卡才怪。解决批量导入时临时关掉代码里generateColliders材质用ImportViaMaterialDescription代替None。更有效的办法是分批操作一次拖 50 个导入完成再下一批。实战里我甚至在EditorApplication.delayCall里做了 List 队列每帧只处理 2 个模型瞬间不卡了。5.6 模型导入后碰撞体乱七八糟现象场景里添加 Rigidbody 后物体滚得乱七八糟碰撞体积和模型外形完全对不上。原因Unity 自动生成的碰撞体是原始形状Box/Sphere复杂曲面模型用 Box Collider 近似凹陷模型容器、洞穴优先用 Mesh Collider。但 Mesh Collider 一开性能成本直线上升移动端直接崩帧。解决我的做法是generateColliders false然后在场景里按需手动挂 Collider。只有静态环境物体墙壁、地面才允许生成 Mesh Collider动态物体一律走BoxCollider 自定义近似体。碰撞体问题不在转换脚本里解决在关卡设计里解决。6. 验证与进阶跑完转换后怎么确认你的管线真的没翻车转换脚本跑完不能只看 FBX 文件存在就算成功得做三层验证。第一层是几何完整性检查用 Blender 的bpy.data.objects读导出后的 FBX——网格是否有非流形边Non-Manifold Edges、顶点数是否和源文件一致、法线是否为空。第二层是 Unity 端的导入日志检查OnPostprocessModel里加一行Debug.Assert(root.GetComponentsInChildrenMeshRenderer().Length 0)报错就说明网格没导全。第三层是运行时表现写一个简单的加载脚本把模型生成在场景中心FOV 固定 45 度拍一张截图存档下次对比差异。我用得最多的是 Python 端的离屏验证法# 验证脚本不导出只读回文件检查关键属性 import bpy import sys fbx_path sys.argv[-1] bpy.ops.import_scene.fbx(filepathfbx_path, use_animTrue) mesh_count 0 vert_count 0 for obj in bpy.data.objects: if obj.type MESH: mesh_count 1 vert_count len(obj.data.vertices) if obj.type ARMATURE: print(f[骨骼] {obj.name} 骨骼数{len(obj.data.bones)}) print(f[检查] 网格数量{mesh_count}, 总顶点数{vert_count}) if mesh_count 0 or vert_count 0: sys.exit(1)验证完再进 Unity你的时间就不白烧。最后的经验之谈我见过太多人死在看着转出来了就马上拖进项目结果贴图断链、轴向翻转然后在 Unity 的报错日志里打转。花两分钟跑一下这层离屏验证能省掉一晚上的排查时间。还有个小习惯——转换脚本参数和版本信息每次跑完后追加到一个 CSV 里格式、版本、路径、耗时都记全。下个月美术跑来说模型变了你能立刻定位是哪次转换、用的哪个版本脚本而不是全靠记忆复盘。希望这套流程能帮你把模型导入这件事从手动折腾变成一键自动少踩几个我当年踩过的坑。本文还有配套的精品资源点击获取
网站建设高端定制企业官网