// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "Raster.h" #include "LightingSystem.h" #include "LightmassSwarm.h" #include "Misc/Compression.h" #include "HAL/RunnableThread.h" #include "HAL/PlatformProcess.h" #include "TextureMappingSetup.h" #include "Misc/Compression.h" namespace Lightmass { bool bCompressRadiosityCachedData = false; void FCompressedGatherHitPoints::Compress(const FGatherHitPoints& Source) { { const int32 UncompressedSize = Source.GatherHitPointRanges.Num() * Source.GatherHitPointRanges.GetTypeSize(); TArray TempCompressedMemory; // Compressed can be slightly larger than uncompressed TempCompressedMemory.Empty(UncompressedSize * 4 / 3); TempCompressedMemory.AddUninitialized(UncompressedSize * 4 / 3); int32 CompressedSize = TempCompressedMemory.Num() * TempCompressedMemory.GetTypeSize(); verify(FCompression::CompressMemory( NAME_Zlib, TempCompressedMemory.GetData(), CompressedSize, Source.GatherHitPointRanges.GetData(), UncompressedSize, COMPRESS_BiasSpeed)); GatherHitPointRanges.Empty(CompressedSize); GatherHitPointRanges.AddUninitialized(CompressedSize); FPlatformMemory::Memcpy(GatherHitPointRanges.GetData(), TempCompressedMemory.GetData(), CompressedSize); GatherHitPointRangesUncompressedSize = UncompressedSize; } { const int32 UncompressedSize = Source.GatherHitPointData.Num() * Source.GatherHitPointData.GetTypeSize(); TArray TempCompressedMemory; // Compressed can be slightly larger than uncompressed TempCompressedMemory.Empty(UncompressedSize * 4 / 3); TempCompressedMemory.AddUninitialized(UncompressedSize * 4 / 3); int32 CompressedSize = TempCompressedMemory.Num() * TempCompressedMemory.GetTypeSize(); verify(FCompression::CompressMemory( NAME_Zlib, TempCompressedMemory.GetData(), CompressedSize, Source.GatherHitPointData.GetData(), UncompressedSize)); GatherHitPointData.Empty(CompressedSize); GatherHitPointData.AddUninitialized(CompressedSize); FPlatformMemory::Memcpy(GatherHitPointData.GetData(), TempCompressedMemory.GetData(), CompressedSize); GatherHitPointDataUncompressedSize = UncompressedSize; } } void FCompressedGatherHitPoints::Decompress(FGatherHitPoints& Dest) const { Dest.GatherHitPointRanges.Reset(GatherHitPointRangesUncompressedSize); Dest.GatherHitPointRanges.AddUninitialized(GatherHitPointRangesUncompressedSize); verify(FCompression::UncompressMemory( NAME_Zlib, Dest.GatherHitPointRanges.GetData(), GatherHitPointRangesUncompressedSize, GatherHitPointRanges.GetData(), GatherHitPointRanges.Num())); Dest.GatherHitPointData.Reset(GatherHitPointDataUncompressedSize); Dest.GatherHitPointData.AddUninitialized(GatherHitPointDataUncompressedSize); verify(FCompression::UncompressMemory( NAME_Zlib, Dest.GatherHitPointData.GetData(), GatherHitPointDataUncompressedSize, GatherHitPointData.GetData(), GatherHitPointData.Num())); } void FCompressedInfluencingRecords::Compress(const FInfluencingRecords& Source) { { const int32 UncompressedSize = Source.Ranges.Num() * Source.Ranges.GetTypeSize(); TArray TempCompressedMemory; // Compressed can be slightly larger than uncompressed TempCompressedMemory.Empty(UncompressedSize * 4 / 3); TempCompressedMemory.AddUninitialized(UncompressedSize * 4 / 3); int32 CompressedSize = TempCompressedMemory.Num() * TempCompressedMemory.GetTypeSize(); verify(FCompression::CompressMemory( NAME_Zlib, TempCompressedMemory.GetData(), CompressedSize, Source.Ranges.GetData(), UncompressedSize, COMPRESS_BiasSpeed)); Ranges.Empty(CompressedSize); Ranges.AddUninitialized(CompressedSize); FPlatformMemory::Memcpy(Ranges.GetData(), TempCompressedMemory.GetData(), CompressedSize); RangesUncompressedSize = UncompressedSize; } { const int32 UncompressedSize = Source.Data.Num() * Source.Data.GetTypeSize(); TArray TempCompressedMemory; // Compressed can be slightly larger than uncompressed TempCompressedMemory.Empty(UncompressedSize * 4 / 3); TempCompressedMemory.AddUninitialized(UncompressedSize * 4 / 3); int32 CompressedSize = TempCompressedMemory.Num() * TempCompressedMemory.GetTypeSize(); verify(FCompression::CompressMemory( NAME_Zlib, TempCompressedMemory.GetData(), CompressedSize, Source.Data.GetData(), UncompressedSize)); Data.Empty(CompressedSize); Data.AddUninitialized(CompressedSize); FPlatformMemory::Memcpy(Data.GetData(), TempCompressedMemory.GetData(), CompressedSize); DataUncompressedSize = UncompressedSize; } } void FCompressedInfluencingRecords::Decompress(FInfluencingRecords& Dest) const { Dest.Ranges.Reset(RangesUncompressedSize); Dest.Ranges.AddUninitialized(RangesUncompressedSize); verify(FCompression::UncompressMemory( NAME_Zlib, Dest.Ranges.GetData(), RangesUncompressedSize, Ranges.GetData(), Ranges.Num())); Dest.Data.Reset(DataUncompressedSize); Dest.Data.AddUninitialized(DataUncompressedSize); verify(FCompression::UncompressMemory( NAME_Zlib, Dest.Data.GetData(), DataUncompressedSize, Data.GetData(), Data.Num())); } void FStaticLightingSystem::SetupRadiosity() { const double RadiosityStartTime = FPlatformTime::Seconds(); for(int32 ThreadIndex = 1; ThreadIndex < NumStaticLightingThreads; ThreadIndex++) { FMappingProcessingThreadRunnable* ThreadRunnable = new FMappingProcessingThreadRunnable(this, ThreadIndex, StaticLightingTask_RadiositySetup); RadiositySetupThreads.Add(ThreadRunnable); const FString ThreadName = FString::Printf(TEXT("RadiositySetupThread%u"), ThreadIndex); ThreadRunnable->Thread = FRunnableThread::Create(ThreadRunnable, *ThreadName); } // Start the static lighting thread loop on the main thread, too. // Once it returns, all static lighting mappings have begun processing. RadiositySetupThreadLoop(0, true); // Stop the static lighting threads. for(int32 ThreadIndex = 0;ThreadIndex < RadiositySetupThreads.Num();ThreadIndex++) { // Wait for the thread to exit. RadiositySetupThreads[ThreadIndex].Thread->WaitForCompletion(); // Check that it didn't terminate with an error. RadiositySetupThreads[ThreadIndex].CheckHealth(); // Destroy the thread. delete RadiositySetupThreads[ThreadIndex].Thread; } RadiositySetupThreads.Empty(); const float RadiosityDuration = FPlatformTime::Seconds() - RadiosityStartTime; LogSolverMessage(FString::Printf(TEXT("Radiosity Setup %.1fs"), RadiosityDuration)); } void FStaticLightingSystem::RadiositySetupThreadLoop(int32 ThreadIndex, bool bIsMainThread) { GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing4, ThreadIndex ) ); bool bIsDone = false; while (!bIsDone) { // Atomically read and increment the next mapping index to process. const int32 MappingIndex = NextMappingToProcessRadiositySetup.Increment() - 1; if (MappingIndex < AllMappings.Num()) { // If this is the main thread, update progress and apply completed static lighting. if (bIsMainThread) { // Check the health of all static lighting threads. for (int32 ThreadIndexIter = 0; ThreadIndexIter < RadiositySetupThreads.Num(); ThreadIndexIter++) { RadiositySetupThreads[ThreadIndexIter].CheckHealth(); } } FStaticLightingTextureMapping* TextureMapping = AllMappings[MappingIndex]->GetTextureMapping(); if (TextureMapping) { RadiositySetupTextureMapping(TextureMapping); } } else { // Processing has begun for all mappings. bIsDone = true; } } } /** Caches irradiance photons on a single texture mapping. */ void FStaticLightingSystem::RadiositySetupTextureMapping(FStaticLightingTextureMapping* TextureMapping) { checkSlow(TextureMapping); FStaticLightingMappingContext MappingContext(TextureMapping->Mesh,*this,&StartupDebugOutput); LIGHTINGSTAT(FScopedRDTSCTimer CachingTime(MappingContext.Stats.RadiositySetupThreadTime)); const FBoxSphereBounds3f ImportanceBounds = Scene.GetImportanceBounds(); FTexelToVertexMap TexelToVertexMap(TextureMapping->SurfaceCacheSizeX, TextureMapping->SurfaceCacheSizeY); bool bDebugThisMapping = false; #if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING bDebugThisMapping = TextureMapping == Scene.DebugMapping; int32 SurfaceCacheDebugX = -1; int32 SurfaceCacheDebugY = -1; if (bDebugThisMapping) { SurfaceCacheDebugX = FMath::TruncToInt(Scene.DebugInput.LocalX / (float)TextureMapping->CachedSizeX * TextureMapping->SurfaceCacheSizeX); SurfaceCacheDebugY = FMath::TruncToInt(Scene.DebugInput.LocalY / (float)TextureMapping->CachedSizeY * TextureMapping->SurfaceCacheSizeY); } #endif RasterizeToSurfaceCacheTextureMapping(TextureMapping, bDebugThisMapping, TexelToVertexMap); TextureMapping->SurfaceCacheLighting.Empty(TextureMapping->SurfaceCacheSizeX * TextureMapping->SurfaceCacheSizeY); TextureMapping->SurfaceCacheLighting.AddZeroed(TextureMapping->SurfaceCacheSizeX * TextureMapping->SurfaceCacheSizeY); TextureMapping->RadiositySurfaceCache[0].Empty(TextureMapping->SurfaceCacheSizeX * TextureMapping->SurfaceCacheSizeY); TextureMapping->RadiositySurfaceCache[0].AddZeroed(TextureMapping->SurfaceCacheSizeX * TextureMapping->SurfaceCacheSizeY); TextureMapping->RadiositySurfaceCache[1].Empty(TextureMapping->SurfaceCacheSizeX * TextureMapping->SurfaceCacheSizeY); TextureMapping->RadiositySurfaceCache[1].AddZeroed(TextureMapping->SurfaceCacheSizeX * TextureMapping->SurfaceCacheSizeY); const bool bCacheFinalGatherHitPoints = ImportanceTracingSettings.bCacheFinalGatherHitPointsForRadiosity && GeneralSettings.NumSkyLightingBounces > 0; FGatherHitPoints& GatherHitPoints = TextureMapping->UncompressedGatherHitPoints; if (bCacheFinalGatherHitPoints) { GatherHitPoints.GatherHitPointRanges.Empty(TextureMapping->SurfaceCacheSizeY * TextureMapping->SurfaceCacheSizeX / 4); GatherHitPoints.GatherHitPointData.Empty(TextureMapping->SurfaceCacheSizeY * TextureMapping->SurfaceCacheSizeX / 4); } TLightingCache RadiosityCache(TextureMapping->Mesh->BoundingBox, *this, 1); FLMRandomStream RandomStream(0); #if LIGHTMASS_DO_PROCESSING if (GeneralSettings.NumSkyLightingBounces > 0) { for (int32 Y = 0; Y < TextureMapping->SurfaceCacheSizeY; Y++) { for (int32 X = 0; X < TextureMapping->SurfaceCacheSizeX; X++) { bool bDebugThisTexel = false; #if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING if (bDebugThisMapping && Y == SurfaceCacheDebugY && X == SurfaceCacheDebugX) { bDebugThisTexel = true; } #endif const FTexelToVertex& TexelToVertex = TexelToVertexMap(X,Y); if (TexelToVertex.TotalSampleWeight > 0.0f) { FFullStaticLightingVertex Vertex = TexelToVertex.GetFullVertex(); Vertex.ApplyVertexModifications(TexelToVertex.ElementIndex, MaterialSettings.bUseNormalMapsForLighting, TextureMapping->Mesh); FFinalGatherSample SkyLighting; FFinalGatherSample UnusedSecondLighting; float UnusedBackfacingHitsFraction; if (!RadiosityCache.InterpolateLighting(Vertex, true, false, 1.0f, SkyLighting, UnusedSecondLighting, UnusedBackfacingHitsFraction, MappingContext.DebugCacheRecords)) { FFinalGatherSample UniformSampledIncomingRadiance; TArray ImportancePhotonDirections; FLightingCacheGatherInfo GatherInfo; const int32 NumAdaptiveRefinementLevels = GeneralSettings.IndirectLightingQuality <= 10 ? 1 : 2; if (bCacheFinalGatherHitPoints) { GatherHitPoints.GatherHitPointRanges.Add(FArrayRange(GatherHitPoints.GatherHitPointData.Num())); GatherInfo.HitPointRecorder = &GatherHitPoints; } UniformSampledIncomingRadiance = IncomingRadianceAdaptive( TextureMapping, Vertex, TexelToVertex.SampleRadius, false, TexelToVertex.ElementIndex, 2, /** BounceNumber */ RBM_ConstantNormalOffset, GLM_GatherLightEmitted, /* Gather sky light and emissive only */ NumAdaptiveRefinementLevels, 1.0f, CachedHemisphereSamplesForRadiosity[0], CachedHemisphereSamplesForRadiosityUniforms[0], 1, ImportancePhotonDirections, MappingContext, RandomStream, GatherInfo, true, /* bGatheringForCachedDirectLighting */ false); float OverrideRadius = 0; TLightingCache::FRecord NewRecord( Vertex, TexelToVertex.ElementIndex, GatherInfo, TexelToVertex.SampleRadius, OverrideRadius, IrradianceCachingSettings, GeneralSettings, UniformSampledIncomingRadiance, FVector4f(0, 0, 0, 0), FVector4f(0, 0, 0, 0) ); // Add the incident radiance sample to the cache. RadiosityCache.AddRecord(NewRecord, false, false); } } } } } FInfluencingRecords& InfluencingRecords = TextureMapping->InfluencingRecordsSurfaceCache; if (bCacheFinalGatherHitPoints) { InfluencingRecords.Ranges.Empty(TextureMapping->SurfaceCacheSizeX * TextureMapping->SurfaceCacheSizeY); InfluencingRecords.Ranges.AddZeroed(TextureMapping->SurfaceCacheSizeX * TextureMapping->SurfaceCacheSizeY); InfluencingRecords.Data.Empty(GatherHitPoints.GatherHitPointData.Num()); } for (int32 Y = 0; Y < TextureMapping->SurfaceCacheSizeY; Y++) { for (int32 X = 0; X < TextureMapping->SurfaceCacheSizeX; X++) { bool bDebugThisTexel = false; #if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING if (bDebugThisMapping && Y == SurfaceCacheDebugY && X == SurfaceCacheDebugX) { bDebugThisTexel = true; } #endif const FTexelToVertex& TexelToVertex = TexelToVertexMap(X,Y); if (TexelToVertex.TotalSampleWeight > 0.0f) { FFullStaticLightingVertex CurrentVertex = TexelToVertex.GetFullVertex(); CurrentVertex.ApplyVertexModifications(TexelToVertex.ElementIndex, MaterialSettings.bUseNormalMapsForLighting, TextureMapping->Mesh); const int32 SurfaceCacheIndex = Y * TextureMapping->SurfaceCacheSizeX + X; FInfluencingRecordCollector RecordCollector(InfluencingRecords, SurfaceCacheIndex); FInfluencingRecordCollector* RecordCollectorPtr = NULL; if (bCacheFinalGatherHitPoints) { RecordCollectorPtr = &RecordCollector; InfluencingRecords.Ranges[SurfaceCacheIndex] = FArrayRange(InfluencingRecords.Data.Num()); } FLinearColor IncidentLighting = FLinearColor::Black; FLinearColor IncidentLightingForRadiosity = FLinearColor::Black; if (GeneralSettings.NumSkyLightingBounces > 0) { FFinalGatherSample SkyLighting; FFinalGatherSample UnusedSecondLighting; float UnusedBackfacingHitsFraction; RadiosityCache.InterpolateLighting(CurrentVertex, false, false, IrradianceCachingSettings.SkyOcclusionSmoothnessReduction, SkyLighting, UnusedSecondLighting, UnusedBackfacingHitsFraction, MappingContext.DebugCacheRecords, RecordCollectorPtr); if (GeneralSettings.ViewSingleBounceNumber < 0 || GeneralSettings.ViewSingleBounceNumber == 1) { IncidentLighting += SkyLighting.IncidentLighting + SkyLighting.StationarySkyLighting.IncidentLighting; } IncidentLightingForRadiosity += SkyLighting.IncidentLighting + SkyLighting.StationarySkyLighting.IncidentLighting; } if (ImportanceTracingSettings.bUseRadiositySolverForLightMultibounce) { FGatheredLightSample DirectLighting; float Unused2; TArray> VertexOffsets; VertexOffsets.Add(FVector3f(0, 0, 0)); CalculateApproximateDirectLighting(CurrentVertex, TexelToVertex.TexelRadius, VertexOffsets, .1f, true, true, bDebugThisTexel, MappingContext, DirectLighting, Unused2); IncidentLightingForRadiosity += DirectLighting.IncidentLighting; } TextureMapping->SurfaceCacheLighting[SurfaceCacheIndex] = IncidentLighting; TextureMapping->RadiositySurfaceCache[0][SurfaceCacheIndex] = IncidentLightingForRadiosity; } } } if (bCacheFinalGatherHitPoints && bCompressRadiosityCachedData) { TextureMapping->CompressedGatherHitPoints.Compress(GatherHitPoints); GatherHitPoints = FGatherHitPoints(); TextureMapping->CompressedInfluencingRecords.Compress(InfluencingRecords); InfluencingRecords = FInfluencingRecords(); } #endif } /** */ void FStaticLightingSystem::RunRadiosityIterations() { // First bounce done in setup const int32 NumRadiosityIterations = FMath::Max(GeneralSettings.NumSkyLightingBounces - 1, 0); if (NumRadiosityIterations > 0) { const double RadiosityStartTime = FPlatformTime::Seconds(); for(int32 ThreadIndex = 1; ThreadIndex < NumStaticLightingThreads; ThreadIndex++) { FMappingProcessingThreadRunnable* ThreadRunnable = new FMappingProcessingThreadRunnable(this, ThreadIndex, StaticLightingTask_RadiosityIterations); RadiosityIterationThreads.Add(ThreadRunnable); const FString ThreadName = FString::Printf(TEXT("RadiosityIterationThread%u"), ThreadIndex); ThreadRunnable->Thread = FRunnableThread::Create(ThreadRunnable, *ThreadName); } // Start the static lighting thread loop on the main thread, too. // Once it returns, all static lighting mappings have begun processing. RadiosityIterationThreadLoop(0, true); // Stop the static lighting threads. for(int32 ThreadIndex = 0;ThreadIndex < RadiosityIterationThreads.Num();ThreadIndex++) { // Wait for the thread to exit. RadiosityIterationThreads[ThreadIndex].Thread->WaitForCompletion(); // Check that it didn't terminate with an error. RadiosityIterationThreads[ThreadIndex].CheckHealth(); // Destroy the thread. delete RadiosityIterationThreads[ThreadIndex].Thread; } RadiosityIterationThreads.Empty(); size_t TemporariesSize = 0; for (int32 MappingIndex = 0; MappingIndex < AllMappings.Num(); MappingIndex++) { TemporariesSize += AllMappings[MappingIndex]->FreeRadiosityTemporaries(); } const float RadiosityDuration = FPlatformTime::Seconds() - RadiosityStartTime; LogSolverMessage(FString::Printf(TEXT("Radiosity Iterations %.1fs with %.1fMb of cached data"), RadiosityDuration, TemporariesSize / 1024.0f / 1024.0f)); } } /** */ void FStaticLightingSystem::RadiosityIterationThreadLoop(int32 ThreadIndex, bool bIsMainThread) { const int32 NumRadiosityIterations = FMath::Max(GeneralSettings.NumSkyLightingBounces - 1, 0); bool bIsDone = false; while (!bIsDone) { // Atomically read and increment the next mapping index to process. const int32 TaskIndex = NextMappingToProcessRadiosityIterations.Increment() - 1; if (TaskIndex < AllMappings.Num() * NumRadiosityIterations) { const int32 PassIndex = TaskIndex / AllMappings.Num(); const int32 MappingIndex = TaskIndex - PassIndex * AllMappings.Num(); // If this is the main thread, update progress and apply completed static lighting. if (bIsMainThread) { // Check the health of all static lighting threads. for (int32 ThreadIndexIter = 0; ThreadIndexIter < RadiosityIterationThreads.Num(); ThreadIndexIter++) { RadiosityIterationThreads[ThreadIndexIter].CheckHealth(); } } if (PassIndex > 0) { // Sleep-loop until other threads have completed the previous pass while (NumCompletedRadiosityIterationMappings[PassIndex - 1].GetValue() < AllMappings.Num()) { FPlatformProcess::Sleep(0.0f); } } FStaticLightingTextureMapping* TextureMapping = AllMappings[MappingIndex]->GetTextureMapping(); if (TextureMapping) { RadiosityIterationTextureMapping(TextureMapping, PassIndex); // Make sure writes to mapping data are seen on other threads before the change to NumCompletedRadiosityIterationMappings FPlatformMisc::MemoryBarrier(); NumCompletedRadiosityIterationMappings[PassIndex].Increment(); } } else { // Processing has begun for all mappings. bIsDone = true; } } GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing4, ThreadIndex ) ); } void FStaticLightingSystem::RadiosityIterationTextureMapping(FStaticLightingTextureMapping* TextureMapping, int32 PassIndex) { checkSlow(TextureMapping); FStaticLightingMappingContext MappingContext(TextureMapping->Mesh,*this,&StartupDebugOutput); LIGHTINGSTAT(FScopedRDTSCTimer CachingTime(MappingContext.Stats.RadiosityIterationThreadTime)); const FBoxSphereBounds3f ImportanceBounds = Scene.GetImportanceBounds(); FTexelToVertexMap TexelToVertexMap(TextureMapping->SurfaceCacheSizeX, TextureMapping->SurfaceCacheSizeY); bool bDebugThisMapping = false; #if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING bDebugThisMapping = TextureMapping == Scene.DebugMapping; int32 TexelDebugX = -1; int32 TexelDebugY = -1; if (bDebugThisMapping) { TexelDebugX = FMath::TruncToInt(Scene.DebugInput.LocalX / (float)TextureMapping->CachedSizeX * TextureMapping->SurfaceCacheSizeX); TexelDebugY = FMath::TruncToInt(Scene.DebugInput.LocalY / (float)TextureMapping->CachedSizeY * TextureMapping->SurfaceCacheSizeY); } #endif RasterizeToSurfaceCacheTextureMapping(TextureMapping, bDebugThisMapping, TexelToVertexMap); if (ImportanceTracingSettings.bCacheFinalGatherHitPointsForRadiosity) { RadiosityIterationCachedHitpointsTextureMapping(TexelToVertexMap, TextureMapping, PassIndex); } else { const int32 SourceRadiosityBufferIndex = PassIndex % 2; const int32 DestRadiosityBufferIndex = 1 - SourceRadiosityBufferIndex; int32 NumAdaptiveRefinementLevels = PassIndex == 0 ? 1 : 0; if (GeneralSettings.IndirectLightingQuality > 10) { NumAdaptiveRefinementLevels++; } const int32 RadiositySampleSet = FMath::Min(PassIndex, UE_ARRAY_COUNT(CachedHemisphereSamplesForRadiosity) - 1); TLightingCache RadiosityCache(TextureMapping->Mesh->BoundingBox, *this, 1); FLMRandomStream RandomStream(0); for (int32 Y = 0; Y < TextureMapping->SurfaceCacheSizeY; Y++) { for (int32 X = 0; X < TextureMapping->SurfaceCacheSizeX; X++) { bool bDebugThisTexel = false; #if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING if (bDebugThisMapping && Y == TexelDebugY && X == TexelDebugX) { bDebugThisTexel = true; } #endif const FTexelToVertex& TexelToVertex = TexelToVertexMap(X,Y); if (TexelToVertex.TotalSampleWeight > 0.0f) { FFullStaticLightingVertex Vertex = TexelToVertex.GetFullVertex(); Vertex.ApplyVertexModifications(TexelToVertex.ElementIndex, MaterialSettings.bUseNormalMapsForLighting, TextureMapping->Mesh); FFinalGatherSample SkyLighting; FFinalGatherSample UnusedSecondLighting; float UnusedBackfacingHitsFraction; if (!RadiosityCache.InterpolateLighting(Vertex, true, false, 1.0f, SkyLighting, UnusedSecondLighting, UnusedBackfacingHitsFraction, MappingContext.DebugCacheRecords)) { FFinalGatherSample UniformSampledIncomingRadiance; //@todo - find and pass in photons from the appropriate bounce number to improve bUseRadiositySolverForLightMultibounce quality TArray ImportancePhotonDirections; FLightingCacheGatherInfo GatherInfo; // Gather previous iteration results (double buffer) EHemisphereGatherClassification GatherClassification = SourceRadiosityBufferIndex == 0 ? GLM_GatherRadiosityBuffer0 : GLM_GatherRadiosityBuffer1; UniformSampledIncomingRadiance = IncomingRadianceAdaptive( TextureMapping, Vertex, TexelToVertex.SampleRadius, false, TexelToVertex.ElementIndex, PassIndex + 3, /** BounceNumber */ RBM_ConstantNormalOffset, GatherClassification, NumAdaptiveRefinementLevels, 1.0f, CachedHemisphereSamplesForRadiosity[RadiositySampleSet], CachedHemisphereSamplesForRadiosityUniforms[RadiositySampleSet], 1, ImportancePhotonDirections, MappingContext, RandomStream, GatherInfo, true, /* bGatheringForCachedDirectLighting */ false); float OverrideRadius = 0; TLightingCache::FRecord NewRecord( Vertex, TexelToVertex.ElementIndex, GatherInfo, TexelToVertex.SampleRadius, OverrideRadius, IrradianceCachingSettings, GeneralSettings, UniformSampledIncomingRadiance, FVector4f(0, 0, 0, 0), FVector4f(0, 0, 0, 0) ); // Add the incident radiance sample to the cache. RadiosityCache.AddRecord(NewRecord, false, false); } } } } for (int32 Y = 0; Y < TextureMapping->SurfaceCacheSizeY; Y++) { for (int32 X = 0; X < TextureMapping->SurfaceCacheSizeX; X++) { bool bDebugThisTexel = false; #if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING if (bDebugThisMapping && Y == TexelDebugY && X == TexelDebugX) { bDebugThisTexel = true; } #endif const FTexelToVertex& TexelToVertex = TexelToVertexMap(X,Y); if (TexelToVertex.TotalSampleWeight > 0.0f) { FFullStaticLightingVertex CurrentVertex = TexelToVertex.GetFullVertex(); CurrentVertex.ApplyVertexModifications(TexelToVertex.ElementIndex, MaterialSettings.bUseNormalMapsForLighting, TextureMapping->Mesh); const int32 SurfaceCacheIndex = Y * TextureMapping->SurfaceCacheSizeX + X; FFinalGatherSample IterationLighting; FFinalGatherSample UnusedSecondLighting; float UnusedBackfacingHitsFraction; RadiosityCache.InterpolateLighting(CurrentVertex, false, false, IrradianceCachingSettings.SkyOcclusionSmoothnessReduction, IterationLighting, UnusedSecondLighting, UnusedBackfacingHitsFraction, MappingContext.DebugCacheRecords); const bool bIsTranslucent = TextureMapping->Mesh->IsTranslucent(TexelToVertex.ElementIndex); const FLinearColor Reflectance = (bIsTranslucent ? FLinearColor::Black : TextureMapping->Mesh->EvaluateTotalReflectance(CurrentVertex, TexelToVertex.ElementIndex)) * (float)INV_PI; const FLinearColor IterationRadiosity = IterationLighting.IncidentLighting * Reflectance; if (GeneralSettings.ViewSingleBounceNumber < 0 || GeneralSettings.ViewSingleBounceNumber == PassIndex + 2) { // Accumulate this bounce's lighting TextureMapping->SurfaceCacheLighting[SurfaceCacheIndex] += IterationRadiosity; } // Store in one of the radiosity buffers for the next iteration TextureMapping->RadiositySurfaceCache[DestRadiosityBufferIndex][SurfaceCacheIndex] = IterationRadiosity; } } } } } void FStaticLightingSystem::RadiosityIterationCachedHitpointsTextureMapping(const FTexelToVertexMap& TexelToVertexMap, FStaticLightingTextureMapping* TextureMapping, int32 PassIndex) { const int32 SourceRadiosityBufferIndex = PassIndex % 2; const int32 DestRadiosityBufferIndex = 1 - SourceRadiosityBufferIndex; FGatherHitPoints LocalGatherHitPoints; if (bCompressRadiosityCachedData) { TextureMapping->CompressedGatherHitPoints.Decompress(LocalGatherHitPoints); } const FGatherHitPoints& GatherHitPoints = bCompressRadiosityCachedData ? LocalGatherHitPoints : TextureMapping->UncompressedGatherHitPoints; TArray IterationRecordRadiosity; IterationRecordRadiosity.Empty(GatherHitPoints.GatherHitPointRanges.Num()); IterationRecordRadiosity.AddUninitialized(GatherHitPoints.GatherHitPointRanges.Num()); for (int32 LightingCacheRecordIndex = 0; LightingCacheRecordIndex < GatherHitPoints.GatherHitPointRanges.Num(); LightingCacheRecordIndex++) { FLinearColor NewRadiosity = FLinearColor::Black; const FArrayRange& HitPointRange = GatherHitPoints.GatherHitPointRanges[LightingCacheRecordIndex]; for (int32 HitPointIndex = HitPointRange.StartIndex; HitPointIndex < HitPointRange.StartIndex + HitPointRange.NumEntries; HitPointIndex++) { const FFinalGatherHitPoint& HitPoint = GatherHitPoints.GatherHitPointData[HitPointIndex]; const FStaticLightingMapping* HitTextureMapping = AllMappings[HitPoint.MappingIndex]; const FLinearColor IncomingRadiance = HitTextureMapping->GetCachedRadiosity(SourceRadiosityBufferIndex, HitPoint.MappingSurfaceCoordinate); NewRadiosity += IncomingRadiance * HitPoint.Weight.GetFloat(); } IterationRecordRadiosity[LightingCacheRecordIndex] = NewRadiosity; } FInfluencingRecords LocalInfluencingRecords; if (bCompressRadiosityCachedData) { TextureMapping->CompressedInfluencingRecords.Decompress(LocalInfluencingRecords); } const FInfluencingRecords& InfluencingRecords = bCompressRadiosityCachedData ? LocalInfluencingRecords : TextureMapping->InfluencingRecordsSurfaceCache; for (int32 Y = 0; Y < TextureMapping->SurfaceCacheSizeY; Y++) { for (int32 X = 0; X < TextureMapping->SurfaceCacheSizeX; X++) { const FTexelToVertex& TexelToVertex = TexelToVertexMap(X, Y); if (TexelToVertex.TotalSampleWeight > 0.0f) { FFullStaticLightingVertex CurrentVertex = TexelToVertex.GetFullVertex(); CurrentVertex.ApplyVertexModifications(TexelToVertex.ElementIndex, MaterialSettings.bUseNormalMapsForLighting, TextureMapping->Mesh); const int32 SurfaceCacheIndex = Y * TextureMapping->SurfaceCacheSizeX + X; FArrayRange Range = InfluencingRecords.Ranges[SurfaceCacheIndex]; float TotalWeight = 0.0f; FLinearColor AccumulatedRadiosity = FLinearColor::Black; for (int32 InfluenceIndex = Range.StartIndex; InfluenceIndex < Range.StartIndex + Range.NumEntries; InfluenceIndex++) { FInfluencingRecord InfluencingRecord = InfluencingRecords.Data[InfluenceIndex]; float RecordWeight = InfluencingRecord.RecordWeight; AccumulatedRadiosity += IterationRecordRadiosity[InfluencingRecord.RecordIndex] * RecordWeight; TotalWeight += RecordWeight; } const bool bIsTranslucent = TextureMapping->Mesh->IsTranslucent(TexelToVertex.ElementIndex); const FLinearColor DiffuseReflectance = (bIsTranslucent ? FLinearColor::Black : TextureMapping->Mesh->EvaluateTotalReflectance(CurrentVertex, TexelToVertex.ElementIndex)) * (float)INV_PI; checkSlow(TotalWeight > 0.0f); FLinearColor IterationRadiosity = (AccumulatedRadiosity / TotalWeight) * DiffuseReflectance; if (GeneralSettings.ViewSingleBounceNumber < 0 || GeneralSettings.ViewSingleBounceNumber == PassIndex + 2) { // Accumulate this bounce's lighting TextureMapping->SurfaceCacheLighting[SurfaceCacheIndex] += IterationRadiosity; } // Store in one of the radiosity buffers for the next iteration TextureMapping->RadiositySurfaceCache[DestRadiosityBufferIndex][SurfaceCacheIndex] = IterationRadiosity; } } } } size_t FStaticLightingMapping::FreeRadiosityTemporaries() { size_t RadiosityTemporariesSize = 0; RadiosityTemporariesSize += RadiositySurfaceCache[0].GetAllocatedSize() + RadiositySurfaceCache[1].GetAllocatedSize(); RadiosityTemporariesSize += CompressedGatherHitPoints.GetAllocatedSize() + UncompressedGatherHitPoints.GetAllocatedSize(); RadiosityTemporariesSize += CompressedInfluencingRecords.GetAllocatedSize() + InfluencingRecordsSurfaceCache.GetAllocatedSize(); RadiositySurfaceCache[0].Empty(); RadiositySurfaceCache[1].Empty(); CompressedGatherHitPoints = FCompressedGatherHitPoints(); UncompressedGatherHitPoints = FGatherHitPoints(); CompressedInfluencingRecords = FCompressedInfluencingRecords(); InfluencingRecordsSurfaceCache = FInfluencingRecords(); return RadiosityTemporariesSize; } }