SurfaceLightSampleSpacing

SurfaceLightSampleSpacing

#Overview

name: SurfaceLightSampleSpacing

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 11 C++ source files.

#Summary

#Usage in the C++ source code

The purpose of SurfaceLightSampleSpacing is to control the world space distance between samples placed on upward-facing surfaces for dynamic object lighting calculations in Unreal Engine 5’s Lightmass system.

This setting variable is primarily used in the Lightmass subsystem, which is responsible for precomputed lighting in Unreal Engine. It is part of the dynamic object lighting calculations, which affect how light interacts with movable objects in the scene.

The value of this variable is set in the Lightmass configuration file (GLightmassIni) under the “DevOptions.PrecomputedDynamicObjectLighting” section. It is read during the scene export process for Lightmass calculations.

SurfaceLightSampleSpacing interacts with several other variables, including:

Developers should be aware that:

  1. This variable directly affects the density of light samples on surfaces, impacting both lighting quality and performance.
  2. It’s scaled by the VolumeLightSamplePlacementScale and StaticLightingLevelScale, so changes to those variables will affect the final spacing.
  3. If MaxSurfaceLightSamples is set and would be exceeded, SurfaceLightSampleSpacing is automatically increased to reduce the number of samples.

Best practices when using this variable include:

  1. Balancing between quality and performance: Smaller values provide more detailed lighting but increase computation time.
  2. Adjusting it in conjunction with related variables like FirstSurfaceSampleLayerHeight and SurfaceSampleLayerHeightSpacing for optimal results.
  3. Monitoring the actual number of samples generated and adjusting if necessary to stay within performance budgets.
  4. Considering the scale of your level when setting this value, as it represents a world space distance.

#Setting Variables

#References In INI files

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

#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:2256

Scope (from outer to inner):

file
function     void FLightmassExporter::WriteSceneSettings

Source code excerpt:

		Scene.DynamicObjectSettings.bVisualizeVolumeLightInterpolation = bConfigBool;
		VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("NumHemisphereSamplesScale"), Scene.DynamicObjectSettings.NumHemisphereSamplesScale, GLightmassIni));
		VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("SurfaceLightSampleSpacing"), Scene.DynamicObjectSettings.SurfaceLightSampleSpacing, GLightmassIni));
		VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("FirstSurfaceSampleLayerHeight"), Scene.DynamicObjectSettings.FirstSurfaceSampleLayerHeight, GLightmassIni));
		VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("SurfaceSampleLayerHeightSpacing"), Scene.DynamicObjectSettings.SurfaceSampleLayerHeightSpacing, GLightmassIni));
		VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("NumSurfaceSampleLayers"), Scene.DynamicObjectSettings.NumSurfaceSampleLayers, GLightmassIni));
		VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("DetailVolumeSampleSpacing"), Scene.DynamicObjectSettings.DetailVolumeSampleSpacing, GLightmassIni));
		VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("VolumeLightSampleSpacing"), Scene.DynamicObjectSettings.VolumeLightSampleSpacing, GLightmassIni));
		VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("MaxVolumeSamples"), Scene.DynamicObjectSettings.MaxVolumeSamples, GLightmassIni));

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

Scope (from outer to inner):

file
function     void FLightmassExporter::WriteSceneSettings

Source code excerpt:

		VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("MaxSurfaceLightSamples"), Scene.DynamicObjectSettings.MaxSurfaceLightSamples, GLightmassIni));

		Scene.DynamicObjectSettings.SurfaceLightSampleSpacing *= LevelSettings.VolumeLightSamplePlacementScale;
		Scene.DynamicObjectSettings.VolumeLightSampleSpacing *= LevelSettings.VolumeLightSamplePlacementScale;
		Scene.DynamicObjectSettings.DetailVolumeSampleSpacing *= LevelSettings.VolumeLightSamplePlacementScale;
	}
	{
		SetVolumetricLightmapSettings(Scene.VolumetricLightmapSettings);
	}

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

Scope (from outer to inner):

file
namespace    Lightmass
function     void FScene::ApplyStaticLightingScale

Source code excerpt:

	MeshAreaLightSettings.MeshAreaLightGeneratedDynamicLightSurfaceOffset *= SceneConstants.StaticLightingLevelScale;
	DynamicObjectSettings.FirstSurfaceSampleLayerHeight *= SceneConstants.StaticLightingLevelScale;
	DynamicObjectSettings.SurfaceLightSampleSpacing *= SceneConstants.StaticLightingLevelScale;
	DynamicObjectSettings.SurfaceSampleLayerHeightSpacing *= SceneConstants.StaticLightingLevelScale;
	DynamicObjectSettings.DetailVolumeSampleSpacing *= SceneConstants.StaticLightingLevelScale;
	DynamicObjectSettings.VolumeLightSampleSpacing *= SceneConstants.StaticLightingLevelScale;
	VolumeDistanceFieldSettings.VoxelSize *= SceneConstants.StaticLightingLevelScale;
	VolumeDistanceFieldSettings.VolumeMaxDistance *= SceneConstants.StaticLightingLevelScale;
	ShadowSettings.MaxTransitionDistanceWorldSpace *= SceneConstants.StaticLightingLevelScale;

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/SampleVolume.cpp:249

