You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb None #lockdown Nick.Penwarden ============================ MAJOR FEATURES & CHANGES ============================ Change 3629223 by Rolando.Caloca DR - Rollback //UE4/Dev-Rendering/Engine/Source/Runtime/VulkanRHI to changelist 3627847 Change 3629708 by Rolando.Caloca DR - vk - Redo some changes from DevMobile 3601439 3604186 3606672 3617383 3617474 3617483 Change 3761370 by Arne.Schober DR - Added CityHash to use with conatiners and stuff. It provides good performance and high quallity across multiple platforms. Change 3761437 by Guillaume.Abadie Optimises motion blur compute shader for consoles. Change 3761483 by Guillaume.Abadie Fixes D3D11 RHI lying to dynamic resolution heuristic with t.MaxFPS. Change 3761995 by Mark.Satterthwaite Add the Metal compiler path to the local .pch filename to avoid problems when Xcode moves. Change 3761996 by Mark.Satterthwaite Emit more details when a pixel shader is found to have no outputs at all which Metal doesn't permit. More likely this is a bug in the shader compiler not configuring the in-out mask correctly... #jira UE-52292 Change 3761999 by Mark.Satterthwaite No need to avoid tessellation for FMetalRHICommandContext::RHIEndDrawIndexedPrimitiveUP anymore - that was from back when the tessellation logic was replicated in each RHI*Draw* implementation. #jira UE-51937 Change 3762181 by Joe.Graf Changed MaxShaderJobBatchSize to 25 on Mac as it reduced shader compile time by 21% Change 3762607 by Mark.Satterthwaite Remove accidentally included changes from 3761995. Change 3762612 by Mark.Satterthwaite Enable the explicit sincos intrinsic for Metal to avoid instances of UE-52477 that can cause shaders to compile incorrectly through hlslcc. #jira UE-52477 Change 3762772 by Michael.Lentine Move RHI calls to render thread. Change 3763021 by Richard.Wallis Remove shader cache tool project and implementation. #jira UE-51613 Change 3763082 by Guillaume.Abadie More SceneTexture, SceneColor and SceneDepth automated tests Change 3763111 by Richard.Wallis Clone of CL 3763033 (Release-4.18): Fix for crash upon launching packaged game on Mac with Share Material Shader Code enabled. #jira UE-52121 Change 3763657 by Michael.Lentine Invalidate ddc for skeletal mesh render data so that the duplicated vertex render structures are properly serialized. Change 3763727 by Jian.Ru Fix Player Collision view mode. It is caused by checking an uninitialized vertex buffer so the check always fail. #jira UE-52052 Change 3763738 by Guillaume.Abadie Implements SSR input post process material location. Change 3764271 by Mark.Satterthwaite Allow ControlPointPatch lists to flow through MetalRHI as it was setup to handle this transparently - the VSHS compute shader will convert them to triangles to draw. Report the same warning as in the pipeline creation stage as this hasn't been formally validated. #jira UE-52454 Change 3764316 by Daniel.Wright Added AVolumetricLightmapDensityVolume - gives local control over Volumetric Lightmap density. Dropping the top mip outside of the play area in Monolith saves 20Mb (35Mb original). Volumetric Lightmap no longer refines around static translucent geometry - saves 5Mb in Monolith Reworked brick culling by error mechanism. Now compares error to interpolated parent lighting instead of the brick average - prevents dropping constant value bricks which are near a wall and cause leaking due to parent interpolation after being culled. Change 3764318 by Daniel.Wright Missing file Change 3764321 by Daniel.Wright Shader compiling memory optimizations * Editor memory: Sharing uniform buffer includes and GeneratedInstancedStereo.ush per FShaderType (was previously duplicated per FShader job) * SCW input size: Sharing uniform buffer includes and SharedEnvironment per batch * 7.6Gb of shader job inputs in memory -> .5Gb (13x less) when doing a full shader compile of Paragon Editor * 13.8Gb written into worker input files -> 2.9Gb (4.7x less). Global shaders are never batched when sent to SCW so unoptimized by these changes. Change 3764595 by Daniel.Wright Added VolumetricLightmapDensityVolume asset icons Change 3764701 by Michael.Lentine Add duplicated vertices merging for meshmerge. Change 3766002 by Guillaume.Abadie Fixes a crash in translucency. Change 3766007 by Guillaume.Abadie Oups.... Fixes compilation failure. Change 3766697 by Guillaume.Abadie Giant refactor of global shader interface for upcoming native support of permutation. CL generated by python script. Change 3767205 by Chris.Bunner Deferring FMaterial::RenderingThreadShaderMap update to render-thread rather than assumption commands have been flushed. #jira UE-50652 Change 3767207 by Chris.Bunner Clamp fetched texture coordinates to those available on the mesh. Change 3767209 by Chris.Bunner PR #4203: Early-outs in UMaterialInstance parameter setters (Contributed by stefanzimecki) #jira UE-52193 Change 3767772 by Mark.Satterthwaite MetalShaderFormat will no longer fallback to text shaders when you ask it to compile to bytecode but the bytecode compiler is not available (either locally or remotely) - this ensures that the DDC can't be poisoned by incorrectly configured clients. The Editor is already setup such that if the remote shader compiler is not configured & Xcode is not available locally the shader-compiler will be invoked to generate text shaders. #jira UE-52554 Change 3768604 by Guillaume.Abadie Polish up with new global shader function signature. Change 3768993 by Guillaume.Abadie Fixes r.Upscale.Panini cvars Change 3769478 by Mark.Satterthwaite Move the ue4_stdlib.metal & PCH into a temporary directory that exists for the lifetime of the SCW on the remote side as well as the local one and add this path as an include directory. #jira UE-52587 Change 3769703 by Mark.Satterthwaite For all Metal platforms >= Metal v1.2 transform mul(a,b) into fma(a,b,0) to prevent the Apple compiler reordering operations differently between the base & depth passes which results in variance in the position output. For iOS disable fast-math when the vertex-shader uses World-Position-Offset because there are additional problems on the iOS shader compiler that result in position variance even with the above fix - WPO performance will suffer but I don't have any alternatives. Remove the depth-offset hack from the depth-only vertex shader again. #jira UES-5651 Change 3769763 by Mark.Satterthwaite Handle swizzle's in the hlslcc fma identification pass so that we reduce the number of instructions and the platform compiler can't break the instructions up. Change 3769849 by Mark.Satterthwaite Fix CIS error. Change 3770517 by Richard.Wallis Fix for crash when creating a new media texture (AppleIntelHD5000GraphicsMTLDriver!SamplerStage::bindSamplerToTexture()). Missing texture resource for binding. Old InitDynamicRHI() code has been refactored out into seperate functions which leaves us on Mac with a NULL resource initially after creation which Metal doesn't like. This fix puts InitDynamicRHI down the default setup/clear path which inits default resources - I don't think we should use a global dummy in this instance as this is a render target. #jira UE-51940 Change 3770688 by Uriel.Doyon Fixed texture resolution returning 0 when running blueprint construction scripts at cook time. Change 3771115 by Mark.Satterthwaite Report errors from failed attempts to compile global shaders or we can't see why things fail on non-Windows platforms. Change 3771263 by Mark.Satterthwaite Change the way ManualVertexFetch is enabled on Metal platforms so that it is enabled when targeting Metal v1.2 and higher (macOS 10.12+/iOS 10+). This brings iOS in the Desktop Forward renderer back into line with the Mac. #jira UERNDR-300 Change3773472by Guillaume.Abadie Fixes a crash on PIE of SimpleComposure project. Change 3773475 by Guillaume.Abadie Fixes bug in editor viewport caused by SSR input changes. Change 3774677 by Arne.Schober DR - Deprecated SetLocal from the RHICmdlist Fixed some unnecessary PSO collisions. Change 3777037 by Mark.Satterthwaite Remove incorrect change that caused a reference to "accurate::sincos" to appear in some Metal shaders rather than "precise::sincos". Change 3777122 by Mark.Satterthwaite Back out changelist 3777037 - I'm blind and wasn't seeing the real problem was a stale shader cache... Change 3777196 by Mark.Satterthwaite Fix text-shader compilation on iOS 10 - maybe iOS 9 too (untested!). We need our own make_scalar type-trait template for ue4_stdlib.metal so that we still compile with older iOS runtime compilers and we can't use as_type to directly implement the packHalf2x16/unpackHalf2x16 intrinsics for these older runtime compilers either. Change 3779098 by Rolando.Caloca DR - vk - Fix query index Change 3779275 by Mark.Satterthwaite Silence the Metal runtime compiler warning caused by use of a deprecated enum value when running text shaders compiled for Metal v1.0/1.1 on a Metal v1.2+ OS. #jira UE-52554 Change 3779427 by Rolando.Caloca DR - vk - Fix for allocator contention Change 3779608 by Uriel.Doyon Fixed invalid access in the resave package commantlet when building texture streaming material data for materials enabling tesselation. Change 3784496 by Mark.Satterthwaite Temporarily disable USE_OBJECT_COMPOSITING_TILE_CULLING for Metal shader compilation only - other platforms are unaffected - as it isn't working properly for some reason. need to work out what's up but don't want Distance Fields to be completely snookered in the interim. #jira UE-52952 Change 3784608 by Rolando.Caloca DR - Copy 3784588 - Fix for drivers returning out of date swapchains during resizes Change 3784734 by Mark.Satterthwaite Real fix for UE-52952 - MetalShaderFormat wasn't propagating the full thread-group value. #jira UE-52952 Change 3784741 by Mark.Satterthwaite More Metal debugging commandline options "-metalfastmath" & "-metalnofastmath" to force fast-math on or off for all shaders, must be using runtime-compiled shaders (i.e. -metalshaderdebug or r.Shaders.Optimise=0) to take effect. Change 3787103 by Guillaume.Abadie Kills BuiltinSamplers UB Change 3787207 by Guillaume.Abadie Sorry, compile fix that were fine with local changes... Change 3787396 by Marcus.Wassmer PR #4271: UE-52901: Set VIS_Max meta to hidden (Contributed by projectgheist) Change 3788028 by Peter.Sumanaseni Working linear HDR exr output from sequencer Change 3788536 by Mark.Satterthwaite Track whether the Metal shader uses the discard_fragment function as when this is used but without any other outputs we know we need to bind at least one render-target or a depth-stencil surface but we don't know which. This lets us correctly error when we encounter a shader with no outputs at all which Metal doesn't permit. #jira UE-52292 Change 3788538 by Mark.Satterthwaite Let's try mitigating UE-46604 on Nvidia by retaining resource references in the command-buffer. This shouldn't be necessary and isn't typically on other vendors but we haven't been able to reproduce this reliably enough to get to the bottom of it. #jira UE-46604 Change 3789083 by Guillaume.Abadie Implements global shader permutations. Example in ScreenSpaceReflections.cpp. Change 3789090 by Guillaume.Abadie Fixes linux build. Change 3789106 by Guillaume.Abadie Fixes compilation failure in niagara plugin. Change 3789274 by Guillaume.Abadie Avoid hit proxies to clobber TAA's hitsory. #jira UE-52968 Change 3789380 by Guillaume.Abadie Back out changelist 3789083: global shader permutation because compilation failure in clang. Change 3789648 by Guillaume.Abadie Relands global shader permutation, with clang support. Change 3789712 by Guillaume.Abadie Fixes TestImage show flag with TAAU on. #jira UE-53061 Change 3791593 by Guillaume.Abadie Reinvalidates shaders with shader permutations. Change 3791884 by Daniel.Wright Added BP setter for LowerHemisphereColor Change 3791886 by Daniel.Wright Added LightmapType to PrimitiveComponent * ForceVolumetric allows forcing static geometry to use Volumetric Lightmaps, which can be useful on instanced foliage where seams are prevalent. Lightmass internal caching still requires lightmap UVs and reasonable lightmap resolution. * ForceSurface replaces bLightAsIfStatic Improvements to Volumetric Lightmap quality needed for static geometry * Stationary light shadowing is now dilated inside geometry * Now doing two dilation passes since samples near geometry see inside due to ray start bias * Refinement around geometry uses an expanded cell bounds when the geometry is going to use Volumetric Lightmaps, since cross-resolution stitching causes leaking Lightmass debug primitives are now tied to a swarm task instead of global - allows debugging of Volumetric Lightmap tasks Change 3792256 by Guillaume.Abadie Fixes a bug where permutation was not actually serialised in FShader, so was ending up recompiling shader at every load. Change 3792884 by Marcus.Wassmer Copying //UE4/Partner-AMD to Dev-Rendering (//UE4/Dev-Rendering) Change 3793200 by Marcus.Wassmer Copying //UE4/Partner-IDV-SpeedTree to Dev-Rendering (//UE4/Dev-Rendering) Speedtree 8 support Change 3793206 by Brian.Karis Added color grading control BlueCorrection to correct for artifacts with "electric" blues due to the ACEScg color space. Bright blue desaturates instead of going to violet. Added color grading control ExpandGamut which expands bright saturated colors outside the sRGB gamut to fake wide gamut rendering. ACES changes. Change 3793344 by Marcus.Wassmer Fix editortest compile Change 3794285 by Guillaume.Abadie Serializes PermutationId according to archive rendering version to avoid issues with old material that were serializing a shader map into UObject. Change 3794307 by Guillaume.Abadie Resaves uassets that were modified between 3789648 and 3794285 Change 3794627 by Mark.Satterthwaite Implement two components for MTLPP, an IMP cache for Objective-C selector implementations & an interposition framework for those same selectors: - imp_SelectorCache & friends provide the IMP caching for each of the Metal protocols which constitute most of the API, so far I've not covered the Metal classes used for the various descriptor/initializer types. Each type has its own IMPTable which caches the selector's implementation pointer and provides the mechanism to hook that implementation. As Objective-C is runtime dynamic this look up must be performed on the actual Class value returned by an object at runtime - you can't do this at compile time. Even things like NSString which appear compile-time static are really not as NSString is an alias for a class-cluster (NSString, NSMutableString, __NSInlineString and more). - The interpose directory contains MTI* files which are the framework for interposing all the functions in Metal's runtime API - I deliberately omit the descriptor classes & read-only functions as there's no benefit to interposing them - which I can build off to create a trace tool or a superior validation layer. Right now this is Mac only as there'll be some problems to solve for iOS/tvOS due to difference in linking requirements - not insurmountable. - Rebuild MTLPP's implementation of the C++ wrapper classes around the IMPTable's - this means we avoid all the objc_msgSend overhead for all the classes and functions whose implementations are cached. Right now the IMPTable is going to incur a look-up for all non-copy/move constructors which is suboptimal - ideally the Metal IMPTables would be cached in the Device object as they will be consistent within a single Device. - Sort out the MTLPP availability logic - it now exports the availability warnings to the caller and internally just blithely assumes it may call the functions, the caller is responsible for ensuring that calls are made only on appropriate devices & OSes. This reduces MTLPP complexity and better fits how MetalRHI works. - Fix a number of retain/release bugs that were lying dormant in MTLPP but exposed by the switch to IMPTables. - Add tvOS support. Next up, put this into MetalRHI and start fixing all the fallout. Change 3794631 by Mark.Satterthwaite Missed updating mtlpp's build.cs for TVOS. Change 3794651 by Uriel.Doyon UPointLightComponent::GetUnitsConversionFactor() now takes the cone angle as parameter. This allows to fix spotlight unit conversion when using lumens. Change 3794720 by Guillaume.Abadie Fixes a bug in Global{Bilinear,Trilinear}ClampedSampler that was actually doing a Point sampling. Change 3794749 by Mark.Satterthwaite Fix mtlpp.build.cs paths. Change3794856by Mark.Satterthwaite Fix some shadowing warnings. Change 3795484 by Daniel.Wright Implemented the Spherical Harmonic windowing algorithm from 'Stupid Spherical Harmonics (SH) Tricks' New WorldSettings Lightmass property VolumetricLightmapSphericalHarmonicSmoothing controls the global amount of smoothing applied Change 3795590 by Brian.Karis Area light fixes Fixed order of operations. This helps mixing of SourceRadius, SourceLength, and SoftSourceRadius. Change 3796832 by Marcus.Wassmer Correct shouldcache condition for new resolve shader Change 3796884 by Marcus.Wassmer Doing it right this time. Change 3797196 by Mark.Satterthwaite More updates to MTLPP to make things simpler and reduce the number of spurious Objective-C warnings that are emitted because of the way we are using the runtime. Change 3797200 by Daniel.Wright Lightmass now uses the highest density VolumetricLightmapDensityVolume settings that affect any part of a cell Change 3797221 by Daniel.Wright Reduced default SphericalHarmonicSmoothing based on RoboRecall tests. Now only active with strong direct lighting from static lights by default. Change 3797411 by Brian.Karis Disable ExpandGamut for old tone mapper. Change 3797462 by Mark.Satterthwaite More build warnings silenced after changing to the lowest possible deployment target OS for each library. Change 3797585 by Mark.Satterthwaite Range-based-For support in the NSArray wrapper. Change 3797836 by Mark.Satterthwaite Even more forward-declarations to avoid system headers poking through to the including code from mtlpp. Change 3798027 by Mark.Satterthwaite Fix handling of nil objects, on which no functions may be called, command-buffer retention and IMP declaration. Change 3798154 by Mark.Satterthwaite Fix some egregious memory leaks that rewriting to use mtlpp exposed before we carry on - don't want these slipping into 4.19. Change 3800990 by Mark.Satterthwaite Typedef all the completion-handler callback types in mtlpp to make future me's life easier. Change 3801400 by Chris.Bunner Improving automated test errors on failure to generate report data. Change 3801726 by Mark.Satterthwaite Correct some function availability and the command-buffer error status in mtlpp. Change 3801808 by Chris.Bunner Added DefaultScalability.ini to EngineTest that forces all quality levels to Engine default Epic for now to improve consistency. Change 3801862 by Marcus.Wassmer Update automated tests with color gamut change Change 3802214 by Chris.Bunner When running automated tests in and editor-locked PIE viewport, skip resizing as the editor can't handle this. Added bindable delegate called when ScreenshotRequest is processed - Useful to allow screenshots to override and restore settings per capture. #jira UE-53188 Change 3802243 by Chris.Bunner Added button to automated test screenshot browser to add or replace all outstanding test reports if appropriate. DeleteAllReports button is now only enabled whilst there are reports in the list. Change 3802372 by Chris.Bunner Updating more test screenshots. Change 3803683 by Chris.Bunner Adding more logging and multiple attempts to automated test report network save. Added small wait on repeated operations that are known to fail. Change 3803826 by Rolando.Caloca DR - vk - Fix merge issue Change 3804181 by Chris.Bunner Tentative fix for CIS test failure. Change 3804236 by Chris.Bunner Additional logging for case where file write silently fails, report platform-specific error. Change 3804303 by zachary.wilson Cleaning up assets in QAGame saved with empty engine versions to resolve warnings seen when launching on Change 3804410 by Chris.Bunner Added additional logging when automated screenshot test fails due to size mismatch. Mismatched bounds are colored red in the delta. Change 3804455 by Mark.Satterthwaite Fix a small number of persistent memory leaks on the Mac build that slowly consume more and more memory as you use the Editor - interacting with menu's was particularly egregious as each NSMenu would leak after you move away. #jira NA Change 3804667 by Chris.Bunner Speculative CIS fixes. Change 3806008 by Chris.Bunner Partially reimplementing backed-out CL 3804181 to improve consistency of how automated screenshot test settings are applied/restored. #tests CIS preflight job 8174412 Change 3806909 by Mark.Satterthwaite Use the vertex-shader's in-out mask to ensure that we only validate legitmate vertex-streams in Metal's DrawIndexedPrimitive implementation. #jira UE-53046 Change 3807059 by laz.matech Checking in QAGame Rendering Map, QA-PhysicalLightingUnits, for testing Physical Light Units. Wanted to get this in before copy up. #Jira none Change 3807726 by Chris.Bunner Removed a check that we can't fix up. The check hits unbound buffers which it assumes means a failure but is actually due to m.v.fetch. We don't have the information available to know which are which removed from the input without reading from the shader. #jira UE-53046 Change 3807800 by Guillaume.Abadie Fixes some warning in shader headers. Change 3807804 by Guillaume.Abadie Back out changelist 3807800 Change 3807807 by Guillaume.Abadie Relands shader header warnings. Change 3808046 by Chris.Bunner Dropping a new automated test error back to a warning as this may lead to genuine issues being ignored in the short term. Change 3809579 by Chris.Bunner Back out changelist 3774677. #jira UE-53483 Change 3809620 by Chris.Bunner Updating animated cloth test screenshot. Change 3803629 by Chris.Bunner Rebuilt CornellBox and DistanceField test maps, updated screenshots. Change 3787045 by Guillaume.Abadie Moves some global samplers to Common.ush Change 3809756 by Chris.Bunner Updating animated cloth test screenshot. [CL 3809764 by Chris Bunner in Main branch]
2556 lines
116 KiB
C++
2556 lines
116 KiB
C++
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "LightingSystem.h"
|
|
#include "Exporter.h"
|
|
#include "LightmassSwarm.h"
|
|
#include "CPUSolver.h"
|
|
#include "MonteCarlo.h"
|
|
#include "Misc/ScopeLock.h"
|
|
#include "UnrealLightmass.h"
|
|
#include "HAL/RunnableThread.h"
|
|
#include "HAL/PlatformProcess.h"
|
|
#include "Misc/OutputDeviceRedirector.h"
|
|
#include "ExceptionHandling.h"
|
|
#if USE_LOCAL_SWARM_INTERFACE
|
|
#include "IMessagingModule.h"
|
|
#include "Async/TaskGraphInterfaces.h"
|
|
#endif
|
|
|
|
#if PLATFORM_WINDOWS
|
|
#include "WindowsHWrapper.h"
|
|
#include "AllowWindowsPlatformTypes.h"
|
|
#include <psapi.h>
|
|
#include "HideWindowsPlatformTypes.h"
|
|
|
|
#pragma comment(lib, "psapi.lib")
|
|
#endif
|
|
|
|
namespace Lightmass
|
|
{
|
|
static void ConvertToLightSampleHelper(const FGatheredLightSample& InGatheredLightSample, float OutCoefficients[2][3])
|
|
{
|
|
// SHCorrection is SHVector sampled with the normal
|
|
float DirCorrection = 1.0f / FMath::Max( 0.0001f, InGatheredLightSample.SHCorrection );
|
|
float DirLuma[4];
|
|
for( int32 i = 0; i < 4; i++ )
|
|
{
|
|
DirLuma[i] = 0.30f * InGatheredLightSample.SHVector.R.V[i];
|
|
DirLuma[i] += 0.59f * InGatheredLightSample.SHVector.G.V[i];
|
|
DirLuma[i] += 0.11f * InGatheredLightSample.SHVector.B.V[i];
|
|
|
|
// Lighting is already in IncidentLighting. Force directional SH as applied to a flat normal map to be 1 to get purely directional data.
|
|
DirLuma[i] *= DirCorrection / PI;
|
|
}
|
|
|
|
// Scale directionality so that DirLuma[0] == 1. Then scale color to compensate and toss DirLuma[0].
|
|
float DirScale = 1.0f / FMath::Max( 0.0001f, DirLuma[0] );
|
|
float ColorScale = DirLuma[0];
|
|
|
|
// IncidentLighting is ground truth for a representative direction, the vertex normal
|
|
OutCoefficients[0][0] = ColorScale * InGatheredLightSample.IncidentLighting.R;
|
|
OutCoefficients[0][1] = ColorScale * InGatheredLightSample.IncidentLighting.G;
|
|
OutCoefficients[0][2] = ColorScale * InGatheredLightSample.IncidentLighting.B;
|
|
|
|
// Will force DirLuma[0] to 0.282095f
|
|
OutCoefficients[1][0] = -0.325735f * DirLuma[1] * DirScale;
|
|
OutCoefficients[1][1] = 0.325735f * DirLuma[2] * DirScale;
|
|
OutCoefficients[1][2] = -0.325735f * DirLuma[3] * DirScale;
|
|
}
|
|
|
|
FLightSample FGatheredLightMapSample::ConvertToLightSample(bool bDebugThisSample) const
|
|
{
|
|
if (bDebugThisSample)
|
|
{
|
|
int32 asdf = 0;
|
|
}
|
|
|
|
FLightSample NewSample;
|
|
NewSample.bIsMapped = bIsMapped;
|
|
|
|
ConvertToLightSampleHelper(HighQuality, &NewSample.Coefficients[0]);
|
|
ConvertToLightSampleHelper(LowQuality, &NewSample.Coefficients[ LM_LQ_LIGHTMAP_COEF_INDEX ]);
|
|
|
|
NewSample.SkyOcclusion[0] = HighQuality.SkyOcclusion.X;
|
|
NewSample.SkyOcclusion[1] = HighQuality.SkyOcclusion.Y;
|
|
NewSample.SkyOcclusion[2] = HighQuality.SkyOcclusion.Z;
|
|
|
|
NewSample.AOMaterialMask = HighQuality.AOMaterialMask;
|
|
|
|
return NewSample;
|
|
}
|
|
|
|
FLightMapData2D* FGatheredLightMapData2D::ConvertToLightmap2D(bool bDebugThisMapping, int32 PaddedDebugX, int32 PaddedDebugY) const
|
|
{
|
|
FLightMapData2D* ConvertedLightMap = new FLightMapData2D(SizeX, SizeY);
|
|
ConvertedLightMap->Lights = Lights;
|
|
ConvertedLightMap->bHasSkyShadowing = bHasSkyShadowing;
|
|
|
|
for (int32 SampleIndex = 0; SampleIndex < Data.Num(); SampleIndex++)
|
|
{
|
|
const bool bDebugThisSample = bDebugThisMapping && SampleIndex == PaddedDebugY * SizeX + PaddedDebugX;
|
|
(*ConvertedLightMap)(SampleIndex, 0) = Data[SampleIndex].ConvertToLightSample(bDebugThisSample);
|
|
}
|
|
return ConvertedLightMap;
|
|
}
|
|
|
|
FStaticLightingMappingContext::FStaticLightingMappingContext(const FStaticLightingMesh* InSubjectMesh, FStaticLightingSystem& InSystem, FDebugLightingOutput* InDebugOutput) :
|
|
FirstBounceCache(InSubjectMesh ? InSubjectMesh->BoundingBox : FBox::BuildAABB(FVector4(0,0,0), FVector4(HALF_WORLD_MAX)), InSystem, 1),
|
|
System(InSystem),
|
|
DebugOutput(InDebugOutput)
|
|
{}
|
|
|
|
FStaticLightingMappingContext::~FStaticLightingMappingContext()
|
|
{
|
|
{
|
|
// Update the main threads stats with the stats from this mapping
|
|
FScopeLock Lock(&System.Stats.StatsSync);
|
|
System.Stats.Cache[0] += FirstBounceCache.Stats;
|
|
System.Stats += Stats;
|
|
System.Stats.NumFirstHitRaysTraced += RayCache.NumFirstHitRaysTraced;
|
|
System.Stats.NumBooleanRaysTraced += RayCache.NumBooleanRaysTraced;
|
|
System.Stats.FirstHitRayTraceThreadTime += RayCache.FirstHitRayTraceTime;
|
|
System.Stats.BooleanRayTraceThreadTime += RayCache.BooleanRayTraceTime;
|
|
}
|
|
|
|
for (int32 EntryIndex = 0; EntryIndex < RefinementTreeFreePool.Num(); EntryIndex++)
|
|
{
|
|
// Delete on the main thread to avoid a TBB inefficiency deleting many same-sized allocations on different threads
|
|
delete RefinementTreeFreePool[EntryIndex];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes this static lighting system, and builds static lighting based on the provided options.
|
|
* @param InOptions - The static lighting build options.
|
|
* @param InScene - The scene containing all the lights and meshes
|
|
* @param InExporter - The exporter used to send completed data back to UE4
|
|
* @param InNumThreads - Number of concurrent threads to use for lighting building
|
|
*/
|
|
FStaticLightingSystem::FStaticLightingSystem(const FLightingBuildOptions& InOptions, FScene& InScene, FLightmassSolverExporter& InExporter, int32 InNumThreads)
|
|
: Options(InOptions)
|
|
, GeneralSettings(InScene.GeneralSettings)
|
|
, SceneConstants(InScene.SceneConstants)
|
|
, MaterialSettings(InScene.MaterialSettings)
|
|
, MeshAreaLightSettings(InScene.MeshAreaLightSettings)
|
|
, DynamicObjectSettings(InScene.DynamicObjectSettings)
|
|
, VolumetricLightmapSettings(InScene.VolumetricLightmapSettings)
|
|
, PrecomputedVisibilitySettings(InScene.PrecomputedVisibilitySettings)
|
|
, VolumeDistanceFieldSettings(InScene.VolumeDistanceFieldSettings)
|
|
, AmbientOcclusionSettings(InScene.AmbientOcclusionSettings)
|
|
, ShadowSettings(InScene.ShadowSettings)
|
|
, ImportanceTracingSettings(InScene.ImportanceTracingSettings)
|
|
, PhotonMappingSettings(InScene.PhotonMappingSettings)
|
|
, IrradianceCachingSettings(InScene.IrradianceCachingSettings)
|
|
, TasksInProgressThatWillNeedHelp(0)
|
|
, NextVolumeSampleTaskIndex(-1)
|
|
, NumVolumeSampleTasksOutstanding(0)
|
|
, bShouldExportVolumeSampleData(false)
|
|
, VolumeLightingInterpolationOctree(FVector4(0,0,0), HALF_WORLD_MAX)
|
|
, bShouldExportMeshAreaLightData(false)
|
|
, bShouldExportVolumeDistanceField(false)
|
|
, NumPhotonsEmittedDirect(0)
|
|
, DirectPhotonMap(FVector4(0,0,0), HALF_WORLD_MAX)
|
|
, NumPhotonsEmittedFirstBounce(0)
|
|
, FirstBouncePhotonMap(FVector4(0,0,0), HALF_WORLD_MAX)
|
|
, FirstBounceEscapedPhotonMap(FVector4(0,0,0), HALF_WORLD_MAX)
|
|
, FirstBouncePhotonSegmentMap(FVector4(0,0,0), HALF_WORLD_MAX)
|
|
, NumPhotonsEmittedSecondBounce(0)
|
|
, SecondBouncePhotonMap(FVector4(0,0,0), HALF_WORLD_MAX)
|
|
, IrradiancePhotonMap(FVector4(0,0,0), HALF_WORLD_MAX)
|
|
, AggregateMesh(NULL)
|
|
, Scene(InScene)
|
|
, NumTexelsCompleted(0)
|
|
, NumOutstandingVolumeDataLayers(0)
|
|
, OutstandingVolumeDataLayerIndex(-1)
|
|
, NumStaticLightingThreads(InScene.GeneralSettings.bAllowMultiThreadedStaticLighting ? FMath::Max(InNumThreads, 1) : 1)
|
|
, DebugIrradiancePhotonCalculationArrayIndex(INDEX_NONE)
|
|
, DebugIrradiancePhotonCalculationPhotonIndex(INDEX_NONE)
|
|
, Exporter(InExporter)
|
|
{
|
|
const double SceneSetupStart = FPlatformTime::Seconds();
|
|
UE_LOG(LogLightmass, Log, TEXT("FStaticLightingSystem started using GKDOPMaxTrisPerLeaf: %d"), GKDOPMaxTrisPerLeaf );
|
|
|
|
ValidateSettings(InScene);
|
|
|
|
bool bDumpAllMappings = false;
|
|
|
|
GroupVisibilityGridSizeXY = 0;
|
|
GroupVisibilityGridSizeZ = 0;
|
|
|
|
// Pre-allocate containers.
|
|
int32 NumMeshes = 0;
|
|
int32 NumVertices = 0;
|
|
int32 NumTriangles = 0;
|
|
int32 NumMappings = InScene.TextureLightingMappings.Num() +
|
|
InScene.FluidMappings.Num() + InScene.LandscapeMappings.Num() + InScene.BspMappings.Num();
|
|
int32 NumMeshInstances = InScene.BspMappings.Num() + InScene.StaticMeshInstances.Num() + InScene.VolumeMappings.Num();
|
|
AllMappings.Reserve( NumMappings );
|
|
Meshes.Reserve( NumMeshInstances );
|
|
|
|
// Initialize Meshes, Mappings, AllMappings and AggregateMesh from the scene
|
|
UE_LOG(LogLightmass, Log, TEXT("Number of texture mappings: %d"), InScene.TextureLightingMappings.Num() );
|
|
for (int32 MappingIndex = 0; MappingIndex < InScene.TextureLightingMappings.Num(); MappingIndex++)
|
|
{
|
|
FStaticMeshStaticLightingTextureMapping* Mapping = &InScene.TextureLightingMappings[MappingIndex];
|
|
Mappings.Add(Mapping->Guid, Mapping);
|
|
AllMappings.Add(Mapping);
|
|
if (bDumpAllMappings)
|
|
{
|
|
UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(Mapping->Guid.ToString()));
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogLightmass, Log, TEXT("Number of fluid mappings: %d"), InScene.FluidMappings.Num());
|
|
for (int32 MappingIndex = 0; MappingIndex < InScene.FluidMappings.Num(); MappingIndex++)
|
|
{
|
|
FFluidSurfaceStaticLightingTextureMapping* Mapping = &InScene.FluidMappings[MappingIndex];
|
|
NumMeshes++;
|
|
NumVertices += Mapping->Mesh->NumVertices;
|
|
NumTriangles += Mapping->Mesh->NumTriangles;
|
|
Mappings.Add(Mapping->Guid, Mapping);
|
|
AllMappings.Add(Mapping);
|
|
if (bDumpAllMappings)
|
|
{
|
|
UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(Mapping->Guid.ToString()));
|
|
}
|
|
}
|
|
|
|
for (int32 MeshIndex = 0; MeshIndex < InScene.FluidMeshInstances.Num(); MeshIndex++)
|
|
{
|
|
Meshes.Add(&InScene.FluidMeshInstances[MeshIndex]);
|
|
}
|
|
|
|
UE_LOG(LogLightmass, Log, TEXT("Number of landscape mappings: %d"), InScene.LandscapeMappings.Num());
|
|
for (int32 MappingIndex = 0; MappingIndex < InScene.LandscapeMappings.Num(); MappingIndex++)
|
|
{
|
|
FLandscapeStaticLightingTextureMapping* Mapping = &InScene.LandscapeMappings[MappingIndex];
|
|
NumMeshes++;
|
|
NumVertices += Mapping->Mesh->NumVertices;
|
|
NumTriangles += Mapping->Mesh->NumTriangles;
|
|
Mappings.Add(Mapping->Guid, Mapping);
|
|
LandscapeMappings.Add(Mapping);
|
|
AllMappings.Add(Mapping);
|
|
if (bDumpAllMappings)
|
|
{
|
|
UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(Mapping->Guid.ToString()));
|
|
}
|
|
}
|
|
|
|
for (int32 MeshIndex = 0; MeshIndex < InScene.LandscapeMeshInstances.Num(); MeshIndex++)
|
|
{
|
|
Meshes.Add(&InScene.LandscapeMeshInstances[MeshIndex]);
|
|
}
|
|
|
|
UE_LOG(LogLightmass, Log, TEXT("Number of BSP mappings: %d"), InScene.BspMappings.Num() );
|
|
for( int32 MeshIdx=0; MeshIdx < InScene.BspMappings.Num(); ++MeshIdx )
|
|
{
|
|
FBSPSurfaceStaticLighting* BSPMapping = &InScene.BspMappings[MeshIdx];
|
|
Meshes.Add(BSPMapping);
|
|
NumMeshes++;
|
|
NumVertices += BSPMapping->NumVertices;
|
|
NumTriangles += BSPMapping->NumTriangles;
|
|
|
|
// add the BSP mappings light mapping object
|
|
AllMappings.Add(&BSPMapping->Mapping);
|
|
Mappings.Add(BSPMapping->Mapping.Guid, &BSPMapping->Mapping);
|
|
if (bDumpAllMappings)
|
|
{
|
|
UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(BSPMapping->Mapping.Guid.ToString()));
|
|
}
|
|
}
|
|
|
|
for (int32 MappingIndex = 0; MappingIndex < InScene.VolumeMappings.Num(); MappingIndex++)
|
|
{
|
|
FStaticLightingGlobalVolumeMapping* Mapping = &InScene.VolumeMappings[MappingIndex];
|
|
Mappings.Add(Mapping->Guid, Mapping);
|
|
AllMappings.Add(Mapping);
|
|
if (bDumpAllMappings)
|
|
{
|
|
UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(Mapping->Guid.ToString()));
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogLightmass, Log, TEXT("Number of static mesh instance mappings: %d"), InScene.StaticMeshInstances.Num() );
|
|
for (int32 MeshIndex = 0; MeshIndex < InScene.StaticMeshInstances.Num(); MeshIndex++)
|
|
{
|
|
FStaticMeshStaticLightingMesh* MeshInstance = &InScene.StaticMeshInstances[MeshIndex];
|
|
FStaticLightingMapping** MappingPtr = Mappings.Find(MeshInstance->Guid);
|
|
if (MappingPtr != NULL)
|
|
{
|
|
MeshInstance->Mapping = *MappingPtr;
|
|
}
|
|
else
|
|
{
|
|
MeshInstance->Mapping = NULL;
|
|
}
|
|
Meshes.Add(MeshInstance);
|
|
NumMeshes++;
|
|
NumVertices += MeshInstance->NumVertices;
|
|
NumTriangles += MeshInstance->NumTriangles;
|
|
}
|
|
|
|
check(Meshes.Num() == AllMappings.Num());
|
|
|
|
int32 MaxVisibilityId = -1;
|
|
for (int32 MeshIndex = 0; MeshIndex < Meshes.Num(); MeshIndex++)
|
|
{
|
|
const FStaticLightingMesh* Mesh = Meshes[MeshIndex];
|
|
for (int32 VisIndex = 0; VisIndex < Mesh->VisibilityIds.Num(); VisIndex++)
|
|
{
|
|
MaxVisibilityId = FMath::Max(MaxVisibilityId, Mesh->VisibilityIds[VisIndex]);
|
|
}
|
|
}
|
|
|
|
VisibilityMeshes.Empty(MaxVisibilityId + 1);
|
|
VisibilityMeshes.AddZeroed(MaxVisibilityId + 1);
|
|
for (int32 MeshIndex = 0; MeshIndex < Meshes.Num(); MeshIndex++)
|
|
{
|
|
FStaticLightingMesh* Mesh = Meshes[MeshIndex];
|
|
for (int32 VisIndex = 0; VisIndex < Mesh->VisibilityIds.Num(); VisIndex++)
|
|
{
|
|
const int32 VisibilityId = Mesh->VisibilityIds[VisIndex];
|
|
if (VisibilityId >= 0)
|
|
{
|
|
VisibilityMeshes[VisibilityId].Meshes.AddUnique(Mesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int32 VisibilityMeshIndex = 0; VisibilityMeshIndex < VisibilityMeshes.Num(); VisibilityMeshIndex++)
|
|
{
|
|
checkSlow(VisibilityMeshes[VisibilityMeshIndex].Meshes.Num() > 0);
|
|
}
|
|
|
|
{
|
|
FScopedRDTSCTimer MeshSetupTimer(Stats.MeshAreaLightSetupTime);
|
|
for (int32 MeshIndex = 0; MeshIndex < Meshes.Num(); MeshIndex++)
|
|
{
|
|
const int32 BckNumMeshAreaLights = MeshAreaLights.Num();
|
|
// Create mesh area lights from each mesh
|
|
Meshes[MeshIndex]->CreateMeshAreaLights(*this, Scene, MeshAreaLights);
|
|
if (MeshAreaLights.Num() > BckNumMeshAreaLights)
|
|
{
|
|
Stats.NumMeshAreaLightMeshes++;
|
|
}
|
|
Meshes[MeshIndex]->SetDebugMaterial(MaterialSettings.bUseDebugMaterial, MaterialSettings.DebugDiffuse);
|
|
}
|
|
}
|
|
|
|
for (int32 MeshIndex = 0; MeshIndex < Meshes.Num(); MeshIndex++)
|
|
{
|
|
for (int32 LightIndex = 0; LightIndex < MeshAreaLights.Num(); LightIndex++)
|
|
{
|
|
// Register the newly created mesh area lights with every relevant mesh so they are used for lighting
|
|
if (MeshAreaLights[LightIndex].AffectsBounds(FBoxSphereBounds(Meshes[MeshIndex]->BoundingBox)))
|
|
{
|
|
Meshes[MeshIndex]->RelevantLights.Add(&MeshAreaLights[LightIndex]);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if USE_EMBREE
|
|
if (Scene.EmbreeDevice)
|
|
{
|
|
if (Scene.bVerifyEmbree)
|
|
{
|
|
AggregateMesh = new FEmbreeVerifyAggregateMesh(Scene);
|
|
}
|
|
else
|
|
{
|
|
AggregateMesh = new FEmbreeAggregateMesh(Scene);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
AggregateMesh = new FDefaultAggregateMesh(Scene);
|
|
}
|
|
// Add all meshes to the kDOP.
|
|
AggregateMesh->ReserveMemory(NumMeshes, NumVertices, NumTriangles);
|
|
|
|
for (int32 MappingIndex = 0; MappingIndex < InScene.FluidMappings.Num(); MappingIndex++)
|
|
{
|
|
FFluidSurfaceStaticLightingTextureMapping* Mapping = &InScene.FluidMappings[MappingIndex];
|
|
AggregateMesh->AddMesh(Mapping->Mesh, Mapping);
|
|
}
|
|
for (int32 MappingIndex = 0; MappingIndex < InScene.LandscapeMappings.Num(); MappingIndex++)
|
|
{
|
|
FLandscapeStaticLightingTextureMapping* Mapping = &InScene.LandscapeMappings[MappingIndex];
|
|
AggregateMesh->AddMesh(Mapping->Mesh, Mapping);
|
|
}
|
|
for( int32 MeshIdx=0; MeshIdx < InScene.BspMappings.Num(); ++MeshIdx )
|
|
{
|
|
FBSPSurfaceStaticLighting* BSPMapping = &InScene.BspMappings[MeshIdx];
|
|
AggregateMesh->AddMesh(BSPMapping, &BSPMapping->Mapping);
|
|
}
|
|
for (int32 MeshIndex = 0; MeshIndex < InScene.StaticMeshInstances.Num(); MeshIndex++)
|
|
{
|
|
FStaticMeshStaticLightingMesh* MeshInstance = &InScene.StaticMeshInstances[MeshIndex];
|
|
AggregateMesh->AddMesh(MeshInstance, MeshInstance->Mapping);
|
|
}
|
|
|
|
// Comparing mappings based on cost, descending.
|
|
struct FCompareProcessingCost
|
|
{
|
|
FORCEINLINE bool operator()( const FStaticLightingMapping& A, const FStaticLightingMapping& B ) const
|
|
{
|
|
return B.GetProcessingCost() < A.GetProcessingCost();
|
|
}
|
|
};
|
|
// Sort mappings by processing cost, descending.
|
|
Mappings.ValueSort(FCompareProcessingCost());
|
|
AllMappings.Sort(FCompareProcessingCost());
|
|
|
|
GStatistics.NumTotalMappings = Mappings.Num();
|
|
|
|
const FBoxSphereBounds SceneBounds = FBoxSphereBounds(AggregateMesh->GetBounds());
|
|
const FBoxSphereBounds ImportanceBounds = GetImportanceBounds();
|
|
// Never trace further than the importance or scene diameter
|
|
MaxRayDistance = ImportanceBounds.SphereRadius > 0.0f ? ImportanceBounds.SphereRadius * 2.0f : SceneBounds.SphereRadius * 2.0f;
|
|
|
|
Stats.NumLights = InScene.DirectionalLights.Num() + InScene.PointLights.Num() + InScene.SpotLights.Num() + MeshAreaLights.Num();
|
|
Stats.NumMeshAreaLights = MeshAreaLights.Num();
|
|
for (int32 i = 0; i < MeshAreaLights.Num(); i++)
|
|
{
|
|
Stats.NumMeshAreaLightPrimitives += MeshAreaLights[i].GetNumPrimitives();
|
|
Stats.NumSimplifiedMeshAreaLightPrimitives += MeshAreaLights[i].GetNumSimplifiedPrimitives();
|
|
}
|
|
|
|
// Add all light types except sky lights to the system's Lights array
|
|
Lights.Reserve(Stats.NumLights);
|
|
for (int32 LightIndex = 0; LightIndex < InScene.DirectionalLights.Num(); LightIndex++)
|
|
{
|
|
InScene.DirectionalLights[LightIndex].Initialize(
|
|
SceneBounds,
|
|
PhotonMappingSettings.bEmitPhotonsOutsideImportanceVolume,
|
|
ImportanceBounds,
|
|
Scene.PhotonMappingSettings.IndirectPhotonEmitDiskRadius,
|
|
Scene.SceneConstants.LightGridSize,
|
|
Scene.PhotonMappingSettings.DirectPhotonDensity,
|
|
Scene.PhotonMappingSettings.DirectPhotonDensity * Scene.PhotonMappingSettings.OutsideImportanceVolumeDensityScale);
|
|
|
|
Lights.Add(&InScene.DirectionalLights[LightIndex]);
|
|
}
|
|
|
|
// Initialize lights and add them to the solver's Lights array
|
|
for (int32 LightIndex = 0; LightIndex < InScene.PointLights.Num(); LightIndex++)
|
|
{
|
|
InScene.PointLights[LightIndex].Initialize(Scene.PhotonMappingSettings.IndirectPhotonEmitConeAngle);
|
|
Lights.Add(&InScene.PointLights[LightIndex]);
|
|
}
|
|
|
|
for (int32 LightIndex = 0; LightIndex < InScene.SpotLights.Num(); LightIndex++)
|
|
{
|
|
InScene.SpotLights[LightIndex].Initialize(Scene.PhotonMappingSettings.IndirectPhotonEmitConeAngle);
|
|
Lights.Add(&InScene.SpotLights[LightIndex]);
|
|
}
|
|
|
|
const FBoxSphereBounds EffectiveImportanceBounds = ImportanceBounds.SphereRadius > 0.0f ? ImportanceBounds : SceneBounds;
|
|
for (int32 LightIndex = 0; LightIndex < MeshAreaLights.Num(); LightIndex++)
|
|
{
|
|
MeshAreaLights[LightIndex].Initialize(Scene.PhotonMappingSettings.IndirectPhotonEmitConeAngle, EffectiveImportanceBounds);
|
|
Lights.Add(&MeshAreaLights[LightIndex]);
|
|
}
|
|
|
|
for (int32 LightIndex = 0; LightIndex < InScene.SkyLights.Num(); LightIndex++)
|
|
{
|
|
SkyLights.Add(&InScene.SkyLights[LightIndex]);
|
|
}
|
|
|
|
//@todo - only count mappings being built
|
|
Stats.NumMappings = AllMappings.Num();
|
|
for (int32 MappingIndex = 0; MappingIndex < AllMappings.Num(); MappingIndex++)
|
|
{
|
|
FStaticLightingTextureMapping* TextureMapping = AllMappings[MappingIndex]->GetTextureMapping();
|
|
if (TextureMapping && !AllMappings[MappingIndex]->GetVolumeMapping())
|
|
{
|
|
Stats.NumTexelsProcessed += TextureMapping->CachedSizeX * TextureMapping->CachedSizeY;
|
|
}
|
|
AllMappings[MappingIndex]->SceneMappingIndex = MappingIndex;
|
|
AllMappings[MappingIndex]->Initialize(*this);
|
|
}
|
|
|
|
InitializePhotonSettings();
|
|
|
|
// Prepare the aggregate mesh for raytracing.
|
|
AggregateMesh->PrepareForRaytracing();
|
|
AggregateMesh->DumpStats();
|
|
|
|
NumCompletedRadiosityIterationMappings.Empty(GeneralSettings.NumSkyLightingBounces);
|
|
NumCompletedRadiosityIterationMappings.AddDefaulted(GeneralSettings.NumSkyLightingBounces);
|
|
|
|
Stats.SceneSetupTime = FPlatformTime::Seconds() - SceneSetupStart;
|
|
GStatistics.SceneSetupTime += Stats.SceneSetupTime;
|
|
|
|
// spread out the work over multiple parallel threads
|
|
MultithreadProcess();
|
|
}
|
|
|
|
FStaticLightingSystem::~FStaticLightingSystem()
|
|
{
|
|
delete AggregateMesh;
|
|
AggregateMesh = NULL;
|
|
}
|
|
|
|
/**
|
|
* Creates multiple worker threads and starts the process locally.
|
|
*/
|
|
void FStaticLightingSystem::MultithreadProcess()
|
|
{
|
|
const double StartTime = FPlatformTime::Seconds();
|
|
|
|
UE_LOG(LogLightmass, Log, TEXT("Processing...") );
|
|
|
|
GStatistics.PhotonsStart = FPlatformTime::Seconds();
|
|
CacheSamples();
|
|
|
|
if (PhotonMappingSettings.bUsePhotonMapping)
|
|
{
|
|
// Build photon maps
|
|
EmitPhotons();
|
|
}
|
|
|
|
if (ImportanceTracingSettings.bUseRadiositySolverForSkylightMultibounce)
|
|
{
|
|
SetupRadiosity();
|
|
RunRadiosityIterations();
|
|
}
|
|
|
|
FinalizeSurfaceCache();
|
|
|
|
if (DynamicObjectSettings.bVisualizeVolumeLightInterpolation)
|
|
{
|
|
// Calculate volume samples now if they will be needed by the lighting threads for shading,
|
|
// Otherwise the volume samples will be calculated when the task is received from swarm.
|
|
BeginCalculateVolumeSamples();
|
|
}
|
|
|
|
SetupPrecomputedVisibility();
|
|
|
|
// Before we spawn the static lighting threads, prefetch tasks they'll be working on
|
|
GSwarm->PrefetchTasks();
|
|
|
|
GStatistics.PhotonsEnd = GStatistics.WorkTimeStart = FPlatformTime::Seconds();
|
|
|
|
const double SequentialThreadedProcessingStart = FPlatformTime::Seconds();
|
|
// Spawn the static lighting threads.
|
|
for(int32 ThreadIndex = 0;ThreadIndex < NumStaticLightingThreads;ThreadIndex++)
|
|
{
|
|
FMappingProcessingThreadRunnable* ThreadRunnable = new(Threads) FMappingProcessingThreadRunnable(this, ThreadIndex, StaticLightingTask_ProcessMappings);
|
|
const FString ThreadName = FString::Printf(TEXT("MappingProcessingThread%u"), ThreadIndex);
|
|
ThreadRunnable->Thread = FRunnableThread::Create(ThreadRunnable, *ThreadName);
|
|
}
|
|
GStatistics.NumThreads = NumStaticLightingThreads + 1; // Includes the main-thread who is only exporting.
|
|
|
|
// Stop the static lighting threads.
|
|
double MaxThreadTime = GStatistics.ThreadStatistics.TotalTime;
|
|
float MaxThreadBusyTime = 0;
|
|
|
|
int32 NumStaticLightingThreadsDone = 0;
|
|
while ( NumStaticLightingThreadsDone < NumStaticLightingThreads )
|
|
{
|
|
for (int32 ThreadIndex = 0; ThreadIndex < Threads.Num(); ThreadIndex++ )
|
|
{
|
|
if ( Threads[ThreadIndex].Thread != NULL )
|
|
{
|
|
// Check to see if the thread has exited with an error
|
|
if ( Threads[ThreadIndex].CheckHealth( true ) )
|
|
{
|
|
// Wait for the thread to exit
|
|
if (Threads[ThreadIndex].IsComplete())
|
|
{
|
|
Threads[ThreadIndex].Thread->WaitForCompletion();
|
|
// Accumulate all thread statistics
|
|
GStatistics.ThreadStatistics += Threads[ThreadIndex].ThreadStatistics;
|
|
MaxThreadTime = FMath::Max<double>(MaxThreadTime, Threads[ThreadIndex].ThreadStatistics.TotalTime);
|
|
if ( GReportDetailedStats )
|
|
{
|
|
UE_LOG(LogLightmass, Log, TEXT("Thread %d finished: %s"), ThreadIndex, *FPlatformTime::PrettyTime(Threads[ThreadIndex].ThreadStatistics.TotalTime) );
|
|
}
|
|
|
|
MaxThreadBusyTime = FMath::Max(MaxThreadBusyTime, Threads[ThreadIndex].ExecutionTime - Threads[ThreadIndex].IdleTime);
|
|
Stats.TotalLightingThreadTime += Threads[ThreadIndex].ExecutionTime - Threads[ThreadIndex].IdleTime;
|
|
|
|
// We're done with the thread object, destroy it
|
|
delete Threads[ThreadIndex].Thread;
|
|
Threads[ThreadIndex].Thread = NULL;
|
|
NumStaticLightingThreadsDone++;
|
|
}
|
|
else
|
|
{
|
|
FPlatformProcess::Sleep(0.01f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to do some mappings while we're waiting for threads to finish
|
|
if ( NumStaticLightingThreadsDone < NumStaticLightingThreads )
|
|
{
|
|
CompleteTextureMappingList.ApplyAndClear( *this );
|
|
ExportNonMappingTasks();
|
|
}
|
|
|
|
#if USE_LOCAL_SWARM_INTERFACE
|
|
FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread);
|
|
#endif
|
|
|
|
GLog->FlushThreadedLogs();
|
|
}
|
|
Threads.Empty();
|
|
GStatistics.WorkTimeEnd = FPlatformTime::Seconds();
|
|
|
|
// Threads will idle when they have no more tasks but before the user accepts the async build changes, so we have to make sure we only count busy time
|
|
Stats.MainThreadLightingTime = (SequentialThreadedProcessingStart - StartTime) + MaxThreadBusyTime;
|
|
|
|
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_ExportingResults, -1 ) );
|
|
|
|
// Apply any outstanding completed mappings.
|
|
CompleteTextureMappingList.ApplyAndClear( *this );
|
|
ExportNonMappingTasks();
|
|
|
|
// Adjust worktime to represent the slowest thread, since that's when all threads were finished.
|
|
// This makes it easier to see how well the actual thread processing is parallelized.
|
|
double Adjustment = (GStatistics.WorkTimeEnd - GStatistics.WorkTimeStart) - MaxThreadTime;
|
|
if ( Adjustment > 0.0 )
|
|
{
|
|
GStatistics.WorkTimeEnd -= Adjustment;
|
|
}
|
|
|
|
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Finished, -1 ) );
|
|
|
|
// Let's say the main thread used up the whole parallel time.
|
|
GStatistics.ThreadStatistics.TotalTime += MaxThreadTime;
|
|
const float FinishAndExportTime = FPlatformTime::Seconds() - GStatistics.WorkTimeEnd;
|
|
DumpStats(Stats.SceneSetupTime + Stats.MainThreadLightingTime + FinishAndExportTime);
|
|
AggregateMesh->DumpCheckStats();
|
|
}
|
|
|
|
/** Exports tasks that are not mappings, if they are ready. */
|
|
void FStaticLightingSystem::ExportNonMappingTasks()
|
|
{
|
|
// Export volume lighting samples to Swarm if they are complete
|
|
if (bShouldExportVolumeSampleData)
|
|
{
|
|
bShouldExportVolumeSampleData = false;
|
|
|
|
Exporter.ExportVolumeLightingSamples(
|
|
DynamicObjectSettings.bVisualizeVolumeLightSamples,
|
|
VolumeLightingDebugOutput,
|
|
VolumeBounds.Origin,
|
|
VolumeBounds.BoxExtent,
|
|
VolumeLightingSamples);
|
|
|
|
// Release volume lighting samples unless they are being used by the lighting threads for shading
|
|
if (!DynamicObjectSettings.bVisualizeVolumeLightInterpolation)
|
|
{
|
|
VolumeLightingSamples.Empty();
|
|
}
|
|
|
|
// Tell Swarm the task is complete (if we're not in debugging mode).
|
|
if ( !IsDebugMode() )
|
|
{
|
|
FLightmassSwarm* Swarm = GetExporter().GetSwarm();
|
|
Swarm->TaskCompleted( PrecomputedVolumeLightingGuid );
|
|
}
|
|
}
|
|
|
|
CompleteVisibilityTaskList.ApplyAndClear(*this);
|
|
CompleteVolumetricLightmapTaskList.ApplyAndClear(*this);
|
|
|
|
{
|
|
TMap<const FLight*, FStaticShadowDepthMap*> CompletedStaticShadowDepthMapsCopy;
|
|
{
|
|
// Enter a critical section before modifying DominantSpotLightShadowInfos since the worker threads may also modify it at any time
|
|
FScopeLock Lock(&CompletedStaticShadowDepthMapsSync);
|
|
CompletedStaticShadowDepthMapsCopy = CompletedStaticShadowDepthMaps;
|
|
CompletedStaticShadowDepthMaps.Empty();
|
|
}
|
|
|
|
for (TMap<const FLight*,FStaticShadowDepthMap*>::TIterator It(CompletedStaticShadowDepthMapsCopy); It; ++It)
|
|
{
|
|
const FLight* Light = It.Key();
|
|
Exporter.ExportStaticShadowDepthMap(Light->Guid, *It.Value());
|
|
|
|
// Tell Swarm the task is complete (if we're not in debugging mode).
|
|
if (!IsDebugMode())
|
|
{
|
|
FLightmassSwarm* Swarm = GetExporter().GetSwarm();
|
|
Swarm->TaskCompleted(Light->Guid);
|
|
}
|
|
|
|
delete It.Value();
|
|
}
|
|
}
|
|
|
|
if (bShouldExportMeshAreaLightData)
|
|
{
|
|
Exporter.ExportMeshAreaLightData(MeshAreaLights, MeshAreaLightSettings.MeshAreaLightGeneratedDynamicLightSurfaceOffset);
|
|
|
|
// Tell Swarm the task is complete (if we're not in debugging mode).
|
|
if ( !IsDebugMode() )
|
|
{
|
|
FLightmassSwarm* Swarm = GetExporter().GetSwarm();
|
|
Swarm->TaskCompleted( MeshAreaLightDataGuid );
|
|
}
|
|
bShouldExportMeshAreaLightData = 0;
|
|
}
|
|
|
|
if (bShouldExportVolumeDistanceField)
|
|
{
|
|
Exporter.ExportVolumeDistanceField(VolumeSizeX, VolumeSizeY, VolumeSizeZ, VolumeDistanceFieldSettings.VolumeMaxDistance, DistanceFieldVolumeBounds, VolumeDistanceField);
|
|
|
|
// Tell Swarm the task is complete (if we're not in debugging mode).
|
|
if ( !IsDebugMode() )
|
|
{
|
|
FLightmassSwarm* Swarm = GetExporter().GetSwarm();
|
|
Swarm->TaskCompleted( VolumeDistanceFieldGuid );
|
|
}
|
|
bShouldExportVolumeDistanceField = 0;
|
|
}
|
|
}
|
|
|
|
int32 FStaticLightingSystem::GetNumShadowRays(int32 BounceNumber, bool bPenumbra) const
|
|
{
|
|
int32 NumShadowRaysResult = 0;
|
|
if (BounceNumber == 0 && bPenumbra)
|
|
{
|
|
NumShadowRaysResult = ShadowSettings.NumPenumbraShadowRays;
|
|
}
|
|
else if (BounceNumber == 0 && !bPenumbra)
|
|
{
|
|
NumShadowRaysResult = ShadowSettings.NumShadowRays;
|
|
}
|
|
else if (BounceNumber > 0)
|
|
{
|
|
// Use less rays for each progressive bounce, since the variance will matter less.
|
|
NumShadowRaysResult = FMath::Max(ShadowSettings.NumBounceShadowRays / BounceNumber, 1);
|
|
}
|
|
return NumShadowRaysResult;
|
|
}
|
|
|
|
int32 FStaticLightingSystem::GetNumUniformHemisphereSamples(int32 BounceNumber) const
|
|
{
|
|
int32 NumSamplesResult = CachedHemisphereSamples.Num();
|
|
checkSlow(BounceNumber > 0);
|
|
return NumSamplesResult;
|
|
}
|
|
|
|
int32 FStaticLightingSystem::GetNumPhotonImportanceHemisphereSamples() const
|
|
{
|
|
return PhotonMappingSettings.bUsePhotonMapping ?
|
|
FMath::TruncToInt(ImportanceTracingSettings.NumHemisphereSamples * PhotonMappingSettings.FinalGatherImportanceSampleFraction) : 0;
|
|
}
|
|
|
|
FBoxSphereBounds FStaticLightingSystem::GetImportanceBounds(bool bClampToScene) const
|
|
{
|
|
FBoxSphereBounds ImportanceBounds = Scene.GetImportanceBounds();
|
|
|
|
if (bClampToScene)
|
|
{
|
|
const FBoxSphereBounds SceneBounds = FBoxSphereBounds(AggregateMesh->GetBounds());
|
|
const float SceneToImportanceOriginSquared = (ImportanceBounds.Origin - SceneBounds.Origin).SizeSquared();
|
|
if (SceneToImportanceOriginSquared > FMath::Square(SceneBounds.SphereRadius))
|
|
{
|
|
// Disable the importance bounds if the center of the importance volume is outside of the scene.
|
|
ImportanceBounds.SphereRadius = 0.0f;
|
|
}
|
|
else if (SceneToImportanceOriginSquared > FMath::Square(SceneBounds.SphereRadius - ImportanceBounds.SphereRadius))
|
|
{
|
|
// Clamp the importance volume's radius so that all parts of it are inside the scene.
|
|
ImportanceBounds.SphereRadius = SceneBounds.SphereRadius - FMath::Sqrt(SceneToImportanceOriginSquared);
|
|
}
|
|
else if (SceneBounds.SphereRadius <= ImportanceBounds.SphereRadius)
|
|
{
|
|
// Disable the importance volume if it is larger than the scene.
|
|
ImportanceBounds.SphereRadius = 0.0f;
|
|
}
|
|
}
|
|
|
|
return ImportanceBounds;
|
|
}
|
|
|
|
/** Returns true if the specified position is inside any of the importance volumes. */
|
|
bool FStaticLightingSystem::IsPointInImportanceVolume(const FVector4& Position, float Tolerance) const
|
|
{
|
|
if (Scene.ImportanceVolumes.Num() > 0)
|
|
{
|
|
return Scene.IsPointInImportanceVolume(Position, Tolerance);
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/** Changes the scene's settings if necessary so that only valid combinations are used */
|
|
void FStaticLightingSystem::ValidateSettings(FScene& InScene)
|
|
{
|
|
//@todo - verify valid ranges of all settings
|
|
|
|
InScene.GeneralSettings.NumIndirectLightingBounces = FMath::Max(InScene.GeneralSettings.NumIndirectLightingBounces, 0);
|
|
InScene.GeneralSettings.IndirectLightingSmoothness = FMath::Clamp(InScene.GeneralSettings.IndirectLightingSmoothness, .25f, 10.0f);
|
|
InScene.GeneralSettings.IndirectLightingQuality = FMath::Clamp(InScene.GeneralSettings.IndirectLightingQuality, .1f, 100.0f);
|
|
InScene.GeneralSettings.ViewSingleBounceNumber = FMath::Min(InScene.GeneralSettings.ViewSingleBounceNumber, InScene.GeneralSettings.NumIndirectLightingBounces);
|
|
|
|
if (FMath::IsNearlyEqual(InScene.PhotonMappingSettings.IndirectPhotonDensity, 0.0f))
|
|
{
|
|
// Allocate all samples toward uniform sampling if there are no indirect photons
|
|
InScene.PhotonMappingSettings.FinalGatherImportanceSampleFraction = 0;
|
|
}
|
|
#if !LIGHTMASS_NOPROCESSING
|
|
if (!InScene.PhotonMappingSettings.bUseIrradiancePhotons)
|
|
#endif
|
|
{
|
|
InScene.PhotonMappingSettings.bCacheIrradiancePhotonsOnSurfaces = false;
|
|
}
|
|
InScene.PhotonMappingSettings.FinalGatherImportanceSampleFraction = FMath::Clamp(InScene.PhotonMappingSettings.FinalGatherImportanceSampleFraction, 0.0f, 1.0f);
|
|
if (FMath::TruncToInt(InScene.ImportanceTracingSettings.NumHemisphereSamples * (1.0f - InScene.PhotonMappingSettings.FinalGatherImportanceSampleFraction) < 1))
|
|
{
|
|
// Irradiance caching needs some uniform samples
|
|
InScene.IrradianceCachingSettings.bAllowIrradianceCaching = false;
|
|
}
|
|
|
|
if (InScene.PhotonMappingSettings.bUsePhotonMapping && !InScene.PhotonMappingSettings.bUseFinalGathering)
|
|
{
|
|
// Irradiance caching currently only supported with final gathering
|
|
InScene.IrradianceCachingSettings.bAllowIrradianceCaching = false;
|
|
}
|
|
|
|
InScene.PhotonMappingSettings.ConeFilterConstant = FMath::Max(InScene.PhotonMappingSettings.ConeFilterConstant, 1.0f);
|
|
if (!InScene.IrradianceCachingSettings.bAllowIrradianceCaching)
|
|
{
|
|
InScene.IrradianceCachingSettings.bUseIrradianceGradients = false;
|
|
}
|
|
|
|
if (InScene.IrradianceCachingSettings.bUseIrradianceGradients)
|
|
{
|
|
// Irradiance gradients require stratified sampling because the information from each sampled cell is used to calculate the gradient
|
|
InScene.ImportanceTracingSettings.bUseStratifiedSampling = true;
|
|
}
|
|
else
|
|
{
|
|
InScene.IrradianceCachingSettings.bShowGradientsOnly = false;
|
|
}
|
|
|
|
if (InScene.DynamicObjectSettings.bVisualizeVolumeLightInterpolation)
|
|
{
|
|
// Disable irradiance caching if we are visualizing volume light interpolation, otherwise we will be getting a twice interpolated result.
|
|
InScene.IrradianceCachingSettings.bAllowIrradianceCaching = false;
|
|
}
|
|
|
|
// Round up to nearest odd number
|
|
ShadowSettings.MinDistanceFieldUpsampleFactor = FMath::Clamp(ShadowSettings.MinDistanceFieldUpsampleFactor - ShadowSettings.MinDistanceFieldUpsampleFactor % 2 + 1, 1, 17);
|
|
ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceX = FMath::Max(ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceX, DELTA);
|
|
ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceY = FMath::Max(ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceY, DELTA);
|
|
|
|
InScene.IrradianceCachingSettings.InterpolationMaxAngle = FMath::Clamp(InScene.IrradianceCachingSettings.InterpolationMaxAngle, 0.0f, 90.0f);
|
|
InScene.IrradianceCachingSettings.PointBehindRecordMaxAngle = FMath::Clamp(InScene.IrradianceCachingSettings.PointBehindRecordMaxAngle, 0.0f, 90.0f);
|
|
InScene.IrradianceCachingSettings.DistanceSmoothFactor = FMath::Max(InScene.IrradianceCachingSettings.DistanceSmoothFactor, 1.0f);
|
|
InScene.IrradianceCachingSettings.AngleSmoothFactor = FMath::Max(InScene.IrradianceCachingSettings.AngleSmoothFactor, 1.0f);
|
|
InScene.IrradianceCachingSettings.SkyOcclusionSmoothnessReduction = FMath::Clamp(InScene.IrradianceCachingSettings.SkyOcclusionSmoothnessReduction, 0.1f, 1.0f);
|
|
|
|
if (InScene.GeneralSettings.IndirectLightingQuality > 50)
|
|
{
|
|
InScene.ImportanceTracingSettings.NumAdaptiveRefinementLevels += 2;
|
|
}
|
|
else if (InScene.GeneralSettings.IndirectLightingQuality > 10)
|
|
{
|
|
InScene.ImportanceTracingSettings.NumAdaptiveRefinementLevels += 1;
|
|
}
|
|
|
|
InScene.ShadowSettings.NumShadowRays = FMath::TruncToInt(InScene.ShadowSettings.NumShadowRays * FMath::Sqrt(InScene.GeneralSettings.IndirectLightingQuality));
|
|
InScene.ShadowSettings.NumPenumbraShadowRays = FMath::TruncToInt(InScene.ShadowSettings.NumPenumbraShadowRays * FMath::Sqrt(InScene.GeneralSettings.IndirectLightingQuality));
|
|
|
|
InScene.ImportanceTracingSettings.NumAdaptiveRefinementLevels = FMath::Min(InScene.ImportanceTracingSettings.NumAdaptiveRefinementLevels, MaxNumRefiningDepths);
|
|
}
|
|
|
|
/** Logs solver stats */
|
|
void FStaticLightingSystem::DumpStats(float TotalStaticLightingTime) const
|
|
{
|
|
FString SolverStats = TEXT("\n\n");
|
|
SolverStats += FString::Printf(TEXT("Total Static Lighting time: %7.2f seconds, %i threads\n"), TotalStaticLightingTime, NumStaticLightingThreads );
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Scene setup\n"), 100.0f * Stats.SceneSetupTime / TotalStaticLightingTime, Stats.SceneSetupTime);
|
|
if (Stats.NumMeshAreaLights > 0)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Mesh Area Light setup\n"), 100.0f * Stats.MeshAreaLightSetupTime / TotalStaticLightingTime, Stats.MeshAreaLightSetupTime);
|
|
}
|
|
|
|
if (PhotonMappingSettings.bUsePhotonMapping)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Emit Direct Photons\n"), 100.0f * Stats.EmitDirectPhotonsTime / TotalStaticLightingTime, Stats.EmitDirectPhotonsTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Cache Indirect Photon Paths\n"), 100.0f * Stats.CachingIndirectPhotonPathsTime / TotalStaticLightingTime, Stats.CachingIndirectPhotonPathsTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Emit Indirect Photons\n"), 100.0f * Stats.EmitIndirectPhotonsTime / TotalStaticLightingTime, Stats.EmitIndirectPhotonsTime);
|
|
if (PhotonMappingSettings.bUseIrradiancePhotons)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Mark %.3f million Irradiance Photons\n"), 100.0f * Stats.IrradiancePhotonMarkingTime / TotalStaticLightingTime, Stats.IrradiancePhotonMarkingTime, Stats.NumIrradiancePhotons / 1000000.0f);
|
|
if (PhotonMappingSettings.bCacheIrradiancePhotonsOnSurfaces)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Cache %.3f million Irradiance Photon Samples on surfaces\n"), 100.0f * Stats.CacheIrradiancePhotonsTime / TotalStaticLightingTime, Stats.CacheIrradiancePhotonsTime, Stats.NumCachedIrradianceSamples / 1000000.0f);
|
|
}
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Calculate %.3f million Irradiance Photons\n"), 100.0f * Stats.IrradiancePhotonCalculatingTime / TotalStaticLightingTime, Stats.IrradiancePhotonCalculatingTime, Stats.NumFoundIrradiancePhotons / 1000000.0f);
|
|
}
|
|
}
|
|
|
|
if (Stats.PrecomputedVisibilitySetupTime / TotalStaticLightingTime > .02f)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs sPVS setup\n"), 100.0f * Stats.PrecomputedVisibilitySetupTime / TotalStaticLightingTime, Stats.PrecomputedVisibilitySetupTime);
|
|
}
|
|
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Lighting\n"), 100.0f * Stats.MainThreadLightingTime / TotalStaticLightingTime, Stats.MainThreadLightingTime);
|
|
const float UnaccountedMainThreadTime = FMath::Max(TotalStaticLightingTime - (Stats.SceneSetupTime + Stats.EmitDirectPhotonsTime + Stats.CachingIndirectPhotonPathsTime + Stats.EmitIndirectPhotonsTime + Stats.IrradiancePhotonMarkingTime + Stats.CacheIrradiancePhotonsTime + Stats.IrradiancePhotonCalculatingTime + Stats.MainThreadLightingTime), 0.0f);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedMainThreadTime / TotalStaticLightingTime, UnaccountedMainThreadTime);
|
|
|
|
// Send the message in multiple parts since it cuts off in the middle otherwise
|
|
LogSolverMessage(SolverStats);
|
|
SolverStats = TEXT("");
|
|
if (PhotonMappingSettings.bUsePhotonMapping)
|
|
{
|
|
if (Stats.EmitDirectPhotonsTime / TotalStaticLightingTime > .02)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("Total Direct Photon Emitting thread seconds: %.1f\n"), Stats.EmitDirectPhotonsThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Sampling Lights\n"), 100.0f * Stats.DirectPhotonsLightSamplingThreadTime / Stats.EmitDirectPhotonsThreadTime, Stats.DirectPhotonsLightSamplingThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Custom attenuation\n"), 100.0f * Stats.DirectCustomAttenuationThreadTime / Stats.EmitDirectPhotonsThreadTime, Stats.DirectCustomAttenuationThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Tracing\n"), 100.0f * Stats.DirectPhotonsTracingThreadTime / Stats.EmitDirectPhotonsThreadTime, Stats.DirectPhotonsTracingThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Processing results\n"), 100.0f * Stats.ProcessDirectPhotonsThreadTime / Stats.EmitDirectPhotonsThreadTime, Stats.ProcessDirectPhotonsThreadTime);
|
|
const float UnaccountedDirectPhotonThreadTime = FMath::Max(Stats.EmitDirectPhotonsThreadTime - (Stats.ProcessDirectPhotonsThreadTime + Stats.DirectPhotonsLightSamplingThreadTime + Stats.DirectPhotonsTracingThreadTime + Stats.DirectCustomAttenuationThreadTime), 0.0f);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedDirectPhotonThreadTime / Stats.EmitDirectPhotonsThreadTime, UnaccountedDirectPhotonThreadTime);
|
|
}
|
|
|
|
if (Stats.EmitIndirectPhotonsTime / TotalStaticLightingTime > .02)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("\n") );
|
|
SolverStats += FString::Printf( TEXT("Total Indirect Photon Emitting thread seconds: %.1f\n"), Stats.EmitIndirectPhotonsThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Sampling Lights\n"), 100.0f * Stats.LightSamplingThreadTime / Stats.EmitIndirectPhotonsThreadTime, Stats.LightSamplingThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Intersect Light rays\n"), 100.0f * Stats.IntersectLightRayThreadTime / Stats.EmitIndirectPhotonsThreadTime, Stats.IntersectLightRayThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs PhotonBounceTracing\n"), 100.0f * Stats.PhotonBounceTracingThreadTime / Stats.EmitIndirectPhotonsThreadTime, Stats.PhotonBounceTracingThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Custom attenuation\n"), 100.0f * Stats.IndirectCustomAttenuationThreadTime / Stats.EmitIndirectPhotonsThreadTime, Stats.IndirectCustomAttenuationThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Processing results\n"), 100.0f * Stats.ProcessIndirectPhotonsThreadTime / Stats.EmitIndirectPhotonsThreadTime, Stats.ProcessIndirectPhotonsThreadTime);
|
|
const float UnaccountedIndirectPhotonThreadTime = FMath::Max(Stats.EmitIndirectPhotonsThreadTime - (Stats.ProcessIndirectPhotonsThreadTime + Stats.LightSamplingThreadTime + Stats.IntersectLightRayThreadTime + Stats.PhotonBounceTracingThreadTime), 0.0f);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedIndirectPhotonThreadTime / Stats.EmitIndirectPhotonsThreadTime, UnaccountedIndirectPhotonThreadTime);
|
|
}
|
|
|
|
if (PhotonMappingSettings.bUseIrradiancePhotons)
|
|
{
|
|
if (PhotonMappingSettings.bCacheIrradiancePhotonsOnSurfaces
|
|
// Only log Irradiance photon caching stats if it was more than 2 percent of the total time
|
|
&& Stats.CacheIrradiancePhotonsTime / TotalStaticLightingTime > .02)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("\n") );
|
|
SolverStats += FString::Printf( TEXT("Total Irradiance Photon Caching thread seconds: %.1f\n"), Stats.IrradiancePhotonCachingThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Octree traversal\n"), 100.0f * Stats.IrradiancePhotonOctreeTraversalTime / Stats.IrradiancePhotonCachingThreadTime, Stats.IrradiancePhotonOctreeTraversalTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs %.3f million Visibility rays\n"), 100.0f * Stats.IrradiancePhotonSearchRayTime / Stats.IrradiancePhotonCachingThreadTime, Stats.IrradiancePhotonSearchRayTime, Stats.NumIrradiancePhotonSearchRays / 1000000.0f);
|
|
const float UnaccountedIrradiancePhotonCachingThreadTime = FMath::Max(Stats.IrradiancePhotonCachingThreadTime - (Stats.IrradiancePhotonOctreeTraversalTime + Stats.IrradiancePhotonSearchRayTime), 0.0f);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedIrradiancePhotonCachingThreadTime / Stats.IrradiancePhotonCachingThreadTime, UnaccountedIrradiancePhotonCachingThreadTime);
|
|
}
|
|
|
|
// Only log Irradiance photon calculating stats if it was more than 2 percent of the total time
|
|
if (Stats.IrradiancePhotonCalculatingTime / TotalStaticLightingTime > .02)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("\n") );
|
|
SolverStats += FString::Printf( TEXT("Total Calculating Irradiance Photons thread seconds: %.1f\n"), Stats.IrradiancePhotonCalculatingThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Pushing Octree Children\n"), 100.0f * Stats.CalculateIrradiancePhotonStats.PushingOctreeChildrenThreadTime / Stats.IrradiancePhotonCalculatingThreadTime, Stats.CalculateIrradiancePhotonStats.PushingOctreeChildrenThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Processing Octree Elements\n"), 100.0f * Stats.CalculateIrradiancePhotonStats.ProcessingOctreeElementsThreadTime / Stats.IrradiancePhotonCalculatingThreadTime, Stats.CalculateIrradiancePhotonStats.ProcessingOctreeElementsThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Finding furthest photon\n"), 100.0f * Stats.CalculateIrradiancePhotonStats.FindingFurthestPhotonThreadTime / Stats.IrradiancePhotonCalculatingThreadTime, Stats.CalculateIrradiancePhotonStats.FindingFurthestPhotonThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Calculating Irradiance\n"), 100.0f * Stats.CalculateIrradiancePhotonStats.CalculateIrradianceThreadTime / Stats.IrradiancePhotonCalculatingThreadTime, Stats.CalculateIrradiancePhotonStats.CalculateIrradianceThreadTime);
|
|
const float UnaccountedCalculateIrradiancePhotonsTime = FMath::Max(Stats.IrradiancePhotonCalculatingThreadTime -
|
|
(Stats.CalculateIrradiancePhotonStats.PushingOctreeChildrenThreadTime + Stats.CalculateIrradiancePhotonStats.ProcessingOctreeElementsThreadTime + Stats.CalculateIrradiancePhotonStats.CalculateIrradianceThreadTime), 0.0f);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedCalculateIrradiancePhotonsTime / Stats.IrradiancePhotonCalculatingThreadTime, UnaccountedCalculateIrradiancePhotonsTime);
|
|
}
|
|
}
|
|
|
|
SolverStats += FString::Printf( TEXT("\n") );
|
|
SolverStats += FString::Printf( TEXT("Radiosity Setup thread seconds: %.1f, Radiosity Iteration thread seconds: %.1f\n"), Stats.RadiositySetupThreadTime, Stats.RadiosityIterationThreadTime);
|
|
}
|
|
|
|
// Send the message in multiple parts since it cuts off in the middle otherwise
|
|
LogSolverMessage(SolverStats);
|
|
SolverStats = TEXT("");
|
|
|
|
const float TotalLightingBusyThreadTime = Stats.TotalLightingThreadTime;
|
|
|
|
SolverStats += FString::Printf( TEXT("\n") );
|
|
SolverStats += FString::Printf( TEXT("Total busy Lighting thread seconds: %.2f\n"), TotalLightingBusyThreadTime);
|
|
const float SampleSetupTime = Stats.VertexSampleCreationTime + Stats.TexelRasterizationTime;
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Texel and vertex setup\n"), 100.0f * SampleSetupTime / TotalLightingBusyThreadTime, SampleSetupTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Direct lighting\n"), 100.0f * Stats.DirectLightingTime / TotalLightingBusyThreadTime, Stats.DirectLightingTime);
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Area shadows with %.3f million rays\n"), 100.0f * Stats.AreaShadowsThreadTime / TotalLightingBusyThreadTime, Stats.AreaShadowsThreadTime, Stats.NumDirectLightingShadowRays / 1000000.0f);
|
|
if (Stats.AreaLightingThreadTime / TotalLightingBusyThreadTime > .04f)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%12.1f%%%8.1fs Area lighting\n"), 100.0f * Stats.AreaLightingThreadTime / TotalLightingBusyThreadTime, Stats.AreaLightingThreadTime);
|
|
}
|
|
|
|
if (Stats.NumSignedDistanceFieldCalculations > 0)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Signed distance field source sparse sampling\n"), 100.0f * Stats.SignedDistanceFieldSourceFirstPassThreadTime / TotalLightingBusyThreadTime, Stats.SignedDistanceFieldSourceFirstPassThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Signed distance field source refining sampling\n"), 100.0f * Stats.SignedDistanceFieldSourceSecondPassThreadTime / TotalLightingBusyThreadTime, Stats.SignedDistanceFieldSourceSecondPassThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Signed distance field transition searching\n"), 100.0f * Stats.SignedDistanceFieldSearchThreadTime / TotalLightingBusyThreadTime, Stats.SignedDistanceFieldSearchThreadTime);
|
|
}
|
|
const float UnaccountedDirectLightingTime = FMath::Max(Stats.DirectLightingTime - (Stats.AreaShadowsThreadTime + Stats.SignedDistanceFieldSourceFirstPassThreadTime + Stats.SignedDistanceFieldSourceSecondPassThreadTime + Stats.SignedDistanceFieldSearchThreadTime), 0.0f);
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedDirectLightingTime / TotalLightingBusyThreadTime, UnaccountedDirectLightingTime);
|
|
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Block on indirect lighting cache tasks\n"), 100.0f * Stats.BlockOnIndirectLightingCacheTasksTime / TotalLightingBusyThreadTime, Stats.BlockOnIndirectLightingCacheTasksTime);
|
|
|
|
if (IrradianceCachingSettings.bAllowIrradianceCaching)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Block on IrradianceCache Interpolation tasks\n"), 100.0f * Stats.BlockOnIndirectLightingInterpolateTasksTime / TotalLightingBusyThreadTime, Stats.BlockOnIndirectLightingInterpolateTasksTime);
|
|
}
|
|
if (Stats.StaticShadowDepthMapThreadTime > 0.1f)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Static shadow depth maps (max %.1fs)\n"), 100.0f * Stats.StaticShadowDepthMapThreadTime / TotalLightingBusyThreadTime, Stats.StaticShadowDepthMapThreadTime, Stats.MaxStaticShadowDepthMapThreadTime);
|
|
}
|
|
if (Stats.VolumeDistanceFieldThreadTime > 0.1f)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Volume distance field\n"), 100.0f * Stats.VolumeDistanceFieldThreadTime / TotalLightingBusyThreadTime, Stats.VolumeDistanceFieldThreadTime);
|
|
}
|
|
const float PrecomputedVisibilityThreadTime = Stats.PrecomputedVisibilityThreadTime;
|
|
if (PrecomputedVisibilityThreadTime > 0.1f)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Precomputed Visibility\n"), 100.0f * PrecomputedVisibilityThreadTime / TotalLightingBusyThreadTime, PrecomputedVisibilityThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Sample generation\n"), 100.0f * Stats.PrecomputedVisibilitySampleSetupThreadTime / TotalLightingBusyThreadTime, Stats.PrecomputedVisibilitySampleSetupThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Uniform tracing\n"), 100.0f * Stats.PrecomputedVisibilityRayTraceThreadTime / TotalLightingBusyThreadTime, Stats.PrecomputedVisibilityRayTraceThreadTime);
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs Importance sampling\n"), 100.0f * Stats.PrecomputedVisibilityImportanceSampleThreadTime / TotalLightingBusyThreadTime, Stats.PrecomputedVisibilityImportanceSampleThreadTime);
|
|
}
|
|
if (Stats.NumDynamicObjectSurfaceSamples + Stats.NumDynamicObjectVolumeSamples > 0)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Volume Sample placement\n"), 100.0f * Stats.VolumeSamplePlacementThreadTime / TotalLightingBusyThreadTime, Stats.VolumeSamplePlacementThreadTime);
|
|
}
|
|
if (Stats.NumVolumetricLightmapSamples > 0)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Volumetric Lightmap - %.3f million samples\n"), 100.0f * Stats.TotalVolumetricLightmapLightingThreadTime / TotalLightingBusyThreadTime, Stats.TotalVolumetricLightmapLightingThreadTime, Stats.NumVolumetricLightmapSamples / 1000000.0f);
|
|
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs VoxelizationTime\n"), 100.0f * Stats.VolumetricLightmapVoxelizationTime / TotalLightingBusyThreadTime, Stats.VolumetricLightmapVoxelizationTime);
|
|
|
|
|
|
if (Stats.VolumetricLightmapGatherImportancePhotonsTime > 0)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs GatherImportancePhotons\n"), 100.0f * Stats.VolumetricLightmapGatherImportancePhotonsTime / TotalLightingBusyThreadTime, Stats.VolumetricLightmapGatherImportancePhotonsTime);
|
|
}
|
|
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs DirectLightingTime\n"), 100.0f * Stats.VolumetricLightmapDirectLightingTime / TotalLightingBusyThreadTime, Stats.VolumetricLightmapDirectLightingTime);
|
|
SolverStats += FString::Printf( TEXT("%8.1f%%%8.1fs FinalGatherTime\n"), 100.0f * Stats.VolumetricLightmapFinalGatherTime / TotalLightingBusyThreadTime, Stats.VolumetricLightmapFinalGatherTime);
|
|
}
|
|
const float UnaccountedLightingThreadTime = FMath::Max(TotalLightingBusyThreadTime - (SampleSetupTime + Stats.DirectLightingTime + Stats.BlockOnIndirectLightingCacheTasksTime + Stats.BlockOnIndirectLightingInterpolateTasksTime + Stats.IndirectLightingCacheTaskThreadTimeSeparateTask + Stats.SecondPassIrradianceCacheInterpolationTime + Stats.SecondPassIrradianceCacheInterpolationTimeSeparateTask + Stats.VolumeSamplePlacementThreadTime + Stats.StaticShadowDepthMapThreadTime + Stats.VolumeDistanceFieldThreadTime + PrecomputedVisibilityThreadTime + Stats.TotalVolumetricLightmapLightingThreadTime), 0.0f);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs Unaccounted\n"), 100.0f * UnaccountedLightingThreadTime / TotalLightingBusyThreadTime, UnaccountedLightingThreadTime);
|
|
// Send the message in multiple parts since it cuts off in the middle otherwise
|
|
LogSolverMessage(SolverStats);
|
|
SolverStats = TEXT("\n");
|
|
|
|
float IndirectLightingCacheTaskThreadTime = Stats.IndirectLightingCacheTaskThreadTime + Stats.IndirectLightingCacheTaskThreadTimeSeparateTask;
|
|
|
|
SolverStats += FString::Printf( TEXT("Indirect lighting cache task thread seconds: %.2f\n"), IndirectLightingCacheTaskThreadTime);
|
|
// These inner loop timings rely on rdtsc to avoid the massive overhead of Query Performance Counter.
|
|
// rdtsc is not dependable with multi-threading (see FRDTSCCycleTimer comments and Intel documentation) but we use it anyway because it's the only option.
|
|
//@todo - rdtsc is also not dependable if the OS changes which processor the thread gets executed on.
|
|
// Use SetThreadAffinityMask to prevent this case.
|
|
if (PhotonMappingSettings.bUsePhotonMapping)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs ImportancePhotonGatherTime\n"), 100.0f * Stats.ImportancePhotonGatherTime / IndirectLightingCacheTaskThreadTime, Stats.ImportancePhotonGatherTime);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs CalculateImportanceSampleTime\n"), 100.0f * Stats.CalculateImportanceSampleTime / IndirectLightingCacheTaskThreadTime, Stats.CalculateImportanceSampleTime);
|
|
}
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs FirstBounceRayTraceTime for %.3f million rays\n"), 100.0f * Stats.FirstBounceRayTraceTime / IndirectLightingCacheTaskThreadTime, Stats.FirstBounceRayTraceTime, Stats.NumFirstBounceRaysTraced / 1000000.0f);
|
|
SolverStats += FString::Printf( TEXT("%4.1f%%%8.1fs CalculateExitantRadiance\n"), 100.0f * Stats.CalculateExitantRadianceTime / IndirectLightingCacheTaskThreadTime, Stats.CalculateExitantRadianceTime);
|
|
|
|
SolverStats += FString::Printf( TEXT("\n") );
|
|
SolverStats += FString::Printf( TEXT("Traced %.3f million first hit visibility rays for a total of %.1f thread seconds (%.3f million per thread second)\n"), Stats.NumFirstHitRaysTraced / 1000000.0f, Stats.FirstHitRayTraceThreadTime, Stats.NumFirstHitRaysTraced / 1000000.0f / Stats.FirstHitRayTraceThreadTime);
|
|
SolverStats += FString::Printf( TEXT("Traced %.3f million boolean visibility rays for a total of %.1f thread seconds (%.3f million per thread second)\n"), Stats.NumBooleanRaysTraced / 1000000.0f, Stats.BooleanRayTraceThreadTime, Stats.NumBooleanRaysTraced / 1000000.0f / Stats.BooleanRayTraceThreadTime);
|
|
const FBoxSphereBounds SceneBounds = FBoxSphereBounds(AggregateMesh->GetBounds());
|
|
const FBoxSphereBounds ImportanceBounds = GetImportanceBounds();
|
|
SolverStats += FString::Printf( TEXT("Scene radius %.1f, Importance bounds radius %.1f\n"), SceneBounds.SphereRadius, ImportanceBounds.SphereRadius);
|
|
SolverStats += FString::Printf( TEXT("%u Mappings, %.3f million Texels, %.3f million mapped texels\n"), Stats.NumMappings, Stats.NumTexelsProcessed / 1000000.0f, Stats.NumMappedTexels / 1000000.0f);
|
|
|
|
// Send the message in multiple parts since it cuts off in the middle otherwise
|
|
LogSolverMessage(SolverStats);
|
|
SolverStats = TEXT("");
|
|
|
|
float TextureMappingThreadTime = Stats.TotalTextureMappingLightingThreadTime + Stats.SecondPassIrradianceCacheInterpolationTimeSeparateTask + Stats.IndirectLightingCacheTaskThreadTimeSeparateTask;
|
|
const float UnaccountedMappingThreadTimePct = 100.0f * FMath::Max(TotalLightingBusyThreadTime - (TextureMappingThreadTime + Stats.TotalVolumeSampleLightingThreadTime + Stats.TotalVolumetricLightmapLightingThreadTime + PrecomputedVisibilityThreadTime), 0.0f) / TotalLightingBusyThreadTime;
|
|
SolverStats += FString::Printf( TEXT("%.1f%% of Total Lighting thread seconds on Texture Mappings, %1.f%% on Volume Samples, %1.f%% on Volumetric Lightmap, %1.f%% on Visibility, %.1f%% Unaccounted\n"), 100.0f * TextureMappingThreadTime / TotalLightingBusyThreadTime, 100.0f * Stats.TotalVolumeSampleLightingThreadTime / TotalLightingBusyThreadTime, 100.0f * Stats.TotalVolumetricLightmapLightingThreadTime / TotalLightingBusyThreadTime, 100.0f * PrecomputedVisibilityThreadTime / TotalLightingBusyThreadTime, UnaccountedMappingThreadTimePct);
|
|
SolverStats += FString::Printf( TEXT("%u Lights total, %.1f Shadow rays per light sample on average\n"), Stats.NumLights, Stats.NumDirectLightingShadowRays / (float)(Stats.NumMappedTexels + Stats.NumVertexSamples));
|
|
if (Stats.NumMeshAreaLights > 0)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%u Emissive meshes, %u Mesh area lights, %lld simplified mesh area light primitives, %lld original primitives\n"), Stats.NumMeshAreaLightMeshes, Stats.NumMeshAreaLights, Stats.NumSimplifiedMeshAreaLightPrimitives, Stats.NumMeshAreaLightPrimitives);
|
|
}
|
|
if (Stats.NumSignedDistanceFieldCalculations > 0)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("Signed distance field shadows: %.1f average upsample factor, %.3f million sparse source rays, %.3f million refining source rays, %.3f million transition search scatters\n"), Stats.AccumulatedSignedDistanceFieldUpsampleFactors / Stats.NumSignedDistanceFieldCalculations, Stats.NumSignedDistanceFieldAdaptiveSourceRaysFirstPass / 1000000.0f, Stats.NumSignedDistanceFieldAdaptiveSourceRaysSecondPass / 1000000.0f, Stats.NumSignedDistanceFieldScatters / 1000000.0f);
|
|
}
|
|
const int32 TotalVolumeLightingSamples = Stats.NumDynamicObjectSurfaceSamples + Stats.NumDynamicObjectVolumeSamples;
|
|
if (TotalVolumeLightingSamples > 0)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%u Volume lighting samples, %.1f%% placed on surfaces, %.1f%% placed in the volume, %.1f thread seconds\n"), TotalVolumeLightingSamples, 100.0f * Stats.NumDynamicObjectSurfaceSamples / (float)TotalVolumeLightingSamples, 100.0f * Stats.NumDynamicObjectVolumeSamples / (float)TotalVolumeLightingSamples, Stats.TotalVolumeSampleLightingThreadTime);
|
|
}
|
|
|
|
if (Stats.NumPrecomputedVisibilityQueries > 0)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("Precomputed Visibility %u Cells (%.1f%% from camera tracks, %u processed on this agent), %u Meshes, %.3f million rays, %.1fKb data\n"), Stats.NumPrecomputedVisibilityCellsTotal, 100.0f * Stats.NumPrecomputedVisibilityCellsCamaraTracks / Stats.NumPrecomputedVisibilityCellsTotal, Stats.NumPrecomputedVisibilityCellsProcessed, Stats.NumPrecomputedVisibilityMeshes, Stats.NumPrecomputedVisibilityRayTraces / 1000000.0f, Stats.PrecomputedVisibilityDataBytes / 1024.0f);
|
|
const uint64 NumQueriesVisible = Stats.NumQueriesVisibleByDistanceRatio + Stats.NumQueriesVisibleExplicitSampling + Stats.NumQueriesVisibleImportanceSampling;
|
|
const uint64 TotalNumQueries = Stats.NumPrecomputedVisibilityQueries + Stats.NumPrecomputedVisibilityGroupQueries;
|
|
SolverStats += FString::Printf( TEXT(" %.3f million mesh queries, %.3f million group queries, %.1f%% visible, (%.1f%% trivially visible, %.1f%% explicit sampling, %.1f%% importance sampling)\n"), Stats.NumPrecomputedVisibilityQueries / 1000000.0f, Stats.NumPrecomputedVisibilityGroupQueries / 1000000.0f, 100.0f * NumQueriesVisible / TotalNumQueries, 100.0f * Stats.NumQueriesVisibleByDistanceRatio / NumQueriesVisible, 100.0f * Stats.NumQueriesVisibleExplicitSampling / NumQueriesVisible, 100.0f * Stats.NumQueriesVisibleImportanceSampling / NumQueriesVisible);
|
|
SolverStats += FString::Printf( TEXT(" %ux%ux%u group cells with %u occupied, %u meshes individually queried, %.3f million mesh queries skipped\n"), GroupVisibilityGridSizeXY, GroupVisibilityGridSizeXY, GroupVisibilityGridSizeZ, VisibilityGroups.Num(), Stats.NumPrecomputedVisibilityMeshesExcludedFromGroups, Stats.NumPrecomputedVisibilityMeshQueriesSkipped / 1000000.0f);
|
|
}
|
|
|
|
// Send the message in multiple parts since it cuts off in the middle otherwise
|
|
LogSolverMessage(SolverStats);
|
|
SolverStats = TEXT("");
|
|
if (PhotonMappingSettings.bUsePhotonMapping)
|
|
{
|
|
const float FirstPassEmittedPhotonEfficiency = 100.0f * FMath::Max(Stats.NumDirectPhotonsGathered, NumIndirectPhotonPaths) / Stats.NumFirstPassPhotonsEmitted;
|
|
SolverStats += FString::Printf( TEXT("%.3f million first pass Photons Emitted (out of %.3f million requested) to deposit %.3f million Direct Photons and %u Indirect Photon Paths, efficiency of %.2f%%\n"), Stats.NumFirstPassPhotonsEmitted / 1000000.0f, Stats.NumFirstPassPhotonsRequested / 1000000.0f, Stats.NumDirectPhotonsGathered / 1000000.0f, NumIndirectPhotonPaths, FirstPassEmittedPhotonEfficiency);
|
|
const float SecondPassEmittedPhotonEfficiency = 100.0f * Stats.NumIndirectPhotonsGathered / Stats.NumSecondPassPhotonsEmitted;
|
|
SolverStats += FString::Printf( TEXT("%.3f million second pass Photons Emitted (out of %.3f million requested) to deposit %.3f million Indirect Photons, efficiency of %.2f%%\n"), Stats.NumSecondPassPhotonsEmitted / 1000000.0f, Stats.NumSecondPassPhotonsRequested / 1000000.0f, Stats.NumIndirectPhotonsGathered / 1000000.0f, SecondPassEmittedPhotonEfficiency);
|
|
SolverStats += FString::Printf( TEXT("%.3f million Photon Gathers, %.3f million Irradiance Photon Gathers\n"), Stats.NumPhotonGathers / 1000000.0f, Stats.NumIrradiancePhotonMapSearches / 1000000.0f);
|
|
SolverStats += FString::Printf( TEXT("%.3f million Importance Photons found, %.3f million Importance Photon PDF calculations\n"), Stats.TotalFoundImportancePhotons / 1000000.0f, Stats.NumImportancePDFCalculations / 1000000.0f);
|
|
if (PhotonMappingSettings.bUseIrradiancePhotons && Stats.IrradiancePhotonCalculatingTime / TotalStaticLightingTime > .02)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%.3f million Irradiance Photons, %.1f%% Direct, %.1f%% Indirect, %.3f million actually found\n"), Stats.NumIrradiancePhotons / 1000000.0f, 100.0f * Stats.NumDirectIrradiancePhotons / Stats.NumIrradiancePhotons, 100.0f * (Stats.NumIrradiancePhotons - Stats.NumDirectIrradiancePhotons) / Stats.NumIrradiancePhotons, Stats.NumFoundIrradiancePhotons / 1000000.0f);
|
|
const float IterationsPerSearch = Stats.CalculateIrradiancePhotonStats.NumSearchIterations / (float)Stats.CalculateIrradiancePhotonStats.NumIterativePhotonMapSearches;
|
|
if (Stats.CalculateIrradiancePhotonStats.NumIterativePhotonMapSearches > 0)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%.1f Irradiance calculating search iterations per search (%.3f million searches, %.3f million iterations)\n"), IterationsPerSearch, Stats.CalculateIrradiancePhotonStats.NumIterativePhotonMapSearches / 1000000.0f, Stats.CalculateIrradiancePhotonStats.NumSearchIterations / 1000000.0f);
|
|
}
|
|
SolverStats += FString::Printf( TEXT("%.3f million octree nodes tested during irradiance photon calculating, %.3f million nodes visited, %.3f million elements tested, %.3f million elements accepted\n"), Stats.CalculateIrradiancePhotonStats.NumOctreeNodesTested / 1000000.0f, Stats.CalculateIrradiancePhotonStats.NumOctreeNodesVisited / 1000000.0f, Stats.CalculateIrradiancePhotonStats.NumElementsTested / 1000000.0f, Stats.CalculateIrradiancePhotonStats.NumElementsAccepted / 1000000.0f);
|
|
}
|
|
}
|
|
if (IrradianceCachingSettings.bAllowIrradianceCaching)
|
|
{
|
|
const int32 NumIrradianceCacheBounces = PhotonMappingSettings.bUsePhotonMapping ? 1 : GeneralSettings.NumIndirectLightingBounces;
|
|
for (int32 BounceIndex = 0; BounceIndex < NumIrradianceCacheBounces; BounceIndex++)
|
|
{
|
|
const FIrradianceCacheStats& CurrentStats = Stats.Cache[BounceIndex];
|
|
if (CurrentStats.NumCacheLookups > 0)
|
|
{
|
|
const float MissRate = 100.0f * CurrentStats.NumRecords / CurrentStats.NumCacheLookups;
|
|
SolverStats += FString::Printf( TEXT("%.1f%% Bounce %i Irradiance cache miss rate (%.3f million lookups, %.3f million misses, %.3f million inside geometry)\n"), MissRate, BounceIndex + 1, CurrentStats.NumCacheLookups / 1000000.0f, CurrentStats.NumRecords / 1000000.0f, CurrentStats.NumInsideGeometry / 1000000.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (PhotonMappingSettings.bUseFinalGathering)
|
|
{
|
|
uint64 TotalNumRefiningSamples = 0;
|
|
|
|
for (int i = 0; i < ImportanceTracingSettings.NumAdaptiveRefinementLevels; i++)
|
|
{
|
|
TotalNumRefiningSamples += Stats.NumRefiningFinalGatherSamples[i];
|
|
}
|
|
|
|
SolverStats += FString::Printf( TEXT("Final Gather: %.1fs on %.3f million base samples, %.1fs on %.3f million refining samples for %u refinement levels. \n"), Stats.BaseFinalGatherSampleTime, Stats.NumBaseFinalGatherSamples / 1000000.0f, Stats.RefiningFinalGatherSampleTime, TotalNumRefiningSamples / 1000000.0f, ImportanceTracingSettings.NumAdaptiveRefinementLevels);
|
|
|
|
if (TotalNumRefiningSamples > 0)
|
|
{
|
|
SolverStats += FString::Printf( TEXT(" %.1f%% due to brightness differences, %.1f%% due to importance photons, %.1f%% other reasons, Samples at depth: "), 100.0f * Stats.NumRefiningSamplesDueToBrightness / TotalNumRefiningSamples, 100.0f * Stats.NumRefiningSamplesDueToImportancePhotons / TotalNumRefiningSamples, 100.0f * Stats.NumRefiningSamplesOther / TotalNumRefiningSamples);
|
|
|
|
for (int i = 0; i < ImportanceTracingSettings.NumAdaptiveRefinementLevels; i++)
|
|
{
|
|
SolverStats += FString::Printf(TEXT("%.1f%%, "), 100.0f * Stats.NumRefiningFinalGatherSamples[i] / TotalNumRefiningSamples);
|
|
}
|
|
|
|
SolverStats += FString::Printf(TEXT("\n"));
|
|
}
|
|
}
|
|
|
|
#if PLATFORM_WINDOWS
|
|
PROCESS_MEMORY_COUNTERS_EX ProcessMemoryInfo;
|
|
if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&ProcessMemoryInfo, sizeof(ProcessMemoryInfo)))
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%.1f Mb Peak Working Set\n"), ProcessMemoryInfo.PeakWorkingSetSize / (1024.0f * 1024.0f));
|
|
}
|
|
else
|
|
{
|
|
SolverStats += TEXT("GetProcessMemoryInfo Failed!");
|
|
}
|
|
SolverStats += FString::Printf( TEXT("\n") );
|
|
#elif PLATFORM_MAC
|
|
{
|
|
struct rusage MemUsage;
|
|
if (getrusage( RUSAGE_SELF, &MemUsage ) == 0)
|
|
{
|
|
SolverStats += FString::Printf( TEXT("%.1f Mb Peak Working Set\n"), MemUsage.ru_maxrss / (1024.0f * 1024.0f));
|
|
}
|
|
else
|
|
{
|
|
SolverStats += TEXT("getrusage() failed!");
|
|
}
|
|
}
|
|
SolverStats += FString::Printf( TEXT("\n") );
|
|
#endif
|
|
|
|
LogSolverMessage(SolverStats);
|
|
|
|
const bool bDumpMemoryStats = false;
|
|
if (bDumpMemoryStats)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
PROCESS_MEMORY_COUNTERS ProcessMemory;
|
|
verify(GetProcessMemoryInfo(GetCurrentProcess(), &ProcessMemory, sizeof(ProcessMemory)));
|
|
UE_LOG(LogLightmass, Log, TEXT("Virtual memory used %.1fMb, Peak %.1fMb"),
|
|
ProcessMemory.PagefileUsage / 1048576.0f,
|
|
ProcessMemory.PeakPagefileUsage / 1048576.0f);
|
|
#elif PLATFORM_MAC
|
|
{
|
|
task_basic_info_64_data_t TaskInfo;
|
|
mach_msg_type_number_t TaskInfoCount = TASK_BASIC_INFO_COUNT;
|
|
task_info( mach_task_self(), TASK_BASIC_INFO, (task_info_t)&TaskInfo, &TaskInfoCount );
|
|
UE_LOG(LogLightmass, Log, TEXT("Virtual memory used %.1fMb"),
|
|
TaskInfo.virtual_size / 1048576.0f); // can't get peak virtual memory on Mac
|
|
}
|
|
#endif
|
|
AggregateMesh->DumpStats();
|
|
UE_LOG(LogLightmass, Log, TEXT("DirectPhotonMap"));
|
|
DirectPhotonMap.DumpStats(false);
|
|
UE_LOG(LogLightmass, Log, TEXT("FirstBouncePhotonMap"));
|
|
FirstBouncePhotonMap.DumpStats(false);
|
|
UE_LOG(LogLightmass, Log, TEXT("FirstBounceEscapedPhotonMap"));
|
|
FirstBounceEscapedPhotonMap.DumpStats(false);
|
|
UE_LOG(LogLightmass, Log, TEXT("FirstBouncePhotonSegmentMap"));
|
|
FirstBouncePhotonSegmentMap.DumpStats(false);
|
|
UE_LOG(LogLightmass, Log, TEXT("SecondBouncePhotonMap"));
|
|
SecondBouncePhotonMap.DumpStats(false);
|
|
UE_LOG(LogLightmass, Log, TEXT("IrradiancePhotonMap"));
|
|
IrradiancePhotonMap.DumpStats(false);
|
|
uint64 IrradiancePhotonCacheBytes = 0;
|
|
for (int32 i = 0; i < AllMappings.Num(); i++)
|
|
{
|
|
IrradiancePhotonCacheBytes += AllMappings[i]->GetIrradiancePhotonCacheBytes();
|
|
}
|
|
UE_LOG(LogLightmass, Log, TEXT("%.3fMb for Irradiance Photon surface caches"), IrradiancePhotonCacheBytes / 1048576.0f);
|
|
}
|
|
}
|
|
|
|
/** Logs a solver message */
|
|
void FStaticLightingSystem::LogSolverMessage(const FString& Message) const
|
|
{
|
|
if (Scene.DebugInput.bRelaySolverStats)
|
|
{
|
|
// Relay the message back to Unreal if allowed
|
|
GSwarm->SendMessage(NSwarm::FInfoMessage(*Message));
|
|
}
|
|
GLog->Log(*Message);
|
|
}
|
|
|
|
/** Logs a progress update message when appropriate */
|
|
void FStaticLightingSystem::UpdateInternalStatus(int32 OldNumTexelsCompleted) const
|
|
{
|
|
const int32 NumProgressSteps = 10;
|
|
|
|
const float InvTotal = 1.0f / Stats.NumTexelsProcessed;
|
|
const float PreviousCompletedFraction = OldNumTexelsCompleted * InvTotal;
|
|
const float CurrentCompletedFraction = NumTexelsCompleted * InvTotal;
|
|
// Only log NumProgressSteps times
|
|
if (FMath::TruncToInt(PreviousCompletedFraction * NumProgressSteps) < FMath::TruncToInt(CurrentCompletedFraction * NumProgressSteps))
|
|
{
|
|
LogSolverMessage(FString::Printf(TEXT("Lighting %.1f%%"), CurrentCompletedFraction * 100.0f));
|
|
}
|
|
}
|
|
|
|
/** Caches samples for any sampling distributions that are known ahead of time, which greatly reduces noise in those estimates in exchange for structured artifacts. */
|
|
void FStaticLightingSystem::CacheSamples()
|
|
{
|
|
FLMRandomStream RandomStream(102341);
|
|
|
|
int32 NumUniformHemisphereSamples = 0;
|
|
|
|
if (PhotonMappingSettings.bUsePhotonMapping)
|
|
{
|
|
const float NumSamplesFloat =
|
|
ImportanceTracingSettings.NumHemisphereSamples
|
|
* GeneralSettings.IndirectLightingQuality;
|
|
|
|
NumUniformHemisphereSamples = FMath::TruncToInt(NumSamplesFloat);
|
|
}
|
|
else
|
|
{
|
|
NumUniformHemisphereSamples = ImportanceTracingSettings.NumHemisphereSamples;
|
|
}
|
|
|
|
CachedHemisphereSamples.Empty(NumUniformHemisphereSamples);
|
|
CachedHemisphereSampleUniforms.Empty(NumUniformHemisphereSamples);
|
|
|
|
if (ImportanceTracingSettings.bUseStratifiedSampling)
|
|
{
|
|
// Split the sampling domain up into cells with equal area
|
|
// Using PI times more Phi steps as Theta steps, but the relationship between them could be anything
|
|
const float NumThetaStepsFloat = FMath::Sqrt(NumUniformHemisphereSamples / (float)PI);
|
|
const int32 NumThetaSteps = FMath::TruncToInt(NumThetaStepsFloat);
|
|
const int32 NumPhiSteps = FMath::TruncToInt(NumThetaStepsFloat * (float)PI);
|
|
|
|
GenerateStratifiedUniformHemisphereSamples(NumThetaSteps, NumPhiSteps, RandomStream, CachedHemisphereSamples, CachedHemisphereSampleUniforms);
|
|
}
|
|
else
|
|
{
|
|
for (int32 SampleIndex = 0; SampleIndex < NumUniformHemisphereSamples; SampleIndex++)
|
|
{
|
|
const FVector4& CurrentSample = GetUniformHemisphereVector(RandomStream, ImportanceTracingSettings.MaxHemisphereRayAngle);
|
|
CachedHemisphereSamples.Add(CurrentSample);
|
|
}
|
|
}
|
|
|
|
{
|
|
FVector4 CombinedVector(0);
|
|
|
|
for (int32 SampleIndex = 0; SampleIndex < CachedHemisphereSamples.Num(); SampleIndex++)
|
|
{
|
|
CombinedVector += CachedHemisphereSamples[SampleIndex];
|
|
}
|
|
|
|
CachedSamplesMaxUnoccludedLength = (CombinedVector / CachedHemisphereSamples.Num()).Size3();
|
|
}
|
|
|
|
for (int32 SampleSet = 0; SampleSet < ARRAY_COUNT(CachedHemisphereSamplesForRadiosity); SampleSet++)
|
|
{
|
|
float SampleSetScale = FMath::Lerp(.5f, .125f, SampleSet / ((float)ARRAY_COUNT(CachedHemisphereSamplesForRadiosity) - 1));
|
|
int32 TargetNumApproximateSkyLightingSamples = FMath::Max(FMath::TruncToInt(ImportanceTracingSettings.NumHemisphereSamples * SampleSetScale * GeneralSettings.IndirectLightingQuality), 12);
|
|
CachedHemisphereSamplesForRadiosity[SampleSet].Empty(TargetNumApproximateSkyLightingSamples);
|
|
CachedHemisphereSamplesForRadiosityUniforms[SampleSet].Empty(TargetNumApproximateSkyLightingSamples);
|
|
|
|
const float NumThetaStepsFloat = FMath::Sqrt(TargetNumApproximateSkyLightingSamples / (float)PI);
|
|
const int32 NumThetaSteps = FMath::TruncToInt(NumThetaStepsFloat);
|
|
const int32 NumPhiSteps = FMath::TruncToInt(NumThetaStepsFloat * (float)PI);
|
|
|
|
GenerateStratifiedUniformHemisphereSamples(NumThetaSteps, NumPhiSteps, RandomStream, CachedHemisphereSamplesForRadiosity[SampleSet], CachedHemisphereSamplesForRadiosityUniforms[SampleSet]);
|
|
}
|
|
|
|
// Cache samples on the surface of each light for area shadows
|
|
for (int32 LightIndex = 0; LightIndex < Lights.Num(); LightIndex++)
|
|
{
|
|
FLight* Light = Lights[LightIndex];
|
|
for (int32 BounceIndex = 0; BounceIndex < GeneralSettings.NumIndirectLightingBounces + 1; BounceIndex++)
|
|
{
|
|
const int32 NumPenumbraTypes = BounceIndex == 0 ? 2 : 1;
|
|
Light->CacheSurfaceSamples(BounceIndex, GetNumShadowRays(BounceIndex, false), GetNumShadowRays(BounceIndex, true), RandomStream);
|
|
}
|
|
}
|
|
|
|
{
|
|
const int32 NumUpperVolumeSamples = ImportanceTracingSettings.NumHemisphereSamples * DynamicObjectSettings.NumHemisphereSamplesScale;
|
|
const float NumThetaStepsFloat = FMath::Sqrt(NumUpperVolumeSamples / (float)PI);
|
|
const int32 NumThetaSteps = FMath::TruncToInt(NumThetaStepsFloat);
|
|
const int32 NumPhiSteps = FMath::TruncToInt(NumThetaStepsFloat * (float)PI);
|
|
|
|
GenerateStratifiedUniformHemisphereSamples(NumThetaSteps, NumPhiSteps, RandomStream, CachedVolumetricLightmapUniformHemisphereSamples, CachedVolumetricLightmapUniformHemisphereSampleUniforms);
|
|
|
|
FVector4 CombinedVector(0);
|
|
|
|
for (int32 SampleIndex = 0; SampleIndex < CachedVolumetricLightmapUniformHemisphereSamples.Num(); SampleIndex++)
|
|
{
|
|
CombinedVector += CachedVolumetricLightmapUniformHemisphereSamples[SampleIndex];
|
|
}
|
|
|
|
CachedVolumetricLightmapMaxUnoccludedLength = (CombinedVector / CachedVolumetricLightmapUniformHemisphereSamples.Num()).Size3();
|
|
}
|
|
|
|
CachedVolumetricLightmapVertexOffsets.Add(FVector(0, 0, 0));
|
|
}
|
|
|
|
bool FStaticLightingThreadRunnable::CheckHealth(bool bReportError) const
|
|
{
|
|
if( bTerminatedByError && bReportError )
|
|
{
|
|
UE_LOG(LogLightmass, Fatal, TEXT("Static lighting thread exception:\r\n%s"), *ErrorMessage );
|
|
}
|
|
return !bTerminatedByError;
|
|
}
|
|
|
|
uint32 FMappingProcessingThreadRunnable::Run()
|
|
{
|
|
const double StartThreadTime = FPlatformTime::Seconds();
|
|
#if PLATFORM_WINDOWS
|
|
if(!FPlatformMisc::IsDebuggerPresent())
|
|
{
|
|
__try
|
|
{
|
|
if (TaskType == StaticLightingTask_ProcessMappings)
|
|
{
|
|
System->ThreadLoop(false, ThreadIndex, ThreadStatistics, IdleTime);
|
|
}
|
|
else if (TaskType == StaticLightingTask_CacheIrradiancePhotons)
|
|
{
|
|
System->CacheIrradiancePhotonsThreadLoop(ThreadIndex, false);
|
|
}
|
|
else if (TaskType == StaticLightingTask_RadiositySetup)
|
|
{
|
|
System->RadiositySetupThreadLoop(ThreadIndex, false);
|
|
}
|
|
else if (TaskType == StaticLightingTask_RadiosityIterations)
|
|
{
|
|
System->RadiosityIterationThreadLoop(ThreadIndex, false);
|
|
}
|
|
else if (TaskType == StaticLightingTask_FinalizeSurfaceCache)
|
|
{
|
|
System->FinalizeSurfaceCacheThreadLoop(ThreadIndex, false);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLightmass, Fatal, TEXT("Unsupported task type"));
|
|
}
|
|
}
|
|
__except( ReportCrash( GetExceptionInformation() ) )
|
|
{
|
|
ErrorMessage = GErrorHist;
|
|
|
|
bTerminatedByError = true;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if (TaskType == StaticLightingTask_ProcessMappings)
|
|
{
|
|
System->ThreadLoop(false, ThreadIndex, ThreadStatistics, IdleTime);
|
|
}
|
|
else if (TaskType == StaticLightingTask_CacheIrradiancePhotons)
|
|
{
|
|
System->CacheIrradiancePhotonsThreadLoop(ThreadIndex, false);
|
|
}
|
|
else if (TaskType == StaticLightingTask_RadiositySetup)
|
|
{
|
|
System->RadiositySetupThreadLoop(ThreadIndex, false);
|
|
}
|
|
else if (TaskType == StaticLightingTask_RadiosityIterations)
|
|
{
|
|
System->RadiosityIterationThreadLoop(ThreadIndex, false);
|
|
}
|
|
else if (TaskType == StaticLightingTask_FinalizeSurfaceCache)
|
|
{
|
|
System->FinalizeSurfaceCacheThreadLoop(ThreadIndex, false);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLightmass, Fatal, TEXT("Unsupported task type"));
|
|
}
|
|
}
|
|
ExecutionTime = FPlatformTime::Seconds() - StartThreadTime;
|
|
FinishedCounter.Increment();
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the next task from Swarm. Blocking, thread-safe function call. Returns NULL when there are no more tasks.
|
|
* @return The next mapping task to process.
|
|
*/
|
|
FStaticLightingMapping* FStaticLightingSystem::ThreadGetNextMapping(
|
|
FThreadStatistics& ThreadStatistics,
|
|
FGuid& TaskGuid,
|
|
uint32 WaitTime,
|
|
bool& bWaitTimedOut,
|
|
bool& bDynamicObjectTask,
|
|
int32& PrecomputedVisibilityTaskIndex,
|
|
int32& VolumetricLightmapTaskIndex,
|
|
bool& bStaticShadowDepthMapTask,
|
|
bool& bMeshAreaLightDataTask,
|
|
bool& bVolumeDataTask)
|
|
{
|
|
FStaticLightingMapping* Mapping = NULL;
|
|
|
|
// Initialize output parameters
|
|
bWaitTimedOut = true;
|
|
bDynamicObjectTask = false;
|
|
PrecomputedVisibilityTaskIndex = INDEX_NONE;
|
|
VolumetricLightmapTaskIndex = INDEX_NONE;
|
|
bStaticShadowDepthMapTask = false;
|
|
bMeshAreaLightDataTask = false;
|
|
bVolumeDataTask = false;
|
|
|
|
if ( GDebugMode )
|
|
{
|
|
FScopeLock Lock( &CriticalSection );
|
|
bWaitTimedOut = false;
|
|
|
|
// If we're in debugging mode, just grab the next mapping from the scene.
|
|
TMap<FGuid, FStaticLightingMapping*>::TIterator It(Mappings);
|
|
if ( It )
|
|
{
|
|
Mapping = It.Value();
|
|
It.RemoveCurrent();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Request a new task from Swarm.
|
|
FLightmassSwarm* Swarm = Exporter.GetSwarm();
|
|
double SwarmRequestStart = FPlatformTime::Seconds();
|
|
bool bGotTask = Swarm->RequestTask( TaskGuid, WaitTime );
|
|
double SwarmRequestEnd = FPlatformTime::Seconds();
|
|
if ( bGotTask )
|
|
{
|
|
if (TaskGuid == PrecomputedVolumeLightingGuid)
|
|
{
|
|
bDynamicObjectTask = true;
|
|
Swarm->AcceptTask( TaskGuid );
|
|
bWaitTimedOut = false;
|
|
}
|
|
else if (TaskGuid == MeshAreaLightDataGuid)
|
|
{
|
|
bMeshAreaLightDataTask = true;
|
|
Swarm->AcceptTask( TaskGuid );
|
|
bWaitTimedOut = false;
|
|
}
|
|
else if (TaskGuid == VolumeDistanceFieldGuid)
|
|
{
|
|
bVolumeDataTask = true;
|
|
Swarm->AcceptTask( TaskGuid );
|
|
bWaitTimedOut = false;
|
|
}
|
|
else if (Scene.FindLightByGuid(TaskGuid))
|
|
{
|
|
bStaticShadowDepthMapTask = true;
|
|
Swarm->AcceptTask( TaskGuid );
|
|
bWaitTimedOut = false;
|
|
}
|
|
else
|
|
{
|
|
int32 FoundVisibilityIndex = INDEX_NONE;
|
|
Scene.VisibilityBucketGuids.Find(TaskGuid, FoundVisibilityIndex);
|
|
|
|
if (FoundVisibilityIndex >= 0)
|
|
{
|
|
PrecomputedVisibilityTaskIndex = FoundVisibilityIndex;
|
|
Swarm->AcceptTask(TaskGuid);
|
|
bWaitTimedOut = false;
|
|
}
|
|
else
|
|
{
|
|
int32 FoundVolumetricLightmapIndex = INDEX_NONE;
|
|
Scene.VolumetricLightmapTaskGuids.Find(TaskGuid, FoundVolumetricLightmapIndex);
|
|
|
|
if (FoundVolumetricLightmapIndex >= 0)
|
|
{
|
|
VolumetricLightmapTaskIndex = FoundVolumetricLightmapIndex;
|
|
Swarm->AcceptTask(TaskGuid);
|
|
bWaitTimedOut = false;
|
|
}
|
|
else
|
|
{
|
|
FStaticLightingMapping** MappingPtr = Mappings.Find(TaskGuid);
|
|
if (MappingPtr && FPlatformAtomics::InterlockedExchange(&(*MappingPtr)->bProcessed, true) == false)
|
|
{
|
|
// We received a new mapping to process. Tell Swarm we accept the task.
|
|
Swarm->AcceptTask(TaskGuid);
|
|
bWaitTimedOut = false;
|
|
Mapping = *MappingPtr;
|
|
}
|
|
else
|
|
{
|
|
// Couldn't find the mapping. Tell Swarm we reject the task and try again later.
|
|
UE_LOG(LogLightmass, Log, TEXT("Lightmass - Rejecting task (%08X%08X%08X%08X)!"), TaskGuid.A, TaskGuid.B, TaskGuid.C, TaskGuid.D);
|
|
Swarm->RejectTask(TaskGuid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( Swarm->ReceivedQuitRequest() || Swarm->IsDone() )
|
|
{
|
|
bWaitTimedOut = false;
|
|
}
|
|
ThreadStatistics.SwarmRequestTime += SwarmRequestEnd - SwarmRequestStart;
|
|
}
|
|
return Mapping;
|
|
}
|
|
|
|
void FStaticLightingSystem::ThreadLoop(bool bIsMainThread, int32 ThreadIndex, FThreadStatistics& ThreadStatistics, float& IdleTime)
|
|
{
|
|
const double ThreadTimeStart = FPlatformTime::Seconds();
|
|
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Processing0, ThreadIndex ) );
|
|
|
|
bool bSignaledMappingsComplete = false;
|
|
bool bIsDone = false;
|
|
while (!bIsDone)
|
|
{
|
|
const double StartLoopTime = FPlatformTime::Seconds();
|
|
|
|
if (NumOutstandingVolumeDataLayers > 0)
|
|
{
|
|
const int32 ThreadZ = FPlatformAtomics::InterlockedIncrement(&OutstandingVolumeDataLayerIndex);
|
|
if (ThreadZ < VolumeSizeZ)
|
|
{
|
|
CalculateVolumeDistanceFieldWorkRange(ThreadZ);
|
|
const int32 NumTasksRemaining = FPlatformAtomics::InterlockedDecrement(&NumOutstandingVolumeDataLayers);
|
|
if (NumTasksRemaining == 0)
|
|
{
|
|
FPlatformAtomics::InterlockedExchange(&bShouldExportVolumeDistanceField, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 DefaultRequestForTaskTimeout = 0;
|
|
FGuid TaskGuid;
|
|
bool bRequestForTaskTimedOut;
|
|
bool bDynamicObjectTask;
|
|
int32 PrecomputedVisibilityTaskIndex;
|
|
int32 VolumetricLightmapTaskIndex;
|
|
bool bStaticShadowDepthMapTask;
|
|
bool bMeshAreaLightDataTask;
|
|
bool bVolumeDataTask;
|
|
|
|
const double RequestTimeStart = FPlatformTime::Seconds();
|
|
FStaticLightingMapping* Mapping = ThreadGetNextMapping(
|
|
ThreadStatistics,
|
|
TaskGuid,
|
|
DefaultRequestForTaskTimeout,
|
|
bRequestForTaskTimedOut,
|
|
bDynamicObjectTask,
|
|
PrecomputedVisibilityTaskIndex,
|
|
VolumetricLightmapTaskIndex,
|
|
bStaticShadowDepthMapTask,
|
|
bMeshAreaLightDataTask,
|
|
bVolumeDataTask);
|
|
|
|
const double RequestTimeEnd = FPlatformTime::Seconds();
|
|
ThreadStatistics.RequestTime += RequestTimeEnd - RequestTimeStart;
|
|
if (Mapping)
|
|
{
|
|
const double MappingTimeStart = FPlatformTime::Seconds();
|
|
// Build the mapping's static lighting.
|
|
|
|
if(Mapping->GetTextureMapping())
|
|
{
|
|
check(!Mapping->GetVolumeMapping());
|
|
ProcessTextureMapping(Mapping->GetTextureMapping());
|
|
double MappingTimeEnd = FPlatformTime::Seconds();
|
|
ThreadStatistics.TextureMappingTime += MappingTimeEnd - MappingTimeStart;
|
|
ThreadStatistics.NumTextureMappings++;
|
|
}
|
|
}
|
|
else if (bDynamicObjectTask)
|
|
{
|
|
BeginCalculateVolumeSamples();
|
|
|
|
// If we didn't generate any samples then we can end the task
|
|
if (!IsDebugMode() && NumVolumeSampleTasksOutstanding <= 0)
|
|
{
|
|
FLightmassSwarm* Swarm = GetExporter().GetSwarm();
|
|
Swarm->TaskCompleted(PrecomputedVolumeLightingGuid);
|
|
}
|
|
}
|
|
else if (PrecomputedVisibilityTaskIndex >= 0)
|
|
{
|
|
CalculatePrecomputedVisibility(PrecomputedVisibilityTaskIndex);
|
|
}
|
|
else if (VolumetricLightmapTaskIndex >= 0)
|
|
{
|
|
CalculateAdaptiveVolumetricLightmap(VolumetricLightmapTaskIndex);
|
|
}
|
|
else if (bMeshAreaLightDataTask)
|
|
{
|
|
FPlatformAtomics::InterlockedExchange(&bShouldExportMeshAreaLightData, true);
|
|
}
|
|
else if (bVolumeDataTask)
|
|
{
|
|
BeginCalculateVolumeDistanceField();
|
|
}
|
|
else if (bStaticShadowDepthMapTask)
|
|
{
|
|
CalculateStaticShadowDepthMap(TaskGuid);
|
|
}
|
|
else
|
|
{
|
|
if (!bSignaledMappingsComplete && NumOutstandingVolumeDataLayers <= 0)
|
|
{
|
|
bSignaledMappingsComplete = true;
|
|
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_Processing0, ThreadIndex ) );
|
|
}
|
|
|
|
FCacheIndirectTaskDescription* NextCacheTask = CacheIndirectLightingTasks.Pop();
|
|
|
|
if (NextCacheTask)
|
|
{
|
|
//UE_LOG(LogLightmass, Warning, TEXT("Thread %u picked up Cache Indirect task for %u"), ThreadIndex, NextCacheTask->TextureMapping->Guid.D);
|
|
ProcessCacheIndirectLightingTask(NextCacheTask, false);
|
|
NextCacheTask->TextureMapping->CompletedCacheIndirectLightingTasks.Push(NextCacheTask);
|
|
FPlatformAtomics::InterlockedDecrement(&NextCacheTask->TextureMapping->NumOutstandingCacheTasks);
|
|
}
|
|
|
|
FInterpolateIndirectTaskDescription* NextInterpolateTask = InterpolateIndirectLightingTasks.Pop();
|
|
|
|
if (NextInterpolateTask)
|
|
{
|
|
//UE_LOG(LogLightmass, Warning, TEXT("Thread %u picked up Interpolate indirect task for %u"), ThreadIndex, NextInterpolateTask->TextureMapping->Guid.D);
|
|
ProcessInterpolateTask(NextInterpolateTask, false);
|
|
NextInterpolateTask->TextureMapping->CompletedInterpolationTasks.Push(NextInterpolateTask);
|
|
FPlatformAtomics::InterlockedDecrement(&NextInterpolateTask->TextureMapping->NumOutstandingInterpolationTasks);
|
|
}
|
|
|
|
ProcessVolumetricLightmapTaskIfAvailable();
|
|
|
|
if (NumVolumeSampleTasksOutstanding > 0)
|
|
{
|
|
const int32 TaskIndex = FPlatformAtomics::InterlockedIncrement(&NextVolumeSampleTaskIndex);
|
|
|
|
if (TaskIndex < VolumeSampleTasks.Num())
|
|
{
|
|
ProcessVolumeSamplesTask(VolumeSampleTasks[TaskIndex]);
|
|
const int32 NumTasksRemaining = FPlatformAtomics::InterlockedDecrement(&NumVolumeSampleTasksOutstanding);
|
|
|
|
if (NumTasksRemaining == 0)
|
|
{
|
|
FPlatformAtomics::InterlockedExchange(&bShouldExportVolumeSampleData, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!NextCacheTask
|
|
&& !NextInterpolateTask
|
|
&& NumVolumeSampleTasksOutstanding <= 0
|
|
&& NumOutstandingVolumeDataLayers <= 0)
|
|
{
|
|
if (TasksInProgressThatWillNeedHelp <= 0 && !bRequestForTaskTimedOut)
|
|
{
|
|
// All mappings have been processed, so end this thread.
|
|
bIsDone = true;
|
|
}
|
|
else
|
|
{
|
|
FPlatformProcess::Sleep(.001f);
|
|
IdleTime += FPlatformTime::Seconds() - StartLoopTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: Main thread shouldn't be running this anymore.
|
|
check( !bIsMainThread );
|
|
}
|
|
ThreadStatistics.TotalTime += FPlatformTime::Seconds() - ThreadTimeStart;
|
|
FPlatformAtomics::InterlockedIncrement(&GStatistics.NumThreadsFinished);
|
|
}
|
|
|
|
/**
|
|
* Applies the static lighting to the mappings in the list, and clears the list.
|
|
* Also reports back to Unreal after each mapping has been exported.
|
|
* @param LightingSystem - Reference to the static lighting system
|
|
*/
|
|
template<typename StaticLightingDataType>
|
|
void TCompleteStaticLightingList<StaticLightingDataType>::ApplyAndClear(FStaticLightingSystem& LightingSystem)
|
|
{
|
|
while(FirstElement)
|
|
{
|
|
// Atomically read the complete list and clear the shared head pointer.
|
|
TList<StaticLightingDataType>* LocalFirstElement;
|
|
TList<StaticLightingDataType>* CurrentElement;
|
|
uint32 ElementCount = 0;
|
|
|
|
do { LocalFirstElement = FirstElement; }
|
|
while(FPlatformAtomics::InterlockedCompareExchangePointer((void**)&FirstElement,NULL,LocalFirstElement) != LocalFirstElement);
|
|
|
|
// Traverse the local list, count the number of entries, and find the minimum guid
|
|
TList<StaticLightingDataType>* PreviousElement = NULL;
|
|
TList<StaticLightingDataType>* MinimumElementLink = NULL;
|
|
TList<StaticLightingDataType>* MinimumElement = NULL;
|
|
|
|
CurrentElement = LocalFirstElement;
|
|
MinimumElement = CurrentElement;
|
|
FGuid MinimumGuid = MinimumElement->Element.Mapping->Guid;
|
|
|
|
while(CurrentElement)
|
|
{
|
|
ElementCount++;
|
|
if (CurrentElement->Element.Mapping->Guid < MinimumGuid)
|
|
{
|
|
MinimumGuid = CurrentElement->Element.Mapping->Guid;
|
|
MinimumElementLink = PreviousElement;
|
|
MinimumElement = CurrentElement;
|
|
}
|
|
PreviousElement = CurrentElement;
|
|
CurrentElement = CurrentElement->Next;
|
|
}
|
|
// Slice and dice the list to put the minimum at the head before we continue
|
|
if (MinimumElementLink != NULL)
|
|
{
|
|
MinimumElementLink->Next = MinimumElement->Next;
|
|
MinimumElement->Next = LocalFirstElement;
|
|
LocalFirstElement = MinimumElement;
|
|
}
|
|
|
|
// Traverse the local list and export
|
|
CurrentElement = LocalFirstElement;
|
|
|
|
// Start exporting, planning to put everything into one file
|
|
bool bUseUniqueChannel = true;
|
|
if (LightingSystem.GetExporter().BeginExportResults(CurrentElement->Element, ElementCount) >= 0)
|
|
{
|
|
// We opened a group channel, export all mappings out together
|
|
bUseUniqueChannel = false;
|
|
}
|
|
|
|
const double ExportTimeStart = FPlatformTime::Seconds();
|
|
while(CurrentElement)
|
|
{
|
|
// write back to Unreal
|
|
LightingSystem.GetExporter().ExportResults(CurrentElement->Element, bUseUniqueChannel);
|
|
|
|
// Update the corresponding statistics depending on whether we're exporting in parallel to the worker threads or not.
|
|
bool bIsRunningInParallel = GStatistics.NumThreadsFinished < (GStatistics.NumThreads-1);
|
|
if ( bIsRunningInParallel )
|
|
{
|
|
GStatistics.ThreadStatistics.ExportTime += FPlatformTime::Seconds() - ExportTimeStart;
|
|
}
|
|
else
|
|
{
|
|
static bool bFirst = true;
|
|
if ( bFirst )
|
|
{
|
|
bFirst = false;
|
|
GSwarm->SendMessage( NSwarm::FTimingMessage( NSwarm::PROGSTATE_ExportingResults, -1 ) );
|
|
}
|
|
GStatistics.ExtraExportTime += FPlatformTime::Seconds() - ExportTimeStart;
|
|
}
|
|
GStatistics.NumExportedMappings++;
|
|
|
|
// Move to the next element
|
|
CurrentElement = CurrentElement->Next;
|
|
}
|
|
|
|
// If we didn't use unique channels, close up the group channel now
|
|
if (!bUseUniqueChannel)
|
|
{
|
|
LightingSystem.GetExporter().EndExportResults();
|
|
}
|
|
|
|
// Traverse again, cleaning up and notifying swarm
|
|
FLightmassSwarm* Swarm = LightingSystem.GetExporter().GetSwarm();
|
|
CurrentElement = LocalFirstElement;
|
|
while(CurrentElement)
|
|
{
|
|
// Tell Swarm the task is complete (if we're not in debugging mode).
|
|
if ( !LightingSystem.IsDebugMode() )
|
|
{
|
|
Swarm->TaskCompleted( CurrentElement->Element.Mapping->Guid );
|
|
}
|
|
|
|
// Delete this link and advance to the next.
|
|
TList<StaticLightingDataType>* NextElement = CurrentElement->Next;
|
|
delete CurrentElement;
|
|
CurrentElement = NextElement;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename DataType>
|
|
void TCompleteTaskList<DataType>::ApplyAndClear(FStaticLightingSystem& LightingSystem)
|
|
{
|
|
while(this->FirstElement)
|
|
{
|
|
// Atomically read the complete list and clear the shared head pointer.
|
|
TList<DataType>* LocalFirstElement;
|
|
TList<DataType>* CurrentElement;
|
|
uint32 ElementCount = 0;
|
|
|
|
do { LocalFirstElement = this->FirstElement; }
|
|
while(FPlatformAtomics::InterlockedCompareExchangePointer((void**)&this->FirstElement,NULL,LocalFirstElement) != LocalFirstElement);
|
|
|
|
// Traverse the local list, count the number of entries, and find the minimum guid
|
|
TList<DataType>* PreviousElement = NULL;
|
|
TList<DataType>* MinimumElementLink = NULL;
|
|
TList<DataType>* MinimumElement = NULL;
|
|
|
|
CurrentElement = LocalFirstElement;
|
|
MinimumElement = CurrentElement;
|
|
FGuid MinimumGuid = MinimumElement->Element.Guid;
|
|
|
|
while(CurrentElement)
|
|
{
|
|
ElementCount++;
|
|
if (CurrentElement->Element.Guid < MinimumGuid)
|
|
{
|
|
MinimumGuid = CurrentElement->Element.Guid;
|
|
MinimumElementLink = PreviousElement;
|
|
MinimumElement = CurrentElement;
|
|
}
|
|
PreviousElement = CurrentElement;
|
|
CurrentElement = CurrentElement->Next;
|
|
}
|
|
// Slice and dice the list to put the minimum at the head before we continue
|
|
if (MinimumElementLink != NULL)
|
|
{
|
|
MinimumElementLink->Next = MinimumElement->Next;
|
|
MinimumElement->Next = LocalFirstElement;
|
|
LocalFirstElement = MinimumElement;
|
|
}
|
|
|
|
// Traverse the local list and export
|
|
CurrentElement = LocalFirstElement;
|
|
|
|
const double ExportTimeStart = FPlatformTime::Seconds();
|
|
while(CurrentElement)
|
|
{
|
|
// write back to Unreal
|
|
LightingSystem.GetExporter().ExportResults(CurrentElement->Element);
|
|
|
|
// Move to the next element
|
|
CurrentElement = CurrentElement->Next;
|
|
}
|
|
|
|
// Traverse again, cleaning up and notifying swarm
|
|
FLightmassSwarm* Swarm = LightingSystem.GetExporter().GetSwarm();
|
|
CurrentElement = LocalFirstElement;
|
|
while(CurrentElement)
|
|
{
|
|
// Tell Swarm the task is complete (if we're not in debugging mode).
|
|
if ( !LightingSystem.IsDebugMode() )
|
|
{
|
|
Swarm->TaskCompleted( CurrentElement->Element.Guid );
|
|
}
|
|
|
|
// Delete this link and advance to the next.
|
|
TList<DataType>* NextElement = CurrentElement->Next;
|
|
delete CurrentElement;
|
|
CurrentElement = NextElement;
|
|
}
|
|
}
|
|
}
|
|
|
|
class FStoredLightingSample
|
|
{
|
|
public:
|
|
FLinearColor IncomingRadiance;
|
|
FVector4 WorldSpaceDirection;
|
|
};
|
|
|
|
class FSampleCollector
|
|
{
|
|
public:
|
|
|
|
inline void SetOcclusion(float InOcclusion)
|
|
{}
|
|
|
|
inline void AddIncomingRadiance(const FLinearColor& IncomingRadiance, float Weight, const FVector4& TangentSpaceDirection, const FVector4& WorldSpaceDirection)
|
|
{
|
|
if (FLinearColorUtils::LinearRGBToXYZ(IncomingRadiance * Weight).G > DELTA)
|
|
{
|
|
FStoredLightingSample NewSample;
|
|
NewSample.IncomingRadiance = IncomingRadiance * Weight;
|
|
NewSample.WorldSpaceDirection = WorldSpaceDirection;
|
|
Samples.Add(NewSample);
|
|
}
|
|
}
|
|
|
|
bool AreFloatsValid() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
FSampleCollector operator+( const FSampleCollector& Other ) const
|
|
{
|
|
FSampleCollector NewCollector;
|
|
NewCollector.Samples = Samples;
|
|
NewCollector.Samples.Append(Other.Samples);
|
|
NewCollector.EnvironmentSamples = EnvironmentSamples;
|
|
NewCollector.EnvironmentSamples.Append(Other.EnvironmentSamples);
|
|
return NewCollector;
|
|
}
|
|
|
|
TArray<FStoredLightingSample> Samples;
|
|
TArray<FStoredLightingSample> EnvironmentSamples;
|
|
};
|
|
|
|
void FStaticLightingSystem::CalculateStaticShadowDepthMap(FGuid LightGuid)
|
|
{
|
|
const FLight* Light = Scene.FindLightByGuid(LightGuid);
|
|
check(Light);
|
|
const FDirectionalLight* DirectionalLight = Light->GetDirectionalLight();
|
|
const FSpotLight* SpotLight = Light->GetSpotLight();
|
|
const FPointLight* PointLight = Light->GetPointLight();
|
|
check(DirectionalLight || SpotLight || PointLight);
|
|
const float ClampedResolutionScale = FMath::Clamp(Light->ShadowResolutionScale, .125f, 8.0f);
|
|
|
|
const double StartTime = FPlatformTime::Seconds();
|
|
|
|
FStaticLightingMappingContext Context(NULL, *this);
|
|
FStaticShadowDepthMap* ShadowDepthMap = new FStaticShadowDepthMap();
|
|
|
|
if (DirectionalLight)
|
|
{
|
|
FVector4 XAxis, YAxis;
|
|
DirectionalLight->Direction.FindBestAxisVectors3(XAxis, YAxis);
|
|
// Create a coordinate system for the dominant directional light, with the z axis corresponding to the light's direction
|
|
ShadowDepthMap->WorldToLight = FBasisVectorMatrix(XAxis, YAxis, DirectionalLight->Direction, FVector4(0,0,0));
|
|
|
|
FBoxSphereBounds ImportanceVolume = GetImportanceBounds().SphereRadius > 0.0f ? GetImportanceBounds() : FBoxSphereBounds(AggregateMesh->GetBounds());
|
|
const FBox LightSpaceImportanceBounds = ImportanceVolume.GetBox().TransformBy(ShadowDepthMap->WorldToLight);
|
|
|
|
ShadowDepthMap->ShadowMapSizeX = FMath::TruncToInt(FMath::Max(LightSpaceImportanceBounds.GetExtent().X * 2.0f * ClampedResolutionScale / ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceX, 4.0f));
|
|
ShadowDepthMap->ShadowMapSizeX = ShadowDepthMap->ShadowMapSizeX == appTruncErrorCode ? INT_MAX : ShadowDepthMap->ShadowMapSizeX;
|
|
ShadowDepthMap->ShadowMapSizeY = FMath::TruncToInt(FMath::Max(LightSpaceImportanceBounds.GetExtent().Y * 2.0f * ClampedResolutionScale / ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceY, 4.0f));
|
|
ShadowDepthMap->ShadowMapSizeY = ShadowDepthMap->ShadowMapSizeY == appTruncErrorCode ? INT_MAX : ShadowDepthMap->ShadowMapSizeY;
|
|
|
|
// Clamp the number of dominant shadow samples generated if necessary while maintaining aspect ratio
|
|
if ((uint64)ShadowDepthMap->ShadowMapSizeX * (uint64)ShadowDepthMap->ShadowMapSizeY > (uint64)ShadowSettings.StaticShadowDepthMapMaxSamples)
|
|
{
|
|
const float AspectRatio = ShadowDepthMap->ShadowMapSizeX / (float)ShadowDepthMap->ShadowMapSizeY;
|
|
ShadowDepthMap->ShadowMapSizeY = FMath::TruncToInt(FMath::Sqrt(ShadowSettings.StaticShadowDepthMapMaxSamples / AspectRatio));
|
|
ShadowDepthMap->ShadowMapSizeX = FMath::TruncToInt(ShadowSettings.StaticShadowDepthMapMaxSamples / ShadowDepthMap->ShadowMapSizeY);
|
|
}
|
|
|
|
// Allocate the shadow map
|
|
ShadowDepthMap->ShadowMap.Empty(ShadowDepthMap->ShadowMapSizeX * ShadowDepthMap->ShadowMapSizeY);
|
|
ShadowDepthMap->ShadowMap.AddZeroed(ShadowDepthMap->ShadowMapSizeX * ShadowDepthMap->ShadowMapSizeY);
|
|
|
|
{
|
|
const float InvDistanceRange = 1.0f / (LightSpaceImportanceBounds.Max.Z - LightSpaceImportanceBounds.Min.Z);
|
|
const FMatrix LightToWorld = ShadowDepthMap->WorldToLight.InverseFast();
|
|
|
|
for (int32 Y = 0; Y < ShadowDepthMap->ShadowMapSizeY; Y++)
|
|
{
|
|
for (int32 X = 0; X < ShadowDepthMap->ShadowMapSizeX; X++)
|
|
{
|
|
float MaxSampleDistance = 0.0f;
|
|
// Super sample each cell
|
|
for (int32 SubSampleY = 0; SubSampleY < ShadowSettings.StaticShadowDepthMapSuperSampleFactor; SubSampleY++)
|
|
{
|
|
const float YFraction = (Y + SubSampleY / (float)ShadowSettings.StaticShadowDepthMapSuperSampleFactor) / (float)(ShadowDepthMap->ShadowMapSizeY - 1);
|
|
for (int32 SubSampleX = 0; SubSampleX < ShadowSettings.StaticShadowDepthMapSuperSampleFactor; SubSampleX++)
|
|
{
|
|
const float XFraction = (X + SubSampleX / (float)ShadowSettings.StaticShadowDepthMapSuperSampleFactor) / (float)(ShadowDepthMap->ShadowMapSizeX - 1);
|
|
// Construct a ray in light space along the direction of the light, starting at the minimum light space Z going to the maximum.
|
|
const FVector4 LightSpaceStartPosition(
|
|
LightSpaceImportanceBounds.Min.X + XFraction * (LightSpaceImportanceBounds.Max.X - LightSpaceImportanceBounds.Min.X),
|
|
LightSpaceImportanceBounds.Min.Y + YFraction * (LightSpaceImportanceBounds.Max.Y - LightSpaceImportanceBounds.Min.Y),
|
|
LightSpaceImportanceBounds.Min.Z);
|
|
const FVector4 LightSpaceEndPosition(LightSpaceStartPosition.X, LightSpaceStartPosition.Y, LightSpaceImportanceBounds.Max.Z);
|
|
// Transform the ray into world space in order to trace against the world space aggregate mesh
|
|
const FVector4 WorldSpaceStartPosition = LightToWorld.TransformPosition(LightSpaceStartPosition);
|
|
const FVector4 WorldSpaceEndPosition = LightToWorld.TransformPosition(LightSpaceEndPosition);
|
|
const FLightRay LightRay(
|
|
WorldSpaceStartPosition,
|
|
WorldSpaceEndPosition,
|
|
NULL,
|
|
NULL,
|
|
// We are tracing from the light instead of to the light,
|
|
// So flip sidedness so that backface culling matches up with tracing to the light
|
|
LIGHTRAY_FLIP_SIDEDNESS
|
|
);
|
|
|
|
FLightRayIntersection Intersection;
|
|
AggregateMesh->IntersectLightRay(LightRay, true, false, true, Context.RayCache, Intersection);
|
|
|
|
if (Intersection.bIntersects)
|
|
{
|
|
// Use the maximum distance of all super samples for each cell, to get a conservative shadow map
|
|
MaxSampleDistance = FMath::Max(MaxSampleDistance, (Intersection.IntersectionVertex.WorldPosition - WorldSpaceStartPosition).Size3());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MaxSampleDistance == 0.0f)
|
|
{
|
|
MaxSampleDistance = LightSpaceImportanceBounds.Max.Z - LightSpaceImportanceBounds.Min.Z;
|
|
}
|
|
|
|
ShadowDepthMap->ShadowMap[Y * ShadowDepthMap->ShadowMapSizeX + X] = FStaticShadowDepthMapSample(FFloat16(MaxSampleDistance * InvDistanceRange));
|
|
}
|
|
}
|
|
}
|
|
|
|
ShadowDepthMap->WorldToLight *= FTranslationMatrix(-LightSpaceImportanceBounds.Min)
|
|
* FScaleMatrix(FVector(1.0f) / (LightSpaceImportanceBounds.Max - LightSpaceImportanceBounds.Min));
|
|
|
|
FScopeLock Lock(&CompletedStaticShadowDepthMapsSync);
|
|
CompletedStaticShadowDepthMaps.Add(DirectionalLight, ShadowDepthMap);
|
|
}
|
|
else if (SpotLight)
|
|
{
|
|
FVector4 XAxis, YAxis;
|
|
SpotLight->Direction.FindBestAxisVectors3(XAxis, YAxis);
|
|
// Create a coordinate system for the spot light, with the z axis corresponding to the light's direction, and translated to the light's origin
|
|
ShadowDepthMap->WorldToLight = FTranslationMatrix(-SpotLight->Position)
|
|
* FBasisVectorMatrix(XAxis, YAxis, SpotLight->Direction, FVector4(0,0,0));
|
|
|
|
// Distance from the light's direction axis to the edge of the cone at the radius of the light
|
|
const float HalfCrossSectionLength = SpotLight->Radius * FMath::Tan(SpotLight->OuterConeAngle * (float)PI / 180.0f);
|
|
|
|
const FVector4 LightSpaceImportanceBoundMin = FVector4(-HalfCrossSectionLength, -HalfCrossSectionLength, 0);
|
|
const FVector4 LightSpaceImportanceBoundMax = FVector4(HalfCrossSectionLength, HalfCrossSectionLength, SpotLight->Radius);
|
|
|
|
ShadowDepthMap->ShadowMapSizeX = FMath::TruncToInt(FMath::Max(HalfCrossSectionLength * ClampedResolutionScale / ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceX, 4.0f));
|
|
ShadowDepthMap->ShadowMapSizeX = ShadowDepthMap->ShadowMapSizeX == appTruncErrorCode ? INT_MAX : ShadowDepthMap->ShadowMapSizeX;
|
|
ShadowDepthMap->ShadowMapSizeY = ShadowDepthMap->ShadowMapSizeX;
|
|
|
|
// Clamp the number of dominant shadow samples generated if necessary while maintaining aspect ratio
|
|
if ((uint64)ShadowDepthMap->ShadowMapSizeX * (uint64)ShadowDepthMap->ShadowMapSizeY > (uint64)ShadowSettings.StaticShadowDepthMapMaxSamples)
|
|
{
|
|
const float AspectRatio = ShadowDepthMap->ShadowMapSizeX / (float)ShadowDepthMap->ShadowMapSizeY;
|
|
ShadowDepthMap->ShadowMapSizeY = FMath::TruncToInt(FMath::Sqrt(ShadowSettings.StaticShadowDepthMapMaxSamples / AspectRatio));
|
|
ShadowDepthMap->ShadowMapSizeX = FMath::TruncToInt(ShadowSettings.StaticShadowDepthMapMaxSamples / ShadowDepthMap->ShadowMapSizeY);
|
|
}
|
|
|
|
ShadowDepthMap->ShadowMap.Empty(ShadowDepthMap->ShadowMapSizeX * ShadowDepthMap->ShadowMapSizeY);
|
|
ShadowDepthMap->ShadowMap.AddZeroed(ShadowDepthMap->ShadowMapSizeX * ShadowDepthMap->ShadowMapSizeY);
|
|
|
|
// Calculate the maximum possible distance for quantization
|
|
const float MaxPossibleDistance = LightSpaceImportanceBoundMax.Z - LightSpaceImportanceBoundMin.Z;
|
|
const FMatrix LightToWorld = ShadowDepthMap->WorldToLight.InverseFast();
|
|
const FBoxSphereBounds ImportanceVolume = GetImportanceBounds().SphereRadius > 0.0f ? GetImportanceBounds() : FBoxSphereBounds(AggregateMesh->GetBounds());
|
|
|
|
for (int32 Y = 0; Y < ShadowDepthMap->ShadowMapSizeY; Y++)
|
|
{
|
|
for (int32 X = 0; X < ShadowDepthMap->ShadowMapSizeX; X++)
|
|
{
|
|
float MaxSampleDistance = 0.0f;
|
|
// Super sample each cell
|
|
for (int32 SubSampleY = 0; SubSampleY < ShadowSettings.StaticShadowDepthMapSuperSampleFactor; SubSampleY++)
|
|
{
|
|
const float YFraction = (Y + SubSampleY / (float)ShadowSettings.StaticShadowDepthMapSuperSampleFactor) / (float)(ShadowDepthMap->ShadowMapSizeY - 1);
|
|
for (int32 SubSampleX = 0; SubSampleX < ShadowSettings.StaticShadowDepthMapSuperSampleFactor; SubSampleX++)
|
|
{
|
|
const float XFraction = (X + SubSampleX / (float)ShadowSettings.StaticShadowDepthMapSuperSampleFactor) / (float)(ShadowDepthMap->ShadowMapSizeX - 1);
|
|
// Construct a ray in light space along the direction of the light, starting at the light and going to the maximum light space Z.
|
|
const FVector4 LightSpaceStartPosition(0,0,0);
|
|
const FVector4 LightSpaceEndPosition(
|
|
LightSpaceImportanceBoundMin.X + XFraction * (LightSpaceImportanceBoundMax.X - LightSpaceImportanceBoundMin.X),
|
|
LightSpaceImportanceBoundMin.Y + YFraction * (LightSpaceImportanceBoundMax.Y - LightSpaceImportanceBoundMin.Y),
|
|
LightSpaceImportanceBoundMax.Z);
|
|
// Transform the ray into world space in order to trace against the world space aggregate mesh
|
|
const FVector4 WorldSpaceStartPosition = LightToWorld.TransformPosition(LightSpaceStartPosition);
|
|
const FVector4 WorldSpaceEndPosition = LightToWorld.TransformPosition(LightSpaceEndPosition);
|
|
const FLightRay LightRay(
|
|
WorldSpaceStartPosition,
|
|
WorldSpaceEndPosition,
|
|
NULL,
|
|
NULL,
|
|
// We are tracing from the light instead of to the light,
|
|
// So flip sidedness so that backface culling matches up with tracing to the light
|
|
LIGHTRAY_FLIP_SIDEDNESS
|
|
);
|
|
|
|
FLightRayIntersection Intersection;
|
|
AggregateMesh->IntersectLightRay(LightRay, true, false, true, Context.RayCache, Intersection);
|
|
|
|
if (Intersection.bIntersects)
|
|
{
|
|
const FVector4 LightSpaceIntersectPosition = ShadowDepthMap->WorldToLight.TransformPosition(Intersection.IntersectionVertex.WorldPosition);
|
|
// Use the maximum distance of all super samples for each cell, to get a conservative shadow map
|
|
MaxSampleDistance = FMath::Max(MaxSampleDistance, LightSpaceIntersectPosition.Z);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MaxSampleDistance == 0.0f)
|
|
{
|
|
MaxSampleDistance = MaxPossibleDistance;
|
|
}
|
|
|
|
ShadowDepthMap->ShadowMap[Y * ShadowDepthMap->ShadowMapSizeX + X] = FStaticShadowDepthMapSample(FFloat16(MaxSampleDistance / MaxPossibleDistance));
|
|
}
|
|
}
|
|
|
|
ShadowDepthMap->WorldToLight *=
|
|
// Perspective projection sized to the spotlight cone
|
|
FPerspectiveMatrix(SpotLight->OuterConeAngle * (float)PI / 180.0f, 1, 1, 0, SpotLight->Radius)
|
|
// Convert from NDC to texture space, normalize Z
|
|
* FMatrix(
|
|
FPlane(.5f, 0, 0, 0),
|
|
FPlane(0, .5f, 0, 0),
|
|
FPlane(0, 0, 1.0f / LightSpaceImportanceBoundMax.Z, 0),
|
|
FPlane(.5f, .5f, 0, 1));
|
|
|
|
FScopeLock Lock(&CompletedStaticShadowDepthMapsSync);
|
|
CompletedStaticShadowDepthMaps.Add(SpotLight, ShadowDepthMap);
|
|
}
|
|
else if (PointLight)
|
|
{
|
|
ShadowDepthMap->ShadowMapSizeX = FMath::TruncToInt(FMath::Max(PointLight->Radius * 4 * ClampedResolutionScale / ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceX, 4.0f));
|
|
ShadowDepthMap->ShadowMapSizeX = ShadowDepthMap->ShadowMapSizeX == appTruncErrorCode ? INT_MAX : ShadowDepthMap->ShadowMapSizeX;
|
|
ShadowDepthMap->ShadowMapSizeY = ShadowDepthMap->ShadowMapSizeX;
|
|
|
|
// Clamp the number of dominant shadow samples generated if necessary while maintaining aspect ratio
|
|
if ((uint64)ShadowDepthMap->ShadowMapSizeX * (uint64)ShadowDepthMap->ShadowMapSizeY > (uint64)ShadowSettings.StaticShadowDepthMapMaxSamples)
|
|
{
|
|
const float AspectRatio = ShadowDepthMap->ShadowMapSizeX / (float)ShadowDepthMap->ShadowMapSizeY;
|
|
ShadowDepthMap->ShadowMapSizeY = FMath::TruncToInt(FMath::Sqrt(ShadowSettings.StaticShadowDepthMapMaxSamples / AspectRatio));
|
|
ShadowDepthMap->ShadowMapSizeX = FMath::TruncToInt(ShadowSettings.StaticShadowDepthMapMaxSamples / ShadowDepthMap->ShadowMapSizeY);
|
|
}
|
|
|
|
// Allocate the shadow map
|
|
ShadowDepthMap->ShadowMap.Empty(ShadowDepthMap->ShadowMapSizeX * ShadowDepthMap->ShadowMapSizeY);
|
|
ShadowDepthMap->ShadowMap.AddZeroed(ShadowDepthMap->ShadowMapSizeX * ShadowDepthMap->ShadowMapSizeY);
|
|
|
|
ShadowDepthMap->WorldToLight = FMatrix::Identity;
|
|
|
|
for (int32 Y = 0; Y < ShadowDepthMap->ShadowMapSizeY; Y++)
|
|
{
|
|
for (int32 X = 0; X < ShadowDepthMap->ShadowMapSizeX; X++)
|
|
{
|
|
float MaxSampleDistance = 0.0f;
|
|
// Super sample each cell
|
|
for (int32 SubSampleY = 0; SubSampleY < ShadowSettings.StaticShadowDepthMapSuperSampleFactor; SubSampleY++)
|
|
{
|
|
const float YFraction = (Y + SubSampleY / (float)ShadowSettings.StaticShadowDepthMapSuperSampleFactor) / (float)(ShadowDepthMap->ShadowMapSizeY - 1);
|
|
const float Phi = YFraction * PI;
|
|
const float SinPhi = FMath::Sin(Phi);
|
|
|
|
for (int32 SubSampleX = 0; SubSampleX < ShadowSettings.StaticShadowDepthMapSuperSampleFactor; SubSampleX++)
|
|
{
|
|
const float XFraction = (X + SubSampleX / (float)ShadowSettings.StaticShadowDepthMapSuperSampleFactor) / (float)(ShadowDepthMap->ShadowMapSizeX - 1);
|
|
const float Theta = XFraction * 2 * PI;
|
|
const FVector Direction(FMath::Cos(Theta) * SinPhi, FMath::Sin(Theta) * SinPhi, FMath::Cos(Phi));
|
|
|
|
const FVector4 WorldSpaceStartPosition = PointLight->Position;
|
|
const FVector4 WorldSpaceEndPosition = PointLight->Position + Direction * PointLight->Radius;
|
|
const FLightRay LightRay(
|
|
WorldSpaceStartPosition,
|
|
WorldSpaceEndPosition,
|
|
NULL,
|
|
NULL,
|
|
// We are tracing from the light instead of to the light,
|
|
// So flip sidedness so that backface culling matches up with tracing to the light
|
|
LIGHTRAY_FLIP_SIDEDNESS
|
|
);
|
|
|
|
FLightRayIntersection Intersection;
|
|
AggregateMesh->IntersectLightRay(LightRay, true, false, true, Context.RayCache, Intersection);
|
|
|
|
if (Intersection.bIntersects)
|
|
{
|
|
// Use the maximum distance of all super samples for each cell, to get a conservative shadow map
|
|
MaxSampleDistance = FMath::Max(MaxSampleDistance, (Intersection.IntersectionVertex.WorldPosition - PointLight->Position).Size3());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MaxSampleDistance == 0.0f)
|
|
{
|
|
MaxSampleDistance = PointLight->Radius;
|
|
}
|
|
|
|
ShadowDepthMap->ShadowMap[Y * ShadowDepthMap->ShadowMapSizeX + X] = FStaticShadowDepthMapSample(FFloat16(MaxSampleDistance / PointLight->Radius));
|
|
}
|
|
}
|
|
|
|
FScopeLock Lock(&CompletedStaticShadowDepthMapsSync);
|
|
CompletedStaticShadowDepthMaps.Add(PointLight, ShadowDepthMap);
|
|
}
|
|
|
|
const float NewTime = FPlatformTime::Seconds() - StartTime;
|
|
Context.Stats.StaticShadowDepthMapThreadTime = NewTime;
|
|
Context.Stats.MaxStaticShadowDepthMapThreadTime = NewTime;
|
|
}
|
|
|
|
/**
|
|
* Calculates shadowing for a given mapping surface point and light.
|
|
* @param Mapping - The mapping the point comes from.
|
|
* @param WorldSurfacePoint - The point to check shadowing at.
|
|
* @param Light - The light to check shadowing from.
|
|
* @param CoherentRayCache - The calling thread's collision cache.
|
|
* @return true if the surface point is shadowed from the light.
|
|
*/
|
|
bool FStaticLightingSystem::CalculatePointShadowing(
|
|
const FStaticLightingMapping* Mapping,
|
|
const FVector4& WorldSurfacePoint,
|
|
const FLight* Light,
|
|
FStaticLightingMappingContext& MappingContext,
|
|
bool bDebugThisSample
|
|
) const
|
|
{
|
|
if(Light->GetSkyLight())
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Treat points which the light doesn't affect as shadowed to avoid the costly ray check.
|
|
if(!Light->AffectsBounds(FBoxSphereBounds(WorldSurfacePoint,FVector4(0,0,0,0),0)))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Check for visibility between the point and the light.
|
|
bool bIsShadowed = false;
|
|
if ((Light->LightFlags & GI_LIGHT_CASTSHADOWS) && (Light->LightFlags & GI_LIGHT_CASTSTATICSHADOWS))
|
|
{
|
|
// TODO find best point on light to shadow from
|
|
// Construct a line segment between the light and the surface point.
|
|
const FVector4 LightPosition = FVector4(Light->Position.X, Light->Position.Y, Light->Position.Z, 0);
|
|
const FVector4 LightVector = LightPosition - WorldSurfacePoint * Light->Position.W;
|
|
const FLightRay LightRay(
|
|
WorldSurfacePoint + LightVector.GetSafeNormal() * SceneConstants.VisibilityRayOffsetDistance,
|
|
WorldSurfacePoint + LightVector,
|
|
Mapping,
|
|
Light
|
|
);
|
|
|
|
// Check the line segment for intersection with the static lighting meshes.
|
|
FLightRayIntersection Intersection;
|
|
AggregateMesh->IntersectLightRay(LightRay, false, false, true, MappingContext.RayCache, Intersection);
|
|
bIsShadowed = Intersection.bIntersects;
|
|
|
|
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
|
|
if (bDebugThisSample)
|
|
{
|
|
FDebugStaticLightingRay DebugRay(LightRay.Start, LightRay.End, bIsShadowed != 0);
|
|
if (bIsShadowed)
|
|
{
|
|
DebugRay.End = Intersection.IntersectionVertex.WorldPosition;
|
|
}
|
|
MappingContext.DebugOutput->ShadowRays.Add(DebugRay);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return bIsShadowed;
|
|
}
|
|
}
|
|
|
|
/** Calculates area shadowing from a light for the given vertex. */
|
|
int32 FStaticLightingSystem::CalculatePointAreaShadowing(
|
|
const FStaticLightingMapping* Mapping,
|
|
const FStaticLightingVertex& Vertex,
|
|
int32 ElementIndex,
|
|
float SampleRadius,
|
|
const FLight* Light,
|
|
FStaticLightingMappingContext& MappingContext,
|
|
FLMRandomStream& RandomStream,
|
|
FLinearColor& UnnormalizedTransmission,
|
|
const TArray<FLightSurfaceSample>& LightPositionSamples,
|
|
bool bDebugThisSample
|
|
) const
|
|
{
|
|
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
|
|
if (bDebugThisSample)
|
|
{
|
|
int32 TempBreak = 0;
|
|
}
|
|
#endif
|
|
|
|
UnnormalizedTransmission = FLinearColor::Black;
|
|
// Treat points which the light doesn't affect as shadowed to avoid the costly ray check.
|
|
if( !Light->AffectsBounds(FBoxSphereBounds(Vertex.WorldPosition,FVector4(0,0,0),0)))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Check for visibility between the point and the light
|
|
if ((Light->LightFlags & GI_LIGHT_CASTSHADOWS) && (Light->LightFlags & GI_LIGHT_CASTSTATICSHADOWS))
|
|
{
|
|
MappingContext.Stats.NumDirectLightingShadowRays += LightPositionSamples.Num();
|
|
const bool bIsTwoSided = Mapping->Mesh->IsTwoSided(ElementIndex);
|
|
int32 UnShadowedRays = 0;
|
|
|
|
// Integrate over the surface of the light using monte carlo integration
|
|
// Note that we are making the approximation that the BRDF and the Light's emission are equal in all of these directions and therefore are not in the integrand
|
|
for(int32 RayIndex = 0; RayIndex < LightPositionSamples.Num(); RayIndex++)
|
|
{
|
|
FLightSurfaceSample CurrentSample = LightPositionSamples[RayIndex];
|
|
// Allow the light to modify the surface position for this receiving position
|
|
Light->ValidateSurfaceSample(Vertex.WorldPosition, CurrentSample);
|
|
|
|
// Construct a line segment between the light and the surface point.
|
|
const FVector4 LightVector = CurrentSample.Position - Vertex.WorldPosition;
|
|
FVector4 SampleOffset(0,0,0);
|
|
if (GeneralSettings.bAccountForTexelSize)
|
|
{
|
|
/*
|
|
//@todo - the rays cross over on the way to the light and mess up penumbra shapes.
|
|
//@todo - need to use more than texel size, otherwise BSP generates lots of texels that become half shadowed at corners
|
|
SampleOffset = Vertex.WorldTangentX * LightPositionSamples(RayIndex).DiskPosition.X * SampleRadius * SceneConstants.VisibilityTangentOffsetSampleRadiusScale
|
|
+ Vertex.WorldTangentY * LightPositionSamples(RayIndex).DiskPosition.Y * SampleRadius * SceneConstants.VisibilityTangentOffsetSampleRadiusScale;
|
|
*/
|
|
}
|
|
|
|
FVector4 NormalForOffset = Vertex.WorldTangentZ;
|
|
// Flip the normal used for offsetting the start of the ray for two sided materials if a flipped normal would be closer to the light.
|
|
// This prevents incorrect shadowing where using the frontface normal would cause the ray to start inside a nearby object.
|
|
if (bIsTwoSided && Dot3(-NormalForOffset, LightVector) > Dot3(NormalForOffset, LightVector))
|
|
{
|
|
NormalForOffset = -NormalForOffset;
|
|
}
|
|
|
|
const FLightRay LightRay(
|
|
// Offset the start of the ray by some fraction along the direction of the ray and some fraction along the vertex normal.
|
|
Vertex.WorldPosition
|
|
+ LightVector.GetSafeNormal() * SceneConstants.VisibilityRayOffsetDistance
|
|
+ NormalForOffset * SampleRadius * SceneConstants.VisibilityNormalOffsetSampleRadiusScale
|
|
+ SampleOffset,
|
|
Vertex.WorldPosition + LightVector,
|
|
Mapping,
|
|
Light
|
|
);
|
|
|
|
// Check the line segment for intersection with the static lighting meshes.
|
|
FLightRayIntersection Intersection;
|
|
//@todo - change this back to request boolean visibility once transmission is supported with boolean visibility ray intersections
|
|
AggregateMesh->IntersectLightRay(LightRay, true, true, true, MappingContext.RayCache, Intersection);
|
|
|
|
if (!Intersection.bIntersects)
|
|
{
|
|
UnnormalizedTransmission += Intersection.Transmission;
|
|
UnShadowedRays++;
|
|
}
|
|
|
|
#if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
|
|
if (bDebugThisSample)
|
|
{
|
|
FDebugStaticLightingRay DebugRay(LightRay.Start, LightRay.End, Intersection.bIntersects);
|
|
if (Intersection.bIntersects)
|
|
{
|
|
DebugRay.End = Intersection.IntersectionVertex.WorldPosition;
|
|
}
|
|
MappingContext.DebugOutput->ShadowRays.Add(DebugRay);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return UnShadowedRays;
|
|
}
|
|
UnnormalizedTransmission = FLinearColor::White * LightPositionSamples.Num();
|
|
return LightPositionSamples.Num();
|
|
}
|
|
|
|
/** Calculates the lighting contribution of a light to a mapping vertex. */
|
|
FGatheredLightSample FStaticLightingSystem::CalculatePointLighting(
|
|
const FStaticLightingMapping* Mapping,
|
|
const FStaticLightingVertex& Vertex,
|
|
int32 ElementIndex,
|
|
const FLight* Light,
|
|
const FLinearColor& InLightIntensity,
|
|
const FLinearColor& InTransmission
|
|
) const
|
|
{
|
|
// don't do sky lights here
|
|
if (Light->GetSkyLight() == NULL)
|
|
{
|
|
// Calculate the direction from the vertex to the light.
|
|
const FVector4 WorldLightVector = Light->GetDirectLightingDirection(Vertex.WorldPosition, Vertex.WorldTangentZ);
|
|
|
|
// Transform the light vector to tangent space.
|
|
const FVector4 TangentLightVector =
|
|
FVector4(
|
|
Dot3(WorldLightVector, Vertex.WorldTangentX),
|
|
Dot3(WorldLightVector, Vertex.WorldTangentY),
|
|
Dot3(WorldLightVector, Vertex.WorldTangentZ),
|
|
0
|
|
).GetSafeNormal();
|
|
|
|
// Compute the incident lighting of the light on the vertex.
|
|
const FLinearColor LightIntensity = InLightIntensity * InTransmission;
|
|
|
|
// Compute the light-map sample for the front-face of the vertex.
|
|
FGatheredLightSample FrontFaceSample = FGatheredLightSampleUtil::PointLightWorldSpace<2>(LightIntensity, TangentLightVector, WorldLightVector.GetSafeNormal());
|
|
|
|
if (Mapping->Mesh->UsesTwoSidedLighting(ElementIndex))
|
|
{
|
|
const FVector4 BackFaceTangentLightVector =
|
|
FVector4(
|
|
Dot3(WorldLightVector, -Vertex.WorldTangentX),
|
|
Dot3(WorldLightVector, -Vertex.WorldTangentY),
|
|
Dot3(WorldLightVector, -Vertex.WorldTangentZ),
|
|
0
|
|
).GetSafeNormal();
|
|
const FGatheredLightSample BackFaceSample = FGatheredLightSampleUtil::PointLightWorldSpace<2>(LightIntensity, BackFaceTangentLightVector, -WorldLightVector.GetSafeNormal());
|
|
// Average front and back face lighting
|
|
return (FrontFaceSample + BackFaceSample) * .5f;
|
|
}
|
|
else
|
|
{
|
|
return FrontFaceSample;
|
|
}
|
|
}
|
|
|
|
return FGatheredLightSample();
|
|
}
|
|
|
|
/** Returns a light sample that represents the material attribute specified by MaterialSettings.ViewMaterialAttribute at the intersection. */
|
|
FGatheredLightSample FStaticLightingSystem::GetVisualizedMaterialAttribute(const FStaticLightingMapping* Mapping, const FLightRayIntersection& Intersection) const
|
|
{
|
|
FGatheredLightSample MaterialSample;
|
|
if (Intersection.bIntersects && Intersection.Mapping == Mapping)
|
|
{
|
|
// The ray intersected an opaque surface, we can visualize anything that opaque materials store
|
|
//@todo - Currently can't visualize emissive on translucent materials
|
|
if (MaterialSettings.ViewMaterialAttribute == VMA_Emissive)
|
|
{
|
|
FLinearColor Emissive = FLinearColor::Black;
|
|
if (Intersection.Mesh->IsEmissive(Intersection.ElementIndex))
|
|
{
|
|
Emissive = Intersection.Mesh->EvaluateEmissive(Intersection.IntersectionVertex.TextureCoordinates[0], Intersection.ElementIndex);
|
|
}
|
|
MaterialSample = FGatheredLightSampleUtil::AmbientLight<2>(Emissive);
|
|
}
|
|
else if (MaterialSettings.ViewMaterialAttribute == VMA_Diffuse)
|
|
{
|
|
const FLinearColor Diffuse = Intersection.Mesh->EvaluateDiffuse(Intersection.IntersectionVertex.TextureCoordinates[0], Intersection.ElementIndex);
|
|
MaterialSample = FGatheredLightSampleUtil::AmbientLight<2>(Diffuse);
|
|
}
|
|
else if (MaterialSettings.ViewMaterialAttribute == VMA_Normal)
|
|
{
|
|
const FVector4 Normal = Intersection.Mesh->EvaluateNormal(Intersection.IntersectionVertex.TextureCoordinates[0], Intersection.ElementIndex);
|
|
|
|
FLinearColor NormalColor;
|
|
NormalColor.R = Normal.X * 0.5f + 0.5f;
|
|
NormalColor.G = Normal.Y * 0.5f + 0.5f;
|
|
NormalColor.B = Normal.Z * 0.5f + 0.5f;
|
|
NormalColor.A = 1.0f;
|
|
|
|
MaterialSample = FGatheredLightSampleUtil::AmbientLight<2>(NormalColor);
|
|
}
|
|
}
|
|
else if (MaterialSettings.ViewMaterialAttribute != VMA_Transmission)
|
|
{
|
|
// The ray didn't intersect an opaque surface and we're not visualizing transmission
|
|
MaterialSample = FGatheredLightSampleUtil::AmbientLight<2>(FLinearColor::Black);
|
|
}
|
|
|
|
if (MaterialSettings.ViewMaterialAttribute == VMA_Transmission)
|
|
{
|
|
// Visualizing transmission, replace the light sample with the transmission picked up along the ray
|
|
MaterialSample = FGatheredLightSampleUtil::AmbientLight<2>(Intersection.Transmission);
|
|
}
|
|
return MaterialSample;
|
|
}
|
|
|
|
/**
|
|
* Checks if a light is behind a triangle.
|
|
* @param TrianglePoint - Any point on the triangle.
|
|
* @param TriangleNormal - The (not necessarily normalized) triangle surface normal.
|
|
* @param Light - The light to classify.
|
|
* @return true if the light is behind the triangle.
|
|
*/
|
|
bool IsLightBehindSurface(const FVector4& TrianglePoint, const FVector4& TriangleNormal, const FLight* Light)
|
|
{
|
|
const bool bIsSkyLight = Light->GetSkyLight() != NULL;
|
|
if (!bIsSkyLight)
|
|
{
|
|
// Calculate the direction from the triangle to the light.
|
|
const FVector4 LightPosition = FVector4(Light->Position.X, Light->Position.Y, Light->Position.Z, 0);
|
|
const FVector4 WorldLightVector = LightPosition - TrianglePoint * Light->Position.W;
|
|
|
|
// Check if the light is in front of the triangle.
|
|
const float Dot = Dot3(WorldLightVector, TriangleNormal);
|
|
return Dot < 0.0f;
|
|
}
|
|
else
|
|
{
|
|
// Sky lights are always in front of a surface.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Culls lights that are behind a triangle.
|
|
* @param bTwoSidedMaterial - true if the triangle has a two-sided material. If so, lights behind the surface are not culled.
|
|
* @param TrianglePoint - Any point on the triangle.
|
|
* @param TriangleNormal - The (not necessarily normalized) triangle surface normal.
|
|
* @param Lights - The lights to cull.
|
|
* @return A map from Lights index to a boolean which is true if the light is in front of the triangle.
|
|
*/
|
|
TBitArray<> CullBackfacingLights(bool bTwoSidedMaterial,const FVector4& TrianglePoint,const FVector4& TriangleNormal,const TArray<FLight*>& Lights)
|
|
{
|
|
if(!bTwoSidedMaterial)
|
|
{
|
|
TBitArray<> Result(false,Lights.Num());
|
|
for(int32 LightIndex = 0;LightIndex < Lights.Num();LightIndex++)
|
|
{
|
|
Result[LightIndex] = !IsLightBehindSurface(TrianglePoint,TriangleNormal,Lights[LightIndex]);
|
|
}
|
|
return Result;
|
|
}
|
|
else
|
|
{
|
|
return TBitArray<>(true,Lights.Num());
|
|
}
|
|
}
|
|
|
|
|
|
} //namespace Lightmass
|
|
|