简介:ExportSceneToObj 是一款面向 Unity 开发者的场景导出插件,可将场景中的 GameObject、Terrain 地形与 .fbx 模型一键导出为 .obj 文件,适用于 Recast Navigation 导航网格烘焙或跨 DCC 工具交换模型。它还提供自定义裁剪区域、自动裁剪及非正式选择导出等能力,便于灵活限定导出范围。
资源包仅 10 个文件、约 540KB,内含 ExportScene.cs 功能脚本、package.json 配置、README 与 CHANGELOG 文档、效果截图及 LICENSE 授权说明,结构紧凑,可阅读源码或经 Package Manager 的 Git 方式导入 Unity 2018.3+。目前已有 1630 人学习下载。
包内还附 Documentation 目录与 Editor 脚本目录,适合想理解导出原理或二次定制裁剪、选择逻辑的开发者,整体是一份轻量、实用且文档完整的 Unity 插件源码包。
1. ExportSceneToObj 是什么:为什么 Unity 导出 OBJ 不能靠另存为
做数字孪生、Unity 地图或跨软件资产交换的人,迟早会撞上同一个问题:Unity 场景里的模型和地形,怎么交给 Blender、Maya 或 C4D 继续改?项目另存为里根本没有 OBJ 这个选项,Unity 编辑器也从不提供“导出当前场景”的原生入口。ExportSceneToObj 这类工具解决的就是这件事——把场景里的对象(GameObject)和 Terrain(地形)导出成通用 .obj 文件,FBX 也能一并转出去。OBJ 虽然老,但它是 3D 软件之间互通的“通用语”,Blender、Maya、3ds Max、Meshlab 全认它。适合谁?美术外包对接、地形导出做 Web 端展示、Unity 里做的白模想拿回 DCC 重拓扑的人。一句话:你要的不是“另存为”,是一条能稳定复现、带贴图路径、不丢坐标的导出链路。
2. 导出前的格式认知:OBJ 四类记录、坐标系与 Unity 资源约束
2.1 OBJ 文件到底存了什么:v / vt / vn / f 四类记录
OBJ 本质是纯文本,逐行描述几何数据。打开任何一个 .obj 文件,最常见的就是四类行:v 开头的是顶点坐标(x y z),vt 是 UV 坐标,vn 是法线,f 是面索引。面索引里三个数字对应顶点、UV、法线的序号,OBJ 的索引从 1 开始,不是从 0 开始,这是新手最容易看错的地方。ExportSceneToObj 做的事,本质就是遍历 Unity 场景里所有带 MeshFilter / SkinnedMeshRenderer 的对象,把顶点数组、UV 数组、法线数组、三角形索引数组拿出来,再按 OBJ 规范写进文本。
理解这个格式,对你排查导出结果很重要。比如导出的文件在 Blender 里显示“面数不对”,多半不是工具的问题,而是 Unity 里 Mesh 的三角形索引本身就包含退化三角形(面积为零),导出时没做过滤。又比如法线黑脸,先看 vn 行是不是全为 0。打开导出的 obj,用支持文本查看的编辑器搜 vn,如果发现 vn 0.000000 0.000000 0.000000,说明 Unity 那个 Mesh 没有法线数据,导出工具没生成平滑法线。这些细节,下面避坑章节会展开。
2.2 导出前要处理的 Unity 资源约束:Mesh 读取标记与材质回退
ExportSceneToObj 的原理解起来不复杂,但第一次跑通的人几乎都会翻车在 Mesh 读取这一步。Unity 的模型导入器默认不会把 Mesh 数据留在内存供运行时读取,编辑器环境下跑导出脚本,需要先确认模型的 Import Settings 里开启了 Read/Write(读写权限)。如果没开,脚本执行到 GetMesh 那一步会直接抛异常,或者拿到一个空 Mesh。
常见的做法是:导出工具内部尝试读取,失败时提示“Mesh 不可读,请勾选 Read/Write"。这个提示不是工具抽风,是 Unity 的资源导入管线决定的。你要么在 Project 窗口选中模型,在 Inspector 里勾选 Model 选项卡下的 Read/Write Enabled,要么在导入脚本里改导入器配置然后 Reimport。材质方面同样有约束——Unity 的材质默认是 Lit Shader,导出的 OBJ 只有几何和 UV,而 MTL 文件里记录的是颜色、漫反射贴图路径。如果对象的材质是 HDRP 或 URP 的 Lit 变体,工具通常只能读取到基础贴图 _BaseMap,读不到 _BaseColorMap 这类变体命名。导出后颜色发灰、贴图丢失是常态,不是你操作错,是命名不匹配。
2.3 坐标轴、缩放与单位:让 Blender / Maya 里不翻车
Unity 是左手坐标系,Y 轴向上;Blender、Maya 是右手坐标系,Blender 默认 Z 轴向上。直接拿 Unity 导出的 OBJ 进 Blender,模型会“躺倒”——楼房的立面变成地面。这不是 ExportSceneToObj 独有的问题,而是 OBJ 格式本身只存坐标值,不存坐标系约定。
大多数导出工具在写文件时会做一次 Y-up 到 Z-up 的轴变换:把顶点坐标的 (x, y, z) 写成 (x, z, y) 或者 (x, y, z) 按目标软件约定反转。你在使用 ExportSceneToObj 的时候,先看工具有没有 Forward / Up 轴的选项,常见配置是“Y-Up,右手规则”。如果没有轴选项,那就默认按 Y-up 输出,进 Blender 后用 Rotation 调整。另一个高频翻车点是单位:Unity 的 1 单位是 1 米,Blender 默认也是米(虽然老项目可能是厘米),C4D 默认是厘米。场景里一个 2 米高的角色,导入 C4D 变成 2 厘米,直接缩到看不见。这是单位换算造成的幻觉,不是数据丢了。
导出前建议先看一眼场景里对象的 Transform 是否有非均匀缩放。Unity 允许 (1, 2, 1) 这种非等比缩放,但 OBJ 导出时如果直接把本地坐标乘上非均匀缩放,法线会跟着变形,导致光照方向不对。经验做法:导出前把非均匀缩放的对象先做一个依赖反转(用 Object > Transform > Reset 或手工调整模型本身),再进导出流程。
3. 跑通 ExportSceneToObj:导出场景对象与 Terrain 的完整流程
3.1 找到入口:编辑器菜单与窗口面板
ExportSceneToObj 这类 Unity 扩展的入口通常挂在菜单栏上,常见的是 Window > ExportSceneToObj ,或者 Tools > Obj Exporter。少数版本集成到右键菜单,选中层级里的对象后右键,Export to OBJ。如果是从 Asset Store 导入的,导入完成后如果菜单没出现,检查菜单路径:Edit > Project Settings > Editor > 是否启用该工具所在的程序集。
常见做法是:先备份一次空场景测试,确认菜单出现。如果装完工具菜单没出现,第一步不是重装,而是看 Console 里有没有编译错误。Unity 6 和 Unity 2021 之后的版本对 API 兼容性要求不同,工具里用了被弃用的Object.FindObjectsOfType或者MeshCollider.sharedMesh相关 API 会直接编译失败,导致整个菜单消失。这时候去工具官方页面或 Asset Store 评论区,看它支持的 Unity 版本,再决定要不要换旧版本。
3.2 一次完整的场景导出操作步骤
打开目标场景,确认要导出的对象都在激活状态。然后这样操作:
菜单:Tools > ExportSceneToObj > Export Current Scene 参数:勾选 Export Terrain、勾选 Export FBX、勾选 Merge Children 输出:选择输出目录,点击 Export说明:Export Current Scene 会把当前打开的.unity场景里所有有效对象扫一遍。勾选 Export Terrain 后,工具会把地形组件转换为一个临时 Mesh——读 TerrainData 里的 heightmap 采样点,构造成顶点网格再写入 OBJ。这个转换过程会消耗内存,地形分辨率是 512x512 时,顶点会到几十万级别,注意不要开着其他大场景同时操作。Merge Children 是把子对象合并成一个 OBJ 文件,不勾的话,一个 GameObject 对应一个 obj 文件,便于逐件修改回导。
参数说明里最容易被忽略的是世界空间(World Space)和本地空间(Local Space)的选择。导出时选 World Space,Mesh 顶点会被转换成场景世界坐标,适合对整体场景做白模审查。导出到 Blender 后对象在原点附近正常摆放。选 Local Space 则保留每个对象的本地坐标,适合只导单个物件、回来还要保持层级关系时用。工具默认通常是世界空间,但如果你发现导出后所有东西都挤在原点,大概率是选成了 Local 且忘了勾选“保持位置”。
3.3 导出后的文件清单:OBJ、MTL 与目录结构
导出成功之后,输出目录里会生成这些文件:
| 文件 | 内容 | 作用 |
|---|---|---|
scene.obj | 几何数据(顶点/UV/法线/面) | 目标文件,给 DCC 软件导入 |
scene.mtl | 材质定义(颜色 + 贴图引用路径) | 告知 DCC 软件每个面用什么材质 |
scene_folder/ | 拷贝后的贴图文件 | 保证贴图路径与 MTL 引用一致 |
OBJ 文件的贴图路径记录在 MTL 里,路径可能是绝对路径也可能是相对路径。绝对路径(如C:/Users/xxx/Pictures/tex.jpg)在自己机器上没问题,发给外包同事就会全红。拿到导出结果第一步,用文本工具打开 MTL,检查里面map_Kd行写的是相对路径(./textures/tex.jpg)还是绝对路径。好的导出工具通常提供“复制贴图到输出目录”的选项,勾选后 MTL 引用的是相对于 OBJ 所在文件夹的路径。如果没有这个选项,就自己在输出目录建一个 textures 文件夹,手工替换 MTL 里的路径字符串。
提示:OBJ 和 MTL 文件默认是无 BOM 的 UTF-8 或 ANSI 编码。用记事本打开后另存为带 BOM 的 UTF-8,Blender 可能识别不出中文路径。路径里尽量别有中文和空格,这能省掉八成路径报错。
4. 从 FBX 到 OBJ:为什么要转、怎么转、参数怎么设
4.1 为什么会有 FBX 转 OBJ 的硬需求
Unity 项目里大量资产是从 Maya、3ds Max 导出的 FBX。FBX 是二进制或 ASCII 的私有格式,Blender 能导入但兼容性时不时出问题,Meshlab 直接不认。OBJ 则是纯文本,谁都能读。于是“Unity 里的 FBX 转成 OBJ”成了跨软件协作的常见诉求——最常见的使用场景有两个:一是外包交付的模型格式是 FBX,但内部标准化要求 OBJ;二是 Unity 里加载的 FBX 带有动画和骨骼,你要的是静态网格,用工具把它剥出来。
ExportSceneToObj 的“Export FBX”功能做的就是这个事情:把项目里的.fbx资产解码,读它的 Mesh 数据,再写成一个.obj。注意这里有个边界:FBX 里的 骨骼动画 和 顶点动画 不是 OBJ 能表达的,导出的只是绑定姿势(Bind Pose)下的静态网格。如果你想着把动画一起导出去,这条路走不通,你需要 FBX 本身的导出器而不是 OBJ 转换器。
4.2 替换 FBX 模型的推荐做法:先 Import 再 Export
在 Unity 里直接选一个 FBX 文件,右键有没有“Export to OBJ”?大多数工具不做这个右键菜单,而是要求你把 FBX 拖进场景,生成一个 GameObject 引用,再导出整个场景。这么做有个坑:Unity 导入 FBX 时会做轴转换和缩放,导出时如果工具没有做逆向换算,得到的 OBJ 坐标会和原始 FBX 对不上。
更好的做法是先把 FBX 导入到 Unity(项目里出现该资产),然后确认 Import Settings 里的 Scale Factor、Bake Axis Conversion 等选项。常见做法是:勾选Bake Axis Conversion,确保 Unity 内部已经做过一次轴对齐,导出 OBJ 时工具再从 Unity 数据反向写文本。这样到 Blender 里坐标才是正的。我在实际项目里习惯先把 FBX 拖到一个空场景,单独导出单个对象,而不是连地形带灯光一起导出——这样排查坐标问题更快。
4.3 导出精度参数:比例因子、平滑夹角与索引类型
转 FBX 时你会看到类似这些参数:
Scale Factor: 1.0 Smoothing Angle: 30 Merge Vertices: true Vertex Index Type: 32-bitScale Factor 表示从 Unity 单位到 OBJ 单位的倍率。Unity 1 单位 = 1 米时,导出到 C4D(厘米)应该填 100。如果填 1,打开 C4D 会看到一个 1:1 的米制模型,需要手工改文档单位。Smoothing Angle 是自动平滑法线的角度阈值:相邻两个面的夹角小于 30 度时视为光滑过渡,法线做插值;大于 30 度则保持硬边。这个参数直接影响 OBJ 看起来是“卡通棱角”还是“圆滑质感”,通常 30-45 度是通用值。
Merge Vertices 决定是否合并位置完全相同的顶点。如果不合并,OBJ 文件体积会膨胀——一个立方体 8 个顶点变成 24-36 个顶点(每个面 4 个,6 个面各写各的)。合并后文件小、导入快,但要注意:如果 Mesh 本身有 UV 接缝、硬边,强制合并会把原本的接缝也焊死,纹理出现拉伸。所以这个选项在“保真”和“精简”之间需要看项目需求。Vertex Index Type 选 32-bit,因为 Unity 场景里某些大 Mesh 顶点数超过 65535,16-bit 索引会直接报错或者截断。
注意:导出 FBX 转 OBJ 时,如果原始 FBX 带有动画,Unity 导入后 SkinnedMeshRenderer 的顶点是绑定姿势下的数据,导出工具读的是
skinnedMeshRenderer.sharedMesh——它是静态网格。此时如果勾了“导出当前姿势”,需要工具额外做骨骼加权计算,不是所有版本都支持。绝大多数工具只导绑定姿势,这点在导出前要心里有数。
5. 避坑与常见问题:导出后材质丢失、地形塌陷、法线黑脸怎么办
5.1 导出后模型偏色或全灰:材质颜色读到了,贴图路径丢了
现象:OBJ 在 Blender 里打开,几何正确,但颜色是灰的,贴图没加载。原因:MTL 里map_Kd引用的贴图路径在目标机器上不存在,或者贴图文件名含中文/空格导致解析失败。解决:打开 MTL 文件检查路径,把贴图拷到 OBJ 目录下的 textures 文件夹,将map_Kd改成相对路径./textures/xxx.png。如果是导出工具读不到 URP 材质贴图,那就是属性名问题——URP 的 Lit Shader 贴图存在_BaseMap,不是旧版的_MainTex,工具没做兼容的时候读出来是空的。解决办法:导出前把材质换回内置管线渲染模式,或者手工在 MTL 里补上贴图引用。
5.2 Terrain 导出后塌陷成一个平面:高度图数据没被读取
现象:地形导出的 OBJ 是平的,或者只有边缘一块有起伏。原因:工具的 Terrain 导出逻辑依赖独立的TerrainData对象,如果你的地形是用第三方插件(如 Gaia、地形编辑器)生成的,TerrainData 不在Terrain.activeTerrain里,脚本没有遍历到。也有一种情况是地形分辨率设置太高,工具采样密度不足,起伏被过滤成平面。解决:先在 Inspector 里选中 Terrain,确认 TerrainData 资产被正确赋值;导出工具如果提供 heightmap resolution 选项,调高到地形实际分辨率(如 512 或 1024)再导。导出后查看 OBJ 的顶点数,如果只有几千个,说明采样没跑全。
5.3 法线黑脸或光照方向明显不对:坐标轴旋转后没有翻转绕序
现象:OBJ 导入 Blender 后模型是“黑”的,翻转法线后部分面好了,另一部分又黑了。原因:OBJ 的面的三角绕序(顺时针/逆时针)决定了正面朝向,Unity 用的是顺时针作为正面(左手坐标系),Blender 默认是逆时针(右手坐标系)。导出工具如果没有做面索引反转,就会出现面朝向交错。解决:导出时寻“Flip Faces”(翻转面)或 Y-up/Z-up 转换会自动翻转索引;如果没有这个选项,在 Blender 里选择模型进入编辑模式,全选网格,按Alt+N选择“Recalculate Outside”。如果是批量文件,推荐用 Python 脚本改 OBJ 的 f 行索引顺序,但注意改完要连带法线一起处理,否则光照还是乱的。
5.4 尺寸差 100 倍或 1000 倍:单位制没对齐
现象:一个 5 米宽的墙壁,导入 C4D 变成 5 厘米,导入 Blender 却正常。原因:Unity 单位的解释依赖目标软件的默认文档单位。Blender 默认米,C4D 默认厘米,3ds Max 默认英寸(经典版)或厘米。解决:导出前先确认目标软件的单位设置。如果导给 C4D,记得在 C4D 文档设置里把单位改成米,或者导出时勾上“Export in Meters”。不要在 OBJ 导出后再手工缩放——缩放模型会导致浮点精度下降,转回 Unity 会出现抖动。
5.5 二次导入 Unity 材质变成紫红色:内置管线与 URP/HDRP 的 Shader 冲突
现象:导出的 OBJ 在 Blender 改完,再导回 Unity,模型显示紫红色(默认 Shader 失败)。原因:OBJ/MTL 导入 Unity 时按标准流程会寻找内置 Diffuse 或 Lit Shader,如果你的项目是 URP,内置管线的标准 Shader 文件不在包内,导入器找不到材质 Shader,直接紫红。解决:导入 OBJ 到 Unity 时,在 Import Settings 里指定材质搜索规则(Material Search 设为 Project 或 Recursive Up)并确保项目里有可用的 URP Shader;更直接的方法是导回后选中模型右键 Create > Material,手动赋 URP/Lit。紫红色不是数据坏了,是 Shader 映射问题,不用重新导出。
6. 进阶:写一个编辑脚本把导出流程并进 Ctrl + S
到这里,能做到单次导出只是入门。真正常用的人会把导出流程自动化——每次在场景里改完白模,按一下快捷键就产出 OBJ,供下游 Blender 或 Meshlab 处理。ExportSceneToObj 这类工具通常暴露了公开方法,你可以通过反射调用它的菜单函数,也可以自己封装一个 Editor 脚本。我一般是这样写的:
using UnityEditor; using UnityEngine; public class ObjQuickExporter { const string MenuPath = "Tools/导出当前场景为 OBJ [Ctrl+Alt+E]"; [MenuItem(MenuPath)] static void ExportSceneWithDefaults() { // 调用目标导出工具的公开入口 // 此处是示例:如果工具提供了静态方法,直接调用即可 // ExportSceneToObj.ExportCurrentScene(new ExportOptions // { // ExportTerrain = true, // ExportFBX = false, // WorldSpace = true, // FlipFaces = true // }); Debug.Log("导出流程已触发,请检查 Console 输出路径"); } }这段脚本的核心逻辑和参数说明:MenuItem 属性在 Unity 编辑器里注册一个菜单项,快捷键写在路径最末尾,Ctrl+Alt+E是关键绑定。导出选项按团队约定写死在脚本里——这个团队的约定是“地形必须导、FBX 不导、世界坐标、翻转面”,避免每个成员手动勾选导致口径不一。脚本的 Debug.Log 只是占位,实际使用时替换为工具公开的静态方法或反射调用。
批量校验导出的 OBJ 是否有效,也是个值得投入的收尾步骤。我用 Python 写过一个 30 行的小脚本,专查 OBJ 文件完整性:统计 v 行数量、f 行数量,检查面索引是否越界。越界就是 OBJ 的 f 行里出现了比 v 行总数更大的序号,说明某个三角形索引指到了不存在的顶点,这种文件进任何 DCC 都可能破面。
# check_obj.py 用法:python check_obj.py scene.obj with open(sys.argv[1], 'r', encoding='utf-8') as f: lines = f.readlines() vertices = [l for l in lines if l.startswith('v ')] faces = [l for l in lines if l.startswith('f ')] bad = 0 for line in faces: for token in line.split()[1:]: idx = int(token.split('/')[0]) if idx < 1 or idx > len(vertices): bad += 1 print(f"顶点数: {len(vertices)}, 面数: {len(faces)}, 越界索引: {bad}")这是我吃过亏后留下的习惯。有一回导出几十个建筑 OBJ,表面上一切正常,丢给外包后对方反馈“模型拉链一样错位”。排查半天,是某个合并后的 Mesh 存在负索引(OBJ 允许负索引表示倒数计数,但 Blender 对负索引的兼容性时好时坏)。从那以后,每次批导出完我都跑一遍这个脚本,越界索引数量为 0 才敢交付。这个小逻辑也放进 CI 里,反正不占几分钟。希望这个习惯帮你在导出 OBJ 这条路上少走一段弯路。
本文还有配套的精品资源,点击获取