Scope (from outer to inner):

file
namespace    Lightmass
function     void FStaticLightingSystem::BeginCalculateVolumeSamples

Source code excerpt:

		if (DynamicObjectSettings.bUseMaxSurfaceSampleNum && DynamicObjectSettings.MaxSurfaceLightSamples > 100)
		{
			float SquaredSpacing = FMath::Square(DynamicObjectSettings.SurfaceLightSampleSpacing);
			if (SquaredSpacing == 0.f) SquaredSpacing = 1.0f;
			for (int32 MappingIndex = 0; MappingIndex < LandscapeMappings.Num(); MappingIndex++)
			{
				FStaticLightingVertex Vertices[3];
				int32 ElementIndex;
				const FStaticLightingMapping* CurrentMapping = LandscapeMappings[MappingIndex];

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/SampleVolume.cpp:264

Scope (from outer to inner):

file
namespace    Lightmass
function     void FStaticLightingSystem::BeginCalculateVolumeSamples

Source code excerpt:

					TriangleNormal.Z = 0.f; // approximate only for X-Y plane
					float TotalArea = 0.5f * TriangleNormal.Size3() * CurrentMesh->NumTriangles;
					LandscapeEstimateNum += TotalArea / FMath::Square(DynamicObjectSettings.SurfaceLightSampleSpacing);
				}
			}
			LandscapeEstimateNum *= DynamicObjectSettings.NumSurfaceSampleLayers;

			if (LandscapeEstimateNum > DynamicObjectSettings.MaxSurfaceLightSamples)
			{
				// Increase DynamicObjectSettings.SurfaceLightSampleSpacing to reduce light sample number
				float OldMaxSurfaceLightSamples = DynamicObjectSettings.SurfaceLightSampleSpacing;
				DynamicObjectSettings.SurfaceLightSampleSpacing = DynamicObjectSettings.SurfaceLightSampleSpacing * FMath::Sqrt((float)LandscapeEstimateNum / DynamicObjectSettings.MaxSurfaceLightSamples);
				UE_LOG(LogLightmass, Log, TEXT("Too many LightSamples : DynamicObjectSettings.SurfaceLightSampleSpacing is increased from %g to %g"), OldMaxSurfaceLightSamples, DynamicObjectSettings.SurfaceLightSampleSpacing);
				LandscapeEstimateNum = DynamicObjectSettings.MaxSurfaceLightSamples;
			}
		}

		//@todo - can this be presized more accurately?
		VolumeLightingSamples.Empty(FMath::Max<int32>(5000, LandscapeEstimateNum));

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/SampleVolume.cpp:286

Scope (from outer to inner):

file
namespace    Lightmass
function     void FStaticLightingSystem::BeginCalculateVolumeSamples

Source code excerpt:

		// Octree used for interpolating lighting for debugging
		VolumeLightingInterpolationOctree = FVolumeLightingInterpolationOctree(VolumeBounds.Origin, VolumeBounds.BoxExtent.GetMax());
		// Determine the resolution that the scene should be rasterized at based on SurfaceLightSampleSpacing and the scene's extent
		const int32 RasterSizeX = FMath::TruncToInt(2.0f * VolumeBounds.BoxExtent.X / DynamicObjectSettings.SurfaceLightSampleSpacing);
		const int32 RasterSizeY = FMath::TruncToInt(2.0f * VolumeBounds.BoxExtent.Y / DynamicObjectSettings.SurfaceLightSampleSpacing);

		// Expand the radius to touch a diagonal sample on the grid for a little overlap
		const float DiagonalRadius = DynamicObjectSettings.SurfaceLightSampleSpacing * FMath::Sqrt(2.0f);
		// Make sure the space between layers is covered
		const float SampleRadius = FMath::Max(DiagonalRadius, DynamicObjectSettings.SurfaceSampleLayerHeightSpacing * FMath::Sqrt(2.0f));

		FTriangleRasterizer<FVolumeSamplePlacementRasterPolicy> Rasterizer(
			FVolumeSamplePlacementRasterPolicy(
			RasterSizeX, 
			RasterSizeY, 
			// Use a minimum sample distance slightly less than the SurfaceLightSampleSpacing
			0.9f * FMath::Min(DynamicObjectSettings.SurfaceLightSampleSpacing, DynamicObjectSettings.SurfaceSampleLayerHeightSpacing), 
			FBoxSphereBounds3f(AggregateMesh->GetBounds()).SphereRadius,
			SampleRadius,
			*this,
			MappingContext.RayCache,
			VolumeLightingOctree));

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/SampleVolume.cpp:342

