MaxTransitionDistanceWorldSpace

MaxTransitionDistanceWorldSpace

#Overview

name: MaxTransitionDistanceWorldSpace

The value of this variable can be defined or overridden in .ini config files. 1 .ini config file referencing this setting variable.

It is referenced in 14 C++ source files.

#Summary

#Usage in the C++ source code

The purpose of MaxTransitionDistanceWorldSpace is to control the maximum distance for shadow transitions in the static lighting system of Unreal Engine 5. This setting is primarily used in the rendering system, specifically for static shadow calculations.

This variable is utilized by the Lightmass subsystem, which is responsible for precalculating static lighting in Unreal Engine. It’s particularly important for the signed distance field shadow mapping technique.

The value of this variable is set in the Lightmass configuration file (GLightmassIni). It’s read from the “DevOptions.StaticShadows” section of this configuration file.

MaxTransitionDistanceWorldSpace interacts with several other variables, including:

  1. ApproximateHighResTexelsPerMaxTransitionDistance
  2. MinDistanceFieldUpsampleFactor
  3. StaticLightingLevelScale

Developers should be aware that:

  1. This variable directly affects the precision and maximum penumbra size that can be reconstructed from the distance field.
  2. Larger values decrease precision but allow for larger penumbra sizes.
  3. It’s scaled by the StaticLightingLevelScale, which means it adjusts based on the overall scale of the level.

Best practices when using this variable include:

  1. Carefully balancing it with ApproximateHighResTexelsPerMaxTransitionDistance to achieve the desired quality and performance.
  2. Considering the scale of your level when setting this value, as it’s in world space units.
  3. Testing different values to find the optimal balance between shadow quality and performance for your specific scene.
  4. Being mindful of its impact on build times, as larger values can increase the time needed for static lighting calculations.

#Setting Variables

#References In INI files

Location: <Workspace>/Engine/Config/BaseLightmass.ini:143, section: [DevOptions.StaticShadows]

#References in C++ code

#Callsites

This variable is referenced in the following C++ source code:

#Loc: <Workspace>/Engine/Source/Editor/UnrealEd/Private/Lightmass/Lightmass.cpp:2315

Scope (from outer to inner):

file
function     void FLightmassExporter::WriteSceneSettings

Source code excerpt:

		VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticShadows"), TEXT("bAllowSignedDistanceFieldShadows"), bConfigBool, GLightmassIni));
		Scene.ShadowSettings.bAllowSignedDistanceFieldShadows = bConfigBool;
		VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticShadows"), TEXT("MaxTransitionDistanceWorldSpace"), Scene.ShadowSettings.MaxTransitionDistanceWorldSpace, GLightmassIni));
		VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticShadows"), TEXT("ApproximateHighResTexelsPerMaxTransitionDistance"), Scene.ShadowSettings.ApproximateHighResTexelsPerMaxTransitionDistance, GLightmassIni));
		VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticShadows"), TEXT("MinDistanceFieldUpsampleFactor"), Scene.ShadowSettings.MinDistanceFieldUpsampleFactor, GLightmassIni));
		VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticShadows"), TEXT("StaticShadowDepthMapTransitionSampleDistanceX"), Scene.ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceX, GLightmassIni));
		VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticShadows"), TEXT("StaticShadowDepthMapTransitionSampleDistanceY"), Scene.ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceY, GLightmassIni));
		VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticShadows"), TEXT("StaticShadowDepthMapSuperSampleFactor"), Scene.ShadowSettings.StaticShadowDepthMapSuperSampleFactor, GLightmassIni));
		VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticShadows"), TEXT("StaticShadowDepthMapMaxSamples"), Scene.ShadowSettings.StaticShadowDepthMapMaxSamples, GLightmassIni));

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/ImportExport/LightmassScene.cpp:523

Scope (from outer to inner):

file
namespace    Lightmass
function     void FScene::ApplyStaticLightingScale

