Baked lighting & World partition

Hi,

We’ve been trying to stand up baking of static lighting on CI, for World Partition maps; using the WorldPartitionStaticLightingBuilder via the WorldPartitionBuilderCommandlet.

We’re using WorldPartition for our maps to minimize actor overhead for actors outside of our relevant space, and so that we can take advantage of data layers.

This is also a mobile title - if that matters for this topic

We’ve run into a couple issues, we’ve fixed a couple things. But some issues remain unsolved.

Issues we’ve encountered:

issue 1 - If you use the two step process where the mappings are deferred till the second phase, the generated static light maps are dropped before export, and lost.

This was due to a missing value in a copy constructor.

Fix copy constructor causing static shadows to be discarded during bake and export by Owen3000s · Pull Request #15150 · EpicGames/UnrealEngine

issue 2 - We were getting an out of memory crash when an artist was using GPU Lightmass.

Digging into the logs, we found this:

LogLightmassSolver: Warning: Volumetric lightmap grid size is too large which can cause potential crashes or long build time, clamping from (2049, 2865, 1030792152) to (2049, 2865, 3200)

In StaticLightingBuildContext.cpp, the bounds of the volumetric lighting grid are determined like so:

VolumetricLightMapGridDesc->Initialize(InWorld, InWorld->GetWorldPartition()->GetRuntimeWorldBounds());

GetRuntimeWorldBounds() takes into account all the actors in the world.

The issue was that AMapBuildData actors which are generated to store per cell build data, are generated with bounds that reach to +-HALF_WORLD_SIZE in the Z axis. This comes from the Streaming Cell, which is based on a 2D grid.

The first bake is ok, but any subsequent has polluted RuntimeWorldBounds() resulting in oversized Volumetric Lighting Bounds.

Fix crash building static lighting for World Partition maps by Owen3000s · Pull Request #15151 · EpicGames/UnrealEngine

issue 3 - The static shadows aren’t showing up in the Editor after a commandlet bake.

We have verified that the MapBuildData entries for a mesh do contain the light map and shadow map data.

Also they do show up at runtime in Mobile device builds.

We have seen static shadows show up in editor after an in-editor bake, but these may be using the “preview path”

We could use some assistance in tracking down the issue #3, as well as a critical eye over our fixes for 1 and 2.

[Attachment Removed]

Hello, thanks for reaching out about those static lighting issues. I checked in with the dev team, and they told me they are looking to take the two pull requests you created. They are just deciding which version the changes will land in (5.9 or a later release), which is why you haven’t heard back from someone yet.

As for your third issue, can you tell me the following:

  1. In the editor, do the affected static meshes lose only the shadow, or do they lose all of their baked lighting?
  2. And do the lights still cast some kind of shadow, just not the baked one?

Based on your answers, I can suggest some further methods to narrow down the issue. Let me know if anything is unclear.

Best,

Tim

[Attachment Removed]

Hi Tim,

1) In the editor I do see the static lightmaps (with bounce lighting and ambient occlusion shadowing) on objects

2) I do see dynamic shadows when playing in editor. So the objects are casting shadows. I just don’t see the casted static shadows in editor or PIE.

I have validated the following things:

• Lights and meshes are exported to Lightmass during the bake

• SDF Shadow maps are generated for meshes that receive shadows, and Shadow Depth map for lights

• These are imported by the commandlet after the lightmass bake

• They get saved into the _BuiltData and MapBuildData actors

In editor, when loading the map:

In StaticMeshSceneProxy.cpp, in the FLODInfo constsructor I can see a shadow map and light map being assigned from the MapBuildData registry.

In the FLightSceneProxy I can see the static shadow depth map being assigned for a directional light.

[Attachment Removed]

Hi Damon,

Ok, it sounds like the World Partition is fine, and there might be a discrepancy with how the shadows are rendered in-editor and the baked-out data. The quickest single test you can do right now is to switch the editor Preview Platform to Android, or launch with -featureleveles31. That puts the editor on the same mobile renderer and lightmap policy as the device. If the static shadows appear there, the baked data is good, and you can try the following suggestions next:

The first thing I would check is r.HighQualityLightMaps, or Support High Quality Lightmaps in Project Settings > Rendering. In FBasePassMeshProcessor::GetUniformLightMapPolicyType, the policy that actually samples a baked shadowmap, LMP_DISTANCE_FIELD_SHADOWS_AND_HQ_LIGHTMAP, is only reachable inside the bAllowHighQualityLightMaps branch. With HQ lightmaps off, the mesh falls through to LMP_LQ_LIGHTMAP, which has no shadowmap at all, and WRITES_PRECSHADOWFACTOR_ZERO then zeroes the precomputed shadow factor in the GBuffer.

Second, check whether Virtual Shadow Maps are active in your editor viewport (Project Settings > Rendering > Shadow Map Method). FLightSceneProxy’s constructor sets bStaticShadowing to bStaticLighting when non-Nanite VSMs are enabled, which forces ShadowMapChannel to INDEX_NONE and makes shaders treat the surface as having no static shadow map. Since VSMs do not exist on mobile devices, this would explain the dynamic shadows you are seeing. bUseVirtualShadowMaps is cached in the proxy constructor, soturning off VSM (r.Shadow.Virtual.Enable=0) needs a map reload to take effect. While you are there, also confirm r.AllowStaticLighting is 1 and r.DynamicGlobalIlluminationMethod is not 1, since static lights do not render with Lumen enabled.