Scope (from outer to inner):

file
namespace    Lightmass
function     void FStaticLightingSystem::BeginCalculateVolumeSamples

Source code excerpt:

							// Transform world space positions from [VolumeBounds.Origin - VolumeBounds.BoxExtent, VolumeBounds.Origin + VolumeBounds.BoxExtent] into [0,1]
							const FVector4f TransformedPosition = (Vertices[VertIndex].WorldPosition - FVector4f(VolumeBounds.Origin, 0.0f) + FVector4f(VolumeBounds.BoxExtent, 0.0f)) / (2.0f * FVector4f(VolumeBounds.BoxExtent, 1.0f));
							// Project positions onto the XY plane and scale to the resolution determined by DynamicObjectSettings.SurfaceLightSampleSpacing
							XYPositions[VertIndex] = FVector2f(TransformedPosition.X * RasterSizeX, TransformedPosition.Y * RasterSizeY);
						}

						const FVector4f TriangleNormal = (Vertices[2].WorldPosition - Vertices[0].WorldPosition) ^ (Vertices[1].WorldPosition - Vertices[0].WorldPosition);
						const float TriangleArea = 0.5f * TriangleNormal.Size3();

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/SampleVolume.cpp:365

Scope (from outer to inner):

file
namespace    Lightmass
function     void FStaticLightingSystem::BeginCalculateVolumeSamples

Source code excerpt:


								const float TexelDensity = LightmapTriangleArea / TriangleArea;
								// Skip texture lightmapped triangles whose texel density is less than one texel per the area of a right triangle formed by SurfaceLightSampleSpacing.
								// If surface lighting is being calculated at a low resolution, it's unlikely that the volume near that surface needs to have detailed lighting.
								if (TexelDensity < 2.0f / FMath::Square(DynamicObjectSettings.SurfaceLightSampleSpacing))
								{
									continue;
								}
							}

							// Only rasterize upward facing triangles

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/SampleVolume.cpp:406

Scope (from outer to inner):

file
namespace    Lightmass
function     void FStaticLightingSystem::BeginCalculateVolumeSamples

Source code excerpt:

							
						// Only place a sample if there are no surface lighting samples nearby
						if (!FindNearbyVolumeSample(VolumeLightingOctree, SamplePosition, DynamicObjectSettings.SurfaceLightSampleSpacing))
						{
							const FLightRay Ray(
								SamplePosition,
								SamplePosition - FVector4f(0,0,VolumeBounds.BoxExtent.Z * 2.0f),
								nullptr,
								nullptr

#Loc: <Workspace>/Engine/Source/Programs/UnrealLightmass/Private/Lighting/SampleVolume.cpp:477

Scope (from outer to inner):

file
namespace    Lightmass
function     void FStaticLightingSystem::BeginCalculateVolumeSamples

Source code excerpt:

					if (IsPointInImportanceVolume(SamplePosition, EffectiveVolumeSpacing)
						// Only place a sample if there are no surface lighting samples nearby
						&& !FindNearbyVolumeSample(VolumeLightingOctree, SamplePosition, DynamicObjectSettings.SurfaceLightSampleSpacing))
					{
						NumUniformVolumeSamples++;
						// Add a sample and set its radius such that its influence touches a diagonal sample on the 3d grid.
						UniformVolumeSamples->Add(FVolumeLightingSample(FVector4f(SamplePosition, EffectiveVolumeSpacing * FMath::Sqrt(3.0f))));
						VolumeLightingOctree.AddElement(FVolumeSampleProximityElement(UniformVolumeSamples->Num() - 1, *UniformVolumeSamples));
						if (DynamicObjectSettings.bVisualizeVolumeLightInterpolation)

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

Scope (from outer to inner):

file
namespace    Lightmass
class        class FDynamicObjectSettings

Source code excerpt:

	float NumHemisphereSamplesScale;
	/** World space distance between samples placed on upward facing surfaces. */
	float SurfaceLightSampleSpacing;
	/** Height of the first sample layer above the surface. */
	float FirstSurfaceSampleLayerHeight;
	/** Height difference of successive layers. */
	float SurfaceSampleLayerHeightSpacing;
	/** Number of layers to place above surfaces. */
	int32 NumSurfaceSampleLayers;