Files
UnrealEngine/Engine/Source/Programs/UnrealLightmass/Private/Lighting/PhotonMapping.cpp
2025-05-18 13:04:45 +08:00

2499 lines
112 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "LightingSystem.h"
#include "HAL/RunnableThread.h"
#include "MonteCarlo.h"
#include "LightmassSwarm.h"
#include "HAL/ExceptionHandling.h"
namespace Lightmass
{
/** Average fraction of emitted direct photons that get deposited on surfaces, used to presize gathered photon arrays. */
static const float DirectPhotonEfficiency = .3f;
/** Average fraction of emitted indirect photons that get deposited on surfaces, used to presize gathered photon arrays. */
static const float IndirectPhotonEfficiency = .1f;
// Number of parts that photon operating passes will be split into.
// Random number generators and other state will be seeded at the beginning of each work range,
// To ensure deterministic behavior regardless of how many threads are processing and what order operations happen within each thread.
static const int32 NumPhotonWorkRanges = 256;
/** Sets up photon mapping settings. */
void FStaticLightingSystem::InitializePhotonSettings()
{
const FBoxSphereBounds3f SceneBounds = FBoxSphereBounds3f(AggregateMesh->GetBounds());
const FBoxSphereBounds3f ImportanceBounds = GetImportanceBounds();
// Get direct photon counts from each light
#if !LIGHTMASS_DO_PROCESSING
const int32 MaxNumDirectPhotonsToEmit = 10;
#else
// Maximum number of direct photons to emit, used to cap the memory and processing time for a given build
//@todo - remove these clamps and come up with a robust solution for huge scenes
const int32 MaxNumDirectPhotonsToEmit = 40000000;
#endif
NumDirectPhotonsToEmit = 0;
Stats.NumFirstPassPhotonsRequested = 0;
// Only emit direct photons if they will be used for lighting
if (GeneralSettings.NumIndirectLightingBounces > 0 || PhotonMappingSettings.bVisualizeCachedApproximateDirectLighting)
{
for (int32 LightIndex = 0; LightIndex < Lights.Num(); LightIndex++)
{
Stats.NumFirstPassPhotonsRequested += Lights[LightIndex]->GetNumDirectPhotons(PhotonMappingSettings.DirectPhotonDensity);
}
NumDirectPhotonsToEmit = FMath::Min<uint64>(Stats.NumFirstPassPhotonsRequested, (uint64)MaxNumDirectPhotonsToEmit);
if (NumDirectPhotonsToEmit == MaxNumDirectPhotonsToEmit)
{
LogSolverMessage(FString::Printf(TEXT("Clamped the number of direct photons to emit to %.3f million, from %.3f million requested."), MaxNumDirectPhotonsToEmit / 1000000.0f, Stats.NumFirstPassPhotonsRequested / 1000000.0f));
}
}
DirectIrradiancePhotonFraction = FMath::Clamp(Scene.PhotonMappingSettings.DirectIrradiancePhotonDensity / Scene.PhotonMappingSettings.DirectPhotonDensity, 0.0f, 1.0f);
// Calculate numbers of photons to gather based on the scene using the given photon densities, the scene's surface area and the importance volume's surface area
float SceneSurfaceAreaMillionUnits = FMath::Max(AggregateMesh->GetSurfaceArea() / 1000000.0f, DELTA);
float SceneSurfaceAreaMillionUnitsEstimate = FMath::Max(4.0f * (float)PI * SceneBounds.SphereRadius * SceneBounds.SphereRadius / 1000000.0f, DELTA);
float SceneSurfaceAreaMillionUnitsEstimateDiff = SceneSurfaceAreaMillionUnitsEstimate > DELTA ? ( SceneSurfaceAreaMillionUnits / SceneSurfaceAreaMillionUnitsEstimate * 100.0f ) : 0.0f;
LogSolverMessage(FString::Printf(TEXT("Scene surface area calculated at %.3f million units (%.3f%% of the estimated %.3f million units)"), SceneSurfaceAreaMillionUnits, SceneSurfaceAreaMillionUnitsEstimateDiff, SceneSurfaceAreaMillionUnitsEstimate));
float ImportanceSurfaceAreaMillionUnits = FMath::Max(AggregateMesh->GetSurfaceAreaWithinImportanceVolume() / 1000000.0f, DELTA);
float ImportanceSurfaceAreaMillionUnitsEstimate = FMath::Max(4.0f * (float)PI * ImportanceBounds.SphereRadius * ImportanceBounds.SphereRadius / 1000000.0f, DELTA);
float ImportanceSurfaceAreaMillionUnitsEstimateDiff = ImportanceSurfaceAreaMillionUnitsEstimate > DELTA ? ( ImportanceSurfaceAreaMillionUnits / ImportanceSurfaceAreaMillionUnitsEstimate * 100.0f ) : 0.0f;
LogSolverMessage(FString::Printf(TEXT("Importance volume surface area calculated at %.3f million units (%.3f%% of the estimated %.3f million units)"), ImportanceSurfaceAreaMillionUnits, ImportanceSurfaceAreaMillionUnitsEstimateDiff, ImportanceSurfaceAreaMillionUnitsEstimate));
#if !LIGHTMASS_DO_PROCESSING
const int32 MaxNumIndirectPhotonPaths = 10;
#else
const int32 MaxNumIndirectPhotonPaths = 20000;
#endif
// If the importance volume is valid, only gather enough indirect photon paths to meet IndirectPhotonPathDensity inside the importance volume
if (!PhotonMappingSettings.bEmitPhotonsOutsideImportanceVolume && ImportanceBounds.SphereRadius > DELTA)
{
NumIndirectPhotonPaths = FMath::TruncToInt(Scene.PhotonMappingSettings.IndirectPhotonPathDensity * ImportanceSurfaceAreaMillionUnits);
}
else if (ImportanceBounds.SphereRadius > DELTA)
{
NumIndirectPhotonPaths = FMath::TruncToInt(Scene.PhotonMappingSettings.IndirectPhotonPathDensity * ImportanceSurfaceAreaMillionUnits
+ Scene.PhotonMappingSettings.OutsideImportanceVolumeDensityScale * Scene.PhotonMappingSettings.IndirectPhotonPathDensity * SceneSurfaceAreaMillionUnits);
}
else
{
NumIndirectPhotonPaths = FMath::TruncToInt(Scene.PhotonMappingSettings.IndirectPhotonPathDensity * SceneSurfaceAreaMillionUnits);
}
NumIndirectPhotonPaths = NumIndirectPhotonPaths == appTruncErrorCode ? MaxNumIndirectPhotonPaths : NumIndirectPhotonPaths;
NumIndirectPhotonPaths = FMath::Min(NumIndirectPhotonPaths, MaxNumIndirectPhotonPaths);
if (NumIndirectPhotonPaths == MaxNumIndirectPhotonPaths)
{
LogSolverMessage(FString::Printf(TEXT("Clamped the number of indirect photon paths to %u."), MaxNumIndirectPhotonPaths));
}
#if !LIGHTMASS_DO_PROCESSING
const int32 MaxNumIndirectPhotons = 10;
#else
const int32 MaxNumIndirectPhotons = 40000000;
#endif
Stats.NumSecondPassPhotonsRequested = 0;
// If the importance volume is valid, only emit enough indirect photons to meet IndirectPhotonDensity inside the importance volume
if (!PhotonMappingSettings.bEmitPhotonsOutsideImportanceVolume && ImportanceBounds.SphereRadius > DELTA)
{
Stats.NumSecondPassPhotonsRequested = Scene.PhotonMappingSettings.IndirectPhotonDensity * ImportanceSurfaceAreaMillionUnits;
}
else if (ImportanceBounds.SphereRadius > DELTA)
{
Stats.NumSecondPassPhotonsRequested = Scene.PhotonMappingSettings.IndirectPhotonDensity * ImportanceSurfaceAreaMillionUnits
+ Scene.PhotonMappingSettings.OutsideImportanceVolumeDensityScale * Scene.PhotonMappingSettings.IndirectPhotonDensity * SceneSurfaceAreaMillionUnits;
}
else
{
Stats.NumSecondPassPhotonsRequested = Scene.PhotonMappingSettings.IndirectPhotonDensity * SceneSurfaceAreaMillionUnits;
}
NumIndirectPhotonsToEmit = FMath::Min<uint64>(Stats.NumSecondPassPhotonsRequested, (uint64)MaxNumIndirectPhotons);
if (NumIndirectPhotonsToEmit == MaxNumIndirectPhotons)
{
LogSolverMessage(FString::Printf(TEXT("Clamped the number of indirect photons to emit to %.3f million, from %.3f million requested."), MaxNumIndirectPhotons / 1000000.0f, Stats.NumSecondPassPhotonsRequested / 1000000.0f));
}
IndirectIrradiancePhotonFraction = FMath::Clamp(Scene.PhotonMappingSettings.IndirectIrradiancePhotonDensity / Scene.PhotonMappingSettings.IndirectPhotonDensity, 0.0f, 1.0f);
}
/** Emits photons, builds data structures to accelerate photon map lookups, and does any other photon preprocessing required. */
void FStaticLightingSystem::EmitPhotons()
{
const FBoxSphereBounds3f SceneSphereBounds = FBoxSphereBounds3f(AggregateMesh->GetBounds());
FBoxSphereBounds3f ImportanceVolumeBounds = GetImportanceBounds();
if (ImportanceVolumeBounds.SphereRadius < DELTA)
{
ImportanceVolumeBounds = SceneSphereBounds;
}
// Presize for the results from two emitting passes (direct photon emitting, then indirect)
mIrradiancePhotons.Empty(NumPhotonWorkRanges * 2);
const double StartEmitDirectTime = FPlatformTime::Seconds();
TArray<TArray<FIndirectPathRay> > IndirectPathRays;
// Emit photons for the direct photon map, and gather rays which resulted in indirect photon paths.
EmitDirectPhotons(ImportanceVolumeBounds, IndirectPathRays, mIrradiancePhotons);
const double EndEmitDirectTime = FPlatformTime::Seconds();
Stats.EmitDirectPhotonsTime = EndEmitDirectTime - StartEmitDirectTime;
LogSolverMessage(FString::Printf(TEXT("EmitDirectPhotons complete, %.3f million photons emitted in %.1f seconds"), Stats.NumFirstPassPhotonsEmitted / 1000000.0f, Stats.EmitDirectPhotonsTime));
// Let the scene's lights cache information about the indirect path rays,
// Which will be used to accelerate light sampling using those paths when emitting indirect photons.
for (int32 LightIndex = 0; LightIndex < Lights.Num(); LightIndex++)
{
FLight* CurrentLight = Lights[LightIndex];
CurrentLight->CachePathRays(IndirectPathRays[LightIndex]);
}
const double EndCachingIndirectPathsTime = FPlatformTime::Seconds();
Stats.CachingIndirectPhotonPathsTime = EndCachingIndirectPathsTime - EndEmitDirectTime;
// Emit photons for the indirect photon map, using the indirect photon paths to guide photon emission.
EmitIndirectPhotons(ImportanceVolumeBounds, IndirectPathRays, mIrradiancePhotons);
const double EndEmitIndirectTime = FPlatformTime::Seconds();
Stats.EmitIndirectPhotonsTime = EndEmitIndirectTime - EndCachingIndirectPathsTime;
LogSolverMessage(FString::Printf(TEXT("EmitIndirectPhotons complete, %.3f million photons emitted in %.1f seconds"), Stats.NumSecondPassPhotonsEmitted / 1000000.0f, Stats.EmitIndirectPhotonsTime));
if (PhotonMappingSettings.bUseIrradiancePhotons)
{
// Process all irradiance photons and mark ones that have direct photons nearby,
// So that we can search for those with a smaller radius when using them for rendering.
// This allows more accurate direct shadow transitions with irradiance photons.
MarkIrradiancePhotons(ImportanceVolumeBounds, mIrradiancePhotons);
const double EndMarkIrradiancePhotonsTime = FPlatformTime::Seconds();
Stats.IrradiancePhotonMarkingTime = EndMarkIrradiancePhotonsTime - EndEmitIndirectTime;
LogSolverMessage(FString::Printf(TEXT("Marking Irradiance Photons complete, %.3f million photons marked in %.1f seconds"), Stats.NumIrradiancePhotons / 1000000.0f, Stats.IrradiancePhotonMarkingTime));
if (PhotonMappingSettings.bCacheIrradiancePhotonsOnSurfaces)
{
// Cache irradiance photons on surfaces, as an optimization for final gathering.
// Final gather rays already know which surface they intersected, so we can do a constant time lookup to find
// The nearest irradiance photon instead of doing a photon map gather at the end of each final gather ray.
// As an additional benefit, only cached irradiance photons are actually used for rendering, so we only need to calculate irradiance for the used ones.
CacheIrradiancePhotons();
Stats.CacheIrradiancePhotonsTime = FPlatformTime::Seconds() - EndMarkIrradiancePhotonsTime;
LogSolverMessage(FString::Printf(TEXT("Caching Irradiance Photons complete, %.3f million cache samples in %.1f seconds"), Stats.NumCachedIrradianceSamples / 1000000.0f, Stats.CacheIrradiancePhotonsTime));
}
// Calculate irradiance for photons found by the caching on surfaces pass.
// This is done as an optimization to final gathering, as described in the paper titled "Faster Photon Map Global Illumination"
// The optimization is that irradiance is pre-calculated at a subset of the photons so that final gather rays can just lookup the nearest irradiance photon,
// Instead of doing a photon density estimation to calculate irradiance.
const double StartCalculateIrradiancePhotonsTime = FPlatformTime::Seconds();
CalculateIrradiancePhotons(ImportanceVolumeBounds, mIrradiancePhotons);
Stats.IrradiancePhotonCalculatingTime = FPlatformTime::Seconds() - StartCalculateIrradiancePhotonsTime;
LogSolverMessage(FString::Printf(TEXT("Calculate Irradiance Photons complete, %.3f million irradiance calculations in %.1f seconds"), Stats.NumFoundIrradiancePhotons / 1000000.0f, Stats.IrradiancePhotonCalculatingTime));
}
// Verify that temporary photon memory has been freed
check(DirectPhotonEmittingWorkRanges.Num() == 0
&& DirectPhotonEmittingOutputs.Num() == 0
&& IndirectPhotonEmittingWorkRanges.Num() == 0
&& IndirectPhotonEmittingOutputs.Num() == 0
&& IrradianceMarkWorkRanges.Num() == 0
&& IrradianceCalculationWorkRanges.Num() == 0
&& IrradiancePhotonCachingThreads.Num() == 0);
}
/** Emits direct photons and generates indirect photon paths. */
void FStaticLightingSystem::EmitDirectPhotons(
const FBoxSphereBounds3f& ImportanceBounds,
TArray<TArray<FIndirectPathRay> >& IndirectPathRays,
TArray<TArray<FIrradiancePhoton>>& IrradiancePhotons)
{
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing0, 0 ) );
FSceneLightPowerDistribution LightDistribution;
// Create a 1d Step Probability Density Function based on the number of direct photons each light wants to gather.
LightDistribution.LightPDFs.Empty(Lights.Num());
for (int32 LightIndex = 0; LightIndex < Lights.Num(); LightIndex++)
{
const FLight* CurrentLight = Lights[LightIndex];
const int32 LightNumDirectPhotons = CurrentLight->GetNumDirectPhotons(PhotonMappingSettings.DirectPhotonDensity);
LightDistribution.LightPDFs.Add(LightNumDirectPhotons);
}
if (Lights.Num() > 0)
{
// Compute the Cumulative Distribution Function for our step function of light powers
CalculateStep1dCDF(LightDistribution.LightPDFs, LightDistribution.LightCDFs, LightDistribution.UnnormalizedIntegral);
}
IndirectPathRays.Empty(Lights.Num());
IndirectPathRays.AddZeroed(Lights.Num());
// Add irradiance photon array entries for all the work ranges that will be processed
const int32 IrradianceArrayStart = IrradiancePhotons.AddZeroed(NumPhotonWorkRanges);
const FDirectPhotonEmittingInput Input(ImportanceBounds, LightDistribution);
int32 NumIndirectPhotonPathsRemaining = NumIndirectPhotonPaths;
// Setup work ranges, which are sections of work that can be done in parallel.
DirectPhotonEmittingWorkRanges.Empty(NumPhotonWorkRanges);
for (int32 RangeIndex = 0; RangeIndex < NumPhotonWorkRanges - 1; RangeIndex++)
{
int32 NumIndirectPhotonPathsRange = FMath::Max(NumIndirectPhotonPaths / NumPhotonWorkRanges, 1);
if (NumIndirectPhotonPathsRemaining == 0)
{
NumIndirectPhotonPathsRange = 0;
}
NumIndirectPhotonPathsRemaining = FMath::Max(NumIndirectPhotonPathsRemaining - NumIndirectPhotonPathsRange, 0);
DirectPhotonEmittingWorkRanges.Add(FDirectPhotonEmittingWorkRange(RangeIndex, NumDirectPhotonsToEmit / NumPhotonWorkRanges, NumIndirectPhotonPathsRange));
}
// The last work range contains the remainders
DirectPhotonEmittingWorkRanges.Add(FDirectPhotonEmittingWorkRange(
NumPhotonWorkRanges - 1,
NumDirectPhotonsToEmit / NumPhotonWorkRanges + NumDirectPhotonsToEmit % NumPhotonWorkRanges,
NumIndirectPhotonPathsRemaining));
DirectPhotonEmittingOutputs.Empty(NumPhotonWorkRanges);
for (int32 RangeIndex = 0; RangeIndex < NumPhotonWorkRanges; RangeIndex++)
{
// Initialize outputs with the appropriate irradiance photon array
DirectPhotonEmittingOutputs.Add(FDirectPhotonEmittingOutput(&IrradiancePhotons[IrradianceArrayStart + RangeIndex]));
}
// Spawn threads to emit direct photons
TIndirectArray<FDirectPhotonEmittingThreadRunnable> DirectPhotonEmittingThreads;
for (int32 ThreadIndex = 1; ThreadIndex < NumStaticLightingThreads; ThreadIndex++)
{
FDirectPhotonEmittingThreadRunnable* ThreadRunnable = new FDirectPhotonEmittingThreadRunnable(this, ThreadIndex, Input);
DirectPhotonEmittingThreads.Add(ThreadRunnable);
const FString ThreadName = FString::Printf(TEXT("DirectPhotonEmittingThread%u"), ThreadIndex);
ThreadRunnable->Thread = FRunnableThread::Create(ThreadRunnable, *ThreadName);
}
const double StartEmittingDirectPhotonsMainThread = FPlatformTime::Seconds();
// Add the photons into a spatial data structure to accelerate their searches later
//@todo - should try a kd-tree instead as the distribution of photons is highly non-uniform
DirectPhotonMap = FPhotonOctree(ImportanceBounds.Origin, ImportanceBounds.BoxExtent.GetMax());
IrradiancePhotonMap = FIrradiancePhotonOctree(ImportanceBounds.Origin, ImportanceBounds.BoxExtent.GetMax());
Stats.NumDirectPhotonsGathered = 0;
Stats.NumDirectIrradiancePhotons = 0;
int32 NumIndirectPhotonPathsGathered = 0;
int32 NextOutputToProcess = 0;
while (DirectPhotonEmittingWorkRangeIndex.GetValue() < DirectPhotonEmittingWorkRanges.Num()
|| NextOutputToProcess < DirectPhotonEmittingOutputs.Num())
{
// Process one work range on the main thread
EmitDirectPhotonsThreadLoop(Input, 0);
LIGHTINGSTAT(FScopedRDTSCTimer MainThreadProcessTimer(Stats.ProcessDirectPhotonsThreadTime));
// Process the outputs that have been completed by any thread
// Outputs are collected from smallest to largest work range index so that the outputs will be deterministic.
while (NextOutputToProcess < DirectPhotonEmittingOutputs.Num()
&& DirectPhotonEmittingOutputs[NextOutputToProcess].OutputComplete > 0)
{
const FDirectPhotonEmittingOutput& CurrentOutput = DirectPhotonEmittingOutputs[NextOutputToProcess];
for (int32 PhotonIndex = 0; PhotonIndex < CurrentOutput.DirectPhotons.Num(); PhotonIndex++)
{
// Add direct photons to the direct photon map, which is an octree for now
DirectPhotonMap.AddElement(FPhotonElement(CurrentOutput.DirectPhotons[PhotonIndex]));
}
for (int32 LightIndex = 0; LightIndex < CurrentOutput.IndirectPathRays.Num(); LightIndex++)
{
// Gather indirect path rays
IndirectPathRays[LightIndex].Append(CurrentOutput.IndirectPathRays[LightIndex]);
NumIndirectPhotonPathsGathered += CurrentOutput.IndirectPathRays[LightIndex].Num();
}
if (PhotonMappingSettings.bUseIrradiancePhotons && PhotonMappingSettings.bUsePhotonDirectLightingInFinalGather)
{
for (int32 PhotonIndex = 0; PhotonIndex < CurrentOutput.IrradiancePhotons->Num(); PhotonIndex++)
{
// Add the irradiance photons to an octree
IrradiancePhotonMap.AddElement(FIrradiancePhotonElement(PhotonIndex, *CurrentOutput.IrradiancePhotons));
}
Stats.NumIrradiancePhotons += CurrentOutput.IrradiancePhotons->Num();
Stats.NumDirectIrradiancePhotons += CurrentOutput.IrradiancePhotons->Num();
}
Stats.NumFirstPassPhotonsEmitted += CurrentOutput.NumPhotonsEmitted;
NumPhotonsEmittedDirect += CurrentOutput.NumPhotonsEmittedDirect;
Stats.NumDirectPhotonsGathered += CurrentOutput.DirectPhotons.Num();
NextOutputToProcess++;
Stats.DirectPhotonsTracingThreadTime += CurrentOutput.DirectPhotonsTracingThreadTime;
Stats.DirectPhotonsLightSamplingThreadTime += CurrentOutput.DirectPhotonsLightSamplingThreadTime;
Stats.DirectCustomAttenuationThreadTime += CurrentOutput.DirectCustomAttenuationThreadTime;
}
}
Stats.EmitDirectPhotonsThreadTime = FPlatformTime::Seconds() - StartEmittingDirectPhotonsMainThread;
// Wait until all worker threads have completed
for (int32 ThreadIndex = 0; ThreadIndex < DirectPhotonEmittingThreads.Num(); ThreadIndex++)
{
DirectPhotonEmittingThreads[ThreadIndex].Thread->WaitForCompletion();
DirectPhotonEmittingThreads[ThreadIndex].CheckHealth();
delete DirectPhotonEmittingThreads[ThreadIndex].Thread;
DirectPhotonEmittingThreads[ThreadIndex].Thread = NULL;
Stats.EmitDirectPhotonsThreadTime += DirectPhotonEmittingThreads[ThreadIndex].ExecutionTime;
}
if (NumIndirectPhotonPathsGathered != NumIndirectPhotonPaths && GeneralSettings.NumIndirectLightingBounces > 0)
{
LogSolverMessage(FString::Printf(TEXT("Couldn't gather the requested number of indirect photon paths! %u gathered"), NumIndirectPhotonPathsGathered));
}
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (PhotonMappingSettings.bVisualizePhotonPaths)
{
if (GeneralSettings.ViewSingleBounceNumber < 0
|| PhotonMappingSettings.bVisualizeCachedApproximateDirectLighting && GeneralSettings.ViewSingleBounceNumber == 0
|| PhotonMappingSettings.bUseFinalGathering && GeneralSettings.ViewSingleBounceNumber == 1)
{
if (PhotonMappingSettings.bUseIrradiancePhotons)
{
int32 NumDirectIrradiancePhotons = 0;
for (int32 ArrayIndex = 0; ArrayIndex < IrradiancePhotons.Num(); ArrayIndex++)
{
NumDirectIrradiancePhotons += IrradiancePhotons[ArrayIndex].Num();
}
StartupDebugOutput.IrradiancePhotons.Empty(NumDirectIrradiancePhotons);
for (int32 ArrayIndex = 0; ArrayIndex < IrradiancePhotons.Num(); ArrayIndex++)
{
for (int32 i = 0; i < IrradiancePhotons[ArrayIndex].Num(); i++)
{
StartupDebugOutput.IrradiancePhotons.Add(FDebugPhoton(0, IrradiancePhotons[ArrayIndex][i].GetPosition(), IrradiancePhotons[ArrayIndex][i].GetSurfaceNormal(), IrradiancePhotons[ArrayIndex][i].GetSurfaceNormal()));
}
}
}
else
{
StartupDebugOutput.DirectPhotons.Empty(Stats.NumDirectPhotonsGathered);
for (int32 OutputIndex = 0; OutputIndex < DirectPhotonEmittingOutputs.Num(); OutputIndex++)
{
const FDirectPhotonEmittingOutput& CurrentOutput = DirectPhotonEmittingOutputs[OutputIndex];
for (int32 i = 0; i < CurrentOutput.DirectPhotons.Num(); i++)
{
StartupDebugOutput.DirectPhotons.Add(FDebugPhoton(CurrentOutput.DirectPhotons[i].GetId(), CurrentOutput.DirectPhotons[i].GetPosition(), CurrentOutput.DirectPhotons[i].GetIncidentDirection(), CurrentOutput.DirectPhotons[i].GetSurfaceNormal()));
}
}
}
}
if (GeneralSettings.ViewSingleBounceNumber != 0)
{
StartupDebugOutput.IndirectPhotonPaths.Empty(NumIndirectPhotonPathsGathered);
for (int32 LightIndex = 0; LightIndex < IndirectPathRays.Num(); LightIndex++)
{
for (int32 RayIndex = 0; RayIndex < IndirectPathRays[LightIndex].Num(); RayIndex++)
{
StartupDebugOutput.IndirectPhotonPaths.Add(FDebugStaticLightingRay(
IndirectPathRays[LightIndex][RayIndex].Start,
IndirectPathRays[LightIndex][RayIndex].Start + IndirectPathRays[LightIndex][RayIndex].UnitDirection * IndirectPathRays[LightIndex][RayIndex].Length,
true));
}
}
}
}
#endif
DirectPhotonEmittingWorkRanges.Empty();
DirectPhotonEmittingOutputs.Empty();
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing0, 0 ) );
}
uint32 FDirectPhotonEmittingThreadRunnable::Run()
{
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing0, ThreadIndex ) );
const double StartThreadTime = FPlatformTime::Seconds();
FixThreadGroupAffinity();
#if defined(_MSC_VER) && !defined(XBOX)
if(!FPlatformMisc::IsDebuggerPresent())
{
__try
{
System->EmitDirectPhotonsThreadLoop(Input, ThreadIndex);
}
__except( ReportCrash( GetExceptionInformation() ) )
{
ErrorMessage = GErrorHist;
bTerminatedByError = true;
}
}
else
#endif
{
System->EmitDirectPhotonsThreadLoop(Input, ThreadIndex);
}
ExecutionTime = FPlatformTime::Seconds() - StartThreadTime;
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing0, ThreadIndex ) );
return 0;
}
/** Entrypoint for all threads emitting direct photons. */
void FStaticLightingSystem::EmitDirectPhotonsThreadLoop(const FDirectPhotonEmittingInput& Input, int32 ThreadIndex)
{
while (true)
{
// Atomically read and increment the next work range index to process.
// In this way work ranges are processed on-demand, which ensures consistent end times between threads.
// Processing from smallest to largest work range index since the main thread is processing outputs in the same order.
const int32 RangeIndex = DirectPhotonEmittingWorkRangeIndex.Increment() - 1;
if (RangeIndex < DirectPhotonEmittingWorkRanges.Num())
{
EmitDirectPhotonsWorkRange(Input, DirectPhotonEmittingWorkRanges[RangeIndex], DirectPhotonEmittingOutputs[RangeIndex]);
if (ThreadIndex == 0)
{
// Break out of the loop on the main thread after one work range so that it can process any outputs that are ready
break;
}
}
else
{
// Processing has begun for all work ranges
break;
}
}
}
/** Emits direct photons for a given work range. */
void FStaticLightingSystem::EmitDirectPhotonsWorkRange(
const FDirectPhotonEmittingInput& Input,
FDirectPhotonEmittingWorkRange WorkRange,
FDirectPhotonEmittingOutput& Output)
{
// No lights in the scene, so no photons to emit
if (Lights.Num() == 0
// No light power in the scene, so no photons to shoot
|| Input.LightDistribution.UnnormalizedIntegral < DELTA)
{
// Indicate to the main thread that this output is ready for processing
FPlatformAtomics::InterlockedIncrement(&DirectPhotonEmittingOutputs[WorkRange.RangeIndex].OutputComplete);
return;
}
Output.IndirectPathRays.Empty(Lights.Num());
Output.IndirectPathRays.AddZeroed(Lights.Num());
for (int32 LightIndex = 0; LightIndex < Output.IndirectPathRays.Num(); LightIndex++)
{
Output.IndirectPathRays[LightIndex].Empty(WorkRange.TargetNumIndirectPhotonPaths);
}
if (PhotonMappingSettings.bUseIrradiancePhotons)
{
// Attempt to preallocate irradiance photons based on the percentage of photons that go into the irradiance photon map.
// The actual number of irradiance photons is based on probability.
Output.IrradiancePhotons->Empty(FMath::TruncToInt(DirectIrradiancePhotonFraction * DirectPhotonEfficiency * WorkRange.NumDirectPhotonsToEmit));
}
FCoherentRayCache CoherentRayCache;
// Initialize the random stream using the work range's index,
// So that different numbers will be generated for each work range,
// While maintaining determinism regardless of the order that work ranges are processed.
FLMRandomStream RandomStream(WorkRange.RangeIndex);
// Array of rays from each light which resulted in an indirect path.
// These are used in the second emitting pass to guide light sampling for indirect photons.
Output.DirectPhotons.Empty(FMath::TruncToInt(WorkRange.NumDirectPhotonsToEmit * DirectPhotonEfficiency));
Output.NumPhotonsEmitted = 0;
int32 NumIndirectPathRaysGathered = 0;
// Emit photons until we reach the limit for this work range,
while (Output.NumPhotonsEmitted < WorkRange.NumDirectPhotonsToEmit
// Or we haven't found enough indirect photon paths yet.
|| NumIndirectPathRaysGathered < WorkRange.TargetNumIndirectPhotonPaths)
{
Output.NumPhotonsEmitted++;
// Once we have emitted enough direct photons,
// Stop emitting photons if we are getting below 0.2% efficiency for indirect photon paths
// This can happen if the scene is close to convex
if (Output.NumPhotonsEmitted >= WorkRange.NumDirectPhotonsToEmit
&& NumIndirectPathRaysGathered < WorkRange.TargetNumIndirectPhotonPaths
&& Output.NumPhotonsEmitted > WorkRange.TargetNumIndirectPhotonPaths * 500.0f)
{
break;
}
int32 NumberOfPathVertices = 0;
float LightPDF;
float LightIndex;
// Pick a light with probability proportional to the light's fraction of the direct photons being gathered for the whole scene
Sample1dCDF(Input.LightDistribution.LightPDFs, Input.LightDistribution.LightCDFs, Input.LightDistribution.UnnormalizedIntegral, RandomStream, LightPDF, LightIndex);
const int32 QuantizedLightIndex = FMath::TruncToInt(LightIndex * Input.LightDistribution.LightPDFs.Num());
check(QuantizedLightIndex >= 0 && QuantizedLightIndex < Lights.Num());
const FLight* Light = Lights[QuantizedLightIndex];
FLightRay SampleRay;
FVector4f LightSourceNormal;
FVector2f LightSurfacePosition;
float RayDirectionPDF;
FLinearColor PathAlpha;
{
LIGHTINGSTAT(FScopedRDTSCTimer LightSampleTimer(Output.DirectPhotonsLightSamplingThreadTime));
// Generate the first ray for a new path from the light's distribution of emitted light
Light->SampleDirection(RandomStream, SampleRay, LightSourceNormal, LightSurfacePosition, RayDirectionPDF, PathAlpha);
}
// Update the path's throughput based on the probability of picking this light and this direction
PathAlpha = PathAlpha / (LightPDF * RayDirectionPDF);
if (PathAlpha.R <= 0.0f && PathAlpha.G <= 0.0f && PathAlpha.B <= 0.0f)
{
// Skip to next photon since the light doesn't emit any energy in this direction
continue;
}
const float BeforeDirectTraceTime = CoherentRayCache.FirstHitRayTraceTime;
// Find the first vertex of the photon path
FLightRayIntersection PathIntersection;
SampleRay.TraceFlags |= LIGHTRAY_FLIP_SIDEDNESS;
AggregateMesh->IntersectLightRay(SampleRay, true, true, true, CoherentRayCache, PathIntersection);
Output.DirectPhotonsTracingThreadTime += CoherentRayCache.FirstHitRayTraceTime - BeforeDirectTraceTime;
const FVector4f WorldPathDirection = SampleRay.Direction.GetUnsafeNormal3();
// Register this photon path as long as it hit a frontface of something in the scene
if (PathIntersection.bIntersects
&& Dot3(WorldPathDirection, PathIntersection.IntersectionVertex.WorldTangentZ) < 0.0f)
{
{
LIGHTINGSTAT(FScopedRDTSCTimer CustomAttenuationTimer(Output.DirectCustomAttenuationThreadTime));
// Allow the light to attenuate in a non-physically correct way
PathAlpha *= Light->CustomAttenuation(PathIntersection.IntersectionVertex.WorldPosition, RandomStream, true);
}
// Apply transmission
PathAlpha *= PathIntersection.Transmission;
if ((PathAlpha.R < DELTA && PathAlpha.G < DELTA && PathAlpha.B < DELTA)
// Ray can hit translucent meshes if they have bCastShadowAsMasked, but we don't have diffuse for translucency, so just terminate
|| PathIntersection.Mesh->IsTranslucent(PathIntersection.ElementIndex))
{
// Skip to the next photon since the path contribution was completely filtered out by transmission or attenuation
continue;
}
NumberOfPathVertices++;
// Note: SampleRay.Start is offset from the actual start position, but not enough to matter for the algorithms which make use of the photon's traveled distance.
const float RayLength = (SampleRay.Start - PathIntersection.IntersectionVertex.WorldPosition).Size3();
// Create a photon from this path vertex's information
const FPhoton NewPhoton(Output.NumPhotonsEmitted, PathIntersection.IntersectionVertex.WorldPosition, RayLength, -WorldPathDirection, PathIntersection.IntersectionVertex.WorldTangentZ, PathAlpha);
checkSlow(FLinearColorUtils::AreFloatsValid(PathAlpha));
if (Output.NumPhotonsEmitted < WorkRange.NumDirectPhotonsToEmit
// Only deposit photons inside the importance bounds
&& Input.ImportanceBounds.GetBox().IsInside(PathIntersection.IntersectionVertex.WorldPosition))
{
Output.DirectPhotons.Add(NewPhoton);
Output.NumPhotonsEmittedDirect = Output.NumPhotonsEmitted;
if (PhotonMappingSettings.bUseIrradiancePhotons
// Create an irradiance photon for a fraction of the direct photons
&& RandomStream.GetFraction() < DirectIrradiancePhotonFraction)
{
const FIrradiancePhoton NewIrradiancePhoton(PathIntersection.IntersectionVertex.WorldPosition, PathIntersection.IntersectionVertex.WorldTangentZ, true);
Output.IrradiancePhotons->Add(NewIrradiancePhoton);
}
}
// Continue tracing this path if we don't have enough indirect photon paths yet
if (NumIndirectPathRaysGathered < WorkRange.TargetNumIndirectPhotonPaths)
{
FStaticLightingVertex IntersectionVertexWithTangents(PathIntersection.IntersectionVertex);
FVector4f NewWorldPathDirection;
float BRDFDirectionPDF;
// Generate a new path direction from the BRDF
const FLinearColor BRDF = PathIntersection.Mesh->SampleBRDF(
IntersectionVertexWithTangents,
PathIntersection.ElementIndex,
-WorldPathDirection,
NewWorldPathDirection,
BRDFDirectionPDF,
RandomStream);
// Terminate if the path has lost all of its energy due to the surface's BRDF
if (BRDF.Equals(FLinearColor::Black))
{
continue;
}
const float CosFactor = -Dot3(WorldPathDirection, IntersectionVertexWithTangents.WorldTangentZ);
checkSlow(CosFactor >= 0.0f && CosFactor <= 1.0f);
const FVector4f RayStart = IntersectionVertexWithTangents.WorldPosition
+ NewWorldPathDirection * SceneConstants.VisibilityRayOffsetDistance
+ IntersectionVertexWithTangents.WorldTangentZ * SceneConstants.VisibilityNormalOffsetDistance;
const FVector4f RayEnd = IntersectionVertexWithTangents.WorldPosition + NewWorldPathDirection * MaxRayDistance;
FLightRay IndirectSampleRay(
RayStart,
RayEnd,
NULL,
NULL
);
const float BeforeIndirectTraceTime = CoherentRayCache.FirstHitRayTraceTime;
FLightRayIntersection NewPathIntersection;
IndirectSampleRay.TraceFlags |= LIGHTRAY_FLIP_SIDEDNESS;
AggregateMesh->IntersectLightRay(IndirectSampleRay, true, false, false, CoherentRayCache, NewPathIntersection);
Output.DirectPhotonsTracingThreadTime += CoherentRayCache.FirstHitRayTraceTime - BeforeIndirectTraceTime;
if (NewPathIntersection.bIntersects && Dot3(NewWorldPathDirection, NewPathIntersection.IntersectionVertex.WorldTangentZ) < 0.0f)
{
// Store the original photon path which led to an indirect photon path,
// This will be used in a second pass to guide indirect photon emission.
Output.IndirectPathRays[QuantizedLightIndex].Add(FIndirectPathRay(SampleRay.Start, WorldPathDirection, LightSourceNormal, LightSurfacePosition, RayLength));
NumIndirectPathRaysGathered++;
}
}
}
}
// Indicate to the main thread that this output is ready for processing
FPlatformAtomics::InterlockedIncrement(&DirectPhotonEmittingOutputs[WorkRange.RangeIndex].OutputComplete);
}
void FStaticLightingSystem::BuildPhotonSegmentMap(const FPhotonOctree& SourcePhotonMap, FPhotonSegmentOctree& OutPhotonSegementMap, float AddToSegmentMapChance)
{
FLMRandomStream RandomStream(12345);
for(FPhotonOctree::TConstIterator<> NodeIt(SourcePhotonMap); NodeIt.HasPendingNodes(); NodeIt.Advance())
{
const FPhotonOctree::FNode& CurrentNode = NodeIt.GetCurrentNode();
FOREACH_OCTREE_CHILD_NODE(ChildRef)
{
if (CurrentNode.HasChild(ChildRef))
{
NodeIt.PushChild(ChildRef);
}
}
for (FPhotonOctree::ElementConstIt ElementIt(CurrentNode.GetConstElementIt()); ElementIt; ++ElementIt)
{
const FPhotonElement& PhotonElement = *ElementIt;
if (AddToSegmentMapChance >= 1 || RandomStream.GetFraction() < AddToSegmentMapChance)
{
const int32 NumSegments = FMath::DivideAndRoundUp(PhotonElement.Photon.GetDistance(), PhotonMappingSettings.PhotonSegmentMaxLength);
const float InvNumSegments = 1.0f / NumSegments;
for (int32 SegmentIndex = 0; SegmentIndex < NumSegments; SegmentIndex++)
{
FPhotonSegmentElement NewElement(&PhotonElement.Photon, SegmentIndex * InvNumSegments, InvNumSegments);
OutPhotonSegementMap.AddElement(NewElement);
}
}
}
}
}
/** Gathers indirect photons based on the indirect photon paths. */
void FStaticLightingSystem::EmitIndirectPhotons(
const FBoxSphereBounds3f& ImportanceBounds,
const TArray<TArray<FIndirectPathRay> >& IndirectPathRays,
TArray<TArray<FIrradiancePhoton>>& IrradiancePhotons)
{
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing1, 0 ) );
FSceneLightPowerDistribution LightDistribution;
// Create a 1d Step Probability Density Function based on light powers,
// So that lights are chosen with a probability proportional to their fraction of the total light power in the scene.
LightDistribution.LightPDFs.Empty(Lights.Num());
for (int32 LightIndex = 0; LightIndex < Lights.Num(); LightIndex++)
{
const FLight* CurrentLight = Lights[LightIndex];
LightDistribution.LightPDFs.Add(CurrentLight->Power());
}
if (Lights.Num() > 0)
{
// Compute the Cumulative Distribution Function for our step function of light powers
CalculateStep1dCDF(LightDistribution.LightPDFs, LightDistribution.LightCDFs, LightDistribution.UnnormalizedIntegral);
}
// Add irradiance photon array entries for all the work ranges that will be processed
const int32 IndirectIrradianceArrayStart = IrradiancePhotons.AddZeroed(NumPhotonWorkRanges);
const FIndirectPhotonEmittingInput Input(ImportanceBounds, LightDistribution, IndirectPathRays);
// Setup work ranges, which are sections of work that can be done in parallel.
IndirectPhotonEmittingWorkRanges.Empty(NumPhotonWorkRanges);
for (int32 RangeIndex = 0; RangeIndex < NumPhotonWorkRanges - 1; RangeIndex++)
{
IndirectPhotonEmittingWorkRanges.Add(FIndirectPhotonEmittingWorkRange(RangeIndex, NumIndirectPhotonsToEmit / NumPhotonWorkRanges));
}
// The last work range will contain the remainder of photons
IndirectPhotonEmittingWorkRanges.Add(FIndirectPhotonEmittingWorkRange(
NumPhotonWorkRanges - 1,
NumIndirectPhotonsToEmit / NumPhotonWorkRanges + NumIndirectPhotonsToEmit % NumPhotonWorkRanges));
IndirectPhotonEmittingOutputs.Empty(NumPhotonWorkRanges);
for (int32 RangeIndex = 0; RangeIndex < NumPhotonWorkRanges; RangeIndex++)
{
// Initialize outputs with the appropriate irradiance photon array
IndirectPhotonEmittingOutputs.Add(FIndirectPhotonEmittingOutput(&IrradiancePhotons[IndirectIrradianceArrayStart + RangeIndex]));
}
// Spawn threads to emit indirect photons
TIndirectArray<FIndirectPhotonEmittingThreadRunnable> IndirectPhotonEmittingThreads;
for (int32 ThreadIndex = 1; ThreadIndex < NumStaticLightingThreads; ThreadIndex++)
{
FIndirectPhotonEmittingThreadRunnable* ThreadRunnable = new FIndirectPhotonEmittingThreadRunnable(this, ThreadIndex, Input);
IndirectPhotonEmittingThreads.Add(ThreadRunnable);
const FString ThreadName = FString::Printf(TEXT("IndirectPhotonEmittingThread%u"), ThreadIndex);
ThreadRunnable->Thread = FRunnableThread::Create(ThreadRunnable, *ThreadName);
}
const double StartEmittingIndirectPhotonsMainThread = FPlatformTime::Seconds();
// Add the photons into spatial data structures to accelerate their searches later
FirstBouncePhotonMap = FPhotonOctree(ImportanceBounds.Origin, ImportanceBounds.BoxExtent.GetMax());
FirstBounceEscapedPhotonMap = FPhotonOctree(ImportanceBounds.Origin, ImportanceBounds.BoxExtent.GetMax());
SecondBouncePhotonMap = FPhotonOctree(ImportanceBounds.Origin, ImportanceBounds.BoxExtent.GetMax());
Stats.NumIndirectPhotonsGathered = 0;
int32 NextOutputToProcess = 0;
while (IndirectPhotonEmittingWorkRangeIndex.GetValue() < IndirectPhotonEmittingWorkRanges.Num()
|| NextOutputToProcess < IndirectPhotonEmittingOutputs.Num())
{
// Process one work range on the main thread
EmitIndirectPhotonsThreadLoop(Input, 0);
LIGHTINGSTAT(FScopedRDTSCTimer MainThreadProcessTimer(Stats.ProcessIndirectPhotonsThreadTime));
// Process the thread's outputs
// Outputs are collected from smallest to largest work range index so that the outputs will be deterministic.
while (NextOutputToProcess < IndirectPhotonEmittingOutputs.Num()
&& IndirectPhotonEmittingOutputs[NextOutputToProcess].OutputComplete > 0)
{
FIndirectPhotonEmittingOutput& CurrentOutput = IndirectPhotonEmittingOutputs[NextOutputToProcess];
for (int32 PhotonIndex = 0; PhotonIndex < CurrentOutput.FirstBouncePhotons.Num(); PhotonIndex++)
{
FirstBouncePhotonMap.AddElement(FPhotonElement(CurrentOutput.FirstBouncePhotons[PhotonIndex]));
}
if (PhotonMappingSettings.bUsePhotonSegmentsForVolumeLighting)
{
for (int32 PhotonIndex = 0; PhotonIndex < CurrentOutput.FirstBounceEscapedPhotons.Num(); PhotonIndex++)
{
FirstBounceEscapedPhotonMap.AddElement(FPhotonElement(CurrentOutput.FirstBounceEscapedPhotons[PhotonIndex]));
}
}
for (int32 PhotonIndex = 0; PhotonIndex < CurrentOutput.SecondBouncePhotons.Num(); PhotonIndex++)
{
SecondBouncePhotonMap.AddElement(FPhotonElement(CurrentOutput.SecondBouncePhotons[PhotonIndex]));
}
if (PhotonMappingSettings.bUseIrradiancePhotons)
{
for (int32 PhotonIndex = 0; PhotonIndex < CurrentOutput.IrradiancePhotons->Num(); PhotonIndex++)
{
// Add the irradiance photons to an octree
IrradiancePhotonMap.AddElement(FIrradiancePhotonElement(PhotonIndex, *CurrentOutput.IrradiancePhotons));
}
Stats.NumIrradiancePhotons += CurrentOutput.IrradiancePhotons->Num();
}
Stats.NumSecondPassPhotonsEmitted += CurrentOutput.NumPhotonsEmitted;
Stats.LightSamplingThreadTime += CurrentOutput.LightSamplingThreadTime;
Stats.IndirectCustomAttenuationThreadTime += CurrentOutput.IndirectCustomAttenuationThreadTime;
Stats.IntersectLightRayThreadTime += CurrentOutput.IntersectLightRayThreadTime;
Stats.PhotonBounceTracingThreadTime += CurrentOutput.PhotonBounceTracingThreadTime;
NumPhotonsEmittedFirstBounce += CurrentOutput.NumPhotonsEmittedFirstBounce;
NumPhotonsEmittedSecondBounce += CurrentOutput.NumPhotonsEmittedSecondBounce;
Stats.NumIndirectPhotonsGathered += CurrentOutput.FirstBouncePhotons.Num() + CurrentOutput.SecondBouncePhotons.Num();
NextOutputToProcess++;
CurrentOutput.FirstBouncePhotons.Empty();
CurrentOutput.SecondBouncePhotons.Empty();
}
}
FirstBouncePhotonSegmentMap = FPhotonSegmentOctree(ImportanceBounds.Origin, ImportanceBounds.BoxExtent.GetMax());
if (PhotonMappingSettings.bUsePhotonSegmentsForVolumeLighting)
{
const double SegmentStartTime = FPlatformTime::Seconds();
BuildPhotonSegmentMap(FirstBouncePhotonMap, FirstBouncePhotonSegmentMap, PhotonMappingSettings.GeneratePhotonSegmentChance);
BuildPhotonSegmentMap(FirstBounceEscapedPhotonMap, FirstBouncePhotonSegmentMap, 1.0f);
const float BuildSegmentMapTime = (float)(FPlatformTime::Seconds() - SegmentStartTime);
LogSolverMessage(FString::Printf(TEXT("Built photon segment map in %.1f seconds"), BuildSegmentMapTime));
}
Stats.EmitIndirectPhotonsThreadTime = FPlatformTime::Seconds() - StartEmittingIndirectPhotonsMainThread;
// Wait until all worker threads have completed
for (int32 ThreadIndex = 0; ThreadIndex < IndirectPhotonEmittingThreads.Num(); ThreadIndex++)
{
IndirectPhotonEmittingThreads[ThreadIndex].Thread->WaitForCompletion();
IndirectPhotonEmittingThreads[ThreadIndex].CheckHealth();
delete IndirectPhotonEmittingThreads[ThreadIndex].Thread;
IndirectPhotonEmittingThreads[ThreadIndex].Thread = NULL;
Stats.EmitIndirectPhotonsThreadTime += IndirectPhotonEmittingThreads[ThreadIndex].ExecutionTime;
}
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (PhotonMappingSettings.bVisualizePhotonPaths
&& PhotonMappingSettings.bUseIrradiancePhotons
&& GeneralSettings.ViewSingleBounceNumber != 0)
{
int32 NumIndirectIrradiancePhotons = 0;
for (int32 RangeIndex = NumPhotonWorkRanges; RangeIndex < IrradiancePhotons.Num(); RangeIndex++)
{
NumIndirectIrradiancePhotons += IrradiancePhotons[RangeIndex].Num();
}
StartupDebugOutput.IrradiancePhotons.Empty(NumIndirectIrradiancePhotons);
for (int32 RangeIndex = NumPhotonWorkRanges; RangeIndex < IrradiancePhotons.Num(); RangeIndex++)
{
for (int32 i = 0; i < IrradiancePhotons[RangeIndex].Num(); i++)
{
StartupDebugOutput.IrradiancePhotons.Add(FDebugPhoton(0, IrradiancePhotons[RangeIndex][i].GetPosition(), IrradiancePhotons[RangeIndex][i].GetSurfaceNormal(), IrradiancePhotons[RangeIndex][i].GetSurfaceNormal()));
}
}
}
#endif
IndirectPhotonEmittingWorkRanges.Empty();
IndirectPhotonEmittingOutputs.Empty();
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing1, 0 ) );
}
uint32 FIndirectPhotonEmittingThreadRunnable::Run()
{
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing1, ThreadIndex ) );
const double StartThreadTime = FPlatformTime::Seconds();
FixThreadGroupAffinity();
#if defined(_MSC_VER) && !defined(XBOX)
if(!FPlatformMisc::IsDebuggerPresent())
{
__try
{
System->EmitIndirectPhotonsThreadLoop(Input, ThreadIndex);
}
__except( ReportCrash( GetExceptionInformation() ) )
{
ErrorMessage = GErrorHist;
bTerminatedByError = true;
}
}
else
#endif
{
System->EmitIndirectPhotonsThreadLoop(Input, ThreadIndex);
}
const double EndThreadTime = FPlatformTime::Seconds();
EndTime = EndThreadTime - GStartupTime;
ExecutionTime = EndThreadTime - StartThreadTime;
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing1, ThreadIndex ) );
return 0;
}
/** Entrypoint for all threads emitting indirect photons. */
void FStaticLightingSystem::EmitIndirectPhotonsThreadLoop(const FIndirectPhotonEmittingInput& Input, int32 ThreadIndex)
{
while (true)
{
// Atomically read and increment the next work range index to process.
// In this way work ranges are processed on-demand, which ensures consistent end times between threads.
// Processing from smallest to largest work range index since the main thread is processing outputs in the same order.
const int32 RangeIndex = IndirectPhotonEmittingWorkRangeIndex.Increment() - 1;
if (RangeIndex < IndirectPhotonEmittingWorkRanges.Num())
{
EmitIndirectPhotonsWorkRange(Input, IndirectPhotonEmittingWorkRanges[RangeIndex], IndirectPhotonEmittingOutputs[RangeIndex]);
if (ThreadIndex == 0)
{
// Break out of the loop on the main thread after one work range so that it can process any outputs that are ready
break;
}
}
else
{
// Processing has begun for all work ranges
break;
}
}
}
/** Emits indirect photons for a given work range. */
void FStaticLightingSystem::EmitIndirectPhotonsWorkRange(
const FIndirectPhotonEmittingInput& Input,
FIndirectPhotonEmittingWorkRange WorkRange,
FIndirectPhotonEmittingOutput& Output)
{
if (Input.IndirectPathRays.Num() == 0 || Input.LightDistribution.UnnormalizedIntegral < DELTA)
{
// No lights in the scene, so no photons to emit
// Indicate to the main thread that this output is ready for processing
FPlatformAtomics::InterlockedIncrement(&IndirectPhotonEmittingOutputs[WorkRange.RangeIndex].OutputComplete);
return;
}
//@todo - re-evaluate these sizes
Output.FirstBouncePhotons.Empty(FMath::TruncToInt(WorkRange.NumIndirectPhotonsToEmit * .6f * IndirectPhotonEfficiency));
Output.FirstBounceEscapedPhotons.Empty(FMath::TruncToInt(WorkRange.NumIndirectPhotonsToEmit * .6f * IndirectPhotonEfficiency * PhotonMappingSettings.GeneratePhotonSegmentChance));
Output.SecondBouncePhotons.Empty(FMath::TruncToInt(WorkRange.NumIndirectPhotonsToEmit * .4f * IndirectPhotonEfficiency));
if (PhotonMappingSettings.bUseIrradiancePhotons)
{
// Attempt to preallocate irradiance photons based on the percentage of photons that go into the irradiance photon map.
// The actual number of irradiance photons is based on probability.
Output.IrradiancePhotons->Empty(FMath::TruncToInt(IndirectIrradiancePhotonFraction * IndirectPhotonEfficiency * WorkRange.NumIndirectPhotonsToEmit));
}
FCoherentRayCache CoherentRayCache;
// Seed the random number generator at the beginning of each work range, so we get deterministic results regardless of the number of threads being used.
FLMRandomStream RandomStream(WorkRange.RangeIndex);
const bool bIndirectPhotonsNeeded = WorkRange.NumIndirectPhotonsToEmit > 0 && GeneralSettings.NumIndirectLightingBounces > 0;
Output.NumPhotonsEmitted = 0;
// Emit photons until we reach the limit for this work range,
while (bIndirectPhotonsNeeded && Output.NumPhotonsEmitted < WorkRange.NumIndirectPhotonsToEmit)
{
Output.NumPhotonsEmitted++;
int32 NumberOfPathVertices = 0;
FLightRay SampleRay;
FLinearColor PathAlpha;
const FLight* Light = NULL;
{
LIGHTINGSTAT(FScopedRDTSCTimer SampleLightTimer(Output.LightSamplingThreadTime));
float LightPDF;
float LightIndex;
// Pick a light with probability proportional to the light's fraction of the scene's light power
Sample1dCDF(Input.LightDistribution.LightPDFs, Input.LightDistribution.LightCDFs, Input.LightDistribution.UnnormalizedIntegral, RandomStream, LightPDF, LightIndex);
const int32 QuantizedLightIndex = FMath::TruncToInt(LightIndex * Input.LightDistribution.LightPDFs.Num());
check(QuantizedLightIndex >= 0 && QuantizedLightIndex < Lights.Num());
Light = Lights[QuantizedLightIndex];
float RayDirectionPDF;
if (Input.IndirectPathRays[QuantizedLightIndex].Num() > 0)
{
// Use the indirect path rays to sample the light
Light->SampleDirection(
Input.IndirectPathRays[QuantizedLightIndex],
RandomStream,
SampleRay,
RayDirectionPDF,
PathAlpha);
}
else
{
FVector4f LightSourceNormal;
FVector2f LightSurfacePosition;
// No indirect path rays from this light, sample it uniformly
Light->SampleDirection(RandomStream, SampleRay, LightSourceNormal, LightSurfacePosition, RayDirectionPDF, PathAlpha);
}
// Update the path's throughput based on the probability of picking this light and this direction
PathAlpha = PathAlpha / (LightPDF * RayDirectionPDF);
checkSlow(FLinearColorUtils::AreFloatsValid(PathAlpha));
if (PathAlpha.R < DELTA && PathAlpha.G < DELTA && PathAlpha.B < DELTA)
{
// Skip to the next photon since the light doesn't emit any energy in this direction
continue;
}
// Clip the end of the photon path to the importance volume, or skip if the photon path does not intersect the importance volume at all.
FVector4f ClippedStart, ClippedEnd;
if (!ClipLineWithBox(Input.ImportanceBounds.GetBox(), SampleRay.Start, SampleRay.End, ClippedStart, ClippedEnd))
{
continue;
}
SampleRay.End = ClippedEnd;
}
// Find the first vertex of the photon path
FLightRayIntersection PathIntersection;
const float BeforeLightRayTime = CoherentRayCache.FirstHitRayTraceTime;
SampleRay.TraceFlags |= LIGHTRAY_FLIP_SIDEDNESS;
AggregateMesh->IntersectLightRay(SampleRay, true, true, true, CoherentRayCache, PathIntersection);
Output.IntersectLightRayThreadTime += CoherentRayCache.FirstHitRayTraceTime - BeforeLightRayTime;
LIGHTINGSTAT(FScopedRDTSCTimer PhotonTracingTimer(Output.PhotonBounceTracingThreadTime));
FVector4f WorldPathDirection = SampleRay.Direction.GetUnsafeNormal3();
// Continue tracing this photon path as long as it hit a frontface of something in the scene
while (PathIntersection.bIntersects && Dot3(WorldPathDirection, PathIntersection.IntersectionVertex.WorldTangentZ) < 0.0f)
{
if (NumberOfPathVertices == 0)
{
LIGHTINGSTAT(FScopedRDTSCTimer CustomAttenuationTimer(Output.IndirectCustomAttenuationThreadTime));
// Allow the light to attenuate in a non-physically correct way
PathAlpha *= Light->CustomAttenuation(PathIntersection.IntersectionVertex.WorldPosition, RandomStream, false);
}
// Apply transmission
PathAlpha *= PathIntersection.Transmission;
if (PathAlpha.R < DELTA && PathAlpha.G < DELTA && PathAlpha.B < DELTA)
{
// Skip to the next photon since the light was completely filtered out by transmission or attenuation
break;
}
checkSlow(FLinearColorUtils::AreFloatsValid(PathAlpha));
NumberOfPathVertices++;
// Only deposit photons inside the importance bounds
if (Input.ImportanceBounds.GetBox().IsInside(PathIntersection.IntersectionVertex.WorldPosition))
{
// Only deposit a photon if it is not a direct lighting path, and we still need to gather more indirect photons
if (NumberOfPathVertices > 1
&& Output.NumPhotonsEmitted < WorkRange.NumIndirectPhotonsToEmit)
{
// Note: SampleRay.Start is offset from the actual start position, but not enough to matter for the algorithms which make use of the photon's traveled distance.
const float RayLength = (SampleRay.Start - PathIntersection.IntersectionVertex.WorldPosition).Size3();
// Create a photon from this path vertex's information
const FPhoton NewPhoton(Output.NumPhotonsEmitted, PathIntersection.IntersectionVertex.WorldPosition, RayLength, -WorldPathDirection, PathIntersection.IntersectionVertex.WorldTangentZ, PathAlpha);
bool bShouldCreateIrradiancePhoton = false;
if (NumberOfPathVertices == 2)
{
// This is a first bounce photon
Output.FirstBouncePhotons.Add(NewPhoton);
Output.NumPhotonsEmittedFirstBounce = Output.NumPhotonsEmitted;
// Only allow creating an irradiance photon if one or more indirect bounces are required
// The final gather is the first bounce when enabled
bShouldCreateIrradiancePhoton =
(PhotonMappingSettings.bUseFinalGathering && GeneralSettings.NumIndirectLightingBounces > 1)
|| (!PhotonMappingSettings.bUseFinalGathering && GeneralSettings.NumIndirectLightingBounces > 0);
}
else
{
Output.SecondBouncePhotons.Add(NewPhoton);
Output.NumPhotonsEmittedSecondBounce = Output.NumPhotonsEmitted;
// Only allow creating an irradiance photon if two or more indirect bounces are required
// The final gather is the first bounce when enabled
bShouldCreateIrradiancePhoton =
(PhotonMappingSettings.bUseFinalGathering && GeneralSettings.NumIndirectLightingBounces > 2)
|| (!PhotonMappingSettings.bUseFinalGathering && GeneralSettings.NumIndirectLightingBounces > 1);
}
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (PhotonMappingSettings.bVisualizePhotonPaths
&& !PhotonMappingSettings.bUseIrradiancePhotons
&& (GeneralSettings.ViewSingleBounceNumber < 0
|| PhotonMappingSettings.bUseFinalGathering && GeneralSettings.ViewSingleBounceNumber > 1
|| !PhotonMappingSettings.bUseFinalGathering && GeneralSettings.ViewSingleBounceNumber > 0))
{
FScopeLock DebugOutputLock(&DebugOutputSync);
if (StartupDebugOutput.IndirectPhotons.Num() == 0)
{
StartupDebugOutput.IndirectPhotons.Empty(FMath::TruncToInt(NumIndirectPhotonsToEmit * IndirectPhotonEfficiency));
}
StartupDebugOutput.IndirectPhotons.Add(FDebugPhoton(NewPhoton.GetId(), NewPhoton.GetPosition(), SampleRay.Start - NewPhoton.GetPosition(), NewPhoton.GetSurfaceNormal()));
}
#endif
// Create an irradiance photon for a fraction of the deposited photons
if (PhotonMappingSettings.bUseIrradiancePhotons
&& bShouldCreateIrradiancePhoton
&& RandomStream.GetFraction() < IndirectIrradiancePhotonFraction)
{
const FIrradiancePhoton NewIrradiancePhoton(NewPhoton.GetPosition(), PathIntersection.IntersectionVertex.WorldTangentZ, false);
Output.IrradiancePhotons->Add(NewIrradiancePhoton);
}
}
}
// Photon bounce number = NumberOfPathVertices - 1
// But with final gathering, a first bounce photon is second bounce lighting
const int32 EffectiveBounceNumber = NumberOfPathVertices - 1 + 1;
// Stop tracing this photon due to bounce number
if (EffectiveBounceNumber >= GeneralSettings.NumIndirectLightingBounces
// Ray can hit translucent meshes if they have bCastShadowAsMasked, but we don't have diffuse for translucency, so just terminate
|| PathIntersection.Mesh->IsTranslucent(PathIntersection.ElementIndex))
{
break;
}
FStaticLightingVertex IntersectionVertexWithTangents(PathIntersection.IntersectionVertex);
FVector4f NewWorldPathDirection;
float BRDFDirectionPDF;
// Generate a new path direction from the BRDF
const FLinearColor BRDF = PathIntersection.Mesh->SampleBRDF(
IntersectionVertexWithTangents,
PathIntersection.ElementIndex,
-WorldPathDirection,
NewWorldPathDirection,
BRDFDirectionPDF,
RandomStream);
// Terminate if the path has lost all of its energy due to the surface's BRDF
if (BRDF.Equals(FLinearColor::Black)
// Terminate if indirect photons are completed
|| Output.NumPhotonsEmitted >= WorkRange.NumIndirectPhotonsToEmit)
{
break;
}
const float CosFactor = -Dot3(WorldPathDirection, IntersectionVertexWithTangents.WorldTangentZ);
checkSlow(CosFactor >= 0.0f && CosFactor <= 1.0f);
if (NumberOfPathVertices == 1)
{
// On the first bounce, re-weight the photon's throughput instead of using Russian Roulette to maintain equal weights,
// Because NumEmitted/NumDeposited efficiency is more important to first bounce photons than having equal weight,
// Since they are used for importance sampling the final gather.
// Re-weight the throughput based on the probability of surviving.
PathAlpha = PathAlpha * BRDF * CosFactor / BRDFDirectionPDF;
}
else
{
const FLinearColor NewPathAlpha = PathAlpha * BRDF * CosFactor / BRDFDirectionPDF;
// On second and up bounces, terminate the path with probability proportional to the ratio between the new throughput and the old
// This results in a smaller number of photons after surface reflections, but they have the same weight as before the reflection,
// Which is desirable to reduce noise from the photon maps, at the cost of lower NumEmitted/NumDeposited efficiency.
// See the "Extended Photon Map Implementation" paper for details.
// Note: to be physically correct this probability should be clamped to [0,1], however this produces photons with extremely large weights,
// So instead we maintain a constant photon weight after the surface interaction,
// At the cost of introducing bias and leaking energy for bounces where BRDF * CosFactor / BRDFDirectionPDF > 1.
const float ContinueProbability = FLinearColorUtils::LinearRGBToXYZ(NewPathAlpha).G / FLinearColorUtils::LinearRGBToXYZ(PathAlpha).G;
const float RandomFloat = RandomStream.GetFraction();
if (RandomFloat > ContinueProbability)
{
// Terminate due to Russian Roulette
break;
}
PathAlpha = NewPathAlpha / ContinueProbability;
}
checkSlow(FLinearColorUtils::AreFloatsValid(PathAlpha));
const FVector4f RayStart = IntersectionVertexWithTangents.WorldPosition
+ NewWorldPathDirection * SceneConstants.VisibilityRayOffsetDistance
+ IntersectionVertexWithTangents.WorldTangentZ * SceneConstants.VisibilityNormalOffsetDistance;
FVector4f RayEnd = IntersectionVertexWithTangents.WorldPosition + NewWorldPathDirection * MaxRayDistance;
// Clip photon path end points to the importance volume, so we do not bother tracing rays outside the area that photons can be deposited.
// If the photon path does not intersect the importance volume at all, it did not originate from inside the volume, so skip to the next photon.
FVector4f ClippedStart, ClippedEnd;
if (!ClipLineWithBox(Input.ImportanceBounds.GetBox(), RayStart, RayEnd, ClippedStart, ClippedEnd))
{
break;
}
RayEnd = ClippedEnd;
SampleRay = FLightRay(
RayStart,
RayEnd,
NULL,
NULL
);
// Trace a ray to determine the next vertex of the photon's path.
SampleRay.TraceFlags |= LIGHTRAY_FLIP_SIDEDNESS;
AggregateMesh->IntersectLightRay(SampleRay, true, true, false, CoherentRayCache, PathIntersection);
WorldPathDirection = NewWorldPathDirection;
}
// First bounce escaped photon
if (!PathIntersection.bIntersects
&& NumberOfPathVertices == 1
&& PhotonMappingSettings.bUsePhotonSegmentsForVolumeLighting)
{
if (RandomStream.GetFraction() < PhotonMappingSettings.GeneratePhotonSegmentChance)
{
// Apply transmission
PathAlpha *= PathIntersection.Transmission;
checkSlow(FLinearColorUtils::AreFloatsValid(PathAlpha));
FVector4f RayEnd = SampleRay.Start + WorldPathDirection * Input.ImportanceBounds.SphereRadius * 2;
FVector4f ClippedStart, ClippedEnd;
if (ClipLineWithBox(Input.ImportanceBounds.GetBox(), SampleRay.Start, RayEnd, ClippedStart, ClippedEnd))
{
// Create the description for an escaped photon
const FPhoton NewPhoton(Output.NumPhotonsEmitted, ClippedEnd, (ClippedEnd - SampleRay.Start).Size3(), -WorldPathDirection, FVector4f(0, 0, 1), PathAlpha);
Output.FirstBounceEscapedPhotons.Add(NewPhoton);
}
}
}
}
// Indicate to the main thread that this output is ready for processing
FPlatformAtomics::InterlockedIncrement(&IndirectPhotonEmittingOutputs[WorkRange.RangeIndex].OutputComplete);
}
/** Iterates through all irradiance photons, searches for nearby direct photons, and marks the irradiance photon has having direct photon influence if necessary. */
void FStaticLightingSystem::MarkIrradiancePhotons(const FBoxSphereBounds3f& ImportanceBounds, TArray<TArray<FIrradiancePhoton>>& IrradiancePhotons)
{
check(PhotonMappingSettings.bUseIrradiancePhotons);
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing2, 0 ) );
// Setup work ranges for processing the irradiance photons
IrradianceMarkWorkRanges.Empty(IrradiancePhotons.Num());
for (int32 WorkRange = 0; WorkRange < IrradiancePhotons.Num(); WorkRange++)
{
IrradianceMarkWorkRanges.Add(FIrradianceMarkingWorkRange(WorkRange, WorkRange));
}
TIndirectArray<FIrradiancePhotonMarkingThreadRunnable> IrradiancePhotonMarkingThreads;
IrradiancePhotonMarkingThreads.Empty(NumStaticLightingThreads);
for(int32 ThreadIndex = 1; ThreadIndex < NumStaticLightingThreads; ThreadIndex++)
{
FIrradiancePhotonMarkingThreadRunnable* ThreadRunnable = new FIrradiancePhotonMarkingThreadRunnable(this, ThreadIndex, IrradiancePhotons);
IrradiancePhotonMarkingThreads.Add(ThreadRunnable);
const FString ThreadName = FString::Printf(TEXT("IrradiancePhotonMarkingThread%u"), ThreadIndex);
ThreadRunnable->Thread = FRunnableThread::Create(ThreadRunnable, *ThreadName);
}
const double MainThreadStartTime = FPlatformTime::Seconds();
MarkIrradiancePhotonsThreadLoop(0, IrradiancePhotons);
Stats.IrradiancePhotonMarkingThreadTime = FPlatformTime::Seconds() - MainThreadStartTime;
// Stop the static lighting threads.
for(int32 ThreadIndex = 0;ThreadIndex < IrradiancePhotonMarkingThreads.Num();ThreadIndex++)
{
// Wait for the thread to exit.
IrradiancePhotonMarkingThreads[ThreadIndex].Thread->WaitForCompletion();
// Check that it didn't terminate with an error.
IrradiancePhotonMarkingThreads[ThreadIndex].CheckHealth();
// Destroy the thread.
delete IrradiancePhotonMarkingThreads[ThreadIndex].Thread;
// Accumulate each thread's execution time and stats
Stats.IrradiancePhotonMarkingThreadTime += IrradiancePhotonMarkingThreads[ThreadIndex].ExecutionTime;
}
IrradianceMarkWorkRanges.Empty();
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing2, 0 ) );
}
uint32 FIrradiancePhotonMarkingThreadRunnable::Run()
{
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing2, ThreadIndex ) );
const double StartThreadTime = FPlatformTime::Seconds();
FixThreadGroupAffinity();
#if defined(_MSC_VER) && !defined(XBOX)
if(!FPlatformMisc::IsDebuggerPresent())
{
__try
{
System->MarkIrradiancePhotonsThreadLoop(ThreadIndex, IrradiancePhotons);
}
__except( ReportCrash( GetExceptionInformation() ) )
{
ErrorMessage = GErrorHist;
bTerminatedByError = true;
}
}
else
#endif
{
System->MarkIrradiancePhotonsThreadLoop(ThreadIndex, IrradiancePhotons);
}
const double EndThreadTime = FPlatformTime::Seconds();
EndTime = EndThreadTime - GStartupTime;
ExecutionTime = EndThreadTime - StartThreadTime;
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing2, ThreadIndex ) );
return 0;
}
/** Entry point for all threads marking irradiance photons. */
void FStaticLightingSystem::MarkIrradiancePhotonsThreadLoop(
int32 ThreadIndex,
TArray<TArray<FIrradiancePhoton>>& IrradiancePhotons)
{
while (true)
{
// Atomically read and increment the next work range index to process.
// In this way work ranges are processed on-demand, which ensures consistent end times between threads.
const int32 RangeIndex = IrradianceMarkWorkRangeIndex.Increment() - 1;
if (RangeIndex < IrradianceMarkWorkRanges.Num())
{
MarkIrradiancePhotonsWorkRange(IrradiancePhotons, IrradianceMarkWorkRanges[RangeIndex]);
}
else
{
// Processing has begun for all work ranges
break;
}
}
}
/** Marks irradiance photons specified by a single work range. */
void FStaticLightingSystem::MarkIrradiancePhotonsWorkRange(
TArray<TArray<FIrradiancePhoton>>& IrradiancePhotons,
FIrradianceMarkingWorkRange WorkRange)
{
// Temporary array that is reused for all photon searches by this thread, to reduce allocations
TArray<FPhoton> TempFoundPhotons;
TArray<FIrradiancePhoton>& CurrentArray = IrradiancePhotons[WorkRange.IrradiancePhotonArrayIndex];
for (int32 PhotonIndex = 0; PhotonIndex < CurrentArray.Num(); PhotonIndex++)
{
FIrradiancePhoton& CurrentIrradiancePhoton = CurrentArray[PhotonIndex];
// Only add direct contribution if we are final gathering and at least one bounce is required,
if ((PhotonMappingSettings.bUseFinalGathering && GeneralSettings.NumIndirectLightingBounces > 0)
// Or if photon mapping is being used for direct lighting.
|| PhotonMappingSettings.bVisualizeCachedApproximateDirectLighting)
{
// Find a nearby direct photon
const bool bHasDirectContribution = FindAnyNearbyPhoton(DirectPhotonMap, CurrentIrradiancePhoton.GetPosition(), PhotonMappingSettings.DirectPhotonSearchDistance, false);
if (bHasDirectContribution)
{
// Mark the irradiance photon has having direct contribution, which will be used to reduce the search radius for this irradiance photon,
// In order to get more accurate direct shadow transitions using the photon map.
CurrentIrradiancePhoton.SetHasDirectContribution();
}
}
}
}
/** Calculates irradiance for photons randomly chosen to precalculate irradiance. */
void FStaticLightingSystem::CalculateIrradiancePhotons(const FBoxSphereBounds3f& ImportanceBounds, TArray<TArray<FIrradiancePhoton>>& IrradiancePhotons)
{
check(PhotonMappingSettings.bUseIrradiancePhotons);
//@todo - add a preparing stage for the swarm visualizer
//GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing2, 0 ) );
if (!PhotonMappingSettings.bCacheIrradiancePhotonsOnSurfaces)
{
// Without bCacheIrradiancePhotonsOnSurfaces, treat all irradiance photons as found since we'll have to calculate irradiance for all of them.
Stats.NumFoundIrradiancePhotons = Stats.NumIrradiancePhotons;
}
if (PhotonMappingSettings.bVisualizeIrradiancePhotonCalculation && Scene.DebugMapping)
{
float ClosestIrradiancePhotonDistSq = FLT_MAX;
// Skip direct irradiance photons if viewing indirect bounces
const int32 ArrayStart = GeneralSettings.ViewSingleBounceNumber > 0 ? NumPhotonWorkRanges : 0;
// Skip indirect irradiance photons if viewing direct only
const int32 ArrayEnd = GeneralSettings.ViewSingleBounceNumber == 0 ? NumPhotonWorkRanges : IrradiancePhotons.Num();
for (int32 ArrayIndex = ArrayStart; ArrayIndex < ArrayEnd; ArrayIndex++)
{
for (int32 PhotonIndex = 0; PhotonIndex < IrradiancePhotons[ArrayIndex].Num(); PhotonIndex++)
{
const FIrradiancePhoton& CurrentPhoton = IrradiancePhotons[ArrayIndex][PhotonIndex];
const float CurrentDistSquared = (CurrentPhoton.GetPosition() - Scene.DebugInput.Position).SizeSquared3();
if ((!PhotonMappingSettings.bCacheIrradiancePhotonsOnSurfaces || CurrentPhoton.IsUsed())
&& CurrentDistSquared < ClosestIrradiancePhotonDistSq)
{
// Debug the closest irradiance photon to the selected position.
// NOTE: This is not necessarily the photon that will get cached for the selected texel!
// It's not easy to figure out which photon will get cached at this point in the lighting process, so we use the closest instead.
//@todo - if bCacheIrradiancePhotonsOnSurfaces is enabled, we can figure out exactly which photon will be used by the selected texel or vertex.
ClosestIrradiancePhotonDistSq = CurrentDistSquared;
DebugIrradiancePhotonCalculationArrayIndex = ArrayIndex;
DebugIrradiancePhotonCalculationPhotonIndex = PhotonIndex;
}
}
}
}
// Setup work ranges for processing the irradiance photons
IrradianceCalculationWorkRanges.Empty(IrradiancePhotons.Num());
for (int32 WorkRange = 0; WorkRange < IrradiancePhotons.Num(); WorkRange++)
{
IrradianceCalculationWorkRanges.Add(FIrradianceCalculatingWorkRange(WorkRange, WorkRange));
}
TIndirectArray<FIrradiancePhotonCalculatingThreadRunnable> IrradiancePhotonThreads;
IrradiancePhotonThreads.Empty(NumStaticLightingThreads);
for(int32 ThreadIndex = 1; ThreadIndex < NumStaticLightingThreads; ThreadIndex++)
{
FIrradiancePhotonCalculatingThreadRunnable* ThreadRunnable = new FIrradiancePhotonCalculatingThreadRunnable(this, ThreadIndex, IrradiancePhotons);
IrradiancePhotonThreads.Add(ThreadRunnable);
const FString ThreadName = FString::Printf(TEXT("IrradiancePhotonCalculatingThread%u"), ThreadIndex);
ThreadRunnable->Thread = FRunnableThread::Create(ThreadRunnable, *ThreadName);
}
const double MainThreadStartTime = FPlatformTime::Seconds();
FCalculateIrradiancePhotonStats MainThreadStats;
CalculateIrradiancePhotonsThreadLoop(0, IrradiancePhotons, MainThreadStats);
Stats.IrradiancePhotonCalculatingThreadTime = FPlatformTime::Seconds() - MainThreadStartTime;
Stats.CalculateIrradiancePhotonStats = MainThreadStats;
// Stop the static lighting threads.
for(int32 ThreadIndex = 0;ThreadIndex < IrradiancePhotonThreads.Num();ThreadIndex++)
{
// Wait for the thread to exit.
IrradiancePhotonThreads[ThreadIndex].Thread->WaitForCompletion();
// Check that it didn't terminate with an error.
IrradiancePhotonThreads[ThreadIndex].CheckHealth();
// Destroy the thread.
delete IrradiancePhotonThreads[ThreadIndex].Thread;
// Accumulate each thread's execution time and stats
Stats.IrradiancePhotonCalculatingThreadTime += IrradiancePhotonThreads[ThreadIndex].ExecutionTime;
Stats.CalculateIrradiancePhotonStats += IrradiancePhotonThreads[ThreadIndex].Stats;
}
IrradianceCalculationWorkRanges.Empty();
// Release all of the direct photon map memory since we are not going to need it later
DirectPhotonMap.Destroy();
// Release all of the second bounce photon map memory since it will not be used again
SecondBouncePhotonMap.Destroy();
}
uint32 FIrradiancePhotonCalculatingThreadRunnable::Run()
{
const double StartThreadTime = FPlatformTime::Seconds();
FixThreadGroupAffinity();
#if defined(_MSC_VER) && !defined(XBOX)
if(!FPlatformMisc::IsDebuggerPresent())
{
__try
{
System->CalculateIrradiancePhotonsThreadLoop(ThreadIndex, IrradiancePhotons, Stats);
}
__except( ReportCrash( GetExceptionInformation() ) )
{
ErrorMessage = GErrorHist;
bTerminatedByError = true;
}
}
else
#endif
{
System->CalculateIrradiancePhotonsThreadLoop(ThreadIndex, IrradiancePhotons, Stats);
}
const double EndThreadTime = FPlatformTime::Seconds();
EndTime = EndThreadTime - GStartupTime;
ExecutionTime = EndThreadTime - StartThreadTime;
return 0;
}
/** Main loop that all threads access to calculate irradiance photons. */
void FStaticLightingSystem::CalculateIrradiancePhotonsThreadLoop(
int32 ThreadIndex,
TArray<TArray<FIrradiancePhoton>>& IrradiancePhotons,
FCalculateIrradiancePhotonStats& OutStats)
{
while (true)
{
// Atomically read and increment the next work range index to process.
// In this way work ranges are processed on-demand, which ensures consistent end times between threads.
// Processing from smallest to largest work range index since the main thread is processing outputs in the same order.
const int32 RangeIndex = IrradianceCalcWorkRangeIndex.Increment() - 1;
if (RangeIndex < IrradianceCalculationWorkRanges.Num())
{
CalculateIrradiancePhotonsWorkRange(IrradiancePhotons, IrradianceCalculationWorkRanges[RangeIndex], OutStats);
}
else
{
// Processing has begun for all work ranges
break;
}
}
}
/** Calculates irradiance for the photons specified by a single work range. */
void FStaticLightingSystem::CalculateIrradiancePhotonsWorkRange(
TArray<TArray<FIrradiancePhoton>>& IrradiancePhotons,
FIrradianceCalculatingWorkRange WorkRange,
FCalculateIrradiancePhotonStats& OutStats)
{
// Temporary array that is reused for all photon searches by this thread, to reduce allocations
TArray<FPhoton> TempFoundPhotons;
TArray<FIrradiancePhoton>& CurrentArray = IrradiancePhotons[WorkRange.IrradiancePhotonArrayIndex];
for (int32 PhotonIndex = 0; PhotonIndex < CurrentArray.Num(); PhotonIndex++)
{
FIrradiancePhoton& CurrentIrradiancePhoton = CurrentArray[PhotonIndex];
// If we already cached irradiance photons on surfaces, only calculate irradiance for photons which actually got found.
if (PhotonMappingSettings.bCacheIrradiancePhotonsOnSurfaces && !CurrentIrradiancePhoton.IsUsed())
{
continue;
}
const bool bDebugThisPhoton = PhotonMappingSettings.bVisualizeIrradiancePhotonCalculation
&& DebugIrradiancePhotonCalculationArrayIndex == WorkRange.IrradiancePhotonArrayIndex
&& DebugIrradiancePhotonCalculationPhotonIndex == PhotonIndex;
FLinearColor AccumulatedIrradiance(FLinearColor::Black);
// Only add direct contribution if we are final gathering and at least one bounce is required,
if (((PhotonMappingSettings.bUseFinalGathering && GeneralSettings.NumIndirectLightingBounces > 0)
// Or if photon mapping is being used for direct lighting.
|| PhotonMappingSettings.bVisualizeCachedApproximateDirectLighting)
&& PhotonMappingSettings.bUsePhotonDirectLightingInFinalGather)
{
const FLinearColor DirectPhotonIrradiance = CalculatePhotonIrradiance(
DirectPhotonMap,
NumPhotonsEmittedDirect,
PhotonMappingSettings.NumIrradianceCalculationPhotons,
PhotonMappingSettings.DirectPhotonSearchDistance,
CurrentIrradiancePhoton,
bDebugThisPhoton && GeneralSettings.ViewSingleBounceNumber == 0,
TempFoundPhotons,
OutStats);
checkSlow(FLinearColorUtils::AreFloatsValid(DirectPhotonIrradiance));
// Only add direct contribution if it should be viewed given ViewSingleBounceNumber
if (GeneralSettings.ViewSingleBounceNumber < 0
|| (PhotonMappingSettings.bUseFinalGathering && GeneralSettings.ViewSingleBounceNumber == 1)
|| (!PhotonMappingSettings.bUseFinalGathering && GeneralSettings.ViewSingleBounceNumber == 0)
|| (PhotonMappingSettings.bVisualizeCachedApproximateDirectLighting && GeneralSettings.ViewSingleBounceNumber == 0))
{
AccumulatedIrradiance = DirectPhotonIrradiance;
}
}
// If we are final gathering, first bounce photons are actually the second lighting bounce since the final gather is the first bounce
if ((PhotonMappingSettings.bUseFinalGathering && GeneralSettings.NumIndirectLightingBounces > 1)
|| (!PhotonMappingSettings.bUseFinalGathering && GeneralSettings.NumIndirectLightingBounces > 0))
{
const FLinearColor FirstBouncePhotonIrradiance = CalculatePhotonIrradiance(
FirstBouncePhotonMap,
NumPhotonsEmittedFirstBounce,
PhotonMappingSettings.NumIrradianceCalculationPhotons,
PhotonMappingSettings.IndirectPhotonSearchDistance,
CurrentIrradiancePhoton,
bDebugThisPhoton && GeneralSettings.ViewSingleBounceNumber == 1,
TempFoundPhotons,
OutStats);
checkSlow(FLinearColorUtils::AreFloatsValid(FirstBouncePhotonIrradiance));
// Only add first bounce contribution if it should be viewed given ViewSingleBounceNumber
if (GeneralSettings.ViewSingleBounceNumber < 0
|| (PhotonMappingSettings.bUseFinalGathering && GeneralSettings.ViewSingleBounceNumber == 2)
|| (!PhotonMappingSettings.bUseFinalGathering && GeneralSettings.ViewSingleBounceNumber == 1))
{
AccumulatedIrradiance += FirstBouncePhotonIrradiance;
}
// If we are final gathering, second bounce photons are actually the third lighting bounce since the final gather is the first bounce
if ((PhotonMappingSettings.bUseFinalGathering && GeneralSettings.NumIndirectLightingBounces > 2)
|| (!PhotonMappingSettings.bUseFinalGathering && GeneralSettings.NumIndirectLightingBounces > 1))
{
const FLinearColor SecondBouncePhotonIrradiance = CalculatePhotonIrradiance(
SecondBouncePhotonMap,
NumPhotonsEmittedSecondBounce,
PhotonMappingSettings.NumIrradianceCalculationPhotons,
PhotonMappingSettings.IndirectPhotonSearchDistance,
CurrentIrradiancePhoton,
bDebugThisPhoton && GeneralSettings.ViewSingleBounceNumber > 1,
TempFoundPhotons,
OutStats);
checkSlow(FLinearColorUtils::AreFloatsValid(SecondBouncePhotonIrradiance));
// Only add second and up bounce contribution if it should be viewed given ViewSingleBounceNumber
if (GeneralSettings.ViewSingleBounceNumber < 0
|| (PhotonMappingSettings.bUseFinalGathering && GeneralSettings.ViewSingleBounceNumber == 3)
|| (!PhotonMappingSettings.bUseFinalGathering && GeneralSettings.ViewSingleBounceNumber == 2))
{
AccumulatedIrradiance += SecondBouncePhotonIrradiance;
}
}
}
CurrentIrradiancePhoton.SetIrradiance(AccumulatedIrradiance);
}
}
/** Cache irradiance photons on surfaces. */
void FStaticLightingSystem::CacheIrradiancePhotons()
{
check(PhotonMappingSettings.bCacheIrradiancePhotonsOnSurfaces);
for(int32 ThreadIndex = 1; ThreadIndex < NumStaticLightingThreads; ThreadIndex++)
{
FMappingProcessingThreadRunnable* ThreadRunnable = new FMappingProcessingThreadRunnable(this, ThreadIndex, StaticLightingTask_CacheIrradiancePhotons);
IrradiancePhotonCachingThreads.Add(ThreadRunnable);
const FString ThreadName = FString::Printf(TEXT("IrradiancePhotonCachingThread%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.
CacheIrradiancePhotonsThreadLoop(0, true);
// Stop the static lighting threads.
for(int32 ThreadIndex = 0;ThreadIndex < IrradiancePhotonCachingThreads.Num();ThreadIndex++)
{
// Wait for the thread to exit.
IrradiancePhotonCachingThreads[ThreadIndex].Thread->WaitForCompletion();
// Check that it didn't terminate with an error.
IrradiancePhotonCachingThreads[ThreadIndex].CheckHealth();
// Destroy the thread.
delete IrradiancePhotonCachingThreads[ThreadIndex].Thread;
}
IrradiancePhotonCachingThreads.Empty();
IrradiancePhotonMap.Destroy();
}
/** Main loop that all threads access to cache irradiance photons. */
void FStaticLightingSystem::CacheIrradiancePhotonsThreadLoop(int32 ThreadIndex, bool bIsMainThread)
{
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing3, ThreadIndex ) );
bool bIsDone = false;
while (!bIsDone)
{
// Atomically read and increment the next mapping index to process.
const int32 MappingIndex = NextMappingToCacheIrradiancePhotonsOn.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 < IrradiancePhotonCachingThreads.Num(); ThreadIndexIter++)
{
IrradiancePhotonCachingThreads[ThreadIndexIter].CheckHealth();
}
}
FStaticLightingTextureMapping* TextureMapping = AllMappings[MappingIndex]->GetTextureMapping();
if (TextureMapping)
{
CacheIrradiancePhotonsTextureMapping(TextureMapping);
}
}
else
{
// Processing has begun for all mappings.
bIsDone = true;
}
}
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Preparing3, ThreadIndex ) );
}
/** Returns true if a photon was found within MaxPhotonSearchDistance. */
bool FStaticLightingSystem::FindAnyNearbyPhoton(
const FPhotonOctree& PhotonMap,
const FVector4f& SearchPosition,
float MaxPhotonSearchDistance,
bool bDebugThisLookup) const
{
FPlatformAtomics::InterlockedIncrement(&Stats.NumPhotonGathers);
const FBox3f SearchBox = FBox3f::BuildAABB(SearchPosition, FVector4f(MaxPhotonSearchDistance, MaxPhotonSearchDistance, MaxPhotonSearchDistance));
for (FPhotonOctree::TConstIterator<> OctreeIt(PhotonMap); OctreeIt.HasPendingNodes(); OctreeIt.Advance())
{
const FPhotonOctree::FNode& CurrentNode = OctreeIt.GetCurrentNode();
const FOctreeNodeContext& CurrentContext = OctreeIt.GetCurrentContext();
// Push children onto the iterator stack if they intersect the query box
if (!CurrentNode.IsLeaf())
{
FOREACH_OCTREE_CHILD_NODE(ChildRef)
{
if (CurrentNode.HasChild(ChildRef))
{
const FOctreeNodeContext ChildContext = CurrentContext.GetChildContext(ChildRef);
if (ChildContext.Bounds.GetBox().Intersect(SearchBox))
{
OctreeIt.PushChild(ChildRef);
}
}
}
}
// Iterate over all photons in the nodes intersecting the query box
for (FPhotonOctree::ElementConstIt MeshIt(CurrentNode.GetConstElementIt()); MeshIt; ++MeshIt)
{
const FPhotonElement& PhotonElement = *MeshIt;
const float DistanceSquared = (PhotonElement.Photon.GetPosition() - SearchPosition).SizeSquared3();
// Only searching for photons closer than the max distance
if (DistanceSquared < MaxPhotonSearchDistance * MaxPhotonSearchDistance)
{
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (bDebugThisLookup
&& PhotonMappingSettings.bVisualizePhotonGathers
&& &PhotonMap == &DirectPhotonMap)
{
StartupDebugOutput.bDirectPhotonValid = true;
StartupDebugOutput.GatheredDirectPhoton = FDebugPhoton(PhotonElement.Photon.GetId(), PhotonElement.Photon.GetPosition(), PhotonElement.Photon.GetIncidentDirection(), PhotonElement.Photon.GetSurfaceNormal());
}
#endif
return true;
}
}
}
return false;
}
/**
* Searches the given photon map for the nearest NumPhotonsToFind photons to SearchPosition using an iterative process,
* Unless the start and max search distances are the same, in which case all photons in that distance will be returned.
* The iterative search starts at StartPhotonSearchDistance and doubles the search distance until enough photons are found or the distance is greater than MaxPhotonSearchDistance.
* @return - the furthest found photon's distance squared from SearchPosition, unless the start and max search distances are the same,
* in which case FMath::Square(MaxPhotonSearchDistance) will be returned.
*/
float FStaticLightingSystem::FindNearbyPhotonsIterative(
const FPhotonOctree& PhotonMap,
const FVector4f& SearchPosition,
const FVector4f& SearchNormal,
int32 NumPhotonsToFind,
float StartPhotonSearchDistance,
float MaxPhotonSearchDistance,
bool bDebugSearchResults,
bool bDebugSearchProcess,
TArray<FPhoton>& FoundPhotons,
FFindNearbyPhotonStats& SearchStats) const
{
FPlatformAtomics::InterlockedIncrement(&Stats.NumPhotonGathers);
SearchStats.NumIterativePhotonMapSearches++;
// Only enforce the search number if the start and max distances are not equal
const bool bEnforceSearchNumber = !FMath::IsNearlyEqual(StartPhotonSearchDistance, MaxPhotonSearchDistance);
float SearchDistance = StartPhotonSearchDistance;
float FurthestPhotonDistanceSquared = 0.0f;
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (bDebugSearchProcess)
{
// Verify that only one search is debugged
// This will not always catch multiple searches due to re-entrance by multiple threads
checkSlow(StartupDebugOutput.GatheredPhotonNodes.Num() == 0);
}
#endif
// Continue searching until we have found enough photons or have exceeded the max search distance
while (FoundPhotons.Num() < NumPhotonsToFind && SearchDistance <= MaxPhotonSearchDistance)
{
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (bDebugSearchProcess)
{
// Only capture the nodes visited on the last iteration
StartupDebugOutput.GatheredPhotonNodes.Empty();
}
#endif
FurthestPhotonDistanceSquared = SearchDistance * SearchDistance;
// Presize to avoid unnecessary allocations
// Empty last search iteration's results so we don't have to use AddUniqueItem
FoundPhotons.Empty(FMath::Max(NumPhotonsToFind, FoundPhotons.Num() + FoundPhotons.GetSlack()));
const FBox3f SearchBox = FBox3f::BuildAABB(SearchPosition, FVector4f(SearchDistance, SearchDistance, SearchDistance));
for (FPhotonOctree::TConstIterator<TInlineAllocator<600>> OctreeIt(PhotonMap); OctreeIt.HasPendingNodes(); OctreeIt.Advance())
{
const FPhotonOctree::FNode& CurrentNode = OctreeIt.GetCurrentNode();
const FOctreeNodeContext& CurrentContext = OctreeIt.GetCurrentContext();
{
LIGHTINGSTAT(FScopedRDTSCTimer PushingChildrenTimer(SearchStats.PushingOctreeChildrenThreadTime));
// Push children onto the iterator stack if they intersect the query box
if (!CurrentNode.IsLeaf())
{
FOREACH_OCTREE_CHILD_NODE(ChildRef)
{
if (CurrentNode.HasChild(ChildRef))
{
const FOctreeNodeContext ChildContext = CurrentContext.GetChildContext(ChildRef);
if (ChildContext.Bounds.GetBox().Intersect(SearchBox))
{
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (bDebugSearchProcess)
{
StartupDebugOutput.GatheredPhotonNodes.Add(FDebugOctreeNode(ChildContext.Bounds.Center, ChildContext.Bounds.Extent));
}
#endif
OctreeIt.PushChild(ChildRef);
}
}
}
}
}
LIGHTINGSTAT(FScopedRDTSCTimer ProcessingElementsTimer(SearchStats.ProcessingOctreeElementsThreadTime));
// Iterate over all photons in the nodes intersecting the query box
for (FPhotonOctree::ElementConstIt MeshIt(CurrentNode.GetConstElementIt()); MeshIt; ++MeshIt)
{
const FPhotonElement& PhotonElement = *MeshIt;
const float DistanceSquared = (PhotonElement.Photon.GetPosition() - SearchPosition).SizeSquared3();
const float CosNormalTheta = Dot3(SearchNormal, PhotonElement.Photon.GetSurfaceNormal());
const float CosIncidentDirectionTheta = Dot3(SearchNormal, PhotonElement.Photon.GetIncidentDirection());
// Only searching for photons closer than the max distance
if (DistanceSquared < FurthestPhotonDistanceSquared
// Whose normal is within the specified angle from the search normal
&& CosNormalTheta > PhotonMappingSettings.PhotonSearchAngleThreshold
// And whose incident direction is in the same hemisphere as the search normal.
&& CosIncidentDirectionTheta > 0.0f)
{
if (bEnforceSearchNumber)
{
if (FoundPhotons.Num() < NumPhotonsToFind)
{
FoundPhotons.Add(PhotonElement.Photon);
}
else
{
checkSlow(FoundPhotons.Num() == NumPhotonsToFind);
float FurthestFoundPhotonDistSq = 0;
int32 FurthestFoundPhotonIndex = -1;
// Find the furthest photon
// This could be accelerated with a heap instead of doing an O(n) search
LIGHTINGSTAT(FScopedRDTSCTimer FindingFurthestTimer(SearchStats.FindingFurthestPhotonThreadTime));
for (int32 PhotonIndex = 0; PhotonIndex < FoundPhotons.Num(); PhotonIndex++)
{
const float CurrentDistanceSquared = (FoundPhotons[PhotonIndex].GetPosition() - SearchPosition).SizeSquared3();
if (CurrentDistanceSquared > FurthestFoundPhotonDistSq)
{
FurthestFoundPhotonDistSq = CurrentDistanceSquared;
FurthestFoundPhotonIndex = PhotonIndex;
}
}
checkSlow(FurthestFoundPhotonIndex >= 0);
FurthestPhotonDistanceSquared = FurthestFoundPhotonDistSq;
if (DistanceSquared < FurthestFoundPhotonDistSq)
{
// Replace the furthest photon with the new photon since the new photon is closer
FoundPhotons[FurthestFoundPhotonIndex] = PhotonElement.Photon;
}
}
}
else
{
FoundPhotons.Add(PhotonElement.Photon);
}
}
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (bDebugSearchProcess)
{
StartupDebugOutput.IrradiancePhotons.Add(FDebugPhoton(PhotonElement.Photon.GetId(), PhotonElement.Photon.GetPosition(), PhotonElement.Photon.GetIncidentDirection(), PhotonElement.Photon.GetSurfaceNormal()));
}
#endif
}
}
// Double the search radius for each iteration
SearchDistance *= 2.0f;
SearchStats.NumSearchIterations++;
}
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (bDebugSearchProcess || bDebugSearchResults && PhotonMappingSettings.bVisualizePhotonGathers)
{
// Assuming that only importance photons are debugged and enforce the search number
if (bDebugSearchResults && bEnforceSearchNumber)
{
for (int32 i = 0; i < FoundPhotons.Num(); i++)
{
StartupDebugOutput.GatheredImportancePhotons.Add(FDebugPhoton(FoundPhotons[i].GetId(), FoundPhotons[i].GetPosition(), FoundPhotons[i].GetIncidentDirection(), FoundPhotons[i].GetSurfaceNormal()));
}
}
else
{
for (int32 i = 0; i < FoundPhotons.Num(); i++)
{
StartupDebugOutput.GatheredPhotons.Add(FDebugPhoton(FoundPhotons[i].GetId(), FoundPhotons[i].GetPosition(), FoundPhotons[i].GetIncidentDirection(), FoundPhotons[i].GetSurfaceNormal()));
}
}
}
#endif
return FurthestPhotonDistanceSquared;
}
/**
* Searches a volume segment map for photons. Can be used at any point in space, not just on surfaces.
*/
float FStaticLightingSystem::FindNearbyPhotonsInVolumeIterative(
const FPhotonSegmentOctree& PhotonSegmentMap,
const FVector4f& SearchPosition,
int32 NumPhotonsToFind,
float StartPhotonSearchDistance,
float MaxPhotonSearchDistance,
TArray<FPhotonSegmentElement>& FoundPhotonSegments,
bool bDebugThisLookup) const
{
FPlatformAtomics::InterlockedIncrement(&Stats.NumPhotonGathers);
float SearchDistance = StartPhotonSearchDistance;
float FurthestPhotonDistanceSquared = 0.0f;
// Continue searching until we have found enough photons or have exceeded the max search distance
while (FoundPhotonSegments.Num() < NumPhotonsToFind && SearchDistance <= MaxPhotonSearchDistance)
{
FurthestPhotonDistanceSquared = SearchDistance * SearchDistance;
// Presize to avoid unnecessary allocations
// Empty last search iteration's results so we don't have to use AddUniqueItem
FoundPhotonSegments.Empty(FMath::Max(NumPhotonsToFind, FoundPhotonSegments.Num() + FoundPhotonSegments.GetSlack()));
const FBox3f SearchBox = FBox3f::BuildAABB(SearchPosition, FVector4f(SearchDistance, SearchDistance, SearchDistance));
for (FPhotonSegmentOctree::TConstIterator<TInlineAllocator<600>> OctreeIt(PhotonSegmentMap); OctreeIt.HasPendingNodes(); OctreeIt.Advance())
{
const FPhotonSegmentOctree::FNode& CurrentNode = OctreeIt.GetCurrentNode();
const FOctreeNodeContext& CurrentContext = OctreeIt.GetCurrentContext();
{
// Push children onto the iterator stack if they intersect the query box
if (!CurrentNode.IsLeaf())
{
FOREACH_OCTREE_CHILD_NODE(ChildRef)
{
if (CurrentNode.HasChild(ChildRef))
{
const FOctreeNodeContext ChildContext = CurrentContext.GetChildContext(ChildRef);
if (ChildContext.Bounds.GetBox().Intersect(SearchBox))
{
OctreeIt.PushChild(ChildRef);
}
}
}
}
}
// Iterate over all photons in the nodes intersecting the query box
for (FPhotonSegmentOctree::ElementConstIt MeshIt(CurrentNode.GetConstElementIt()); MeshIt; ++MeshIt)
{
const FPhotonSegmentElement& PhotonSegmentElement = *MeshIt;
const float SegmentDistanceSquared = PhotonSegmentElement.ComputeSquaredDistanceToPoint(SearchPosition);
// Only searching for photons closer than the max distance
if (SegmentDistanceSquared < FurthestPhotonDistanceSquared)
{
bool bNewPhoton = true;
for (int32 i = 0; i < FoundPhotonSegments.Num(); i++)
{
if (FoundPhotonSegments[i].Photon == PhotonSegmentElement.Photon)
{
bNewPhoton = false;
break;
}
}
if (bNewPhoton)
{
if (FoundPhotonSegments.Num() < NumPhotonsToFind)
{
FoundPhotonSegments.Add(PhotonSegmentElement);
}
else
{
checkSlow(FoundPhotonSegments.Num() == NumPhotonsToFind);
float FurthestFoundPhotonDistSq = 0;
int32 FurthestFoundPhotonIndex = -1;
// Find the furthest photon
for (int32 PhotonIndex = 0; PhotonIndex < FoundPhotonSegments.Num(); PhotonIndex++)
{
const float OtherSegmentDistanceSquared = FoundPhotonSegments[PhotonIndex].ComputeSquaredDistanceToPoint(SearchPosition);
if (OtherSegmentDistanceSquared > FurthestFoundPhotonDistSq)
{
FurthestFoundPhotonDistSq = OtherSegmentDistanceSquared;
FurthestFoundPhotonIndex = PhotonIndex;
}
}
checkSlow(FurthestFoundPhotonIndex >= 0);
FurthestPhotonDistanceSquared = FurthestFoundPhotonDistSq;
if (SegmentDistanceSquared < FurthestFoundPhotonDistSq)
{
// Replace the furthest photon with the new photon since the new photon is closer
FoundPhotonSegments[FurthestFoundPhotonIndex] = PhotonSegmentElement;
}
}
}
}
}
}
// Double the search radius for each iteration
SearchDistance *= 2.0f;
}
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (bDebugThisLookup && PhotonMappingSettings.bVisualizePhotonGathers)
{
for (int32 i = 0; i < FoundPhotonSegments.Num(); i++)
{
const FPhoton& Photon = *FoundPhotonSegments[i].Photon;
StartupDebugOutput.GatheredImportancePhotons.Add(FDebugPhoton(Photon.GetId(), Photon.GetPosition(), Photon.GetIncidentDirection(), Photon.GetSurfaceNormal()));
}
}
#endif
return FurthestPhotonDistanceSquared;
}
struct FOctreeNodeRefAndDistance
{
FOctreeChildNodeRef NodeRef;
float DistanceSquared;
FORCEINLINE FOctreeNodeRefAndDistance(FOctreeChildNodeRef InNodeRef, float InDistanceSquared) :
NodeRef(InNodeRef),
DistanceSquared(InDistanceSquared)
{}
};
/**
* Searches the given photon map for the nearest NumPhotonsToFind photons to SearchPosition by sorting octree nodes nearest to furthest.
* @return - the furthest found photon's distance squared from SearchPosition.
*/
float FStaticLightingSystem::FindNearbyPhotonsSorted(
const FPhotonOctree& PhotonMap,
const FVector4f& SearchPosition,
const FVector4f& SearchNormal,
int32 NumPhotonsToFind,
float MaxPhotonSearchDistance,
bool bDebugSearchResults,
bool bDebugSearchProcess,
TArray<FPhoton>& FoundPhotons,
FFindNearbyPhotonStats& SearchStats) const
{
FPlatformAtomics::InterlockedIncrement(&Stats.NumPhotonGathers);
float FurthestPhotonDistanceSquared = MaxPhotonSearchDistance * MaxPhotonSearchDistance;
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (bDebugSearchProcess)
{
// Verify that only one search is debugged
// This will not always catch multiple searches due to re-entrance by multiple threads
checkSlow(StartupDebugOutput.GatheredPhotonNodes.Num() == 0);
}
#endif
// Presize to avoid unnecessary allocations
FoundPhotons.Empty(FMath::Max(NumPhotonsToFind, FoundPhotons.Num() + FoundPhotons.GetSlack()));
for (FPhotonOctree::TConstIterator<TInlineAllocator<600>> OctreeIt(PhotonMap); OctreeIt.HasPendingNodes(); OctreeIt.Advance())
{
SearchStats.NumOctreeNodesVisited++;
const FOctreeNodeContext& CurrentContext = OctreeIt.GetCurrentContext();
const float ClosestNodePointDistanceSquared = (CurrentContext.Bounds.Center - SearchPosition).SizeSquared3() - CurrentContext.Bounds.Extent.SizeSquared3();
if (ClosestNodePointDistanceSquared > FurthestPhotonDistanceSquared && !CurrentContext.Bounds.GetBox().IsInside(SearchPosition))
{
// Skip nodes that don't contain the search position and whose closest point is further than FurthestPhotonDistanceSquared
// This check was already done before pushing the node, but FurthestPhotonDistanceSquared may have been reduced since then
//@todo - can we skip all remaining nodes too? Nodes are pushed from closest to furthest.
continue;
}
const FPhotonOctree::FNode& CurrentNode = OctreeIt.GetCurrentNode();
{
LIGHTINGSTAT(FScopedRDTSCTimer ProcessingElementsTimer(SearchStats.ProcessingOctreeElementsThreadTime));
// Iterate over all photons in the nodes intersecting the query box
for (FPhotonOctree::ElementConstIt MeshIt(CurrentNode.GetConstElementIt()); MeshIt; ++MeshIt)
{
SearchStats.NumElementsTested++;
const FPhotonElement& PhotonElement = *MeshIt;
const float DistanceSquared = (PhotonElement.Photon.GetPosition() - SearchPosition).SizeSquared3();
const float CosNormalTheta = Dot3(SearchNormal, PhotonElement.Photon.GetSurfaceNormal());
const float CosIncidentDirectionTheta = Dot3(SearchNormal, PhotonElement.Photon.GetIncidentDirection());
// Only searching for photons closer than the max distance
if (DistanceSquared < FurthestPhotonDistanceSquared
// Whose normal is within the specified angle from the search normal
&& CosNormalTheta > PhotonMappingSettings.PhotonSearchAngleThreshold
// And whose incident direction is in the same hemisphere as the search normal.
&& CosIncidentDirectionTheta > 0.0f)
{
SearchStats.NumElementsAccepted++;
if (FoundPhotons.Num() < NumPhotonsToFind)
{
FoundPhotons.Add(PhotonElement.Photon);
}
else
{
checkSlow(FoundPhotons.Num() == NumPhotonsToFind);
float FurthestFoundPhotonDistSq = 0;
int32 FurthestFoundPhotonIndex = -1;
// Find the furthest photon
// This could be accelerated with a heap instead of doing an O(n) search
LIGHTINGSTAT(FScopedRDTSCTimer FindingFurthestTimer(SearchStats.FindingFurthestPhotonThreadTime));
for (int32 PhotonIndex = FoundPhotons.Num() - 1; PhotonIndex >= 0; PhotonIndex--)
{
const float CurrentDistanceSquared = (FoundPhotons[PhotonIndex].GetPosition() - SearchPosition).SizeSquared3();
if (CurrentDistanceSquared > FurthestFoundPhotonDistSq)
{
FurthestFoundPhotonDistSq = CurrentDistanceSquared;
FurthestFoundPhotonIndex = PhotonIndex;
}
}
checkSlow(FurthestFoundPhotonIndex >= 0);
FurthestPhotonDistanceSquared = FurthestFoundPhotonDistSq;
if (DistanceSquared < FurthestFoundPhotonDistSq)
{
// Replace the furthest photon with the new photon since the new photon is closer
FoundPhotons[FurthestFoundPhotonIndex] = PhotonElement.Photon;
}
}
}
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (bDebugSearchProcess)
{
StartupDebugOutput.IrradiancePhotons.Add(FDebugPhoton(PhotonElement.Photon.GetId(), PhotonElement.Photon.GetPosition(), PhotonElement.Photon.GetIncidentDirection(), PhotonElement.Photon.GetSurfaceNormal()));
}
#endif
}
}
LIGHTINGSTAT(FScopedRDTSCTimer PushingChildrenTimer(SearchStats.PushingOctreeChildrenThreadTime));
// Push children onto the iterator stack if they intersect the query box
if (!CurrentNode.IsLeaf())
{
TArray<FOctreeNodeRefAndDistance, TInlineAllocator<8> > ChildrenInRange;
bool bAllNodesZeroDistance = true;
FOREACH_OCTREE_CHILD_NODE(ChildRef)
{
if (CurrentNode.HasChild(ChildRef))
{
SearchStats.NumOctreeNodesTested++;
const FOctreeNodeContext ChildContext = CurrentContext.GetChildContext(ChildRef);
const bool ChildContainsSearchPosition = ChildContext.Bounds.GetBox().IsInside(SearchPosition);
const float ClosestChildPointDistanceSquared = ChildContainsSearchPosition ?
0 :
FMath::Max((ChildContext.Bounds.Center - SearchPosition).SizeSquared3() - ChildContext.Bounds.Extent.SizeSquared3(), 0.0f);
// Only visit nodes that either contain the search position or whose closest point is closer than FurthestPhotonDistanceSquared
if (ClosestChildPointDistanceSquared <= FurthestPhotonDistanceSquared)
{
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (bDebugSearchProcess)
{
StartupDebugOutput.GatheredPhotonNodes.Add(FDebugOctreeNode(ChildContext.Bounds.Center, ChildContext.Bounds.Extent));
}
#endif
bAllNodesZeroDistance = bAllNodesZeroDistance && ClosestChildPointDistanceSquared < DELTA;
ChildrenInRange.Add(FOctreeNodeRefAndDistance(ChildRef, ClosestChildPointDistanceSquared));
}
}
}
if (!bAllNodesZeroDistance && ChildrenInRange.Num() > 1)
{
// Used to sort FOctreeNodeRefAndDistances from smallest DistanceSquared to largest.
struct FCompareNodeDistance
{
FORCEINLINE bool operator()(const FOctreeNodeRefAndDistance& A, const FOctreeNodeRefAndDistance& B) const
{
return A.DistanceSquared < B.DistanceSquared;
}
};
// Sort the nodes from closest to furthest
ChildrenInRange.Sort(FCompareNodeDistance());
}
for (int32 NodeIndex = 0; NodeIndex < ChildrenInRange.Num(); NodeIndex++)
{
OctreeIt.PushChild(ChildrenInRange[NodeIndex].NodeRef);
}
}
}
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (bDebugSearchProcess || bDebugSearchResults && PhotonMappingSettings.bVisualizePhotonGathers)
{
// Assuming that only importance photons are debugged
if (bDebugSearchResults)
{
for (int32 i = 0; i < FoundPhotons.Num(); i++)
{
StartupDebugOutput.GatheredImportancePhotons.Add(FDebugPhoton(FoundPhotons[i].GetId(), FoundPhotons[i].GetPosition(), FoundPhotons[i].GetIncidentDirection(), FoundPhotons[i].GetSurfaceNormal()));
}
}
else
{
for (int32 i = 0; i < FoundPhotons.Num(); i++)
{
StartupDebugOutput.GatheredPhotons.Add(FDebugPhoton(FoundPhotons[i].GetId(), FoundPhotons[i].GetPosition(), FoundPhotons[i].GetIncidentDirection(), FoundPhotons[i].GetSurfaceNormal()));
}
}
}
#endif
return FurthestPhotonDistanceSquared;
}
/** Comparison class to sort an array of photons into ascending order of distance to ComparePosition. */
class FCompareNearestIrradiancePhotons
{
public:
FCompareNearestIrradiancePhotons(const FVector4f& InPosition) :
ComparePosition(InPosition)
{}
inline bool operator()(const FIrradiancePhoton& A, const FIrradiancePhoton& B) const
{
const float DistanceSquaredA = (A.GetPosition() - ComparePosition).SizeSquared3();
const float DistanceSquaredB = (B.GetPosition() - ComparePosition).SizeSquared3();
return DistanceSquaredA < DistanceSquaredB;
}
private:
const FVector4f ComparePosition;
};
/** Finds the nearest irradiance photon, if one exists. */
FIrradiancePhoton* FStaticLightingSystem::FindNearestIrradiancePhoton(
const FMinimalStaticLightingVertex& Vertex,
FStaticLightingMappingContext& MappingContext,
TArray<FIrradiancePhoton*>& TempIrradiancePhotons,
bool bVisibleOnly,
bool bDebugThisLookup) const
{
MappingContext.Stats.NumIrradiancePhotonMapSearches++;
FIrradiancePhoton* ClosestPhoton = NULL;
// Traverse the octree with the maximum distance required
const float SearchDistance = FMath::Max(PhotonMappingSettings.DirectPhotonSearchDistance, PhotonMappingSettings.IndirectPhotonSearchDistance);
float ClosestDistanceSquared = FMath::Square(SearchDistance);
// Empty the temporary array without reallocating
TempIrradiancePhotons.Empty(TempIrradiancePhotons.Num() + TempIrradiancePhotons.GetSlack());
const FBox3f SearchBox = FBox3f::BuildAABB(Vertex.WorldPosition, FVector4f(SearchDistance, SearchDistance, SearchDistance));
{
LIGHTINGSTAT(FScopedRDTSCTimer OctreeTraversal(MappingContext.Stats.IrradiancePhotonOctreeTraversalTime));
for (FIrradiancePhotonOctree::TConstIterator<> OctreeIt(IrradiancePhotonMap); OctreeIt.HasPendingNodes(); OctreeIt.Advance())
{
const FIrradiancePhotonOctree::FNode& CurrentNode = OctreeIt.GetCurrentNode();
const FOctreeNodeContext& CurrentContext = OctreeIt.GetCurrentContext();
// Push children onto the iterator stack if they intersect the query box
if (!CurrentNode.IsLeaf())
{
FOREACH_OCTREE_CHILD_NODE(ChildRef)
{
if (CurrentNode.HasChild(ChildRef))
{
const FOctreeNodeContext ChildContext = CurrentContext.GetChildContext(ChildRef);
if (ChildContext.Bounds.GetBox().Intersect(SearchBox))
{
OctreeIt.PushChild(ChildRef);
}
}
}
}
// Iterate over all photons in the nodes intersecting the query box
for (FIrradiancePhotonOctree::ElementIt MeshIt(CurrentNode.GetElementIt()); MeshIt; ++MeshIt)
{
FIrradiancePhotonElement& PhotonElement = *MeshIt;
FIrradiancePhoton& CurrentPhoton = PhotonElement.GetPhoton();
const FVector4f PhotonToVertexVector = Vertex.WorldPosition - CurrentPhoton.GetPosition();
const float DistanceSquared = PhotonToVertexVector.SizeSquared3();
const float CosTheta = Dot3(Vertex.WorldTangentZ, CurrentPhoton.GetSurfaceNormal());
// Only searching for irradiance photons with normals similar to the search normal
if (CosTheta > PhotonMappingSettings.PhotonSearchAngleThreshold
// And closer to the search position than the max search distance.
&& ((CurrentPhoton.HasDirectContribution() && (DistanceSquared < FMath::Square(PhotonMappingSettings.DirectPhotonSearchDistance)))
|| (!CurrentPhoton.HasDirectContribution() && (DistanceSquared < FMath::Square(PhotonMappingSettings.IndirectPhotonSearchDistance)))))
{
// Only accept irradiance photons within an angle of the plane defined by the vertex normal
// This avoids expensive visibility traces to photons that are probably not on the same surface
const float DirectionDotNormal = Dot3(CurrentPhoton.GetSurfaceNormal(), PhotonToVertexVector.GetSafeNormal());
if (FMath::Abs(DirectionDotNormal) < PhotonMappingSettings.MinCosIrradiancePhotonSearchCone)
{
if (bVisibleOnly)
{
// Store the photon for later, which is faster than tracing a ray here since this may not be the closest photon
TempIrradiancePhotons.Add(&CurrentPhoton);
}
else if (DistanceSquared < ClosestDistanceSquared)
{
// Only accept the closest photon if visibility is not required
ClosestPhoton = &CurrentPhoton;
ClosestDistanceSquared = DistanceSquared;
}
}
}
}
}
}
if (bVisibleOnly)
{
// Sort the photons so the closest photon is in the beginning of the array
FCompareNearestIrradiancePhotons CompareClassInstance(Vertex.WorldPosition);
TempIrradiancePhotons.Sort(CompareClassInstance);
// Trace a ray from the vertex to each irradiance photon until a visible one is found, starting with the closest
for (int32 PhotonIndex = 0; PhotonIndex < TempIrradiancePhotons.Num(); PhotonIndex++)
{
FIrradiancePhoton* CurrentPhoton = TempIrradiancePhotons[PhotonIndex];
const FVector4f VertexToPhoton = CurrentPhoton->GetPosition() - Vertex.WorldPosition;
const FLightRay VertexToPhotonRay(
Vertex.WorldPosition + VertexToPhoton.GetSafeNormal() * SceneConstants.VisibilityRayOffsetDistance + Vertex.WorldTangentZ * SceneConstants.VisibilityNormalOffsetDistance,
CurrentPhoton->GetPosition() + CurrentPhoton->GetSurfaceNormal() * SceneConstants.VisibilityNormalOffsetDistance,
NULL,
NULL
);
MappingContext.Stats.NumIrradiancePhotonSearchRays++;
const float PreviousShadowTraceTime = MappingContext.RayCache.BooleanRayTraceTime;
// Check the line segment for intersection with the static lighting meshes.
FLightRayIntersection Intersection;
AggregateMesh->IntersectLightRay(VertexToPhotonRay, false, false, false, MappingContext.RayCache, Intersection);
MappingContext.Stats.IrradiancePhotonSearchRayTime += MappingContext.RayCache.BooleanRayTraceTime - PreviousShadowTraceTime;
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (bDebugThisLookup && PhotonMappingSettings.bVisualizePhotonGathers)
{
FDebugStaticLightingRay DebugRay(VertexToPhotonRay.Start, VertexToPhotonRay.End, Intersection.bIntersects);
if (Intersection.bIntersects)
{
DebugRay.End = Intersection.IntersectionVertex.WorldPosition;
}
StartupDebugOutput.ShadowRays.Add(DebugRay);
}
#endif
if (!Intersection.bIntersects)
{
// Break on the first visible photon
ClosestPhoton = CurrentPhoton;
break;
}
}
}
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
if (bDebugThisLookup && ClosestPhoton != NULL && PhotonMappingSettings.bVisualizePhotonGathers)
{
StartupDebugOutput.GatheredPhotons.Add(FDebugPhoton(0, ClosestPhoton->GetPosition(), ClosestPhoton->GetSurfaceNormal(), ClosestPhoton->GetSurfaceNormal()));
}
#endif
return ClosestPhoton;
}
/** Calculates the irradiance for an irradiance photon */
FLinearColor FStaticLightingSystem::CalculatePhotonIrradiance(
const FPhotonOctree& PhotonMap,
int32 NumPhotonsEmitted,
int32 NumPhotonsToFind,
float SearchDistance,
const FIrradiancePhoton& IrradiancePhoton,
bool bDebugThisCalculation,
TArray<FPhoton>& TempFoundPhotons,
FCalculateIrradiancePhotonStats& OutStats) const
{
// Empty TempFoundPhotons without causing any allocations / frees
TempFoundPhotons.Empty(TempFoundPhotons.Num() + TempFoundPhotons.GetSlack());
const float MaxFoundDistanceSquared = FindNearbyPhotonsSorted(
PhotonMap,
IrradiancePhoton.GetPosition(),
IrradiancePhoton.GetSurfaceNormal(),
NumPhotonsToFind,
SearchDistance,
false,
bDebugThisCalculation,
TempFoundPhotons,
OutStats);
FLinearColor PhotonIrradiance(FLinearColor::Black);
if (TempFoundPhotons.Num() > 0)
{
LIGHTINGSTAT(FScopedRDTSCTimer CalculateIrradianceTimer(OutStats.CalculateIrradianceThreadTime));
const float MaxFoundDistance = FMath::Sqrt(MaxFoundDistanceSquared);
// Estimate the photon density using a cone filter, from the paper "Global Illumination using Photon Maps"
const float DiskArea = (float)PI * MaxFoundDistanceSquared;
const float ConeFilterNormalizeConstant = 1.0f - 2.0f / (3.0f * PhotonMappingSettings.ConeFilterConstant);
const float ConstantWeight = 1.0f / (ConeFilterNormalizeConstant * NumPhotonsEmitted * DiskArea);
FCoherentRayCache UnusedRayCache;
for (int32 PhotonIndex = 0; PhotonIndex < TempFoundPhotons.Num(); PhotonIndex++)
{
const FPhoton& CurrentPhoton = TempFoundPhotons[PhotonIndex];
if (Dot3(IrradiancePhoton.GetSurfaceNormal(), CurrentPhoton.GetIncidentDirection()) > 0.0f)
{
float SearchNormalScales[2] = {.1f, .4f};
bool bPhotonVisible = false;
// Try to determine visibility to the photon before letting it contribute
// This helps to prevent leaking through thin walls
for (int32 SearchIndex = 0; SearchIndex < UE_ARRAY_COUNT(SearchNormalScales) && !bPhotonVisible; SearchIndex++)
{
const float NormalOffset = SearchDistance * SearchNormalScales[SearchIndex];
const FLightRay Ray(
IrradiancePhoton.GetPosition() + IrradiancePhoton.GetSurfaceNormal() * NormalOffset,
CurrentPhoton.GetPosition() + CurrentPhoton.GetSurfaceNormal() * NormalOffset,
NULL,
NULL
);
FLightRayIntersection RayIntersection;
AggregateMesh->IntersectLightRay(Ray, false, false, false, UnusedRayCache, RayIntersection);
bPhotonVisible = !RayIntersection.bIntersects;
}
if (bPhotonVisible)
{
const float PhotonDistance = (CurrentPhoton.GetPosition() - IrradiancePhoton.GetPosition()).Size3();
const float ConeWeight = FMath::Max(1.0f - PhotonDistance / (PhotonMappingSettings.ConeFilterConstant * MaxFoundDistance), 0.0f);
PhotonIrradiance += CurrentPhoton.GetPower() * ConeWeight * ConstantWeight;
}
}
}
}
return PhotonIrradiance;
}
/** Calculates incident radiance at a vertex from the given photon map. */
FGatheredLightSample FStaticLightingSystem::CalculatePhotonIncidentRadiance(
const FPhotonOctree& PhotonMap,
int32 NumPhotonsEmitted,
float SearchDistance,
const FStaticLightingVertex& Vertex,
bool bDebugThisDensityEstimation) const
{
TArray<FPhoton> FoundPhotons;
FFindNearbyPhotonStats DummyStats;
FindNearbyPhotonsIterative(PhotonMap, Vertex.WorldPosition, Vertex.WorldTangentZ, 1, SearchDistance, SearchDistance, bDebugThisDensityEstimation, false, FoundPhotons, DummyStats);
FGatheredLightSample PhotonIncidentRadiance;
if (FoundPhotons.Num() > 0)
{
// Estimate the photon density using a cone filter, from the paper "Global Illumination using Photon Maps"
const float DiskArea = (float)PI * SearchDistance * SearchDistance;
const float ConeFilterNormalizeConstant = 1.0f - 2.0f / (3.0f * PhotonMappingSettings.ConeFilterConstant);
const float ConstantWeight = 1.0f / (ConeFilterNormalizeConstant * NumPhotonsEmitted * DiskArea);
for (int32 PhotonIndex = 0; PhotonIndex < FoundPhotons.Num(); PhotonIndex++)
{
const FPhoton& CurrentPhoton = FoundPhotons[PhotonIndex];
const FVector4f TangentPathDirection = Vertex.TransformWorldVectorToTangent(CurrentPhoton.GetIncidentDirection());
if (TangentPathDirection.Z > 0)
{
const float PhotonDistance = (CurrentPhoton.GetPosition() - Vertex.WorldPosition).Size3();
const float ConeWeight = FMath::Max(1.0f - PhotonDistance / (PhotonMappingSettings.ConeFilterConstant * SearchDistance), 0.0f);
PhotonIncidentRadiance.AddWeighted(FGatheredLightSampleUtil::PointLightWorldSpace<2>(CurrentPhoton.GetPower(), TangentPathDirection, CurrentPhoton.GetIncidentDirection()), ConeWeight * ConstantWeight);
}
}
}
return PhotonIncidentRadiance;
}
/** Calculates exitant radiance at a vertex from the given photon map. */
FLinearColor FStaticLightingSystem::CalculatePhotonExitantRadiance(
const FPhotonOctree& PhotonMap,
int32 NumPhotonsEmitted,
float SearchDistance,
const FStaticLightingMesh* Mesh,
const FMinimalStaticLightingVertex& Vertex,
int32 ElementIndex,
const FVector4f& OutgoingDirection,
bool bDebugThisDensityEstimation) const
{
TArray<FPhoton> FoundPhotons;
FFindNearbyPhotonStats DummyStats;
FindNearbyPhotonsIterative(PhotonMap, Vertex.WorldPosition, Vertex.WorldTangentZ, 1, SearchDistance, SearchDistance, bDebugThisDensityEstimation, false, FoundPhotons, DummyStats);
FLinearColor AccumulatedRadiance(FLinearColor::Black);
if (FoundPhotons.Num() > 0)
{
// Estimate the photon density using a cone filter, from the paper "Global Illumination using Photon Maps"
const float DiskArea = (float)PI * SearchDistance * SearchDistance;
const float ConeFilterNormalizeConstant = 1.0f - 2.0f / (3.0f * PhotonMappingSettings.ConeFilterConstant);
const float ConstantWeight = 1.0f / (ConeFilterNormalizeConstant * NumPhotonsEmitted * DiskArea);
for (int32 PhotonIndex = 0; PhotonIndex < FoundPhotons.Num(); PhotonIndex++)
{
const FPhoton& CurrentPhoton = FoundPhotons[PhotonIndex];
if (Dot3(Vertex.WorldTangentZ, CurrentPhoton.GetIncidentDirection()) > 0.0f)
{
const float PhotonDistance = (CurrentPhoton.GetPosition() - Vertex.WorldPosition).Size3();
const float ConeWeight = FMath::Max(1.0f - PhotonDistance / (PhotonMappingSettings.ConeFilterConstant * SearchDistance), 0.0f);
const FLinearColor BRDF = Mesh->EvaluateBRDF(Vertex, ElementIndex, CurrentPhoton.GetIncidentDirection(), OutgoingDirection);
AccumulatedRadiance += CurrentPhoton.GetPower() * ConeWeight * ConstantWeight * BRDF;
}
}
}
return AccumulatedRadiance;
}
}