Contents

UE4 lightmass数据流程

构建

在编辑器中,我们可以找到build的按钮,当然,我们这里讨论的并不是构建所有的东西,而仅仅讨论在构建灯光的时候。关于构建参数,请参考光照设置 @think.ouyang欧阳江 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture6ab4cdb1659af12ca664ee91ca4c36ee.png

编辑器构建系统

当我们点击build的时候,我们将触发下面的函数, https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture4f3dc894baeea3ab07d3ab7bcebdfcaf.png

1
2
3
4
5
6
7
FEditorBuildUtils::EditorBuild();
//其会调用StopRenderingThread();
if (GUseThreadedRendering)
{
    StartRenderingThread();
}  

其中,关于build的内容定义为
https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture8c449d957cee9fbe3adec1f47b3ab6c3.png

目前我们仅关心的是BUILDTYPE_Lighting,看buildLighting的分支

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture4d150e3b8fc9ba17ed5214c29ed93fc5.png

这里会对BSP进行特殊处理,如果场景中没有BSP的话,我们就不需要进行 geometry rebuild。

之后,我们会进行BuildLighting。

我们看一下传入的数据LightingBuildOptions的填充,其是取的config里面的数据

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture62072ab3ffc600b877d23d9f8cb8f9db.png

在这里,收前强制关闭所有的texture property windows,因为这些在构建灯光时是无效的。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picturecfdbe1f0b17a087c9cb469a36ddf856b.png

之后有一个回调这个目前来看并没有东西,我们暂且先不去管他。

建立构建系统

首先FStaticLightingManager,其为单例模式,用来进行管理静态光照的所有系统和子系统。其只是一个管理功能,而不是一个真正的执行build的类。

其最开始的入口就是CreateStaticLightingSystem。如果目前已经有了StaticLightingSystems那么会发生警告说

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture![](mediae2d760a41939c249750e46684c7fb015.png).png

如果目前没有残存的余孽的话,我们会有进行接下来的流程。这里会有一个逻辑,如果我们的配置中没有选择bOnlyBuildVisibility,也就是是由只是构建可见的,其会在结束后build ReflectionCaptures。

之后,对整个world的所有level进行判断,看是否有lighting scenario,这个概念是说同一个level可以有不同的光照图。这里是说,这个level是否是一个lighting scenario。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture109d4be4e555f5c4f977e6376750f4e9.png

UE4光照系统.pptx 中有介绍过。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture10249cfc7b1980a8309c39f54d561894.png

我们会把需要构建的level进行添加,如果我们对应的level,我们添加空的。

我们会将每一个level添加入FStaticLightingManager内部中的真正处理的类为StaticLightingSystem其进行处理的单元是一个level,这从它的构造函数可以很明显的看出来。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture09ffd6c9960f94894c22d3f4529413e3.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture1539110c73cfe1c530426de6f966ec25.png

在FStaticLightingManager中有一个针对class FStaticLightingSystem*的指针。

之后获取列表中StaticLightingSystems的指针,然后进行真正的构建,BeginLightmassProcess,如果成功,SendProgressNotification进行显示,如果失败DestroyStaticLightingSystems通知显示。

每一个level的构建

因此,我们进入FStaticLightingSystem,来进行每一个level的构建

  1. 初始化。对所有的<UPrimitiveComponent>,里面的VisibilityId进行赋值INDEX_NONE初始化。对所有的level,如果不是当前的PersistentLevel,那就FindStreamingLevel。并且在设置中,会有设置,如是否只build CurrentLevel,bOnlyBuildSelectedLevels等会有不同的变化。那些被Skipped的Levels会被收集。这里还会有TextureStreaming的配置的搜集。在进行load一些其他的LoadGlobalIniFile里面的东西。

  2. 收集灯光,剔除所有的AGeneratedMeshAreaLight(在逻辑上是自发光物体)。搜集场景中所有的ULightComponentBase存储在TArray<ULightComponentBase*> Lights;

  3. 收集mesh,Gather static lighting info from BSP.Gather HLOD primitives Gather static lighting info from actors.Recurse through HLOD trees, group actors and calculate child ranges。

这里面非常重要的结构

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture2de16eb9f329a27fab7ddc66cd098ae8.png

显而易见,这里一个是有排序的,一个是没有排序的。

我们将看到FStaticLightingMapping在世界的平面和static lighting cache的一个映射。

其子类为FStaticLightingTextureMapping,其记载着对应关系。

FStaticLightingTextureMapping

  1. 记录着光照图的大小。SizeX,SizeY

  2. 记录着记录coordinate index ,LightmapTextureCoordinateIndex

而每一个FStaticLightingMapping,其实都是从每一个普通的mesh身上获取的,我们都会有获取

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture315dba178efa91b4c563ddab95607759.png

这里对一个Mesh来说,实际上的区别是lod

与lightmass进程交互

当我们收集了几乎所有的东西,我们将建立lightmass的进程。加载SwarmInterface.dll,并进行链接,如果链接成功,进行GatherScene,FStaticLightingSystem会有一个FLightmassProcessor指针,其会有一个导出的东西,这里会进行导出

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture9f3e371827142d666b43d3736ba4a1b4.png

其会添加其所需要的东西ALightmassImportanceVolume,ALightmassCharacterIndirectDetailVolume,AVolumetricLightmapDensityVolume,ULightmassPortalComponent,UAtmosphericFogComponent,GetImportanceVolumes,AddLight,添加所有的Meshes。之后,初始化massProcessor。结束

构建结束

如果构建结束的话,我们会得到lightmass返回的光照结果,这里有着我们更加需要的信息。

首先触发的是

UEditorEngine::UpdateBuildLighting()

