zl程序教程

您现在的位置是:首页 >  云平台

当前栏目

Unreal随笔系列4:UE4关闭指定平台距离场烘培

平台 关闭 系列 指定 距离 UE4 随笔 Unreal
2023-06-13 09:17:38 时间

引言

我们项目构建了Linux版本的客户端,用于DS的压测。最近一段时间, Unreal Linux Client的构建时间异常的久,所以简单的探究了下Cook的原理。最终通过关闭linux平台下的距离场(Distance Field)烘培,缓解了构建时间的问题。

一 烘培,渲染背景知识

介绍具体问题前,先了解下背景知识。

Cook 烘培

Unreal在构建的过程中,大致执行如下四步。

烘培(Cook)的过程,是将Editor内Assets转变为运行在各平台的Assets。这个过程一般会执行三个流程:

  1. 剔除Assets中Editor相关内容
  2. 生成指定平台需要的内容(主要是渲染向)
  3. 转变成指定平台专用文件

Render 渲染

渲染(Render)和烘培(Cook)是相对平行的两个概念。 渲染主要和如下概念有关

render  渲染
static mesh  网格体
material 材质
shader  着色器
texture 贴图

简单的说,render是在mesh(网格体)上进行着色的过程,这个过程最终是由material(材质)完成。material又由shader代码(着色器)和texture(贴图)构成。

Unreal内的类间关系大致如下:

Cook和Render的关系可以简单的描述为,Cook阶段会为Render生成很多必要内容。本文标题中的距离场就是其中之一。

二 Distance Field 距离场的烘培问题

有向距离场(Signed Distance Field) (SDF),会将各点距离最近表面的距离保存到体积纹理中。网格体外的每个点保存的距离为正值,网格体内的每个点保存的距离为负值。

它有两个具体的应用:

  1. DistanceFieldShadows (DFS),距离场柔和阴影。
  2. DistanceFieldAO (Distance Field Ambient Occlusion),距离场环境光遮蔽。

引言中提到的烘培问题就出现在为DistanceFieldAO生成相关数据的过程。通过gdb得到了Cook阶段,引擎的执行堆栈:

UStaticMesh::Serialize
    FStaticMeshRenderData::Serialize
        FDistanceFieldAsyncQueue::BlockUntilBuildComplete

上述堆栈的上下文代码如下:

uint8 ClassDataStripFlags = 0;

#if WITH_EDITOR
        const bool bWantToStripDistanceFieldData = Ar.IsCooking() 
            && (!Ar.CookingTarget()->SupportsFeature(ETargetPlatformFeatures::DistanceFieldAO) || !Ar.CookingTarget()->UsesDistanceFields());

        ClassDataStripFlags |= (bWantToStripDistanceFieldData ? DistanceFieldDataStripFlag : 0);
#endif

        FStripDataFlags StripFlags(Ar, ClassDataStripFlags);
        if (!StripFlags.IsDataStrippedForServer() && !StripFlags.IsClassDataStripped(DistanceFieldDataStripFlag))
        {
            if (Ar.IsSaving())
            {
                GDistanceFieldAsyncQueue->BlockUntilBuildComplete(Owner, false);
            }

            for (int32 ResourceIndex = 0; ResourceIndex < LODResources.Num(); ResourceIndex++)
            {
                FStaticMeshLODResources& LOD = LODResources[ResourceIndex];

                bool bValid = (LOD.DistanceFieldData != nullptr);

                Ar << bValid;

                if (bValid)
                {
#if WITH_EDITOR
                    if (Ar.IsCooking() && Ar.IsSaving())
                    {
                        check(LOD.DistanceFieldData != nullptr);

                        float Divider = Ar.CookingTarget()->GetDownSampleMeshDistanceFieldDivider();

                        if (Divider > 1)
                        {
                            //@todo - strip mips
                            LOD.DistanceFieldData->Serialize(Ar, Owner);
                        }

可以看到这里的逻辑,就是等待距离场的异步构建完成,然后将构建得到的内容序列化到各层级的LOD中。

我简单魔改验证了下,在构建linux client时,忽略如上烘培逻辑确实大大减少了构建时间。 而且和相关同学确认,该部分逻辑不影响Gameplay。

不过比较正式的方法应该是修改Cooking Target的Support Feature,初略看了下,大致在如下类进行修改:TLinuxTargetPlatform, FLinuxPlatformProperties。

三 关于烘培的后续TODO项

手头还有其他事情要处理,有一些关于烘培的细节,后续抽时间再研究下:

  1. 现在Linux Client时间比DS构建仍然长很多,说明还有很多和渲染相关的逻辑在烘培阶段进行, 但单纯的魔改IsDataStrippedForServer会有报错。后续继续研究下,缩短Cook时间的可行方法。
  2. 烘培的更多细节,包括editor资源的哪些部分会转成uexp,shader的单文件存储方式等。
  3. 烘培逻辑的全流程梳理,包括CookOnTheFlyServer,Package Save等逻辑。

随笔系列说明

23年新挖一个《Unreal随笔系列》的坑。所谓随笔就是研究过程中的一些想法随时记录;细节可能来不及考证,甚至一些想法可能也不太成熟,有失偏颇;希望读者也可以帮忙指正和讨论。这个系列主要求量,希望每个月给自己布置一些研究小课题,争取今年发满12篇。