【UE4源代码观察】观察Lightmass一次Build的流程

本篇目标

当使用者在UE4的编辑器内点下了Build按钮,一系列Build命令被触发,而“光照”则是其中重要的一环。我希望粗略的了解一下这背后的代码逻辑,随后我发现这个过程和 SwarmLightmass 这两个概念有关。具体来讲,这个过程涉及的代码分布在下面几个部分中:

  1. UnrealEd模块中相关的代码。主要是EditorBuildUtils.h/cppLightmass文件夹中的代码、StaticLightingSystem文件夹中的代码。
  2. SwarmInterface模块。定义了与Swarm进行交互的接口。
  3. \Source\Programs\UnrealLightmass项目。负责生成 \Engine\Binaries\Win64\UnrealLightmass.exe
  4. \Source\Programs\UnrealSwarm文件夹包含的几个c#项目。负责生成\Engine\Binaries\DotNET\SwarmAgent.exe\Engine\Binaries\DotNET\SwarmCoordinator.exe。以及依赖的一些dll。

我的目标是弄明白在一次Lightmass的Build流程中,这几个部分所扮演的角色。
关于Lightmass这套系统的一些原理,我从《Lightmass源码分析之 与Swarm交互 - 知乎》中以及前后相关的文章中学到了很多。

观察依赖关系

SwarmAgent.exe和SwarmCoordinator.exe

这几个部分中,Swarm的程序是相对独立的。在《官方文档:Unreal Swarm》中也可以看到,当在另一台机器上部署swarm来帮助联机渲染时,只需要将Swarm相关的exe与dll拷贝过去就可以了,并不需要UE4引擎本身。

\Source\Programs\UnrealSwarm下,主要有两个sln:SwarmAgent.slnSwarmCoordinator.sln
打开SwarmCoordinator.sln后可以发现一个同样叫SwarmCoordinator的项目,而这个项目的属性应用程序输出类型Windows应用程序,也就是会输出exe。而剩余的项目的输出类型都是类库,也就是会输出dll。此外,可以看到SwarmCoordinator项目依赖了SwarmCommonUtils项目SwarmCoordinatorInterface项目
在这里插入图片描述
同理,打开SwarmAgent.sln可以发现:Agent项目依赖了AgentInterface项目SwarmCommonUtils项目SwarmCoordinatorInterface项目UnrealControls项目
在这里插入图片描述
也就是说,AgentCoordinator 都依赖了SwarmCommonUtils以及各自的Interface。不过 Agent 还额外依赖了UnrealControls,以及Coordinator的Interface。看来Agent调用了Coordinator的接口。
总结这几个程序集的依赖关系:
在这里插入图片描述

UnrealLightmass.exe

UnrealLightmass是一个C++项目,所以用了UBT进行编译,因此也有配套的UnrealLightmass.Target.cs这个目标描述文件,以及UnrealLightmass.Build.cs这个模块描述文件。
UnrealLightmass这个项目的属性页可以看到它调用了UBT并使用UnrealLightmass.Target.cs作为目标,最后输出UnrealLightmass.exe。
在这里插入图片描述
UnrealLightmass.Build.cs可以看到它依赖了一些诸如CoreCoreUObject这些UE4核心的模块,以及SwarmInterface模块。

SwarmInterface模块

SwarmInterface.Build.cs中可以看到它只依赖了CoreCoreUObject模块。但它还指出了需要AgentInterface.dll

// Copy the AgentInterface DLL to the same output directory as the editor DLL.
RuntimeDependencies.Add("$(BinaryOutputDir)/AgentInterface.dll", "$(EngineDir)/Binaries/DotNET/AgentInterface.dll", StagedFileType.NonUFS);

// Also copy the PDB, if it exists
if(File.Exists(Path.Combine(EngineDirectory, "Binaries", "DotNET", "AgentInterface.pdb")))
{
	RuntimeDependencies.Add("$(BinaryOutputDir)/AgentInterface.pdb", "$(EngineDir)/Binaries/DotNET/AgentInterface.pdb", StagedFileType.DebugNonUFS);
}

