当前位置: 首页 > news >正文

Unreal是如何驾驭内存的 第14章 资产系统——UPackage、.uasset与Cook流程

第14章 资产系统——UPackage、.uasset与Cook流程

本章目标:深入理解UE资产系统的内存模型——UPackage作为资产的内存容器、.uasset文件的二进制结构、Import/Export表的引用管理、Cook流程中的内存转换,以及资产注册表的内存开销。


14.1 资产系统全景

运行时视角

编辑器视角

Cook

打包

Content Browser
/Game/Maps/
/Game/BP/
/Game/Tex/

.uasset (源格式) / .umap

已加载的 UPackage
ULevel · UTexture · UStaticMesh

.uasset (Cook后) / .pak · .ucas

核心映射:一个 .uasset文件 ↔ 一个 UPackage ↔ 一组相关UObject


14.2 UPackage——资产的内存容器

14.2.1 UPackage的本质

classUPackage:publicUObject{// UPackage就是一个UObject!// 它是所有包内UObject的 OuterFName FileName;// 文件路径FGuid Guid;// 包的唯一标识uint32 PackageFlags;// 标志位FLinkerLoad*LinkerLoad;// 关联的加载器// 包内对象关系:// UTexture2D->GetOuter() == UPackage// UStaticMesh->GetOuter() == UPackage};

14.2.2 Outer链与内存组织

UPackage "/Game/Props/Chair"

该包内的对象层级:

  • UStaticMesh“Chair_Mesh”
    • UBodySetup (SubObject)
  • UMaterialInterface“Chair_Mat”
    • UMaterialExpressionTextureSample
  • UTexture2D“Chair_Diffuse”
    • FBulkData (纹理像素)

每个对象的Outer链:

Chair_Mesh->GetOuter() == UPackage("/Game/Props/Chair") UPackage->GetOuter() == nullptr (顶层)

Outer链的内存意义:

  • GC:包被标记不可达 → 包内所有对象一起回收
  • 序列化:包是序列化的单位
  • 资产管理:加载/卸载以包为粒度

14.3 .uasset文件格式

14.3.1 文件结构概览

.uasset文件的二进制布局:

区域内容
FPackageFileSummary(文件头)Magic: 0x9E2A83C1、FileVersion、TotalHeaderSize、PackageName、PackageFlags、NameCount + NameOffset、ImportCount + ImportOffset、ExportCount + ExportOffset 等
NameMap(名称表)“Chair_Mesh”、“StaticMesh”、“Chair_Mat” 等
ImportMap(导入表)Import[0]: /Script/Engine.StaticMesh、Import[1]: /Game/Textures/Wood.Wood 等
ExportMap(导出表)Export[0]: Chair_Mesh (offset, size, class)、Export[1]: Chair_Mat (offset, size, class) 等
Export对象数据区Chair_Mesh的序列化数据、Chair_Mat的序列化数据 等
BulkData纹理/音频等大数据(可选,或在.ubulk中)

14.3.2 NameMap——名称表

// 文件中的名称表// 包内所有FName都通过索引引用此表// 避免重复存储相同的名称字符串// 内存影响:// 加载时NameMap中的名称注册到全局FNamePool// 一旦注册,字符串在FNamePool中永不释放// 累积效应:加载过的包越多,FNamePool越大// 典型大小:每个包 100-1000 个名称// 每个名称在池中占 ~30-60 字节// 1000个名称 ≈ 30-60 KB 池内存

14.3.3 ImportMap——导入表

structFObjectImport{FName ClassPackage;// 类所在的包名FName ClassName;// 类名int32 OuterIndex;// Outer的索引(包内)FName ObjectName;// 对象名称// ...};// Import表记录了本包引用的外部对象// 例如:一个材质引用了另一个包中的纹理// Import[0]: /Game/Textures/WoodGrain.WoodGrain (UTexture2D)//// 加载时:需要解析这些引用 → 可能触发依赖包的加载// 内存影响:Import越多 → 依赖包越多 → 内存占用可能雪崩

14.3.4 ExportMap——导出表

structFObjectExport{int32 ClassIndex;// 对象的类(Import索引)int32 SuperIndex;// 父类int32 OuterIndex;// OuterFName ObjectName;// 对象名uint32 ObjectFlags;// 标志位int64 SerialSize;// 序列化数据大小int64 SerialOffset;// 数据在文件中的偏移// ...};// Export表描述了本包导出(提供)的所有对象// SerialSize告诉加载器需要分配多少内存// SerialOffset用于seek到正确位置读取数据

14.4 引用与依赖

14.4.1 硬引用链——“引用雪崩”

BP_Character

SkeletalMesh
/Game/Mesh/Hero

Material
/Game/Mat/Skin

Texture SkinDiffuse

Texture SkinNormal

Texture SkinRoughness

AnimBlueprint
/Game/Anim/HeroABP

AnimSequence × 50

CompressedAnimData × 50

SoundCue
/Game/Audio/FootStep

SoundWave × 4

加载 BP_Character 时:必须同时加载所有硬引用资产,可能达到50-200MB内存!这就是"引用雪崩"问题。

14.4.2 引用审计

// 使用引用查看器检查依赖// 编辑器中:右键资产 → Reference Viewer// 控制台命令// obj refs name=BP_Character // 查看引用关系// Size Map工具// 编辑器中:右键 → Size Map// 可视化显示资产及其依赖的内存占用// Asset Audit工具// 分析哪些资产被过度引用

14.5 Cook流程与内存

14.5.1 Cook的目的

编辑器资产 (.uasset源格式)

Cook处理

1. 转换为目标平台格式
纹理: PNG/TGA → BC7/ASTC/ETC2
Mesh: 可能Nanite处理
着色器: HLSL → SPIRV/Metal/DXIL

2. 剥离编辑器专有数据
缩略图、编辑器元数据等

3. 优化序列化格式
Unversioned Property序列化(零标签开销)

4. 生成目标平台文件
.uasset/.uexp/.ubulk

Cooked资产 (运行时格式)

14.5.2 Cook后的序列化优化

Cook前(Tagged序列化)每个属性需要存储名称、类型、大小和值,约24字节/属性。例如一个Health属性:"Health" | Float | 4 | 100.0

Cook后(Unversioned序列化)只需存储值本身,仅4字节/属性:100.0

原理:Cook时已知目标类的精确属性布局,不需要标签,按固定偏移量直接读写,序列化体积减少50-70%,加载速度提升(更少的IO和解析)。代价是丧失版本兼容性(仅限同一版本的Cook)。

14.5.3 .uasset / .uexp / .ubulk分离

Cook后一个资产可能拆分为三个文件: .uasset — 包头 + 名称/导入/导出表 较小 (~10-100KB),总是加载 .uexp — 导出对象的序列化数据 中等大小,对象加载时读取 .ubulk — BulkData (纹理Mip、音频、大数据) 可能很大,可延迟/流式加载 分离的内存优势: - 可以只加载.uasset获取元信息(不加载实际数据) - .ubulk可以独立流式加载特定Mip级别 - 减少不必要的IO和内存占用

14.6 DDC——派生数据缓存

14.6.1 DDC的角色

转换

命中 → 直接读取

未命中 → 重新转换

源资产
PNG纹理 1024×1024

DDC Key
Hash&(源数据+转换参数+平台+引擎版本&)

目标格式
BC7纹理 DDS格式

转换处理

14.6.2 DDC的内存影响

DDC在编辑器中的内存使用:

DDC组件内存影响
本地DDC磁盘缓存无运行时内存
内存DDC缓存 (Boot)可配置大小
共享DDC (网络)网络IO缓冲
DDC键哈希表~10-50MB

DDC仅在编辑器/Cook时使用,运行时(Shipping build)不涉及DDC。


14.7 资产注册表——FAssetRegistryState

14.7.1 资产注册表的作用

资产注册表是所有资产元信息的内存索引,无需加载资产即可查询。

classFAssetRegistryState{// 核心数据结构TMap<FName,FAssetData*>CachedAssetsByObjectPath;TMap<FName,TArray<FAssetData*>>CachedAssetsByPackageName;TMap<FName,TArray<FAssetData*>>CachedAssetsByClass;TMap<FName,TArray<FAssetData*>>CachedAssetsByTag;// 依赖关系TMap<FAssetIdentifier,FDependsNode*>CachedDependsNodes;};

14.7.2 FAssetData——每资产内存开销

structFAssetData{FName PackageName;// 8字节FName PackagePath;// 8字节FName AssetName;// 8字节FName AssetClass;// 8字节// 注意:UE 5.1+ 已将 AssetClass (FName) 替换为// FTopLevelAssetPath AssetClassPath,以支持完整的类路径标识。FAssetDataTagMap TagsAndValues;// 变长MapTArray<int32>ChunkIDs;uint32 PackageFlags;// ...};// 每个FAssetData ≈ 100-500 字节(取决于Tag数量)// 大型项目:// 50,000 个资产 × ~300 字节 ≈ 15 MB// 加上依赖图(FDependsNode)≈ 额外 10-20 MB// 资产注册表总计 ≈ 25-40 MB

14.7.3 运行时的资产注册表

编辑器中: - 扫描Content目录构建完整注册表 - 监听文件变化动态更新 - 内存较大(完整元数据) 运行时: - 从Cook生成的 AssetRegistry.bin 加载 - 只包含运行时需要的数据(裁剪后) - 可通过 bSerializeAssetRegistry 控制 - 通常 5-15 MB

14.8 资产与内存的关键规则

14.8.1 一包一主资产

推荐做法: /Game/Textures/Wood.uasset → 一个UTexture2D 不推荐: /Game/AllTextures.uasset → 100个UTexture2D 理由:加载一个纹理会加载整个包 → 99个不需要的纹理也进内存

14.8.2 打破引用链

// 问题:BP_Character直接引用200MB的资产UPROPERTY()USkeletalMesh*Mesh;// 硬引用 → 蓝图加载时立即加载Mesh// 解决:使用软引用UPROPERTY()TSoftObjectPtr<USkeletalMesh>Mesh;// 软引用 → 不自动加载// 需要时手动加载USkeletalMesh*LoadedMesh=Mesh.LoadSynchronous();// 或异步加载

14.9 小结

  1. UPackage是资产的内存容器——一个.uasset对应一个UPackage,包内所有UObject的Outer指向该包。

  2. .uasset文件包含NameMap/ImportMap/ExportMap三大表,加载器通过这些表在内存中重建对象和引用关系。

  3. Import表决定了包的依赖——Import越多,加载时需要解析的依赖越多。硬引用链的"雪崩效应"是内存问题的常见根源。

  4. Cook流程将编辑器资产转换为运行时格式,Unversioned序列化可减少50-70%的序列化体积,.uexp/.ubulk分离支持按需加载。

  5. 资产注册表是所有资产的内存索引,大型项目中占25-40MB,运行时裁剪后通常5-15MB。


下一章:第15章 资产加载——同步、异步加载与StreamableManager

http://www.jsqmd.com/news/672660/

相关文章:

  • Windows 11 下 VirtualBox 启动报错 (VERR_NEM_NOT_AVAILABLE) 的深度排查与修复指南
  • ARM PMUv2和PMUv3到底有啥区别?给嵌入式开发者的避坑指南与迁移实践
  • 2026 年私有化企业 IM 推荐:BeeWorks 引领安全可控新范式
  • SAP ABAP调试实战:七种高效定位错误消息的策略解析
  • 从‘滞环’到‘SVPWM’:聊聊异步电机FOC控制里,发波方式到底该怎么选?(附避坑建议)
  • 2026药学主任药师考试历年真题难找?这3个靠谱题库平台帮你高效备考! - 医考机构品牌测评专家
  • 别再只盯着CVPR了!给AI新手的保姆级指南:如何高效追踪CV/ML顶会论文(附开源工具推荐)
  • 工业视觉实战:用Python+Zernike亚像素检测提升零件尺寸测量精度(附完整项目代码)
  • Fluent二维模拟深度解读:Planar、Axisymmetric 和 Swirl,你的模型到底该选哪个?
  • 2026年装修公司找GEO服务商口碑推荐榜:用户真实反馈与功能表现分析 - 资讯焦点
  • 视频内容总结实用方法,掌握3个核心技巧效率提升70以上
  • 告别Techpoint和Nextchip:实测国产XS9922A/B芯片在车载DVR上的完整替换流程与性能对比
  • 从Element Plus到Naive UI:Vue3管理后台左侧菜单的另一种实现思路与迁移指南
  • 推荐几款好用的医考APP:亲测靠谱高性价比APP - 医考机构品牌测评专家
  • 研磨仪厂家排行榜揭晓:哪家才是行业真正的“领头羊”? - 品牌推荐大师
  • 泰勒展开式不只是考题:从手机GPS定位到游戏图形渲染,聊聊它在你身边的硬核应用
  • 别让FP16毁了你的模型!TensorRT混合精度实战:用Polygraphy精准定位溢出层
  • 信创即时通讯:BeeWorks 领跑 2026 国产化替代
  • 把Chfs文件共享服务变成系统服务:手把手教你配置Systemd自启动与日志管理
  • 2026年长沙画室推荐:从联考战绩到校园管理,谁在定义湖湘美术教育新高度? - 资讯焦点
  • 告别抓瞎调试:用Wireshark抓包分析BR/EDR测试模式下的蓝牙空中交互
  • 2026执业药师考试培训机构哪家好?亲测靠谱选课攻略 - 医考机构品牌测评专家
  • 5分钟掌握GHelper:华硕笔记本轻量控制工具的实战指南
  • shiro-721 代码执行
  • 告别Windows 10臃肿:终极系统清理工具完全指南
  • 从零构建Windows C++开发环境:MSYS2、MinGW-w64 GCC与CMake实战指南
  • 2026效果最好护发产品推荐:护发精油哪款好用?高温造型防护、长效锁色护养 - 资讯焦点
  • 3个核心功能解决B站视频下载难题:BilibiliDown完全指南
  • 从源码到可执行程序:用CMake和VS2017亲手编译OSG3.6.5,深入理解其依赖与构建过程
  • Cursor充值-招行信用卡订阅-官方支持(2026-4-20)