唰唰冷呵映 发表于 2020-12-24 09:35

Unreal 中的资源文件的版本管理

这篇文章大概会将以下的四个内容:
问题背景什么叫资源文件的版本管理Unreal中的资源文件版本管理Unreal资源版本管理背后的原理
1.问题背景
因为xx原因,需要预计算一些数据并添加到level中,最终参与到runtime 的render中,为了保持和虚幻机制的一致性并利用球谐的分级加载更新机制,我决定直接build进leveldata中,但是,经过调研发现,直接向leveldata添加数据并序列化会崩溃的,于是了解到了Unreal的资源文件版本管理
1.什么叫资源文件的版本管理

   实际游戏开发过程中往往会有很多序列化的二进制数据需要存储,比如预烘焙产生的球谐系数,这些数据不是图片,实际存储的时候是直接序列化成为二进制数据。
在烘焙阶段,将计算好的数据直接序列化为二进制文件存储到硬盘上。在Runtime阶段,读取二进制文件到内存中,用于计算间接光照。
有了以上背景,实际项目中就会遇到一个问题:当需要向这个数据结构中添加成员的时候(比如二阶球谐升级为三阶球谐),我用更改后的数据结构去反序列化以前的二进制文件时,就会出问题,拿到的数据明显就是乱码或者直接意外的文件尾然后崩溃。
序列化和反序列化(Binary不变,但struct改变时会出现问题)
显然,这种问题对于引擎来说是必定会遇到的,因为总不能大家升级一个小版本引擎之后,再打开以前的烘焙过的level后,直接引擎就闪退或者一片漆黑吧。另外,对于引擎开发者来说,在level中添加自定义的预烘焙数据也是在所难免的。
解决这个问题的机制就叫做资源文件的版本管理






2.Unreal中的资源版本管理机制

   在Unreal中是通过int32 FArchive::CustomVer(const FGuid& Key) const 和Ar.UsingCustomVersion(FRenderingObjectVersion::GUID)来管理资源版本的,
先讲讲怎么使用,待会再说原理,以LevelPrecomputedVolumetricLightmapBuildData为例子,如果我们需要向这个预计算的数据中塞一份自己的CustomData进去:
a.先得找到这个数据结构全局唯一的ObjectVersion,并找到它对应的版本枚举,在最后面添加一份版本枚举。
      Unreal会为每一种需要序列化的Object准备一份全局唯一的GUID和对应的版本枚举
const FGuid FSequencerObjectVersion::GUID(0x7B5AE74C, 0xD2704C10, 0xA9585798, 0x0B212A5A);
// Register Sequencer custom version with Core
FDevVersionRegistration GRegisterSequencerObjectVersion(FSequencerObjectVersion::GUID, FSequencerObjectVersion::LatestVersion, TEXT("Dev-Sequencer"));添加新levelData的Version Enum(CustomData)


b.在序列化时先调用Ar.UsingCustomVersion(FRenderingObjectVersion::GUID),然后使用CustomVer()函数来判断资源版本决定是否序列化。


只需要以上简单的两个步骤,就可以随意添加自己的数据进入BuildData而不用担心加载老关卡时崩溃或者一片漆黑了。
(当然本篇文章只讨论加入新数据到序列化文件时 遇到版本冲突崩溃的解决方案,至于添加数据本身就是序列化的过程,下篇文章会补充一下。)








3.Unreal资源版本管理背后的原理
序列化数据和二进制文件的关系在第一小节中已经谈过了,在此不多赘述,第二小节中也说了在虚幻中用哪些函数来解决版本问题,但是背后的原理值得深究一下。
1.首先是反序列化二进制文件时(加载文件)时,通过拿到全局唯一的二进制对象的GUID,来索引到文件本身对应的版本信息,然后通过我们写的if (CustomVer)函数来判断文件的版本是不是大于我们要求的版本信息(只有大于或等于我们才会加载,此时数据结构才和二进制文件对得上)。
        if (Ar.CustomVer(FRenderingObjectVersion::GUID) >= FRenderingObjectVersion::VolumetricLightmaps)
                {
                        Ar << LevelPrecomputedVolumetricLightmapBuildData;
                } 2.然后是序列化二进制文件时(写入文件,比如烘焙的BuildData过程),写入的时候其实不用管啥了,直接写入就行了。
以上两步简单来说,就是写入的时候直接按照本机引擎的最新数据结构写入,而读取的时候就要比对文件的版本信息和我们自己数据结构的版本信息了。
虚幻的实现其实是有点饶了,我也看了好一会才明白过来,
完整的序列化过程
在这里先调用了UsingCustomVersion(),可以自己去看实现,这个函数其实就干了一件事:如果当前操作是读取,直接return,如果当前操作是写入,那么就修改Ar中的Version到本机最新。
接下来就通过判断Ar中的版本信息来决定是否序列化,整个过程其实就是做了以下的事情:
以上两步简单来说,就是写入的时候直接按照本机引擎的最新数据结构写入,而读取的时候就要比对文件的版本信息和我们自己数据结构的版本信息了。
页: [1]
查看完整版本: Unreal 中的资源文件的版本管理