// Copyright Epic Games, Inc. All Rights Reserved. #include "LightingSystem.h" #include "Exporter.h" #include "LightmassSwarm.h" #include "CPUSolver.h" #include "MonteCarlo.h" #include "Misc/ScopeLock.h" #include "UnrealLightmass.h" #include "HAL/RunnableThread.h" #include "HAL/PlatformProcess.h" #include "Misc/OutputDeviceRedirector.h" #include "HAL/ExceptionHandling.h" #if USE_LOCAL_SWARM_INTERFACE #include "IMessagingModule.h" #include "Async/TaskGraphInterfaces.h" #endif #if PLATFORM_WINDOWS #include "Windows/AllowWindowsPlatformTypes.h" #include #include "Windows/HideWindowsPlatformTypes.h" #pragma comment(lib, "psapi.lib") #endif namespace Lightmass { static void ConvertToLightSampleHelper(const FGatheredLightSample& InGatheredLightSample, float OutCoefficients[2][3]) { // SHCorrection is SHVector sampled with the normal float DirCorrection = 1.0f / FMath::Max( 0.0001f, InGatheredLightSample.SHCorrection ); float DirLuma[4]; for( int32 i = 0; i < 4; i++ ) { DirLuma[i] = 0.30f * InGatheredLightSample.SHVector.R.V[i]; DirLuma[i] += 0.59f * InGatheredLightSample.SHVector.G.V[i]; DirLuma[i] += 0.11f * InGatheredLightSample.SHVector.B.V[i]; // Lighting is already in IncidentLighting. Force directional SH as applied to a flat normal map to be 1 to get purely directional data. DirLuma[i] *= DirCorrection / PI; } // Scale directionality so that DirLuma[0] == 1. Then scale color to compensate and toss DirLuma[0]. float DirScale = 1.0f / FMath::Max( 0.0001f, DirLuma[0] ); float ColorScale = DirLuma[0]; // IncidentLighting is ground truth for a representative direction, the vertex normal OutCoefficients[0][0] = ColorScale * InGatheredLightSample.IncidentLighting.R; OutCoefficients[0][1] = ColorScale * InGatheredLightSample.IncidentLighting.G; OutCoefficients[0][2] = ColorScale * InGatheredLightSample.IncidentLighting.B; // Will force DirLuma[0] to 0.282095f OutCoefficients[1][0] = -0.325735f * DirLuma[1] * DirScale; OutCoefficients[1][1] = 0.325735f * DirLuma[2] * DirScale; OutCoefficients[1][2] = -0.325735f * DirLuma[3] * DirScale; } FLightSample FGatheredLightMapSample::ConvertToLightSample(bool bDebugThisSample) const { FLightSample NewSample; NewSample.bIsMapped = bIsMapped; ConvertToLightSampleHelper(HighQuality, &NewSample.Coefficients[0]); ConvertToLightSampleHelper(LowQuality, &NewSample.Coefficients[ LM_LQ_LIGHTMAP_COEF_INDEX ]); NewSample.SkyOcclusion[0] = HighQuality.SkyOcclusion.X; NewSample.SkyOcclusion[1] = HighQuality.SkyOcclusion.Y; NewSample.SkyOcclusion[2] = HighQuality.SkyOcclusion.Z; NewSample.AOMaterialMask = HighQuality.AOMaterialMask; return NewSample; } FLightMapData2D* FGatheredLightMapData2D::ConvertToLightmap2D(bool bDebugThisMapping, int32 PaddedDebugX, int32 PaddedDebugY) const { FLightMapData2D* ConvertedLightMap = new FLightMapData2D(SizeX, SizeY); ConvertedLightMap->Lights = Lights; ConvertedLightMap->bHasSkyShadowing = bHasSkyShadowing; for (int32 SampleIndex = 0; SampleIndex < Data.Num(); SampleIndex++) { const bool bDebugThisSample = bDebugThisMapping && SampleIndex == PaddedDebugY * SizeX + PaddedDebugX; (*ConvertedLightMap)(SampleIndex, 0) = Data[SampleIndex].ConvertToLightSample(bDebugThisSample); } return ConvertedLightMap; } FStaticLightingMappingContext::FStaticLightingMappingContext(const FStaticLightingMesh* InSubjectMesh, FStaticLightingSystem& InSystem, FDebugLightingOutput* InDebugOutput) : FirstBounceCache(InSubjectMesh ? InSubjectMesh->BoundingBox : FBox3f::BuildAABB(FVector4f(0,0,0), FVector4f(HALF_WORLD_MAX)), InSystem, 1), System(InSystem), DebugOutput(InDebugOutput) {} FStaticLightingMappingContext::~FStaticLightingMappingContext() { { // Update the main threads stats with the stats from this mapping FScopeLock Lock(&System.Stats.StatsSync); System.Stats.Cache[0] += FirstBounceCache.Stats; System.Stats += Stats; System.Stats.NumFirstHitRaysTraced += RayCache.NumFirstHitRaysTraced; System.Stats.NumBooleanRaysTraced += RayCache.NumBooleanRaysTraced; System.Stats.FirstHitRayTraceThreadTime += RayCache.FirstHitRayTraceTime; System.Stats.BooleanRayTraceThreadTime += RayCache.BooleanRayTraceTime; } for (int32 EntryIndex = 0; EntryIndex < RefinementTreeFreePool.Num(); EntryIndex++) { // Delete on the main thread to avoid a TBB inefficiency deleting many same-sized allocations on different threads delete RefinementTreeFreePool[EntryIndex]; } } /** * Initializes this static lighting system, and builds static lighting based on the provided options. * @param InOptions - The static lighting build options. * @param InScene - The scene containing all the lights and meshes * @param InExporter - The exporter used to send completed data back to UE5 * @param InNumThreads - Number of concurrent threads to use for lighting building */ FStaticLightingSystem::FStaticLightingSystem(const FLightingBuildOptions& InOptions, FScene& InScene, FLightmassSolverExporter& InExporter, int32 InNumThreads) : Options(InOptions) , GeneralSettings(InScene.GeneralSettings) , SceneConstants(InScene.SceneConstants) , MaterialSettings(InScene.MaterialSettings) , MeshAreaLightSettings(InScene.MeshAreaLightSettings) , DynamicObjectSettings(InScene.DynamicObjectSettings) , VolumetricLightmapSettings(InScene.VolumetricLightmapSettings) , PrecomputedVisibilitySettings(InScene.PrecomputedVisibilitySettings) , VolumeDistanceFieldSettings(InScene.VolumeDistanceFieldSettings) , AmbientOcclusionSettings(InScene.AmbientOcclusionSettings) , ShadowSettings(InScene.ShadowSettings) , ImportanceTracingSettings(InScene.ImportanceTracingSettings) , PhotonMappingSettings(InScene.PhotonMappingSettings) , IrradianceCachingSettings(InScene.IrradianceCachingSettings) , TasksInProgressThatWillNeedHelp(0) , NextVolumeSampleTaskIndex(-1) , NumVolumeSampleTasksOutstanding(0) , bShouldExportVolumeSampleData(false) , VolumeLightingInterpolationOctree(FVector4f(0,0,0), HALF_WORLD_MAX) , bShouldExportMeshAreaLightData(false) , bShouldExportVolumeDistanceField(false) , NumPhotonsEmittedDirect(0) , DirectPhotonMap(FVector4f(0,0,0), HALF_WORLD_MAX) , NumPhotonsEmittedFirstBounce(0) , FirstBouncePhotonMap(FVector4f(0,0,0), HALF_WORLD_MAX) , FirstBounceEscapedPhotonMap(FVector4f(0,0,0), HALF_WORLD_MAX) , FirstBouncePhotonSegmentMap(FVector4f(0,0,0), HALF_WORLD_MAX) , NumPhotonsEmittedSecondBounce(0) , SecondBouncePhotonMap(FVector4f(0,0,0), HALF_WORLD_MAX) , IrradiancePhotonMap(FVector4f(0,0,0), HALF_WORLD_MAX) , AggregateMesh(NULL) , VoxelizationSurfaceAggregateMesh(NULL) , VoxelizationVolumeAggregateMesh(NULL) , LandscapeCullingVoxelizationAggregateMesh(NULL) , Scene(InScene) , NumTexelsCompleted(0) , NumOutstandingVolumeDataLayers(0) , OutstandingVolumeDataLayerIndex(-1) , NumStaticLightingThreads(InScene.GeneralSettings.bAllowMultiThreadedStaticLighting ? FMath::Max(InNumThreads, 1) : 1) , DebugIrradiancePhotonCalculationArrayIndex(INDEX_NONE) , DebugIrradiancePhotonCalculationPhotonIndex(INDEX_NONE) , Exporter(InExporter) { const double SceneSetupStart = FPlatformTime::Seconds(); UE_LOG(LogLightmass, Log, TEXT("FStaticLightingSystem started using GKDOPMaxTrisPerLeaf: %d"), DEFAULT_MAX_TRIS_PER_LEAF); ValidateSettings(InScene); bool bDumpAllMappings = false; GroupVisibilityGridSizeXY = 0; GroupVisibilityGridSizeZ = 0; // Pre-allocate containers. int32 NumMeshes = 0; int32 NumVertices = 0; int32 NumTriangles = 0; int32 NumMappings = InScene.TextureLightingMappings.Num() + InScene.FluidMappings.Num() + InScene.LandscapeMappings.Num() + InScene.BspMappings.Num() + InScene.LandscapeVolumeMappings.Num(); int32 NumMeshInstances = InScene.BspMappings.Num() + InScene.StaticMeshInstances.Num() + InScene.VolumeMappings.Num(); AllMappings.Reserve( NumMappings ); Meshes.Reserve( NumMeshInstances ); // Initialize Meshes, Mappings, AllMappings and AggregateMesh from the scene UE_LOG(LogLightmass, Log, TEXT("Number of texture mappings: %d"), InScene.TextureLightingMappings.Num() ); for (int32 MappingIndex = 0; MappingIndex < InScene.TextureLightingMappings.Num(); MappingIndex++) { FStaticMeshStaticLightingTextureMapping* Mapping = &InScene.TextureLightingMappings[MappingIndex]; Mappings.Add(Mapping->Guid, Mapping); AllMappings.Add(Mapping); if (bDumpAllMappings) { UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(Mapping->Guid.ToString())); } } UE_LOG(LogLightmass, Log, TEXT("Number of fluid mappings: %d"), InScene.FluidMappings.Num()); for (int32 MappingIndex = 0; MappingIndex < InScene.FluidMappings.Num(); MappingIndex++) { FFluidSurfaceStaticLightingTextureMapping* Mapping = &InScene.FluidMappings[MappingIndex]; NumMeshes++; NumVertices += Mapping->Mesh->NumVertices; NumTriangles += Mapping->Mesh->NumTriangles; Mappings.Add(Mapping->Guid, Mapping); AllMappings.Add(Mapping); if (bDumpAllMappings) { UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(Mapping->Guid.ToString())); } } for (int32 MeshIndex = 0; MeshIndex < InScene.FluidMeshInstances.Num(); MeshIndex++) { Meshes.Add(&InScene.FluidMeshInstances[MeshIndex]); } UE_LOG(LogLightmass, Log, TEXT("Number of landscape mappings: %d"), InScene.LandscapeMappings.Num()); for (int32 MappingIndex = 0; MappingIndex < InScene.LandscapeMappings.Num(); MappingIndex++) { FLandscapeStaticLightingTextureMapping* Mapping = &InScene.LandscapeMappings[MappingIndex]; NumMeshes++; NumVertices += Mapping->Mesh->NumVertices; NumTriangles += Mapping->Mesh->NumTriangles; Mappings.Add(Mapping->Guid, Mapping); LandscapeMappings.Add(Mapping); AllMappings.Add(Mapping); if (bDumpAllMappings) { UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(Mapping->Guid.ToString())); } } for (int32 MeshIndex = 0; MeshIndex < InScene.LandscapeMeshInstances.Num(); MeshIndex++) { Meshes.Add(&InScene.LandscapeMeshInstances[MeshIndex]); } UE_LOG(LogLightmass, Log, TEXT("Number of BSP mappings: %d"), InScene.BspMappings.Num() ); for( int32 MeshIdx=0; MeshIdx < InScene.BspMappings.Num(); ++MeshIdx ) { FBSPSurfaceStaticLighting* BSPMapping = &InScene.BspMappings[MeshIdx]; Meshes.Add(BSPMapping); NumMeshes++; NumVertices += BSPMapping->NumVertices; NumTriangles += BSPMapping->NumTriangles; // add the BSP mappings light mapping object AllMappings.Add(&BSPMapping->Mapping); Mappings.Add(BSPMapping->Mapping.Guid, &BSPMapping->Mapping); if (bDumpAllMappings) { UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(BSPMapping->Mapping.Guid.ToString())); } } UE_LOG(LogLightmass, Log, TEXT("Number of volume mappings: %d"), InScene.VolumeMappings.Num()); for (int32 MappingIndex = 0; MappingIndex < InScene.VolumeMappings.Num(); MappingIndex++) { FStaticLightingGlobalVolumeMapping* Mapping = &InScene.VolumeMappings[MappingIndex]; Mappings.Add(Mapping->Guid, Mapping); AllMappings.Add(Mapping); if (bDumpAllMappings) { UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(Mapping->Guid.ToString())); } } UE_LOG(LogLightmass, Log, TEXT("Number of landscape volume mappings: %d"), InScene.LandscapeVolumeMappings.Num()); for (int32 MappingIndex = 0; MappingIndex < InScene.LandscapeVolumeMappings.Num(); MappingIndex++) { FLandscapeStaticLightingGlobalVolumeMapping* Mapping = &InScene.LandscapeVolumeMappings[MappingIndex]; Mappings.Add(Mapping->Guid, Mapping); AllMappings.Add(Mapping); LandscapeMappings.Add(Mapping); if (bDumpAllMappings) { UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(Mapping->Guid.ToString())); } } UE_LOG(LogLightmass, Log, TEXT("Number of static mesh instance mappings: %d"), InScene.StaticMeshInstances.Num() ); for (int32 MeshIndex = 0; MeshIndex < InScene.StaticMeshInstances.Num(); MeshIndex++) { FStaticMeshStaticLightingMesh* MeshInstance = &InScene.StaticMeshInstances[MeshIndex]; FStaticLightingMapping** MappingPtr = Mappings.Find(MeshInstance->Guid); if (MappingPtr != NULL) { MeshInstance->Mapping = *MappingPtr; } else { MeshInstance->Mapping = NULL; } Meshes.Add(MeshInstance); NumMeshes++; NumVertices += MeshInstance->NumVertices; NumTriangles += MeshInstance->NumTriangles; } check(Meshes.Num() == AllMappings.Num()); int32 MaxVisibilityId = -1; for (int32 MeshIndex = 0; MeshIndex < Meshes.Num(); MeshIndex++) { const FStaticLightingMesh* Mesh = Meshes[MeshIndex]; for (int32 VisIndex = 0; VisIndex < Mesh->VisibilityIds.Num(); VisIndex++) { MaxVisibilityId = FMath::Max(MaxVisibilityId, Mesh->VisibilityIds[VisIndex]); } } VisibilityMeshes.Empty(MaxVisibilityId + 1); VisibilityMeshes.AddZeroed(MaxVisibilityId + 1); for (int32 MeshIndex = 0; MeshIndex < Meshes.Num(); MeshIndex++) { FStaticLightingMesh* Mesh = Meshes[MeshIndex]; for (int32 VisIndex = 0; VisIndex < Mesh->VisibilityIds.Num(); VisIndex++) { const int32 VisibilityId = Mesh->VisibilityIds[VisIndex]; if (VisibilityId >= 0) { VisibilityMeshes[VisibilityId].Meshes.AddUnique(Mesh); } } } for (int32 VisibilityMeshIndex = 0; VisibilityMeshIndex < VisibilityMeshes.Num(); VisibilityMeshIndex++) { checkSlow(VisibilityMeshes[VisibilityMeshIndex].Meshes.Num() > 0); } { FScopedRDTSCTimer MeshSetupTimer(Stats.MeshAreaLightSetupTime); for (int32 MeshIndex = 0; MeshIndex < Meshes.Num(); MeshIndex++) { const int32 BckNumMeshAreaLights = MeshAreaLights.Num(); // Create mesh area lights from each mesh Meshes[MeshIndex]->CreateMeshAreaLights(*this, Scene, MeshAreaLights); if (MeshAreaLights.Num() > BckNumMeshAreaLights) { Stats.NumMeshAreaLightMeshes++; } Meshes[MeshIndex]->SetDebugMaterial(MaterialSettings.bUseDebugMaterial, MaterialSettings.DebugDiffuse); } } for (int32 MeshIndex = 0; MeshIndex < Meshes.Num(); MeshIndex++) { for (int32 LightIndex = 0; LightIndex < MeshAreaLights.Num(); LightIndex++) { // Register the newly created mesh area lights with every relevant mesh so they are used for lighting if (MeshAreaLights[LightIndex].AffectsBounds(FBoxSphereBounds3f(Meshes[MeshIndex]->BoundingBox))) { Meshes[MeshIndex]->RelevantLights.Add(&MeshAreaLights[LightIndex]); } } } #if USE_EMBREE if (Scene.EmbreeDevice) { if (Scene.bVerifyEmbree) { AggregateMesh = new FEmbreeVerifyAggregateMesh(Scene); } else { AggregateMesh = new FEmbreeAggregateMesh(Scene); } } else #endif { AggregateMesh = new FDefaultAggregateMesh(Scene); } // Add all meshes to the kDOP. AggregateMesh->ReserveMemory(NumMeshes, NumVertices, NumTriangles); if (Scene.GeneralSettings.bUseFastVoxelization) { VoxelizationSurfaceAggregateMesh = new FDefaultAggregateMesh(Scene); VoxelizationVolumeAggregateMesh = new FDefaultAggregateMesh(Scene); LandscapeCullingVoxelizationAggregateMesh = new FDefaultAggregateMesh(Scene); } for (int32 MappingIndex = 0; MappingIndex < InScene.FluidMappings.Num(); MappingIndex++) { FFluidSurfaceStaticLightingTextureMapping* Mapping = &InScene.FluidMappings[MappingIndex]; AggregateMesh->AddMesh(Mapping->Mesh, Mapping); if (Scene.GeneralSettings.bUseFastVoxelization) { if (Mapping->GetVolumeMapping() == nullptr) { VoxelizationSurfaceAggregateMesh->AddMeshForVoxelization(Mapping->Mesh, Mapping); } else { VoxelizationVolumeAggregateMesh->AddMeshForVoxelization(Mapping->Mesh, Mapping); } } } for (int32 MappingIndex = 0; MappingIndex < LandscapeMappings.Num(); MappingIndex++) { FStaticLightingMapping* Mapping = LandscapeMappings[MappingIndex]; AggregateMesh->AddMesh(Mapping->Mesh, Mapping); if (Scene.GeneralSettings.bUseFastVoxelization) { if (Mapping->GetLandscapeVolumeMapping() == nullptr) { VoxelizationSurfaceAggregateMesh->AddMeshForVoxelization(Mapping->Mesh, Mapping); } else { VoxelizationVolumeAggregateMesh->AddMeshForVoxelization(Mapping->Mesh, Mapping); } LandscapeCullingVoxelizationAggregateMesh->AddMeshForVoxelization(Mapping->Mesh, Mapping); } } for( int32 MeshIdx=0; MeshIdx < InScene.BspMappings.Num(); ++MeshIdx ) { FBSPSurfaceStaticLighting* BSPMapping = &InScene.BspMappings[MeshIdx]; AggregateMesh->AddMesh(BSPMapping, &BSPMapping->Mapping); if (Scene.GeneralSettings.bUseFastVoxelization) { if (BSPMapping->Mapping.GetVolumeMapping() == nullptr) { VoxelizationSurfaceAggregateMesh->AddMeshForVoxelization(BSPMapping, &BSPMapping->Mapping); } else { VoxelizationVolumeAggregateMesh->AddMeshForVoxelization(BSPMapping, &BSPMapping->Mapping); } } } for (int32 MeshIndex = 0; MeshIndex < InScene.StaticMeshInstances.Num(); MeshIndex++) { FStaticMeshStaticLightingMesh* MeshInstance = &InScene.StaticMeshInstances[MeshIndex]; AggregateMesh->AddMesh(MeshInstance, MeshInstance->Mapping); if (Scene.GeneralSettings.bUseFastVoxelization) { if (MeshInstance->GetInstanceableStaticMesh() != nullptr) { if (MeshInstance->StaticMesh->VoxelizationMesh == nullptr) { if (MeshInstance->LightingFlags & GI_INSTANCE_CASTSHADOW && MeshInstance->DoesMeshBelongToLOD0()) { MeshInstance->StaticMesh->VoxelizationMesh = new FDefaultAggregateMesh(Scene); MeshInstance->StaticMesh->VoxelizationMesh->AddMeshForVoxelization(MeshInstance, MeshInstance->Mapping, true); MeshInstance->StaticMesh->VoxelizationMesh->PrepareForRaytracing(); } } } else { // For non-instanceable static meshes (splines), add them to the aggregate scene voxelization mesh if (MeshInstance->Mapping->GetVolumeMapping() == nullptr) { VoxelizationSurfaceAggregateMesh->AddMeshForVoxelization(MeshInstance, MeshInstance->Mapping); } else { VoxelizationVolumeAggregateMesh->AddMeshForVoxelization(MeshInstance, MeshInstance->Mapping); } } } } // Comparing mappings based on cost, descending. struct FCompareProcessingCost { FORCEINLINE bool operator()( const FStaticLightingMapping& A, const FStaticLightingMapping& B ) const { return B.GetProcessingCost() < A.GetProcessingCost(); } }; // Sort mappings by processing cost, descending. Mappings.ValueSort(FCompareProcessingCost()); AllMappings.Sort(FCompareProcessingCost()); GStatistics.NumTotalMappings = Mappings.Num(); const FBoxSphereBounds3f SceneBounds = FBoxSphereBounds3f(AggregateMesh->GetBounds()); const FBoxSphereBounds3f ImportanceBounds = GetImportanceBounds(); // Never trace further than the importance or scene diameter MaxRayDistance = ImportanceBounds.SphereRadius > 0.0f ? ImportanceBounds.SphereRadius * 2.0f : SceneBounds.SphereRadius * 2.0f; Stats.NumLights = InScene.DirectionalLights.Num() + InScene.PointLights.Num() + InScene.SpotLights.Num() + InScene.RectLights.Num() + MeshAreaLights.Num(); Stats.NumMeshAreaLights = MeshAreaLights.Num(); for (int32 i = 0; i < MeshAreaLights.Num(); i++) { Stats.NumMeshAreaLightPrimitives += MeshAreaLights[i].GetNumPrimitives(); Stats.NumSimplifiedMeshAreaLightPrimitives += MeshAreaLights[i].GetNumSimplifiedPrimitives(); } // Add all light types except sky lights to the system's Lights array Lights.Reserve(Stats.NumLights); for (int32 LightIndex = 0; LightIndex < InScene.DirectionalLights.Num(); LightIndex++) { InScene.DirectionalLights[LightIndex].Initialize( SceneBounds, PhotonMappingSettings.bEmitPhotonsOutsideImportanceVolume, ImportanceBounds, Scene.PhotonMappingSettings.IndirectPhotonEmitDiskRadius, Scene.SceneConstants.LightGridSize, Scene.PhotonMappingSettings.DirectPhotonDensity, Scene.PhotonMappingSettings.DirectPhotonDensity * Scene.PhotonMappingSettings.OutsideImportanceVolumeDensityScale); Lights.Add(&InScene.DirectionalLights[LightIndex]); } // Initialize lights and add them to the solver's Lights array for (int32 LightIndex = 0; LightIndex < InScene.PointLights.Num(); LightIndex++) { InScene.PointLights[LightIndex].Initialize(Scene.PhotonMappingSettings.IndirectPhotonEmitConeAngle); Lights.Add(&InScene.PointLights[LightIndex]); } for (int32 LightIndex = 0; LightIndex < InScene.SpotLights.Num(); LightIndex++) { InScene.SpotLights[LightIndex].Initialize(Scene.PhotonMappingSettings.IndirectPhotonEmitConeAngle); Lights.Add(&InScene.SpotLights[LightIndex]); } for (int32 LightIndex = 0; LightIndex < InScene.RectLights.Num(); LightIndex++) { InScene.RectLights[LightIndex].Initialize(Scene.PhotonMappingSettings.IndirectPhotonEmitConeAngle); Lights.Add(&InScene.RectLights[LightIndex]); } const FBoxSphereBounds3f EffectiveImportanceBounds = ImportanceBounds.SphereRadius > 0.0f ? ImportanceBounds : SceneBounds; for (int32 LightIndex = 0; LightIndex < MeshAreaLights.Num(); LightIndex++) { MeshAreaLights[LightIndex].Initialize(Scene.PhotonMappingSettings.IndirectPhotonEmitConeAngle, EffectiveImportanceBounds); Lights.Add(&MeshAreaLights[LightIndex]); } for (int32 LightIndex = 0; LightIndex < InScene.SkyLights.Num(); LightIndex++) { SkyLights.Add(&InScene.SkyLights[LightIndex]); } //@todo - only count mappings being built Stats.NumMappings = AllMappings.Num(); for (int32 MappingIndex = 0; MappingIndex < AllMappings.Num(); MappingIndex++) { FStaticLightingTextureMapping* TextureMapping = AllMappings[MappingIndex]->GetTextureMapping(); if (TextureMapping && !(AllMappings[MappingIndex]->GetVolumeMapping() || AllMappings[MappingIndex]->GetLandscapeVolumeMapping())) { Stats.NumTexelsProcessed += TextureMapping->CachedSizeX * TextureMapping->CachedSizeY; } AllMappings[MappingIndex]->SceneMappingIndex = MappingIndex; AllMappings[MappingIndex]->Initialize(*this); } InitializePhotonSettings(); // Prepare the aggregate mesh for raytracing. AggregateMesh->PrepareForRaytracing(); AggregateMesh->DumpStats(); if (Scene.GeneralSettings.bUseFastVoxelization) { VoxelizationSurfaceAggregateMesh->PrepareForRaytracing(); VoxelizationVolumeAggregateMesh->PrepareForRaytracing(); LandscapeCullingVoxelizationAggregateMesh->PrepareForRaytracing(); } NumCompletedRadiosityIterationMappings.Empty(GeneralSettings.NumSkyLightingBounces); NumCompletedRadiosityIterationMappings.AddDefaulted(GeneralSettings.NumSkyLightingBounces); Stats.SceneSetupTime = FPlatformTime::Seconds() - SceneSetupStart; GStatistics.SceneSetupTime += Stats.SceneSetupTime; // spread out the work over multiple parallel threads MultithreadProcess(); } FStaticLightingSystem::~FStaticLightingSystem() { delete AggregateMesh; AggregateMesh = NULL; if (Scene.GeneralSettings.bUseFastVoxelization) { delete VoxelizationSurfaceAggregateMesh; VoxelizationSurfaceAggregateMesh = NULL; delete VoxelizationVolumeAggregateMesh; VoxelizationVolumeAggregateMesh = NULL; for (int32 MeshIndex = 0; MeshIndex < Scene.StaticMeshInstances.Num(); MeshIndex++) { const FStaticMeshStaticLightingMesh* MeshInstance = &Scene.StaticMeshInstances[MeshIndex]; if (MeshInstance->StaticMesh->VoxelizationMesh != nullptr) { delete MeshInstance->StaticMesh->VoxelizationMesh; MeshInstance->StaticMesh->VoxelizationMesh = nullptr; } } delete LandscapeCullingVoxelizationAggregateMesh; LandscapeCullingVoxelizationAggregateMesh = NULL; } } /** * Creates multiple worker threads and starts the process locally. */ void FStaticLightingSystem::MultithreadProcess() { const double StartTime = FPlatformTime::Seconds(); UE_LOG(LogLightmass, Log, TEXT("Processing...") ); GStatistics.PhotonsStart = FPlatformTime::Seconds(); CacheSamples(); if (PhotonMappingSettings.bUsePhotonMapping) { // Build photon maps EmitPhotons(); } if (ImportanceTracingSettings.bUseRadiositySolverForSkylightMultibounce) { SetupRadiosity(); RunRadiosityIterations(); } FinalizeSurfaceCache(); if (DynamicObjectSettings.bVisualizeVolumeLightInterpolation) { // Calculate volume samples now if they will be needed by the lighting threads for shading, // Otherwise the volume samples will be calculated when the task is received from swarm. BeginCalculateVolumeSamples(); } SetupPrecomputedVisibility(); // Before we spawn the static lighting threads, prefetch tasks they'll be working on GSwarm->PrefetchTasks(); GStatistics.PhotonsEnd = GStatistics.WorkTimeStart = FPlatformTime::Seconds(); const double SequentialThreadedProcessingStart = FPlatformTime::Seconds(); // Spawn the static lighting threads. for(int32 ThreadIndex = 0;ThreadIndex < NumStaticLightingThreads;ThreadIndex++) { FMappingProcessingThreadRunnable* ThreadRunnable = new FMappingProcessingThreadRunnable(this, ThreadIndex, StaticLightingTask_ProcessMappings); Threads.Add(ThreadRunnable); const FString ThreadName = FString::Printf(TEXT("MappingProcessingThread%u"), ThreadIndex); ThreadRunnable->Thread = FRunnableThread::Create(ThreadRunnable, *ThreadName); } GStatistics.NumThreads = NumStaticLightingThreads + 1; // Includes the main-thread who is only exporting. // Stop the static lighting threads. double MaxThreadTime = GStatistics.ThreadStatistics.TotalTime; float MaxThreadBusyTime = 0; int32 NumStaticLightingThreadsDone = 0; while ( NumStaticLightingThreadsDone < NumStaticLightingThreads ) { for (int32 ThreadIndex = 0; ThreadIndex < Threads.Num(); ThreadIndex++ ) { if ( Threads[ThreadIndex].Thread != NULL ) { // Check to see if the thread has exited with an error if ( Threads[ThreadIndex].CheckHealth( true ) ) { // Wait for the thread to exit if (Threads[ThreadIndex].IsComplete()) { Threads[ThreadIndex].Thread->WaitForCompletion(); // Accumulate all thread statistics GStatistics.ThreadStatistics += Threads[ThreadIndex].ThreadStatistics; MaxThreadTime = FMath::Max(MaxThreadTime, Threads[ThreadIndex].ThreadStatistics.TotalTime); if ( GReportDetailedStats ) { UE_LOG(LogLightmass, Log, TEXT("Thread %d finished: %s"), ThreadIndex, *FPlatformTime::PrettyTime(Threads[ThreadIndex].ThreadStatistics.TotalTime) ); } MaxThreadBusyTime = FMath::Max(MaxThreadBusyTime, Threads[ThreadIndex].ExecutionTime - Threads[ThreadIndex].IdleTime); Stats.TotalLightingThreadTime += Threads[ThreadIndex].ExecutionTime - Threads[ThreadIndex].IdleTime; // We're done with the thread object, destroy it delete Threads[ThreadIndex].Thread; Threads[ThreadIndex].Thread = NULL; NumStaticLightingThreadsDone++; } else { FPlatformProcess::Sleep(0.01f); } } } } // Try to do some mappings while we're waiting for threads to finish if ( NumStaticLightingThreadsDone < NumStaticLightingThreads ) { CompleteTextureMappingList.ApplyAndClear( *this ); ExportNonMappingTasks(); } #if USE_LOCAL_SWARM_INTERFACE FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); #endif GLog->FlushThreadedLogs(); } Threads.Empty(); GStatistics.WorkTimeEnd = FPlatformTime::Seconds(); // Threads will idle when they have no more tasks but before the user accepts the async build changes, so we have to make sure we only count busy time Stats.MainThreadLightingTime = (SequentialThreadedProcessingStart - StartTime) + MaxThreadBusyTime; GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_ExportingResults, -1 ) ); // Apply any outstanding completed mappings. CompleteTextureMappingList.ApplyAndClear( *this ); ExportNonMappingTasks(); // Adjust worktime to represent the slowest thread, since that's when all threads were finished. // This makes it easier to see how well the actual thread processing is parallelized. double Adjustment = (GStatistics.WorkTimeEnd - GStatistics.WorkTimeStart) - MaxThreadTime; if ( Adjustment > 0.0 ) { GStatistics.WorkTimeEnd -= Adjustment; } GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Finished, -1 ) ); // Let's say the main thread used up the whole parallel time. GStatistics.ThreadStatistics.TotalTime += MaxThreadTime; const float FinishAndExportTime = FPlatformTime::Seconds() - GStatistics.WorkTimeEnd; DumpStats(Stats.SceneSetupTime + Stats.MainThreadLightingTime + FinishAndExportTime); AggregateMesh->DumpCheckStats(); } /** Exports tasks that are not mappings, if they are ready. */ void FStaticLightingSystem::ExportNonMappingTasks() { // Export volume lighting samples to Swarm if they are complete if (bShouldExportVolumeSampleData) { bShouldExportVolumeSampleData = false; Exporter.ExportVolumeLightingSamples( DynamicObjectSettings.bVisualizeVolumeLightSamples, VolumeLightingDebugOutput, VolumeBounds.Origin, VolumeBounds.BoxExtent, VolumeLightingSamples); // Release volume lighting samples unless they are being used by the lighting threads for shading if (!DynamicObjectSettings.bVisualizeVolumeLightInterpolation) { VolumeLightingSamples.Empty(); } // Tell Swarm the task is complete (if we're not in debugging mode). if ( !IsDebugMode() ) { FLightmassSwarm* Swarm = GetExporter().GetSwarm(); Swarm->TaskCompleted( PrecomputedVolumeLightingGuid ); } } CompleteVisibilityTaskList.ApplyAndClear(*this); CompleteVolumetricLightmapTaskList.ApplyAndClear(*this); { TMap CompletedStaticShadowDepthMapsCopy; { // Enter a critical section before modifying DominantSpotLightShadowInfos since the worker threads may also modify it at any time FScopeLock Lock(&CompletedStaticShadowDepthMapsSync); CompletedStaticShadowDepthMapsCopy = CompletedStaticShadowDepthMaps; CompletedStaticShadowDepthMaps.Empty(); } for (TMap::TIterator It(CompletedStaticShadowDepthMapsCopy); It; ++It) { const FLight* Light = It.Key(); Exporter.ExportStaticShadowDepthMap(Light->Guid, *It.Value()); // Tell Swarm the task is complete (if we're not in debugging mode). if (!IsDebugMode()) { FLightmassSwarm* Swarm = GetExporter().GetSwarm(); Swarm->TaskCompleted(Light->Guid); } delete It.Value(); } } if (bShouldExportMeshAreaLightData) { Exporter.ExportMeshAreaLightData(MeshAreaLights, MeshAreaLightSettings.MeshAreaLightGeneratedDynamicLightSurfaceOffset); // Tell Swarm the task is complete (if we're not in debugging mode). if ( !IsDebugMode() ) { FLightmassSwarm* Swarm = GetExporter().GetSwarm(); Swarm->TaskCompleted( MeshAreaLightDataGuid ); } bShouldExportMeshAreaLightData = 0; } if (bShouldExportVolumeDistanceField) { Exporter.ExportVolumeDistanceField(VolumeSizeX, VolumeSizeY, VolumeSizeZ, VolumeDistanceFieldSettings.VolumeMaxDistance, DistanceFieldVolumeBounds, VolumeDistanceField); // Tell Swarm the task is complete (if we're not in debugging mode). if ( !IsDebugMode() ) { FLightmassSwarm* Swarm = GetExporter().GetSwarm(); Swarm->TaskCompleted( VolumeDistanceFieldGuid ); } bShouldExportVolumeDistanceField = 0; } } int32 FStaticLightingSystem::GetNumShadowRays(int32 BounceNumber, bool bPenumbra) const { int32 NumShadowRaysResult = 0; if (BounceNumber == 0 && bPenumbra) { NumShadowRaysResult = ShadowSettings.NumPenumbraShadowRays; } else if (BounceNumber == 0 && !bPenumbra) { NumShadowRaysResult = ShadowSettings.NumShadowRays; } else if (BounceNumber > 0) { // Use less rays for each progressive bounce, since the variance will matter less. NumShadowRaysResult = FMath::Max(ShadowSettings.NumBounceShadowRays / BounceNumber, 1); } return NumShadowRaysResult; } int32 FStaticLightingSystem::GetNumUniformHemisphereSamples(int32 BounceNumber) const { int32 NumSamplesResult = CachedHemisphereSamples.Num(); checkSlow(BounceNumber > 0); return NumSamplesResult; } int32 FStaticLightingSystem::GetNumPhotonImportanceHemisphereSamples() const { return PhotonMappingSettings.bUsePhotonMapping ? FMath::TruncToInt(ImportanceTracingSettings.NumHemisphereSamples * PhotonMappingSettings.FinalGatherImportanceSampleFraction) : 0; } FBoxSphereBounds3f FStaticLightingSystem::GetImportanceBounds(bool bClampToScene) const { FBoxSphereBounds3f ImportanceBounds = Scene.GetImportanceBounds(); if (bClampToScene) { const FBoxSphereBounds3f SceneBounds = FBoxSphereBounds3f(AggregateMesh->GetBounds()); const float SceneToImportanceOriginSquared = (ImportanceBounds.Origin - SceneBounds.Origin).SizeSquared(); if (SceneToImportanceOriginSquared > FMath::Square(SceneBounds.SphereRadius)) { // Disable the importance bounds if the center of the importance volume is outside of the scene. ImportanceBounds.SphereRadius = 0.0f; } else if (SceneToImportanceOriginSquared > FMath::Square(SceneBounds.SphereRadius - ImportanceBounds.SphereRadius)) { // Clamp the importance volume's radius so that all parts of it are inside the scene. ImportanceBounds.SphereRadius = SceneBounds.SphereRadius - FMath::Sqrt(SceneToImportanceOriginSquared); } else if (SceneBounds.SphereRadius <= ImportanceBounds.SphereRadius) { // Disable the importance volume if it is larger than the scene. ImportanceBounds.SphereRadius = 0.0f; } } return ImportanceBounds; } /** Returns true if the specified position is inside any of the importance volumes. */ bool FStaticLightingSystem::IsPointInImportanceVolume(const FVector4f& Position, float Tolerance) const { if (Scene.ImportanceVolumes.Num() > 0) { return Scene.IsPointInImportanceVolume(Position, Tolerance); } else { return true; } } /** Changes the scene's settings if necessary so that only valid combinations are used */ void FStaticLightingSystem::ValidateSettings(FScene& InScene) { //@todo - verify valid ranges of all settings InScene.GeneralSettings.NumIndirectLightingBounces = FMath::Max(InScene.GeneralSettings.NumIndirectLightingBounces, 0); InScene.GeneralSettings.IndirectLightingSmoothness = FMath::Clamp(InScene.GeneralSettings.IndirectLightingSmoothness, .25f, 10.0f); InScene.GeneralSettings.IndirectLightingQuality = FMath::Clamp(InScene.GeneralSettings.IndirectLightingQuality, .1f, 100.0f); InScene.GeneralSettings.ViewSingleBounceNumber = FMath::Min(InScene.GeneralSettings.ViewSingleBounceNumber, InScene.GeneralSettings.NumIndirectLightingBounces); if (FMath::IsNearlyEqual(InScene.PhotonMappingSettings.IndirectPhotonDensity, 0.0f)) { // Allocate all samples toward uniform sampling if there are no indirect photons InScene.PhotonMappingSettings.FinalGatherImportanceSampleFraction = 0; } #if LIGHTMASS_DO_PROCESSING if (!InScene.PhotonMappingSettings.bUseIrradiancePhotons) #endif { InScene.PhotonMappingSettings.bCacheIrradiancePhotonsOnSurfaces = false; } InScene.PhotonMappingSettings.FinalGatherImportanceSampleFraction = FMath::Clamp(InScene.PhotonMappingSettings.FinalGatherImportanceSampleFraction, 0.0f, 1.0f); if (InScene.ImportanceTracingSettings.NumHemisphereSamples * (1.0f - InScene.PhotonMappingSettings.FinalGatherImportanceSampleFraction) < 1) { // Irradiance caching needs some uniform samples InScene.IrradianceCachingSettings.bAllowIrradianceCaching = false; } if (InScene.PhotonMappingSettings.bUsePhotonMapping && !InScene.PhotonMappingSettings.bUseFinalGathering) { // Irradiance caching currently only supported with final gathering InScene.IrradianceCachingSettings.bAllowIrradianceCaching = false; } InScene.PhotonMappingSettings.ConeFilterConstant = FMath::Max(InScene.PhotonMappingSettings.ConeFilterConstant, 1.0f); if (!InScene.IrradianceCachingSettings.bAllowIrradianceCaching) { InScene.IrradianceCachingSettings.bUseIrradianceGradients = false; } if (InScene.IrradianceCachingSettings.bUseIrradianceGradients) { // Irradiance gradients require stratified sampling because the information from each sampled cell is used to calculate the gradient InScene.ImportanceTracingSettings.bUseStratifiedSampling = true; } else { InScene.IrradianceCachingSettings.bShowGradientsOnly = false; } if (InScene.DynamicObjectSettings.bVisualizeVolumeLightInterpolation) { // Disable irradiance caching if we are visualizing volume light interpolation, otherwise we will be getting a twice interpolated result. InScene.IrradianceCachingSettings.bAllowIrradianceCaching = false; } // Round up to nearest odd number ShadowSettings.MinDistanceFieldUpsampleFactor = FMath::Clamp(ShadowSettings.MinDistanceFieldUpsampleFactor - ShadowSettings.MinDistanceFieldUpsampleFactor % 2 + 1, 1, 17); ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceX = FMath::Max(ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceX, DELTA); ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceY = FMath::Max(ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceY, DELTA); InScene.IrradianceCachingSettings.InterpolationMaxAngle = FMath::Clamp(InScene.IrradianceCachingSettings.InterpolationMaxAngle, 0.0f, 90.0f); InScene.IrradianceCachingSettings.PointBehindRecordMaxAngle = FMath::Clamp(InScene.IrradianceCachingSettings.PointBehindRecordMaxAngle, 0.0f, 90.0f); InScene.IrradianceCachingSettings.DistanceSmoothFactor = FMath::Max(InScene.IrradianceCachingSettings.DistanceSmoothFactor, 1.0f); InScene.IrradianceCachingSettings.AngleSmoothFactor = FMath::Max(InScene.IrradianceCachingSettings.AngleSmoothFactor, 1.0f); InScene.IrradianceCachingSettings.SkyOcclusionSmoothnessReduction = FMath::Clamp(InScene.IrradianceCachingSettings.SkyOcclusionSmoothnessReduction, 0.1f, 1.0f); if (InScene.GeneralSettings.IndirectLightingQuality > 50) { InScene.ImportanceTracingSettings.NumAdaptiveRefinementLevels += 2; } else if (InScene.GeneralSettings.IndirectLightingQuality > 10) { InScene.ImportanceTracingSettings.NumAdaptiveRefinementLevels += 1; } InScene.ShadowSettings.NumShadowRays = FMath::TruncToInt(InScene.ShadowSettings.NumShadowRays * FMath::Sqrt(InScene.GeneralSettings.IndirectLightingQuality)); InScene.ShadowSettings.NumPenumbraShadowRays = FMath::TruncToInt(InScene.ShadowSettings.NumPenumbraShadowRays * FMath::Sqrt(InScene.GeneralSettings.IndirectLightingQuality)); InScene.ImportanceTracingSettings.NumAdaptiveRefinementLevels = FMath::Min(InScene.ImportanceTracingSettings.NumAdaptiveRefinementLevels, MaxNumRefiningDepths); } /** Logs solver stats */ void FStaticLightingSystem::DumpStats(float TotalStaticLightingTime) const { FString SolverStats = TEXT("\n\n"); SolverStats += FString::Printf(TEXT("Total Static Lighting time: %7.2f seconds, %i threads\n"), TotalStaticLightingTime, NumStaticLightingThreads ); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Scene setup\n"), 100.0f * Stats.SceneSetupTime / TotalStaticLightingTime, Stats.SceneSetupTime); if (Stats.NumMeshAreaLights > 0) { SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Mesh Area Light setup\n"), 100.0f * Stats.MeshAreaLightSetupTime / TotalStaticLightingTime, Stats.MeshAreaLightSetupTime); } if (PhotonMappingSettings.bUsePhotonMapping) { SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Emit Direct Photons\n"), 100.0f * Stats.EmitDirectPhotonsTime / TotalStaticLightingTime, Stats.EmitDirectPhotonsTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Cache Indirect Photon Paths\n"), 100.0f * Stats.CachingIndirectPhotonPathsTime / TotalStaticLightingTime, Stats.CachingIndirectPhotonPathsTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Emit Indirect Photons\n"), 100.0f * Stats.EmitIndirectPhotonsTime / TotalStaticLightingTime, Stats.EmitIndirectPhotonsTime); if (PhotonMappingSettings.bUseIrradiancePhotons) { SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Mark %.3f million Irradiance Photons\n"), 100.0f * Stats.IrradiancePhotonMarkingTime / TotalStaticLightingTime, Stats.IrradiancePhotonMarkingTime, Stats.NumIrradiancePhotons / 1000000.0f); if (PhotonMappingSettings.bCacheIrradiancePhotonsOnSurfaces) { SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Cache %.3f million Irradiance Photon Samples on surfaces\n"), 100.0f * Stats.CacheIrradiancePhotonsTime / TotalStaticLightingTime, Stats.CacheIrradiancePhotonsTime, Stats.NumCachedIrradianceSamples / 1000000.0f); } SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Calculate %.3f million Irradiance Photons\n"), 100.0f * Stats.IrradiancePhotonCalculatingTime / TotalStaticLightingTime, Stats.IrradiancePhotonCalculatingTime, Stats.NumFoundIrradiancePhotons / 1000000.0f); } } if (Stats.PrecomputedVisibilitySetupTime / TotalStaticLightingTime > .02f) { SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs sPVS setup\n"), 100.0f * Stats.PrecomputedVisibilitySetupTime / TotalStaticLightingTime, Stats.PrecomputedVisibilitySetupTime); } SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Lighting\n"), 100.0f * Stats.MainThreadLightingTime / TotalStaticLightingTime, Stats.MainThreadLightingTime); const float UnaccountedMainThreadTime = FMath::Max(TotalStaticLightingTime - (Stats.SceneSetupTime + Stats.EmitDirectPhotonsTime + Stats.CachingIndirectPhotonPathsTime + Stats.EmitIndirectPhotonsTime + Stats.IrradiancePhotonMarkingTime + Stats.CacheIrradiancePhotonsTime + Stats.IrradiancePhotonCalculatingTime + Stats.MainThreadLightingTime), 0.0f); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedMainThreadTime / TotalStaticLightingTime, UnaccountedMainThreadTime); // Send the message in multiple parts since it cuts off in the middle otherwise LogSolverMessage(SolverStats); SolverStats = TEXT(""); if (PhotonMappingSettings.bUsePhotonMapping) { if (Stats.EmitDirectPhotonsTime / TotalStaticLightingTime > .02) { SolverStats += FString::Printf( TEXT("Total Direct Photon Emitting thread seconds: %.1f\n"), Stats.EmitDirectPhotonsThreadTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Sampling Lights\n"), 100.0f * Stats.DirectPhotonsLightSamplingThreadTime / Stats.EmitDirectPhotonsThreadTime, Stats.DirectPhotonsLightSamplingThreadTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Custom attenuation\n"), 100.0f * Stats.DirectCustomAttenuationThreadTime / Stats.EmitDirectPhotonsThreadTime, Stats.DirectCustomAttenuationThreadTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Tracing\n"), 100.0f * Stats.DirectPhotonsTracingThreadTime / Stats.EmitDirectPhotonsThreadTime, Stats.DirectPhotonsTracingThreadTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Processing results\n"), 100.0f * Stats.ProcessDirectPhotonsThreadTime / Stats.EmitDirectPhotonsThreadTime, Stats.ProcessDirectPhotonsThreadTime); const float UnaccountedDirectPhotonThreadTime = FMath::Max(Stats.EmitDirectPhotonsThreadTime - (Stats.ProcessDirectPhotonsThreadTime + Stats.DirectPhotonsLightSamplingThreadTime + Stats.DirectPhotonsTracingThreadTime + Stats.DirectCustomAttenuationThreadTime), 0.0f); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedDirectPhotonThreadTime / Stats.EmitDirectPhotonsThreadTime, UnaccountedDirectPhotonThreadTime); } if (Stats.EmitIndirectPhotonsTime / TotalStaticLightingTime > .02) { SolverStats += FString::Printf( TEXT("\n") ); SolverStats += FString::Printf( TEXT("Total Indirect Photon Emitting thread seconds: %.1f\n"), Stats.EmitIndirectPhotonsThreadTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Sampling Lights\n"), 100.0f * Stats.LightSamplingThreadTime / Stats.EmitIndirectPhotonsThreadTime, Stats.LightSamplingThreadTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Intersect Light rays\n"), 100.0f * Stats.IntersectLightRayThreadTime / Stats.EmitIndirectPhotonsThreadTime, Stats.IntersectLightRayThreadTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs PhotonBounceTracing\n"), 100.0f * Stats.PhotonBounceTracingThreadTime / Stats.EmitIndirectPhotonsThreadTime, Stats.PhotonBounceTracingThreadTime); SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Custom attenuation\n"), 100.0f * Stats.IndirectCustomAttenuationThreadTime / Stats.EmitIndirectPhotonsThreadTime, Stats.IndirectCustomAttenuationThreadTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Processing results\n"), 100.0f * Stats.ProcessIndirectPhotonsThreadTime / Stats.EmitIndirectPhotonsThreadTime, Stats.ProcessIndirectPhotonsThreadTime); const float UnaccountedIndirectPhotonThreadTime = FMath::Max(Stats.EmitIndirectPhotonsThreadTime - (Stats.ProcessIndirectPhotonsThreadTime + Stats.LightSamplingThreadTime + Stats.IntersectLightRayThreadTime + Stats.PhotonBounceTracingThreadTime), 0.0f); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedIndirectPhotonThreadTime / Stats.EmitIndirectPhotonsThreadTime, UnaccountedIndirectPhotonThreadTime); } if (PhotonMappingSettings.bUseIrradiancePhotons) { if (PhotonMappingSettings.bCacheIrradiancePhotonsOnSurfaces // Only log Irradiance photon caching stats if it was more than 2 percent of the total time && Stats.CacheIrradiancePhotonsTime / TotalStaticLightingTime > .02) { SolverStats += FString::Printf( TEXT("\n") ); SolverStats += FString::Printf( TEXT("Total Irradiance Photon Caching thread seconds: %.1f\n"), Stats.IrradiancePhotonCachingThreadTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Octree traversal\n"), 100.0f * Stats.IrradiancePhotonOctreeTraversalTime / Stats.IrradiancePhotonCachingThreadTime, Stats.IrradiancePhotonOctreeTraversalTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs %.3f million Visibility rays\n"), 100.0f * Stats.IrradiancePhotonSearchRayTime / Stats.IrradiancePhotonCachingThreadTime, Stats.IrradiancePhotonSearchRayTime, Stats.NumIrradiancePhotonSearchRays / 1000000.0f); const float UnaccountedIrradiancePhotonCachingThreadTime = FMath::Max(Stats.IrradiancePhotonCachingThreadTime - (Stats.IrradiancePhotonOctreeTraversalTime + Stats.IrradiancePhotonSearchRayTime), 0.0f); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedIrradiancePhotonCachingThreadTime / Stats.IrradiancePhotonCachingThreadTime, UnaccountedIrradiancePhotonCachingThreadTime); } // Only log Irradiance photon calculating stats if it was more than 2 percent of the total time if (Stats.IrradiancePhotonCalculatingTime / TotalStaticLightingTime > .02) { SolverStats += FString::Printf( TEXT("\n") ); SolverStats += FString::Printf( TEXT("Total Calculating Irradiance Photons thread seconds: %.1f\n"), Stats.IrradiancePhotonCalculatingThreadTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Pushing Octree Children\n"), 100.0f * Stats.CalculateIrradiancePhotonStats.PushingOctreeChildrenThreadTime / Stats.IrradiancePhotonCalculatingThreadTime, Stats.CalculateIrradiancePhotonStats.PushingOctreeChildrenThreadTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Processing Octree Elements\n"), 100.0f * Stats.CalculateIrradiancePhotonStats.ProcessingOctreeElementsThreadTime / Stats.IrradiancePhotonCalculatingThreadTime, Stats.CalculateIrradiancePhotonStats.ProcessingOctreeElementsThreadTime); SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Finding furthest photon\n"), 100.0f * Stats.CalculateIrradiancePhotonStats.FindingFurthestPhotonThreadTime / Stats.IrradiancePhotonCalculatingThreadTime, Stats.CalculateIrradiancePhotonStats.FindingFurthestPhotonThreadTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Calculating Irradiance\n"), 100.0f * Stats.CalculateIrradiancePhotonStats.CalculateIrradianceThreadTime / Stats.IrradiancePhotonCalculatingThreadTime, Stats.CalculateIrradiancePhotonStats.CalculateIrradianceThreadTime); const float UnaccountedCalculateIrradiancePhotonsTime = FMath::Max(Stats.IrradiancePhotonCalculatingThreadTime - (Stats.CalculateIrradiancePhotonStats.PushingOctreeChildrenThreadTime + Stats.CalculateIrradiancePhotonStats.ProcessingOctreeElementsThreadTime + Stats.CalculateIrradiancePhotonStats.CalculateIrradianceThreadTime), 0.0f); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedCalculateIrradiancePhotonsTime / Stats.IrradiancePhotonCalculatingThreadTime, UnaccountedCalculateIrradiancePhotonsTime); } } SolverStats += FString::Printf( TEXT("\n") ); SolverStats += FString::Printf( TEXT("Radiosity Setup thread seconds: %.1f, Radiosity Iteration thread seconds: %.1f\n"), Stats.RadiositySetupThreadTime, Stats.RadiosityIterationThreadTime); } // Send the message in multiple parts since it cuts off in the middle otherwise LogSolverMessage(SolverStats); SolverStats = TEXT(""); const float TotalLightingBusyThreadTime = Stats.TotalLightingThreadTime; SolverStats += FString::Printf( TEXT("\n") ); SolverStats += FString::Printf( TEXT("Total busy Lighting thread seconds: %.2f\n"), TotalLightingBusyThreadTime); const float SampleSetupTime = Stats.VertexSampleCreationTime + Stats.TexelRasterizationTime; SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Texel and vertex setup\n"), 100.0f * SampleSetupTime / TotalLightingBusyThreadTime, SampleSetupTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Direct lighting\n"), 100.0f * Stats.DirectLightingTime / TotalLightingBusyThreadTime, Stats.DirectLightingTime); SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Area shadows with %.3f million rays\n"), 100.0f * Stats.AreaShadowsThreadTime / TotalLightingBusyThreadTime, Stats.AreaShadowsThreadTime, Stats.NumDirectLightingShadowRays / 1000000.0f); if (Stats.AreaLightingThreadTime / TotalLightingBusyThreadTime > .04f) { SolverStats += FString::Printf( TEXT("%12.1f%%%8.1fs Area lighting\n"), 100.0f * Stats.AreaLightingThreadTime / TotalLightingBusyThreadTime, Stats.AreaLightingThreadTime); } if (Stats.NumSignedDistanceFieldCalculations > 0) { SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Signed distance field source sparse sampling\n"), 100.0f * Stats.SignedDistanceFieldSourceFirstPassThreadTime / TotalLightingBusyThreadTime, Stats.SignedDistanceFieldSourceFirstPassThreadTime); SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Signed distance field source refining sampling\n"), 100.0f * Stats.SignedDistanceFieldSourceSecondPassThreadTime / TotalLightingBusyThreadTime, Stats.SignedDistanceFieldSourceSecondPassThreadTime); SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Signed distance field transition searching\n"), 100.0f * Stats.SignedDistanceFieldSearchThreadTime / TotalLightingBusyThreadTime, Stats.SignedDistanceFieldSearchThreadTime); } const float UnaccountedDirectLightingTime = FMath::Max(Stats.DirectLightingTime - (Stats.AreaShadowsThreadTime + Stats.SignedDistanceFieldSourceFirstPassThreadTime + Stats.SignedDistanceFieldSourceSecondPassThreadTime + Stats.SignedDistanceFieldSearchThreadTime), 0.0f); SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedDirectLightingTime / TotalLightingBusyThreadTime, UnaccountedDirectLightingTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Block on indirect lighting cache tasks\n"), 100.0f * Stats.BlockOnIndirectLightingCacheTasksTime / TotalLightingBusyThreadTime, Stats.BlockOnIndirectLightingCacheTasksTime); if (IrradianceCachingSettings.bAllowIrradianceCaching) { SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Block on IrradianceCache Interpolation tasks\n"), 100.0f * Stats.BlockOnIndirectLightingInterpolateTasksTime / TotalLightingBusyThreadTime, Stats.BlockOnIndirectLightingInterpolateTasksTime); } if (Stats.StaticShadowDepthMapThreadTime > 0.1f) { SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Static shadow depth maps (max %.1fs)\n"), 100.0f * Stats.StaticShadowDepthMapThreadTime / TotalLightingBusyThreadTime, Stats.StaticShadowDepthMapThreadTime, Stats.MaxStaticShadowDepthMapThreadTime); } if (Stats.VolumeDistanceFieldThreadTime > 0.1f) { SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Volume distance field\n"), 100.0f * Stats.VolumeDistanceFieldThreadTime / TotalLightingBusyThreadTime, Stats.VolumeDistanceFieldThreadTime); } const float PrecomputedVisibilityThreadTime = Stats.PrecomputedVisibilityThreadTime; if (PrecomputedVisibilityThreadTime > 0.1f) { SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Precomputed Visibility\n"), 100.0f * PrecomputedVisibilityThreadTime / TotalLightingBusyThreadTime, PrecomputedVisibilityThreadTime); SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Sample generation\n"), 100.0f * Stats.PrecomputedVisibilitySampleSetupThreadTime / TotalLightingBusyThreadTime, Stats.PrecomputedVisibilitySampleSetupThreadTime); SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Uniform tracing\n"), 100.0f * Stats.PrecomputedVisibilityRayTraceThreadTime / TotalLightingBusyThreadTime, Stats.PrecomputedVisibilityRayTraceThreadTime); SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Importance sampling\n"), 100.0f * Stats.PrecomputedVisibilityImportanceSampleThreadTime / TotalLightingBusyThreadTime, Stats.PrecomputedVisibilityImportanceSampleThreadTime); } if (Stats.NumDynamicObjectSurfaceSamples + Stats.NumDynamicObjectVolumeSamples > 0) { SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Volume Sample placement\n"), 100.0f * Stats.VolumeSamplePlacementThreadTime / TotalLightingBusyThreadTime, Stats.VolumeSamplePlacementThreadTime); } if (Stats.NumVolumetricLightmapSamples > 0) { SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Volumetric Lightmap - %.3f million samples\n"), 100.0f * Stats.TotalVolumetricLightmapLightingThreadTime / TotalLightingBusyThreadTime, Stats.TotalVolumetricLightmapLightingThreadTime, Stats.NumVolumetricLightmapSamples / 1000000.0f); SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs VoxelizationTime\n"), 100.0f * Stats.VolumetricLightmapVoxelizationTime / TotalLightingBusyThreadTime, Stats.VolumetricLightmapVoxelizationTime); if (Stats.VolumetricLightmapGatherImportancePhotonsTime > 0) { SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs GatherImportancePhotons\n"), 100.0f * Stats.VolumetricLightmapGatherImportancePhotonsTime / TotalLightingBusyThreadTime, Stats.VolumetricLightmapGatherImportancePhotonsTime); } SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs DirectLightingTime\n"), 100.0f * Stats.VolumetricLightmapDirectLightingTime / TotalLightingBusyThreadTime, Stats.VolumetricLightmapDirectLightingTime); SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs FinalGatherTime\n"), 100.0f * Stats.VolumetricLightmapFinalGatherTime / TotalLightingBusyThreadTime, Stats.VolumetricLightmapFinalGatherTime); } const float UnaccountedLightingThreadTime = FMath::Max(TotalLightingBusyThreadTime - (SampleSetupTime + Stats.DirectLightingTime + Stats.BlockOnIndirectLightingCacheTasksTime + Stats.BlockOnIndirectLightingInterpolateTasksTime + Stats.IndirectLightingCacheTaskThreadTimeSeparateTask + Stats.SecondPassIrradianceCacheInterpolationTime + Stats.SecondPassIrradianceCacheInterpolationTimeSeparateTask + Stats.VolumeSamplePlacementThreadTime + Stats.StaticShadowDepthMapThreadTime + Stats.VolumeDistanceFieldThreadTime + PrecomputedVisibilityThreadTime + Stats.TotalVolumetricLightmapLightingThreadTime), 0.0f); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedLightingThreadTime / TotalLightingBusyThreadTime, UnaccountedLightingThreadTime); // Send the message in multiple parts since it cuts off in the middle otherwise LogSolverMessage(SolverStats); SolverStats = TEXT("\n"); float IndirectLightingCacheTaskThreadTime = Stats.IndirectLightingCacheTaskThreadTime + Stats.IndirectLightingCacheTaskThreadTimeSeparateTask; SolverStats += FString::Printf( TEXT("Indirect lighting cache task thread seconds: %.2f\n"), IndirectLightingCacheTaskThreadTime); // These inner loop timings rely on rdtsc to avoid the massive overhead of Query Performance Counter. // rdtsc is not dependable with multi-threading (see FRDTSCCycleTimer comments and Intel documentation) but we use it anyway because it's the only option. //@todo - rdtsc is also not dependable if the OS changes which processor the thread gets executed on. // Use SetThreadAffinityMask to prevent this case. if (PhotonMappingSettings.bUsePhotonMapping) { SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs ImportancePhotonGatherTime\n"), 100.0f * Stats.ImportancePhotonGatherTime / IndirectLightingCacheTaskThreadTime, Stats.ImportancePhotonGatherTime); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs CalculateImportanceSampleTime\n"), 100.0f * Stats.CalculateImportanceSampleTime / IndirectLightingCacheTaskThreadTime, Stats.CalculateImportanceSampleTime); } SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs FirstBounceRayTraceTime for %.3f million rays\n"), 100.0f * Stats.FirstBounceRayTraceTime / IndirectLightingCacheTaskThreadTime, Stats.FirstBounceRayTraceTime, Stats.NumFirstBounceRaysTraced / 1000000.0f); SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs CalculateExitantRadiance\n"), 100.0f * Stats.CalculateExitantRadianceTime / IndirectLightingCacheTaskThreadTime, Stats.CalculateExitantRadianceTime); SolverStats += FString::Printf( TEXT("\n") ); SolverStats += FString::Printf( TEXT("Traced %.3f million first hit visibility rays for a total of %.1f thread seconds (%.3f million per thread second)\n"), Stats.NumFirstHitRaysTraced / 1000000.0f, Stats.FirstHitRayTraceThreadTime, Stats.NumFirstHitRaysTraced / 1000000.0f / Stats.FirstHitRayTraceThreadTime); SolverStats += FString::Printf( TEXT("Traced %.3f million boolean visibility rays for a total of %.1f thread seconds (%.3f million per thread second)\n"), Stats.NumBooleanRaysTraced / 1000000.0f, Stats.BooleanRayTraceThreadTime, Stats.NumBooleanRaysTraced / 1000000.0f / Stats.BooleanRayTraceThreadTime); const FBoxSphereBounds3f SceneBounds = FBoxSphereBounds3f(AggregateMesh->GetBounds()); const FBoxSphereBounds3f ImportanceBounds = GetImportanceBounds(); SolverStats += FString::Printf( TEXT("Scene radius %.1f, Importance bounds radius %.1f\n"), SceneBounds.SphereRadius, ImportanceBounds.SphereRadius); SolverStats += FString::Printf( TEXT("%u Mappings, %.3f million Texels, %.3f million mapped texels\n"), Stats.NumMappings, Stats.NumTexelsProcessed / 1000000.0f, Stats.NumMappedTexels / 1000000.0f); // Send the message in multiple parts since it cuts off in the middle otherwise LogSolverMessage(SolverStats); SolverStats = TEXT(""); float TextureMappingThreadTime = Stats.TotalTextureMappingLightingThreadTime + Stats.SecondPassIrradianceCacheInterpolationTimeSeparateTask + Stats.IndirectLightingCacheTaskThreadTimeSeparateTask; const float UnaccountedMappingThreadTimePct = 100.0f * FMath::Max(TotalLightingBusyThreadTime - (TextureMappingThreadTime + Stats.TotalVolumeSampleLightingThreadTime + Stats.TotalVolumetricLightmapLightingThreadTime + PrecomputedVisibilityThreadTime), 0.0f) / TotalLightingBusyThreadTime; SolverStats += FString::Printf( TEXT("%.1f%% of Total Lighting thread seconds on Texture Mappings, %1.f%% on Volume Samples, %1.f%% on Volumetric Lightmap, %1.f%% on Visibility, %.1f%% Unaccounted\n"), 100.0f * TextureMappingThreadTime / TotalLightingBusyThreadTime, 100.0f * Stats.TotalVolumeSampleLightingThreadTime / TotalLightingBusyThreadTime, 100.0f * Stats.TotalVolumetricLightmapLightingThreadTime / TotalLightingBusyThreadTime, 100.0f * PrecomputedVisibilityThreadTime / TotalLightingBusyThreadTime, UnaccountedMappingThreadTimePct); SolverStats += FString::Printf( TEXT("%u Lights total, %.1f Shadow rays per light sample on average\n"), Stats.NumLights, Stats.NumDirectLightingShadowRays / (float)(Stats.NumMappedTexels + Stats.NumVertexSamples)); if (Stats.NumMeshAreaLights > 0) { SolverStats += FString::Printf( TEXT("%u Emissive meshes, %u Mesh area lights, %lld simplified mesh area light primitives, %lld original primitives\n"), Stats.NumMeshAreaLightMeshes, Stats.NumMeshAreaLights, Stats.NumSimplifiedMeshAreaLightPrimitives, Stats.NumMeshAreaLightPrimitives); } if (Stats.NumSignedDistanceFieldCalculations > 0) { SolverStats += FString::Printf( TEXT("Signed distance field shadows: %.1f average upsample factor, %.3f million sparse source rays, %.3f million refining source rays, %.3f million transition search scatters\n"), Stats.AccumulatedSignedDistanceFieldUpsampleFactors / Stats.NumSignedDistanceFieldCalculations, Stats.NumSignedDistanceFieldAdaptiveSourceRaysFirstPass / 1000000.0f, Stats.NumSignedDistanceFieldAdaptiveSourceRaysSecondPass / 1000000.0f, Stats.NumSignedDistanceFieldScatters / 1000000.0f); } const int32 TotalVolumeLightingSamples = Stats.NumDynamicObjectSurfaceSamples + Stats.NumDynamicObjectVolumeSamples; if (TotalVolumeLightingSamples > 0) { SolverStats += FString::Printf( TEXT("%u Volume lighting samples, %.1f%% placed on surfaces, %.1f%% placed in the volume, %.1f thread seconds\n"), TotalVolumeLightingSamples, 100.0f * Stats.NumDynamicObjectSurfaceSamples / (float)TotalVolumeLightingSamples, 100.0f * Stats.NumDynamicObjectVolumeSamples / (float)TotalVolumeLightingSamples, Stats.TotalVolumeSampleLightingThreadTime); } if (Stats.NumPrecomputedVisibilityQueries > 0) { SolverStats += FString::Printf( TEXT("Precomputed Visibility %u Cells (%.1f%% from camera tracks, %u processed on this agent), %u Meshes, %.3f million rays, %.1fKb data\n"), Stats.NumPrecomputedVisibilityCellsTotal, 100.0f * Stats.NumPrecomputedVisibilityCellsCamaraTracks / Stats.NumPrecomputedVisibilityCellsTotal, Stats.NumPrecomputedVisibilityCellsProcessed, Stats.NumPrecomputedVisibilityMeshes, Stats.NumPrecomputedVisibilityRayTraces / 1000000.0f, Stats.PrecomputedVisibilityDataBytes / 1024.0f); const uint64 NumQueriesVisible = Stats.NumQueriesVisibleByDistanceRatio + Stats.NumQueriesVisibleExplicitSampling + Stats.NumQueriesVisibleImportanceSampling; const uint64 TotalNumQueries = Stats.NumPrecomputedVisibilityQueries + Stats.NumPrecomputedVisibilityGroupQueries; SolverStats += FString::Printf( TEXT(" %.3f million mesh queries, %.3f million group queries, %.1f%% visible, (%.1f%% trivially visible, %.1f%% explicit sampling, %.1f%% importance sampling)\n"), Stats.NumPrecomputedVisibilityQueries / 1000000.0f, Stats.NumPrecomputedVisibilityGroupQueries / 1000000.0f, 100.0f * NumQueriesVisible / TotalNumQueries, 100.0f * Stats.NumQueriesVisibleByDistanceRatio / NumQueriesVisible, 100.0f * Stats.NumQueriesVisibleExplicitSampling / NumQueriesVisible, 100.0f * Stats.NumQueriesVisibleImportanceSampling / NumQueriesVisible); SolverStats += FString::Printf( TEXT(" %ux%ux%u group cells with %u occupied, %u meshes individually queried, %.3f million mesh queries skipped\n"), GroupVisibilityGridSizeXY, GroupVisibilityGridSizeXY, GroupVisibilityGridSizeZ, VisibilityGroups.Num(), Stats.NumPrecomputedVisibilityMeshesExcludedFromGroups, Stats.NumPrecomputedVisibilityMeshQueriesSkipped / 1000000.0f); } // Send the message in multiple parts since it cuts off in the middle otherwise LogSolverMessage(SolverStats); SolverStats = TEXT(""); if (PhotonMappingSettings.bUsePhotonMapping) { const float FirstPassEmittedPhotonEfficiency = 100.0f * FMath::Max(Stats.NumDirectPhotonsGathered, NumIndirectPhotonPaths) / Stats.NumFirstPassPhotonsEmitted; SolverStats += FString::Printf( TEXT("%.3f million first pass Photons Emitted (out of %.3f million requested) to deposit %.3f million Direct Photons and %u Indirect Photon Paths, efficiency of %.2f%%\n"), Stats.NumFirstPassPhotonsEmitted / 1000000.0f, Stats.NumFirstPassPhotonsRequested / 1000000.0f, Stats.NumDirectPhotonsGathered / 1000000.0f, NumIndirectPhotonPaths, FirstPassEmittedPhotonEfficiency); const float SecondPassEmittedPhotonEfficiency = 100.0f * Stats.NumIndirectPhotonsGathered / Stats.NumSecondPassPhotonsEmitted; SolverStats += FString::Printf( TEXT("%.3f million second pass Photons Emitted (out of %.3f million requested) to deposit %.3f million Indirect Photons, efficiency of %.2f%%\n"), Stats.NumSecondPassPhotonsEmitted / 1000000.0f, Stats.NumSecondPassPhotonsRequested / 1000000.0f, Stats.NumIndirectPhotonsGathered / 1000000.0f, SecondPassEmittedPhotonEfficiency); SolverStats += FString::Printf( TEXT("%.3f million Photon Gathers, %.3f million Irradiance Photon Gathers\n"), Stats.NumPhotonGathers / 1000000.0f, Stats.NumIrradiancePhotonMapSearches / 1000000.0f); SolverStats += FString::Printf( TEXT("%.3f million Importance Photons found, %.3f million Importance Photon PDF calculations\n"), Stats.TotalFoundImportancePhotons / 1000000.0f, Stats.NumImportancePDFCalculations / 1000000.0f); if (PhotonMappingSettings.bUseIrradiancePhotons && Stats.IrradiancePhotonCalculatingTime / TotalStaticLightingTime > .02) { SolverStats += FString::Printf( TEXT("%.3f million Irradiance Photons, %.1f%% Direct, %.1f%% Indirect, %.3f million actually found\n"), Stats.NumIrradiancePhotons / 1000000.0f, 100.0f * Stats.NumDirectIrradiancePhotons / Stats.NumIrradiancePhotons, 100.0f * (Stats.NumIrradiancePhotons - Stats.NumDirectIrradiancePhotons) / Stats.NumIrradiancePhotons, Stats.NumFoundIrradiancePhotons / 1000000.0f); const float IterationsPerSearch = Stats.CalculateIrradiancePhotonStats.NumSearchIterations / (float)Stats.CalculateIrradiancePhotonStats.NumIterativePhotonMapSearches; if (Stats.CalculateIrradiancePhotonStats.NumIterativePhotonMapSearches > 0) { SolverStats += FString::Printf( TEXT("%.1f Irradiance calculating search iterations per search (%.3f million searches, %.3f million iterations)\n"), IterationsPerSearch, Stats.CalculateIrradiancePhotonStats.NumIterativePhotonMapSearches / 1000000.0f, Stats.CalculateIrradiancePhotonStats.NumSearchIterations / 1000000.0f); } SolverStats += FString::Printf( TEXT("%.3f million octree nodes tested during irradiance photon calculating, %.3f million nodes visited, %.3f million elements tested, %.3f million elements accepted\n"), Stats.CalculateIrradiancePhotonStats.NumOctreeNodesTested / 1000000.0f, Stats.CalculateIrradiancePhotonStats.NumOctreeNodesVisited / 1000000.0f, Stats.CalculateIrradiancePhotonStats.NumElementsTested / 1000000.0f, Stats.CalculateIrradiancePhotonStats.NumElementsAccepted / 1000000.0f); } } if (IrradianceCachingSettings.bAllowIrradianceCaching) { const int32 NumIrradianceCacheBounces = PhotonMappingSettings.bUsePhotonMapping ? 1 : GeneralSettings.NumIndirectLightingBounces; for (int32 BounceIndex = 0; BounceIndex < NumIrradianceCacheBounces; BounceIndex++) { const FIrradianceCacheStats& CurrentStats = Stats.Cache[BounceIndex]; if (CurrentStats.NumCacheLookups > 0) { const float MissRate = 100.0f * CurrentStats.NumRecords / CurrentStats.NumCacheLookups; SolverStats += FString::Printf( TEXT("%.1f%% Bounce %i Irradiance cache miss rate (%.3f million lookups, %.3f million misses, %.3f million inside geometry)\n"), MissRate, BounceIndex + 1, CurrentStats.NumCacheLookups / 1000000.0f, CurrentStats.NumRecords / 1000000.0f, CurrentStats.NumInsideGeometry / 1000000.0f); } } } if (PhotonMappingSettings.bUseFinalGathering) { uint64 TotalNumRefiningSamples = 0; for (int i = 0; i < ImportanceTracingSettings.NumAdaptiveRefinementLevels; i++) { TotalNumRefiningSamples += Stats.NumRefiningFinalGatherSamples[i]; } SolverStats += FString::Printf( TEXT("Final Gather: %.1fs on %.3f million base samples, %.1fs on %.3f million refining samples for %u refinement levels. \n"), Stats.BaseFinalGatherSampleTime, Stats.NumBaseFinalGatherSamples / 1000000.0f, Stats.RefiningFinalGatherSampleTime, TotalNumRefiningSamples / 1000000.0f, ImportanceTracingSettings.NumAdaptiveRefinementLevels); if (TotalNumRefiningSamples > 0) { SolverStats += FString::Printf( TEXT(" %.1f%% due to brightness differences, %.1f%% due to importance photons, %.1f%% other reasons, Samples at depth: "), 100.0f * Stats.NumRefiningSamplesDueToBrightness / TotalNumRefiningSamples, 100.0f * Stats.NumRefiningSamplesDueToImportancePhotons / TotalNumRefiningSamples, 100.0f * Stats.NumRefiningSamplesOther / TotalNumRefiningSamples); for (int i = 0; i < ImportanceTracingSettings.NumAdaptiveRefinementLevels; i++) { SolverStats += FString::Printf(TEXT("%.1f%%, "), 100.0f * Stats.NumRefiningFinalGatherSamples[i] / TotalNumRefiningSamples); } SolverStats += FString::Printf(TEXT("\n")); } } #if PLATFORM_WINDOWS PROCESS_MEMORY_COUNTERS_EX ProcessMemoryInfo; if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&ProcessMemoryInfo, sizeof(ProcessMemoryInfo))) { SolverStats += FString::Printf( TEXT("%.1f Mb Peak Working Set\n"), ProcessMemoryInfo.PeakWorkingSetSize / (1024.0f * 1024.0f)); } else { SolverStats += TEXT("GetProcessMemoryInfo Failed!"); } SolverStats += FString::Printf( TEXT("\n") ); #elif PLATFORM_MAC { struct rusage MemUsage; if (getrusage( RUSAGE_SELF, &MemUsage ) == 0) { SolverStats += FString::Printf( TEXT("%.1f Mb Peak Working Set\n"), MemUsage.ru_maxrss / (1024.0f * 1024.0f)); } else { SolverStats += TEXT("getrusage() failed!"); } } SolverStats += FString::Printf( TEXT("\n") ); #endif LogSolverMessage(SolverStats); const bool bDumpMemoryStats = false; if (bDumpMemoryStats) { #if PLATFORM_WINDOWS PROCESS_MEMORY_COUNTERS ProcessMemory; verify(GetProcessMemoryInfo(GetCurrentProcess(), &ProcessMemory, sizeof(ProcessMemory))); UE_LOG(LogLightmass, Log, TEXT("Virtual memory used %.1fMb, Peak %.1fMb"), ProcessMemory.PagefileUsage / 1048576.0f, ProcessMemory.PeakPagefileUsage / 1048576.0f); #elif PLATFORM_MAC { task_basic_info_64_data_t TaskInfo; mach_msg_type_number_t TaskInfoCount = TASK_BASIC_INFO_COUNT; task_info( mach_task_self(), TASK_BASIC_INFO, (task_info_t)&TaskInfo, &TaskInfoCount ); UE_LOG(LogLightmass, Log, TEXT("Virtual memory used %.1fMb"), TaskInfo.virtual_size / 1048576.0f); // can't get peak virtual memory on Mac } #endif AggregateMesh->DumpStats(); UE_LOG(LogLightmass, Log, TEXT("DirectPhotonMap")); DirectPhotonMap.DumpStats(false); UE_LOG(LogLightmass, Log, TEXT("FirstBouncePhotonMap")); FirstBouncePhotonMap.DumpStats(false); UE_LOG(LogLightmass, Log, TEXT("FirstBounceEscapedPhotonMap")); FirstBounceEscapedPhotonMap.DumpStats(false); UE_LOG(LogLightmass, Log, TEXT("FirstBouncePhotonSegmentMap")); FirstBouncePhotonSegmentMap.DumpStats(false); UE_LOG(LogLightmass, Log, TEXT("SecondBouncePhotonMap")); SecondBouncePhotonMap.DumpStats(false); UE_LOG(LogLightmass, Log, TEXT("IrradiancePhotonMap")); IrradiancePhotonMap.DumpStats(false); uint64 IrradiancePhotonCacheBytes = 0; for (int32 i = 0; i < AllMappings.Num(); i++) { IrradiancePhotonCacheBytes += AllMappings[i]->GetIrradiancePhotonCacheBytes(); } UE_LOG(LogLightmass, Log, TEXT("%.3fMb for Irradiance Photon surface caches"), IrradiancePhotonCacheBytes / 1048576.0f); } } /** Logs a solver message */ void FStaticLightingSystem::LogSolverMessage(const FString& Message) const { if (Scene.DebugInput.bRelaySolverStats) { // Relay the message back to Unreal if allowed GSwarm->SendMessage(NSwarm::FInfoMessage(*Message)); } GLog->Log(*Message); } /** Logs a progress update message when appropriate */ void FStaticLightingSystem::UpdateInternalStatus(int32 OldNumTexelsCompleted) const { const int32 NumProgressSteps = 10; const float InvTotal = 1.0f / Stats.NumTexelsProcessed; const float PreviousCompletedFraction = OldNumTexelsCompleted * InvTotal; const float CurrentCompletedFraction = NumTexelsCompleted * InvTotal; // Only log NumProgressSteps times if (FMath::TruncToInt(PreviousCompletedFraction * NumProgressSteps) < FMath::TruncToInt(CurrentCompletedFraction * NumProgressSteps)) { LogSolverMessage(FString::Printf(TEXT("Lighting %.1f%%"), CurrentCompletedFraction * 100.0f)); } } /** Caches samples for any sampling distributions that are known ahead of time, which greatly reduces noise in those estimates in exchange for structured artifacts. */ void FStaticLightingSystem::CacheSamples() { FLMRandomStream RandomStream(102341); int32 NumUniformHemisphereSamples = 0; if (PhotonMappingSettings.bUsePhotonMapping) { const float NumSamplesFloat = ImportanceTracingSettings.NumHemisphereSamples * GeneralSettings.IndirectLightingQuality; NumUniformHemisphereSamples = FMath::TruncToInt(NumSamplesFloat); } else { NumUniformHemisphereSamples = ImportanceTracingSettings.NumHemisphereSamples; } CachedHemisphereSamples.Empty(NumUniformHemisphereSamples); CachedHemisphereSampleUniforms.Empty(NumUniformHemisphereSamples); if (ImportanceTracingSettings.bUseStratifiedSampling) { // Split the sampling domain up into cells with equal area // Using PI times more Phi steps as Theta steps, but the relationship between them could be anything const float NumThetaStepsFloat = FMath::Sqrt(NumUniformHemisphereSamples / (float)PI); const int32 NumThetaSteps = FMath::TruncToInt(NumThetaStepsFloat); const int32 NumPhiSteps = FMath::TruncToInt(NumThetaStepsFloat * (float)PI); GenerateStratifiedUniformHemisphereSamples(NumThetaSteps, NumPhiSteps, RandomStream, CachedHemisphereSamples, CachedHemisphereSampleUniforms); } else { for (int32 SampleIndex = 0; SampleIndex < NumUniformHemisphereSamples; SampleIndex++) { const FVector4f& CurrentSample = GetUniformHemisphereVector(RandomStream, ImportanceTracingSettings.MaxHemisphereRayAngle); CachedHemisphereSamples.Add(CurrentSample); } } { FVector4f CombinedVector(0); for (int32 SampleIndex = 0; SampleIndex < CachedHemisphereSamples.Num(); SampleIndex++) { CombinedVector += CachedHemisphereSamples[SampleIndex]; } CachedSamplesMaxUnoccludedLength = (CombinedVector / CachedHemisphereSamples.Num()).Size3(); } for (int32 SampleSet = 0; SampleSet < UE_ARRAY_COUNT(CachedHemisphereSamplesForRadiosity); SampleSet++) { float SampleSetScale = FMath::Lerp(.5f, .125f, SampleSet / ((float)UE_ARRAY_COUNT(CachedHemisphereSamplesForRadiosity) - 1)); int32 TargetNumApproximateSkyLightingSamples = FMath::Max(FMath::TruncToInt(ImportanceTracingSettings.NumHemisphereSamples * SampleSetScale * GeneralSettings.IndirectLightingQuality), 12); CachedHemisphereSamplesForRadiosity[SampleSet].Empty(TargetNumApproximateSkyLightingSamples); CachedHemisphereSamplesForRadiosityUniforms[SampleSet].Empty(TargetNumApproximateSkyLightingSamples); const float NumThetaStepsFloat = FMath::Sqrt(TargetNumApproximateSkyLightingSamples / (float)PI); const int32 NumThetaSteps = FMath::TruncToInt(NumThetaStepsFloat); const int32 NumPhiSteps = FMath::TruncToInt(NumThetaStepsFloat * (float)PI); GenerateStratifiedUniformHemisphereSamples(NumThetaSteps, NumPhiSteps, RandomStream, CachedHemisphereSamplesForRadiosity[SampleSet], CachedHemisphereSamplesForRadiosityUniforms[SampleSet]); } // Cache samples on the surface of each light for area shadows for (int32 LightIndex = 0; LightIndex < Lights.Num(); LightIndex++) { FLight* Light = Lights[LightIndex]; for (int32 BounceIndex = 0; BounceIndex < GeneralSettings.NumIndirectLightingBounces + 1; BounceIndex++) { const int32 NumPenumbraTypes = BounceIndex == 0 ? 2 : 1; Light->CacheSurfaceSamples(BounceIndex, GetNumShadowRays(BounceIndex, false), GetNumShadowRays(BounceIndex, true), RandomStream); } } { const int32 NumUpperVolumeSamples = ImportanceTracingSettings.NumHemisphereSamples * DynamicObjectSettings.NumHemisphereSamplesScale; const float NumThetaStepsFloat = FMath::Sqrt(NumUpperVolumeSamples / (float)PI); const int32 NumThetaSteps = FMath::TruncToInt(NumThetaStepsFloat); const int32 NumPhiSteps = FMath::TruncToInt(NumThetaStepsFloat * (float)PI); GenerateStratifiedUniformHemisphereSamples(NumThetaSteps, NumPhiSteps, RandomStream, CachedVolumetricLightmapUniformHemisphereSamples, CachedVolumetricLightmapUniformHemisphereSampleUniforms); FVector4f CombinedVector(0); for (int32 SampleIndex = 0; SampleIndex < CachedVolumetricLightmapUniformHemisphereSamples.Num(); SampleIndex++) { CombinedVector += CachedVolumetricLightmapUniformHemisphereSamples[SampleIndex]; } CachedVolumetricLightmapMaxUnoccludedLength = (CombinedVector / CachedVolumetricLightmapUniformHemisphereSamples.Num()).Size3(); } CachedVolumetricLightmapVertexOffsets.Add(FVector3f(0, 0, 0)); } bool FStaticLightingThreadRunnable::CheckHealth(bool bReportError) const { if( bTerminatedByError && bReportError ) { UE_LOG(LogLightmass, Fatal, TEXT("Static lighting thread exception:\r\n%s"), *ErrorMessage ); } return !bTerminatedByError; } void FStaticLightingThreadRunnable::FixThreadGroupAffinity() const { #if PLATFORM_WINDOWS int NumProcessorGroups = GetActiveProcessorGroupCount(); int PrefixSum = 0; for (int GroupIndex = 0; GroupIndex < NumProcessorGroups; GroupIndex++) { int NumProcessorsThisGroup = GetActiveProcessorCount(GroupIndex); if (ThreadIndex < PrefixSum + NumProcessorsThisGroup) { GROUP_AFFINITY GroupAffinity = {}; GroupAffinity.Group = GroupIndex; GroupAffinity.Mask = (1ull << NumProcessorsThisGroup) - 1; check(SetThreadGroupAffinity(GetCurrentThread(), &GroupAffinity, NULL)); break; } PrefixSum += NumProcessorsThisGroup; } #endif } uint32 FMappingProcessingThreadRunnable::Run() { const double StartThreadTime = FPlatformTime::Seconds(); FixThreadGroupAffinity(); #if PLATFORM_WINDOWS if(!FPlatformMisc::IsDebuggerPresent()) { __try { if (TaskType == StaticLightingTask_ProcessMappings) { System->ThreadLoop(false, ThreadIndex, ThreadStatistics, IdleTime); } else if (TaskType == StaticLightingTask_CacheIrradiancePhotons) { System->CacheIrradiancePhotonsThreadLoop(ThreadIndex, false); } else if (TaskType == StaticLightingTask_RadiositySetup) { System->RadiositySetupThreadLoop(ThreadIndex, false); } else if (TaskType == StaticLightingTask_RadiosityIterations) { System->RadiosityIterationThreadLoop(ThreadIndex, false); } else if (TaskType == StaticLightingTask_FinalizeSurfaceCache) { System->FinalizeSurfaceCacheThreadLoop(ThreadIndex, false); } else { UE_LOG(LogLightmass, Fatal, TEXT("Unsupported task type")); } } __except( ReportCrash( GetExceptionInformation() ) ) { ErrorMessage = GErrorHist; bTerminatedByError = true; } } else #endif { if (TaskType == StaticLightingTask_ProcessMappings) { System->ThreadLoop(false, ThreadIndex, ThreadStatistics, IdleTime); } else if (TaskType == StaticLightingTask_CacheIrradiancePhotons) { System->CacheIrradiancePhotonsThreadLoop(ThreadIndex, false); } else if (TaskType == StaticLightingTask_RadiositySetup) { System->RadiositySetupThreadLoop(ThreadIndex, false); } else if (TaskType == StaticLightingTask_RadiosityIterations) { System->RadiosityIterationThreadLoop(ThreadIndex, false); } else if (TaskType == StaticLightingTask_FinalizeSurfaceCache) { System->FinalizeSurfaceCacheThreadLoop(ThreadIndex, false); } else { UE_LOG(LogLightmass, Fatal, TEXT("Unsupported task type")); } } ExecutionTime = FPlatformTime::Seconds() - StartThreadTime; FinishedCounter.Increment(); return 0; } /** * Retrieves the next task from Swarm. Blocking, thread-safe function call. Returns NULL when there are no more tasks. * @return The next mapping task to process. */ FStaticLightingMapping* FStaticLightingSystem::ThreadGetNextMapping( FThreadStatistics& ThreadStatistics, FGuid& TaskGuid, uint32 WaitTime, bool& bWaitTimedOut, bool& bDynamicObjectTask, int32& PrecomputedVisibilityTaskIndex, int32& VolumetricLightmapTaskIndex, bool& bStaticShadowDepthMapTask, bool& bMeshAreaLightDataTask, bool& bVolumeDataTask) { FStaticLightingMapping* Mapping = NULL; // Initialize output parameters bWaitTimedOut = true; bDynamicObjectTask = false; PrecomputedVisibilityTaskIndex = INDEX_NONE; VolumetricLightmapTaskIndex = INDEX_NONE; bStaticShadowDepthMapTask = false; bMeshAreaLightDataTask = false; bVolumeDataTask = false; if ( GDebugMode ) { FScopeLock Lock( &CriticalSection ); bWaitTimedOut = false; // If we're in debugging mode, just grab the next mapping from the scene. TMap::TIterator It(Mappings); if ( It ) { Mapping = It.Value(); It.RemoveCurrent(); } } else { // Request a new task from Swarm. FLightmassSwarm* Swarm = Exporter.GetSwarm(); double SwarmRequestStart = FPlatformTime::Seconds(); bool bGotTask = Swarm->RequestTask( TaskGuid, WaitTime ); double SwarmRequestEnd = FPlatformTime::Seconds(); if ( bGotTask ) { if (TaskGuid == PrecomputedVolumeLightingGuid) { bDynamicObjectTask = true; Swarm->AcceptTask( TaskGuid ); bWaitTimedOut = false; } else if (TaskGuid == MeshAreaLightDataGuid) { bMeshAreaLightDataTask = true; Swarm->AcceptTask( TaskGuid ); bWaitTimedOut = false; } else if (TaskGuid == VolumeDistanceFieldGuid) { bVolumeDataTask = true; Swarm->AcceptTask( TaskGuid ); bWaitTimedOut = false; } else if (Scene.FindLightByGuid(TaskGuid)) { bStaticShadowDepthMapTask = true; Swarm->AcceptTask( TaskGuid ); bWaitTimedOut = false; } else { int32 FoundVisibilityIndex = INDEX_NONE; Scene.VisibilityBucketGuids.Find(TaskGuid, FoundVisibilityIndex); if (FoundVisibilityIndex >= 0) { PrecomputedVisibilityTaskIndex = FoundVisibilityIndex; Swarm->AcceptTask(TaskGuid); bWaitTimedOut = false; } else { int32 FoundVolumetricLightmapIndex = INDEX_NONE; Scene.VolumetricLightmapTaskGuids.Find(TaskGuid, FoundVolumetricLightmapIndex); if (FoundVolumetricLightmapIndex >= 0) { VolumetricLightmapTaskIndex = FoundVolumetricLightmapIndex; Swarm->AcceptTask(TaskGuid); bWaitTimedOut = false; } else { FStaticLightingMapping** MappingPtr = Mappings.Find(TaskGuid); if (MappingPtr && FPlatformAtomics::InterlockedExchange(&(*MappingPtr)->bProcessed, true) == false) { // We received a new mapping to process. Tell Swarm we accept the task. Swarm->AcceptTask(TaskGuid); bWaitTimedOut = false; Mapping = *MappingPtr; } else { // Couldn't find the mapping. Tell Swarm we reject the task and try again later. UE_LOG(LogLightmass, Log, TEXT("Lightmass - Rejecting task (%08X%08X%08X%08X)!"), TaskGuid.A, TaskGuid.B, TaskGuid.C, TaskGuid.D); Swarm->RejectTask(TaskGuid); } } } } } else if ( Swarm->ReceivedQuitRequest() || Swarm->IsDone() ) { bWaitTimedOut = false; } ThreadStatistics.SwarmRequestTime += SwarmRequestEnd - SwarmRequestStart; } return Mapping; } void FStaticLightingSystem::ThreadLoop(bool bIsMainThread, int32 ThreadIndex, FThreadStatistics& ThreadStatistics, float& IdleTime) { const double ThreadTimeStart = FPlatformTime::Seconds(); GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Processing0, ThreadIndex ) ); bool bSignaledMappingsComplete = false; bool bIsDone = false; while (!bIsDone) { const double StartLoopTime = FPlatformTime::Seconds(); bool bAnyTaskProcessedByThisThread = false; // Process any existing local tasks before fetching new tasks from Swarm { while (FCacheIndirectTaskDescription * NextCacheTask = CacheIndirectLightingTasks.Pop()) { //UE_LOG(LogLightmass, Warning, TEXT("Thread %u picked up Cache Indirect task for %u"), ThreadIndex, NextCacheTask->TextureMapping->Guid.D); ProcessCacheIndirectLightingTask(NextCacheTask, false); NextCacheTask->TextureMapping->CompletedCacheIndirectLightingTasks.Push(NextCacheTask); FPlatformAtomics::InterlockedDecrement(&NextCacheTask->TextureMapping->NumOutstandingCacheTasks); bAnyTaskProcessedByThisThread = true; } while (FInterpolateIndirectTaskDescription * NextInterpolateTask = InterpolateIndirectLightingTasks.Pop()) { //UE_LOG(LogLightmass, Warning, TEXT("Thread %u picked up Interpolate indirect task for %u"), ThreadIndex, NextInterpolateTask->TextureMapping->Guid.D); ProcessInterpolateTask(NextInterpolateTask, false); NextInterpolateTask->TextureMapping->CompletedInterpolationTasks.Push(NextInterpolateTask); FPlatformAtomics::InterlockedDecrement(&NextInterpolateTask->TextureMapping->NumOutstandingInterpolationTasks); bAnyTaskProcessedByThisThread = true; } bAnyTaskProcessedByThisThread |= ProcessVolumetricLightmapTaskIfAvailable(); while (NumOutstandingVolumeDataLayers > 0) { const int32 ThreadZ = FPlatformAtomics::InterlockedIncrement(&OutstandingVolumeDataLayerIndex); if (ThreadZ < VolumeSizeZ) { CalculateVolumeDistanceFieldWorkRange(ThreadZ); const int32 NumTasksRemaining = FPlatformAtomics::InterlockedDecrement(&NumOutstandingVolumeDataLayers); if (NumTasksRemaining == 0) { FPlatformAtomics::InterlockedExchange(&bShouldExportVolumeDistanceField, true); } bAnyTaskProcessedByThisThread = true; } } while (NumVolumeSampleTasksOutstanding > 0) { const int32 TaskIndex = FPlatformAtomics::InterlockedIncrement(&NextVolumeSampleTaskIndex); if (TaskIndex < VolumeSampleTasks.Num()) { ProcessVolumeSamplesTask(VolumeSampleTasks[TaskIndex]); const int32 NumTasksRemaining = FPlatformAtomics::InterlockedDecrement(&NumVolumeSampleTasksOutstanding); if (NumTasksRemaining == 0) { FPlatformAtomics::InterlockedExchange(&bShouldExportVolumeSampleData, true); } bAnyTaskProcessedByThisThread = true; } } } uint32 DefaultRequestForTaskTimeout = 0; FGuid TaskGuid; bool bRequestForTaskTimedOut; bool bDynamicObjectTask; int32 PrecomputedVisibilityTaskIndex; int32 VolumetricLightmapTaskIndex; bool bStaticShadowDepthMapTask; bool bMeshAreaLightDataTask; bool bVolumeDataTask; const double RequestTimeStart = FPlatformTime::Seconds(); FStaticLightingMapping* Mapping = ThreadGetNextMapping( ThreadStatistics, TaskGuid, DefaultRequestForTaskTimeout, bRequestForTaskTimedOut, bDynamicObjectTask, PrecomputedVisibilityTaskIndex, VolumetricLightmapTaskIndex, bStaticShadowDepthMapTask, bMeshAreaLightDataTask, bVolumeDataTask); const double RequestTimeEnd = FPlatformTime::Seconds(); ThreadStatistics.RequestTime += RequestTimeEnd - RequestTimeStart; if (Mapping) { const double MappingTimeStart = FPlatformTime::Seconds(); // Build the mapping's static lighting. if(Mapping->GetTextureMapping()) { check(!Mapping->GetVolumeMapping()); check(!Mapping->GetLandscapeVolumeMapping()); ProcessTextureMapping(Mapping->GetTextureMapping()); double MappingTimeEnd = FPlatformTime::Seconds(); ThreadStatistics.TextureMappingTime += MappingTimeEnd - MappingTimeStart; ThreadStatistics.NumTextureMappings++; } } else if (bDynamicObjectTask) { BeginCalculateVolumeSamples(); // If we didn't generate any samples then we can end the task if (!IsDebugMode() && NumVolumeSampleTasksOutstanding <= 0) { FLightmassSwarm* Swarm = GetExporter().GetSwarm(); Swarm->TaskCompleted(PrecomputedVolumeLightingGuid); } } else if (PrecomputedVisibilityTaskIndex >= 0) { CalculatePrecomputedVisibility(PrecomputedVisibilityTaskIndex); } else if (VolumetricLightmapTaskIndex >= 0) { CalculateAdaptiveVolumetricLightmap(VolumetricLightmapTaskIndex); } else if (bMeshAreaLightDataTask) { FPlatformAtomics::InterlockedExchange(&bShouldExportMeshAreaLightData, true); } else if (bVolumeDataTask) { BeginCalculateVolumeDistanceField(); } else if (bStaticShadowDepthMapTask) { CalculateStaticShadowDepthMap(TaskGuid); } else { if (!bAnyTaskProcessedByThisThread && TasksInProgressThatWillNeedHelp <= 0 && NumOutstandingVolumeDataLayers <= 0 && NumVolumeSampleTasksOutstanding <= 0) { if (!bSignaledMappingsComplete) { bSignaledMappingsComplete = true; GSwarm->SendMessage(NSwarm::FTimingMessage(NSwarm::PROGSTATE_Processing0, ThreadIndex)); } if (!bRequestForTaskTimedOut) { // All mappings have been processed, so end this thread. bIsDone = true; } else { FPlatformProcess::Sleep(.001f); IdleTime += FPlatformTime::Seconds() - StartLoopTime; } } } // NOTE: Main thread shouldn't be running this anymore. check( !bIsMainThread ); } ThreadStatistics.TotalTime += FPlatformTime::Seconds() - ThreadTimeStart; FPlatformAtomics::InterlockedIncrement(&GStatistics.NumThreadsFinished); } /** * Applies the static lighting to the mappings in the list, and clears the list. * Also reports back to Unreal after each mapping has been exported. * @param LightingSystem - Reference to the static lighting system */ template void TCompleteStaticLightingList::ApplyAndClear(FStaticLightingSystem& LightingSystem) { while(FirstElement) { // Atomically read the complete list and clear the shared head pointer. TList* LocalFirstElement; TList* CurrentElement; uint32 ElementCount = 0; do { LocalFirstElement = FirstElement; } while(FPlatformAtomics::InterlockedCompareExchangePointer((void**)&FirstElement,NULL,LocalFirstElement) != LocalFirstElement); // Should never be null, but this satisfies static-analysis if (!LocalFirstElement) continue; // Traverse the local list, count the number of entries, and find the minimum guid TList* PreviousElement = NULL; TList* MinimumElementLink = NULL; TList* MinimumElement = NULL; CurrentElement = LocalFirstElement; MinimumElement = CurrentElement; FGuid MinimumGuid = MinimumElement->Element.Mapping->Guid; while(CurrentElement) { ElementCount++; if (CurrentElement->Element.Mapping->Guid < MinimumGuid) { MinimumGuid = CurrentElement->Element.Mapping->Guid; MinimumElementLink = PreviousElement; MinimumElement = CurrentElement; } PreviousElement = CurrentElement; CurrentElement = CurrentElement->Next; } // Slice and dice the list to put the minimum at the head before we continue if (MinimumElementLink != NULL) { MinimumElementLink->Next = MinimumElement->Next; MinimumElement->Next = LocalFirstElement; LocalFirstElement = MinimumElement; } // Traverse the local list and export CurrentElement = LocalFirstElement; // Start exporting, planning to put everything into one file bool bUseUniqueChannel = true; if (LightingSystem.GetExporter().BeginExportResults(CurrentElement->Element, ElementCount) >= 0) { // We opened a group channel, export all mappings out together bUseUniqueChannel = false; } const double ExportTimeStart = FPlatformTime::Seconds(); while(CurrentElement) { // write back to Unreal LightingSystem.GetExporter().ExportResults(CurrentElement->Element, bUseUniqueChannel); // Update the corresponding statistics depending on whether we're exporting in parallel to the worker threads or not. bool bIsRunningInParallel = GStatistics.NumThreadsFinished < (GStatistics.NumThreads-1); if ( bIsRunningInParallel ) { GStatistics.ThreadStatistics.ExportTime += FPlatformTime::Seconds() - ExportTimeStart; } else { static bool bFirst = true; if ( bFirst ) { bFirst = false; GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_ExportingResults, -1 ) ); } GStatistics.ExtraExportTime += FPlatformTime::Seconds() - ExportTimeStart; } GStatistics.NumExportedMappings++; // Move to the next element CurrentElement = CurrentElement->Next; } // If we didn't use unique channels, close up the group channel now if (!bUseUniqueChannel) { LightingSystem.GetExporter().EndExportResults(); } // Traverse again, cleaning up and notifying swarm FLightmassSwarm* Swarm = LightingSystem.GetExporter().GetSwarm(); CurrentElement = LocalFirstElement; while(CurrentElement) { // Tell Swarm the task is complete (if we're not in debugging mode). if ( !LightingSystem.IsDebugMode() ) { Swarm->TaskCompleted( CurrentElement->Element.Mapping->Guid ); } // Delete this link and advance to the next. TList* NextElement = CurrentElement->Next; delete CurrentElement; CurrentElement = NextElement; } } } template void TCompleteTaskList::ApplyAndClear(FStaticLightingSystem& LightingSystem) { while(this->FirstElement) { // Atomically read the complete list and clear the shared head pointer. TList* LocalFirstElement; TList* CurrentElement; uint32 ElementCount = 0; do { LocalFirstElement = this->FirstElement; } while(FPlatformAtomics::InterlockedCompareExchangePointer((void**)&this->FirstElement,NULL,LocalFirstElement) != LocalFirstElement); // Traverse the local list, count the number of entries, and find the minimum guid TList* PreviousElement = NULL; TList* MinimumElementLink = NULL; TList* MinimumElement = NULL; CurrentElement = LocalFirstElement; MinimumElement = CurrentElement; FGuid MinimumGuid = MinimumElement->Element.Guid; while(CurrentElement) { ElementCount++; if (CurrentElement->Element.Guid < MinimumGuid) { MinimumGuid = CurrentElement->Element.Guid; MinimumElementLink = PreviousElement; MinimumElement = CurrentElement; } PreviousElement = CurrentElement; CurrentElement = CurrentElement->Next; } // Slice and dice the list to put the minimum at the head before we continue if (MinimumElementLink != NULL) { MinimumElementLink->Next = MinimumElement->Next; MinimumElement->Next = LocalFirstElement; LocalFirstElement = MinimumElement; } // Traverse the local list and export CurrentElement = LocalFirstElement; const double ExportTimeStart = FPlatformTime::Seconds(); while(CurrentElement) { // write back to Unreal LightingSystem.GetExporter().ExportResults(CurrentElement->Element); // Move to the next element CurrentElement = CurrentElement->Next; } // Traverse again, cleaning up and notifying swarm FLightmassSwarm* Swarm = LightingSystem.GetExporter().GetSwarm(); CurrentElement = LocalFirstElement; while(CurrentElement) { // Tell Swarm the task is complete (if we're not in debugging mode). if ( !LightingSystem.IsDebugMode() ) { Swarm->TaskCompleted( CurrentElement->Element.Guid ); } // Delete this link and advance to the next. TList* NextElement = CurrentElement->Next; delete CurrentElement; CurrentElement = NextElement; } } } class FStoredLightingSample { public: FLinearColor IncomingRadiance; FVector4f WorldSpaceDirection; }; class FSampleCollector { public: inline void SetOcclusion(float InOcclusion) {} inline void AddIncomingRadiance(const FLinearColor& IncomingRadiance, float Weight, const FVector4f& TangentSpaceDirection, const FVector4f& WorldSpaceDirection) { if (FLinearColorUtils::LinearRGBToXYZ(IncomingRadiance * Weight).G > DELTA) { FStoredLightingSample NewSample; NewSample.IncomingRadiance = IncomingRadiance * Weight; NewSample.WorldSpaceDirection = WorldSpaceDirection; Samples.Add(NewSample); } } bool AreFloatsValid() const { return true; } FSampleCollector operator+( const FSampleCollector& Other ) const { FSampleCollector NewCollector; NewCollector.Samples = Samples; NewCollector.Samples.Append(Other.Samples); NewCollector.EnvironmentSamples = EnvironmentSamples; NewCollector.EnvironmentSamples.Append(Other.EnvironmentSamples); return NewCollector; } TArray Samples; TArray EnvironmentSamples; }; void FStaticLightingSystem::CalculateStaticShadowDepthMap(FGuid LightGuid) { const FLight* Light = Scene.FindLightByGuid(LightGuid); check(Light); const FDirectionalLight* DirectionalLight = Light->GetDirectionalLight(); const FSpotLight* SpotLight = Light->GetSpotLight(); const FPointLight* PointLight = Light->GetPointLight(); check(DirectionalLight || SpotLight || PointLight); const float ClampedResolutionScale = FMath::Clamp(Light->ShadowResolutionScale, .125f, 8.0f); const double StartTime = FPlatformTime::Seconds(); FStaticLightingMappingContext Context(NULL, *this); FStaticShadowDepthMap* ShadowDepthMap = new FStaticShadowDepthMap(); if (DirectionalLight) { FVector4f XAxis, YAxis; DirectionalLight->Direction.FindBestAxisVectors3(XAxis, YAxis); // Create a coordinate system for the dominant directional light, with the z axis corresponding to the light's direction ShadowDepthMap->WorldToLight = FBasisVectorMatrix44f(XAxis, YAxis, DirectionalLight->Direction, FVector4f(0,0,0)); FBoxSphereBounds3f ImportanceVolume = GetImportanceBounds().SphereRadius > 0.0f ? GetImportanceBounds() : FBoxSphereBounds3f(AggregateMesh->GetBounds()); const FBox3f LightSpaceImportanceBounds = ImportanceVolume.GetBox().TransformBy(ShadowDepthMap->WorldToLight); ShadowDepthMap->ShadowMapSizeX = FMath::TruncToInt(FMath::Max(LightSpaceImportanceBounds.GetExtent().X * 2.0f * ClampedResolutionScale / ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceX, 4.0f)); ShadowDepthMap->ShadowMapSizeX = ShadowDepthMap->ShadowMapSizeX == appTruncErrorCode ? INT_MAX : ShadowDepthMap->ShadowMapSizeX; ShadowDepthMap->ShadowMapSizeY = FMath::TruncToInt(FMath::Max(LightSpaceImportanceBounds.GetExtent().Y * 2.0f * ClampedResolutionScale / ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceY, 4.0f)); ShadowDepthMap->ShadowMapSizeY = ShadowDepthMap->ShadowMapSizeY == appTruncErrorCode ? INT_MAX : ShadowDepthMap->ShadowMapSizeY; // Clamp the number of dominant shadow samples generated if necessary while maintaining aspect ratio if ((uint64)ShadowDepthMap->ShadowMapSizeX * (uint64)ShadowDepthMap->ShadowMapSizeY > (uint64)ShadowSettings.StaticShadowDepthMapMaxSamples) { const float AspectRatio = ShadowDepthMap->ShadowMapSizeX / (float)ShadowDepthMap->ShadowMapSizeY; ShadowDepthMap->ShadowMapSizeY = FMath::TruncToInt(FMath::Sqrt(ShadowSettings.StaticShadowDepthMapMaxSamples / AspectRatio)); ShadowDepthMap->ShadowMapSizeX = FMath::TruncToInt((float)ShadowSettings.StaticShadowDepthMapMaxSamples / (float)ShadowDepthMap->ShadowMapSizeY); } // Allocate the shadow map ShadowDepthMap->ShadowMap.Empty(ShadowDepthMap->ShadowMapSizeX * ShadowDepthMap->ShadowMapSizeY); ShadowDepthMap->ShadowMap.AddZeroed(ShadowDepthMap->ShadowMapSizeX * ShadowDepthMap->ShadowMapSizeY); { const float InvDistanceRange = 1.0f / (LightSpaceImportanceBounds.Max.Z - LightSpaceImportanceBounds.Min.Z); const FMatrix44f LightToWorld = ShadowDepthMap->WorldToLight.InverseFast(); for (int32 Y = 0; Y < ShadowDepthMap->ShadowMapSizeY; Y++) { for (int32 X = 0; X < ShadowDepthMap->ShadowMapSizeX; X++) { float MaxSampleDistance = 0.0f; // Super sample each cell for (int32 SubSampleY = 0; SubSampleY < ShadowSettings.StaticShadowDepthMapSuperSampleFactor; SubSampleY++) { const float YFraction = (Y + SubSampleY / (float)ShadowSettings.StaticShadowDepthMapSuperSampleFactor) / (float)(ShadowDepthMap->ShadowMapSizeY - 1); for (int32 SubSampleX = 0; SubSampleX < ShadowSettings.StaticShadowDepthMapSuperSampleFactor; SubSampleX++) { const float XFraction = (X + SubSampleX / (float)ShadowSettings.StaticShadowDepthMapSuperSampleFactor) / (float)(ShadowDepthMap->ShadowMapSizeX - 1); // Construct a ray in light space along the direction of the light, starting at the minimum light space Z going to the maximum. const FVector4f LightSpaceStartPosition( LightSpaceImportanceBounds.Min.X + XFraction * (LightSpaceImportanceBounds.Max.X - LightSpaceImportanceBounds.Min.X), LightSpaceImportanceBounds.Min.Y + YFraction * (LightSpaceImportanceBounds.Max.Y - LightSpaceImportanceBounds.Min.Y), LightSpaceImportanceBounds.Min.Z); const FVector4f LightSpaceEndPosition(LightSpaceStartPosition.X, LightSpaceStartPosition.Y, LightSpaceImportanceBounds.Max.Z); // Transform the ray into world space in order to trace against the world space aggregate mesh const FVector4f WorldSpaceStartPosition = LightToWorld.TransformPosition(LightSpaceStartPosition); const FVector4f WorldSpaceEndPosition = LightToWorld.TransformPosition(LightSpaceEndPosition); const FLightRay LightRay( WorldSpaceStartPosition, WorldSpaceEndPosition, NULL, NULL, // We are tracing from the light instead of to the light, // So flip sidedness so that backface culling matches up with tracing to the light LIGHTRAY_FLIP_SIDEDNESS ); FLightRayIntersection Intersection; AggregateMesh->IntersectLightRay(LightRay, true, false, true, Context.RayCache, Intersection); if (Intersection.bIntersects) { // Use the maximum distance of all super samples for each cell, to get a conservative shadow map MaxSampleDistance = FMath::Max(MaxSampleDistance, (Intersection.IntersectionVertex.WorldPosition - WorldSpaceStartPosition).Size3()); } } } if (MaxSampleDistance == 0.0f) { MaxSampleDistance = LightSpaceImportanceBounds.Max.Z - LightSpaceImportanceBounds.Min.Z; } ShadowDepthMap->ShadowMap[Y * ShadowDepthMap->ShadowMapSizeX + X] = FStaticShadowDepthMapSample(FFloat16(MaxSampleDistance * InvDistanceRange)); } } } ShadowDepthMap->WorldToLight *= FTranslationMatrix44f(-LightSpaceImportanceBounds.Min) * FScaleMatrix44f(FVector3f(1.0f) / (LightSpaceImportanceBounds.Max - LightSpaceImportanceBounds.Min)); FScopeLock Lock(&CompletedStaticShadowDepthMapsSync); CompletedStaticShadowDepthMaps.Add(DirectionalLight, ShadowDepthMap); } else if (SpotLight) { FVector4f XAxis, YAxis; SpotLight->Direction.FindBestAxisVectors3(XAxis, YAxis); // Create a coordinate system for the spot light, with the z axis corresponding to the light's direction, and translated to the light's origin ShadowDepthMap->WorldToLight = FTranslationMatrix44f(-SpotLight->Position) * FBasisVectorMatrix44f(XAxis, YAxis, SpotLight->Direction, FVector4f(0,0,0)); // Distance from the light's direction axis to the edge of the cone at the radius of the light const float HalfCrossSectionLength = SpotLight->Radius * FMath::Tan(SpotLight->OuterConeAngle * (float)PI / 180.0f); const FVector4f LightSpaceImportanceBoundMin = FVector4f(-HalfCrossSectionLength, -HalfCrossSectionLength, 0); const FVector4f LightSpaceImportanceBoundMax = FVector4f(HalfCrossSectionLength, HalfCrossSectionLength, SpotLight->Radius); ShadowDepthMap->ShadowMapSizeX = FMath::TruncToInt(FMath::Max(HalfCrossSectionLength * ClampedResolutionScale / ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceX, 4.0f)); ShadowDepthMap->ShadowMapSizeX = ShadowDepthMap->ShadowMapSizeX == appTruncErrorCode ? INT_MAX : ShadowDepthMap->ShadowMapSizeX; ShadowDepthMap->ShadowMapSizeY = ShadowDepthMap->ShadowMapSizeX; // Clamp the number of dominant shadow samples generated if necessary while maintaining aspect ratio if ((uint64)ShadowDepthMap->ShadowMapSizeX * (uint64)ShadowDepthMap->ShadowMapSizeY > (uint64)ShadowSettings.StaticShadowDepthMapMaxSamples) { const float AspectRatio = ShadowDepthMap->ShadowMapSizeX / (float)ShadowDepthMap->ShadowMapSizeY; ShadowDepthMap->ShadowMapSizeY = FMath::TruncToInt(FMath::Sqrt(ShadowSettings.StaticShadowDepthMapMaxSamples / AspectRatio)); ShadowDepthMap->ShadowMapSizeX = FMath::TruncToInt((float)ShadowSettings.StaticShadowDepthMapMaxSamples / (float)ShadowDepthMap->ShadowMapSizeY); } ShadowDepthMap->ShadowMap.Empty(ShadowDepthMap->ShadowMapSizeX * ShadowDepthMap->ShadowMapSizeY); ShadowDepthMap->ShadowMap.AddZeroed(ShadowDepthMap->ShadowMapSizeX * ShadowDepthMap->ShadowMapSizeY); // Calculate the maximum possible distance for quantization const float MaxPossibleDistance = LightSpaceImportanceBoundMax.Z - LightSpaceImportanceBoundMin.Z; const FMatrix44f LightToWorld = ShadowDepthMap->WorldToLight.InverseFast(); const FBoxSphereBounds3f ImportanceVolume = GetImportanceBounds().SphereRadius > 0.0f ? GetImportanceBounds() : FBoxSphereBounds3f(AggregateMesh->GetBounds()); for (int32 Y = 0; Y < ShadowDepthMap->ShadowMapSizeY; Y++) { for (int32 X = 0; X < ShadowDepthMap->ShadowMapSizeX; X++) { float MaxSampleDistance = 0.0f; // Super sample each cell for (int32 SubSampleY = 0; SubSampleY < ShadowSettings.StaticShadowDepthMapSuperSampleFactor; SubSampleY++) { const float YFraction = (Y + SubSampleY / (float)ShadowSettings.StaticShadowDepthMapSuperSampleFactor) / (float)(ShadowDepthMap->ShadowMapSizeY - 1); for (int32 SubSampleX = 0; SubSampleX < ShadowSettings.StaticShadowDepthMapSuperSampleFactor; SubSampleX++) { const float XFraction = (X + SubSampleX / (float)ShadowSettings.StaticShadowDepthMapSuperSampleFactor) / (float)(ShadowDepthMap->ShadowMapSizeX - 1); // Construct a ray in light space along the direction of the light, starting at the light and going to the maximum light space Z. const FVector4f LightSpaceStartPosition(0,0,0); const FVector4f LightSpaceEndPosition( LightSpaceImportanceBoundMin.X + XFraction * (LightSpaceImportanceBoundMax.X - LightSpaceImportanceBoundMin.X), LightSpaceImportanceBoundMin.Y + YFraction * (LightSpaceImportanceBoundMax.Y - LightSpaceImportanceBoundMin.Y), LightSpaceImportanceBoundMax.Z); // Transform the ray into world space in order to trace against the world space aggregate mesh const FVector4f WorldSpaceStartPosition = LightToWorld.TransformPosition(LightSpaceStartPosition); const FVector4f WorldSpaceEndPosition = LightToWorld.TransformPosition(LightSpaceEndPosition); const FLightRay LightRay( WorldSpaceStartPosition, WorldSpaceEndPosition, NULL, NULL, // We are tracing from the light instead of to the light, // So flip sidedness so that backface culling matches up with tracing to the light LIGHTRAY_FLIP_SIDEDNESS ); FLightRayIntersection Intersection; AggregateMesh->IntersectLightRay(LightRay, true, false, true, Context.RayCache, Intersection); if (Intersection.bIntersects) { const FVector4f LightSpaceIntersectPosition = ShadowDepthMap->WorldToLight.TransformPosition(Intersection.IntersectionVertex.WorldPosition); // Use the maximum distance of all super samples for each cell, to get a conservative shadow map MaxSampleDistance = FMath::Max(MaxSampleDistance, LightSpaceIntersectPosition.Z); } } } if (MaxSampleDistance == 0.0f) { MaxSampleDistance = MaxPossibleDistance; } ShadowDepthMap->ShadowMap[Y * ShadowDepthMap->ShadowMapSizeX + X] = FStaticShadowDepthMapSample(FFloat16(MaxSampleDistance / MaxPossibleDistance)); } } ShadowDepthMap->WorldToLight *= // Perspective projection sized to the spotlight cone FPerspectiveMatrix44f(SpotLight->OuterConeAngle * (float)PI / 180.0f, 1, 1, 0, SpotLight->Radius) // Convert from NDC to texture space, normalize Z * FMatrix44f( FPlane4f(.5f, 0, 0, 0), FPlane4f(0, .5f, 0, 0), FPlane4f(0, 0, 1.0f / LightSpaceImportanceBoundMax.Z, 0), FPlane4f(.5f, .5f, 0, 1)); FScopeLock Lock(&CompletedStaticShadowDepthMapsSync); CompletedStaticShadowDepthMaps.Add(SpotLight, ShadowDepthMap); } else if (PointLight) { ShadowDepthMap->ShadowMapSizeX = FMath::TruncToInt(FMath::Max(PointLight->Radius * 4 * ClampedResolutionScale / ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceX, 4.0f)); ShadowDepthMap->ShadowMapSizeX = ShadowDepthMap->ShadowMapSizeX == appTruncErrorCode ? INT_MAX : ShadowDepthMap->ShadowMapSizeX; ShadowDepthMap->ShadowMapSizeY = ShadowDepthMap->ShadowMapSizeX; // Clamp the number of dominant shadow samples generated if necessary while maintaining aspect ratio if ((uint64)ShadowDepthMap->ShadowMapSizeX * (uint64)ShadowDepthMap->ShadowMapSizeY > (uint64)ShadowSettings.StaticShadowDepthMapMaxSamples) { const float AspectRatio = ShadowDepthMap->ShadowMapSizeX / (float)ShadowDepthMap->ShadowMapSizeY; ShadowDepthMap->ShadowMapSizeY = FMath::TruncToInt(FMath::Sqrt(ShadowSettings.StaticShadowDepthMapMaxSamples / AspectRatio)); ShadowDepthMap->ShadowMapSizeX = FMath::TruncToInt((float)ShadowSettings.StaticShadowDepthMapMaxSamples / (float)ShadowDepthMap->ShadowMapSizeY); } // Allocate the shadow map ShadowDepthMap->ShadowMap.Empty(ShadowDepthMap->ShadowMapSizeX * ShadowDepthMap->ShadowMapSizeY); ShadowDepthMap->ShadowMap.AddZeroed(ShadowDepthMap->ShadowMapSizeX * ShadowDepthMap->ShadowMapSizeY); ShadowDepthMap->WorldToLight = FMatrix44f::Identity; for (int32 Y = 0; Y < ShadowDepthMap->ShadowMapSizeY; Y++) { for (int32 X = 0; X < ShadowDepthMap->ShadowMapSizeX; X++) { float MaxSampleDistance = 0.0f; // Super sample each cell for (int32 SubSampleY = 0; SubSampleY < ShadowSettings.StaticShadowDepthMapSuperSampleFactor; SubSampleY++) { const float YFraction = (Y + SubSampleY / (float)ShadowSettings.StaticShadowDepthMapSuperSampleFactor) / (float)(ShadowDepthMap->ShadowMapSizeY - 1); const float Phi = YFraction * PI; const float SinPhi = FMath::Sin(Phi); for (int32 SubSampleX = 0; SubSampleX < ShadowSettings.StaticShadowDepthMapSuperSampleFactor; SubSampleX++) { const float XFraction = (X + SubSampleX / (float)ShadowSettings.StaticShadowDepthMapSuperSampleFactor) / (float)(ShadowDepthMap->ShadowMapSizeX - 1); const float Theta = XFraction * 2 * PI; const FVector3f Direction(FMath::Cos(Theta) * SinPhi, FMath::Sin(Theta) * SinPhi, FMath::Cos(Phi)); const FVector4f WorldSpaceStartPosition = PointLight->Position; const FVector4f WorldSpaceEndPosition = PointLight->Position + Direction * PointLight->Radius; const FLightRay LightRay( WorldSpaceStartPosition, WorldSpaceEndPosition, NULL, NULL, // We are tracing from the light instead of to the light, // So flip sidedness so that backface culling matches up with tracing to the light LIGHTRAY_FLIP_SIDEDNESS ); FLightRayIntersection Intersection; AggregateMesh->IntersectLightRay(LightRay, true, false, true, Context.RayCache, Intersection); if (Intersection.bIntersects) { // Use the maximum distance of all super samples for each cell, to get a conservative shadow map MaxSampleDistance = FMath::Max(MaxSampleDistance, (Intersection.IntersectionVertex.WorldPosition - PointLight->Position).Size3()); } } } if (MaxSampleDistance == 0.0f) { MaxSampleDistance = PointLight->Radius; } ShadowDepthMap->ShadowMap[Y * ShadowDepthMap->ShadowMapSizeX + X] = FStaticShadowDepthMapSample(FFloat16(MaxSampleDistance / PointLight->Radius)); } } FScopeLock Lock(&CompletedStaticShadowDepthMapsSync); CompletedStaticShadowDepthMaps.Add(PointLight, ShadowDepthMap); } const float NewTime = FPlatformTime::Seconds() - StartTime; Context.Stats.StaticShadowDepthMapThreadTime = NewTime; Context.Stats.MaxStaticShadowDepthMapThreadTime = NewTime; } /** * Calculates shadowing for a given mapping surface point and light. * @param Mapping - The mapping the point comes from. * @param WorldSurfacePoint - The point to check shadowing at. * @param Light - The light to check shadowing from. * @param CoherentRayCache - The calling thread's collision cache. * @return true if the surface point is shadowed from the light. */ bool FStaticLightingSystem::CalculatePointShadowing( const FStaticLightingMapping* Mapping, const FVector4f& WorldSurfacePoint, const FLight* Light, FStaticLightingMappingContext& MappingContext, bool bDebugThisSample ) const { if(Light->GetSkyLight()) { return true; } else { // Treat points which the light doesn't affect as shadowed to avoid the costly ray check. if(!Light->AffectsBounds(FBoxSphereBounds3f(WorldSurfacePoint,FVector4f(0,0,0,0),0))) { return true; } // Check for visibility between the point and the light. bool bIsShadowed = false; if ((Light->LightFlags & GI_LIGHT_CASTSHADOWS) && (Light->LightFlags & GI_LIGHT_CASTSTATICSHADOWS)) { // TODO find best point on light to shadow from // Construct a line segment between the light and the surface point. const FVector4f LightPosition = FVector4f(Light->Position.X, Light->Position.Y, Light->Position.Z, 0); const FVector4f LightVector = LightPosition - WorldSurfacePoint * Light->Position.W; const FLightRay LightRay( WorldSurfacePoint + LightVector.GetSafeNormal() * SceneConstants.VisibilityRayOffsetDistance, WorldSurfacePoint + LightVector, Mapping, Light ); // Check the line segment for intersection with the static lighting meshes. FLightRayIntersection Intersection; AggregateMesh->IntersectLightRay(LightRay, false, false, true, MappingContext.RayCache, Intersection); bIsShadowed = Intersection.bIntersects; #if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING if (bDebugThisSample) { FDebugStaticLightingRay DebugRay(LightRay.Start, LightRay.End, bIsShadowed != 0); if (bIsShadowed) { DebugRay.End = Intersection.IntersectionVertex.WorldPosition; } MappingContext.DebugOutput->ShadowRays.Add(DebugRay); } #endif } return bIsShadowed; } } /** Calculates area shadowing from a light for the given vertex. */ FVector2f FStaticLightingSystem::CalculatePointAreaShadowing( const FStaticLightingMapping* Mapping, const FStaticLightingVertex& Vertex, int32 ElementIndex, float SampleRadius, const FLight* Light, FStaticLightingMappingContext& MappingContext, FLMRandomStream& RandomStream, FLinearColor& UnnormalizedTransmission, const TArray& LightPositionSamples, bool bDebugThisSample ) const { #if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING if (bDebugThisSample) { int32 TempBreak = 0; } #endif UnnormalizedTransmission = FLinearColor::Black; // Treat points which the light doesn't affect as shadowed to avoid the costly ray check. if( !Light->AffectsBounds(FBoxSphereBounds3f(Vertex.WorldPosition,FVector4f(0,0,0),0))) { return FVector2f::ZeroVector; } // Check for visibility between the point and the light if ((Light->LightFlags & GI_LIGHT_CASTSHADOWS) && (Light->LightFlags & GI_LIGHT_CASTSTATICSHADOWS)) { MappingContext.Stats.NumDirectLightingShadowRays += LightPositionSamples.Num(); const bool bIsTwoSided = Mapping->Mesh->IsTwoSided(ElementIndex); FVector2f ShadowedValue = FVector2f::ZeroVector; // Integrate over the surface of the light using monte carlo integration // Note that we are making the approximation that the BRDF and the Light's emission are equal in all of these directions and therefore are not in the integrand for(int32 RayIndex = 0; RayIndex < LightPositionSamples.Num(); RayIndex++) { FLightSurfaceSample CurrentSample = LightPositionSamples[RayIndex]; // Allow the light to modify the surface position for this receiving position Light->ValidateSurfaceSample(Vertex.WorldPosition, CurrentSample); // Construct a line segment between the light and the surface point. const FVector4f LightVector = CurrentSample.Position - Vertex.WorldPosition; FVector4f SampleOffset(0,0,0); if (GeneralSettings.bAccountForTexelSize) { /* //@todo - the rays cross over on the way to the light and mess up penumbra shapes. //@todo - need to use more than texel size, otherwise BSP generates lots of texels that become half shadowed at corners SampleOffset = Vertex.WorldTangentX * LightPositionSamples(RayIndex).DiskPosition.X * SampleRadius * SceneConstants.VisibilityTangentOffsetSampleRadiusScale + Vertex.WorldTangentY * LightPositionSamples(RayIndex).DiskPosition.Y * SampleRadius * SceneConstants.VisibilityTangentOffsetSampleRadiusScale; */ } float DistSqr = LightVector.SizeSquared3() + KINDA_SMALL_NUMBER; float SamplePDF = CurrentSample.PDF * -Dot3( CurrentSample.Normal, LightVector ) / ( DistSqr * FMath::Sqrt( DistSqr ) ); if( SamplePDF <= 0.0f ) { continue; } FVector4f NormalForOffset = Vertex.WorldTangentZ; // Flip the normal used for offsetting the start of the ray for two sided materials if a flipped normal would be closer to the light. // This prevents incorrect shadowing where using the frontface normal would cause the ray to start inside a nearby object. if (bIsTwoSided && Dot3(-NormalForOffset, LightVector) > Dot3(NormalForOffset, LightVector)) { NormalForOffset = -NormalForOffset; } const FLightRay LightRay( // Offset the start of the ray by some fraction along the direction of the ray and some fraction along the vertex normal. Vertex.WorldPosition + LightVector.GetSafeNormal() * SceneConstants.VisibilityRayOffsetDistance + NormalForOffset * SampleRadius * SceneConstants.VisibilityNormalOffsetSampleRadiusScale + SampleOffset, Vertex.WorldPosition + LightVector, Mapping, Light ); // Check the line segment for intersection with the static lighting meshes. FLightRayIntersection Intersection; //@todo - change this back to request boolean visibility once transmission is supported with boolean visibility ray intersections AggregateMesh->IntersectLightRay(LightRay, true, true, true, MappingContext.RayCache, Intersection); if (!Intersection.bIntersects) { UnnormalizedTransmission += Intersection.Transmission * SamplePDF; ShadowedValue.X += SamplePDF; } ShadowedValue.Y += SamplePDF; #if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING if (bDebugThisSample) { FDebugStaticLightingRay DebugRay(LightRay.Start, LightRay.End, Intersection.bIntersects); if (Intersection.bIntersects) { DebugRay.End = Intersection.IntersectionVertex.WorldPosition; } MappingContext.DebugOutput->ShadowRays.Add(DebugRay); } #endif } return ShadowedValue; } UnnormalizedTransmission = FLinearColor::White; return FVector2f( 1.0f, 1.0f ); } /** Calculates the lighting contribution of a light to a mapping vertex. */ FGatheredLightSample FStaticLightingSystem::CalculatePointLighting( const FStaticLightingMapping* Mapping, const FStaticLightingVertex& Vertex, int32 ElementIndex, const FLight* Light, const FLinearColor& InLightIntensity, const FLinearColor& InTransmission ) const { // don't do sky lights here if (Light->GetSkyLight() == NULL) { // Calculate the direction from the vertex to the light. const FVector4f WorldLightVector = Light->GetDirectLightingDirection(Vertex.WorldPosition, Vertex.WorldTangentZ); // Transform the light vector to tangent space. const FVector4f TangentLightVector = FVector4f( Dot3(WorldLightVector, Vertex.WorldTangentX), Dot3(WorldLightVector, Vertex.WorldTangentY), Dot3(WorldLightVector, Vertex.WorldTangentZ), 0 ).GetSafeNormal(); // Compute the incident lighting of the light on the vertex. const FLinearColor LightIntensity = InLightIntensity * InTransmission; // Compute the light-map sample for the front-face of the vertex. FGatheredLightSample FrontFaceSample = FGatheredLightSampleUtil::PointLightWorldSpace<2>(LightIntensity, TangentLightVector, WorldLightVector.GetSafeNormal()); if (Mapping->Mesh->UsesTwoSidedLighting(ElementIndex)) { const FVector4f BackFaceTangentLightVector = FVector4f( Dot3(WorldLightVector, -Vertex.WorldTangentX), Dot3(WorldLightVector, -Vertex.WorldTangentY), Dot3(WorldLightVector, -Vertex.WorldTangentZ), 0 ).GetSafeNormal(); const FGatheredLightSample BackFaceSample = FGatheredLightSampleUtil::PointLightWorldSpace<2>(LightIntensity, BackFaceTangentLightVector, -WorldLightVector.GetSafeNormal()); // Average front and back face lighting return (FrontFaceSample + BackFaceSample) * .5f; } else { return FrontFaceSample; } } return FGatheredLightSample(); } /** Returns a light sample that represents the material attribute specified by MaterialSettings.ViewMaterialAttribute at the intersection. */ FGatheredLightSample FStaticLightingSystem::GetVisualizedMaterialAttribute(const FStaticLightingMapping* Mapping, const FLightRayIntersection& Intersection) const { FGatheredLightSample MaterialSample; if (Intersection.bIntersects && Intersection.Mapping == Mapping) { // The ray intersected an opaque surface, we can visualize anything that opaque materials store //@todo - Currently can't visualize emissive on translucent materials if (MaterialSettings.ViewMaterialAttribute == VMA_Emissive) { FLinearColor Emissive = FLinearColor::Black; if (Intersection.Mesh->IsEmissive(Intersection.ElementIndex)) { Emissive = Intersection.Mesh->EvaluateEmissive(Intersection.IntersectionVertex.TextureCoordinates[0], Intersection.ElementIndex); } MaterialSample = FGatheredLightSampleUtil::AmbientLight<2>(Emissive); } else if (MaterialSettings.ViewMaterialAttribute == VMA_Diffuse) { const FLinearColor Diffuse = Intersection.Mesh->EvaluateDiffuse(Intersection.IntersectionVertex.TextureCoordinates[0], Intersection.ElementIndex); MaterialSample = FGatheredLightSampleUtil::AmbientLight<2>(Diffuse); } else if (MaterialSettings.ViewMaterialAttribute == VMA_Normal) { const FVector4f Normal = Intersection.Mesh->EvaluateNormal(Intersection.IntersectionVertex.TextureCoordinates[0], Intersection.ElementIndex); FLinearColor NormalColor; NormalColor.R = Normal.X * 0.5f + 0.5f; NormalColor.G = Normal.Y * 0.5f + 0.5f; NormalColor.B = Normal.Z * 0.5f + 0.5f; NormalColor.A = 1.0f; MaterialSample = FGatheredLightSampleUtil::AmbientLight<2>(NormalColor); } } else if (MaterialSettings.ViewMaterialAttribute != VMA_Transmission) { // The ray didn't intersect an opaque surface and we're not visualizing transmission MaterialSample = FGatheredLightSampleUtil::AmbientLight<2>(FLinearColor::Black); } if (MaterialSettings.ViewMaterialAttribute == VMA_Transmission) { // Visualizing transmission, replace the light sample with the transmission picked up along the ray MaterialSample = FGatheredLightSampleUtil::AmbientLight<2>(Intersection.Transmission); } return MaterialSample; } /** * Checks if a light is behind a triangle. * @param TrianglePoint - Any point on the triangle. * @param TriangleNormal - The (not necessarily normalized) triangle surface normal. * @param Light - The light to classify. * @return true if the light is behind the triangle. */ bool IsLightBehindSurface(const FVector4f& TrianglePoint, const FVector4f& TriangleNormal, const FLight* Light) { const bool bIsSkyLight = Light->GetSkyLight() != NULL; if (!bIsSkyLight) { // Calculate the direction from the triangle to the light. const FVector4f LightPosition = FVector4f(Light->Position.X, Light->Position.Y, Light->Position.Z, 0); const FVector4f WorldLightVector = LightPosition - TrianglePoint * Light->Position.W; // Check if the light is in front of the triangle. const float Dot = Dot3(WorldLightVector, TriangleNormal); return Dot < 0.0f; } else { // Sky lights are always in front of a surface. return false; } } /** * Culls lights that are behind a triangle. * @param bTwoSidedMaterial - true if the triangle has a two-sided material. If so, lights behind the surface are not culled. * @param TrianglePoint - Any point on the triangle. * @param TriangleNormal - The (not necessarily normalized) triangle surface normal. * @param Lights - The lights to cull. * @return A map from Lights index to a boolean which is true if the light is in front of the triangle. */ TBitArray<> CullBackfacingLights(bool bTwoSidedMaterial,const FVector4f& TrianglePoint,const FVector4f& TriangleNormal,const TArray& Lights) { if(!bTwoSidedMaterial) { TBitArray<> Result(false,Lights.Num()); for(int32 LightIndex = 0;LightIndex < Lights.Num();LightIndex++) { Result[LightIndex] = !IsLightBehindSurface(TrianglePoint,TriangleNormal,Lights[LightIndex]); } return Result; } else { return TBitArray<>(true,Lights.Num()); } } } //namespace Lightmass