Source code excerpt:

	VolumeDistanceFieldSettings.VoxelSize *= SceneConstants.StaticLightingLevelScale;
	VolumeDistanceFieldSettings.VolumeMaxDistance *= SceneConstants.StaticLightingLevelScale;
	ShadowSettings.MaxTransitionDistanceWorldSpace *= SceneConstants.StaticLightingLevelScale;
	ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceX *= SceneConstants.StaticLightingLevelScale;
	ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceY *= SceneConstants.StaticLightingLevelScale;
	IrradianceCachingSettings.RecordRadiusScale *= SceneConstants.StaticLightingLevelScale;
	IrradianceCachingSettings.MaxRecordRadius *= SceneConstants.StaticLightingLevelScale;

	// Photon mapping does not scale down properly, so this is disabled

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/TextureMapping.cpp:1928

Scope (from outer to inner):

file
namespace    Lightmass
function     void FStaticLightingSystem::CalculateDirectSignedDistanceFieldLightingTextureMappingTextureSpace

Source code excerpt:

		// The result is that small, high resolution meshes will not upsample as much, since they don't need it, 
		// But large, low resolution meshes will upsample a lot.
		const int32 TargetUpsampleFactor = FMath::TruncToInt(ShadowSettings.ApproximateHighResTexelsPerMaxTransitionDistance / (RightTriangleSide * ShadowSettings.MaxTransitionDistanceWorldSpace));
		// Round up to the nearest odd factor, so each destination texel has a high resolution source texel at its center
		// Clamp the upscale factor to be less than 13, since the quality improvements of upsampling higher than that are negligible.
		UpsampleFactor = FMath::Clamp(TargetUpsampleFactor - TargetUpsampleFactor % 2 + 1, ShadowSettings.MinDistanceFieldUpsampleFactor, 13);
	}
	MappingContext.Stats.AccumulatedSignedDistanceFieldUpsampleFactors += UpsampleFactor;
	MappingContext.Stats.NumSignedDistanceFieldCalculations++;

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/TextureMapping.cpp:2354

Scope (from outer to inner):

file
namespace    Lightmass
function     void FStaticLightingSystem::CalculateDirectSignedDistanceFieldLightingTextureMappingTextureSpace

Source code excerpt:

									}

									// Scatter to all distance field texels within MaxTransitionDistanceWorldSpace, rounded up.
									// This is an approximation to the actual set of distance field texels that are within MaxTransitionDistanceWorldSpace that tends to work out well.
									// Apply a clamp to avoid a performance cliff with some texels, whose adjacent texel in lightmap space is actually far away in world space
									const int32 NumLowResScatterTexelsY = FMath::Min(FMath::TruncToInt(ShadowSettings.MaxTransitionDistanceWorldSpace / (WorldSpacePerHighResTexelY * UpsampleFactor)) + 1, 100);
									const int32 NumLowResScatterTexelsX = FMath::Min(FMath::TruncToInt(ShadowSettings.MaxTransitionDistanceWorldSpace / (WorldSpacePerHighResTexelX * UpsampleFactor)) + 1, 100);
									MappingContext.Stats.NumSignedDistanceFieldScatters++;
									for (int32 ScatterOffsetY = -NumLowResScatterTexelsY; ScatterOffsetY <= NumLowResScatterTexelsY; ScatterOffsetY++)
									{
										const int32 LowResScatterY = LowResY + ScatterOffsetY;
										if (LowResScatterY < 0 || LowResScatterY >= TextureMapping->CachedSizeY)
										{

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/TextureMapping.cpp:2440

Scope: file

Source code excerpt:

												// World space distance from the distance field texel to the nearest shadow transition
												const float TransitionDistance = (ScatterPosition - HighResSample.GetPosition()).Size3();
												const float NormalizedDistance = FMath::Clamp(TransitionDistance / ShadowSettings.MaxTransitionDistanceWorldSpace, 0.0f, 1.0f);
												FSignedDistanceFieldShadowSample& FinalShadowSample = (*ShadowMapData)(LowResScatterX, LowResScatterY);
												// If LowResScatterSample.IsMapped() is true, the distance field texel must be mapped.
												checkSlow(FinalShadowSample.bIsMapped);
												// Only write to distance field texels whose existing transition distance is further than the transition distance being scattered.
												if (NormalizedDistance * .5f < FMath::Abs(FinalShadowSample.Distance - .5f))
												{

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/TextureMapping.cpp:2463

Scope: file

Source code excerpt:

													// World space distance from center of penumbra to fully shadowed or fully lit transition
													const float PenumbraSize = HighResSample.GetOccluderDistance() * Light->LightSourceRadius / (ReceiverDistanceFromLight - HighResSample.GetOccluderDistance());
													// Normalize the penumbra size so it is a fraction of MaxTransitionDistanceWorldSpace
													FinalShadowSample.PenumbraSize = FMath::Clamp(PenumbraSize / ShadowSettings.MaxTransitionDistanceWorldSpace, 0.01f, 1.0f);
												}
											}
										}
									}
								}
							}

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/TextureMapping.cpp:2491