此外,关于这个模块我目前不太理解的是,它还包括一些C#代码:
在这里插入图片描述
那么这些C#代码该如何与其他C++部分交互呢?有待后续研究。
不过在其中的SwarmInterface.cs中确实能看到它需要AgentInterface

UnrealEd模块

UnrealEd模块包含了相当多的内容,烘焙光照只是其中一部分。
UnrealEd.Build.cs中可以看到,它依赖了SwarmInterface模块。
而对于UnrealLightmass,它需要include:

// Add include directory for Lightmass
PublicIncludePaths.Add("Programs/UnrealLightmass/Public");

搜索Lightmass命名空间也确实发现了很多使用之处:
在这里插入图片描述

总结依赖关系图

他们之间的依赖关系可以表示成如下:
在这里插入图片描述
箭头的含义并不尽相同,为此我标注了每一个箭头。但相同的是:箭头尖端的代码对箭头尾端的代码是无知的。

观察StaticLightingSystem的各个阶段

当按下Build按钮后:
在这里插入图片描述
FEditorBuildUtils::EditorBuild被调用
在这里插入图片描述
随后,UEditorEngine::BuildLighting被调用来构建光照,而FStaticLightingSystem负责来维护整个光照构建流程。
FStaticLightingSystem有私有变量CurrentBuildStage表示了当前所处的阶段:

/** Lighting comes in various stages (amortized, async, etc.), we track them here. */
enum LightingStage
{
	NotRunning,
	Startup,
	AmortizedExport,
	SwarmKickoff,
	AsynchronousBuilding,
	AutoApplyingImport,
	WaitingForImport,
	ImportRequested,
	Import,
	Finished
};
FStaticLightingSystem::LightingStage CurrentBuildStage;

下面,尝试粗略观察一下每个阶段所做的事情。

0. NotRunning

CurrentBuildStage在构造函数中赋值为NotRunning
随后,当构建光照一开始就被赋值为Startup
在这里插入图片描述

1. Startup

这个阶段包含了很多准备工作,主要是在FStaticLightingSystem::BeginLightmassProcess中完成的。

1.1 GatherStaticLightingInfo

在这里插入图片描述
上面的信息就是来自于:
在这里插入图片描述

1.2 CreateLightmassProcessor

BeginLightmassProcess中创建了一个新的FLightmassProcessor对象。

而在FLightmassProcessor的构造函数中,FSwarmInterface接口的OpenConnection函数被调用。

FSwarmInterfaceImplFSwarmInterface的实现。而FSwarmInterfaceImpl::OpenConnection具体执行的内容实际由C#代码所指定。具体来说:
C++有一个FSwarmInterface:
在这里插入图片描述
而C#也有一个FSwarmInterface
在这里插入图片描述
不过C++的版本的OpenConnection等函数并没有本体,只是企图从外部得到一个函数指针。而C#版的就有具体函数的行为。
在C#版的FSwarmInterfaceOpenConnection函数中,EnsureAgentIsRunning被调用,而它启动了SwarmAgent.exe
在这里插入图片描述

1.3 InitiateLightmassProcessor

其中FLightmassExporter::WriteToChannel尝试将“光照”、“模型”、“地形”等数据从UE的格式导出为Lightmass的格式:
在这里插入图片描述
例如灯光:

void Copy( const ULightComponentBase* In, Lightmass::FLightData& Out )
{	
	FMemory::Memzero(Out);

	Out.LightFlags = 0;
	if (In->CastShadows)
	{
		Out.LightFlags |= Lightmass::GI_LIGHT_CASTSHADOWS;
	}

	if (In->HasStaticLighting())
	{
		Out.LightFlags |= Lightmass::GI_LIGHT_HASSTATICSHADOWING;
		Out.LightFlags |= Lightmass::GI_LIGHT_HASSTATICLIGHTING;
	}
	else if (In->HasStaticShadowing())
	{
		Out.LightFlags |= Lightmass::GI_LIGHT_STORE_SEPARATE_SHADOW_FACTOR;
		Out.LightFlags |= Lightmass::GI_LIGHT_HASSTATICSHADOWING;
	}

	if (In->CastStaticShadows)
	{
		Out.LightFlags |= Lightmass::GI_LIGHT_CASTSTATICSHADOWS;
	}

	Out.Color = In->LightColor;

	// Set brightness here for light types that only derive from ULightComponentBase and not from ULightComponent
	Out.Brightness = In->Intensity;
	Out.Guid = In->LightGuid;
	Out.IndirectLightingScale = In->IndirectLightingIntensity;
}

从中就可以看出来它得到一个U对象ULightComponentBase的参数,并将他们赋值给了一个F对象FLightData对应的参数。而FLightData是在\Source\Programs\UnrealLightmass\Public\SceneExport.h中所定义的。
InitiateLightmassProcessor最后,CurrentBuildStage被赋值为AmortizedExport
在这里插入图片描述
随后,FEditorBuildUtils::EditorBuild返回。

2. AmortizedExport

之后,FStaticLightingSystem::UpdateLightingBuild()在主循环中被调用,不断处理接下来的阶段:
在这里插入图片描述
LightmassProcessor->ExecuteAmortizedMaterialExport()返回“完成时”,CurrentBuildStage被赋值为SwarmKickoff

3. SwarmKickoff

CurrentBuildStage==SwarmKickoff时,KickoffSwarm()立马被调用,其中的逻辑很简单:

bool bSuccessful = LightmassProcessor->BeginRun();
if (bSuccessful)
	CurrentBuildStage = FStaticLightingSystem::AsynchronousBuilding;

LightmassProcessor::BeginRun中,UnrealLightmass.exe以及其依赖的dll被指定:
在这里插入图片描述
这些信息被加入到了一个NSwarm::FJobSpecification对象中,而它将作为调用FSwarmInterface::BeginJobSpecification时的参数。

最后,当LightmassProcessor->BeginRun()成功时,CurrentBuildStage被赋值为AsynchronousBuilding

4. AsynchronousBuilding

此时,UE4编辑器内会不断更新目前的进度:
在这里插入图片描述
此处的代码是:

FText Text = FText::Format(LOCTEXT("LightBuildProgressMessage", "Building lighting{0}:  {1}%"), FText::FromString(ScenarioString), FText::AsNumber(LightmassProcessor->GetAsyncPercentDone()));
FStaticLightingManager::Get()->SetNotificationText( Text );

最后,当LightmassProcessor->Update()给出“完成”的结果时,CurrentBuildStage会被赋值为AutoApplyingImport(如果失败的话则直接被赋值为Finished)
在这里插入图片描述

5. AutoApplyingImport

接下来:
在这里插入图片描述
FStaticLightingManager::ProcessLightingData()中,FStaticLightingSystem::FinishLightmassProcess()被调用,它将CurrentBuildStage赋值为了Import
在这里插入图片描述
当然,这只是暂时的,因为在FStaticLightingManager::ProcessLightingData()执行后CurrentBuildStage就变为了Finished了。

6. Finished

CurrentBuildStage变为了Finished之后,ActiveStaticLightingSystem将变为NULL,因此,FStaticLightingSystem::UpdateLightingBuild()再也不会被执行了:
在这里插入图片描述
至此Build流程结束。

总结

总的来说,目前的观察还是在一个粗粒度级别上的。对于很多细节还并不明白,例如:

  • SwarmInterface模块中的C#代码如何与C++部分进行交互?
  • SwarmAgentSwarmCoordinator将如何交互?
  • FLightmassExporter::WriteToChannel中的数据将怎样导出给Lightmass?
  • Lightmass的结果又将如何返回?

这些都值得后续研究。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值