Third, the directional light’s Dynamic Shadow Distance StationaryLight. In the deferred path, the baked shadow is combined via a lerp between the whole-scene CSM and static shadows, using a distance fadeout (r.Shadow.DistanceScale). Inside the radius, you get only the cascades and zero baked contribution, so if that radius covers your entire view, you will never see the baked shadow in the editor. If mobile CSM is disabled on the device, the far distance is 0, and the baked shadow applies everywhere. Setting the Dynamic Shadow Distance for StationaryLight to 0 on the directional light should then restore the shadows.

Let me know if any of those suggestions help or if you need any further clarification.

Cheers,

Tim

[Attachment Removed]

Hi Damon, sorry I was out of the office for a few days and just now getting caught up on work.

Taking your last point first: the Android preview path is a reasonable thing to try, but in your case, it was an inconclusive test rather than a negative result. FLightMap2D::GetInteraction picks LightmapIndex = AllowHighQualityLightmaps(FeatureLevel) ? 0 : 1, so on ES3.1 it always uses the low-quality texture at index 1. If that texture is null, it returns FLightMapInteraction::None(), which falls into the default case of GetUniformLightMapPolicyType and gives you exactly the LMP_NO_LIGHTMAP you saw. That path never reached any shadow logic at all. Worth checking r.SupportLowQualityLightmaps, since LQ coefficients are only encoded when it is on, and a missing LQ texture in the editor is itself suspicious, given the device build renders lightmaps fine.

The PC_SM6 result is the useful one, and it narrows this a lot. LMP_DISTANCE_FIELD_SHADOWS_AND_HQ_LIGHTMAP is only selected when LCI->GetShadowMapInteraction() returns SMIT_Texture, which requires a non-null shadowmap texture, and GetClusterInput pulls that same texture into the lightmap resource cluster. So the mesh side is fully wired up. The break has to be further down, in the handoff between the mesh and the light.

Given your settings, that is now the only thing left. FLightSceneProxy assigns ShadowMapChannel = MapBuildData->ShadowMapChannel only when MapBuildData is non-null, bStaticShadowing is true, and bStaticLighting is false. With VSMs off, r.AllowStaticLighting=1 and a stationary light, the two flags are already correct, so either GetLightComponentMapBuildData() is returning null or the stored channel is itself INDEX_NONE.

Could you put a breakpoint in the FLightSceneProxy constructor, on the line that fetches GetLightComponentMapBuildData(), and tell me the following for your directional light: whether MapBuildData is null, the values of bStaticShadowing and bStaticLighting, and the resulting ShadowMapChannel. Alongside that, in the FLODInfo constructor, where you already saw the shadowmap, the FShadowMap2D’s bChannelValid array. If the light says channel 2 and the mesh has bChannelValid[2] set to false, or the light says INDEX_NONE, that pins it down exactly.

Second, search your bake log for “Failed to allocate shadowmap channel”. ReassignStationaryLightChannels emits a performance warning when overlapping stationary lights exhaust all four channels. When it fires, the stored channel is INDEX_NONE, and bChannelValid stays false everywhere, which looks exactly like what you are seeing.

One thing I would like to rule out while you are in there: can you confirm the mobile device build was cooked with the same lightmap bake? The missing LQ lightmap in the editor makes me want to verify that the editor and the device are viewing the same content.

[Attachment Removed]

Hi Tim,

This looks like it might have been an issue with data in my branch.

I disovered when I cleaned up the PRs and merged them into new set of branches that the CI built static shadows ARE showing in the editor now. Both in PC_SM6 and Android_Vulkan_High. Across several bakes.

I’ve now merged this into main and we are rolling it out to our Art team to keep experimenting. If our Art team also encounters this issue, I will dig into it further then.

Thanks for all your efforts in helping to investigate this!

[Attachment Removed]

Hi Damon,

No problem at all! Glad to hear that the issue might have just been a mix-up in your merge. If you are still having trouble with this once you have rolled out the change to your Art team, feel free to reach out again. I will close out this ticket for now.

Best,

Tim

[Attachment Removed]

Hi Tim,

I’ve looked over the areas you suggested.

First up, All the render settings mentioned seem ok.

r.HighQualityLightMaps = “1” LastSetBy: Constructor

ShadowMapMethod = ShadowMaps // not Virtual Shadow Maps

r.Shadow.Virtual.Enable = “0” LastSetBy: ProjectSetting

r.AllowStaticLighting = “1” LastSetBy: ProjectSetting

r.AllowStaticLightingInWorldPartitionMaps = “1” LastSetBy: SystemSettingsIni

r.DynamicGlobalIlluminationMethod = “0” LastSetBy: ProjectSetting

r.Shadow.DistanceScale = “1” LastSetBy: Scalability

Regarding each question.

#1 - the lightmap policy

  • When running in the editor on PC_SM6, for some objects I can see we do get LMP_DISTANCE_FIELD_SHADOWS_AND_HQ_LIGHTMAP.
  • When running in the editor on device preview: Android_Vulkan_High, the shader feature level is set to ES31, so the AllowHighQualityLightmaps() function returns false. The lighting policy is LMP_NO_LIGHTMAP.

Can’t see the shadowmaps in either the PC_SM6 or the ES31 paths.

#2 - Virtual shadow map are not enabled.

#3 - directional light settings

Our directional light is stationary, its “Dynamic Shadow Distance” is set to 3000 (30m).

I tried dropping this to 0. It didn’t cause the static shadows to appear.

Is trying to view commandlet built shadows in editor, a bit of an unexpected workflow in the Android preview path?

[Attachment Removed]