FStaticLightingManager::UpdateBuildLighting()

结束后仍然要通知FStaticLightingManager

ActiveStaticLightingSystem->UpdateLightingBuild();

这里仍然对每一个FStaticLightingSystem进行更新。这里对于目前处于的状态有一个判断

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture2b4307ee72477fdb18fcfe9c6db7c999.png

在结束时我们的状态应该时FStaticLightingSystem::AutoApplyingImport

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture9f0f218448e7852aa1bb2443bc770651.png

调用FStaticLightingManager::ProcessLightingData();进行应用数据。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picturee801ab313a34401c757c3f19e04e5908.png

这里我们仍要会到StaticLightingSystem进行FinishLightmassProcess

FinishLightmassProcess

  1. CompleteDeterministicMappings

  2. ReassignStationaryLightChannels

  3. EncodeTextures(bSuccessful);

  4. ApplyNewLightingData(bSuccessful);

  5. ReportStatistics();

到了我对感兴趣的地方,明显是EncodeTextures!!!!!!!!!!!!!!!!!!!!,不光还是要介绍之前的相关细节

CompleteDeterministicMappings

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picturee89445036aebef09f59113ef2479b964.png

会将所有的mapping对应的

Textures

这里会有两个编码,一个是FLightMap2D,一个是FShadowMap2D

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture577df9f12d6b2f5cb1f1e90ed8d14d63.png

只看一个即可

渲染流程

再shader里面总共用到了三方数据

LightmapDataIndex

我们首先要得到关于一个顶点到底使用了那张光照图,我们根据PrimitiveId,和LocalVF_LODLightmapDataIndex最后得到。

LightmapDataIndex = GetPrimitiveData(Intermediates.PrimitiveId).LightmapDataIndex + LocalVF_LODLightmapDataIndex;

LightmapDataIndex
https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture1a4894166cb66ffd1411ab3bc78fddd7.png

这里的LightmapDataIndex是在GPUScene里面的PrimitiveSceneData里面。这里记录的是使用的哪款lightmap.

b. LocalVF_LODLightmapDataIndex

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture156378db0d4183ef907155285f33ad04.png

这里的东西最终是在LocalVertexFactory的uniform里面。但是其是从FStaticMeshDataType传入的,是lod的index。也就是说它的不同的lod是有不同的texture,是硬件做的,还是lightmap出来的目前还没有看。

View_LightmapSceneData

float4 LightMapCoordinateScaleBias = GetLightmapData(LightmapDataIndex).LightMapCoordinateScaleBias;

之后取Bias,bias在View_LightmapSceneData身上,也就是在GPUScene里面。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture6997f7fdf01e7aa79b279b81c04ce0fc.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture89645e1e5eeb0ad63caa8fd506e4f1ca.png

在view的SetupUniformBufferParameters时,这里的LightmapDataBuffer将会传入View的里面。

这里的东西是GPUScene里面的东西。

里面的内容有,StaticShadowMapMasks,InvUniformPenumbraSizes,LightMapCoordinateScaleBias,ShadowMapCoordinateScaleBias,LightMapScale,LightMapAdd,LightMapAdd,LightmapVTPackedPageTableUniform,LightmapVTPackedUniform。

看其GPUSCENE里面的内容是在这里Primitive加入到Fscene里面的时候进行的add

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture91ca66fadf644ffc356234cfda0c6063.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture66163ff29987286821783fa57c078ce8.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture7b2aeea49f6457c4bf9ce098653e383b.png

不过这里的东西只是标记,而没有实际的赋值,真正的赋值在UpdateGPUScene

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture96d779486fd71079d5862e5c3854c6bf.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture6a165b28f085c31188e052db7e6a99e9.png

对于FLightmapSceneShaderData和FPrimitiveSceneShaderData的更新方式应该是一致的。

  1. PrimitiveSceneProxy->GetLCIs(LCIs);这里是直接提取Primitive的FLODInfo

  2. FLightmapSceneShaderData LightmapSceneData(LCIs[i], Scene.GetFeatureLevel());

    对于FLightmapSceneShaderData的构建

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture0501fd4ba2b33a88ab0af2c2ece19966.png

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picturee3f5cafddba08932c50c649481c2aacc.png

  1. LightmapDataUploadBuilder.Add(LightmapDataOffset + i, &LightmapSceneData.Data[0]);

其这里的主要内容也是在LODs里面,我们更加深入来看LODs是在如何构建的

在FLODInfo的构造函数中FStaticMeshSceneProxy::FLODInfo

由于FLODInfo是根据

const FStaticMeshComponentLODInfo& ComponentLODInfo = InComponent->LODData[LODIndex];

const FMeshMapBuildData* FoundData = MeshBuildData.Find(MeshId);

是从FMeshMapBuildData中取的东西,根据MeshId

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture9551791747ab1f2c174a2bcb5b2ccd55.png 主要是 LightMap->GetInteraction(InFeatureLevel)里面的内容

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/pictureb270ff3df15f2f870ec9c15f641c926d.png 在 FLightMapPendingTexture::PostEncode()时进行的

void UMapBuildDataRegistry::InvalidateStaticLighting

FLightmapResourceCluster

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture615a172bfdc6783d9c2a1d821376a1ac.png 在FLightmapResourceCluster中,有一个专门的uniformbuffer, https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture83037ee57e50a285fa29f1641c7bee43.png

这个FLightmapResourceCluster存在FLightCacheInterface中

对于StaticMesh来说,其主要的地方就是FLODInfo中

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/pictureb202bdda00eb45cd13589fe1aefcbeb2.png PrecomputedLightingBuffer

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture50c3f93ed52ea4f17623a12c362777a8.png