Scope (from outer to inner):

file
namespace    Lightmass
function     void FStaticLightingSystem::CalculateDirectSignedDistanceFieldLightingTextureMappingLightSpace

Source code excerpt:

	const FLight* Light) const
{
	const FBoxSphereBounds3f MeshInfluenceBounds(TextureMapping->Mesh->BoundingBox.ExpandBy(ShadowSettings.MaxTransitionDistanceWorldSpace));

	if (Light->AffectsBounds(MeshInfluenceBounds))
	{
		const FBoxSphereBounds3f SceneBounds = FBoxSphereBounds3f(AggregateMesh->GetBounds());
		const FDirectionalLight* DirectionalLight = Light->GetDirectionalLight();
		const FSpotLight* SpotLight = Light->GetSpotLight();

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/TextureMapping.cpp:2513

Scope (from outer to inner):

file
namespace    Lightmass
function     void FStaticLightingSystem::CalculateDirectSignedDistanceFieldLightingTextureMappingLightSpace

Source code excerpt:

			FStaticShadowDepthMap ShadowDepthMap;

			int32 ShadowMapSizeX = FMath::TruncToInt(FMath::Max(LightSpaceImportanceBounds.GetExtent().X * 2.0f * 100.0f / ShadowSettings.MaxTransitionDistanceWorldSpace, 4.0f));
			ShadowMapSizeX = ShadowMapSizeX == appTruncErrorCode ? INT_MAX : ShadowMapSizeX;
			int32 ShadowMapSizeY = FMath::TruncToInt(FMath::Max(LightSpaceImportanceBounds.GetExtent().Y * 2.0f * 100.0f / ShadowSettings.MaxTransitionDistanceWorldSpace, 4.0f));
			ShadowMapSizeY = ShadowMapSizeY == appTruncErrorCode ? INT_MAX : ShadowMapSizeY;

			uint64 ShadowDepthMapMaxSamples = 4194304;

			// Clamp the number of dominant shadow samples generated if necessary while maintaining aspect ratio
			if ((uint64)ShadowMapSizeX * (uint64)ShadowMapSizeY > (uint64)ShadowDepthMapMaxSamples)

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/TextureMapping.cpp:2581

Scope (from outer to inner):

file
namespace    Lightmass
function     void FStaticLightingSystem::CalculateDirectSignedDistanceFieldLightingTextureMappingLightSpace

Source code excerpt:

			LIGHTINGSTAT(FScopedRDTSCTimer SearchTimer(MappingContext.Stats.SignedDistanceFieldSearchThreadTime));
			FSignedDistanceFieldShadowMapData2D* ShadowMapData = new FSignedDistanceFieldShadowMapData2D(TextureMapping->CachedSizeX, TextureMapping->CachedSizeY);
			const int32 TransitionSearchTexelRadiusX = FMath::TruncToInt(ShadowMapSizeX * ShadowSettings.MaxTransitionDistanceWorldSpace / LightSpaceImportanceBounds.GetSize().X);
			const int32 TransitionSearchTexelRadiusY = FMath::TruncToInt(ShadowMapSizeY * ShadowSettings.MaxTransitionDistanceWorldSpace / LightSpaceImportanceBounds.GetSize().Y);
			const float BoundsCellSizeX = (LightSpaceImportanceBounds.Max.X - LightSpaceImportanceBounds.Min.X) / ShadowMapSizeX;
			const float BoundsCellSizeY = (LightSpaceImportanceBounds.Max.Y - LightSpaceImportanceBounds.Min.Y) / ShadowMapSizeY;
			const float DepthBias = FMath::Max(BoundsCellSizeX, BoundsCellSizeY);

			for (int32 Y = 0; Y < TextureMapping->CachedSizeY; Y++)
			{

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/TextureMapping.cpp:2618

Scope (from outer to inner):

file
namespace    Lightmass
function     void FStaticLightingSystem::CalculateDirectSignedDistanceFieldLightingTextureMappingLightSpace

Source code excerpt:

						const float SlopeScaledDepthBias = 4 * FMath::Max(BoundsCellSizeX * FMath::Abs(TanThetaX), BoundsCellSizeY * FMath::Abs(TanThetaY));
						const bool bTexelVisible = TexelShadowMapDepth > SurfaceDepth - SlopeScaledDepthBias - DepthBias;
						float ClosestTransition = ShadowSettings.MaxTransitionDistanceWorldSpace;
						//float ClosestTransitionPenumbraSize = 1;
						float MostShadowingTransition = 1;
						float MostShadowingTransitionDistance = 1;
						float MostShadowingTransitionPenumbraSize = 1;

						for (int32 SearchY = FMath::Max(ShadowMapY - TransitionSearchTexelRadiusY, 0); SearchY < FMath::Min(ShadowMapY + TransitionSearchTexelRadiusY, ShadowMapSizeY); SearchY++)

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/TextureMapping.cpp:2641

Scope: file

Source code excerpt:

								if (bTexelVisible)
								{
									const float SearchNormalizedDistance = FMath::Clamp(SearchTransition / ShadowSettings.MaxTransitionDistanceWorldSpace, 0.0f, 1.0f);
									const float SearchEncodedDistance = bTexelVisible ? (SearchNormalizedDistance) * .5f + .5f : .5f - SearchNormalizedDistance * .5f;

									const float ReceiverDistanceFromLight = SurfaceDepth;
									const float OccluderDistanceFromLight = bTexelVisible ? SearchShadowMapDepth : TexelShadowMapDepth;
									// World space distance from center of penumbra to fully shadowed or fully lit transition
									const float SearchPenumbraSize = (ReceiverDistanceFromLight - OccluderDistanceFromLight) * Light->LightSourceRadius / OccluderDistanceFromLight;
									const float SearchEncodedPenumbraSize = FMath::Clamp(SearchPenumbraSize / ShadowSettings.MaxTransitionDistanceWorldSpace, 0.01f, 1.0f);

									const float SearchShadowing = FMath::Clamp(SearchEncodedDistance / SearchEncodedPenumbraSize - .5f / SearchEncodedPenumbraSize + .5f, 0.0f, 1.0f);

									if (bSearchTexelVisible != bTexelVisible
										/* && SearchTransition < ClosestTransition*/
										&& SearchShadowing < MostShadowingTransition)

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/TextureMapping.cpp:2669

Scope (from outer to inner):

file
function     void FStaticLightingSystem::CalculateDirectSignedDistanceFieldLightingTextureMappingLightSpace

Source code excerpt:

											// World space distance from center of penumbra to fully shadowed or fully lit transition
											const float PenumbraSize = (ReceiverDistanceFromLight - OccluderDistanceFromLight) * Light->LightSourceRadius / OccluderDistanceFromLight;
											// Normalize the penumbra size so it is a fraction of MaxTransitionDistanceWorldSpace
											ClosestTransitionPenumbraSize = FMath::Clamp(PenumbraSize / ShadowSettings.MaxTransitionDistanceWorldSpace, 0.01f, 1.0f);
										}*/
									}
								}
								else
								{
									if (bSearchTexelVisible != bTexelVisible
										&& SearchTransition < ClosestTransition)
									{
										ClosestTransition = SearchTransition;
									}
								}
							}
						}

						FSignedDistanceFieldShadowSample& FinalShadowSample = (*ShadowMapData)(X, Y);
						FinalShadowSample.bIsMapped = true;
						FinalShadowSample.Distance = MostShadowingTransitionDistance;

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/TextureMapping.cpp:2692

