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:
- VolumeLightSamplePlacementScale: Used to scale the SurfaceLightSampleSpacing.
- StaticLightingLevelScale: Applied to adjust the spacing based on the level’s scale.
- MaxSurfaceLightSamples: Used to potentially increase SurfaceLightSampleSpacing if too many samples would be generated.
Developers should be aware that:
- This variable directly affects the density of light samples on surfaces, impacting both lighting quality and performance.
- It’s scaled by the VolumeLightSamplePlacementScale and StaticLightingLevelScale, so changes to those variables will affect the final spacing.
- 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:
- Balancing between quality and performance: Smaller values provide more detailed lighting but increase computation time.
- Adjusting it in conjunction with related variables like FirstSurfaceSampleLayerHeight and SurfaceSampleLayerHeightSpacing for optimal results.
- Monitoring the actual number of samples generated and adjusting if necessary to stay within performance budgets.
- 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]
- INI Section:
DevOptions.PrecomputedDynamicObjectLighting
- Raw value:
300
- Is Array:
False
#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;