Files
obsidian-notes/InBox/zhihu_Unity无GC读取图片和网格.md

3.7 KiB
Raw Permalink Blame History

title, source, url, date, tags, type
title source url date tags type
unity中怎么无gc读取图片和网格 知乎 - 老久 https://www.zhihu.com/question/1967951834250459011/answer/2040581102813894136 2026-06-09
zhihu
unity
performance
gc
mesh
answer

unity中怎么无gc读取图片和网格

你之前所有GC的根源根本不是读AB包而是每次都new新的Mesh/Texture托管对象。Unity的Mesh和Texture本质只是托管层的空壳真正的顶点、像素数据全在原生内存里。无GC的核心就是托管壳子永远复用只替换壳子里的原生数据绝对不销毁重建托管对象。


一、抛弃 AssetBundle.LoadAsset<Mesh>()

这个API只要调用就必然new新的Mesh托管对象神仙都救不了GC。

打包阶段就换掉Mesh资源

不要直接把mesh拖进AB包而是在编辑器里把Mesh的所有原始数据扒出来序列化成你自己的二进制格式打成TextAsset进AB包。具体要扒的数据

  • 顶点属性位置、法线、UV1、UV2、切线Mesh.GetVertexAttributes() 获取布局,按布局连续存成二进制块)
  • 索引数据:每个子网格的索引数组
  • 预计算的Bounds这个一定要存避免运行时RecalculateBounds遍历顶点
  • 子网格数量和每个子网格的拓扑结构

这样AB包里存的就不是Unity的Mesh对象而是纯二进制数据加载时不会触发任何托管对象创建。


二、运行时用对象池 + 原生内存拷贝

提前创建一个Mesh对象池按"最大顶点数+最大索引数+子网格数量"分类。比如大世界每个地块最多10万顶点、20万索引、3个子网格就提前创建20个这样的空Mesh放池子里永远不Destroy

加载流程

  1. 从池子里拿一个空闲的Mesh先调用 Mesh.Clear(false)第二个false表示不清空原生缓冲区只是重置状态零开销
  2. Mesh.AllocateWritableMeshData(1) 拿到可写的MeshData对象这个对象直接指向Mesh的原生内存缓冲区没有任何托管拷贝
  3. 从AB包加载对应的TextAsset绝对不要用TextAsset.bytes属性每次get都会new新的byte数组GC爆炸用 Unity 2021+ 新增的 TextAsset.GetData<byte>() 返回NativeArray直接指向原生内存零GC
  4. UnsafeUtility.MemCpy() 把TextAsset里的顶点数据、索引数据直结拷贝到MeshData对应的缓冲区里连NativeArray的中间拷贝都省了
  5. 把预存的Bounds直接赋给 Mesh.bounds,不要调用 RecalculateBounds
  6. Mesh.ApplyAndDisposeWritableMeshData(meshData, false)第二个false表示CPU不需要再读写直接释放CPU端内存只留GPU端
  7. 把这个复用的Mesh赋值给MeshRenderer完成加载

卸载流程

  1. 先把MeshRenderer的mesh置为null确保没有任何地方引用这个Mesh
  2. 调用 Mesh.Clear(false) 重置状态,直接扔回对象池,绝对不要Destroy

整个流程除了对象池初始化时得那批Mesh运行时没有任何托管堆分配GC Alloc为0。


三、AB包加载本身的GC优化

除了上面说的用 GetData<byte>(),还有两个进阶优化:

  1. AssetBundle.LoadFromFileAsync() 加载AB包不要用 LoadFromMemory后者会把整个AB包拷贝到托管堆
  2. 对于超大的地形纹理和网格数据,用 AssetBundle.LoadFromStreamAsync() 直接流式读取,不需要把整个文件加载进内存
  3. 绝对不要用 Resources.Load它的GC开销比AB包大10倍以上

总结

Unity里无GC加载资源的本质就是托管壳子复用,原生数据替换。之前的问题就是把壳子和数据一起销毁重建了现在把壳子留下只换里面的数据GC自然就没了。