Scope (from outer to inner):

file
function     void FStaticLightingSystem::CalculateDirectSignedDistanceFieldLightingTextureMappingLightSpace

Source code excerpt:

						if (!bTexelVisible)
						{
							const float NormalizedDistance = FMath::Clamp(ClosestTransition / ShadowSettings.MaxTransitionDistanceWorldSpace, 0.0f, 1.0f);
							// Encode the transition distance so that [.5,0] corresponds to [0,1] for shadowed texels, and [.5,1] corresponds to [0,1] for unshadowed texels.
							// .5 of the encoded distance lies exactly on the shadow transition.
							FinalShadowSample.Distance = bTexelVisible ? (NormalizedDistance) * .5f + .5f : .5f - NormalizedDistance * .5f;

							const float ReceiverDistanceFromLight = SurfaceDepth;
							const float OccluderDistanceFromLight = TexelShadowMapDepth;
							const float PenumbraSize = (ReceiverDistanceFromLight - OccluderDistanceFromLight) * Light->LightSourceRadius / OccluderDistanceFromLight;
							FinalShadowSample.PenumbraSize = FMath::Clamp(PenumbraSize / ShadowSettings.MaxTransitionDistanceWorldSpace, 0.01f, 1.0f);
						}
						/*
						const float NormalizedDistance = FMath::Clamp(ClosestTransition / ShadowSettings.MaxTransitionDistanceWorldSpace, 0.0f, 1.0f);
						// Encode the transition distance so that [.5,0] corresponds to [0,1] for shadowed texels, and [.5,1] corresponds to [0,1] for unshadowed texels.
						// .5 of the encoded distance lies exactly on the shadow transition.
						FinalShadowSample.Distance = bTexelVisible ? (NormalizedDistance) * .5f + .5f : .5f - NormalizedDistance * .5f;

						if (bTexelVisible)
						{
							FinalShadowSample.PenumbraSize = ClosestTransitionPenumbraSize;
						}
						else
						{
							const float ReceiverDistanceFromLight = SurfaceDepth;
							const float OccluderDistanceFromLight = TexelShadowMapDepth;
							const float PenumbraSize = (ReceiverDistanceFromLight - OccluderDistanceFromLight) * Light->LightSourceRadius / OccluderDistanceFromLight;
							FinalShadowSample.PenumbraSize = FMath::Clamp(PenumbraSize / ShadowSettings.MaxTransitionDistanceWorldSpace, 0.01f, 1.0f);
						}
						*/
					}
				}
			}

			ShadowMaps.Add(Light, ShadowMapData);
		}
	}
}

