Add Zhihu note: Unity无GC读取图片和网格
This commit is contained in:
68
InBox/zhihu_Unity无GC读取图片和网格.md
Normal file
68
InBox/zhihu_Unity无GC读取图片和网格.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: "unity中怎么无gc读取图片和网格?"
|
||||
source: "知乎 - 老久"
|
||||
url: "https://www.zhihu.com/question/1967951834250459011/answer/2040581102813894136"
|
||||
date: 2026-06-09
|
||||
tags: [zhihu, unity, performance, gc, mesh]
|
||||
type: 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自然就没了。
|
||||
Reference in New Issue
Block a user