You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden ============================ MAJOR FEATURES & CHANGES ============================ Change 3503468 by Marcus.Wassmer Fix merge conflicts Change 3537059 by Ben.Marsh Fixing case of iOS directories, pt1 Change 3537060 by Ben.Marsh Fixing case of iOS directories, pt2 Change 3608300 by Chris.Bunner Added post process material to preview compile cache set to allow post process volume preview scene improvements. Change 3608302 by Chris.Bunner Fixed decal lifetime fading. #jira UE-48400 Change 3608303 by Chris.Bunner Updated default WritesAllPixels input to ignore dithering (as intended, was disabled due to isues at the time). Fixed material instances returning their local data when not overridden. #jira UE-48254 Change 3608455 by Mark.Satterthwaite Enabling WorldPositionOffset requires disabling fast-math on Metal because the manually specified FMA's are not respected if one or more arguments is a literal which then leads to very different compiler optimisation between the depth-only shader and the base-pass shader. This change will only affect the way Metal compiles shaders. #jira UE-47372 Change 3608462 by Rolando.Caloca DR - Cloth vertex buffers no longer generate dummy vertices Copy from 3608349 and 3608407 Change 3608491 by Rolando.Caloca DR - hlsl - Fix crash when type was not found Change 3608513 by Rolando.Caloca DR - Default to real uniform buffers for Vulkan SM4 & SM5 Change 3608794 by Mark.Satterthwaite Implement SV_DepthLessEqual (maybe right?) for Metal - seems to work in the ParallaxOcclusionMapping test map. #jira UE-47614 Change 3608929 by Mark.Satterthwaite Fix ambiguous expression compile error. Change 3608991 by Mark.Satterthwaite Fix a dumb bug when parsing the Metal compiler version that breaks Metal shader PCH generation on HFS+ volumes. Change 3609090 by Uriel.Doyon StaticMeshComponent and LandscapeComponent now register AO material mask and sky occlusion texture in the texture streamer. Changing the current lighting scenario now triggers an update of the texture streamer, and a refresh of lighting data for instanced static meshes. Added an option to the "liststreamingtextures" named UNKOWNREF allowing to inspect texture without references in the texture streamer. BUILDMATERIALTEXTURESTREAMINGDATA now rebuild every shader in memory and mark for save those with different data. MipBias now behaves the same way in shipping than in other builds. Fixed texture resolution logic for editor tooltips and in game stats. Change 3609659 by Richard.Wallis Remove Eye Adaption Pixel Shader Workaround for macOS 10.11 (El Cap) Nividia. #jira UE-48642 Change 3610552 by Mark.Satterthwaite Optimise the constant-propagation pass in hlslcc by using a hash-table to reduce the cost of looking up the existing constant assignment instructions. The get_assignment_entry drops from 25% of runtime to 2.2% of runtime on a 4.2Ghz Quad i7 2017 iMac. Change 3610662 by Rolando.Caloca DR - hlsl - Fix for rwstructured buffer Fix for floats printed as ints Change 3610830 by Michael.Lentine ByteAddressBuffer does not have a subtype. Change 3610869 by Rolando.Caloca DR - hlsl - Fix disambiguation between 1.r and 1.0.r Change 3610982 by Mark.Satterthwaite Use the correct code to dump Metal shader text for debugging at runtime. Change 3610996 by Rolando.Caloca DR - hlsl - Actual fix for 0.r Change 3611312 by Rolando.Caloca DR - Integrate: Improve performance of bokeh depth of field. * Fewer instances with more work (higher quad count) per instance. * Improves performance on RX 480 in the Infiltrator demo by 0.37 ms at 1080p and 0.50 ms at 1440p (average frame time over the beginning of the demo, including the hallway confrontation between the guard and the infiltrator, where heavy DOF is used). * Similar optimizations may be possible for other systems that perform similar "instanced draws of quads" (e.g. virtual texture page table updates, lens blur, and velocity scatter). Change 3611345 by Mark.Satterthwaite Missed the hash-table destructor in previous change. Change 3611372 by Rolando.Caloca DR - vk - New barrier/layout api Change 3611445 by Mark.Satterthwaite Fix stupid bugs in MetalBackend's LoadRWBuffer helper function where the wrong type was being used - won't fix problems in the LinearTexture case though. Change 3611686 by Mark.Satterthwaite Remove the sampler from the Metal Linear Texture SRV path as for reasons so far unknown it doesn?╟╓t work with the light grid culling. #jira UE-48881 Change 3611743 by Mark.Satterthwaite Implement early depth test for Metal - it is implemented such that manual specification of the SV_Depth* outputs will elide the early_fragment_test qualifier as Metal does not permit both at present. Change 3611746 by Mark.Satterthwaite Use early fragment tests implicitly unless we perform a direct resource write or use discard - explicit depth writes always disable early fragment tests as Metal doesn?╟╓t allow both. This should better match D3D driver behaviour. Change 3611756 by Mark.Satterthwaite Missed a header file in last commit. Change 3611836 by Mark.Satterthwaite Fixed the use of Metal?╟╓s capture manager so that it doesn?╟╓t capture more frames than intended. Change 3611843 by Mark.Satterthwaite Tidy up the handling of when to increment the frame count for the Metal capture manager. Change 3612279 by Michael.Lentine Move FP16 Math to Public so that it can be included as part of platform which is where the other float/half defines happen. Change 3612595 by Rolando.Caloca DR - hlslcc - Rebuilt with CL 3611345 Change 3612665 by Rolando.Caloca DR - Make cubemap mip barrier consistent with HZB mip barriers Change 3612758 by Daniel.Wright FColor usage comment Change 3612980 by Rolando.Caloca DR - hlsl - Do not overflow ints Change 3613068 by Rolando.Caloca DR - vk - Initial fix for transition validation warnings Change 3613115 by Daniel.Wright Volumetric lightmap voxels are now always cubes Bricks outside of any Lightmass Importance Volume will never be refined Change 3613124 by zachary.wilson Enabling Eye-Adaptation in TM-ShaderModels. Change 3613205 by Mark.Satterthwaite Fully disable linear textures in Metal - they simply aren't performant. Instead we'll have to use helper functions to dynamically type-cast appropriately within the shader. This is currently only configured for a handful of UAV types and will need to be extended. Change 3613208 by Mark.Satterthwaite Add code to MetalBackend to promote half types to float for math operations to avoid compiler errors. Change 3613354 by zachary.wilson Fixing up the Bloom_FFT map. Renaming to fit qa conventions, updating content and improving workflow. Change 3613409 by Rolando.Caloca DR - vk - Layout as part of descriptor writes Some access flag warning fixes Change 3613518 by Daniel.Wright Added 'Render Unbuilt Preview Shadows in game' rendering project setting and r.Shadow.UnbuiltPreviewInGame cvar Change 3613610 by Daniel.Wright Volumetric lightmap visualization sphere size is now a fraction of the corresponding brick world size Change 3613651 by Daniel.Wright [Copy] Fixed landscape in the Global Distance field on PS4. Multiple updates to a vertex buffer using BUF_Dynamic cause a race condition on PS4 with no assert. Also added shrinking for GDistanceFieldUploadData which saved 15Mb. Change 3613696 by Mark.Satterthwaite Add the Metal SRV format for Index buffers so that they can be properly type-cast inside the shader. Fixes recompute tangents with latest changes. Change 3613697 by Rolando.Caloca DR - vk - Fix missing layout Change 3613922 by Rolando.Caloca DR - vk - Some fixes for layout/transitions - Disable GSupportsDepthFetchDuringDepthTest on desktop as the deferred renderer is not copying the aux depth in the right spot and will be removed Change 3614009 by Mark.Satterthwaite TPS Approved: Integrating the MIT-licensed mtlpp C++ Metal wrapper from Nikolay Aleksiev which will slowly replace previous Metal API wrappers in MetalRHI. Change 3614015 by Mark.Satterthwaite Initial extensions to mtlpp: - Fixed over retention of alloc-init'd objects. - Added 10_13 & 11_0 availablity macros. - Started, but have not yet finished adding new Metal API function wrappers. Change 3614909 by Rolando.Caloca DR - Fix static analysis Change 3614916 by Michael.Lentine Add function to convert FP32 to FP16 Change 3614957 by Mark.Satterthwaite mtlpp declarations for macOS 10.13 & iOS 11 Metal features - no matching definitions yet. Change 3614995 by Mark.Satterthwaite Revert all changes to project config's from Rhino that should not have come back to Dev-Rendering, keeping only the solitary change to Metal shader standard necessary for ShowdownDemo. Change 3615035 by Rolando.Caloca DR - Generate mips using shader for HZB Change 3615561 by Rolando.Caloca DR - Fix deprecation warning Change 3615787 by Mark.Satterthwaite Only emit min. OS version specification into the Metal shader bytecode for macOS as we share shaders between iOS & tvOS and this option inhibts that. #jira UE-48919 Change 3616317 by Mark.Satterthwaite Make TonemapperConfBitmaskPC the proper size so we dn't attempt to access uninitialized memory. Change 3616357 by Mark.Satterthwaite And fix some compile errors... Change 3616473 by Rolando.Caloca DR - Render pass api minor changes Change 3616518 by Mark.Satterthwaite Fix a merge snafu where dead code was retained where it shouldn't be. #jira UE-48472 Change 3616706 by Rolando.Caloca DR - Vulkan fixes (integration from Vulkan working branch) - Fix for editor outline - Fix for profilegpu Change 3616770 by Rolando.Caloca DR - vk - Mark GIsGPUCrashed on device lost Change3616993by Daniel.Wright IndirectLightingCacheQuality respects VolumetricLightingMethod Change 3616996 by Daniel.Wright Volumetric Lightmap show flag is respected by Volumetric Fog Change 3616999 by Daniel.Wright Fixed ObjectRadius in Volume domain materials Change 3617777 by Rolando.Caloca DR - Fix static analysis warning Change 3617863 by Guillaume.Abadie PR #3875: Removed Duplicated "RHI" Module Dependency (Contributed by DavidNSilva) #jira UE-48159 Change 3618133 by Rolando.Caloca DR - vk - Set general layout for compute shader resources - Assume transitions to writable imply end render pass Change 3618292 by Michael.Lentine Add support for Expressions, Jump Statments, and Structs. Change 3618326 by Rolando.Caloca DR - vk - Fix transition flags Change 3618408 by Daniel.Wright Lightmass skylight solver improvements * Lightmass uses a filtered cubemap to represent the skylight instead of a 3rd order Spherical Harmonic. Directionality in shadowed areas is improved. Mip level is chosen based on the ray differential for anti-aliasing. * Multiple skylight and emissive bounces are now supported with a radiosity solver, controlled by NumSkyLightingBounces in Lightmass WorldSettings. More bounces results in longer build times, and the radiosity time is not distributable. * The mapping surface cache is now rasterized with supersampling, reduces incorrect darkness in corners * Combined direct lighting, photon irradiance, skylight radiosity and diffuse in the mapping surface cache so final gather rays only have to do one memory fetch, speeds up lighting builds by 7%. * Added support for Embree packet tracing although no solver algorithms use it yet Change 3618413 by Daniel.Wright Swarm hands out the most expensive tasks in roughly a round robin ordering among distribution agents. Lightmass processing of a single task is multithreaded, so ideally the most expensive tasks are evenly distributed among active agents. This has the biggest impact in small scenes with 10's of high resolution lightmaps, and with a distribution farm. Build time in one scene went from to 113s -> 47s. Change 3618439 by Mark.Satterthwaite Fix the assert in hlslcc when we have saturate(int) and the shader language spec. supports a native saturate intrinsic. Change 3618468 by Rolando.Caloca DR - vk - Fix copy to non render target surface Change 3618696 by Daniel.Wright Worked around Lightmass crash callstacks not getting reported back to the editor Change 3618779 by Mark.Satterthwaite mtlpp definitions for a few of the new calls & fixing the max. number of samplers it assumes. Change 3618789 by Daniel.Wright Added missing file Change3618816by Daniel.Wright Another missing file Change 3618855 by Rolando.Caloca DR - vk - Show user debug markers when using dump layers - Remove old defines Change 3618887 by Rolando.Caloca DR - Fix for missing transition to readable for blur widget. Was causing corruption on Vulkan. Change 3618999 by Mark.Satterthwaite Definitions for Metal's new CaptureManager & CaptureScope classes. Change 3619790 by Jian.Ru Add some debug info #jira UE-48710 Change 3619834 by Rolando.Caloca DR - vk - static analysis fix Change 3619952 by Rolando.Caloca DR - vk - Static analysis not smart enough... Change 3620191 by Jian.Ru Revert 3584245 to prevent focus stealing #jira UE-49044 Change 3620402 by Mark.Satterthwaite Remaining Metal definitions for mtlpp. Change 3620803 by Brian.Karis Removed faceting bug I introduced to Dither Opacity Mask. Removes the attempt to make opacity stack properly. Change 3620904 by Michael.Lentine Change the order of static and const Change 3620975 by Rolando.Caloca DR - Updated Vulkan headers to SDK 1.0.57.0 Change 3621026 by Rolando.Caloca DR - Remove unused type - Force recompile with new Vulkan headers Change 3621070 by Rolando.Caloca DR - glslang - Fix pdb option Change 3621157 by Arciel.Rekman Added files to cross-build glslang on Windows. (Edigrating //UE4/Main/...@3621127 to //UE4/Dev-Rendering/...) Change 3621194 by Rolando.Caloca DR - glslang - Update to 1.0.57.0 - Fix some tab/whitespace mismatch Change 3621225 by Rolando.Caloca DR - Revert glslang (Back out changelist 3621194) Change 3621254 by Mark.Satterthwaite Duplicate 3610656 and revert the incorrect merge from the Rhino task stream. Fixes EyeAdaptation on all clang platforms properly thanks to RCL. Change 3621261 by Mark.Satterthwaite Trivial FMetalStateCache optimisations - won't help much but equally they shouldn't hurt. Change 3621262 by Mark.Satterthwaite Correct the handling of MSAA target in Desktop Forward for iOS - now the problem is that iOS always creates an internal resolve target so which texture to bind depends on the shader parameter type. Not sure (yet) how best to solve that. Change 3621263 by Mark.Satterthwaite Don't mandate Mobile Metal for projects that have Metal MRT enabled. Change 3621301 by Rolando.Caloca DR - Unity build fix Change 3621349 by Mark.Satterthwaite Fix a bug in MetalBackend that was omitting the depth-output variable from the hlslcc signature if the semantic was SV_DepthLessEqual rather than SV_Depth. Change 3621546 by Uriel.Doyon Refactor of the texture 2D mip update logic to offload more work on the async thread. #jira UE-45332 #jira UE-45789 Change 3622210 by Rolando.Caloca DR - Do not store DDC data if static mesh failed to build #jira UE-48358 Change 3622349 by Arciel.Rekman Better build script for Linux glslang and a bugfix. (Edigrating CL 3622235 from //UE4/Main/... to //UE4/Dev-Rendering/...) Change 3622401 by Rolando.Caloca DR - vk - Integration - Support for r.Vulkan.ProfileCmdBuffers Change 3622506 by Rolando.Caloca DR - vk - Back out changelist 3622401 Change 3622521 by Mark.Satterthwaite Support disabling V-Sync in MetalRHI on macOS 10.13+. Change 3622910 by Rolando.Caloca DR - static analysis fix Change 3622964 by Mark.Satterthwaite Fix generation of .metallib on local Macs and exclude .metallib files from the pak - they must always be loaded from disk. #jira UE-48193 Change 3622986 by Mark.Satterthwaite A couple more trivial optimisations to MetalRHI for iOS: - Metal page size is 4k but only buffers under 512 bytes should go through set*Bytes on iOS to balance CPU cost. - On iOS the minimum buffer size should therefore be 1k and on Mac 4k as nothing else makes much sense. - No need to rebind uniform buffers if to the same slot - it just wastes cycles. Change 3623266 by Rolando.Caloca DR - Fix GL4 rendering #jira UE-49187 Change 3623377 by Daniel.Wright Volume materials applied to static meshes operate on the object's bounding sphere Change 3623427 by Mark.Satterthwaite Fix MetalViewport compile errors on Xode 8.3. #jira UE-49231 Change 3623443 by Daniel.Wright Fixed out of bounds crash in lightmass Change 3623751 by Daniel.Wright Volume materials on static meshes now voxelize the mesh's Object space bounding box Change 3625142 by Guillaume.Abadie PR #2992: Fixing aspect ratio issue of SceneCapture2D rendering in "Ortho" camera mode (Contributed by monsieurgustav) Change 3625983 by Jian.Ru Fix a LPV race condtion due to parallel RSM draw-call submission #jira UE-48247 Change 3626015 by Jian.Ru Small fix to 3625983 Change 3626294 by Michael.Trepka Copy of CL 3535792 and 3576637 Added support for changing monitor's display mode on Mac in fullscreen mode. This greatly improves performance on Retina screens when playing in resolutions lower than native. Fixed a problem with incorrect viewport size being set in windowed fullscreen in some cases. Also, slightly improved screen fades for fullscreen mode transitions on Mac. #jira UE-48018 Change 3626532 by Marcus.Wassmer Fix divide by 0 crash when GPU timing frequency not available for whatever reason. Change 3626548 by Ryan.Brucks KismetRenderingLibrary: Added EditorOnly function for creating static textures from Render Targets. Has options for Mip and Compression Settings Change 3626874 by Mark.Satterthwaite Fix Metal 2.0 compilation. Change 3626997 by Rolando.Caloca DR - vk - cis fix - Initial RGBA16 readback Change 3627016 by Mark.Satterthwaite Workaround more of Metal's unfortunate tendency to re-associate float mul/add/sub operations - this time from Metal's own standard-library. Change 3627040 by Brian.Karis Removed old rasterized deferred reflection env path. Removed reflection compute shader. Replaced with PS. Small perf gain. Change 3627055 by Mark.Satterthwaite No MSAA support on Intel Metal or iOS Desktop Forward for the moment as neitehr work and I don't want to have lots of crashes out in the wild until we have a solution. Change 3627057 by Mark.Satterthwaite Make SCW's directcompile not fall over with Metal when there are compilation errors. Change 3627083 by Mark.Satterthwaite Invalidate Metal shaders so QA testing picks up the most recent changes. Change 3627788 by Chris.Bunner [Duplicate, CL 3627751] - VisibleExpressions static switch value evaluation needs to handle reroute nodes rather than only verify first expression. Change 3627834 by Rolando.Caloca DR - cis fix Change 3627847 by Rolando.Caloca DR - 4th try to fix static analysis Change 3627877 by Guillaume.Abadie Works arround a HLSLCC bug in a SimpleComposure project's material where x != x does not work for an unknown reason yet. #jira UE-48063 Change 3628035 by Marcus.Wassmer Duplicate 3620990 Smarter scenecapture allocation behavior. Change 3628204 by Daniel.Wright Fixed denormalization scale on one of the 2nd SH band of volumetric lightmaps Change 3628217 by Mark.Satterthwaite Fix InfiltratorForward project defaults so that iOS will package. Change 3628515 by Arne.Schober DR - [UE-49213] - Fix case where HZB was not generated for SSR and SSAO when Occlusion culling was disabled. #RB Marcus.Wassmer Change 3628550 by Chris.Bunner Merge fixes. Change 3628597 by Chris.Bunner Merge fixes. Change 3628656 by Michael.Trepka One more workaround for a bug in StandardPlatformString.cpp. It doesn't handle %lf format correctly, parsing it as long double instead of ignoring the 'l' format sub-specifier. Change 3628685 by Daniel.Wright CPU interpolation of Volumetric Lightmaps for the mobile renderer. They use a scene cache based on interpolation position, since the precomputed lighting buffer for movable objects is recreated every frame. Change 3629094 by Ryan.Brucks Fixes to RenderTargetCreateStaticTexture2DEditorOnly with additional error checks #RB none Change 3629223 by Rolando.Caloca DR - Rollback //UE4/Dev-Rendering/Engine/Source/Runtime/VulkanRHI to changelist 3627847 Change 3629491 by Rolando.Caloca DR - Revert back to emulated uniform buffers on SM4/SM5 Change 3629663 by Daniel.Wright Fixed NaN when capsule shadow direction is derived from volumetric lightmap with completely black lighting Change 3629664 by Daniel.Wright Don't render dynamic indirect occlusion from mesh distance fields when operating on a movable skylight, since DFAO fills that role Change 3629708 by Rolando.Caloca DR - vk - Redo some changes from DevMobile 3601439 3604186 3606672 3617383 3617474 3617483 Change 3629770 by Mark.Satterthwaite Fix a mobile Metal shader compilation error when using the FMA workaround for "cross" which should only be applied if the min. Metal version is 1.2 (as FMA is not known to work prior to this). Change 3629793 by Daniel.Wright Fixed VolumetricLightmapDetailCellSize not being respected in small levels, causing too much volumetric lightmap density and memory Change 3629859 by Mark.Satterthwaite macOS 10.12 also had problems with MSAA in forward rendering - so only permit it to work on macOS 10.13 and above. Change 3630790 by Mark.Satterthwaite Move RHISupportsMSAA so that the Metal related complications for when it is viable can be hidden within. Change 3630990 by Rolando.Caloca DR - vk - Redid CL 3617437 (optimize number of Buffer Views, eg 165 to 58) Change 3631071 by Mark.Satterthwaite Fix a small gotcha in a change from Dev-Mobile: for MetalRHI we need to explicitly configure the ShaderCacheContext for the immediate/device context after initialising the shader-cache. #jira UE-49431 Change 3631076 by Rolando.Caloca DR - vk - Redo 3617574, reduce number of render pass objects created Change 3631250 by Mark.Satterthwaite Make another Metal warning a Verbose log instead as it isn't interesting unless you are me. Change 3631911 by Chris.Bunner Back out changelist 3628035. #jira UE-49364, UE-49365 Change 3632041 by Mark.Satterthwaite Fix cloth rendering on Metal - some of the data in FClothVertex is uint but we load it from a float buffer. This could be due to a bug in Metal's as_type<uint4>() or it could be that Xcode 9's compiler is now finally enforcing Metal's official flush-to-zero-on-load semantics for denorms - it isn't immediately obvious. #jira UE-49439 Change 3632261 by Brian.Karis SM4 fallback for reflection captures. Change 3632281 by Mark.Satterthwaite Fix an intermittent assert on startup when the AVFoundation movie player gets the QAGame TM-ShaderModels video ready to play prior to the rendering thread being back online when resizing the window. This is done by deferring the processing of AVFoundation events to the game-thread where it won't cause a threading violation. Change 3632382 by Rolando.Caloca DR - vk - Fix clang warning Change 3633338 by Chris.Bunner Static analysis/Linux compile fix. #jira UE-49502 Change 3633616 by Jian.Ru Force alpha to 0xff for functional UI screenshot tests #jira UE-48266 Change 3633818 by Daniel.Wright Better indirection texture clamping and asserts Change 3634319 by Mark.Satterthwaite Stop FVolumetricLightmapDataLayer ::Discard which is invoked by the Editor RHI during texture-upload from chucking the backing data when in the Editor - because if we do that then cooking will serialise an empty array. This was only apparent on Mac because Metal always invokes Discard on BulkDataInterfaces and D3D11 never does. #jira UE-49381 Change 3634613 by Rolando.Caloca DR - Call discard on bulk data for textures #jira UE-49533 Change 3634654 by Mark.Satterthwaite Fixes for broken iOS builds: - Fix RHIGetShaderLanguageVersion returning the wrong version for iOS Metal if the Mac version had already been queried - this has been wrong for a long while. - Remove the precise:: qualifier for Metal's fma intrinsic - it isn't necessary and breaks on older OSes. #jira UE-49381 Change 3634820 by Mark.Satterthwaite Change the hash-function for the preprocessed HLSL source in FMetalShaderOutputCooker to reduce risk of hash-collisions. Fixes one cause of UE-49381 and reveals an underlying driver bug on iOS 9 with runtime-compiled text shaders *only*. #jira UE-49381 Change 3634821 by Mark.Satterthwaite Force Metal shaders only to recompile by incrementing the format version. [CL 3635058 by Chris Bunner in Main branch]
2548 lines
116 KiB
C++
2548 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) :
|
|
FirstBounceCache(InSubjectMesh ? InSubjectMesh->BoundingBox : FBox::BuildAABB(FVector4(0,0,0), FVector4(HALF_WORLD_MAX)), InSystem, 1),
|
|
System(InSystem)
|
|
{}
|
|
|
|
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();
|
|
AllMappings.Reserve( NumMappings );
|
|
Meshes.Reserve( NumMeshInstances );
|
|
|
|
// Initialize Meshes, Mappings, AllMappings and AggregateMesh from the scene
|
|
UE_LOG(LogLightmass, Log, TEXT("Number of texture mappings: %d"), InScene.TextureLightingMappings.Num() );
|
|
for (int32 MappingIndex = 0; MappingIndex < InScene.TextureLightingMappings.Num(); MappingIndex++)
|
|
{
|
|
FStaticMeshStaticLightingTextureMapping* Mapping = &InScene.TextureLightingMappings[MappingIndex];
|
|
Mappings.Add(Mapping->Guid, Mapping);
|
|
AllMappings.Add(Mapping);
|
|
if (bDumpAllMappings)
|
|
{
|
|
UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(Mapping->Guid.ToString()));
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogLightmass, Log, TEXT("Number of fluid mappings: %d"), InScene.FluidMappings.Num());
|
|
for (int32 MappingIndex = 0; MappingIndex < InScene.FluidMappings.Num(); MappingIndex++)
|
|
{
|
|
FFluidSurfaceStaticLightingTextureMapping* Mapping = &InScene.FluidMappings[MappingIndex];
|
|
NumMeshes++;
|
|
NumVertices += Mapping->Mesh->NumVertices;
|
|
NumTriangles += Mapping->Mesh->NumTriangles;
|
|
Mappings.Add(Mapping->Guid, Mapping);
|
|
AllMappings.Add(Mapping);
|
|
if (bDumpAllMappings)
|
|
{
|
|
UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(Mapping->Guid.ToString()));
|
|
}
|
|
}
|
|
|
|
for (int32 MeshIndex = 0; MeshIndex < InScene.FluidMeshInstances.Num(); MeshIndex++)
|
|
{
|
|
Meshes.Add(&InScene.FluidMeshInstances[MeshIndex]);
|
|
}
|
|
|
|
UE_LOG(LogLightmass, Log, TEXT("Number of landscape mappings: %d"), InScene.LandscapeMappings.Num());
|
|
for (int32 MappingIndex = 0; MappingIndex < InScene.LandscapeMappings.Num(); MappingIndex++)
|
|
{
|
|
FLandscapeStaticLightingTextureMapping* Mapping = &InScene.LandscapeMappings[MappingIndex];
|
|
NumMeshes++;
|
|
NumVertices += Mapping->Mesh->NumVertices;
|
|
NumTriangles += Mapping->Mesh->NumTriangles;
|
|
Mappings.Add(Mapping->Guid, Mapping);
|
|
LandscapeMappings.Add(Mapping);
|
|
AllMappings.Add(Mapping);
|
|
if (bDumpAllMappings)
|
|
{
|
|
UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(Mapping->Guid.ToString()));
|
|
}
|
|
}
|
|
|
|
for (int32 MeshIndex = 0; MeshIndex < InScene.LandscapeMeshInstances.Num(); MeshIndex++)
|
|
{
|
|
Meshes.Add(&InScene.LandscapeMeshInstances[MeshIndex]);
|
|
}
|
|
|
|
UE_LOG(LogLightmass, Log, TEXT("Number of BSP mappings: %d"), InScene.BspMappings.Num() );
|
|
for( int32 MeshIdx=0; MeshIdx < InScene.BspMappings.Num(); ++MeshIdx )
|
|
{
|
|
FBSPSurfaceStaticLighting* BSPMapping = &InScene.BspMappings[MeshIdx];
|
|
Meshes.Add(BSPMapping);
|
|
NumMeshes++;
|
|
NumVertices += BSPMapping->NumVertices;
|
|
NumTriangles += BSPMapping->NumTriangles;
|
|
|
|
// add the BSP mappings light mapping object
|
|
AllMappings.Add(&BSPMapping->Mapping);
|
|
Mappings.Add(BSPMapping->Mapping.Guid, &BSPMapping->Mapping);
|
|
if (bDumpAllMappings)
|
|
{
|
|
UE_LOG(LogLightmass, Log, TEXT("\t%s"), *(BSPMapping->Mapping.Guid.ToString()));
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogLightmass, Log, TEXT("Number of 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)
|
|
{
|
|
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())
|
|
{
|
|
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)
|
|
{
|
|
if (CurrentElement->Element.Mapping->Guid == LightingSystem.GetDebugGuid())
|
|
{
|
|
// Send debug info back with the mapping task that is being debugged
|
|
LightingSystem.GetExporter().ExportDebugInfo(LightingSystem.DebugOutput);
|
|
}
|
|
// 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;
|
|
}
|
|
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;
|
|
}
|
|
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
|
|
|