/**
 * Estimate direct lighting using the direct photon map.
 * This is only useful for debugging what the final gather rays see.
 */
void FStaticLightingSystem::CalculateDirectLightingTextureMappingPhotonMap(
	FStaticLightingTextureMapping* TextureMapping, 
	FStaticLightingMappingContext& MappingContext,
	FGatheredLightMapData2D& LightMapData, 
	TMap<const FLight*, FShadowMapData2D*>& ShadowMaps,
	const FTexelToVertexMap& TexelToVertexMap, 
	bool bDebugThisMapping) const
{

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Public/SceneExport.h:472

Scope (from outer to inner):

file
namespace    Lightmass
class        class FStaticShadowSettings

Source code excerpt:

	 * Larger distances decrease precision but increase the maximum penumbra size that can be reconstructed from the distance field.
	 */
	float MaxTransitionDistanceWorldSpace;

	/** 
	 * The number of high resolution samples to calculate per MaxTransitionDistanceWorldSpace. 
	 * Higher values increase the distance field reconstruction quality, at the cost of longer build times.
	 */
	int32 ApproximateHighResTexelsPerMaxTransitionDistance;

	/** 
	 * The minimum upsample factor to calculate the high resolution samples at.  
	 * Larger values increase distance field reconstruction quality on small, high resolution meshes, at the cost of longer build times.
	 */
	int32 MinDistanceFieldUpsampleFactor;