Unity编辑器保存1.5亿顶点Mesh崩溃的解决方案咨询
问题
我通过C#脚本创建Mesh对象,尝试用AssetDatabase.CreateAsset()方法保存为资源,保存代码如下:
AssetDatabase.StartAssetEditing(); AssetDatabase.CreateAsset(tempMesh, $"Assets/Export/{fileName}.asset"); AssetDatabase.StopAssetEditing();
已添加tempMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;设置,当tempMesh顶点数约为80000时可正常保存,但顶点数达约150000000时编辑器崩溃。
我考虑将Mesh按每100000个顶点拆分(生成1500个Mesh),再为每个Mesh生成对象,但尽可能不想拆分。请问如何保存约1.5亿顶点的Mesh?
环境:
- MacBook Pro 2020,13英寸,8GB RAM
- Unity 2020.3.47f1
崩溃日志:
Obtained 46 stack frames. #0 0x0000011048d0d1 in BytesToHexString(void const*, unsigned long, char*) #1 0x0000011014044d in TextDOMTransferWriteBase<YAMLWrite>::TransferTypelessData(unsigned int, void*, TransferMetaFlags) #2 0x0000011025898a in void VertexData::Transfer<YAMLWrite>(YAMLWrite&) #3 0x0000010ff78f64 in void YAMLWrite::Transfer<VertexData>(VertexData&, char const*, TransferMetaFlags) #4 0x00000111d3ab32 in void Mesh::Transfer<YAMLWrite>(YAMLWrite&) #5 0x00000111d3a514 in void YAMLWrite::Transfer<Mesh>(Mesh&, char const*, TransferMetaFlags) #6 0x000001106dabbf in void SerializedFile::WriteTextSerialized<Object>(core::basic_string<char, core::StringStorageDefault<char>>&, Object&, BuildUsageTag const&, TransferInstructionFlags) #7 0x000001106da73e in SerializedFile::WriteObject(Object&, long long, short, BuildUsageTag const&, GlobalBuildData const&) #8 0x000001106b9f6d in PersistentManager::WriteFile(core::basic_string_ref<char>, int, WriteData const*, int, GlobalBuildData const&, VerifyWriteObjectResult (*)(Object*, BuildTargetPlatform), BuildTargetSelection, TransferInstructionFlags, WriteInformation&, InstanceIDResolver const*, PersistentManager::LockFlags, int (*)(PersistentManager::ReportWriteObjectStep, int, core::basic_string<char, core::StringStorageDefault<char>> const&, int, void*), void*) #9 0x000001106b8b23 in PersistentManager::WriteFile(core::basic_string_ref<char>, int, WriteData const*, int, GlobalBuildData const&, VerifyWriteObjectResult (*)(Object*, BuildTargetPlatform), BuildTargetSelection, TransferInstructionFlags, InstanceIDResolver const*, PersistentManager::LockFlags, int (*)(PersistentManager::ReportWriteObjectStep, int, core::basic_string<char, core::StringStorageDefault<char>> const&, int, void*), void*) #10 0x000001110e11c8 in WriteObjectsToDiskThreadSafe(core::basic_string<char, core::StringStorageDefault<char>> const&, Object**, long long const*, unsigned long, TransferInstructionFlags) #11 0x000001113e722f in CreateSerializedAssetV2(Object**, int, core::basic_string<char, core::StringStorageDefault<char>> const&, AssetDatabase::CreateAssetMask) #12 0x00000111382c9c in AssetDatabase::CreateSerializedAsset(Object&, core::basic_string<char, core::StringStorageDefault<char>> const&, AssetDatabase::CreateAssetMask) #13 0x00000111739d78 in AssetDatabaseBindings::CreateAsset(PPtr<Object>, core::basic_string<char, core::StringStorageDefault<char>> const&, ScriptingExceptionPtr*) #14 0x000001117d7b04 in AssetDatabase_CUSTOM_CreateAsset(ScriptingBackendNativeObjectPtrOpaque*, ScriptingBackendNativeStringPtrOpaque*) #15 0x000001b52959e5 in (wrapper managed-to-native) UnityEditor.AssetDatabase:CreateAsset (UnityEngine.Object,string) {0x7fc4732dab20} + 0x65 (0x1b5295980 0x1b5295a6d) [0x151982960 - Unity Child Domain] #16 0x000001824d2fab in Cysharp.Threading.Tasks.CompilerServices.AsyncUniTask`1<MainAsync/<tengunMeshGenerator>d__3>:Run () {0x7fc471c24c10} + 0x2b (0x1824d2f80 0x1824d2fcb) [0x151982960 - Unity Child Domain] #17 0x000001824dd2cb in Cysharp.Threading.Tasks.Internal.ContinuationQueue:Update () {0x7fc4729ea3d0} + 0x1b (0x1824dd2b0 0x1824dd2d0) [0x151982960 - Unity Child Domain] #18 0x0000015683b1f5 in mono_jit_runtime_invoke #19 0x000001569d9213 in do_runtime_invoke #20 0x000001569d916d in mono_runtime_invoke #21 0x0000011064c197 in scripting_method_invoke(ScriptingMethodPtr, ScriptingObjectPtr, ScriptingArguments&, ScriptingExceptionPtr*, bool) #22 0x00000110647746 in ScriptingInvocation::Invoke(ScriptingExceptionPtr*, bool) #23 0x00000110323cd9 in ExecutePlayerLoop(NativePlayerLoopSystem*) #24 0x00000110323b65 in ExecutePlayerLoop(NativePlayerLoopSystem*) #25 0x00000110323fb5 in PlayerLoop() #26 0x00000110e930c2 in PlayerLoopController::UpdateScene(bool) #27 0x00000110e8d207 in PlayerLoopController::UpdateSceneIfNeeded() #28 0x00000110e8a664 in Application::TickTimer() #29 0x000001121bca3f in -[EditorApplication TickTimer] #30 0x007ff805399766 in __NSFireTimer #31 0x007ff8044e9358 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ #32 0x007ff8044e8ed3 in __CFRunLoopDoTimer #33 0x007ff8044e8af9 in __CFRunLoopDoTimers #34 0x007ff8044cee39 in __CFRunLoopRun #35 0x007ff8044cdf31 in CFRunLoopRunSpecific #36 0x007ff80df49dad in RunCurrentEventLoopInMode #37 0x007ff80df49bbe in ReceiveNextEventCommon #38 0x007ff80df49918 in _BlockUntilNextEventMatchingListInModeWithFilter #39 0x007ff8075625d0 in _DPSNextEvent #40 0x007ff80756147a in -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] #41 0x007ff807553ae8 in -[NSApplication run] #42 0x007ff807527d02 in NSApplicationMain #43 0x000001121d856d in EditorMain(int, char const**) #44 0x000001121d8799 in main #45 0x007ff80409a41f in start Launching bug reporter
解决方案
1. 先解决内存瓶颈
你的设备仅8GB RAM,1.5亿顶点的Mesh序列化时会占用巨量内存(按单顶点最低32字节计算,仅顶点数据就需要4.8GB,还未包含索引、UV等额外数据),再加上Unity编辑器本身的内存开销,极易触发内存不足导致崩溃。
- 关闭编辑器内所有不必要的窗口、场景对象,释放尽可能多的内存。
- 保存Mesh前手动调用内存清理方法,减少内存碎片:
GC.Collect(); Resources.UnloadUnusedAssets(); AssetDatabase.StartAssetEditing(); AssetDatabase.CreateAsset(tempMesh, $"Assets/Export/{fileName}.asset"); AssetDatabase.StopAssetEditing();
2. 改用二进制序列化替代YAML
Unity默认用YAML文本格式序列化Mesh,内存开销极大。可以尝试使用Mesh.SaveMesh()内部方法直接保存二进制格式的.asset文件,跳过Unity的YAML序列化流程:
using UnityEditorInternal; using System.IO; var filePath = Path.Combine(Application.dataPath, $"Export/{fileName}.asset"); Directory.CreateDirectory(Path.GetDirectoryName(filePath)); tempMesh.SaveMesh(filePath, false); AssetDatabase.Refresh();
注意:SaveMesh是Unity内部方法,不同版本可能存在兼容性问题,需测试验证。
3. 调整编辑器内存限制
打开Unity编辑器的Edit > Preferences > Performance,将Memory Limit调至系统允许的最大值,降低内存限制触发崩溃的概率。
4. 拆分Mesh仍是最稳妥方案
如果以上方法均无效,拆分Mesh是最可靠的选择。可以将大Mesh按顶点数切割为多个子Mesh,保存时分开存储,运行时可通过合并渲染保证效果:
List<Mesh> subMeshes = SplitMeshIntoSubMeshes(tempMesh, 100000); AssetDatabase.StartAssetEditing(); for (int i = 0; i < subMeshes.Count; i++) { AssetDatabase.CreateAsset(subMeshes[i], $"Assets/Export/{fileName}_{i}.asset"); } AssetDatabase.StopAssetEditing();
拆分逻辑可按顶点索引范围切割,确保每个子Mesh的顶点数在安全阈值内。
内容的提问来源于stack exchange,提问作者sssccc210

